<feed xmlns='http://www.w3.org/2005/Atom'>
<title>doot.git/android/app/src/test, branch master</title>
<subtitle>doot — personal productivity web app
</subtitle>
<id>https://git.terst.org/doot.git/atom?h=master</id>
<link rel='self' href='https://git.terst.org/doot.git/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/'/>
<updated>2026-08-06T09:31:03+00:00</updated>
<entry>
<title>Widget: ship Material You theming, past-events restructuring, legibility fixes, new settings</title>
<updated>2026-08-06T09:31:03+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-08-06T09:31:03+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=702e0ea04984f203048d9a440bc6e1c8ba8b6d4e'/>
<id>urn:sha1:702e0ea04984f203048d9a440bc6e1c8ba8b6d4e</id>
<content type='text'>
This lands the color-theming work that had sat uncommitted since a prior
session (2026-07-28) -- every build published in between stripped it out
deliberately to avoid shipping unreviewed work -- plus a full round of
fixes and new features layered on top since it finally shipped:

Theming (WidgetPalette.kt, new):
- 5 themes now: NEUTRAL/ACCENT/TONAL (wallpaper-derived via Material You),
  VIVID (new -- all three text roles pull from a different accent slot
  instead of anchoring primary to neutral, for real hue variety), CLASSIC
  (fixed, wallpaper-independent).
- Settings picker redesigned to match Android's native wallpaper "Basic
  colors" circular swatches (bottom half + two top quadrants, filled with
  each theme's actual buildWidgetPalette() output, not an approximation).
- Per-source accent colors (colored checkboxes/bars) fully removed from
  the grid.

Past events (DootWidget.kt, WidgetRows.kt):
- Already-ended-today events pulled out of the hourly grid, shown as list
  rows above it instead (matching how past tasks already float) -- fixes
  the grid's start hour getting stretched backward by stale events.

Legibility (WidgetRows.kt):
- ShadowedText upgraded from a single-corner drop shadow to a 4-corner
  halo/outline (protects all sides of a glyph, not just one).
- Halo color now tracks each palette's text luminance (dark halo for
  light text, light halo for dark text) -- a hardcoded black halo behind
  already-dark light-mode text was doing essentially nothing. Bumped
  opacity 0.45/0.55 -&gt; 0.65/0.7 as the cheap, low-risk strength dial.

New settings (SettingsActivity.kt, DataStore.kt):
- Background transparency slider (0-85%, default 0% unchanged) --
  exposed for testing per explicit request, not a default change.
- Hide-checkboxes toggle: drops the leading checkbox/dot element
  entirely (not just hides the icon) so task titles land flush with
  event titles; tap-to-complete-from-widget trades off for
  TaskDetailActivity's Complete button.

Also: today's moon phase in the TODAY header (moonPhaseEmoji, pure
date computation, no network) -- verified against direct calculation
before writing test assertions, not hand-computed. Removed the unused
glance-material3 dependency (verified zero usages before removing;
turned out to save ~2KB, not the ~280KB expected, since material3
itself already pulls the same transitive deps -- noted honestly rather
than oversold).

Every new pure-logic piece has unit tests (isPastEvent, moonPhaseEmoji,
theme construction) -- 59 total, all green. Verified installable and
crash-free via a real API 36 emulator launch before each publish, not
assumed. Deployed as doot-widget.apk.
</content>
</entry>
<entry>
<title>Widget: add postpone (tomorrow/next week/next month) to task detail popup</title>
<updated>2026-08-06T09:30:26+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-08-06T09:30:26+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=f479211f0885776c379c598dac738ac08ea72548'/>
<id>urn:sha1:f479211f0885776c379c598dac738ac08ea72548</id>
<content type='text'>
Alongside Complete/Edit, doot-native tasks now get a Postpone button with
a dropdown (Tomorrow / Next week / Next month), reusing the existing
reschedule wiring (WidgetRepository.reschedule) the due-date picker
already uses.

Caught and documented a real divergence while writing the test: Java's
LocalDate.plusMonths CLAMPS to the target month's last valid day (Jan 31
-&gt; Feb 28), while the Go server's ComputeNextOccurrence (recurrence
math) OVERFLOWS instead (Jan 31 + 1 month -&gt; Mar 3) via time.AddDate.
Verified by actually running both, not assumed -- a first draft of this
test asserted the wrong (Go-style) behavior before checking. Not
reconciled here, just accurately documented as a known inconsistency
between this client-side helper and the server's date math.

4 new tests, all passing.
</content>
</entry>
<entry>
<title>Widget: fix "complete works once, then stops" race between overlapping completions</title>
<updated>2026-08-05T00:36:18+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-08-05T00:36:18+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=7b9eb14db9b953b3c5882b3b85c819d9baa2dd2c'/>
<id>urn:sha1:7b9eb14db9b953b3c5882b3b85c819d9baa2dd2c</id>
<content type='text'>
Root cause traced from server logs, not guessed: every completion request
was succeeding server-side (100% 200s, including a 5-tap burst spanning
different tasks), and each successful completion's response payload was
correctly shrinking. So the failure wasn't dispatch or network -- it was
that a successful completion could still get silently undone client-side.

fetchAndPersist does an unconditional full overwrite of the cached item
list on every successful GET. CompleteWorker/DeferWorker run one instance
per task id with no ordering guarantee between different ids' workers
(different unique work names, no KEEP protection across them -- that
protection only ever covered same-task double-taps). So: tapping complete
on task A starts a GET that's still in flight; tapping complete on task B
before A's GET returns optimistically removes B locally; A's slower GET
response, captured before B's completion landed, then overwrites the
cache and silently resurrects B.

Fix: track locally-optimistic removals with a timestamp (PendingRemovals.kt)
and filter them out of every fetchAndPersist write for a bounded TTL (2
min), regardless of which worker's fetch is doing the writing. The TTL
means a completion that never actually confirms (permanent network
failure) still self-heals via the next periodic refresh, matching an
existing self-healing property already relied on elsewhere in this
codebase, instead of hiding the task forever.

Added PendingRemovalsTest.kt (pure-function unit tests, no Android
runtime needed) covering the exact race scenario plus TTL expiry and
edge cases. Verified the tests actually catch a regression by deliberately
reverting the fix to a no-op against a real backup, confirming 3 tests
failed with the exact expected assertion, then restoring and confirming
green again.

Built, tested, and published as doot-widget.apk.
</content>
</entry>
<entry>
<title>feat(widget): add Android data layer for projects and label colors</title>
<updated>2026-07-16T02:54:17+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-15T18:36:00+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=0aa5bd0de79df82958a7314a7db6e91e892d28b7'/>
<id>urn:sha1:0aa5bd0de79df82958a7314a7db6e91e892d28b7</id>
<content type='text'>
</content>
</entry>
<entry>
<title>feat(widget): add description linkification for URLs and phone numbers</title>
<updated>2026-07-16T02:44:52+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-15T00:12:48+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=7c7d9246e778d0e9eeb8fce4894e1bb0923b624e'/>
<id>urn:sha1:7c7d9246e778d0e9eeb8fce4894e1bb0923b624e</id>
<content type='text'>
</content>
</entry>
<entry>
<title>feat(widget): add WidgetRepository methods for task detail/recurrence</title>
<updated>2026-07-16T02:44:52+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-15T00:08:02+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=ffc56608ace1a80f020c53add081514a5428252d'/>
<id>urn:sha1:ffc56608ace1a80f020c53add081514a5428252d</id>
<content type='text'>
</content>
</entry>
<entry>
<title>fix(widget): scale content font sizes down 5% across all text-size tiers</title>
<updated>2026-07-16T02:43:18+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-14T07:22:55+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=f5dfa7a4261e2fa2674e26f43622bef4c3dacdba'/>
<id>urn:sha1:f5dfa7a4261e2fa2674e26f43622bef4c3dacdba</id>
<content type='text'>
</content>
</entry>
<entry>
<title>feat(widget): show multi-day calendar events on every day they span</title>
<updated>2026-07-16T02:43:18+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-13T18:26:46+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=3ccb4eb45ff0f2a0314c36dc530d5118b59a35fd'/>
<id>urn:sha1:3ccb4eb45ff0f2a0314c36dc530d5118b59a35fd</id>
<content type='text'>
Multi-day events (Start and End on different calendar days) are pulled
out of the normal grid/all-day pipeline and rendered as an all-day-style
row on every day they touch (Today and/or Tomorrow), labeled
starts/ends/plain per the day being rendered. Previously such an event
either only appeared in the single hourly grid slot matching its start
time (never again on later days) or, if genuinely flagged all-day,
never had its End forwarded at all.
</content>
</entry>
<entry>
<title>fix(widget): floor header text size/weight at NORMAL even when SMALL</title>
<updated>2026-07-16T02:40:56+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-13T16:50:24+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=842f001c601d27952cdd6bd47ed007039b146c86'/>
<id>urn:sha1:842f001c601d27952cdd6bd47ed007039b146c86</id>
<content type='text'>
Headers (hour labels, section titles) shrinking along with content at
the SMALL setting made them too small relative to content. Content
still shrinks at SMALL; headers now stay at NORMAL's scale/weight
regardless of the selected tier.
</content>
</entry>
<entry>
<title>feat(widget): add WidgetTextSize enum with independent header/content scaling</title>
<updated>2026-07-16T02:39:57+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-13T09:55:32+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=2c041a9d1e370f14fffd15e9f4a6113002f05b1b'/>
<id>urn:sha1:2c041a9d1e370f14fffd15e9f4a6113002f05b1b</id>
<content type='text'>
</content>
</entry>
</feed>
