Notechondria

Version: 0.1.67 Build Date: 2026-04-23T00:00

What's Changed

User-reported: opening a #/notes/<uuid> share URL in a fresh tab didn't always land on the note. Three root causes bundled into one fix:

  • Regex was over-strict. The fragment pattern in editor_app/lib/app_shell.dart used ^/?notes/<uuid>$ — the trailing $ anchor rejected any suffix, so #/notes/<uuid>/ (trailing slash) and #/notes/<uuid>?ref=share (tracking query) both failed to match. Relaxed to drop the ^ and $ anchors; the UUID shape is still strict enough that it won't false-match on unrelated fragments.
  • OAuth callback stripped the fragment. app_shell_oauth_mixin.dart::handleOAuthCallback cleaned the URL with uri.removeFragment().replace(queryParameters: {}) so a refresh wouldn't re-process the OAuth code. But that also dropped a #/notes/<uuid> that survived the OAuth round-trip — common on "click share link → redirected to login → sign in with Google" flows. Fix: keep the fragment, only clear the query.
  • Private-note 403 surfaced as raw exception text. _openNoteByUuid stuffed the backend's 403 string straight into _errorMessage. Now the editor detects permission / forbidden / 403 with no active session and substitutes a clear "This note is private. Sign in to view it" prompt so a cold share-link open actually tells the user what to do.

Regression test for the full cold-start deep-link path (with and without session, with and without OAuth callback in the URL) is tracked in docs/TODO.md — the editor smoke-test harness doesn't have a web navigator shim yet.

Multi-device session manager — HTTP client methods (3 apps)

  • 0.1.65 shipped the backend for multi-session auth (creators.Session, MultiSessionAuthentication, the two new endpoints GET /api/v1/auth/sessions/ and DELETE /api/v1/auth/sessions/<id>/). 0.1.67 adds the HTTP client glue so the UI work can slot in without more plumbing:
    • Shared AuthClient interface gains listSessions(token) and revokeSession(token, sessionId).
    • All three per-app HttpNotechondriaClient classes (editor, planner, portal) implement them against /auth/sessions/ and /auth/sessions/<id>/.
    • Per-app abstract NotechondriaClient classes dropped their redundant checkSession / logout decls so the inherited AuthClient is the single source of truth. (Both were already duplicated there from a pre-share refactor.)
  • Active Sessions card UI + multi-device warning banner remain deferred in TODO.md — both now unblocked by this round.

Splash — mobile cross-fade gap

  • On narrow (mobile) layouts the prev / active / next metabolite skeletal formulas all disappeared briefly once per full rotation, producing a visible blank moment. Root cause: the outer if (activePos.dx > -30) guard in splash_painter.dart::paint skipped the ENTIRE crossfade block whenever the active cycle node drifted off the left edge — even though the previous and next formulas sit at different positions on the ring. paintFormulaAt already does its own per-formula off-screen test, so the outer gate was redundant and harmful. Removed. Also dropped the now-dead activePos local.

Files Changed

  • VERSION — bumped 0.1.66 → 0.1.67.
  • frontend/editor_app/lib/app_shell.dart — relaxed _noteUuidPattern regex (drop ^…$ anchors).
  • frontend/editor_app/lib/core/auth_flows.dart_openNoteByUuid now substitutes a "Sign in to view this note" message on 403/permission errors when there's no active session.
  • frontend/editor_app/lib/core/client.dart — dropped redundant checkSession / logout duplicates (inherited from shared AuthClient now).
  • frontend/editor_app/lib/core/http_client.dart — new listSessions and revokeSession implementations.
  • frontend/planner_app/lib/core/client.dart — dropped duplicate decls; added listSessions + revokeSession impls.
  • frontend/portal_app/lib/core/client.dart — added listSessions + revokeSession impls.
  • frontend/notechondria_shared/lib/src/app_shell/auth_client.dart — interface gains listSessions(token) and revokeSession(token, sessionId).
  • frontend/notechondria_shared/lib/src/app_shell/app_shell_oauth_mixin.darthandleOAuthCallback preserves the fragment when cleaning the URL after processing the OAuth ?code=&state=.
  • frontend/notechondria_shared/lib/src/components/splash_painter.dart — removed the outer activePos.dx > -30 gate and the unused activePos local.
  • docs/TODO.md — closed the deep-link bug entry (replaced with a regression-test follow-up) and the start-up-animation cross-fade entry; updated the multi-device-session-manager entry to reflect the shipped HTTP methods.