musora-content-services 2.174.0 → 2.176.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/decisions/2026-08-13-behltp-306-add-plan-rank-and-tier-key.md +24 -0
- package/.idea/codeStyles/Project.xml +61 -0
- package/.idea/codeStyles/codeStyleConfig.xml +5 -0
- package/.idea/dataSources/6549dc28-6466-4ea3-a66b-7750cfeae4e7/storage_v2/_src_/schema/information_schema.FNRwLQ.meta +2 -0
- package/.idea/dataSources/6549dc28-6466-4ea3-a66b-7750cfeae4e7/storage_v2/_src_/schema/mysql.osA4Bg.meta +2 -0
- package/.idea/dataSources/6549dc28-6466-4ea3-a66b-7750cfeae4e7/storage_v2/_src_/schema/performance_schema.kIw0nw.meta +2 -0
- package/.idea/dataSources/6549dc28-6466-4ea3-a66b-7750cfeae4e7/storage_v2/_src_/schema/sys.zb4BAA.meta +2 -0
- package/.idea/dataSources/6549dc28-6466-4ea3-a66b-7750cfeae4e7.xml +25767 -0
- package/.idea/dataSources/data_sources_history.xml +23 -0
- package/.idea/dataSources.local.xml +19 -0
- package/.idea/dataSources.xml +12 -0
- package/.idea/modules.xml +8 -0
- package/.idea/musora-content-services.iml +8 -0
- package/.idea/php.xml +19 -0
- package/.idea/prettier.xml +6 -0
- package/.idea/vcs.xml +6 -0
- package/.idea/workspace.xml +343 -0
- package/.yarn/install-state.gz +0 -0
- package/CHANGELOG.md +21 -0
- package/package.json +1 -1
- package/src/index.d.ts +10 -0
- package/src/index.js +10 -0
- package/src/services/offline/practices.ts +48 -5
- package/src/services/user/account.ts +8 -3
- package/src/services/user/memberships.ts +2 -0
- package/src/services/user/streakCalculator.ts +23 -2
- package/src/services/userActivity.js +146 -40
- package/src/version-info.js +1 -1
- package/.claude/settings.local.json +0 -60
|
@@ -3,31 +3,74 @@ import { Q } from '@nozbe/watermelondb'
|
|
|
3
3
|
import dayjs from 'dayjs'
|
|
4
4
|
import { globalConfig } from '../config'
|
|
5
5
|
import { calculateLongestStreaks } from '../userActivity.js'
|
|
6
|
+
import { getMonday } from '../dateUtils.js'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @param offlineTimestamp - Minimum `updated_at` epoch ms to include
|
|
9
10
|
* @param day
|
|
10
11
|
* @param options.day - Date in YYYY-MM-DD format, defaults to today
|
|
11
|
-
* @
|
|
12
|
+
* @param options.page - Page number, only applied when `limit` is set (default 1)
|
|
13
|
+
* @param options.limit - Max sessions to return
|
|
14
|
+
* @returns {Promise<{data: {practices: object[], practiceDuration: number, total: number, currentPage: number, totalPages: number}}>}
|
|
12
15
|
*/
|
|
13
16
|
export async function getPracticeSessionsOffline(
|
|
14
17
|
offlineTimestamp: number, {
|
|
15
|
-
day = dayjs().format('YYYY-MM-DD')
|
|
18
|
+
day = dayjs().format('YYYY-MM-DD'),
|
|
19
|
+
page = 1,
|
|
20
|
+
limit
|
|
21
|
+
}: { day?: string, page?: number, limit?: number } = {}
|
|
16
22
|
) {
|
|
17
23
|
|
|
18
24
|
const query = await db.practices.queryAll(
|
|
19
25
|
Q.where('date', day),
|
|
20
26
|
Q.sortBy('created_at', 'asc'))
|
|
21
|
-
const practices = query.data
|
|
22
27
|
|
|
23
|
-
|
|
28
|
+
return formatPracticeSessionDataOffline(query.data, page, limit)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param offlineTimestamp - Minimum `updated_at` epoch ms to include
|
|
33
|
+
* @param options.page - Page number, only applied when `limit` is set (default 1)
|
|
34
|
+
* @param options.limit - Max sessions to return
|
|
35
|
+
* @returns {Promise<{data: {practices: object[], practiceDuration: number, total: number, currentPage: number, totalPages: number}}>}
|
|
36
|
+
*/
|
|
37
|
+
export async function getWeeklyPracticeSessionsOffline(
|
|
38
|
+
offlineTimestamp: number, {
|
|
39
|
+
page = 1,
|
|
40
|
+
limit
|
|
41
|
+
}: { page?: number, limit?: number } = {}
|
|
42
|
+
) {
|
|
43
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
44
|
+
const startOfWeek = getMonday(new Date(), timeZone)
|
|
45
|
+
const weekDays = Array.from({ length: 7 }, (_, i) => startOfWeek.add(i, 'day').format('YYYY-MM-DD'))
|
|
46
|
+
|
|
47
|
+
const query = await db.practices.queryAll(
|
|
48
|
+
Q.where('date', Q.oneOf(weekDays)),
|
|
49
|
+
Q.sortBy('created_at', 'asc'))
|
|
50
|
+
|
|
51
|
+
return formatPracticeSessionDataOffline(query.data, page, limit)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function formatPracticeSessionDataOffline(practices: any[], page: number, limit?: number) {
|
|
55
|
+
if (!practices.length)
|
|
56
|
+
return { data: { practices: [], practiceDuration: 0, total: 0, currentPage: page, totalPages: 1 } }
|
|
24
57
|
|
|
25
58
|
const practiceDuration = Math.round(practices.reduce(
|
|
26
59
|
(total, practice) => total + (practice.duration_seconds || 0),
|
|
27
60
|
0
|
|
28
61
|
))
|
|
29
62
|
|
|
30
|
-
|
|
63
|
+
const pagedPractices = limit ? practices.slice((page - 1) * limit, page * limit) : practices
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
data: {
|
|
67
|
+
practices: pagedPractices,
|
|
68
|
+
practiceDuration,
|
|
69
|
+
total: practices.length,
|
|
70
|
+
currentPage: page,
|
|
71
|
+
totalPages: limit ? Math.ceil(practices.length / limit) : 1,
|
|
72
|
+
},
|
|
73
|
+
}
|
|
31
74
|
}
|
|
32
75
|
|
|
33
76
|
export async function otherStatsOffline(userId = globalConfig.sessionConfig.userId) {
|
|
@@ -115,18 +115,23 @@ export interface PendingAccountProps {
|
|
|
115
115
|
previousEmail?: string
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
// Not AccountSetupResponse -- this is the permanent shape for this endpoint.
|
|
119
|
+
export interface PendingAccountResponse {
|
|
120
|
+
user_id: number
|
|
121
|
+
}
|
|
122
|
+
|
|
118
123
|
/**
|
|
119
124
|
* @param {Object} props - The parameters for creating the pending account.
|
|
120
125
|
* @property {string} email - The email address for the account.
|
|
121
126
|
* @property {string} [revenuecatAppUserId] - The RevenueCat App User ID for MA environments.
|
|
122
127
|
* @property {string} [previousEmail] - The previous email address, if this account replaces an existing one.
|
|
123
128
|
*
|
|
124
|
-
* @returns {Promise<
|
|
129
|
+
* @returns {Promise<PendingAccountResponse>} - A promise that resolves when the pending account is created or an HttpError if the request fails.
|
|
125
130
|
* @throws {HttpError} - Throws an HttpError if the HTTP request fails.
|
|
126
131
|
*/
|
|
127
|
-
export async function createPendingAccount(props: PendingAccountProps): Promise<
|
|
132
|
+
export async function createPendingAccount(props: PendingAccountProps): Promise<PendingAccountResponse> {
|
|
128
133
|
const httpClient = new HttpClient(globalConfig.baseUrl)
|
|
129
|
-
return await httpClient.post<
|
|
134
|
+
return await httpClient.post<PendingAccountResponse>(
|
|
130
135
|
`/api/user-management-system/v1/accounts/pending`,
|
|
131
136
|
{
|
|
132
137
|
email: props.email,
|
|
@@ -75,6 +75,8 @@ export interface UpgradeProduct {
|
|
|
75
75
|
tier: 'plus' | 'basic' | '' // deprecated in favour of membership_level
|
|
76
76
|
membership_level: 'plus' | 'basic' | ''
|
|
77
77
|
plan_type: 'family' | 'duo' | 'solo'
|
|
78
|
+
tier_key: string
|
|
79
|
+
rank: number | null
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
export interface UpgradeOption {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import dayjs from 'dayjs'
|
|
1
2
|
import { db } from '../sync'
|
|
2
3
|
import { getStreaksAndMessage } from '../../services/userActivity.js'
|
|
3
4
|
|
|
@@ -5,8 +6,12 @@ export interface StreakData {
|
|
|
5
6
|
currentDailyStreak: number
|
|
6
7
|
currentWeeklyStreak: number
|
|
7
8
|
streakMessage: string
|
|
9
|
+
streakMessagePart1: string
|
|
10
|
+
streakMessagePart2: string
|
|
8
11
|
calculatedAt: number // timestamp
|
|
9
12
|
lastPracticeDate: string | null
|
|
13
|
+
currentWeekPracticeDays: number
|
|
14
|
+
todaysPracticeSeconds: number
|
|
10
15
|
}
|
|
11
16
|
export interface PracticeData {
|
|
12
17
|
[date: string]: Array<{
|
|
@@ -27,14 +32,25 @@ class StreakCalculator {
|
|
|
27
32
|
async recalculate(): Promise<StreakData> {
|
|
28
33
|
const allPractices = await this.fetchAllPractices()
|
|
29
34
|
|
|
30
|
-
const {
|
|
35
|
+
const {
|
|
36
|
+
currentDailyStreak,
|
|
37
|
+
currentWeeklyStreak,
|
|
38
|
+
streakMessage,
|
|
39
|
+
streakMessagePart1,
|
|
40
|
+
streakMessagePart2,
|
|
41
|
+
currentWeekPracticeDays,
|
|
42
|
+
} = getStreaksAndMessage(allPractices)
|
|
31
43
|
|
|
32
44
|
this.cache = {
|
|
33
45
|
currentDailyStreak: currentDailyStreak,
|
|
34
46
|
currentWeeklyStreak: currentWeeklyStreak,
|
|
35
47
|
streakMessage: streakMessage,
|
|
48
|
+
streakMessagePart1: streakMessagePart1,
|
|
49
|
+
streakMessagePart2: streakMessagePart2,
|
|
36
50
|
calculatedAt: Date.now(),
|
|
37
|
-
lastPracticeDate: this.getLastPracticeDate(allPractices)
|
|
51
|
+
lastPracticeDate: this.getLastPracticeDate(allPractices),
|
|
52
|
+
currentWeekPracticeDays: currentWeekPracticeDays,
|
|
53
|
+
todaysPracticeSeconds: this.getTodaysPracticeSeconds(allPractices),
|
|
38
54
|
}
|
|
39
55
|
return this.cache
|
|
40
56
|
}
|
|
@@ -59,6 +75,11 @@ class StreakCalculator {
|
|
|
59
75
|
const dates = Object.keys(practices).sort()
|
|
60
76
|
return dates.length > 0 ? dates[dates.length - 1] : null
|
|
61
77
|
}
|
|
78
|
+
|
|
79
|
+
private getTodaysPracticeSeconds(practices: PracticeData): number {
|
|
80
|
+
const today = dayjs().format('YYYY-MM-DD')
|
|
81
|
+
return (practices[today] || []).reduce((total, practice) => total + practice.duration_seconds, 0)
|
|
82
|
+
}
|
|
62
83
|
}
|
|
63
84
|
|
|
64
85
|
|
|
@@ -18,26 +18,40 @@ import { mapContentsThatWereLastProgressedFromMethod } from "./content-org/learn
|
|
|
18
18
|
const DAYS = ['M', 'T', 'W', 'T', 'F', 'S', 'S']
|
|
19
19
|
|
|
20
20
|
const streakMessages = {
|
|
21
|
-
startStreak: 'Start your streak by taking any lesson!',
|
|
22
|
-
restartStreak: 'Restart your streak by taking any lesson!',
|
|
21
|
+
startStreak: { part1: 'Start your streak by taking any lesson!', part2: '' },
|
|
22
|
+
restartStreak: { part1: 'Restart your streak by taking any lesson!', part2: '' },
|
|
23
23
|
|
|
24
24
|
// Messages when last active day is today
|
|
25
|
-
dailyStreak: (streak) =>
|
|
26
|
-
`Nice! You have ${getIndefiniteArticle(streak)} ${streak} day streak
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
`You have ${getIndefiniteArticle(streak)} ${streak}
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
dailyStreak: (streak) => ({
|
|
26
|
+
part1: `Nice! You have ${getIndefiniteArticle(streak)} ${streak} day streak!`,
|
|
27
|
+
part2: 'Way to keep it going!',
|
|
28
|
+
}),
|
|
29
|
+
dailyStreakShort: (streak) => ({
|
|
30
|
+
part1: `Nice! You have ${getIndefiniteArticle(streak)} ${streak} day streak!`,
|
|
31
|
+
part2: '',
|
|
32
|
+
}),
|
|
33
|
+
weeklyStreak: (streak) => ({
|
|
34
|
+
part1: `You have ${getIndefiniteArticle(streak)} ${streak} week streak!`,
|
|
35
|
+
part2: 'Way to keep up the momentum!',
|
|
36
|
+
}),
|
|
37
|
+
greatJobWeeklyStreak: (streak) => ({
|
|
38
|
+
part1: `Great job! You have ${getIndefiniteArticle(streak)} ${streak} week streak!`,
|
|
39
|
+
part2: 'Way to keep it going!',
|
|
40
|
+
}),
|
|
33
41
|
|
|
34
42
|
// Messages when last active day is NOT today
|
|
35
|
-
dailyStreakReminder: (streak) =>
|
|
36
|
-
`You have ${getIndefiniteArticle(streak)} ${streak} day streak
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
`You have ${getIndefiniteArticle(streak)} ${streak} week streak
|
|
43
|
+
dailyStreakReminder: (streak) => ({
|
|
44
|
+
part1: `You have ${getIndefiniteArticle(streak)} ${streak} day streak!`,
|
|
45
|
+
part2: 'Keep it going with any lesson or song!',
|
|
46
|
+
}),
|
|
47
|
+
weeklyStreakKeepUp: (streak) => ({
|
|
48
|
+
part1: `You have ${getIndefiniteArticle(streak)} ${streak} week streak!`,
|
|
49
|
+
part2: 'Keep up the momentum!',
|
|
50
|
+
}),
|
|
51
|
+
weeklyStreakReminder: (streak) => ({
|
|
52
|
+
part1: `You have ${getIndefiniteArticle(streak)} ${streak} week streak!`,
|
|
53
|
+
part2: 'Keep it going with any lesson or song!',
|
|
54
|
+
}),
|
|
41
55
|
}
|
|
42
56
|
|
|
43
57
|
function getIndefiniteArticle(streak) {
|
|
@@ -46,6 +60,10 @@ function getIndefiniteArticle(streak) {
|
|
|
46
60
|
: 'a'
|
|
47
61
|
}
|
|
48
62
|
|
|
63
|
+
function joinStreakMessageParts(part1, part2) {
|
|
64
|
+
return part2 ? `${part1} ${part2}` : part1
|
|
65
|
+
}
|
|
66
|
+
|
|
49
67
|
async function getUserPractices(userId) {
|
|
50
68
|
if (userId === globalConfig.sessionConfig.userId) {
|
|
51
69
|
return getOwnPractices()
|
|
@@ -118,7 +136,11 @@ export async function getUserWeeklyStats() {
|
|
|
118
136
|
data: {
|
|
119
137
|
dailyActiveStats: dailyStats,
|
|
120
138
|
streakMessage: streakData.streakMessage,
|
|
139
|
+
streakMessagePart1: streakData.streakMessagePart1,
|
|
140
|
+
streakMessagePart2: streakData.streakMessagePart2,
|
|
121
141
|
practices: weekPractices,
|
|
142
|
+
currentWeekPracticeDays: streakData.currentWeekPracticeDays,
|
|
143
|
+
todaysPracticeSeconds: streakData.todaysPracticeSeconds,
|
|
122
144
|
},
|
|
123
145
|
}
|
|
124
146
|
}
|
|
@@ -417,9 +439,14 @@ export async function restorePracticeSession(date) {
|
|
|
417
439
|
* @param {Object} params - Parameters for fetching practice sessions.
|
|
418
440
|
* @param {string} params.day - The date for which practice sessions should be retrieved (format: YYYY-MM-DD).
|
|
419
441
|
* @param {number} [params.userId=globalConfig.sessionConfig.userId] - Optional user ID to retrieve sessions for a specific user. Defaults to the logged-in user.
|
|
442
|
+
* @param {number} [params.page=1] - The page number, only applied when `limit` is set.
|
|
443
|
+
* @param {number} [params.limit] - Max sessions to return; omit to return all of the day's sessions unpaginated.
|
|
420
444
|
* @returns {Promise<Object>} - A promise that resolves to an object containing:
|
|
421
|
-
* - `practices`: An array of formatted practice session data.
|
|
422
|
-
* - `practiceDuration`: Total practice duration (in seconds) for the
|
|
445
|
+
* - `practices`: An array of formatted practice session data (paginated if `limit` is set).
|
|
446
|
+
* - `practiceDuration`: Total practice duration (in seconds) for the whole day, regardless of pagination.
|
|
447
|
+
* - `total`: Total number of sessions for the day, regardless of pagination.
|
|
448
|
+
* - `currentPage`: The page number returned
|
|
449
|
+
* - `totalPages`: Total number of pages for the day
|
|
423
450
|
*
|
|
424
451
|
* @example
|
|
425
452
|
* // Get practice sessions for the current user on a specific day
|
|
@@ -434,7 +461,7 @@ export async function restorePracticeSession(date) {
|
|
|
434
461
|
* .catch(error => console.error(error));
|
|
435
462
|
*/
|
|
436
463
|
export async function getPracticeSessions(params = {}, options = {}) {
|
|
437
|
-
const { day, userId = globalConfig.sessionConfig.userId } = params
|
|
464
|
+
const { day, userId = globalConfig.sessionConfig.userId, page = 1, limit } = params
|
|
438
465
|
|
|
439
466
|
let data
|
|
440
467
|
|
|
@@ -455,15 +482,66 @@ export async function getPracticeSessions(params = {}, options = {}) {
|
|
|
455
482
|
data = query.data
|
|
456
483
|
}
|
|
457
484
|
|
|
458
|
-
|
|
485
|
+
return formatPracticeSessionData(data, page, limit)
|
|
486
|
+
}
|
|
459
487
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
488
|
+
/**
|
|
489
|
+
* Retrieves and formats the current user's practice sessions for the current Monday-Sunday week
|
|
490
|
+
*
|
|
491
|
+
* @param {Object} [params={}] - Parameters for fetching the week's practice sessions.
|
|
492
|
+
* @param {number} [params.page=1] - The page number, only applied when `limit` is set.
|
|
493
|
+
* @param {number} [params.limit] - Max sessions to return; omit to return all of the week's sessions unpaginated.
|
|
494
|
+
* @returns {Promise<Object>} - A promise that resolves to an object containing:
|
|
495
|
+
* - `practices`: An array of formatted practice session data for the week (paginated if `limit` is set).
|
|
496
|
+
* - `practiceDuration`: Total practice duration (in seconds) for the whole week, regardless of pagination.
|
|
497
|
+
* - `total`: Total number of sessions for the week, regardless of pagination.
|
|
498
|
+
* - `currentPage`: The page number returned
|
|
499
|
+
* - `totalPages`: Total number of pages for the week
|
|
500
|
+
*
|
|
501
|
+
* @example
|
|
502
|
+
* getWeeklyPracticeSessions()
|
|
503
|
+
* .then(response => console.log(response))
|
|
504
|
+
* .catch(error => console.error(error));
|
|
505
|
+
*/
|
|
506
|
+
export async function getWeeklyPracticeSessions(params = {}, options = {}) {
|
|
507
|
+
const { page = 1, limit } = params
|
|
508
|
+
|
|
509
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
510
|
+
const startOfWeek = getMonday(new Date(), timeZone)
|
|
511
|
+
const weekDays = Array.from({ length: 7 }, (_, i) => startOfWeek.add(i, 'day').format('YYYY-MM-DD'))
|
|
512
|
+
|
|
513
|
+
if (options.pull) {
|
|
514
|
+
await db.practices.pull()
|
|
515
|
+
} else {
|
|
516
|
+
db.practices.pull()
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const query = await db.practices.queryAll(Q.where('date', Q.oneOf(weekDays)), Q.sortBy('created_at', 'asc'))
|
|
520
|
+
const data = query.data
|
|
465
521
|
|
|
466
|
-
return
|
|
522
|
+
return formatPracticeSessionData(data, page, limit)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async function formatPracticeSessionData(data, page, limit) {
|
|
526
|
+
if (!data.length)
|
|
527
|
+
return { data: { practices: [], practiceDuration: 0, total: 0, currentPage: page, totalPages: 1 } }
|
|
528
|
+
|
|
529
|
+
const practiceDuration = Math.round(
|
|
530
|
+
data.reduce((total, practice) => total + (practice.duration_seconds || 0), 0)
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
const pagedData = limit ? data.slice((page - 1) * limit, page * limit) : data
|
|
534
|
+
const formattedMeta = await formatPracticeMeta(pagedData)
|
|
535
|
+
|
|
536
|
+
return {
|
|
537
|
+
data: {
|
|
538
|
+
practices: formattedMeta,
|
|
539
|
+
practiceDuration,
|
|
540
|
+
total: data.length,
|
|
541
|
+
currentPage: page,
|
|
542
|
+
totalPages: limit ? Math.ceil(data.length / limit) : 1,
|
|
543
|
+
},
|
|
544
|
+
}
|
|
467
545
|
}
|
|
468
546
|
|
|
469
547
|
/**
|
|
@@ -575,12 +653,22 @@ export async function updatePracticeNotes(payload) {
|
|
|
575
653
|
}
|
|
576
654
|
|
|
577
655
|
export function getStreaksAndMessage(practices) {
|
|
578
|
-
let {
|
|
656
|
+
let {
|
|
657
|
+
currentDailyStreak,
|
|
658
|
+
currentWeeklyStreak,
|
|
659
|
+
streakMessage,
|
|
660
|
+
streakMessagePart1,
|
|
661
|
+
streakMessagePart2,
|
|
662
|
+
currentWeekPracticeDays,
|
|
663
|
+
} = calculateStreaks(practices, true)
|
|
579
664
|
|
|
580
665
|
return {
|
|
581
666
|
currentDailyStreak,
|
|
582
667
|
currentWeeklyStreak,
|
|
583
668
|
streakMessage,
|
|
669
|
+
streakMessagePart1,
|
|
670
|
+
streakMessagePart2,
|
|
671
|
+
currentWeekPracticeDays,
|
|
584
672
|
}
|
|
585
673
|
}
|
|
586
674
|
|
|
@@ -589,7 +677,7 @@ function calculateStreaks(practices, includeStreakMessage = false) {
|
|
|
589
677
|
let currentDailyStreak = 0
|
|
590
678
|
let currentWeeklyStreak = 0
|
|
591
679
|
let lastActiveDay = null
|
|
592
|
-
let
|
|
680
|
+
let streakMessageParts = { part1: '', part2: '' }
|
|
593
681
|
|
|
594
682
|
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
595
683
|
let sortedPracticeDays = Object.keys(practices)
|
|
@@ -602,7 +690,13 @@ function calculateStreaks(practices, includeStreakMessage = false) {
|
|
|
602
690
|
return {
|
|
603
691
|
currentDailyStreak: 0,
|
|
604
692
|
currentWeeklyStreak: 0,
|
|
605
|
-
|
|
693
|
+
currentWeekPracticeDays: 0,
|
|
694
|
+
streakMessage: joinStreakMessageParts(
|
|
695
|
+
streakMessages.startStreak.part1,
|
|
696
|
+
streakMessages.startStreak.part2
|
|
697
|
+
),
|
|
698
|
+
streakMessagePart1: streakMessages.startStreak.part1,
|
|
699
|
+
streakMessagePart2: streakMessages.startStreak.part2,
|
|
606
700
|
}
|
|
607
701
|
}
|
|
608
702
|
lastActiveDay = sortedPracticeDays[sortedPracticeDays.length - 1]
|
|
@@ -647,14 +741,18 @@ function calculateStreaks(practices, includeStreakMessage = false) {
|
|
|
647
741
|
}
|
|
648
742
|
currentWeeklyStreak = weeklyStreak
|
|
649
743
|
|
|
744
|
+
// Qualifying days (calendar days with at least one recorded practice) within the
|
|
745
|
+
// current Monday-anchored week — used for the weekly practice-days target progress.
|
|
746
|
+
let today = new Date()
|
|
747
|
+
let currentWeekStart = getMonday(today, timeZone)
|
|
748
|
+
let currentWeekPracticeDays = sortedPracticeDays.filter((date) => date >= currentWeekStart).length
|
|
749
|
+
|
|
650
750
|
// Calculate streak message only if includeStreakMessage is true
|
|
651
751
|
if (includeStreakMessage) {
|
|
652
|
-
let today = new Date()
|
|
653
752
|
today.setHours(0, 0, 0, 0)
|
|
654
753
|
let yesterday = new Date(today)
|
|
655
754
|
yesterday.setDate(today.getDate() - 1)
|
|
656
755
|
|
|
657
|
-
let currentWeekStart = getMonday(today, timeZone)
|
|
658
756
|
let lastWeekStart = currentWeekStart.subtract(7, 'days')
|
|
659
757
|
|
|
660
758
|
let hasYesterdayPractice = sortedPracticeDays.some((date) => isSameDate(date, yesterday))
|
|
@@ -669,13 +767,13 @@ function calculateStreaks(practices, includeStreakMessage = false) {
|
|
|
669
767
|
|
|
670
768
|
if (isSameDate(lastActiveDay, today)) {
|
|
671
769
|
if (hasYesterdayPractice) {
|
|
672
|
-
|
|
770
|
+
streakMessageParts = streakMessages.dailyStreak(currentDailyStreak)
|
|
673
771
|
} else if (hasCurrentWeekPreviousPractice) {
|
|
674
|
-
|
|
772
|
+
streakMessageParts = streakMessages.weeklyStreak(currentWeeklyStreak)
|
|
675
773
|
} else if (hasLastWeekPractice) {
|
|
676
|
-
|
|
774
|
+
streakMessageParts = streakMessages.greatJobWeeklyStreak(currentWeeklyStreak)
|
|
677
775
|
} else {
|
|
678
|
-
|
|
776
|
+
streakMessageParts = streakMessages.dailyStreakShort(currentDailyStreak)
|
|
679
777
|
}
|
|
680
778
|
} else {
|
|
681
779
|
if (
|
|
@@ -683,18 +781,25 @@ function calculateStreaks(practices, includeStreakMessage = false) {
|
|
|
683
781
|
(hasYesterdayPractice && sortedPracticeDays.length == 1) ||
|
|
684
782
|
(hasYesterdayPractice && !hasLastWeekPractice && hasOlderPractice)
|
|
685
783
|
) {
|
|
686
|
-
|
|
784
|
+
streakMessageParts = streakMessages.dailyStreakReminder(currentDailyStreak)
|
|
687
785
|
} else if (hasCurrentWeekPractice) {
|
|
688
|
-
|
|
786
|
+
streakMessageParts = streakMessages.weeklyStreakKeepUp(currentWeeklyStreak)
|
|
689
787
|
} else if (hasLastWeekPractice) {
|
|
690
|
-
|
|
788
|
+
streakMessageParts = streakMessages.weeklyStreakReminder(currentWeeklyStreak)
|
|
691
789
|
} else {
|
|
692
|
-
|
|
790
|
+
streakMessageParts = streakMessages.restartStreak
|
|
693
791
|
}
|
|
694
792
|
}
|
|
695
793
|
}
|
|
696
794
|
|
|
697
|
-
return {
|
|
795
|
+
return {
|
|
796
|
+
currentDailyStreak,
|
|
797
|
+
currentWeeklyStreak,
|
|
798
|
+
streakMessage: joinStreakMessageParts(streakMessageParts.part1, streakMessageParts.part2),
|
|
799
|
+
streakMessagePart1: streakMessageParts.part1,
|
|
800
|
+
streakMessagePart2: streakMessageParts.part2,
|
|
801
|
+
currentWeekPracticeDays,
|
|
802
|
+
}
|
|
698
803
|
}
|
|
699
804
|
|
|
700
805
|
/**
|
|
@@ -805,6 +910,7 @@ async function formatPracticeMeta(practices = []) {
|
|
|
805
910
|
return {
|
|
806
911
|
id: practice.id,
|
|
807
912
|
auto: practice.auto,
|
|
913
|
+
date: practice.date,
|
|
808
914
|
thumbnail: practice.content_id ? content?.thumbnail : practice.thumbnail_url || '',
|
|
809
915
|
thumbnail_url: practice.content_id ? content?.thumbnail : practice.thumbnail_url || '',
|
|
810
916
|
duration: practice.duration_seconds || 0,
|
package/src/version-info.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const MCS_VERSION = '2.
|
|
1
|
+
export const MCS_VERSION = '2.176.0'
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(npx jest *)",
|
|
5
|
-
"Bash(npx tsc *)",
|
|
6
|
-
"Skill(counselors)",
|
|
7
|
-
"Bash(counselors ls *)",
|
|
8
|
-
"Bash(counselors groups *)",
|
|
9
|
-
"Bash(counselors run *)",
|
|
10
|
-
"Bash(npm test *)",
|
|
11
|
-
"Bash(gh pr *)",
|
|
12
|
-
"Bash(gh api *)",
|
|
13
|
-
"Bash(mkdir -p /tmp/pr-review-v2)",
|
|
14
|
-
"Read(//tmp/pr-review-v2/**)",
|
|
15
|
-
"Bash(cat /home/alesevero/railenvironment/applications/musora-content-services/AGENTS.md)",
|
|
16
|
-
"Bash(echo \"no AGENTS.md\")",
|
|
17
|
-
"Bash(echo \"exit=$?\")",
|
|
18
|
-
"Bash(git checkout *)",
|
|
19
|
-
"Skill(pr)",
|
|
20
|
-
"Skill(create-decision)",
|
|
21
|
-
"mcp__github__pull_request_read",
|
|
22
|
-
"mcp__github__push_files",
|
|
23
|
-
"Bash(git push *)",
|
|
24
|
-
"mcp__github__create_pull_request",
|
|
25
|
-
"mcp__github__search_pull_requests",
|
|
26
|
-
"Bash(git commit *)",
|
|
27
|
-
"Bash(git add *)",
|
|
28
|
-
"mcp__github__list_pull_requests",
|
|
29
|
-
"mcp__sanity__get_schema",
|
|
30
|
-
"mcp__sanity__query_documents",
|
|
31
|
-
"Bash(git rm *)",
|
|
32
|
-
"Bash(git rebase *)"
|
|
33
|
-
]
|
|
34
|
-
},
|
|
35
|
-
"model": "sonnet",
|
|
36
|
-
"enabledMcpjsonServers": [
|
|
37
|
-
"atlassian",
|
|
38
|
-
"figma",
|
|
39
|
-
"google-workspace",
|
|
40
|
-
"snowflake",
|
|
41
|
-
"aws",
|
|
42
|
-
"hex",
|
|
43
|
-
"sanity",
|
|
44
|
-
"mysql",
|
|
45
|
-
"slack",
|
|
46
|
-
"langfuse",
|
|
47
|
-
"chrome-devtools",
|
|
48
|
-
"railway",
|
|
49
|
-
"github",
|
|
50
|
-
"asana"
|
|
51
|
-
],
|
|
52
|
-
"disabledMcpjsonServers": [
|
|
53
|
-
"nightwatch",
|
|
54
|
-
"1password",
|
|
55
|
-
"mysql-staging",
|
|
56
|
-
"mysql-local",
|
|
57
|
-
"mobile"
|
|
58
|
-
],
|
|
59
|
-
"effortLevel": "medium"
|
|
60
|
-
}
|