Notechondria

Version: 0.1.18 Build Date: 2026-04-10T00:00

What's Changed

Planner — Learner folder grouping

  • The Learner (notes) view now groups notes into expandable course folders instead of a single flat list. Each folder shows its course title, a note count, and an ExpansionTile that defaults to expanded. Notes inside each folder keep the existing chronological order (most recent on top).

Planner — Activity view ics import with confirmation

  • The Activity view's floating add button now long-presses to open an import / subscribe menu. The Import iCal file path accepts both raw .ics files and .zip archives (extracting the first .ics entry with the pure-Dart archive package), then surfaces a confirmation dialog before the feed is created.
  • The confirmation dialog parses the iCal on the client with a lightweight RFC 5545 scanner that unfolds continuation lines, walks BEGIN:VEVENT/END:VEVENT blocks, and extracts SUMMARY, DTSTART and X-WR-CALNAME. It displays the event count, the first/last event dates, and the first five sample events with their start times. The default title is prefilled from X-WR-CALNAME with an inline editable text field.
  • Added archive: ^3.4.10 to planner_app/pubspec.yaml. The package is only used at import time, so startup cost stays minimal.
  • Root-caused the "cannot subscribe from Google Calendar share links" bug. The backend was fetching feed.source_url verbatim with urllib.request.urlopen, which fails against Google Calendar HTML share URLs (/calendar/embed?src=… or ?cid=<base64>) because those are HTML pages, not iCal streams. The Grammarly stack trace the user saw was unrelated browser-extension noise.
  • Added normalize_calendar_url(url) in backend/notes/services.py. It recognizes the two most common Google Calendar share shapes and rewrites them to the canonical https://calendar.google.com/calendar/ical/<id>/public/basic.ics form. Non-Google URLs (iCloud, Outlook, raw .ics) pass through unchanged.
  • CalendarFeedListCreateApiView.post now normalizes source_url before persisting, so the stored feed is always fetchable.
  • read_calendar_feed now issues the HTTP GET via a urllib.request.Request with a real User-Agent and Accept: text/calendar header, working around providers that reject default Python user agents.
  • Frontend: the subscribe dialog's helper text now tells users to prefer the "Secret address in iCal format" from Google Calendar settings and notes that public share URLs and direct .ics URLs also work.
  • New NormalizeCalendarUrlTests in backend/notes/tests.py cover pass-through, embed?src= rewrites, cid= base64 rewrites, direct .ics URLs, and empty inputs.

Portal — Full sidebar with Learner / Course / Activity / Settings

  • Portal now surfaces all five modules (Front, Learner, Course, Activity, Settings) via visibleIndices: <int>[0, 1, 2, 3, 4] in portal_app/lib/main.dart, renamed _titles / _destinations in portal_app/lib/app_shell.dart, and real icons for each route.

Portal — Front page with public courses and heatmap

  • Rewrote portal_app/lib/modules/front.dart. The old "portal route cards" widget is gone; the new front page renders three sections driven by the existing /api/v1/front-page/ payload:
    • _PublicCoursesSection — horizontally-scrolling carousel of recent public courses with cover image (or theme-colored placeholder), title and description. Tapping a card routes into the Course view.
    • _HeatmapSection — GitHub-style contribution heatmap that groups cells into 7-row weekly columns, tints past cells with the primary color and upcoming planner load with the tertiary color, and marks the current day with a border. Only visible when authenticated.
    • _RecentPublicNotesSection — compact discovery list of six recent public notes that opens the note viewer on tap.

Portal — Root URL redirect

  • The Pages root *.github.io/Notechondria/ now lands on the portal app. The gh-pages workflow writes a index.html with both a meta http-equiv="refresh" and a JavaScript fallback that preserves any incoming query/hash, so deep links keep working.
  • The docker gateway already routed //portal/ (see deployment/docker/nginx/default.conf), so the fullstack stack was already correct for this task.

Backend — Welcome note on first sign-in

  • Added seed_inbox_and_welcome_note(creator) in backend/notes/services.py. It ensures the creator has a default Inbox category, then inserts a single onboarding note (title, two NoteBlocks, matching NoteIndex rows) if the Inbox is currently empty. The helper is fully idempotent so re-verifying or repeat OAuth sign-ins never duplicate the welcome note.
  • VerifyEmailApiView.post calls the helper after activating the user, giving every email-registered user a populated Inbox on first login.
  • _get_or_create_oauth_user also calls the helper in the "brand-new OAuth account" branch, so Google / GitHub sign-ups land on the same onboarding experience.
  • New WelcomeNoteSeedingTests in backend/notes/tests.py cover the three paths: fresh creator, creator with existing notes in Inbox (idempotent no-op), and creator with an empty Inbox course already present (reuse, do not duplicate).

Docs — Versions index

  • docs/SUMMARY.md now has a Versions section listing every release doc under docs/versions/ in reverse chronological order, with the two most recent entries annotated so the mdBook site doubles as a changelog.

Deferred

  • Task 14 (Portal Settings aggregation with full feature parity to editor Settings) is only partially delivered in 0.1.18: the Settings module is now visible in portal's sidebar and covers account/preferences/sync, but the v0.1.17 editor-only additions (API key section, password/email change dialogs with identity-code verification, config file download) still need to be ported into portal's modules/settings.dart. This requires syncing client methods, app_shell callback wiring, and the _ApiKeySection widget; tracking for 0.1.19.

Files Changed

  • frontend/planner_app/lib/main.dart — 4-module visibleIndices, archive import
  • frontend/planner_app/lib/app_shell.dart — fixed hardcoded index references for the 4-module planner (Settings at index 3, Course at 1)
  • frontend/planner_app/lib/modules/learner.dart_NoteFolder, _groupNotesByCourse, _NoteFolderSection folder-grouped rendering
  • frontend/planner_app/lib/modules/activity.dart — comprehensive _showImportCalendarDialog with .ics/.zip input, RFC 5545 preview parsing, confirmation dialog; subscribe dialog helper text update
  • frontend/planner_app/pubspec.yamlarchive: ^3.4.10
  • frontend/portal_app/lib/main.dart — 5-module visibleIndices
  • frontend/portal_app/lib/app_shell.dart — renamed titles/destinations
  • frontend/portal_app/lib/modules/front.dart — rewrite with _PublicCoursesSection, _HeatmapSection, _RecentPublicNotesSection
  • .github/workflows/frontend-pages.yml — root index.html redirect to ./portal/
  • backend/notes/services.pynormalize_calendar_url, seed_inbox_and_welcome_note, User-Agent header on feed reads
  • backend/notes/api.py — normalize URL in CalendarFeedListCreateApiView
  • backend/notes/tests.pyNormalizeCalendarUrlTests, WelcomeNoteSeedingTests
  • backend/creators/api.py — welcome-note seeding in VerifyEmailApiView.post and _get_or_create_oauth_user
  • docs/SUMMARY.md — new Versions section
  • docs/TASKS.md — moved completed items into the 0.1.18 section
  • docs/versions/0.1.18.md — this document
  • VERSION — bumped from 0.1.17 to 0.1.18