langcoach 0.3.0 → 0.4.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/README.md +156 -169
- package/dist/index.js +704 -6
- package/extension/assets/{sidepanel-DMjVhmRR.css → sidepanel-BBkB7IY_.css} +300 -41
- package/extension/background.js +2 -2
- package/extension/chunks/{translate-CM6Phtg_.js → translate-BpGu9iuQ.js} +59 -4
- package/extension/manifest.json +1 -1
- package/extension/sidepanel.html +2 -2
- package/extension/sidepanel.js +114 -150
- package/package.json +1 -1
package/extension/sidepanel.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as
|
|
1
|
+
import { C as toLocalDate, T as emptyState, a as resolveLanguage, c as version, d as send, f as levelFor, g as isDue, h as computeStreak, i as LLL_LANGUAGES, l as DEFAULT_SETTINGS, n as nativeLanguageCode, o as GOALS, r as translationPair, s as suggestThemes, t as createTranslatorCache, w as DAILY_GOALS, x as addDays } from "./chunks/translate-BpGu9iuQ.js";
|
|
2
2
|
//#region ../../packages/core/src/export-cards.ts
|
|
3
3
|
var TEXT_MIME = "text/plain;charset=utf-8";
|
|
4
4
|
var CSV_HEADER = [
|
|
@@ -136,6 +136,118 @@ function exportCards(state, format, opts = {}) {
|
|
|
136
136
|
};
|
|
137
137
|
}
|
|
138
138
|
//#endregion
|
|
139
|
+
//#region ../../packages/core/src/achievements.ts
|
|
140
|
+
/** Sessions recorded before the `perfect` flag existed: perfect iff XP = correct + 5 + 5. */
|
|
141
|
+
var wasPerfect = (s) => s.perfect ?? (s.exercises > 0 && s.xp === s.correct + 10);
|
|
142
|
+
function bestDayXp(log) {
|
|
143
|
+
const byDate = /* @__PURE__ */ new Map();
|
|
144
|
+
for (const e of log) byDate.set(e.date, (byDate.get(e.date) ?? 0) + e.xp);
|
|
145
|
+
return Math.max(0, ...byDate.values());
|
|
146
|
+
}
|
|
147
|
+
function achievements(state, today) {
|
|
148
|
+
const lessons = state.lessons.length;
|
|
149
|
+
const curStreak = computeStreak({
|
|
150
|
+
log: state.log,
|
|
151
|
+
today,
|
|
152
|
+
freezeUsedDates: state.freezeUsedDates ?? []
|
|
153
|
+
});
|
|
154
|
+
const cards = state.cards.length;
|
|
155
|
+
const reviews = state.cards.reduce((sum, c) => sum + c.srs.reps, 0);
|
|
156
|
+
const sessions = state.sessions ?? [];
|
|
157
|
+
const sessionCount = Math.max(sessions.length, state.log.filter((e) => e.taskId.startsWith("practice:")).length);
|
|
158
|
+
return [
|
|
159
|
+
{
|
|
160
|
+
id: "first_lesson",
|
|
161
|
+
raw: lessons,
|
|
162
|
+
goal: 1
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: "streak_3",
|
|
166
|
+
raw: curStreak,
|
|
167
|
+
goal: 3
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: "streak_7",
|
|
171
|
+
raw: curStreak,
|
|
172
|
+
goal: 7
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: "streak_30",
|
|
176
|
+
raw: curStreak,
|
|
177
|
+
goal: 30
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: "cards_50",
|
|
181
|
+
raw: cards,
|
|
182
|
+
goal: 50
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
id: "cards_100",
|
|
186
|
+
raw: cards,
|
|
187
|
+
goal: 100
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: "cards_500",
|
|
191
|
+
raw: cards,
|
|
192
|
+
goal: 500
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: "reviews_100",
|
|
196
|
+
raw: reviews,
|
|
197
|
+
goal: 100
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: "perfect_session",
|
|
201
|
+
raw: sessions.some(wasPerfect) ? 1 : 0,
|
|
202
|
+
goal: 1
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: "xp_100_day",
|
|
206
|
+
raw: bestDayXp(state.log),
|
|
207
|
+
goal: 100
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
id: "sessions_10",
|
|
211
|
+
raw: sessionCount,
|
|
212
|
+
goal: 10
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
id: "first_match",
|
|
216
|
+
raw: sessions.some((s) => (s.matchGames ?? 0) > 0) ? 1 : 0,
|
|
217
|
+
goal: 1
|
|
218
|
+
}
|
|
219
|
+
].map(({ id, raw, goal }) => ({
|
|
220
|
+
id,
|
|
221
|
+
unlocked: raw >= goal,
|
|
222
|
+
progress: Math.min(raw, goal),
|
|
223
|
+
goal
|
|
224
|
+
}));
|
|
225
|
+
}
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region ../../packages/core/src/strength.ts
|
|
228
|
+
/** FSRS card states (ts-fsrs `State`). */
|
|
229
|
+
var NEW = 0;
|
|
230
|
+
var LEARNING = 1;
|
|
231
|
+
var RELEARNING = 3;
|
|
232
|
+
/**
|
|
233
|
+
* Word strength 0–5 for the strength meter, from the card's FSRS state and stability (days until
|
|
234
|
+
* recall drops to 90%): new 0; (re)learning 1; in review by stability <3 d → 2, <10 → 3, <30 → 4, else 5.
|
|
235
|
+
*/
|
|
236
|
+
function strengthOf(card) {
|
|
237
|
+
const { state, reps } = card.srs;
|
|
238
|
+
if (reps <= 0 || state === NEW) return 0;
|
|
239
|
+
if (state === LEARNING || state === RELEARNING) return 1;
|
|
240
|
+
const s = Number(card.srs.stability);
|
|
241
|
+
if (!Number.isFinite(s) || s < 3) return 2;
|
|
242
|
+
if (s < 10) return 3;
|
|
243
|
+
if (s < 30) return 4;
|
|
244
|
+
return 5;
|
|
245
|
+
}
|
|
246
|
+
/** Practised at least once but still fragile (strength ≤ 2). */
|
|
247
|
+
function isWeak(card) {
|
|
248
|
+
return card.srs.reps > 0 && strengthOf(card) <= 2;
|
|
249
|
+
}
|
|
250
|
+
//#endregion
|
|
139
251
|
//#region \0vite/modulepreload-polyfill.js
|
|
140
252
|
(function polyfill() {
|
|
141
253
|
const relList = document.createElement("link").relList;
|
|
@@ -5890,130 +6002,6 @@ function CountUp({ to, instant, prefix = "" }) {
|
|
|
5890
6002
|
});
|
|
5891
6003
|
}
|
|
5892
6004
|
//#endregion
|
|
5893
|
-
//#region src/lib/achievements.ts
|
|
5894
|
-
/** Sessions recorded before the `perfect` flag existed: perfect iff XP = correct + 5 + 5. */
|
|
5895
|
-
var wasPerfect = (s) => s.perfect ?? (s.exercises > 0 && s.xp === s.correct + 10);
|
|
5896
|
-
function bestDayXp(log) {
|
|
5897
|
-
const byDate = /* @__PURE__ */ new Map();
|
|
5898
|
-
for (const e of log) byDate.set(e.date, (byDate.get(e.date) ?? 0) + e.xp);
|
|
5899
|
-
return Math.max(0, ...byDate.values());
|
|
5900
|
-
}
|
|
5901
|
-
function achievements(state, today) {
|
|
5902
|
-
const lessons = state.lessons.length;
|
|
5903
|
-
const curStreak = computeStreak({
|
|
5904
|
-
log: state.log,
|
|
5905
|
-
today,
|
|
5906
|
-
freezeUsedDates: state.freezeUsedDates ?? []
|
|
5907
|
-
});
|
|
5908
|
-
const cards = state.cards.length;
|
|
5909
|
-
const reviews = state.cards.reduce((sum, c) => sum + c.srs.reps, 0);
|
|
5910
|
-
const sessions = state.sessions ?? [];
|
|
5911
|
-
const sessionCount = Math.max(sessions.length, state.log.filter((e) => e.taskId.startsWith("practice:")).length);
|
|
5912
|
-
return [
|
|
5913
|
-
{
|
|
5914
|
-
id: "first_lesson",
|
|
5915
|
-
raw: lessons,
|
|
5916
|
-
goal: 1
|
|
5917
|
-
},
|
|
5918
|
-
{
|
|
5919
|
-
id: "streak_3",
|
|
5920
|
-
raw: curStreak,
|
|
5921
|
-
goal: 3
|
|
5922
|
-
},
|
|
5923
|
-
{
|
|
5924
|
-
id: "streak_7",
|
|
5925
|
-
raw: curStreak,
|
|
5926
|
-
goal: 7
|
|
5927
|
-
},
|
|
5928
|
-
{
|
|
5929
|
-
id: "streak_30",
|
|
5930
|
-
raw: curStreak,
|
|
5931
|
-
goal: 30
|
|
5932
|
-
},
|
|
5933
|
-
{
|
|
5934
|
-
id: "cards_50",
|
|
5935
|
-
raw: cards,
|
|
5936
|
-
goal: 50
|
|
5937
|
-
},
|
|
5938
|
-
{
|
|
5939
|
-
id: "cards_100",
|
|
5940
|
-
raw: cards,
|
|
5941
|
-
goal: 100
|
|
5942
|
-
},
|
|
5943
|
-
{
|
|
5944
|
-
id: "cards_500",
|
|
5945
|
-
raw: cards,
|
|
5946
|
-
goal: 500
|
|
5947
|
-
},
|
|
5948
|
-
{
|
|
5949
|
-
id: "reviews_100",
|
|
5950
|
-
raw: reviews,
|
|
5951
|
-
goal: 100
|
|
5952
|
-
},
|
|
5953
|
-
{
|
|
5954
|
-
id: "perfect_session",
|
|
5955
|
-
raw: sessions.some(wasPerfect) ? 1 : 0,
|
|
5956
|
-
goal: 1
|
|
5957
|
-
},
|
|
5958
|
-
{
|
|
5959
|
-
id: "xp_100_day",
|
|
5960
|
-
raw: bestDayXp(state.log),
|
|
5961
|
-
goal: 100
|
|
5962
|
-
},
|
|
5963
|
-
{
|
|
5964
|
-
id: "sessions_10",
|
|
5965
|
-
raw: sessionCount,
|
|
5966
|
-
goal: 10
|
|
5967
|
-
},
|
|
5968
|
-
{
|
|
5969
|
-
id: "first_match",
|
|
5970
|
-
raw: sessions.some((s) => (s.matchGames ?? 0) > 0) ? 1 : 0,
|
|
5971
|
-
goal: 1
|
|
5972
|
-
}
|
|
5973
|
-
].map(({ id, raw, goal }) => ({
|
|
5974
|
-
id,
|
|
5975
|
-
unlocked: raw >= goal,
|
|
5976
|
-
progress: Math.min(raw, goal),
|
|
5977
|
-
goal
|
|
5978
|
-
}));
|
|
5979
|
-
}
|
|
5980
|
-
var LEVELS = [
|
|
5981
|
-
0,
|
|
5982
|
-
50,
|
|
5983
|
-
150,
|
|
5984
|
-
300,
|
|
5985
|
-
500,
|
|
5986
|
-
800,
|
|
5987
|
-
1200,
|
|
5988
|
-
1700,
|
|
5989
|
-
2400,
|
|
5990
|
-
3300
|
|
5991
|
-
].map((minXp, i) => ({
|
|
5992
|
-
level: i + 1,
|
|
5993
|
-
minXp,
|
|
5994
|
-
key: `level${i + 1}`
|
|
5995
|
-
}));
|
|
5996
|
-
function levelFor(totalXp) {
|
|
5997
|
-
const xp = Number.isFinite(totalXp) ? Math.max(0, totalXp) : 0;
|
|
5998
|
-
let i = 0;
|
|
5999
|
-
while (i + 1 < LEVELS.length && xp >= LEVELS[i + 1].minXp) i++;
|
|
6000
|
-
const cur = LEVELS[i];
|
|
6001
|
-
const next = LEVELS[i + 1];
|
|
6002
|
-
if (!next) return {
|
|
6003
|
-
...cur,
|
|
6004
|
-
nextMinXp: null,
|
|
6005
|
-
progress: 1,
|
|
6006
|
-
toNext: 0
|
|
6007
|
-
};
|
|
6008
|
-
const span = next.minXp - cur.minXp;
|
|
6009
|
-
return {
|
|
6010
|
-
...cur,
|
|
6011
|
-
nextMinXp: next.minXp,
|
|
6012
|
-
progress: Math.min(1, (xp - cur.minXp) / span),
|
|
6013
|
-
toNext: next.minXp - xp
|
|
6014
|
-
};
|
|
6015
|
-
}
|
|
6016
|
-
//#endregion
|
|
6017
6005
|
//#region src/sidepanel/components/WeekChart.tsx
|
|
6018
6006
|
var weekday = (date, locale) => {
|
|
6019
6007
|
try {
|
|
@@ -6990,30 +6978,6 @@ function Settings({ snapshot, settings, t, send, onRefresh }) {
|
|
|
6990
6978
|
]
|
|
6991
6979
|
});
|
|
6992
6980
|
}
|
|
6993
|
-
//#endregion
|
|
6994
|
-
//#region src/lib/strength.ts
|
|
6995
|
-
/** FSRS card states (ts-fsrs `State`). */
|
|
6996
|
-
var NEW = 0;
|
|
6997
|
-
var LEARNING = 1;
|
|
6998
|
-
var RELEARNING = 3;
|
|
6999
|
-
/**
|
|
7000
|
-
* Word strength 0–5 for the strength meter, from the card's FSRS state and stability (days until
|
|
7001
|
-
* recall drops to 90%): new 0; (re)learning 1; in review by stability <3 d → 2, <10 → 3, <30 → 4, else 5.
|
|
7002
|
-
*/
|
|
7003
|
-
function strengthOf(card) {
|
|
7004
|
-
const { state, reps } = card.srs;
|
|
7005
|
-
if (reps <= 0 || state === NEW) return 0;
|
|
7006
|
-
if (state === LEARNING || state === RELEARNING) return 1;
|
|
7007
|
-
const s = Number(card.srs.stability);
|
|
7008
|
-
if (!Number.isFinite(s) || s < 3) return 2;
|
|
7009
|
-
if (s < 10) return 3;
|
|
7010
|
-
if (s < 30) return 4;
|
|
7011
|
-
return 5;
|
|
7012
|
-
}
|
|
7013
|
-
/** Practised at least once but still fragile (strength ≤ 2). */
|
|
7014
|
-
function isWeak(card) {
|
|
7015
|
-
return card.srs.reps > 0 && strengthOf(card) <= 2;
|
|
7016
|
-
}
|
|
7017
6981
|
/** Five small bars; `value` 0–5 (lib/strength). Exposed as an ARIA meter. */
|
|
7018
6982
|
function StrengthMeter({ value, t }) {
|
|
7019
6983
|
const v = Math.max(0, Math.min(5, Math.round(value)));
|
|
@@ -7838,7 +7802,7 @@ function Words({ snapshot, uiLocale, t, send, onRefresh, native, initialMode, de
|
|
|
7838
7802
|
"aria-label": t("wordsFilterLabel"),
|
|
7839
7803
|
children: FILTERS.map((f) => /* @__PURE__ */ u("button", {
|
|
7840
7804
|
type: "button",
|
|
7841
|
-
class: `chip ${filter === f ? "active" : ""}`,
|
|
7805
|
+
class: `chip chip-${f} ${filter === f ? "active" : ""}`,
|
|
7842
7806
|
"data-testid": `words-filter-${f}`,
|
|
7843
7807
|
"aria-pressed": filter === f,
|
|
7844
7808
|
onClick: () => setFilter(f),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "langcoach",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Language-learning coach MCP server for Claude Code: Claude writes the lessons, LangCoach keeps your weekly plan, streak, XP and spaced-repetition (FSRS) reviews on your machine.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"language-learning",
|