@vue-skuilder/common-ui 0.2.21 → 0.2.23

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 { ReplanHints, OutcomeObserver } from '../../../db/dist/index.mjs';
1
+ import { ReplanHints, OutcomeObserver, SessionStateSnapshotProvider } from '../../../db/dist/index.mjs';
2
2
  /**
3
3
  * Misc. config for a study StudySessionConfig
4
4
  */
@@ -40,6 +40,12 @@ export type StudySessionConfig = {
40
40
  * session hints, request a replan). See `OutcomeObserver` in `@vue-skuilder/db`.
41
41
  */
42
42
  outcomeObservers?: OutcomeObserver[];
43
+ /**
44
+ * Thin learner-state snapshot taken at session open and close, stored on the
45
+ * session's `StudySessionDoc`. Keep the returned object small — counts, ids,
46
+ * scores. Time-boxed and error-isolated by the controller.
47
+ */
48
+ sessionStateSnapshot?: SessionStateSnapshotProvider;
43
49
  };
44
50
  /**
45
51
  * Built-in card transition presets provided by StudySession.
@@ -1 +1 @@
1
- {"version":3,"file":"StudySession.types.d.ts","sourceRoot":"","sources":["../../src/components/StudySession.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAErE;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,aAAa,EAAE,OAAO,CAAC;IAEvB;;;;;;;;;;;;OAYG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,WAAW,CAAC;IAExB;;;;;;;;;OASG;IACH,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;CACtC,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG,YAAY,GAAG,YAAY,CAAC;AAElF,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC"}
1
+ {"version":3,"file":"StudySession.types.d.ts","sourceRoot":"","sources":["../../src/components/StudySession.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAC;AAEnG;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,aAAa,EAAE,OAAO,CAAC;IAEvB;;;;;;;;;;;;OAYG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,WAAW,CAAC;IAExB;;;;;;;;;OASG;IACH,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;IAErC;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,4BAA4B,CAAC;CACrD,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG,YAAY,GAAG,YAAY,CAAC;AAElF,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"StudySession.vue.d.ts","sourceRoot":"","sources":["../../src/components/StudySession.vue"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"StudySession.vue.d.ts","sourceRoot":"","sources":["../../src/components/StudySession.vue"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"UserLogin.vue.d.ts","sourceRoot":"","sources":["../../../src/components/auth/UserLogin.vue"],"names":[],"mappings":"AA2DA;AAyNA,UAAU,KAAK;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAmID,iBAAS,cAAc;WAuPT,OAAO,IAA6B;;mCAVV,GAAG;;;;EAe1C;AAqBD,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AAC9D,QAAA,MAAM,eAAe;;;;;;;;;gBAtZN,MAAM;6EA+ZnB,CAAC;wBACkB,uBAAuB,CAAC,OAAO,eAAe,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAAnG,wBAAoG;AAapG,KAAK,uBAAuB,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IACxC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KAEV,CAAA;CACD,CAAC"}
1
+ {"version":3,"file":"UserLogin.vue.d.ts","sourceRoot":"","sources":["../../../src/components/auth/UserLogin.vue"],"names":[],"mappings":"AA2DA;AA2OA,UAAU,KAAK;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAqJD,iBAAS,cAAc;WAwPT,OAAO,IAA6B;;mCAVV,GAAG;;;;EAe1C;AAsBD,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AAC9D,QAAA,MAAM,eAAe;;;;;;;;;gBA1aN,MAAM;6EAmbnB,CAAC;wBACkB,uBAAuB,CAAC,OAAO,eAAe,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAAnG,wBAAoG;AAapG,KAAK,uBAAuB,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IACxC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KAEV,CAAA;CACD,CAAC"}
@@ -44,6 +44,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
44
44
  getCourseRegDoc: (courseId: string) => Promise<import('../../../../db/dist/index.mjs').CourseRegistration>;
45
45
  getActiveCourses: () => Promise<import('../../../../db/dist/index.mjs').CourseRegistration[]>;
46
46
  getPendingReviews: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
47
+ getStudySessions: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').StudySessionDoc[]>;
47
48
  getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
48
49
  getActivityRecords: () => Promise<import('../../../../db/dist/index.mjs').ActivityRecord[]>;
49
50
  getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
@@ -73,6 +74,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
73
74
  putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
74
75
  deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
75
76
  putUserOutcome: (record: import('../../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
77
+ putStudySession: (doc: import('../../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
76
78
  update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
77
79
  createAccount: (username: string, password: string) => Promise<{
78
80
  status: Status;
@@ -109,6 +111,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
109
111
  getCourseRegDoc: (courseId: string) => Promise<import('../../../../db/dist/index.mjs').CourseRegistration>;
110
112
  getActiveCourses: () => Promise<import('../../../../db/dist/index.mjs').CourseRegistration[]>;
111
113
  getPendingReviews: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
114
+ getStudySessions: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').StudySessionDoc[]>;
112
115
  getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
113
116
  getActivityRecords: () => Promise<import('../../../../db/dist/index.mjs').ActivityRecord[]>;
114
117
  getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
@@ -138,6 +141,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
138
141
  putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
139
142
  deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
140
143
  putUserOutcome: (record: import('../../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
144
+ putStudySession: (doc: import('../../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
141
145
  update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
142
146
  createAccount: (username: string, password: string) => Promise<{
143
147
  status: Status;
@@ -174,6 +178,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
174
178
  getCourseRegDoc: (courseId: string) => Promise<import('../../../../db/dist/index.mjs').CourseRegistration>;
175
179
  getActiveCourses: () => Promise<import('../../../../db/dist/index.mjs').CourseRegistration[]>;
176
180
  getPendingReviews: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
181
+ getStudySessions: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').StudySessionDoc[]>;
177
182
  getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
178
183
  getActivityRecords: () => Promise<import('../../../../db/dist/index.mjs').ActivityRecord[]>;
179
184
  getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
@@ -203,6 +208,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
203
208
  putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
204
209
  deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
205
210
  putUserOutcome: (record: import('../../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
211
+ putStudySession: (doc: import('../../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
206
212
  update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
207
213
  createAccount: (username: string, password: string) => Promise<{
208
214
  status: Status;
@@ -240,6 +246,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
240
246
  getCourseRegDoc: (courseId: string) => Promise<import('../../../../db/dist/index.mjs').CourseRegistration>;
241
247
  getActiveCourses: () => Promise<import('../../../../db/dist/index.mjs').CourseRegistration[]>;
242
248
  getPendingReviews: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
249
+ getStudySessions: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').StudySessionDoc[]>;
243
250
  getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
244
251
  getActivityRecords: () => Promise<import('../../../../db/dist/index.mjs').ActivityRecord[]>;
245
252
  getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
@@ -269,6 +276,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
269
276
  putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
270
277
  deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
271
278
  putUserOutcome: (record: import('../../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
279
+ putStudySession: (doc: import('../../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
272
280
  update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
273
281
  createAccount: (username: string, password: string) => Promise<{
274
282
  status: Status;
@@ -1 +1 @@
1
- {"version":3,"file":"UserRegistration.vue.d.ts","sourceRoot":"","sources":["../../../src/components/auth/UserRegistration.vue"],"names":[],"mappings":"AA8EA;AAAA,OAgSO,EAAmB,QAAQ,EAAE,MAAM,KAAK,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,MAAM,EAAO,MAAM,sBAAsB,CAAC;;;cAU3B,QAAQ,CAAC,CAAC,OAAO,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAA;SAAE,KAAK,IAAI,CAAC;;;;;;;;;;;;;;;;;;UAuBrD,eAAe,GAAG,IAAI;WACO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBAqR5C,CAAC;;;;;;;;;;;;wBAqCJ,CAAC;yBAAuB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBArCtB,CAAC;;;;;;;;;;;;wBAqCJ,CAAC;yBAAuB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBArCtB,CAAC;;;;;;;;;;;;wBAqCJ,CAAC;yBAAuB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBArCtB,CAAC;;;;;;;;;;;;wBAqCJ,CAAC;yBAAuB,CAAC;;;;;;;;;;;;;;;;;yBAjTV,OAAO;;;;;qBASX,MAAM;2BAGA,MAAM;;;;;;;;cA7CT,QAAQ,CAAC,CAAC,OAAO,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAA;SAAE,KAAK,IAAI,CAAC;;;;;;;AALzE,wBAoMG"}
1
+ {"version":3,"file":"UserRegistration.vue.d.ts","sourceRoot":"","sources":["../../../src/components/auth/UserRegistration.vue"],"names":[],"mappings":"AA8EA;AAAA,OAgSO,EAAmB,QAAQ,EAAE,MAAM,KAAK,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,MAAM,EAAO,MAAM,sBAAsB,CAAC;;;cAU3B,QAAQ,CAAC,CAAC,OAAO,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAA;SAAE,KAAK,IAAI,CAAC;;;;;;;;;;;;;;;;;;UAuBrD,eAAe,GAAG,IAAI;WACO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA2d0kK,CAAC;;;;;;;;;;;;;wBAAm7D,CAAC;yBAAuB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBAA78D,CAAC;;;;;;;;;;;;;wBAAm7D,CAAC;yBAAuB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBAA78D,CAAC;;;;;;;;;;;;;wBAAm7D,CAAC;yBAAuB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBAA78D,CAAC;;;;;;;;;;;;;wBAAm7D,CAAC;yBAAuB,CAAC;;;;;;;;;;;;;;;;;yBAldvjO,OAAO;;;;;qBASX,MAAM;2BAGA,MAAM;;;;;;;;cA7CT,QAAQ,CAAC,CAAC,OAAO,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAA;SAAE,KAAK,IAAI,CAAC;;;;;;;AALzE,wBAoMG"}
@@ -32,6 +32,16 @@ export interface UserStatusResponse extends AuthResponse {
32
32
  }
33
33
  export interface ResolveLoginResponse extends AuthResponse {
34
34
  username?: string;
35
+ /**
36
+ * HTTP status of the resolve call on failure, so callers can distinguish a
37
+ * genuine credential rejection from a system/service failure. The server
38
+ * returns a uniform 401 for every real auth failure (unknown identifier,
39
+ * unverified email, wrong password — no enumeration leak); anything else
40
+ * (404 = route not deployed, 5xx, or `undefined` for a network error) means
41
+ * the resolver itself is unavailable, not that the credentials were wrong.
42
+ * Undefined on success.
43
+ */
44
+ status?: number;
35
45
  }
36
46
  /**
37
47
  * Triggers verification email send for a newly created account.
@@ -1 +1 @@
1
- {"version":3,"file":"authAPI.d.ts","sourceRoot":"","sources":["../../src/services/authAPI.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,mBAAmB,CAAC,EAAE;YACpB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;SACxB,CAAC;KACH;CACF;AAgCD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAoB,SAAQ,YAAY;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAE3D,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,sBAAsB,GAAG,UAAU,GAAG,WAAW,CAAC;IAC3D,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,YAAY,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAED,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CA+BvB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA+B7E;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,oBAAoB,CAAC,CA6B/B;AAED;;;GAGG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAsBjE;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CA4BvB;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,CAAC,CAuBvB"}
1
+ {"version":3,"file":"authAPI.d.ts","sourceRoot":"","sources":["../../src/services/authAPI.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,mBAAmB,CAAC,EAAE;YACpB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;SACxB,CAAC;KACH;CACF;AAgCD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAoB,SAAQ,YAAY;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAE3D,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,sBAAsB,GAAG,UAAU,GAAG,WAAW,CAAC;IAC3D,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,YAAY,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAED,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CA+BvB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA+B7E;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,oBAAoB,CAAC,CAgC/B;AAED;;;GAGG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAsBjE;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CA4BvB;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,CAAC,CAuBvB"}
@@ -26,6 +26,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
26
26
  getCourseRegDoc: (courseId: string) => Promise<import('../../../db/dist/index.mjs').CourseRegistration>;
27
27
  getActiveCourses: () => Promise<import('../../../db/dist/index.mjs').CourseRegistration[]>;
28
28
  getPendingReviews: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
29
+ getStudySessions: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').StudySessionDoc[]>;
29
30
  getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
30
31
  getActivityRecords: () => Promise<import('../../../db/dist/index.mjs').ActivityRecord[]>;
31
32
  getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
@@ -55,6 +56,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
55
56
  putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
56
57
  deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
57
58
  putUserOutcome: (record: import('../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
59
+ putStudySession: (doc: import('../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
58
60
  update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
59
61
  createAccount: (username: string, password: string) => Promise<{
60
62
  status: import('../../../common/dist/index.mjs').Status;
@@ -91,6 +93,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
91
93
  getCourseRegDoc: (courseId: string) => Promise<import('../../../db/dist/index.mjs').CourseRegistration>;
92
94
  getActiveCourses: () => Promise<import('../../../db/dist/index.mjs').CourseRegistration[]>;
93
95
  getPendingReviews: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
96
+ getStudySessions: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').StudySessionDoc[]>;
94
97
  getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
95
98
  getActivityRecords: () => Promise<import('../../../db/dist/index.mjs').ActivityRecord[]>;
96
99
  getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
@@ -120,6 +123,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
120
123
  putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
121
124
  deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
122
125
  putUserOutcome: (record: import('../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
126
+ putStudySession: (doc: import('../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
123
127
  update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
124
128
  createAccount: (username: string, password: string) => Promise<{
125
129
  status: import('../../../common/dist/index.mjs').Status;
@@ -156,6 +160,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
156
160
  getCourseRegDoc: (courseId: string) => Promise<import('../../../db/dist/index.mjs').CourseRegistration>;
157
161
  getActiveCourses: () => Promise<import('../../../db/dist/index.mjs').CourseRegistration[]>;
158
162
  getPendingReviews: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
163
+ getStudySessions: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').StudySessionDoc[]>;
159
164
  getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
160
165
  getActivityRecords: () => Promise<import('../../../db/dist/index.mjs').ActivityRecord[]>;
161
166
  getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
@@ -185,6 +190,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
185
190
  putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
186
191
  deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
187
192
  putUserOutcome: (record: import('../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
193
+ putStudySession: (doc: import('../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
188
194
  update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
189
195
  createAccount: (username: string, password: string) => Promise<{
190
196
  status: import('../../../common/dist/index.mjs').Status;
@@ -222,6 +228,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
222
228
  getCourseRegDoc: (courseId: string) => Promise<import('../../../db/dist/index.mjs').CourseRegistration>;
223
229
  getActiveCourses: () => Promise<import('../../../db/dist/index.mjs').CourseRegistration[]>;
224
230
  getPendingReviews: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
231
+ getStudySessions: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').StudySessionDoc[]>;
225
232
  getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
226
233
  getActivityRecords: () => Promise<import('../../../db/dist/index.mjs').ActivityRecord[]>;
227
234
  getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
@@ -251,6 +258,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
251
258
  putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
252
259
  deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
253
260
  putUserOutcome: (record: import('../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
261
+ putStudySession: (doc: import('../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
254
262
  update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
255
263
  createAccount: (username: string, password: string) => Promise<{
256
264
  status: import('../../../common/dist/index.mjs').Status;
@@ -1 +1 @@
1
- {"version":3,"file":"useAuthStore.d.ts","sourceRoot":"","sources":["../../src/stores/useAuthStore.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGjE,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,eAAe,GAAG,SAAS,CAAC;IACnC,oBAAoB,EAAE;QACpB,IAAI,EAAE,OAAO,CAAC;QACd,QAAQ,EAAE,OAAO,CAAC;QAClB,aAAa,EAAE,OAAO,CAAC;QACvB,eAAe,EAAE,OAAO,CAAC;KAC1B,CAAC;IACF,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAiB3E;AAED,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAgF2vkB,CAAC;;;;;;;;;;;;oBAAm0D,CAAC;qBAAuB,CAAC;;;;;;;kBA3GvmoB,OAAO;sBACH,OAAO;2BACF,OAAO;6BACL,OAAO;;wBAEV,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAsG2vkB,CAAC;;;;;;;;;;;;oBAAm0D,CAAC;qBAAuB,CAAC;;;;;;;kBA3GvmoB,OAAO;sBACH,OAAO;2BACF,OAAO;6BACL,OAAO;;wBAEV,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAsG2vkB,CAAC;;;;;;;;;;;;oBAAm0D,CAAC;qBAAuB,CAAC;;;;;;;kBA3GvmoB,OAAO;sBACH,OAAO;2BACF,OAAO;6BACL,OAAO;;wBAEV,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAsG2vkB,CAAC;;;;;;;;;;;;oBAAm0D,CAAC;qBAAuB,CAAC;;;;;;;;;yBA1CtloB,OAAO;uBAIT,OAAO;;;;;EAqC/B,CAAC"}
1
+ {"version":3,"file":"useAuthStore.d.ts","sourceRoot":"","sources":["../../src/stores/useAuthStore.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGjE,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,eAAe,GAAG,SAAS,CAAC;IACnC,oBAAoB,EAAE;QACpB,IAAI,EAAE,OAAO,CAAC;QACd,QAAQ,EAAE,OAAO,CAAC;QAClB,aAAa,EAAE,OAAO,CAAC;QACvB,eAAe,EAAE,OAAO,CAAC;KAC1B,CAAC;IACF,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAiB3E;AAED,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAgFw/gC,CAAC;;;;;;;;;;;;;oBAAm7D,CAAC;qBAAuB,CAAC;;;;;;;kBA3Gp9kC,OAAO;sBACH,OAAO;2BACF,OAAO;6BACL,OAAO;;wBAEV,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAsGw/gC,CAAC;;;;;;;;;;;;;oBAAm7D,CAAC;qBAAuB,CAAC;;;;;;;kBA3Gp9kC,OAAO;sBACH,OAAO;2BACF,OAAO;6BACL,OAAO;;wBAEV,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAsGw/gC,CAAC;;;;;;;;;;;;;oBAAm7D,CAAC;qBAAuB,CAAC;;;;;;;kBA3Gp9kC,OAAO;sBACH,OAAO;2BACF,OAAO;6BACL,OAAO;;wBAEV,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAsGw/gC,CAAC;;;;;;;;;;;;;oBAAm7D,CAAC;qBAAuB,CAAC;;;;;;;;;yBA1Cn8kC,OAAO;uBAIT,OAAO;;;;;EAqC/B,CAAC"}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.2.21",
6
+ "version": "0.2.23",
7
7
  "sideEffects": false,
8
8
  "main": "./dist/common-ui.umd.js",
9
9
  "module": "./dist/common-ui.es.js",
@@ -34,8 +34,8 @@
34
34
  "@highlightjs/vue-plugin": "^2.1.2",
35
35
  "@mdi/font": "^7.3.67",
36
36
  "@vojtechlanka/vue-tags-input": "^3",
37
- "@vue-skuilder/common": "0.2.21",
38
- "@vue-skuilder/db": "0.2.21",
37
+ "@vue-skuilder/common": "0.2.23",
38
+ "@vue-skuilder/db": "0.2.23",
39
39
  "highlight.js": "^11.0.1",
40
40
  "marked": "^15.0.6",
41
41
  "moment": "^2.29.4",
@@ -71,5 +71,5 @@
71
71
  "vite-plugin-dts": "^4.5.3",
72
72
  "vitest": "^4.1.0"
73
73
  },
74
- "stableVersion": "0.2.21"
74
+ "stableVersion": "0.2.23"
75
75
  }
@@ -1,4 +1,4 @@
1
- import type { ReplanHints, OutcomeObserver } from '@vue-skuilder/db';
1
+ import type { ReplanHints, OutcomeObserver, SessionStateSnapshotProvider } from '@vue-skuilder/db';
2
2
 
3
3
  /**
4
4
  * Misc. config for a study StudySessionConfig
@@ -44,6 +44,13 @@ export type StudySessionConfig = {
44
44
  * session hints, request a replan). See `OutcomeObserver` in `@vue-skuilder/db`.
45
45
  */
46
46
  outcomeObservers?: OutcomeObserver[];
47
+
48
+ /**
49
+ * Thin learner-state snapshot taken at session open and close, stored on the
50
+ * session's `StudySessionDoc`. Keep the returned object small — counts, ids,
51
+ * scores. Time-boxed and error-isolated by the controller.
52
+ */
53
+ sessionStateSnapshot?: SessionStateSnapshotProvider;
47
54
  };
48
55
 
49
56
  /**
@@ -113,6 +113,7 @@ import {
113
113
  import confetti from 'canvas-confetti';
114
114
 
115
115
  import { StudySessionConfig, CardTransitionPreset, CardTransitionMode } from './StudySession.types';
116
+ import type { SessionStateSnapshotProvider } from '@vue-skuilder/db';
116
117
 
117
118
  interface StudyRefs {
118
119
  shadowWrapper: HTMLDivElement;
@@ -273,6 +274,17 @@ export default defineComponent({
273
274
  console.log('[StudySession] InitSession completed in created hook');
274
275
  },
275
276
 
277
+ beforeUnmount() {
278
+ // Torn down mid-session (route change, parent v-if) = abandoned.
279
+ // `endSession` is idempotent, so a naturally-ended session keeps `closed`.
280
+ if (this.sessionController && !this.sessionFinished) {
281
+ void this.sessionController.endSession('abandoned');
282
+ }
283
+ if (this.intervalHandler) {
284
+ clearInterval(this.intervalHandler);
285
+ }
286
+ },
287
+
276
288
  errorCaptured(err: unknown, _instance: unknown, info: string) {
277
289
  console.error(`[StudySession] Card render error (${info}), skipping card "${this.cardID}":`, err);
278
290
  if (this.sessionController) {
@@ -399,6 +411,8 @@ export default defineComponent({
399
411
  const scOptions: {
400
412
  defaultBatchLimit?: number;
401
413
  outcomeObservers?: OutcomeObserver[];
414
+ courseId?: string;
415
+ stateSnapshotProvider?: SessionStateSnapshotProvider;
402
416
  } = {};
403
417
  if (this.sessionConfig?.defaultBatchLimit !== undefined) {
404
418
  scOptions.defaultBatchLimit = this.sessionConfig.defaultBatchLimit;
@@ -406,6 +420,15 @@ export default defineComponent({
406
420
  if (this.sessionConfig?.outcomeObservers?.length) {
407
421
  scOptions.outcomeObservers = this.sessionConfig.outcomeObservers;
408
422
  }
423
+ // A session can span several sources; the first course-type one owns
424
+ // the record. Absent it, the controller skips session recording.
425
+ const recordingCourseId = this.contentSources.find((s) => s.type === 'course')?.id;
426
+ if (recordingCourseId) {
427
+ scOptions.courseId = recordingCourseId;
428
+ }
429
+ if (this.sessionConfig?.sessionStateSnapshot) {
430
+ scOptions.stateSnapshotProvider = this.sessionConfig.sessionStateSnapshot;
431
+ }
409
432
 
410
433
  this.sessionController = markRaw(
411
434
  new SessionController<ViewComponent>(
@@ -487,6 +510,9 @@ export default defineComponent({
487
510
 
488
511
  r.cardID = this.cardID;
489
512
  r.courseID = this.courseID;
513
+ // The single chokepoint every response passes through en route to
514
+ // putCardRecord.
515
+ r.sessionId = this.sessionController?.sessionId;
490
516
  this.currentCard.records.push(r);
491
517
 
492
518
  console.log(`[StudySession] StudySession.processResponse is running...`);
@@ -26,7 +26,7 @@
26
26
  ></v-text-field>
27
27
 
28
28
  <v-snackbar v-model="badLoginAttempt" location="bottom right" :timeout="errorTimeout">
29
- Username or password was incorrect.
29
+ {{ errorMessage }}
30
30
  <v-btn color="pink" variant="text" @click="badLoginAttempt = false">Close</v-btn>
31
31
  </v-snackbar>
32
32
 
@@ -97,6 +97,15 @@ const passwordVisible = ref(false);
97
97
  const awaitingResponse = ref(false);
98
98
  const badLoginAttempt = ref(false);
99
99
  const errorTimeout = ref(7000);
100
+ const errorMessage = ref('Username or password was incorrect.');
101
+
102
+ // Shown when the email→username resolver is unreachable (route missing / 5xx /
103
+ // network) rather than when credentials are actually wrong. Telling the two
104
+ // apart matters: "wrong password" sends the user to re-check what is in fact
105
+ // correct, and hides a real backend outage.
106
+ const BAD_CREDENTIALS_MSG = 'Username or password was incorrect.';
107
+ const LOGIN_UNAVAILABLE_MSG =
108
+ 'Login is temporarily unavailable. Please try again in a moment.';
100
109
  const user = ref<User | undefined>(undefined);
101
110
 
102
111
  const loginRoute = computed(() => route.name === 'login');
@@ -106,11 +115,12 @@ const buttonStatus = computed(() => ({
106
115
  text: badLoginAttempt.value ? 'Try again' : 'Log In',
107
116
  }));
108
117
 
109
- const initBadLogin = () => {
118
+ const initBadLogin = (message: string = BAD_CREDENTIALS_MSG) => {
119
+ errorMessage.value = message;
110
120
  badLoginAttempt.value = true;
111
121
 
112
122
  alertUser({
113
- text: 'Username or password was incorrect.',
123
+ text: message,
114
124
  status: Status.error,
115
125
  timeout: errorTimeout.value,
116
126
  });
@@ -138,8 +148,16 @@ const login = async () => {
138
148
  if (identifier.value.includes('@')) {
139
149
  const resolved = await resolveLoginIdentifier(identifier.value, password.value);
140
150
  if (!resolved.ok || !resolved.username) {
141
- log('Email identifier did not resolve (bad credentials)');
142
- initBadLogin();
151
+ // 401 is the server's uniform "real auth failure" code; anything else
152
+ // (404 = route not deployed, 5xx, or no status = network) means the
153
+ // resolver is unavailable, not that the credentials were wrong.
154
+ if (resolved.status === 401) {
155
+ log('Email identifier did not resolve: invalid credentials');
156
+ initBadLogin();
157
+ } else {
158
+ log(`Email login unavailable (resolver error: ${resolved.error ?? 'network'})`);
159
+ initBadLogin(LOGIN_UNAVAILABLE_MSG);
160
+ }
143
161
  awaitingResponse.value = false;
144
162
  return;
145
163
  }
@@ -70,6 +70,16 @@ export interface UserStatusResponse extends AuthResponse {
70
70
 
71
71
  export interface ResolveLoginResponse extends AuthResponse {
72
72
  username?: string;
73
+ /**
74
+ * HTTP status of the resolve call on failure, so callers can distinguish a
75
+ * genuine credential rejection from a system/service failure. The server
76
+ * returns a uniform 401 for every real auth failure (unknown identifier,
77
+ * unverified email, wrong password — no enumeration leak); anything else
78
+ * (404 = route not deployed, 5xx, or `undefined` for a network error) means
79
+ * the resolver itself is unavailable, not that the credentials were wrong.
80
+ * Undefined on success.
81
+ */
82
+ status?: number;
73
83
  }
74
84
 
75
85
  /**
@@ -181,12 +191,15 @@ export async function resolveLoginIdentifier(
181
191
  }
182
192
  return {
183
193
  ok: false,
194
+ status: response.status,
184
195
  error: serverError || `HTTP ${response.status}: ${response.statusText}`,
185
196
  };
186
197
  }
187
198
 
188
199
  return await response.json();
189
200
  } catch (error) {
201
+ // Network / fetch failure — no HTTP status. Left undefined so callers treat
202
+ // it as a system failure, not a credential rejection.
190
203
  return {
191
204
  ok: false,
192
205
  error: error instanceof Error ? error.message : 'Network error',