| Age | Commit message (Collapse) | Author |
|
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.
|
|
|
|
|
|
|
|
DootWidget.kt (582 lines) mixed the GlanceAppWidget entry point/root
composable, all the row-rendering composables, and multi-day-event
detection logic in one file. Split into:
- MultiDayEvents.kt: MultiDayVariant, effectiveEndDay, isMultiDayEvent,
multiDayVariant, multiDayLabel, timeSuffix.
- WidgetRows.kt: sourceColor, calendarViewIntent, and all row/section
composables (AllDayRow, RefreshButton, QuickAddButton, HourRow,
EventBlock, TaskFragmentBlock, TaskRow, TomorrowSection,
TomorrowEventRow), plus hourLabel/calcGridStart/calcGridEnd.
- DootWidget.kt: just the GlanceAppWidget class and WidgetRoot, now
145 lines.
All same package (org.terst.doot.widget.ui), so no import changes
needed for cross-file references. Pure move, no behavior change --
full unit test suite (including DootWidgetGridTest/
DootWidgetMultiDayTest, which call the internal functions directly)
passes unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTUSAEKfsPc6WGDq45yPHD
|
|
The 4 frequency chips and 7 weekday chips were each laid out in a
single non-wrapping Row -- on a real phone width these overflow/get
cut off rather than wrapping. The interval field was a full-width
OutlinedTextField with the unit baked into its label ("Every N
dailys"/"weeklys" -- ungrammatical for anything but weekly), and the
whole dialog had no visual grouping.
Redesigned: FlowRow (wraps instead of overflowing) for both chip rows;
single-letter weekday chips (S M T W T F S) to stay compact; a narrow
fixed-width (72dp) interval field paired with a correctly-pluralized
unit label rendered separately from the field; muted section labels
(REPEATS / EVERY / ON THESE DAYS) for structure. No callback/API
changes -- purely a layout rewrite of the same onSave/onClear/onDismiss
contract.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTUSAEKfsPc6WGDq45yPHD
|
|
TaskRow used a flat hardcoded gray (0xFFDDDDDD) while EventBlock used
Color.White (dimmed only when past) at the same size/weight -- the
color mismatch read as a font difference. Both now use the same
Color.White base.
CompleteTaskAction previously did no local update at all: the row
stayed visible until CompleteWorker's full complete() ->
fetchAndPersist() -> updateAll() round trip finished (two sequential
network calls). It now optimistically removes the completed item from
the cached list and re-renders immediately, matching RefreshTaskAction's
existing synchronous-flag-then-updateAll pattern -- the background
worker's own fetchAndPersist still replaces this with the authoritative
server state (including a newly-created recurring successor, if any)
moments later.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTUSAEKfsPc6WGDq45yPHD
|
|
onEnabled only fires when the first widget instance is added to a home
screen, so if the periodic RefreshWorker job is ever silently dropped
(an app reinstall can clear WorkManager's persisted schedule without
the widget itself being removed/re-added -- exactly what happens when
sideloading a new APK build, as opposed to a Play Store update), there
was no way for it to come back except manually removing and re-adding
the widget. onUpdate fires far more often (reboot, periodic OS ticks)
and now also re-arms the schedule (a no-op via KEEP if already running).
Root-caused via manual refresh restoring all missing content instantly
(rules out a data/rendering bug) plus the widget only showing one
stale morning event beforehand (consistent with the periodic job
having stopped ticking hours earlier, right around today's APK
reinstalls).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTUSAEKfsPc6WGDq45yPHD
|
|
Overdue and non-overdue tasks now render with the same plain title
color; no more red highlighting for overdue items.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTUSAEKfsPc6WGDq45yPHD
|
|
Editable title/description (Edit -> Cancel/Save toggle), linkified
description, and independently-tappable date/recurrence/next-date
chips. Non-doot sources (Trello, Google Tasks) are unaffected --
same title + Complete button as before, no live fetch, no edit UI.
|
|
|
|
|
|
legibility, soften overdue red
- AllDayRow now has the same 32dp leading gutter as HourRow's hour-label
column, so its color bar lines up with EventBlock's bar in the grid
below instead of sitting flush left; also dropped the title's
defaultWeight() so the multi-day label sits right after the title
instead of being pushed to the far-right edge.
- TaskFragmentBlock no longer renders its own "TODAY" sub-header --
it's always inside the already-labeled TODAY section, and Tomorrow's
floating tasks never had this redundant label to begin with.
- Section headers and hour/time labels bumped from ~30-40% to ~50-60%
opacity -- against a home-screen wallpaper the previous values were
barely legible.
- Overdue task text alpha reduced to 0.75 -- full-opacity bright red
read as too alarming.
|
|
|
|
The label was concatenated into the title string, inheriting its 0.9
alpha. Split into its own Text at full opacity, matching EventBlock's
treatment of upcoming (non-past) events.
|
|
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.
|
|
Tapping a calendar event (or any event-type item) now opens the event's
URL (Google Calendar, Plan to Eat, etc.) directly instead of showing an
intermediate popup with an "Open in Calendar" button. Removes
EventDetailActivity and the recurrence-schedule lookup it was the only
consumer of: WidgetRepository.getRecurrence, the Go
/api/widget/recurrence endpoint, HandleWidgetRecurrence,
GoogleCalendarAPI.GetRecurrenceRule, and formatRecurrence, plus their
tests. RecurringEventID itself stays -- it's general calendar-sync
metadata used elsewhere in the timeline pipeline, not exclusive to the
removed popup.
|
|
These were the only labels in the settings screen without an explicit
color, unlike the rest of the screen's text elements.
|
|
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.
|
|
TaskDetailActivity, QuickAddActivity, and EventDetailActivity shared the
app's default task affinity with SettingsActivity. FLAG_ACTIVITY_NEW_TASK
reuses an existing task with matching affinity rather than creating a
fresh one, so if SettingsActivity's task was still alive in recents,
these translucent popups rendered on top of it instead of the home
screen behind them, as the theme's transparency was designed to show.
|
|
Unused after threading WidgetTextSize's scaling functions through every
Text() call site that previously used a literal .sp value.
|
|
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTUSAEKfsPc6WGDq45yPHD
|
|
|
|
|
|
|
|
|
|
calcGridStart/calcGridEnd only looked at hour-of-day, so a tomorrow
event's early or late hour could inflate today's grid range with empty
rows, pushing the non-scrolling TomorrowSection below the widget's
visible area. Filter to today-only events before computing bounds.
|
|
Another session pushed 27 commits in parallel covering quick-add, event
detail popups, recurrence display, overdue badges, a manual refresh button,
and its own fix for the same overdue-tasks bug (via a separate
GetOverdueNativeTasks fetch folded into BuildTimeline, rather than widening
GetNativeTasksByDateRange's bound directly). Reconciled rather than blindly
taking one side:
- Reverted GetNativeTasksByDateRange to its original bounded query and kept
upstream's GetOverdueNativeTasks + BuildTimeline fold-in as the sole
overdue mechanism for native tasks, to avoid double-counting overdue
items (my widened query + their separate fetch would have both returned
them). Re-pointed the regression test at the now-correct contract and
added a store-level test for GetOverdueNativeTasks directly.
- Kept my GetGoogleTasksByDateRange fix as-is (single unbounded query) --
upstream never touched Google Tasks overdue handling, so there's no
duplication risk there.
- Rewove WidgetRoot's LazyColumn structure (added for scrolling) around
upstream's new header buttons, pinned all-day event rows, and the
enhanced TomorrowSection, none of which were written LazyColumn-aware
since that work landed on this side only.
- Combined both sides' additions to TaskDetailActivity/TaskDetailSheet
(description-edit detail popup + due-date reschedule label) and
WidgetRepository/Actions (optimistic local removal + refresh button
wiring) -- these were independent, non-overlapping features that both
needed to survive.
- Renumbered the migration collision: both sides independently added a
migration numbered 022. Card-description was already applied to the live
production DB under that filename earlier this session (migrations are
tracked by filename), so it keeps 022; the recurring-event-id migration,
never deployed under any name here, moves to 023.
Verified: go build clean, full test suite passes (only the two
pre-existing agent-handler failures and the pre-existing models package
build error remain, both confirmed unrelated via git stash before this
session began), and a dry run against a copy of the live production
database applies both migrations cleanly with no re-run conflicts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|
|
- Add description editing to the widget's task detail popup for
doot/gtasks/trello, backed by new GET /api/widget/detail and
POST /api/widget/update endpoints
- Make Google Tasks and Trello cards completable via the widget (Trello
completion archives the card); fix Trello description never being
fetched, which meant saving could silently wipe a card's real desc
- Fix google_tasks.due_date/updated_at (TEXT columns) never round-tripping
through sql.NullTime, which broke cached Google Tasks reads whenever
the cache was valid
- Fix native-task and Google-Task date-range queries excluding anything
due before the window start, which dropped incomplete tasks off the
widget the moment their due day passed (the "overdue tasks disappeared"
bug)
- Fix native task description edits blanking the task's title
- Make the widget's day list scroll (LazyColumn) instead of clipping
- Optimistically remove a task from the widget immediately on completion,
ahead of the authoritative background refresh
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|
|
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTUSAEKfsPc6WGDq45yPHD
|
|
|
|
|
|
|
|
|
|
|
|
WidgetItem.isAllDay was carried by the client's data model but nothing
ever read it. All-day events had no Start at all, so they fell into the
same floating-task queue as ordinary untimed tasks; if enough tasks were
ahead of one in the queue, SlotPacker could assign it an hour slot past
the visible grid range entirely -- not merely unpinned, actually
invisible.
Server: TimelineItemToWidgetItem now populates Start for all-day
CALENDAR EVENTS specifically (their real event date), while leaving
undated doot/gtask tasks -- also flagged IsAllDay as a "no specific
time" fallback, a different concept -- on the existing nil-Start
floating behavior.
Client: all-day events are filtered out of the hourly grid/floating-task
pipeline entirely, bucketed by day using the new Start date, and
rendered in a new pinned AllDayRow section right after the TODAY/TOMORROW
headers.
|
|
CompleteWorker.enqueue used a plain WorkManager.enqueue(), which allows
unlimited concurrent OneTimeWorkRequests. A rapid double-tap on the same
row (plausible since the checkbox doesn't visually update until the full
async round-trip -- complete() -> fetchAndPersist() -> updateAll() --
finishes) could spawn two independent, unordered CompleteWorker runs for
the same task, each doing its own fetchAndPersist(); a second worker's
fetch started before the first worker's complete() call had actually
landed server-side could persist a stale snapshot after the first
worker's correct one.
Now uses enqueueUniqueWork("complete_$id", KEEP, ...) so a tap on a task
that already has a completion in flight is dropped rather than racing a
second worker. Different task ids remain independent.
|
|
In Glance 1.1.0 (RemoteViews), a parent Row with .clickable() silently
overrides any nested child .clickable() — tapping the checkbox fired the
detail-open action instead of CompleteTaskAction, producing no visible
effect. Fix by splitting TaskRow into two sibling Boxes: a 24dp tap
target wrapping the checkbox icon (routes to CompleteTaskAction) and a
defaultWeight Box for the title (routes to actionStartActivity). No
nesting, so both actions are independently reachable.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Widget now renders a TOMORROW block below the today grid: events with
inline time labels (slightly dimmed) and task rows. Separated by a
divider. Covers both explicit-start tomorrow items and slot-packed
fragments that overflow from today.
Web view: TODO comment to flatten the tomorrow section to match widget.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Tapping a doot task shows a date picker. On confirm, POSTs to
/api/widget/reschedule, updates due_date in native_tasks, refreshes
widget. Reschedule button only shows for source="doot" tasks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Adds doot-owned task storage (native_tasks table) so tasks can be
managed without Todoist. CompleteTask for 'doot' source just updates
the DB — no external API call, no token dependency.
Migration path:
POST /settings/import-from-todoist — copies Todoist cache → native_tasks
Then remove TODOIST_TOKEN from .env to disable Todoist
Changes:
- migration 020: native_tasks table
- store: GetNativeTasks, GetNativeTasksByDateRange, GetUndatedNativeTasks,
CreateNativeTask, CompleteNativeTask, UncompleteNativeTask,
UpdateNativeTask, ImportFromTodoist
- timeline: native tasks appear as source="doot" (teal)
- handleAtomToggle: "doot" case — no external API needed
- HandleWidgetComplete: method on Handler, handles "doot" natively
- HandleUnifiedAdd: "doot" source creates in native_tasks
- widget: "doot" tasks are completable, teal color indicator
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Replaces browser deep-link with a transparent TaskDetailActivity that
shows a Material3 ModalBottomSheet (20-40% screen height). The launcher
shows through the transparent window behind the dark scrim. Sheet shows
source color dot, task title, and Mark Complete button for Todoist tasks.
Tapping outside or swiping down dismisses. No browser involved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Adds GET /task?id=xxx&source=xxx route that renders a full mobile-friendly
task detail page (session-protected). Widget task rows now open this page
when tapped. HandleUpdateTask redirects back to the page after a non-HTMX
save. Android: threads serverUrl through composable chain to TaskRow.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
|
|
|
|
|
|
|
|
import in tests
|
|
SettingsActivity
|
|
|