| Age | Commit message (Collapse) | Author |
|
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|
|
The Tasks tab (/tabs/tasks) existed server-side and was tested, but
nothing in the nav linked to it -- it was pure dead weight in the other
direction. Wiring it up as the natural home for everything that was
either misplaced in Settings or missing a web UI entirely:
- Maintenance Buckets, Projects, and Labels moved out of Settings and
into the Tasks tab (restyled from Settings' opaque slate cards to the
glass/backdrop-blur look already used by the tab's chain/atom cards --
they're now embedded in index.html's page shell, not a standalone
page, so the shared bg-card/bg-input classes from that shell apply).
Settings keeps only what's actually settings: Passkeys, Trusted
Agents, Data Sources.
- Added a Recurrence section to the task-detail modal (freq/interval/
weekday form, posting to a new POST /tasks/recurrence -- the HTMX
counterpart to the widget API's HandleWidgetTaskRecurrence). Native
task recurrence previously had zero web UI at all, only reachable via
the Android widget's RecurrenceEditDialog.
Also fixed a real bug found while touching this code: HandleGetTaskDetail's
source switch only had a case for "trello" -- opening any native ("doot")
task's detail modal, which is most tasks in this tab, showed a blank
title and description. Factored both call sites (initial GET and the
re-render after a recurrence edit) through one loadTaskDetailData helper
and added the missing "doot" case. Also fixed task-detail.html's styling,
which was still using pre-dark-theme classes (text-gray-900 etc.) --
functionally invisible text on the modal's dark background.
Verified with a throwaway local server (real templates + real DB, not
the MockRenderer the unit tests use) seeded with a recurring task,
a bucket, a project, and a label -- confirmed all five touched routes
render 200 with the expected content, including the populated
Buckets/Projects/Labels sections and a real weekly-recurrence form
with the correct weekdays pre-checked. Caught and fixed a copy bug
this way too ("every 2 weeklys" -> "every 2 weeks"). Not committed;
deleted after use.
go build ./..., go vet ./..., and go test ./... all clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|
|
Audited it (couldn't query the live DB directly -- auto-mode classifier
blocks direct production reads without prior approval -- so this is a
code-only audit): GetFeatureToggles/SetFeatureEnabled/IsFeatureEnabled/
CreateFeatureToggle/DeleteFeatureToggle had exactly one caller each, all
inside their own CRUD handlers. Nothing anywhere else in the codebase
read a toggle's Enabled state to gate any actual behavior -- confirmed
by grepping every remaining .Enabled/IsFeatureEnabled reference back to
either this dead code or its own tests. It was pure UI-managed CRUD with
no consumer, unlike Trusted Agents (wired into agent.go/websocket.go)
or Data Sources (wired into the sync pipeline) which stayed.
Removes the Settings page section, the three /settings/features* routes
and handlers, the five Store methods, the FeatureToggle model, and adds
028_drop_feature_toggles.sql (next free migration number, per this
repo's convention of never renumbering -- see 021_drop_tasks.sql for
the same drop-table-forward pattern) to drop the now-unused table.
Also removed the now-dead tests for all of the above.
go build ./... and go test ./... both clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|
|
Icon: adaptive-icon vector launcher icon reusing web/static/favicon.svg's
brand mark (indigo->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 <title>). 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
<a href="/">, which in the WebView pushes a NEW history entry for "/"
instead of reusing the one already on the stack -- so Settings <-> 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 > App info, since this
AVD's launcher doesn't expose an app drawer over adb), no crashes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|
|
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|
|
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.
|
|
fixes, new settings
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 -> 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.
|
|
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.
|
|
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
-> Feb 28), while the Go server's ComputeNextOccurrence (recurrence
math) OVERFLOWS instead (Jan 31 + 1 month -> 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.
|
|
GetUpcomingEvents built its TimeMin cutoff from raw time.Now() (server/UTC
time), not the start of today in the configured display timezone. Google's
API filters TimeMin against each event's END time (exclusive), so for a
UTC-10 display timezone (Pacific/Honolulu) that cutoff landed mid-afternoon
the PREVIOUS Hawaii-local day, not midnight of the actual display day.
Confirmed against live data before fixing, not assumed: the cached
calendar_events table's earliest row was "2026-08-05 15:00:00-10:00" --
exactly the mid-afternoon-yesterday skew this bug produces -- with a
complete gap for all of 2026-08-06 (today). This is what caused two
symptoms reported against the widget: already-ended-today events missing
entirely (never fetched into the cache in the first place, not a
client-side rendering bug -- the 2026-08-05 Android "past events" feature
was built correctly but had nothing to render) and the tomorrow section
appearing empty (same skewed data window scrambling the day-bucketing
downstream).
Fix: compute TimeMin from time.Now().In(displayTZ)'s start-of-day instead.
Added TestGetUpcomingEvents_TimeMinIsStartOfTodayInDisplayTimezone, which
asserts the actual outgoing timeMin query parameter is midnight-in-tz and
lands on today's date -- verified it catches the regression by reverting
to raw time.Now() against a real backup and confirming the exact failure
mode (captured timeMin = mid-afternoon the day before), then restored.
go test ./... -race is green. Deployed. Note: the calendar cache only
refreshes when the web dashboard is visited (aggregateData) -- the widget's
own refresh only re-reads the DB cache, never triggers a live Calendar
resync -- so the currently-cached stale data needs one dashboard visit to
actually reflect this fix, not just the deploy.
|
|
This is a single-user app, sideloaded onto exactly one phone, never
distributed broadly -- "what's the widest range of devices this should
install on" was the wrong question to ask about it. A prior pass
(2026-08-05) walked minSdk down to 31 on that reasoning, without
checking what device this actually runs on: Android 16 / API 36.
minSdk=targetSdk=compileSdk=36 now, matching the real device exactly.
Verified both directions on real emulator targets, not assumed:
installs and launches cleanly (no crash, target_sdk_version=36
confirmed via logcat) on a new dedicated API 36 AVD (doot_test_api36),
and correctly refuses to install on the existing API 34 emulator
(INSTALL_FAILED_OLDER_SDK) -- proving the constraint is real, not just
declared.
AGP 8.2.0's bundled D8 still warns "API level of 36 is not supported
by this compiler" even with build-tools;36.0.0 installed locally (AGP
doesn't delegate to the standalone SDK build-tools binary) -- confirmed
harmless via the install+launch verification above, not chased further
since a real fix means upgrading AGP itself, a bigger change than
justified here.
Audited the rest of the app for SDK-version fallback/polyfill code
that could now be simplified given the guaranteed floor: found none:
the only place minSdk mattered was this file.
|
|
completions
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.
|
|
addTask()'s failure path had no .onFailure handler, so a failed POST
(network hiccup, server down) left the Add sheet just sitting there
with zero feedback -- looked like the button did nothing.
Also: the five calls chained after a successful addTask (updateTask,
reschedule, setTaskProject, setTaskLabels, setTaskRecurrence) were
still unchecked, so a task could get created with silently-dropped
metadata even with the addTask fix in place. Both paths now collect
and toast what failed; the task itself still gets created and the
sheet still closes, since aborting would leave a task that already
exists server-side with no way to tell the user without a duplicate.
|
|
Three .Do() calls in google_calendar.go accepted a ctx parameter but
never chained .Context(ctx) into the actual SDK call, so the existing
global 60s request timeout never reached the blocking network call.
One hung Google Calendar request wedged every DB/session-touching
request path in production for three days (2026-08-01 through
2026-08-04), undetected because /health unconditionally returned 200
throughout.
- Wire .Context(ctx) into GetUpcomingEvents, GetEventsByDateRange, and
GetCalendarList.
- Bound aggregateData's four external fetches with a per-fetch
sub-context as defense-in-depth (only effective if the callee
actually honors ctx -- documented as such, not oversold).
- Make GetUpcomingEvents/GetEventsByDateRange fetch calendars
concurrently instead of sequentially: a review of this fix caught
that a shared per-fetch deadline over a sequential loop would starve
calendars past the first under any real latency, silently caching
partial results as complete. Concurrent fetches give every calendar
an equal shot at the same deadline instead.
- /health now does a real PingContext DB check instead of a static
"ok" (Handler.PingDB, tested for both healthy and closed-DB cases).
- Add internal/api/context_audit_test.go: an AST-based structural
guard that fails any future .Do() call in google_*.go missing
.Context(...) anywhere in its chain, so this class of bug can't
silently recur. Verified by deliberately reintroducing the original
bug against a backup and confirming the guard catches it.
- Add scripts/health-watchdog.sh: cron job restarts the service if
/health fails twice in a row, five minutes apart.
go test ./... -race is green. Deployed and live-verified.
|
|
Google Tasks' due field is date-only but always serialized as an
RFC3339 timestamp fixed at midnight UTC regardless of the user's
timezone. Converting that instant into displayTZ (dueDate.In(tz))
reinterpreted it as a real moment in time instead of re-anchoring the
same calendar date to local midnight -- midnight UTC becomes 2pm the
previous day in UTC-10 (Pacific/Honolulu), so every dated Google Task
silently landed one day earlier than its actual due date. A task due
tomorrow showed up in today's section.
Fix: extract the Y/M/D from the UTC-anchored timestamp (which IS the
intended calendar date) and rebuild midnight in displayTZ from those
components, instead of converting the instant.
|
|
ComputeDaySection unconditionally recomputed IsAllDay from a
midnight-time heuristic, clobbering the IsAllDay:true flag callers set
to mark floating (no-due-date) tasks -- since undated tasks use Time =
now, this silently reset IsAllDay to false and gave them a real Start,
so the widget could render them via EventBlock/HourRow instead of
TaskRow. Their click handler always opens the calendar/source URL, so
tapping a no-due-date task (e.g. a Google Task) opened Google Calendar
instead of the task detail sheet.
ComputeDaySection now only ever sets IsAllDay true, never clears an
already-true value. Also flag IsAllDay for undated Google Tasks, which
never got it set at all (only native undated doot tasks did).
|
|
TomorrowSection emitted its divider, header, and every event/task row
as top-level siblings with no shared layout container. Since the whole
function is composed inside a single LazyColumn item{} slot (one
RemoteViews node), nothing told Glance to stack them vertically -- they
all rendered on top of each other instead of flowing top-to-bottom.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|
|
New "Maintenance Buckets" section: create a bucket, add a pool item by
title (creates the task and assigns it in one step), remove an item,
delete a bucket (unbuckets its tasks rather than deleting them).
New read-only Projects and Labels sections (name + color swatch) --
both are simple enough that read-only is the right call on web, per
user direction, rather than duplicating the Android popup's editing
UX.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|
|
The flat Tasks-tab atom list was silently dumping every chain step
(locked and unlocked) and dormant bucket-pool items in as ordinary
undated cards, with no chain/project context and no protection against
completing a locked step out of order.
- CompleteNativeTask now rejects completing a locked chain task
(ErrChainTaskLocked), mapped to 400 in both the widget and web
complete-atom handlers.
- Chain tasks and dormant bucket items are excluded from the flat atom
list; a new "Chains" section shows one card per active/paused chain
with the current step and N/M progress.
- New chain checklist modal (GET /chains/{id}) lists every position in
order with pause/resume/abandon -- the web view originally deferred
as Android-only.
- Fixed a real bug this surfaced: resuming a paused chain only flipped
the status flag, never unlocking the deferred successor, so a chain
paused right after a completion stayed stuck forever. SetChainStatus
now catches up the deferred advancement on resume, idempotently.
- Atom cards gained a project-name chip for general visibility.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|
|
CreateChain and POST /api/widget/chains previously only took bare
title strings, with priority hardcoded to 1 -- narrower than the
spec's "an ordered list of task titles/descriptions" API line. Now
accepts []models.ChainTaskInput{Content, Description, Priority} per
position, priority defaulting to 1 when omitted.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|
|
Fleshes out the "not built" notes in both specs into actionable
follow-up scope: what data/API layer already exists, what the screen
needs to do, and precedent to follow.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|
|
Refuses to run if hostname doesn't match the documented deploy host,
preventing an accidental deploy from a sandbox/dev environment.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|
|
Backend, web timeline, and Android widget wiring for the last two
unimplemented items from doot-future-task-scheduling-ideas.
Chains: task_chains table + chain_id/chain_position/chain_unlocked on
native_tasks (migration 026), WIP-limit-1 advancement hooked into
CompleteNativeTask, locked tasks excluded from all date-based queries,
5 new /api/widget/chains* endpoints, an N/M position badge on web and
Android widget rows.
Buckets: maintenance_buckets table + bucket_id/bucket_state/
bucket_last_active_at on native_tasks (migration 027),
staleness-then-priority selection scoring, a new RunBucketCycleCheck
scheduler loop, 5 new endpoints including the distinct Defer action, a
Defer button on web and Android widget rows.
Also corrected stale "not yet approved" status headers on the two
already-shipped specs this work depended on (labels/projects, budgets/
availability) -- their headers were never updated after implementation.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|
|
deployment/deploy duplicated scripts/deploy (the one actually in use).
deployment/post-receive documented installing a git hook at
/site/doot.terst.org/app-code/hooks/post-receive, but that hook was
never actually installed and the app-code checkout it depended on has
been removed -- this path was never wired up.
|
|
Quick Add's text field never requested focus or triggered the keyboard,
so tapping it opened a sheet with no visible way to type. Added a
FocusRequester + delayed requestFocus()/keyboard show (ModalBottomSheet
needs a beat to finish its enter animation before focus requests land).
While in there, rebuilt the quick-add sheet to match the task edit
popup instead of being a bare title field: due date, recurrence,
project, and label chips, plus a description field, reusing the same
dialogs the edit popup already uses. POST /api/widget/add now returns
the created task's id so the client can chain the same
project/labels/recurrence/due-date setter calls edit already relies on.
|
|
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQaPGQVSfmKUiHXB87qRTC
|
|
Threads the /api/widget budget_status field through the Android widget's
DataStore-backed cache (a new BUDGET_STATUS_JSON pref, since the widget
decomposes WidgetResponse into individual prefs rather than caching it
whole) and renders a small "Nm/Nm" badge next to TODAY when there's a
nonzero tracked load.
|
|
Task 11 of the task-budgets-and-availability plan: reuse the
computeBudgetStatus helper (Task 8) in HandleTimeline so the dashboard's
Today section header shows scheduled/available minutes when a
budget-tracked task exists.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQaPGQVSfmKUiHXB87qRTC
|
|
|
|
Adds 6 widget HTTP handlers (availability get/create/delete, task
estimate, project and label budget-tracked toggles) plus route
registration, following the existing widget handler conventions.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQaPGQVSfmKUiHXB87qRTC
|
|
Wires ComputeBudgetPeriod together with availability blocks, tracked
project/label sets, calendar events, and native tasks into a new
computeBudgetStatus helper on *Handler. HandleWidgetGet now populates
WidgetResponse.BudgetStatus with today/week periods, but only when at
least one incomplete budget-tracked task exists in the rolling week
window -- otherwise the field is omitted entirely so unconfigured users
see no new UI. Errors are logged and swallowed, matching the existing
resilience pattern for this widget endpoint.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQaPGQVSfmKUiHXB87qRTC
|
|
task-filter doc comment; add multi-day test
Overlapping calendar events within the same availability block were each
subtracted independently, double-counting their intersection and making
availability under-counted (a 120-min block with two 60-min-overlap events
that overlap each other 18:30-19:00 came out to 0 min instead of the
correct 30 min). Replace the per-event overlapMinutes subtraction with
busyMinutesInBlock, which clips each event to the block, merges the
clipped intervals, and subtracts the union's total length.
Also clarify ComputeBudgetPeriod's doc comment: task filtering only
enforces an upper bound (due < end); start is intentionally not used to
filter tasks, since callers are expected to pre-fetch and pass in
already-overdue tasks. Add a multi-day-window test verifying availability
sums correctly across days, a block only contributes on its matching
weekday, and the day-iteration loop excludes the day at `end`.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQaPGQVSfmKUiHXB87qRTC
|
|
ComputeBudgetPeriod is a standalone, dependency-free function over
in-memory availability blocks, calendar events, and tasks: sums weekly
availability minus overlapping calendar events for [start, end), and
sums estimated minutes for tracked, incomplete tasks due in that window.
|
|
|
|
flag
|
|
|
|
|
|
Fix formatting and alignment violations: align struct field tags in Task,
realign Weekday comment in AvailabilityBlock, and remove extra blank line
before CalendarEvent.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session
|
|
Add EstimatedMinutes to Task, BudgetTracked to Project and LabelColor
structs. Create budget.go with AvailabilityBlock, BudgetPeriod, and
BudgetStatus types for modeling task budgets and calendar availability.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQaPGQVSfmKUiHXB87qRTC
|
|
Creates migrations/025_task_budgets_and_availability.sql with:
- New availability_blocks table for manual weekly availability templates
- estimated_minutes column on native_tasks for time budgeting
- budget_tracked flag on projects and labels for opt-in tracking
Fixes pre-existing bug in sqlite_test.go's setupTestStoreWithNativeTasks where
project_id column was missing from the schema, causing test failures.
Updates all hand-rolled test helpers (sqlite_test.go, native_tasks_test.go)
to include the new columns, and adds availability_test.go with newAvailabilityTestStore
helper for Task 4's availability CRUD tests.
All store tests pass, including the two that were previously failing
(TestGetNativeTasksByDateRange_ExcludesOverdue, TestGetOverdueNativeTasks_IncludesOnlyPastDue).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQaPGQVSfmKUiHXB87qRTC
|
|
HandleAgentTaskCreate decoded project_id from the request but never
forwarded it to CreateNativeTask, so a task created via the external
Agent API with a project assigned would silently create the task
unassigned instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTUSAEKfsPc6WGDq45yPHD
|
|
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTUSAEKfsPc6WGDq45yPHD
|
|
|
|
|
|
|
|
Tests verify ProjectColor is populated in BuildTimeline and copied to WidgetItem.
|
|
Projects are fetched once per BuildTimeline call into a color lookup
map, not queried per-task -- same pattern as RecurringEventID's
existing TimelineItem->WidgetItem threading.
|
|
Tests for HandleWidgetProjectsGet, HandleWidgetProjectsCreate,
HandleWidgetTaskProject, HandleWidgetTaskLabels, HandleWidgetLabelsGet,
HandleWidgetLabelsColorSet, and the extended HandleWidgetTaskDetail
response with project and labels fields.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTUSAEKfsPc6WGDq45yPHD
|
|
Extends the task-detail response with project/labels so the popup can
display them without a second round trip.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTUSAEKfsPc6WGDq45yPHD
|
|
|