summaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-02-07 13:47:13 -1000
committerPeter Stone <thepeterstone@gmail.com>2026-02-07 13:47:13 -1000
commit0620afc98fdc0f764e82807bb0090b78618ddb1d (patch)
treef0a868246adaa1a3e47ffa227787ce2eb3158e4a /web
parent27ee1a271248e9f1de8ecb981a6cabfa8e498b1b (diff)
Fix timeline task completion replacing view, fix passkey registration CSRF
- 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>
Diffstat (limited to 'web')
-rw-r--r--web/templates/partials/timeline-tab.html16
-rw-r--r--web/templates/settings.html15
2 files changed, 10 insertions, 21 deletions
diff --git a/web/templates/partials/timeline-tab.html b/web/templates/partials/timeline-tab.html
index c73c1b5..4979744 100644
--- a/web/templates/partials/timeline-tab.html
+++ b/web/templates/partials/timeline-tab.html
@@ -134,8 +134,8 @@
{{if .IsCompleted}}checked{{end}}
hx-post="{{if .IsCompleted}}/uncomplete-atom{{else}}/complete-atom{{end}}"
hx-vals='{"id": "{{.ID}}", "source": "{{.Source}}"{{if .ListID}}, "listId": "{{.ListID}}"{{end}}}'
- hx-target="#tab-content"
- hx-swap="innerHTML"
+ hx-target="closest .untimed-item"
+ hx-swap="outerHTML"
class="h-4 w-4 rounded bg-black/40 border-white/30 text-white/80 focus:ring-white/30 cursor-pointer flex-shrink-0">
{{end}}
<span class="{{if .IsCompleted}}line-through text-white/50{{end}}">{{.Title}}</span>
@@ -186,8 +186,8 @@
{{if .IsCompleted}}checked{{end}}
hx-post="{{if .IsCompleted}}/uncomplete-atom{{else}}/complete-atom{{end}}"
hx-vals='{"id": "{{.ID}}", "source": "{{.Source}}"{{if .ListID}}, "listId": "{{.ListID}}"{{end}}}'
- hx-target="#tab-content"
- hx-swap="innerHTML"
+ hx-target="closest .calendar-event"
+ hx-swap="outerHTML"
onclick="event.stopPropagation();"
class="h-4 w-4 rounded bg-black/40 border-white/30 text-white/80 focus:ring-white/30 cursor-pointer flex-shrink-0">
{{end}}
@@ -223,8 +223,8 @@
{{if .IsCompleted}}checked{{end}}
hx-post="{{if .IsCompleted}}/uncomplete-atom{{else}}/complete-atom{{end}}"
hx-vals='{"id": "{{.ID}}", "source": "{{.Source}}"{{if .ListID}}, "listId": "{{.ListID}}"{{end}}}'
- hx-target="#tab-content"
- hx-swap="innerHTML"
+ hx-target="closest .untimed-item"
+ hx-swap="outerHTML"
class="h-4 w-4 rounded bg-black/40 border-white/30 text-white/80 focus:ring-white/30 cursor-pointer flex-shrink-0">
{{end}}
<span class="{{if .IsCompleted}}line-through text-white/50{{end}}">{{.Title}}</span>
@@ -271,8 +271,8 @@
{{if .IsCompleted}}checked{{end}}
hx-post="{{if .IsCompleted}}/uncomplete-atom{{else}}/complete-atom{{end}}"
hx-vals='{"id": "{{.ID}}", "source": "{{.Source}}"{{if .ListID}}, "listId": "{{.ListID}}"{{end}}}'
- hx-target="#tab-content"
- hx-swap="innerHTML"
+ hx-target="closest .calendar-event"
+ hx-swap="outerHTML"
onclick="event.stopPropagation();"
class="h-4 w-4 rounded bg-black/40 border-white/30 text-white/80 focus:ring-white/30 cursor-pointer flex-shrink-0">
{{end}}
diff --git a/web/templates/settings.html b/web/templates/settings.html
index 50569e4..0803ae3 100644
--- a/web/templates/settings.html
+++ b/web/templates/settings.html
@@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="csrf-token" content="{{.CSRFToken}}">
<title>Settings - Task Dashboard</title>
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<style>
@@ -237,19 +238,7 @@
statusEl.textContent = 'Starting registration...';
try {
- const csrfMeta = document.querySelector('input[name="csrf_token"]') || document.querySelector('[name="csrf_token"]');
- let csrfToken = '';
- // Get CSRF token from the feature toggle form's hidden fields or cookie
- const forms = document.querySelectorAll('form');
- for (const f of forms) {
- const input = f.querySelector('input[name="csrf_token"]');
- if (input) { csrfToken = input.value; break; }
- }
- // Fallback: fetch from HTMX headers
- if (!csrfToken) {
- const resp = await fetch('/settings/passkeys');
- // Try to extract from response
- }
+ const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
const beginResp = await fetch('/passkeys/register/begin', {
method: 'POST',