<feed xmlns='http://www.w3.org/2005/Atom'>
<title>doot.git/internal/config, branch master</title>
<subtitle>doot — personal productivity web app
</subtitle>
<id>https://git.terst.org/doot.git/atom?h=master</id>
<link rel='self' href='https://git.terst.org/doot.git/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/'/>
<updated>2026-08-16T00:03:51+00:00</updated>
<entry>
<title>Replace Google Tasks service-account auth with real OAuth</title>
<updated>2026-08-16T00:03:51+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-08-16T00:03:51+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=3660486153a16760d2b980e546bbbd29408fb8d4'/>
<id>urn:sha1:3660486153a16760d2b980e546bbbd29408fb8d4</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>Fix production wedge: propagate context to Google Calendar API calls</title>
<updated>2026-08-04T20:10:25+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-08-04T20:10:25+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=0a410243dea33f204764000be81814e541dcae48'/>
<id>urn:sha1:0a410243dea33f204764000be81814e541dcae48</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>feat: add authenticated /docs viewer for superpowers specs/plans</title>
<updated>2026-07-16T02:44:02+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-14T18:13:22+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=9a7f19270c50def47be5dc1a9b30d8655d7098c9'/>
<id>urn:sha1:9a7f19270c50def47be5dc1a9b30d8655d7098c9</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>feat: remove Todoist integration entirely</title>
<updated>2026-07-06T00:00:59+00:00</updated>
<author>
<name>Doot Agent</name>
<email>agent@doot.local</email>
</author>
<published>2026-07-06T00:00:59+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=945c34590677e161a711ccaff0e1077197b1b178'/>
<id>urn:sha1:945c34590677e161a711ccaff0e1077197b1b178</id>
<content type='text'>
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 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: make Todoist optional, guard nil client calls</title>
<updated>2026-06-29T09:08:02+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-06-29T09:08:02+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=509ae15621a3981246e8b2b54804959294938d42'/>
<id>urn:sha1:509ae15621a3981246e8b2b54804959294938d42</id>
<content type='text'>
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 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: add WidgetToken config + WidgetItem model for Android widget API</title>
<updated>2026-06-29T00:35:17+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-06-29T00:35:17+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=6c2df12903bb9e4688866e30a446c69e91283c84'/>
<id>urn:sha1:6c2df12903bb9e4688866e30a446c69e91283c84</id>
<content type='text'>
- Add WidgetToken field to Config struct to store bearer token for /api/widget endpoint
- Load WidgetToken from WIDGET_TOKEN environment variable (optional)
- Create WidgetItem and WidgetResponse models for widget API responses
- Widget token authentication is optional; endpoint won't authenticate if token is empty

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: add scout service mount to gateway</title>
<updated>2026-06-18T00:44:14+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-06-18T00:44:14+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=35e5cb916391e98181169d0228fc23514362988f'/>
<id>urn:sha1:35e5cb916391e98181169d0228fc23514362988f</id>
<content type='text'>
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 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: service gateway framework + playground demo</title>
<updated>2026-06-15T09:14:14+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-06-15T09:14:14+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=c86abd29845fa6feb61b1fddb8e8bb41312d55a8'/>
<id>urn:sha1:c86abd29845fa6feb61b1fddb8e8bb41312d55a8</id>
<content type='text'>
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 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: gate Claudomator UI behind Doot session auth via reverse proxy</title>
<updated>2026-03-25T04:03:13+00:00</updated>
<author>
<name>Doot Agent</name>
<email>agent@doot.terst.org</email>
</author>
<published>2026-03-25T04:03:13+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=2db5020047640361066510f29f908ca9fd1c99aa'/>
<id>urn:sha1:2db5020047640361066510f29f908ca9fd1c99aa</id>
<content type='text'>
Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Add passkey (WebAuthn) authentication support</title>
<updated>2026-02-06T01:35:01+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-02-06T01:35:01+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/doot.git/commit/?id=0a1001eb0bd2d1f7c0624ae1ef8ae7ccdb3447d4'/>
<id>urn:sha1:0a1001eb0bd2d1f7c0624ae1ef8ae7ccdb3447d4</id>
<content type='text'>
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 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
