Notechondria
Version: 0.1.82 Build Date: 2026-04-27T01:30
What's Changed
AppShellSessionMixin — eighth and final cross-app mixin shipped
Closes the 0.1.52 → 0.1.82 cross-app deduplication arc. All eight
mixins planned in docs/TODO.md are now in
notechondria_shared/lib/src/app_shell/ (with one in
lib/src/http/). The largest single body consolidated this round:
applyAuthPayload was ~120 lines per app and logout was ~35
lines — the biggest dedup target left after the 0.1.78–0.1.81
sweep handled the smaller mixins.
-
New
frontend/notechondria_shared/lib/src/app_shell/app_shell_session_mixin.dartexposes two concrete public methods (the only auth surface the per-app code calls into):applyAuthPayload(payload)— establishes a new session after password login / OAuth / email-verify / session-restore. Fetches the user's server/settings/, reconciles client-vs-serverapp_settingstimestamps, applies local theme + log-preference settings, stampstoken/profile/settingson the State, firesloadInitialData(), and pushes any locally-created courses + drafts. On a 401 / 5xx during settings fetch, falls back to cached local settings so the user still gets a usable session.logout()— best-effort cloud logout, then clearstoken/profile/settings/deletedNotesplus per-app session metadata, drops the persisted session, and refiresloadInitialData()so the UI lands on the anonymous front page.
Five hooks for per-app session-shape divergences
The TODO underestimated how much the three apps' bodies actually diverged. These hooks isolate the differences:
applySessionMetadata(payload)— editor populates_currentSessionId/_multiDevice/_otherSessionsCountfrom the 0.1.65 multi-device payload shape. Planner / portal default no-op.clearSessionMetadata()— editor clears those three fields on logout. Planner / portal no-op.clearAppSpecificSessionFields()— planner + portal use this to reset_plannerEventson logout. Editor no-op.persistSession(token, user)— editor calls_LocalAppStore.saveSession. Planner / portal default no-op (they don't persist the session token client-side; every cold boot re-authenticates).clearPersistedSession()— editor calls_LocalAppStore.clearSession. Same per-app pattern.
Per-app abstract surface (forwarders to existing helpers)
Five method hooks each app's State overrides to forward to the
existing per-app core/...dart helpers — no new code, just
public-named entry points the mixin can call:
currentAppSettingsPayload({themePreset, themeMode, apiBaseUrl})→_currentAppSettingsPayload(...)applyLocalAppSettings(settings, {persist})→_applyLocalAppSettings(...)loadInitialData()→_loadInitialData()syncAllLocalCourses()→_syncAllLocalCourses()syncAllLocalDrafts()→_syncAllLocalDrafts()
Plus the read-write field surface this mixin needs:
set token/get profile+set/get settings+set/get deletedNotes+set— all one-liners on each State.
Behavioral change: editor's api_base_url safety promoted to all apps
Editor's 0.1.66 fix — api_base_url is CLIENT-side state and the
server's stored value (which defaults to
http://localhost:9080/api/v1) must NEVER overwrite the user's
real URL on login — was promoted to the shared mixin. Planner +
portal now get the same protection for free. Their old
applyAuthPayload bodies used settings['api_base_url'] as the
fallback in the build-from-defaults branch, which would have
silently broken any planner / portal user whose server app_settings
was empty (e.g. first login from a fresh device). Strict
improvement, but worth flagging.
Style change: setState → refreshState
Planner + portal's old applyAuthPayload wrapped the field
mutation in setState(() { _token = token; ... }). Editor used
inline assign + a separate refreshState() call. The mixin can't
call protected setState, so it uses refreshState()
(equivalent to if (mounted) setState(() {})). Functionally the
same — the rebuild fires after the assignments either way.
_logout extension renamed logout (planner + portal)
Planner + portal's old logout flow lived in core/logout.dart
as an underscored extension method _logout. The mixin's public
logout() replaces both. Two places needed updating:
core/logout.dartreduced to a documentation shim (file kept so the parts manifest inlib/main.dartdoesn't break).- One call site per app:
onLogout: _logout→onLogout: logout.
Files Changed
VERSION— 0.1.81 → 0.1.82.frontend/notechondria_shared/lib/src/app_shell/app_shell_session_mixin.dart— new shared mixin (~340 lines, well under §1.5 1000-LOC cap).frontend/notechondria_shared/lib/notechondria_shared.dart— exportsAppShellSessionMixin.frontend/editor_app/lib/app_shell.dart— mixin slot + 14 override one-liners;applyAuthPayload+logoutdeleted inline (~165 lines removed); stale_parseUpdatedAthelper was already insettings_helpers.dartso unaffected.frontend/planner_app/lib/app_shell.dart— mixin slot + 12 override one-liners (no multi-device);applyAuthPayload+_parseUpdatedAtdeleted inline (~120 lines).frontend/planner_app/lib/core/logout.dart— reduced to a documentation shim.frontend/portal_app/lib/app_shell.dart— same shape as planner.frontend/portal_app/lib/core/logout.dart— shim.docs/TODO.md— mixin entry rewritten "1 of 8" → "8 of 8 COMPLETE"; whole bullet checked off.
Notes
- All eight cross-app mixins now shipped. The remaining work
in
app_shell/-style code is per-app (UI surfaces, app-specific state,_LocalAppStoreschemas) and shouldn't have further byte-identical chunks across the three apps. - All four packages (editor / planner / portal / shared) pass
flutter analyze(zero errors) andflutter testsmoke suites. - §1.5 1000-LOC cap respected — and notably, both planner and
portal
app_shell.dartSHRANK by ~67 lines this round (971 → 904 portal; 969 → 902 planner). The cap pressure flagged in 0.1.80.md is now resolved. Largest file in the repo islearner_note_editor.dartat 939 (planner / portal). - Behavioral micro-changes in planner + portal:
api_base_urlno longer falls back to the server's value in the build-from-defaults branch ofapplyAuthPayload(now always uses the local value, matching editor's 0.1.66 fix).- Field mutations during
applyAuthPayloaduserefreshState()instead of an explicitsetState(() { ... })block; same end result. Both changes are net-positive correctness improvements; flagged here so they don't surprise anyone reviewing the diff.
- Pre-existing unrelated warnings on planner / portal
app_shell(_handleDestinationSelectedunused; deprecatedsurfaceVariant/withOpacity) carried over — separate from this round. - The two
core/logout.dartshim files in planner / portal can be deleted entirely once a future round regenerates the parts manifest in each app'slib/main.dart. Left for now to keep this round's diff focused on the mixin port.