Notechondria
Version: 0.1.70 Build Date: 2026-04-23T00:00
What's Changed
Bug — frontend API URL reverted on every theme / state change
-
Root cause: every app's
app_shell.darthadclient: widget.client ?? HttpNotechondriaClient()inline in the_NotechondriaAppState.build()method.setStateon that state (e.g. a theme change from_handleThemeChanged) callsbuild()again and re-evaluates the??, minting a freshHttpNotechondriaClientwhose_baseUrlis the compile-time default. That new client replaces the one whose_loadLocalStatehad calledupdateBaseUrl(saved), so subsequent HTTP calls went to the default host (notechondria.trance-0.com) regardless of thenote.zheyuanwu.comthe user had saved in App Preferences. -
Fix: cache the client in a
late final NotechondriaClient _client = widget.client ?? HttpNotechondriaClient()field. Construction happens once per app-instance lifetime; rebuilds read the stored reference. Applied in all three apps: editor_app, planner_app, portal_app. Header comment on each spells out the bug so the next rewrite doesn't reintroduce it.
Bug — backend 400 DisallowedHost for the user's custom domain
-
Root cause: settings.py computed
_default_hostswithBACKEND_CUSTOM_DOMAIN+RENDER_EXTERNAL_HOSTNAMEfolded in, then didALLOWED_HOSTS = env_list("DJANGO_ALLOWED_HOSTS", _default_hosts). IfDJANGO_ALLOWED_HOSTSwas explicitly set in env (as it is in Northflank's Secret Group), the explicit value silently superseded_default_hosts— so settingBACKEND_CUSTOM_DOMAIN=notechondria.trance-0.comhad no effect on ALLOWED_HOSTS. Any request arriving with that Host header (because the user's DNS points the custom domain at the backend) was rejected withdjango.core.exceptions.DisallowedHost: Invalid HTTP_HOST header: 'notechondria.trance-0.com'. You may need to add 'notechondria.trance-0.com' to ALLOWED_HOSTS. -
Fix: after
env_list, unionBACKEND_CUSTOM_DOMAINandRENDER_EXTERNAL_HOSTNAMEintoALLOWED_HOSTSwhen they're not already present (and when*isn't already in the list, which would allow everything anyway). Platform-provided hostnames are always trusted; the env var can still extend the list with extra allowed hosts. Same union logic applied toCSRF_TRUSTED_ORIGINSso cross-origin POSTs from the frontend to the backend's platform hostname don't 403 on CSRF either.FRONTEND_ORIGINgets the same treatment so a GitHub Pages frontend talking to a Render/Northflank backend works without manually listing it.
Files Changed
VERSION— bumped 0.1.69 → 0.1.70.frontend/editor_app/lib/app_shell.dart— cache client in alate final _clientfield on_NotechondriaAppState;build()passes the cached reference instead of re-evaluating the??expression.frontend/planner_app/lib/app_shell.dart— same.frontend/portal_app/lib/app_shell.dart— same.backend/notechondria/settings.py— unionBACKEND_CUSTOM_DOMAIN/RENDER_EXTERNAL_HOSTNAMEintoALLOWED_HOSTS(and mirrored forCSRF_TRUSTED_ORIGINS) regardless of whetherDJANGO_ALLOWED_HOSTSis explicitly set.docs/versions/0.1.70.md— this file.
Notes
- The frontend client caching is a straightforward Flutter
pattern mistake — easy to reintroduce when someone copy-pastes
the
client: widget.client ?? HttpNotechondriaClient()idiom from an older commit. The comment block on each file should be loud enough to catch it in review. - The ALLOWED_HOSTS union runs after
env_list, so operators can still setDJANGO_ALLOWED_HOSTSto a narrow list for defence-in-depth. The guarantee is thatBACKEND_CUSTOM_DOMAINandRENDER_EXTERNAL_HOSTNAME(both set by the operator / platform, not by external traffic) are at least included. - If
DJANGO_ALLOWED_HOSTS=*(the Northflank sample default), the union is a no-op —*already permits everything. The check avoids appending to an already-permissive list. - Frontend requires a rebuild for the fix to take effect.
Triggering a tag or a docs-path commit on
mainwill republish the Pages build with the fixed client caching.