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 the errors list for invalid token, authentication credentials were not provided, or token_not_valid (case-insensitive). On match, clear the in-memory _token + _profile via setState. 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 _token is null/empty: log a warning under Planner.Auth/bind / Portal.Auth/bind and show a snackbar telling the user to sign in first, instead of silently falling through to the public login endpoint with intent=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.adaptive row below the theme row, rendered only when the host supplies both offlineMode and onOfflineModeChanged. 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 seeds offline_mode: false.
  • Settings modules in all three apps forward a new optional callback onOfflineModeChanged(bool) through AppPreferencesCard. 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 to notechondria.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'] == true up 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 call widget.client directly, 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 _openDetails dialog).
    • 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 _openAttachmentsList bottom-sheet). Hidden when the note has no attachment support (widget.onUploadAttachment == null).
  • 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.small carrying the paperclip (heroTag: 'editor-attach-file'). The previous editor-attachments-list FAB 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

Verification

  • editor_app / planner_app / portal_app: flutter analyze issue counts unchanged vs 0.1.45 (54 / 70 / 68); no errors. flutter test test/smoke_test.dart passes on all three.

Notes / follow-ups

  • Offline-mode side-effects not yet gated: _LearnerPage still 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.