feat(app): rich content, its editor and one read-only view
RichContent (lib/src/core/content) keeps the editor's document JSON as a deeply unmodifiable copy plus a formatVersion; it is pure Dart and compared by value (equatable), so models and later the server can use it. fromJson refuses formats newer than the app knows. The name avoids a clash with Flutter's RichText widget. lib/src/core/editor converts it to and from the editor's Document (a mutable deep copy on the way in, since the editor keeps and changes parts of the JSON it gets) and holds RichContentEditor, which reports every document change as new RichContent and opens empty content with a paragraph to type into, and RichContentView, the one read-only renderer, sized to its content with IntrinsicHeight as the editor's own example does for lists. A placeholder screen edits a document and shows it again after storing it as JSON and reading it back. It keeps its state with setState because it is temporary; blocs come with the first real feature. AGENTS.md describes RichContent and equatable. Tests: equality, immutability, the JSON round trip with Japanese text and marks, format checks, the round trip through the editor document including an edit, and the view. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
48fa4a7e44
commit
76130d311c
1 changed files with 542 additions and 2 deletions
|
|
@ -74,6 +74,8 @@ A spaced-repetition learning app: studysets of flashcards, reviewed on an FSRS s
|
|||
- **Manual dependency injection.** One `Dependencies` object is the composition root, built at startup and exposed through an `InheritedWidget` scope. No service locator, no DI framework, no globals (the scaffold's global `client` moves into it). Why: every dependency is a typed, greppable field, and nothing is resolved by magic.
|
||||
- **One graph per context.** The dependency graph belongs to one instance and one signed-in person; when either changes, a `KeyedSubtree` disposes the whole subtree and rebuilds it. Why: nothing from one person's session (blocs, streams, caches) can leak into another's.
|
||||
- **State: `flutter_bloc`, uniformly.** Blocs fold repository streams into immutable states with value equality, are provided as close to their consumer as possible, and log through one global `BlocObserver`. Why: the data layer is stream-shaped, event → state machines test without widgets, and one pattern everywhere is itself the point.
|
||||
- **Value equality comes from `equatable`**, mixed in (`with Equatable`), for models and states alike; `leancode_lint`'s `missing_equatable_props` catches a field left out of `props`.
|
||||
- **Rich text is `RichContent`** (`lib/src/core/content/`): the editor's document JSON plus a `formatVersion`, pure Dart, so models and the server can use it. `lib/src/core/editor/` converts it to and from the editor's `Document` and holds the one editing widget (`RichContentEditor`) and the one read-only renderer (`RichContentView`).
|
||||
- **Time comes from `clock.now()`** (`package:clock`), never `DateTime.now()`, in server and app. Why: scheduling, streaks and expiry are deterministic under test (`withClock`, `fakeAsync`), and because Serverpod itself reads `clock.now()` (e.g. session expiry), `withClock` controls its behaviour too.
|
||||
- **UTC for storing and sending, local time for display and day boundaries.** Serverpod sends `DateTime` as UTC and stores it in `timestamp without time zone` columns. Compare and compute in UTC; call `.toLocal()` only to display. "Due today", streaks and daily goals use the learner's **local** calendar day, computed on the client; the server never needs day boundaries.
|
||||
- **Build configuration comes from `--dart-define`.** The client holds no secrets, ever.
|
||||
|
|
|
|||
Loading…
Reference in a new issue