langcoach 0.3.0 → 0.4.1

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.
@@ -1,4 +1,4 @@
1
- import { C as DAILY_GOALS, S as toLocalDate, a as resolveLanguage, b as addDays, c as version, d as send, h as isDue, i as LLL_LANGUAGES, l as DEFAULT_SETTINGS, m as computeStreak, n as nativeLanguageCode, o as GOALS, r as translationPair, s as suggestThemes, t as createTranslatorCache, w as emptyState } from "./chunks/translate-CM6Phtg_.js";
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-DvfZknmj.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.0",
3
+ "version": "0.4.1",
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",