From fa0edc16317a6df2251df29e43cf3b24da5da952 Mon Sep 17 00:00:00 2001 From: NiloCK Date: Tue, 9 Sep 2025 11:28:49 -0300 Subject: [PATCH 01/14] add working doc --- agent/a.2.refactor-plan.md | 53 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 agent/a.2.refactor-plan.md diff --git a/agent/a.2.refactor-plan.md b/agent/a.2.refactor-plan.md new file mode 100644 index 000000000..5af83cedd --- /dev/null +++ b/agent/a.2.refactor-plan.md @@ -0,0 +1,53 @@ +# Refactor Plan: Modularize Session Logic + +## 1. Goal + +Refactor `StudySession.vue` and `SessionController.ts` to establish a clean separation of concerns. The `SessionController` will be refactored to act as a **Façade**, orchestrating a suite of modular, single-purpose sub-services. This will make the view component "dumber" and the core logic more modular, testable, and extensible. + +## 2. Target Architecture + +1. **`StudySession.vue` (The View):** + - Becomes a thin layer responsible for UI rendering and capturing user events. + - Its single major dependency for all study logic will be the `SessionController`. + +2. **`SessionController.ts` (The Façade/Orchestrator):** + - Provides a high-level API to the view layer (e.g., `nextCard()`, `submitResponse()`). + - It will no longer contain complex implementation details. + - Instead, it will be composed of and delegate to the various sub-services. + +3. **Sub-Services (The Logic):** + - New, single-purpose classes will be created to house the specific business logic. + - Examples: + - `SrsService.ts`: Handles Spaced Repetition scheduling logic. + - `EloService.ts`: Handles ELO score calculations and updates. + - `ResponseProcessor.ts`: Uses the above services to interpret a user's `CardRecord` and determine the outcome. + - `CardHydrator.ts`: (Optional but recommended) Handles the logic of fetching card data from the database. + +## 3. Implementation Plan + +### Phase 1: Create the Sub-Services + +*This phase involves extracting the business logic currently in `StudySession.vue` into new, focused services.* + +1. **Create `SrsService.ts`:** + - **File:** `packages/db/src/study/services/SrsService.ts` + - **Responsibility:** Move the SRS scheduling logic (currently `scheduleReview` in `StudySession.vue`) into this service. + +2. **Create `EloService.ts`:** + - **File:** `packages/db/src/study/services/EloService.ts` + - **Responsibility:** Move the ELO calculation and update logic (currently `updateUserAndCardElo` in `StudySession.vue`) into this service. + +3. **Create `ResponseProcessor.ts`:** + - **File:** `packages/db/src/study/services/ResponseProcessor.ts` + - **Responsibility:** This service will use the `SrsService` and `EloService` to process a user's response and decide what action the `SessionController` should take next. + +### Phase 2: Refactor `SessionController.ts` to act as a Façade + +1. **Update Constructor:** Modify the `SessionController` constructor to accept the new sub-services as dependencies (Dependency Injection). +2. **Create `submitResponse` Method:** Add a new public method, `submitResponse(response: CardRecord)`, to the controller. This method will delegate the complex logic to the `ResponseProcessor` service and use the result to manage its internal queues. + +### Phase 3: Simplify `StudySession.vue` + +1. **Update Instantiation:** In `initSession`, instantiate the `SessionController` and all the new sub-services it depends on. +2. **Simplify `processResponse`:** The `processResponse` method will be reduced to a few lines: capture the response, call `sessionController.submitResponse()`, and then ask the controller for the next card. +3. **Remove Migrated Logic:** Delete the `updateUserAndCardElo` and `scheduleReview` methods from the component, as that logic now lives in the new services. From 896f595714f480881c022fe9fd42eb185ce32966 Mon Sep 17 00:00:00 2001 From: NiloCK Date: Tue, 9 Sep 2025 11:29:38 -0300 Subject: [PATCH 02/14] refactor: move scheduleReview to own service file --- .../common-ui/src/components/StudySession.vue | 21 +------- packages/db/src/study/SessionController.ts | 12 +++++ packages/db/src/study/services/SrsService.ts | 49 +++++++++++++++++++ 3 files changed, 62 insertions(+), 20 deletions(-) create mode 100644 packages/db/src/study/services/SrsService.ts diff --git a/packages/common-ui/src/components/StudySession.vue b/packages/common-ui/src/components/StudySession.vue index d0c298a15..08cf02b1b 100644 --- a/packages/common-ui/src/components/StudySession.vue +++ b/packages/common-ui/src/components/StudySession.vue @@ -407,7 +407,7 @@ export default defineComponent({ this.loadCard(await this.sessionController!.nextCard('dismiss-success')); cardHistory.then((history: CardHistory) => { - this.scheduleReview(history, item); + this.sessionController.services.srs.scheduleReview(history, item); if (history.records.length === 1) { this.updateUserAndCardElo(0.5 + (r.performance as number) / 2, this.courseID, this.cardID); } else { @@ -526,25 +526,6 @@ export default defineComponent({ return await this.user!.putCardRecord(r); }, - async scheduleReview(history: CardHistory, item: StudySessionItem) { - const nextInterval = newInterval(this.$props.user, history); - const nextReviewTime = moment.utc().add(nextInterval, 'seconds'); - - if (isReview(item)) { - console.log(`[StudySession] Removing previously scheduled review for: ${item.cardID}`); - this.user!.removeScheduledCardReview(item.reviewID); - } - - this.user!.scheduleCardReview({ - user: this.user!.getUsername(), - course_id: history.courseID, - card_id: history.cardID, - time: nextReviewTime, - scheduledFor: item.contentSourceType, - schedulingAgentId: item.contentSourceID, - }); - }, - async loadCard(card: HydratedCard | null) { if (this.loading) { console.warn(`Attempted to load card while loading another...`); diff --git a/packages/db/src/study/SessionController.ts b/packages/db/src/study/SessionController.ts index 6642e7248..e5801882d 100644 --- a/packages/db/src/study/SessionController.ts +++ b/packages/db/src/study/SessionController.ts @@ -1,3 +1,4 @@ +import { SrsService } from './services/SrsService'; import { isReview, StudyContentSource, @@ -87,8 +88,15 @@ class ItemQueue { import { DataLayerProvider } from '@db/core'; +interface SessionServices { + srs: SrsService; +} + export class SessionController extends Loggable { _className = 'SessionController'; + + public services: SessionServices; + private sources: StudyContentSource[]; private dataLayer: DataLayerProvider; private getViewComponent: (viewId: string) => TView; @@ -131,6 +139,10 @@ export class SessionController extends Loggable { ) { super(); + this.services = { + srs: new SrsService(dataLayer.getUserDB()), + }; + this.sources = sources; this.startTime = new Date(); this._secondsRemaining = time; diff --git a/packages/db/src/study/services/SrsService.ts b/packages/db/src/study/services/SrsService.ts new file mode 100644 index 000000000..6ae2aef0a --- /dev/null +++ b/packages/db/src/study/services/SrsService.ts @@ -0,0 +1,49 @@ +import moment from 'moment'; +import { + CardHistory, + CardRecord, + isReview, + newInterval, + StudySessionItem, + UserDBInterface, +} from '@vue-skuilder/db'; +import { logger } from '@db/util/logger'; + +/** + * Service responsible for Spaced Repetition System (SRS) scheduling logic. + */ +export class SrsService { + private user: UserDBInterface; + + constructor(user: UserDBInterface) { + this.user = user; + } + + /** + * Calculates the next review time for a card based on its history and + * schedules it in the user's database. + * @param history The full history of the card. + * @param item The study session item, used to determine if a previous review needs to be cleared. + */ + public async scheduleReview( + history: CardHistory, + item: StudySessionItem + ): Promise { + const nextInterval = newInterval(this.user, history); + const nextReviewTime = moment.utc().add(nextInterval, 'seconds'); + + if (isReview(item)) { + logger.info(`[SrsService] Removing previously scheduled review for: ${item.cardID}`); + void this.user.removeScheduledCardReview(item.reviewID); + } + + void this.user.scheduleCardReview({ + user: this.user.getUsername(), + course_id: history.courseID, + card_id: history.cardID, + time: nextReviewTime, + scheduledFor: item.contentSourceType, + schedulingAgentId: item.contentSourceID, + }); + } +} From 83f041f1c59c1a996ead412c88f6c11ad850f0e0 Mon Sep 17 00:00:00 2001 From: NiloCK Date: Tue, 9 Sep 2025 13:19:59 -0300 Subject: [PATCH 03/14] define response-processing enum type --- .../common-ui/src/components/StudySession.vue | 115 +++++++----------- packages/db/src/study/SessionController.ts | 18 +-- 2 files changed, 48 insertions(+), 85 deletions(-) diff --git a/packages/common-ui/src/components/StudySession.vue b/packages/common-ui/src/components/StudySession.vue index 08cf02b1b..9a6a25879 100644 --- a/packages/common-ui/src/components/StudySession.vue +++ b/packages/common-ui/src/components/StudySession.vue @@ -65,35 +65,35 @@