Notechondria

Version: 0.1.38 Build Date: 2026-04-18T16:00

What's Changed

.nchron v1 local-data archive (shared spec + editor wiring)

Replaces the minimal .env-style config-file download with a full-state versioned ZIP export/import pair. This is commit 1 of 3 in the plan from 0.1.37 \u2014 spec + shared helpers + editor end-to-end; planner + portal wire-through remains in docs/TODO.md.

Spec

New docs/export_format_v1.md documents the entire package layout, manifest schema, attachments/ promotion rules, cross-app portability policy, importer version gating, and the legacy .env migration shim. Current package format version is 1.

Shared helpers

New frontend/notechondria_shared/lib/src/utils/local_archive.dart provides:

  • writeLocalArchive(LocalArchiveInput) \u2192 Uint8List ZIP bytes.
  • readLocalArchive(Uint8List) \u2192 LocalArchiveOutput with a non-null errorMessage on failure. The output rehydrates queued_attachments paths back into bytes_base64 so the host's existing sync promotion code works without special-casing imports.
  • tryReadLegacyEnvConfig(Uint8List) \u2192 Map<String, String>? for the pre-0.1.38 .env migration shim.
  • LocalArchiveApp enum + tag/fromTag extension for exporter attribution across editor / planner / portal.
  • kLocalArchivePackageVersion constant (1).

Promotes every draft's metadata_json['queued_attachments'] binary payload out to real archive entries under attachments/<draft-id>/ and rewrites the queue with path pointers. On read, the pointers rehydrate back to base64 so the existing Editor.Sync.Notes/attachment.promote pass uploads them normally the next time the draft syncs.

archive: ^3.4.10 added to notechondria_shared/pubspec.yaml; exports wired through the barrel (notechondria_shared.dart).

Shared tests

New frontend/notechondria_shared/test/local_archive_test.dart exercises six scenarios:

  • Editor bucket round-trip.
  • Attachment promotion on write + rehydration on read.
  • Future-version rejection.
  • Legacy .env detection.
  • Legacy sniff correctly ignores plain ZIPs.
  • Empty-payload returns a \u00a71.7-shaped Shared.LocalArchive/read error.

All six pass (flutter test -r compact in frontend/notechondria_shared).

Editor wiring

frontend/editor_app/lib/app_shell.dart:

  • Removed _buildConfigFileContent + _downloadConfigFile (the legacy .env download path).
  • Added _exportLocalArchive() \u2014 builds a LocalArchiveInput from current session state (profile stripped of token + API key prefix per the v1 spec), writes the ZIP, and uses getSaveLocation / XFile.fromData to drop it to disk with a .nchron extension. Success/fail messages follow \u00a71.7 as Editor.LocalStore/export_zip.
  • Added _restoreFromLocalImport() \u2014 picks a file via openFile, sniffs for legacy .env first (runs the migration shim on hit), else parses via readLocalArchive. On successful parse, shows a 5-second ConfirmWithDelayDialog summarizing the archive counts + exporter app, then replaces every _LocalAppStore bucket in one transaction, rebinds the debug log controller, and re-runs _loadInitialData to refresh UI. Source: Editor.LocalStore/restore_from_import.

frontend/editor_app/lib/modules/settings.dart:

  • Dropped onDownloadConfig parameter from _SettingsPage.
  • Added onExportLocalData + onRestoreFromLocalImport optional callbacks.
  • Configuration section now shows "Download local user data" and "Restore from local imports" buttons when the callbacks are present.

Editor SettingsPage call site in app_shell.dart now passes the new two callbacks instead of onDownloadConfig.

Backward compatibility

  • The legacy .env downloads users may already have on disk still import cleanly via the sniff path: API_BASE_URL is applied through _applyLocalAppSettings and the other local state stays untouched.
  • Archive format bumps (e.g. adding a new planner_events.json bucket) can happen without bumping the VERSION file as long as the new file stays optional; the importer already treats missing optional files as empty defaults.

Files Changed

New

  • docs/export_format_v1.md \u2014 spec.
  • docs/versions/0.1.38.md (this file).
  • frontend/notechondria_shared/lib/src/utils/local_archive.dart \u2014 helpers.
  • frontend/notechondria_shared/test/local_archive_test.dart \u2014 6 passing unit tests.

Modified

  • VERSION: 0.1.37 \u2192 0.1.38.
  • docs/TODO.md: export/import task marked partially done; planner + portal replication and cross-app test matrix deferred.
  • frontend/notechondria_shared/pubspec.yaml: adds archive: ^3.4.10.
  • frontend/notechondria_shared/lib/notechondria_shared.dart: exports the new local_archive symbols.
  • frontend/editor_app/lib/app_shell.dart: removes legacy download code, adds _exportLocalArchive + _restoreFromLocalImport, updates _SettingsPage call site.
  • frontend/editor_app/lib/modules/settings.dart: replaces onDownloadConfig with onExportLocalData + onRestoreFromLocalImport; Configuration buttons updated.

Verification

  • notechondria_shared: flutter analyze \u2014 2 pre-existing surfaceVariant deprecation infos; no new errors. flutter test -r compact \u2014 6 tests pass.
  • editor_app: flutter analyze \u2014 52 issues (same as 0.1.37; no new lint or errors). flutter test test/smoke_test.dart -r compact \u2014 passes.
  • planner_app / portal_app: still analyze + smoke-test clean with the updated shared dependency.

Notes / follow-ups

  • Planner + portal wiring \u2014 their Settings pages do not yet surface the new two buttons, and their app_shells still lack _exportLocalArchive / _restoreFromLocalImport. Replicate the editor pattern and add the app-specific buckets (plannerEvents, calendarFeeds, activityWeek for planner; frontPage for portal) to their LocalArchiveInput.
  • Cross-app import coverage \u2014 the shared parser already returns empty defaults for missing optional files, so an editor export imports cleanly into planner / portal (and vice versa). Add cross-app round-trip tests in notechondria_shared/test/local_archive_test.dart when the planner + portal wiring lands so the matrix stays green.
  • UI copy pass \u2014 "Restore from local imports" reads a bit awkwardly; consider "Restore from backup" in a later round.