Notechondria
Version: 0.1.46 Build Date: 2026-04-19T17:00
What's Changed
Three user-reported items, all landed this round:
1. Invalid-token / bind-without-token fix replicated to planner + portal
The editor fix from 0.1.20 was never ported to the other two apps, so a stale/revoked token still silently fell through into offline mode with a phantom signed-in identity, and a social-account bind started between click and OAuth-callback with no session left went into a confusing login flow instead of a clear "session expired" message.
Both app_shell.dart files now:
- Detect a rejected token at the end of
_loadInitialData()by scanning theerrorslist forinvalid token,authentication credentials were not provided, ortoken_not_valid(case-insensitive). On match, clear the in-memory_token+_profileviasetState. Planner and portal don't persist the session to disk (only editor does), so no_LocalAppStore.clearSession()call is needed — in-memory reset is the full fix there. - Short-circuit the
intent == 'bind'branch when_tokenis null/empty: log a warning underPlanner.Auth/bind/Portal.Auth/bindand show a snackbar telling the user to sign in first, instead of silently falling through to the public login endpoint withintent=bind(which produced a generic 400 that looked like a backend bug). - Log at warning level with the message "Session expired — signed out. Please sign in again." when a rejected token is detected during bootstrap, matching the editor's pattern.
2. offline_mode toggle — now in all three apps
A new shared preference wired end-to-end:
- notechondria_shared/lib/src/settings/app_preferences_card.dart:
new
SwitchListTile.adaptiverow below the theme row, rendered only when the host supplies bothofflineModeandonOfflineModeChanged. Subtitle: "Skip remote fetches at startup. The app renders from the local cache only — sign-in and explicit cloud pulls still work on demand." _LocalAppStore.defaultSettings()in all three apps now seedsoffline_mode: false.- Settings modules in all three apps forward a new optional
callback
onOfflineModeChanged(bool)throughAppPreferencesCard. The toggle fires immediately instead of waiting for the preferences-save button — this is a one-boolean flag that doesn't participate in the "dirty/save" flow. - Each app_shell.dart gets a new
_setOfflineMode(bool)helper that calls_applyLocalAppSettings({'offline_mode': value})to persist tonotechondria.local_settings, logs the toggle under<App>.Sync.Settings/offline_mode, and re-runs_loadInitialData()so the mode takes effect immediately. _loadInitialData()in all three apps now checks_localSettings['offline_mode'] == trueup front and, when true, renders from the local cache + returns before issuing any remote fetch. Sign-in and explicit sync buttons still work because those paths callwidget.clientdirectly, not through_loadInitialData.
3. Editor note view: overflow menu + single FAB
Before: the note editor had a plain IconButton(Icons.more_horiz)
that opened the meta dialog directly, plus a standalone editor-mode
dropdown in the top bar, plus a two-FAB column (attachments list +
attach file).
After (frontend/editor_app/lib/modules/note_editor.dart):
- The "..." is now a
PopupMenuButton<String>with three action groups:- Edit note meta (opens the existing
_openDetailsdialog). - Switch editor: Plain text / Switch editor: Live
markdown as
CheckedPopupMenuItems, so the current mode carries a visual check mark. Picking one calls the existing_setEditorMode(mode). - View attachments (opens the existing
_openAttachmentsListbottom-sheet). Hidden when the note has no attachment support (widget.onUploadAttachment == null).
- Edit note meta (opens the existing
- The top-bar
DropdownButtonFormField<String>editor picker is gone. The title field now stretches across the full top-bar row on desktop instead of competing for 220 px of sidebar space. The narrow-layout second row that held the dropdown is also removed. - The two-FAB Column is collapsed to a single
FloatingActionButton.smallcarrying the paperclip (heroTag: 'editor-attach-file'). The previouseditor-attachments-listFAB is deleted — that action now lives in the popup menu under "View attachments".
Files Changed
New
docs/versions/0.1.46.md(this file).
Modified
VERSION: 0.1.45 → 0.1.46.- frontend/notechondria_shared/lib/src/settings/app_preferences_card.dart:
new
offlineMode+onOfflineModeChangedoptional props andSwitchListTile.adaptiverow. - frontend/editor_app/lib/core/local_store.dart,
frontend/planner_app/lib/core/local_store.dart,
frontend/portal_app/lib/core/local_store.dart:
defaultSettings()seeds'offline_mode': false. - frontend/editor_app/lib/modules/settings.dart,
frontend/planner_app/lib/modules/settings.dart,
frontend/portal_app/lib/modules/settings.dart:
new
onOfflineModeChangedconstructor prop, forwarded intoAppPreferencesCard. - frontend/editor_app/lib/app_shell.dart:
new
_setOfflineModehelper,_loadInitialDatagates onoffline_mode,onOfflineModeChanged: _setOfflineModewired to the settings page. - frontend/planner_app/lib/app_shell.dart:
same offline-mode wiring + the 0.1.20-style invalid-token
session-clear in
_loadInitialData+ bind-without-token short-circuit in_handleOAuthCallback. - frontend/portal_app/lib/app_shell.dart:
same offline-mode wiring + the 0.1.20-style invalid-token
session-clear in
_loadInitialData+ bind-without-token short-circuit in_handleOAuthCallback. - frontend/editor_app/lib/modules/note_editor.dart:
top-bar overflow
IconButton→PopupMenuButton; editor-mode dropdown removed; two-FAB column collapsed to a single attach FAB.
Verification
editor_app/planner_app/portal_app:flutter analyzeissue counts unchanged vs 0.1.45 (54 / 70 / 68); no errors.flutter test test/smoke_test.dartpasses on all three.
Notes / follow-ups
- Offline-mode side-effects not yet gated:
_LearnerPagestill lazily fetches public notes on first scroll even when offline_mode is true, and the category auto-sync triggered by note drag/drop still hits/courses/unconditionally. Fold these into a later pass once the toggle's telemetry shows the coarse-grained boot skip is actually working. - Session-clear on refresh: the invalid-token detector only
fires during
_loadInitialData. A rejected-token response from a later action (e.g. a mid-session note save after the server revokes the token) still bubbles as a raw error. Route all post-bootstrap errors through the same detector in a future round. - Editor-mode picker surface: "Switch editor" now lives inside the overflow menu, which is two clicks deep vs the previous one-click dropdown. Acceptable trade-off per the user's request, but if feedback surfaces that it's too deep we can promote it to a segmented control adjacent to the paperclip FAB.