Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

**alvachien.com** is an Angular 21 AI-powered learning platform for English, Chinese, and Knowledge Bank exercises. Built with Angular CLI 21.1.2. The app is fully standalone (no NgModules for feature code) and uses `@jsverse/transloco` for i18n (English + Chinese).
**alvachien.com** is an Angular 22 AI-powered learning platform for English, Chinese, and Knowledge Bank exercises. Built with Angular CLI 22.1.6 (TypeScript ~6.0). The app is fully standalone (no NgModules for feature code) and uses `@jsverse/transloco` for i18n (English + Chinese).

## Development Commands

Expand Down Expand Up @@ -87,8 +87,8 @@ Decomposed into a container component, presentational children, and three signal
- **`VocabularyExercisesWordListComponent`** - the presentational list screen. Receives the container's shared dataSource/selection/signals as inputs and forwards user intents as outputs; wires its template's paginator/sort onto the shared dataSource via `@ViewChild` setters.
- **Signal stores** - `ReviewSessionStore` (queue, cursor, auto-mode interval, per-word `ratingMap`, progress computeds), `SpellingSessionStore` (queue, per-letter reveal state, results), and `QuizSessionStore` (single-choice question queue, per-question answer state, results). Plain `@Injectable()` classes (no state library), provided in the container's `providers` so the container and session screens share one instance per page instance. They are imported directly, not via the barrel.
- **Session screens** - `review-session`, `spelling-session`, `spelling-result`, `quiz-session`, and `quiz-result` components inject their store directly (no state inputs) and each registers a `document:keyup` `@HostListener`, so keystroke handling only lives while that screen is mounted.
- **Dialogs** - one mode-driven select dialog (By Count / Free Selection / By Word), plus `reviewoptions`, `spellingoptions`, `worksheetoptions`, `quizoptions`, and the **shared** `filter` dialog (`src/app/shared/filter-dialog/` — `SharedFilterDialogComponent`, design in `docs/reusable-filter-dialog-design.md`), opened with the page's `VOCABULARY_FILTER_PROPERTIES` schema and an actslib `IFilterDefinition` seed; Submit returns the edited definition, Cancel/backdrop/Esc return `undefined`. It is a project-wide component (all four list pages — vocabulary, knowledge, Chinese and translate — now filter through it, each with its own `*_FILTER_PROPERTIES` schema), not a vocabulary-page file. Options dialogs round-trip `currentSettings` so reopening shows the last picks.
- **Filter pipeline** - free-text + the actslib **`IFilterDefinition`** (word/rating conditions in AND/OR-joined, nestable groups, produced directly by the shared dialog no page-side tree model or translation step) combine into a `VocabularyListFilter` (`interfaces/vocabulary.ts`), serialized as JSON into `MatTableDataSource.filter`; the row predicate delegates to the pure `matchVocabularyListFilter()` (closing over the rating map), which runs `FilterUtility.MatchFilter` against a case-folded target `{ enword, cnword, rating }` (string condition values are folded by each property's `prepareValue` at dialog emit time; free-text stays a hand-written cross-field check). After async rating loads, re-assign `dataSource.filter = dataSource.filter` to force re-filtering.
- **Dialogs** - one mode-driven select dialog (By Count / Free Selection / By Word), plus `reviewoptions`, `spellingoptions`, `worksheetoptions`, `quizoptions`, and the **shared** `filter` dialog (`src/app/shared/filter-dialog/` — `SharedFilterDialogComponent`, design in `docs/reusable-filter-dialog-design.md`, contract in `docs/filter-hierarchy-contract.md`), opened with the page's `VOCABULARY_FILTER_PROPERTIES` schema and an actslib `FilterRoot` seed (a group definition, or a bare single condition); Submit returns the edited `FilterRoot` — a single-condition filter comes back as a bare `IFilterCondition` (`FilterUtility.Simplify` at the boundary) and an empty tree is not submittable (case 0 belongs to the Clear Filter button — a "new filter" dialog opens scaffolded in case 1 instead — one blank, selected condition; the editor tree IS the actslib root — the top level holds a SINGLE node (case 1: one condition row; case 2: one group row with its members nested; the wrapper is never rendered and holds at most one member, its join inert) — and the tree toolbar holds exactly three buttons enabled by the selected node's kind (nothing selected — the empty tree — arms both inserts, a condition arms delete only, a group arms all three); delete returns the selection to the parent group (or to nothing when the tree empties, where the inserts re-arm), so a node is always selected unless the tree is empty); Cancel/backdrop/Esc return `undefined`. It is a project-wide component (all four list pages — vocabulary, knowledge, Chinese and translate — now filter through it, each with its own `*_FILTER_PROPERTIES` schema), not a vocabulary-page file. Options dialogs round-trip `currentSettings` so reopening shows the last picks.
- **Filter pipeline** - free-text + the actslib **`FilterRoot`** (word/rating conditions in AND/OR-joined, nestable groups — a bare condition when the filter is a single condition; produced directly by the shared dialog, no page-side tree model or translation step) combine into a `VocabularyListFilter` (`interfaces/vocabulary.ts`), serialized as JSON into `MatTableDataSource.filter`; the row predicate delegates to the pure `matchVocabularyListFilter()` (closing over the rating map), which runs `FilterUtility.MatchFilter` against a case-folded target `{ enword, cnword, rating }` (string condition values are folded by each property's `prepareValue` at dialog emit time; free-text stays a hand-written cross-field check). After async rating loads, re-assign `dataSource.filter = dataSource.filter` to force re-filtering.
- **Ratings** - list ratings live in a `contentRatingMap` signal; the review store captures only server-confirmed ratings and returns them on `quit()`, which the container merges back into the map. Temporary (uploaded) content gets a synthetic `LearningContent` with a negative id, and rating calls are disabled for it.
- **Quiz exercise** - the Exercises-menu Quiz item reuses the shared word-queue selection (table selection, else filter + `prepareWordQueue`), then builds single-choice questions via the pure `buildVocabularyQuizQuestions()` (`interfaces/vocabulary.ts`): EN word -> pick the CN explanation among 4 candidates (`en2cn`), or CN explanation -> pick the EN word (`cn2en`). Distractors come from the visible (filtered) rows, deduplicated by displayed text; questions that cannot gather one distinct alternative are skipped.

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

An AI-powered web-based Learning app for English, Chinese, and Knowledge Bank.

Built with Angular 21 (standalone components, Signals, Angular Material, Tailwind CSS, Transloco i18n en/zh-CN). This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 21.1.2.
Built with Angular 22 (standalone components, Signals, Angular Material, Tailwind CSS, Transloco i18n en/zh-CN). This project was originally generated with [Angular CLI](https://github.com/angular/angular-cli) version 21.1.2.

### Prerequisites

Expand Down
Loading
Loading