<feed xmlns='http://www.w3.org/2005/Atom'>
<title>doot.git/android/app/src/main, 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-09-02T23:14:33+00:00</updated>
<entry>
<title>Restore WebView state across Activity recreation in the dashboard widget</title>
<updated>2026-09-02T23:14:33+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-09-02T23:14:33+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=0642dab358ac7c84d56314277d4664043598dcc5'/>
<id>urn:sha1:0642dab358ac7c84d56314277d4664043598dcc5</id>
<content type='text'>
The web dashboard is a single HTMX page -- tab switches are client-side
swaps + history.pushState (?tab=shopping), never a real navigation. The
WebView never saved/restored that state, so any Activity recreation
(Android reclaiming a backgrounded Activity, a rotation, anything that
reruns the AndroidView factory with a fresh WebView) fell back to
loadUrl() on the bare server URL in the update block, landing on
whatever tab is default. Reported as "shopping randomly jumps back to
timeline" -- the prior swipe-detection fix (a17a159) addressed a real
but separate false-positive source and didn't touch this path, which
isn't tied to touch input at all.

WebView.saveState/restoreState is the standard mechanism for exactly
this; wired into onSaveInstanceState and the AndroidView factory.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
</content>
</entry>
<entry>
<title>Fix web quick-add keyboard viewport bug and shopping-mode add scroll jump</title>
<updated>2026-08-29T21:22:36+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-08-29T21:22:36+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=533d89befd1fe4b8134440e8d415d40a5fb7c04b'/>
<id>urn:sha1:533d89befd1fe4b8134440e8d415d40a5fb7c04b</id>
<content type='text'>
DashboardActivity (the WebView-wrapped web dashboard) had no
windowSoftInputMode override, unlike QuickAddActivity which already sets
adjustResize|stateVisible for the same class of problem: without it,
Android WebViews default to adjustPan, which shifts the whole page up
behind the keyboard instead of resizing the viewport, pushing a centered
`position: fixed` modal's input out of reach when the keyboard opens.

shopping-mode.html's fixed-bottom quick-add bar called a plain .focus()
on the item-name input right after the item list above it grew by one
row. The browser's default scroll-into-view for that focus call was
computed against the now-taller document and dragged the whole page to
the bottom instead of leaving the always-visible fixed bar alone.
preventScroll: true stops that.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
</content>
</entry>
<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>
</feed>
