📎 now picks any file (Android: Camera / Gallery / File; desktop: an
unfiltered dialog) and the desktop page accepts any dropped file. Photo and
screenshot formats up to 64 MB are still re-encoded and shown inline;
everything else is stored byte for byte on dufs beside the images and linked
as [name](url), shown as a row with its size that opens in the platform's
app (Android) or a new browser tab (desktop). BACKLOG.md rewrites file links
to ~/data/cloud/todo-images/ like image links.
There is no size cap, so no layer holds a file in memory: uploads and
downloads stream through DufsImageClient, writes land in a .tmp renamed when
whole (an interrupted download no longer leaves a truncated file that looks
done), the desktop page POSTs the browser's own Blob with fetch, and Android's
"File" uses file_picker, because file_selector's Android side reads the whole
file into memory on the UI thread. Verified with a 1 GB file end to end on
desktop (peak RSS +36 MB) and on an API 36 emulator.
The wrapper serves attachments from the origin that holds the GitHub token
and the notes, so only script-free types open inline (nosniff), the rest
download; writes carrying another site's Origin are refused (this also
closes cross-site overwrites of the backup files); and attachment routes run
concurrently so a long upload or a playing video cannot stall the page.
Also: cupertino_icons 2.0.0 (unused, but the freshness gate blocks the
pubspec otherwise), open_file 4.0.0 and file_picker 13.1.0 pinned.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A ref is about to name any file stored under todo-images/, not only a
re-encoded JPEG. Mechanical rename of the type and its file; no behaviour
change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both files were donors for the new package rather than casualties of it:
theme.dart was byte-identical to untools' copy (md5 6cf8e82d), and
confirm.dart's confirmDestructive() was the ready-made pattern for the
~35 AlertDialogs inlined across the fleet. Keeping local copies after
promoting them upstream is exactly how the two would drift apart again,
so they are deleted and imported from design_system-v0.1.0 instead.
test/confirm_test.dart moves upstream with its subject -- the package
covers that behaviour (Escape and the barrier both refuse, Cancel is
autofocused so a reflexive Return cannot delete) as part of its own
100% gate. test/theme_test.dart stays as a consumer-side smoke check
that the dependency actually resolves to the frozen palette.
Verified: flutter analyze clean (bar one pre-existing bin/ style info),
308 tests pass at 100% coverage (1962/1962 -- the drop from 2005 is
exactly the promoted lines), and the release APK is installed and
running on the Pixel 6a with all 108 notes intact, the gold #B8862E
focus ring rendering from the package's inputDecorationTheme, and no
crashes in logcat.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XS1m7b1piBtAL22USLW6jL
Replaces the inline ColorScheme.fromSeed(Colors.deepPurple) theme with a new
lib/ui/theme.dart built from explicit light/dark ColorSchemes on the frozen
unified-design-system tokens (same shared spacing/radius/type-scale constants
as diet-guard/habit_stack's theme.dart), and adds darkTheme (previously
absent).
Fixes 2 of 3 violations in DESIGN_AUDIT_TODO.md:
- settings_screen.dart's one hardcoded Colors.red -> colorScheme.error,
matching every other error-state text in the app
- markdown_view.dart and note_editor.dart's raw-mode TextField now cap their
reading/editing column at kProseMaxWidth (640) via Center+ConstrainedBox --
the desktop build is an arbitrarily wide Chrome --app window, so text was
running the full viewport. Deliberately no extra Column introduced around
the raw-mode TextField, per its own documented expands:true/nested-Column
regression history.
The 3rd violation (a few off-scale SizedBox/EdgeInsets values, e.g. 6px vs
the file's dominant 4px scale) was attempted but reverted: bumping the
_EnumChips/_MultiChips/_DateRangeField 6px gaps to 8px broke 3 real tests --
the filter bottom sheet's "Apply" button is tight enough against its edge
that even a 2px shift pushes it out of the hit-testable area. Not worth
trading a working control for a cosmetic 2px scale nitpick; left at 6px.
Verified: flutter analyze clean, dart format clean on all touched files,
188/188 tests passing (added test/theme_test.dart for the new file, which
had 0% coverage on its own), 100% total line coverage maintained, re-verified
identical in an isolated worktree at HEAD (narrowly staged around this
repo's own pre-existing uncommitted CLAUDE.md edit, left untouched). A
Flutter-web headless-Chrome screenshot check for the desktop target was
attempted but blocked by a CanvasKit/WebGL rendering issue in this sandbox,
unrelated to the app itself; the widget test suite (which does catch real
RenderFlex overflow, as confirmed during this same rollout) passed clean for
both touched screens instead. Mobile on-device verification (phone-deploy)
is pending -- the phone isn't connected via ADB right now.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Standardizes on the same stricter lint set + shared analysis_options.yaml
used by diet-guard/app and the new habit_stack app. Fixes all 127 issues
surfaced: dart fix --apply for imports/cascades/sorting, unawaited() on
fire-and-forget Futures, bare catch(e) narrowed to on Exception catch(e)
(matching diet_guard's existing pattern), one documented ignore for a
deliberate TypeError catch around untrusted peer JSON, and missing
public-member doc comments throughout. No behavior change: 174/174 tests
pass, 100% line coverage maintained.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Notes were previously only openable via a quick-actions sheet; you
could not read or edit a note in full. Add a shared NoteEditor used by
both the capture and detail screens, plus selectable templates and a
rendered Markdown view.
- note_template.dart: pure assemble/parse layer over a Markdown subset
(# title, ## sections + italic guidance, dropping empty sections).
assemble(parse(text)) is idempotent for conforming text; non-conforming
/ legacy / freeform text is reported so the UI falls back to raw,
untouched. Two templates: llm-design-spec (default) and blank.
- note_editor.dart: View / Guided / Raw modes. Guided is an inline
Stepper (one step per section with its guidance); View renders the
note via MarkdownView; Raw is the verbatim text. Guided is offered
only for structured templates; switching to it is blocked when the
raw text no longer conforms.
- markdown_view.dart: lean read-only renderer for the note subset,
wrapped in a SelectionArea for copy-out.
- note_detail_screen.dart: full-screen note; opens in View, edits
persist immediately, priority/status dropdowns, delete.
- capture_screen / notes_list_screen wired to the new editor and detail
screen (tap a note opens it; quick actions move to the overflow button).
The editor is a view over plain text, so CRDT storage and Markdown
export/sync are unaffected. 138 tests, 100% line coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>