Notechondria
Version: 0.1.78 Build Date: 2026-04-26T23:30
What's Changed
AppShellLocalPersistMixin — fourth shared mixin shipped
Continues the cross-app deduplication kicked off in 0.1.54 (Log),
0.1.55 (AuthActions), and 0.1.56 (OAuth). One of the five mixins
listed in docs/TODO.md lands this round; four remain.
-
New
frontend/notechondria_shared/lib/src/app_shell/app_shell_local_persist_mixin.dartexposes a uniform persist surface:- Read-side getters —
localSettings,localDrafts,localCourses,localStats,persistedUiLogs. Each app's_AppShellStateoverrides them with=> _localXone-liners. - Write-side adapters —
saveLocalSettings(value),saveLocalDrafts(value),saveLocalCourses(value),saveLocalStats(value),saveLocalLogs(value). Each app overrides them with=> _LocalAppStore.saveX(value)one-liners. - Concrete persist methods —
persistLocalSettings(),persistLocalDrafts(),persistLocalCourses(),persistLocalStats(),persistUiLogs(). Each is a one-liner that wires the read getter to the write adapter. These replace the byte-identical_persistLocal*()extension methods that were duplicated three times.
- Read-side getters —
-
AppShellLogMixin.persistUiLogs(declared abstract since 0.1.54) is now satisfied automatically:AppShellLocalPersistMixinprovides a concrete implementation with the matching signature, so each app's_AppShellStateno longer needs theFuture<void> persistUiLogs() => _persistUiLogs();forwarder.
Three apps ported
Each app's wiring follows the same shape:
-
_AppShellStatemixes inAppShellLocalPersistMixin<AppShell>(slotted betweenAppShellLogMixinandAppShellAuthActionsMixinin thewithclause). Five getter overrides + five adapter overrides in the State class — all one-liners. -
core/local_persist.dartextension trimmed to just_persistLocalCache(the only persist helper that diverges per app — editor / planner / portal each merge different fields into the cache bucket). The extracted methods are documented in the top-of-file comment so a reader landing here knows where they went. -
All call sites of
_persistLocalSettings()/_persistLocalDrafts()/_persistLocalCourses()/_persistLocalStats()/_persistUiLogs()renamed to drop the leading underscore. Per app: editor 11 files / 42 sites, planner 8 files, portal 8 files. Public name on the mixin, consistent with the existingappendUiLog/logpattern.
What stayed per-app
_persistLocalCache() lives in each app's core/local_persist.dart
because the three apps pack different fields into the cache map:
- editor —
front_page+courses+updated_at. - planner —
courses+activity+planner_events+activity_week+updated_at. - portal —
front_page+courses+activity+updated_at.
Pulling these into the shared mixin would require a per-app hook
returning a Map<String, dynamic> for the merge — net-zero or
slightly negative dedup. Left as-is.
TODO.md updated
Cross-app shared mixins — remaining 5 of 8 rewritten to
remaining 4 of 8. The four still pending (Session, DraftHelpers,
CourseHelpers, HttpClientInternals) each warrant their own round
because their abstract surfaces are bigger and the porting is
riskier.
Files Changed
VERSION— 0.1.77 → 0.1.78.frontend/notechondria_shared/lib/src/app_shell/app_shell_local_persist_mixin.dart— new shared mixin.frontend/notechondria_shared/lib/notechondria_shared.dart— exportsAppShellLocalPersistMixin.frontend/editor_app/lib/app_shell.dart— mixin slot + 10 override one-liners; drops the explicitpersistUiLogsforwarder.frontend/editor_app/lib/core/local_persist.dart— trimmed to_persistLocalCache+ documentation pointer.frontend/editor_app/lib/core/category_actions.dart,core/draft_sync.dart,core/initial_data.dart,core/local_course_builders.dart,core/local_starter.dart,core/local_trash.dart,core/maintenance_actions.dart,core/note_crud.dart,core/settings_actions.dart,core/settings_helpers.dart—_persistLocalX→persistLocalXrename (call sites only).frontend/planner_app/lib/app_shell.dart— same wiring shape as editor.frontend/planner_app/lib/core/local_persist.dart— trimmed.frontend/planner_app/lib/core/draft_sync.dart,core/local_course_builders.dart,core/local_starter.dart,core/local_trash.dart,core/maintenance_actions.dart,core/note_crud.dart,core/settings_actions.dart,core/settings_helpers.dart— call-site rename.frontend/portal_app/lib/app_shell.dart— same wiring shape.frontend/portal_app/lib/core/local_persist.dart— trimmed.frontend/portal_app/lib/core/draft_sync.dart,core/local_course_builders.dart,core/local_starter.dart,core/local_trash.dart,core/maintenance_actions.dart,core/note_crud.dart,core/settings_actions.dart,core/settings_helpers.dart— call-site rename.docs/TODO.md— mixin entry rewritten "5 of 8" → "4 of 8";AppShellLocalPersistMixinremoved from the pending list.
Notes
- All four packages (editor / planner / portal / shared) pass
flutter analyze(zero errors; only pre-existing info / lint noise) andflutter testsmoke suites. - §1.5 1000-LOC cap respected: largest file in the repo is now
learner_note_editor.dartat 939 lines (planner / portal each). The new mixin file is 90 lines. Editorapp_shell.dartshrank slightly (one fewer override). Planner / portalapp_shell.dartgrew ~14 lines each from the new wiring (they sit at 934 / 936). - Behavioral surface unchanged: every call to
persistLocalX()routes to the same_LocalAppStore.saveX(_localX)body it did before. The rename is mechanical; the dedup is in the bodies the mixin now owns. - Ordering note: when both
AppShellLogMixinandAppShellLocalPersistMixinare in thewithclause,AppShellLocalPersistMixinmust come AFTER the log mixin because it provides the concretepersistUiLogs()body that satisfies the log mixin's abstract declaration. Dart resolves method-from-mixin via "rightmost wins" — get the order wrong and you'd get back to needing the explicit forwarder.