summaryrefslogtreecommitdiff
path: root/internal/handlers
AgeCommit message (Collapse)Author
2026-07-16feat: add authenticated /docs viewer for superpowers specs/plansPeter Stone
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.
2026-07-16fix(web): account for Google Calendar's exclusive all-day end-datePeter Stone
A single-day all-day event's End.Date is one calendar day past its own day (Google Calendar convention, already documented by TestParseEventTime_AllDayEvent). isMultiDayEvent/multiDayVariant didn't adjust for this, so every all-day event -- including single-day ones -- was misclassified as multi-day and duplicated across Today and Tomorrow.
2026-07-16feat(web): show multi-day calendar events on every day they spanPeter Stone
TimelineItemView wraps a TimelineItem with a per-render-day MultiDayVariant (starts/ends/spans/none). HandleTimeline's bucketing now places a multi-day event into both TodayItems and TomorrowItems when it touches both, instead of only the list matching its start day.
2026-07-16fix(widget): forward End for all-day multi-day calendar eventsPeter Stone
A genuine all-day Google Calendar event spanning multiple days (e.g. a 3-day conference) never got its EndTime forwarded to the widget client, since the conversion only set End when !IsAllDay. The client has no way to detect a multi-day span without both Start and End.
2026-07-16feat(widget): tap event to open its source directly, drop detail popupPeter Stone
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.
2026-07-13Merge github/master: reconcile with parallel widget workPeter Stone
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
2026-07-13feat(widget): editable task details, scrollable list, and overdue-task fixesPeter Stone
- 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
2026-07-12feat(widget): add recurrence lookup endpoint (GET /api/widget/recurrence)Peter Stone
2026-07-12feat(widget): capture and forward RecurringEventID for calendar eventsPeter Stone
2026-07-12feat(widget): add POST /api/widget/add for quick-addPeter Stone
2026-07-12feat(widget): add DueDate field for doot tasks to the widget APIPeter Stone
2026-07-12feat(widget): forward IsOverdue from TimelineItem to WidgetItem APIPeter Stone
2026-07-12fix(timeline): include overdue native tasks in timeline and widgetPeter Stone
GetNativeTasksByDateRange's SQL bound (due_date >= start) excluded any task overdue from a previous day before ComputeDaySection ever got a chance to mark it IsOverdue, so both the web Timeline view and the widget API (which both call BuildTimeline with start = today) silently dropped overdue tasks entirely -- only the Tasks tab (unbounded GetNativeTasks) showed them. Added GetOverdueNativeTasks and folded its results into BuildTimeline alongside the ranged fetch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VTUSAEKfsPc6WGDq45yPHD
2026-07-12fix(calendar): split comma-joined GOOGLE_CALENDAR_ID before usePeter Stone
fetchCalendarEvents falls back to config.GoogleCalendarID whenever no source_configs rows exist yet for the gcal source -- which is the current live state (0 rows). GoogleCalendarID is a single env var that itself holds a comma-separated list of calendar IDs, but the whole joined string was being wrapped in a single-element []string{...} and passed straight to SetCalendarIDs. Google's API takes one calendarId per call, so every fetch failed with '404 Not Found' on the literal comma-joined string -- confirmed live in production logs. This broke all calendar events, in both the web dashboard and the widget, silently. Now splits on commas and trims whitespace before use.
2026-07-12fix(widget): pin all-day calendar events to the top instead of losing themPeter Stone
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.
2026-07-12fix(widget): surface unmatched task IDs instead of silently no-oppingPeter Stone
CompleteNativeTask/UncompleteNativeTask/RescheduleNativeTask ran a plain UPDATE ... WHERE id = ? and returned whatever error Exec gave back -- which is nil even when 0 rows match, since that's not a SQL error. A stale or wrong id from the widget looked identical to a real completion: HTTP 200, nothing changed in the database. Real incident: 1 of 3 widget completeTask taps silently no-opped this way. Now checks RowsAffected() and returns ErrNativeTaskNotFound (mirrors the existing pattern in sqlite.go's ApproveAgentSession/DenyAgentSession). HandleWidgetComplete and HandleWidgetReschedule surface this as 404 instead of a fake 200, so the widget can tell 'nothing changed' apart from 'it worked.'
2026-07-06feat: remove Todoist integration entirelyDoot Agent
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>
2026-07-05Merge deploy/master (gateway/proxy fixes) with doot-native task completion fixPeter Stone
Reconciles diverged histories: deploy/master had gateway proxy Host-header and publicPaths fixes that were never synced back to local/github; this branch had the native-task Tasks-tab/completion fix. Merging both.
2026-07-05fix: native tasks missing from Tasks tab and blank title on completionDoot Agent
Bug 1: BuildUnifiedAtomList never called GetNativeTasks(), so doot-sourced tasks were absent from the web Tasks tab. Added GetNativeTasks() fetch and NativeTaskToAtom conversion (new model helper, SourceDoot constant). Bug 2: handleAtomToggle called getAtomDetails after CompleteNativeTask, but GetNativeTasks filters WHERE completed=0, so the task was already gone and the title came back blank. Moved getAtomDetails call to before the completion switch so all sources (including doot) capture title/dueDate first. Also fixed widget_test.go compile error: TestHandleWidgetComplete_NonTodoist called HandleWidgetComplete as a package-level function but it is a method on *Handler. Rewrote the file as package handlers (internal) and construct &Handler{} directly. Regression tests added for both bugs in atoms_test.go and handlers_test.go. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05fix(proxy): set Host header to upstream target to satisfy loopback MCP ↵Peter Stone
DNS-rebinding check mcp.NewStreamableHTTPHandler rejects requests where the server is bound to loopback but the Host header is not (DNS-rebinding protection). Requests proxied through doot arrived at claudomator with Host: doot.terst.org, triggering a 403. Set req.Host = target.Host in the ReverseProxy Director so upstream services see the target host (e.g. 127.0.0.1:8484) rather than the original public hostname.
2026-06-29fix: calendar/meals sync, tomorrow flat layout, /health endpointPeter Stone
- 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>
2026-06-29feat: reschedule tasks from widget bottom sheetPeter Stone
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>
2026-06-29feat: make Todoist optional, guard nil client callsPeter Stone
TODOIST_API_KEY is no longer required — native task management works without it. Guards nil todoistClient in handleAtomToggle. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29feat: native task management + Todoist migrationPeter Stone
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>
2026-06-29feat: add standalone task detail page for Android widget deep-linksPeter Stone
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>
2026-06-29fix: widget doubling (hourEnd), transparent bg, tomorrow items, larger textPeter Stone
2026-06-29fix: widget handler timezone, auth guard, and type switch clarityPeter Stone
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29feat: add widget HTTP handlers with bearer token authPeter Stone
Implements WidgetAuthMiddleware (static bearer token), TimelineItemToWidgetItem (conversion helper), HandleWidgetGet (today's items as JSON), and HandleWidgetComplete (proxies task completion to Todoist). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15feat: service gateway framework + playground demoPeter Stone
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>
2026-06-03fix: add CSRF and WebSocket origin protection to Claudomator proxyPeter Stone
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>
2026-06-03feat: synchronize background image rotation across sessionsPeter Stone
Server seeds bg URL from a 5-minute time bucket so all open sessions show the same image simultaneously. Client-side JS rotates in sync and prefetches the next image on the same interval. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25feat: add Claudomator stories as atom source in Doot tasks tabClaude Agent
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25feat: gate Claudomator UI behind Doot session auth via reverse proxyDoot Agent
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23fix: support multiple enabled Google Task lists and CalendarsPeter Stone
- Update Google API interfaces with setters for list/calendar IDs - Update fetchCalendarEvents and fetchGoogleTasks to use enabled IDs from source_configs - Update timeline logic tests to reflect interface changes and seed config data - Ensure dashboard respects user-selected lists from settings
2026-03-23feat: unify Google Tasks with main system via caching and integrated UIPeter Stone
- Implement SQLite caching layer for Google Tasks - Integrate Google Tasks into unified Atoms loop (showing in Tasks tab) - Update Planning tab to include cached Google Tasks - Enhance Quick Add form with Todoist project selector - Remove orphaned HandleTasksTab/HandleRefreshTab methods - Update tests to reflect new BuildTimeline signature and data structures
2026-03-23feat: complete Agent Context API Phase 2 & 3 (Write/Create/Management)Peter Stone
- 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
2026-03-23fix: parse Todoist local datetimes, show near-future tasks, add undated ↵Peter Stone
tasks to timeline - parseDueDate: handle date field containing "YYYY-MM-DDTHH:MM:SS" (local time, no tz offset) — Todoist REST API v1 uses this format for recurring tasks with a set time, causing due dates to silently parse as nil - IsFuture threshold: widen from tomorrow to 7 days out so tasks due this week show in the main tasks section with dates visible (not collapsed) - BuildTimeline: include undated Todoist tasks in the Today section (mirrors existing Google Tasks behavior) - GetUndatedTasks: new store method for tasks with due_date IS NULL Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22chore: unify and centralize agent configuration in .agent/Peter Stone
2026-03-21feat: Phase 1 — remove bug feature and dead codePeter Stone
- 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>
2026-03-19refactor: RF-04/08 migrate inline HTML to templates, standardize form parsingClaude Agent
RF-04: Already complete in codebase (handlers use HTMLResponse + partials). RF-08: Migrate single-required-field handlers from Pattern A/B to Pattern C (requireFormValue). Affected: HandleCompleteTask, HandleCompleteCard, HandleReportBug, HandleCreateTask in handlers.go; HandleToggleFeature, HandleCreateFeature in settings.go. Reduces 6-8 lines of boilerplate to 2. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19test: add coverage for planning tab, meals, Google Tasks, bug handlers, ↵Agent
completed task parsing - HandleTabPlanning: happy-path test verifying tasks/cards land in correct sections (scheduled/unscheduled/upcoming); boundary test confirming a task due exactly at midnight of tomorrow lands in Upcoming, not Scheduled - HandleTabMeals: grouping test verifying two meals sharing date+mealType produce one CombinedMeal with both recipe names merged - Google Tasks GetTasksByDateRange: four boundary tests (start inclusive, end exclusive, no-due-date always included, out-of-range excluded) using redirectingTransport mock server pattern - HandleGetBugs: data assertions verifying bug list and empty-list cases - HandleReportBug: success test verifying bug is saved and bugs template is re-rendered - GetCompletedTasks: timestamp parsing test ensuring CompletedAt is not zero when inserted with a known "2006-01-02 15:04:05" string Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18refactor: RF-03/06 extract groupMeals helper, eliminate ↵Claude
convertSyncItemToTask wrapper RF-03: Extract shared groupMeals helper into internal/handlers/meals.go. Both HandleTabMeals and BuildTimeline now call groupMeals instead of duplicating the date+mealType grouping algorithm inline. CombinedMeal gains ID and Meals fields to carry the first-meal ID and original records needed by BuildTimeline when constructing TimelineItems. RF-06: Add api.ConvertSyncItemToTask for single-item conversion. ConvertSyncItemsToTasks now delegates to it, eliminating duplication. The Handler.convertSyncItemToTask wrapper (which allocated a one-element slice just to unwrap it) is deleted; its caller uses api.ConvertSyncItemToTask directly. Covered by TestConvertSyncItemToTask in internal/api/todoist_test.go. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18chore: autocommit uncommitted changesClaudomator Agent
2026-03-04feat: sync log, cache clear endpoint, Todoist projects from cached tasksPeter Stone
- 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>
2026-02-17Add calendar cache layer, incremental sync tests, completion assertionsPeter Stone
- Google Calendar events now cached via CacheFetcher pattern with stale-cache fallback on API errors (new migration 015, store methods, fetchCalendarEvents handler, BuildTimeline reads from store) - Todoist incremental sync path covered by 5 new tests - Task completion tests assert response body, headers, and template data Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07Add no-cache header tests, update SESSION_STATE with audit resultsPeter Stone
- Added TestHTMLResponse_SetsNoCacheHeaders and TestJSONResponse_SetsNoCacheHeaders - Rewrote SESSION_STATE.md with verified test coverage per completed item - Documented known gaps (Google API client unit tests, WebAuthn env vars) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07Fix passkey registration showing broken UI when WebAuthn not configuredPeter Stone
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>
2026-02-07Add build version footer, deploy ldflags, template test helper, and logs scriptPeter Stone
- 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>
2026-02-07Fix timeline task completion replacing view, fix passkey registration CSRFPeter Stone
- Fix checkboxes in timeline calendar grid targeting #tab-content (replaced entire view). Now target closest .untimed-item/.calendar-event with outerHTML - Fix passkey registration 403 by passing CSRFToken from settings handler and exposing it via meta tag for JS to read - Add TDD workflow requirement to CLAUDE.md - Tests written first (red-green): template content assertions for checkbox targets and CSRF token presence, handler data struct verification Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06Fix missing settings button, disappeared events, and tab refresh bugPeter Stone
- Add settings gear icon link to dashboard header - Fix GetTasksByDateRange/GetCardsByDateRange to include overdue items (changed from BETWEEN to <= end, filter completed tasks) - Fix refresh replacing active tab with tasks tab by using htmx.trigger(body, 'refresh-tasks') instead of innerHTML+htmx.process - Add refresh-tasks hx-trigger to meals, shopping, conditions tabs - Add tests for overdue inclusion/exclusion, settings link, template data Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>