| Age | Commit message (Collapse) | Author |
|
Tapping an item outside shopping mode now opens an edit sheet. User-added items
can be renamed, moved between stores and sections, and deleted. PlanToEat items
show why they cannot be edited — the client is read-only, so presenting fields
that silently would not save is worse than saying so — and offer removal, which
marks them checked, the same mechanism shopping mode already uses.
This needed a store registry first. Stores previously existed only because some
item referenced them by name, with Trello's list names quietly acting as the
registry; removing Trello as a source made the gap visible. Without a registry
you cannot move an item into an empty store, and moving the last item out of a
store makes it vanish mid-edit. Migration 034 adds shopping_stores and
shopping_sections, seeded from the stores currently in use, and gives items a
section column — sections previously existed only as PlanToEat category
strings, so a user item could not be filed under one at all.
Registered stores and sections now appear in the list even when empty, which is
the whole point of the registry.
Two deliberate choices about destructive edges:
* Renaming a store renames it and moves its items in one transaction. Items
reference the store by name, so doing either alone orphans them.
* Deleting a store or section keeps the items, moving them to Unassigned or
clearing the section. Losing the list is far worse than re-filing an item.
* An UPDATE matching no rows is an error, not a silent success, so a stale id
cannot look like a saved edit.
Section options are rebuilt per store in the edit sheet rather than shipped as
one flat list, so a Costco item cannot be filed under a Safeway aisle.
18 new store tests, 11 handler tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|
|
doot reads Google Calendar through a service account, by calendar ID. That
cannot consume an .ics URL, and it cannot see a calendar subscribed to inside a
personal Google account either, because Google does not allow re-sharing a
subscribed calendar. Published schedules — sports fixtures, school terms, bin
collections — are distributed almost exclusively as .ics, so none of them could
be added at all.
Feeds are now fetched on the normal calendar refresh and merged into
calendar_events alongside Google events, so the timeline, widget and everything
downstream keep seeing one list and needed no changes.
Failure handling is deliberately asymmetric. A Google error is still returned as
an error, unchanged, so an outage there surfaces. A single failing feed is
recorded against that feed and skipped, because one dead URL should not take the
whole calendar down — and last_error is shown in settings, since a feed that has
quietly stopped resolving otherwise looks identical to a team with no games
left.
The parser handles what real feeds actually contain: line unfolding, CRLF and
LF, UTC / TZID / VALUE=DATE times, and escaped text. Two details worth naming:
a TZID time is wall-clock in that zone, and reading it as UTC would put a 1:15pm
Pacific kickoff on the timeline at 1:15pm local — wrong by the offset but
entirely plausible-looking. And non-calendar content is a hard error rather than
zero events, because an HTML error page from a moved feed URL is the common
failure and "no events" reads as success.
RRULE is deliberately unsupported: every schedule feed this targets expands
recurrence itself, one VEVENT per fixture, and a correct expansion is a large
amount of subtle code. A recurring event appears once rather than wrongly.
Settings gains an add/enable/disable/remove UI. webcal:// links are accepted and
rewritten, since that is how feeds are usually advertised.
16 parser tests, 5 store tests. Verified against the real Seattle Seahawks feed:
21 events, all with valid times.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|
|
The Trello "Shopping" board had become a dumping ground: of 24 cards, 13 had no
list at all (rendering as "Uncategorized"), and the rest were spread across
Costco, Hardware Store, "Perennial " (trailing space, so a separate store from
"Perennial") and online. Its items were read-only in doot — no rename, no move,
no delete, only hide — and its list names were doubling as an accidental store
registry, which is the only reason stores like Hardware Store existed at all.
Migration 032 imports the two groups asked for, Hardware Store and the unlisted
cards, into user_shopping_items. Costco, Perennial and online cards are left
behind. Nothing is destroyed: the cards remain in Trello, this only stops doot
displaying them. Imported rows become ordinary user items, so they also become
editable, which Trello-sourced items never were.
Trello remains the source for the other eight boards; this is only the Shopping
board feeding the shopping list.
Also updated the quick-action modal's Shopping tab, which posted source=trello
with a Trello list_id. Left alone it would have kept creating cards that no
longer appear anywhere. It now posts source=shopping with a store name, handled
by a new case in HandleUnifiedAdd, and its picker reads from a new
/partials/shopping-stores endpoint. The Trello-lists endpoint it used to call
is deleted along with its route.
Note for the edit feature still to come: stores have no existence independent
of items, so a store with nothing in it cannot be chosen. That was previously
masked by Trello's lists persisting. "Move between stores and sections" needs a
real stores/sections table.
Migration tested against a seeded snapshot of the real board, including the
trailing-space store name, other boards, replay safety and the empty-cache
no-op. Two existing tests asserting Trello check-state in shopping mode were
updated to plantoeat, the only remaining external source.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|
|
due_date had been carrying two incompatible meanings: "this has a real
deadline" (Bills, meds, a signup cutoff) and "this is when I intend to
work on it." Chains leaned entirely on the second -- every step was
stamped due_date = now the moment it unlocked -- so a five-minute call
and a six-month certification both arrived looking overdue, and the only
way to quiet them was to hand-reschedule. The list therefore always
contained work that could not be finished that day, which made ending a
day at zero impossible by construction rather than by effort.
Splits the two apart:
- due_date keeps only its real meaning: an externally-imposed deadline.
- committed_date (new, migration 031) means "I chose this for today."
Chain steps no longer get a due date on unlock (CreateChain, advanceChain,
and the delete-promotion path), and are excluded from the undated list so
they don't simply fall through to it instead. They reach the day only by
being committed, via a "+ today" control on the chain card. Bucket-active
items get the same control. BuildTimeline pulls in committed tasks,
deduped against tasks already present for a different reason (committed
and due-today are two reasons for one row, not two rows).
Migration 031 also clears the auto-stamped due dates from open chain
steps; completed steps keep theirs as historical record.
Two supporting changes made while in here:
- Extracted the native_tasks SELECT list into nativeTaskColumns. It had
been copy-pasted into nine queries that must each agree exactly with
scanNativeTasks' field order; adding committed_date meant editing all
nine correctly or silently breaking whichever was missed.
- Extracted templateFuncMap() and added TestTemplates_AllParse. New()
only logs a warning when template parsing fails, so a malformed
template shipped silently and surfaced as a broken page at runtime.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|
|
Single-user personal server -- there's no one else's session at risk
from staying logged in, so the short expiry was just friction. The 24h
idle timeout was the more disruptive of the two in practice: a day of
not opening the dashboard is a normal gap, not "abandoned," and was
forcing a re-login well before the 7-day Lifetime ever mattered.
Setting IdleTimeout = SessionLifetime rather than a separate shorter
value collapses this to one real limit instead of two overlapping ones
that could reproduce the same complaint later (e.g. a few weeks away).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
|
|
From the sitemap audit: /tabs/meals (same dead-tab pattern as the
removed /tabs/conditions, only reachable via ?tab=meals with no nav
button), /partials/lists (superseded by inline .Lists rendering in
trello-board.html), /shopping/toggle and /shopping/mode/{store}/toggle
(superseded by the one-way complete/filter model), plus the orphaned
trello-boards.html and error-banner.html templates that nothing
rendered or included. Rewrote the meals grouping test to exercise
groupMeals() directly since that logic is still live via the timeline.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017GMEkHeqKz6FLkmizowBTK
|
|
Only the standalone /conditions page was ever meant to exist; the
HTMX tab variant was dead weight, reachable only via an unlinked
?tab=conditions query param with no button in the tab bar.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017GMEkHeqKz6FLkmizowBTK
|
|
Service-account auth structurally cannot see a regular user's personal
task lists (no equivalent of Calendar's per-item sharing model) --
confirmed via GetTaskLists returning exactly the service account's own
empty "My Tasks" list, never the real user's three lists. Zero rows
were ever cached in production as a result.
Adds a standard 3-legged OAuth flow: /settings/google-tasks/connect
redirects to Google's consent screen (AccessTypeOffline+ApprovalForce
so a refresh_token is always issued), /callback exchanges the code and
persists the token (new oauth_tokens table), /disconnect clears it.
GoogleTasksClient now takes an option.ClientOption instead of a
credentials file path; NewGoogleTasksOAuthClient wraps it with a
dbTokenSource that reloads/refreshes from the DB on each access-token
expiry and re-persists -- carefully preserving the original
refresh_token when Google's refresh response omits one (it usually
does), which would otherwise silently and permanently break future
refreshes.
Settings page shows connection status and a Connect/Disconnect
button. Calendar keeps using service-account auth (that one actually
works). Requires a one-time manual step: create an OAuth 2.0 Client ID
in Google Cloud Console and set GOOGLE_OAUTH_CLIENT_ID/SECRET in .env
-- documented in .env.example.
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
GET /api/widget/task, POST /api/widget/task/update,
POST /api/widget/task/recurrence, POST /api/widget/task/next-date.
Doot-only; the recurrence/next-date fields are null in the detail
response for a non-recurring task.
|
|
A recurring task's successor now also gets created once its due date
passes, independent of completion -- an ignored/overdue recurring task
no longer blocks the next occurrence from appearing. Runs every 15
minutes via a new goroutine in main.go, cancelled on shutdown.
|
|
Renders docs/superpowers/{specs,plans}/*.md to HTML via goldmark,
served from doot's own web server behind the existing session auth
instead of as raw files. DOCS_DIR is configurable (defaults to
docs/superpowers relative to the working directory) so the deployed
server can point straight at the working repo and stay live-synced
with no separate copy/deploy step for new docs.
|
|
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.
|
|
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
|
|
|
|
|
|
Native tasks (native_tasks table) fully replace Todoist. All Todoist
API code, store functions, handlers, routes, templates, and tests have
been removed. Migration 021 drops the now-unused tasks cache table.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Adds a publicPaths field to serviceMount alongside webhookPaths.
webhookPaths remains POST-only (HMAC-signed webhooks). publicPaths
bypasses session auth for all methods, relying on the upstream
service's own bearer-token auth instead.
Used to expose /claudomator/chatbot/mcp for MCP client connections
(claude-code) which carry their own Authorization header and cannot
carry a doot session cookie.
|
|
- Calendar: fall back to GOOGLE_CALENDAR_ID config when no source_configs
synced yet (fixes blank calendar after fresh deploy)
- Meals: call fetchMeals in HandleTimeline so PlanToEat cache refreshes
on every timeline load, not just during manual refresh
- Tomorrow section: replace calendar-grid with flat chronological list
matching widget layout (time label | source bar | title)
- Add /health endpoint (no auth required) for deploy health checks
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>
|
|
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>
|
|
Add widget API routes for Android widget integration. Routes use bearer token
authentication and are placed outside session-protected groups. GET
/api/widget returns today's timeline items; POST /api/widget/complete
completes Todoist tasks. Routes only register if WIDGET_TOKEN env var is set.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Wires scout listing feed (SCOUT_URL env var) into the service gateway
loop. Set SCOUT_URL=http://127.0.0.1:8081 to expose it at /scout/.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Formalizes doot as an authenticated reverse proxy for arbitrary upstream
services. Any local HTTP service can now be registered behind doot's
existing auth + SSL layer with 3 lines of config.
- Rename NewClaudomatorProxy → NewServiceProxy (generic)
- Replace hard-coded claudomator proxy block with serviceMount slice loop
- Add PlaygroundURL config (PLAYGROUND_URL env var)
- Add playground/web/server.py: stdlib Python status page on port 9090
- Document pattern in .agent/design.md; update .env.example
To add a new service: set its URL env var, add a config field, append
one mount entry in main.go. Claudomator's GitHub webhook bypass is
expressed as webhookPaths on its mount.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Token-based CSRF is impractical for a reverse proxy whose UI doesn't
know Doot's session tokens, so state-changing requests to /claudomator/*
are now validated against the configured WebAuthn origin via Origin/Referer
header. WebSocket upgrades reject mismatched or missing Origin headers
before hijacking the connection. Both checks are no-ops when WebAuthnOrigin
is unset (local dev).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
|
|
- Implement write operations (complete, uncomplete, update due date, update task)
- Implement create operations (create task, add shopping item)
- Add Trusted Agents management UI in Settings with revocation support
- Fix SQLite timestamp scanning bug for completed tasks
- Add comprehensive unit tests for all new agent endpoints
- Update worklog and feature documentation
|
|
- Delete Bug struct, BugToAtom, SourceBug, TypeBug, TypeNote
- Remove bug store methods (SaveBug, GetBugs, ResolveBug, etc.)
- Remove HandleGetBugs, HandleReportBug, bug branches in handlers
- Remove bug routes, bugs.html template, bug UI from index.html
- Remove AddMealToPlanner stub + interface method
- Migration 018: DROP TABLE IF EXISTS bugs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- migration 016: sync_log table
- store: AddSyncLogEntry, GetRecentSyncLog, InvalidateAllCaches, GetProjectsFromTasks
- settings: HandleClearCache (POST /settings/clear-cache), SyncLog in page data
- settings: use GetProjectsFromTasks instead of deprecated Todoist REST /projects
- handlers: populate atom projects from store
- agent: log warning on registration failure instead of silently swallowing
- google_tasks: simplify URL literal
- tests: sync log CRUD, clear cache handler, settings page includes sync log,
sync sources adds log entry, incremental sync paths, task completion
response/headers, calendar cache fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Root cause: WEBAUTHN_RP_ID and WEBAUTHN_ORIGIN env vars not set in
production, so WebAuthn is nil and all passkey endpoints return 404.
The settings page was unconditionally showing the passkey registration
card, leading to confusing "Failed to start registration" errors.
Fix: Pass WebAuthnEnabled flag from main.go through Handler to the
settings template, which now conditionally renders the passkey card
only when WebAuthn is properly configured.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
- Display build commit hash in unobtrusive footer overlay
- Inject buildCommit/buildTime via ldflags in deploy.sh
- Add assertTemplateContains test helper, refactor existing template tests
- Add scripts/logs for fetching production journalctl via SSH
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
Enable passwordless login via passkeys as an alternative to password auth.
Users register passkeys from Settings; the login page offers both options.
WebAuthn is optional — only active when WEBAUTHN_RP_ID and WEBAUTHN_ORIGIN
env vars are set.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
Session improvements:
- Extend session lifetime to 7 days for mobile convenience
- Add idle timeout to extend session on activity
- Use standard cookie name for better compatibility
Shopping model:
- Add FlattenItemsForStore helper for extracting store items
- Add StoreNames helper for store list
- Improve shopping-tab.html with inline add forms
Frontend:
- Add WebSocket reconnection and agent approval UI to app.js
- Simplify timeline calendar JS (move event positioning to CSS)
- Update login page styling
Deployment:
- Remove unused git checkout step from deploy.sh
- Update apache.conf WebSocket proxy settings
Documentation:
- Add Agent Context API feature spec to issues/
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
|
Shopping mode:
- Click to complete items (deletes user items, hides external items)
- Add print button with compact two-column print layout
- Fix CSRF token for HTMX requests
- Fix input clearing with proper htmx:afterRequest handler
- Remove "Quick Add" store option, require valid store
Navigation:
- Replace dropdown menu with flat nav showing all tabs
- Remove unused dropdown JS
Tests:
- Add TestHandleShoppingModeComplete for user and external items
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
|
- Add feature_toggles table (migration 012)
- Add source_config table for future source selection (migration 013)
- Create settings page at /settings with:
- Feature toggle management (enable/disable/create/delete)
- Data source configuration (sync and toggle boards/calendars)
- Add store methods for feature toggles and source config
- Add GetCalendarList and GetTaskLists to Google API clients
- Document feature toggle workflow in DESIGN.md
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
|
Phase 1: Authentication and read-only context
- POST /agent/auth/request - request access with name + agent_id
- GET /agent/auth/poll - poll for approval status
- POST /agent/auth/approve|deny - user approval (browser auth required)
- GET /agent/context - 7-day timeline context (agent session required)
Phase 1.5: Browser-only agent endpoints (HTML pages)
- GET /agent/web/request - request page with token
- GET /agent/web/status - status page with polling
- GET /agent/web/context - context page with timeline data
WebSocket notifications:
- GET /ws/notifications - push agent requests to browsers
- Approval modal with trust indicators and countdown timer
Database:
- agents table for registered agent tracking
- agent_sessions table for pending/active sessions
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
|
Bug fixes:
- #62: Increase FAB button z-index from z-40 to z-50
- #63: Combine multiple meals per date+mealType in Meals tab
- #64: Make /conditions route public (no auth required)
Changes:
- FAB button now z-50 (same as modals, appears on top when scrolling)
- Meals tab groups meals by date+mealType, joins recipe names with " + "
- Conditions page moved outside protected routes group
DESIGN.md updates:
- Updated z-index hierarchy table
- Added Meals View section
- Noted conditions page is public
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
|
- New GoogleTasksClient for fetching and managing Google Tasks
- Tasks appear in Timeline view with yellow indicator dot
- Tap checkbox to complete/uncomplete tasks via Google API
- Shares credentials file with Google Calendar (GOOGLE_CREDENTIALS_FILE)
- Configure task list via GOOGLE_TASKS_LIST_ID env var (default: @default)
- Supports comma-separated list IDs for multiple lists
New files:
- internal/api/google_tasks.go - Google Tasks API client
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|