<feed xmlns='http://www.w3.org/2005/Atom'>
<title>doot.git/android/app/src/main/java/org/terst, 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-14T07:34:01+00:00</updated>
<entry>
<title>Fix tomorrow's all-day events using the wrong text color</title>
<updated>2026-08-14T07:34:01+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-08-14T07:34:01+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=dcfb7204d654c98657b0638a569e8ec69bb5d852'/>
<id>urn:sha1:dcfb7204d654c98657b0638a569e8ec69bb5d852</id>
<content type='text'>
TomorrowSection de-emphasizes every other row to palette.textSecondary,
but AllDayRow had no titleColor parameter and always rendered at
palette.textPrimary -- so all-day/multi-day items (like a birthday)
stayed bright while every timed event and task around them dimmed.
AllDayRow now takes a titleColor (defaulting to textPrimary, which
keeps today's section unchanged), and TomorrowSection passes
textSecondary like it already does for TimeLabeledEventRow/TaskRow.
</content>
</entry>
<entry>
<title>Add task title editing/deletion, timeline click-to-open, widget app launch</title>
<updated>2026-08-12T23:35:47+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-08-12T23:35:47+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=2509dde6aa372a505b186657706f4d21bd391807'/>
<id>urn:sha1:2509dde6aa372a505b186657706f4d21bd391807</id>
<content type='text'>
Task-detail modal was description-only with no delete affordance;
HandleUpdateTask now saves the title too and a Delete button hits a new
DELETE /tasks/{id} route backed by store.DeleteNativeTask, which repairs
chain_position/unlocks the successor when the deleted task belongs to a
chain. Timeline tab task/card/gtask rows now open the same detail modal
as the Tasks tab. Android widget's "TODAY" header is now a tap target
that launches DashboardActivity, since nothing previously opened the
full app from the widget.
</content>
</entry>
<entry>
<title>Fix tomorrow's task rows: color and alignment vs tomorrow's events</title>
<updated>2026-08-12T07:57:51+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-08-12T07:57:51+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=3e8ad60431d6cc783f9f7c555bfde5db54ebec75'/>
<id>urn:sha1:3e8ad60431d6cc783f9f7c555bfde5db54ebec75</id>
<content type='text'>
TaskRow always used textPrimary for its title, while tomorrow's timed
events (TimeLabeledEventRow) are deliberately de-emphasized with
textSecondary -- tomorrow's tasks now match. TaskRow also had no
leading gutter of its own outside the hourly grid (where HourRow's
hour-label column supplies it externally), so in TomorrowSection its
title landed at a variable offset depending on checkbox/dot size and
hideCheckboxes, instead of the fixed 32dp gutter AllDayRow and
TimeLabeledEventRow use there. Added optional titleColor/gutterWidth
params to TaskRow (both default to prior grid behavior) and set them
from TomorrowSection.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_017GMEkHeqKz6FLkmizowBTK
</content>
</entry>
<entry>
<title>Widget: Quick Add dismisses instantly instead of blocking on network</title>
<updated>2026-08-09T05:03:09+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-08-09T05:03:09+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=b4bb981d98b0c2d4758f5acc0e1deb1f2a651c79'/>
<id>urn:sha1:b4bb981d98b0c2d4758f5acc0e1deb1f2a651c79</id>
<content type='text'>
Tapping "Add" ran addTask() plus up to five sequential follow-up calls
(description, due date, project, labels, recurrence) and a widget
refresh, all inside the sheet's own lifecycleScope, before finish() --
the sheet sat on screen doing nothing visible for however long that
chain took, reported as "sits black for a couple seconds."

Moved the actual submission into a new AddTaskWorker, following the
same pattern already established by CompleteWorker/DeferWorker: a
CoroutineWorker enqueued fire-and-forget, so it survives the activity
finishing (lifecycleScope wouldn't -- it's cancelled the moment the
activity is destroyed). onAdd now does a local-only DataStore config
check (fast, no network, so a missing server URL/token still doesn't
silently eat what was typed), then confirms via toast and calls
finish() immediately, mirroring the optimistic-dismiss pattern
TaskDetailActivity.onComplete already uses. The worker preserves the
original partial-failure reporting (a toast listing what didn't stick)
for the rare case something after addTask itself fails, now delivered
asynchronously via Toast.makeText posted to the main thread rather than
blocking the sheet on it.

Verification: this exact activity hit the same headless-emulator input
limitation noted earlier this session (2026-08-06, QuickAdd keyboard
focus) -- confirmed it's an environment constraint, not a regression,
by trying both `input text` and raw `input keyevent` injection (which
also failed) against a field that visibly has focus, on the same
emulator where the same commands work fine for an equivalent
OutlinedTextField in SettingsActivity. Verified instead by: go build/
test equivalent (./gradlew testDebugUnitTest, all passing -- the
individual WidgetRepository calls AddTaskWorker orchestrates already
have unit coverage in WidgetRepositoryTest.kt), a clean assembleDebug,
and a crash-sanity launch on emulator-5556 with no FATAL in logcat.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
</content>
</entry>
<entry>
<title>Widget: real launcher icon, fix double title bar, rename app to "doot"</title>
<updated>2026-08-06T17:32:08+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-08-06T17:32:08+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=f7d18eae924a221f12293c3063e46b791468623f'/>
<id>urn:sha1:f7d18eae924a221f12293c3063e46b791468623f</id>
<content type='text'>
Icon: adaptive-icon vector launcher icon reusing web/static/favicon.svg's
brand mark (indigo-&gt;purple gradient square, white checkmark) so the app
and web dashboard visually match. minSdk 36 (per build.gradle.kts) means
mipmap-anydpi-v26 alone is sufficient, no legacy PNG fallback needed.

Double title bar: DashboardActivity inherited the app-wide
Theme.DeviceDefault.DayNight, which has a native ActionBar, stacked on
top of the new Compose TopAppBar from the last commit -- visibly two
bars, showing "Doot" (static ActionBar label) above "Personal Dashboard"
(the Compose bar tracking the web page's own &lt;title&gt;). Added
Theme.Dashboard (NoActionBar) for the activity, and stopped tracking
the WebView's document.title for the Compose bar's title text --
the dashboard is a single HTMX-swapped page (see DashboardActivity's
class doc), so the title never meaningfully changes, and it was
just producing a second, differently-branded piece of text.

Renamed the app from "Doot Widget" to "doot" throughout (application
label, widget-picker description, Settings screen heading) to match
the project's actual branding.

Also: the web Settings page's "Back to Dashboard" link was a plain
&lt;a href="/"&gt;, which in the WebView pushes a NEW history entry for "/"
instead of reusing the one already on the stack -- so Settings &lt;-&gt; Home
round trips kept growing the WebView back-stack ("zigzag"), and the
in-app back arrow/hardware back key never actually unwound it. Now it
calls history.back() when there's stack to pop, falling back to a plain
navigation only if there isn't (e.g. Settings opened directly).

Verified on emulator-5556: rebuilt and reinstalled, confirmed a single
title bar (no native ActionBar behind the Compose one), confirmed the
launcher icon renders (checked via Settings &gt; App info, since this
AVD's launcher doesn't expose an app drawer over adb), no crashes.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
</content>
</entry>
<entry>
<title>Widget: give Dashboard its own launcher icon, add toolbar nav</title>
<updated>2026-08-06T16:57:44+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-08-06T16:57:44+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=a1ca48e0dc760393a59fe745736b6b0668fade17'/>
<id>urn:sha1:a1ca48e0dc760393a59fe745736b6b0668fade17</id>
<content type='text'>
DashboardActivity is now the app's single home-screen launcher entry
(MAIN/LAUNCHER intent-filter), replacing SettingsActivity in that role.
SettingsActivity keeps only APPWIDGET_CONFIGURE (still exported=true,
since the launcher/home-screen process starts it directly during widget
placement) and is now reached via a gear button in Dashboard's toolbar.

DashboardActivity itself is rewritten from a bare setContentView(webView)
to Compose Scaffold/TopAppBar wrapping the WebView via AndroidView, adding:
- a back arrow (shown only when the WebView has history) instead of relying
  solely on the hardware back key
- a settings gear action launching SettingsActivity
- the page title tracked from the loaded page

Also fixes a race in the original version: server URL was loaded via a
lifecycleScope coroutine racing the WebView's own initialization order.
Now it's plain Compose state (LaunchedEffect + AndroidView's update
callback), so the WebView never loads before the URL is known.

No native tab bar: web/templates/index.html's tabs are HTMX partials
(hx-get targeting #tab-content), not separate pages, so loading "/" in
the WebView already gets full in-app navigation for free.

Verified on emulator-5556 (doot_test_api36): pm resolve-activity confirms
DashboardActivity is the launcher default; launched it, confirmed no
crash and topResumedActivity is Dashboard; configured a dummy server URL
via Settings and confirmed the toolbar renders (title, gear icon) and
the gear button correctly navigates to SettingsActivity (topResumedActivity
becomes SettingsActivity) with no crash.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
</content>
</entry>
<entry>
<title>Widget: fix QuickAdd keyboard-focus race, force widget re-render on save</title>
<updated>2026-08-06T16:35:50+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-08-06T16:35:50+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=b56da63fe7776b14bc2cff4fe3c63ae2dd65ac73'/>
<id>urn:sha1:b56da63fe7776b14bc2cff4fe3c63ae2dd65ac73</id>
<content type='text'>
QuickAddActivity's autofocus used delay(150) then requestFocus() +
keyboard.show() -- a fixed delay racing against the window actually
gaining focus. This is a translucent, separate-taskAffinity popup
activity, so window focus transfer is variable/device-dependent; a
show() call made before the window is focused is silently dropped by
the IME service, no error, no retry -- the classic cause of "keyboard
doesn't appear until I tap out and back in" (reported 2026-08-06, "more
consistent here" than the same general Android quirk elsewhere). Fixed
to react to LocalWindowInfo.isWindowFocused instead of guessing a
timing window, plus windowSoftInputMode="adjustResize|stateVisible" as
an OS-level second line of defense.

Tried to verify live and hit a real limitation worth recording: this
box's AVDs run -no-window (headless), and in that mode mInputShown
never reports true via dumpsys input_method even for a manual,
deliberate tap on the field -- confirmed by testing a plain tap
directly, independent of any app code. The harness can't observe IME
visibility here, so this fix is verified by code-level reasoning
(LocalWindowInfo-driven focus is the standard, documented fix for this
exact bug class) and confirmed window-focus DOES transfers correctly
(mServedView moves to the bottom sheet's window), not by watching the
keyboard actually appear. Real confirmation has to happen on-device.

Also: SettingsActivity.saveAndFinish() only ever called
DootWidget().updateAll() indirectly, as a side effect of RefreshWorker
succeeding its network fetch -- so a slow or failing request could
delay or block the widget from reflecting a setting the user just
saved, even though every setting saved there (theme, text size,
background, checkboxes) is already fully local and needs no network
round trip to take effect. Now calls updateAll() directly and
immediately after writing prefs; RefreshWorker still runs afterward to
separately pull fresh server data.

Verified installable and crash-free on a real API 36 emulator.
Deployed as doot-widget.apk.
</content>
</entry>
<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: wrap the web dashboard in a WebView (DashboardActivity)</title>
<updated>2026-08-06T09:30:36+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-08-06T09:30:36+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=ab76bcae1324aadb6d163e565f5994d0db10ca61'/>
<id>urn:sha1:ab76bcae1324aadb6d163e565f5994d0db10ca61</id>
<content type='text'>
First cut per the 2026-08-06 feasibility check (verdict: easy, no
architecture blockers): a plain WebView pointed at the configured
server URL, cookie jar enabled for the existing session-cookie login
(internal/auth/middleware.go's RequireAuth) -- no separate auth bridge
needed, the widget's own bearer token is a completely different scheme
and doesn't need to touch this at all. External links (e.g. a calendar
event's source URL) escape to the user's real browser instead of
getting stuck in the WebView.

No deep-linking to specific tabs, no native-rendered chrome -- this is
the minimal first step to validate the wrapped experience is worth
building further, not the final shape.
</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>
</feed>
