Commit graph

1050 commits

Author SHA1 Message Date
Arthur Belleville
d8a130cd01
docs(state): record phase 6 context session 2026-05-15 15:13:11 +02:00
Arthur Belleville
9d1e24fc7e
docs(06): capture phase context 2026-05-15 15:13:06 +02:00
Arthur Belleville
8215f53356
docs(05-files): mark validation nyquist_compliant true, fill gap map
- Set nyquist_compliant: true and wave_0_complete: true in frontmatter
- Update Per-Task Verification Map: FILE-01..06 integration tests marked ⚠️
  (clean skip without TEST_DATABASE_URL); new unit tests marked  green
- Check all Wave 0 requirements as complete
- Fill in Validation Sign-Off checklist

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 13:29:13 +02:00
Arthur Belleville
cb7d5d1dd1
test(05-files): add pure unit tests for formatBytes, byteCountReader, and content-type sniff
Gap fill: three no-infrastructure unit tests that run without TEST_DATABASE_URL or S3_ENDPOINT:
- backend/templates/files_helpers_test.go — formatBytes boundary cases (B/KB/MB/GB)
- backend/internal/files/store_unit_test.go — byteCountReader accumulation, io.ErrUnexpectedEOF
  guard for small files, and MultiReader body reconstruction after 512-byte sniff

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 13:29:08 +02:00
Arthur Belleville
7fed6d1049
docs(phase-5): add security threat verification 2026-05-15 12:54:45 +02:00
Arthur Belleville
3a7726e6ed
docs(05): add code review fix report 2026-05-15 12:51:29 +02:00
Arthur Belleville
49e84c8176
fix(05-WR-01): raise ReadTimeout/WriteTimeout to 120s for large uploads
15s was too short for 25MB uploads on slow connections (~256KB/s takes
~100s). Both timeouts are raised to 120s to accommodate MAX_UPLOAD_SIZE_MB
at worst-case bandwidth with headroom.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 12:50:25 +02:00
Arthur Belleville
690ea2ddaf
fix(05): CR-01/WR-02/WR-03/WR-04 handlers_files.go fixes
- CR-01: add S3 cleanup before 500 when InsertTabloFile fails
- WR-02: validate empty filename, return 400 before S3 upload
- WR-03: remove dead errMsg variable (was silenced with _ = errMsg)
- WR-04: delete itoa/formatMBError helpers, inline strconv.Itoa

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 12:50:07 +02:00
Arthur Belleville
ab30db4a2f
docs(phase-5): complete phase execution 2026-05-15 12:46:25 +02:00
Arthur Belleville
1876ad0956
test(05): persist human verification items as UAT 2026-05-15 12:46:16 +02:00
Arthur Belleville
7fb3156638
test(05): add phase verification report 2026-05-15 12:46:02 +02:00
Arthur Belleville
f93ef972ad
docs(05): add code review report 2026-05-15 12:42:20 +02:00
Arthur Belleville
aab336fa36
docs(phase-05): update tracking after wave 4 2026-05-15 12:38:20 +02:00
Arthur Belleville
facd2846f2
docs(05-04): complete Phase 5 verification checkpoint
- Pre-flight automation passed: go test ./... exits 0, go build exits 0
- Human-verify checkpoint auto-approved (AUTO_MODE=true)
- All FILE-01..06 requirements confirmed implemented in Plans 01-03
2026-05-15 12:37:48 +02:00
Arthur Belleville
fe4f166ce3
docs(phase-05): update tracking after wave 3 2026-05-15 12:36:06 +02:00
Arthur Belleville
e30d3c3d7f
docs(05-03): complete file download + delete plan SUMMARY
- All six FILE requirements (FILE-01..06) implemented across Plans 01-03
- TDD gates documented (RED 98a5a02, GREEN 9d4dd4f)
- Self-check passed
2026-05-15 12:35:13 +02:00
Arthur Belleville
9d4dd4f3e2
feat(05-03): implement FileDownloadHandler, FileDeleteConfirmHandler, FileDeleteHandler
- FileDownloadHandler: nil guard → loadOwnedTabloForFile → PresignDownload → 302 redirect (FILE-04)
- FileDeleteConfirmHandler: nil guard → loadOwnedTabloForFile → render FileDeleteConfirmFragment
- FileDeleteHandler: nil guard → loadOwnedTabloForFile → S3 Delete (log+continue) → DeleteTabloFile → FileRowGone HTMX / 303 redirect (FILE-05, FILE-06)
- Add FileDeleteConfirmFragment templ component mirroring TaskDeleteConfirmFragment pattern (T-05-03-05)
2026-05-15 12:34:07 +02:00
Arthur Belleville
98a5a02b93
test(05-03): add RED test scaffold for file download + delete handlers
- Expand stubbedFileStorer with deletedKey tracking and deleteErr injection field
- Implement TestFileDownload (FILE-04): 302 redirect to presigned URL
- Implement TestFileDownload_NonOwner: non-owner gets 404
- Implement TestFileDelete (FILE-05): HTMX delete, S3+DB both deleted
- Implement TestFileDelete_S3Failure: S3 error does not abort DB delete, 200 returned
- Implement TestFileOwnership (FILE-06): non-owner gets 404 on all three routes
2026-05-15 12:32:49 +02:00
Arthur Belleville
072eda1028
docs(phase-05): update tracking after wave 2 2026-05-15 12:30:36 +02:00
Arthur Belleville
99719e6e96
docs(05-02): complete file upload + tab navigation plan SUMMARY
- Tasks: 2 + RED scaffold committed
- Commits: cc0d6cf (RED), f50836f (GREEN handlers), a12c5ab (templates + router + wiring)
- All acceptance criteria met; go build + go test ./... pass
- Known stubs: FileDownloadHandler/DeleteHandler/DeleteConfirmHandler (Plan 03)
2026-05-15 12:29:42 +02:00
Arthur Belleville
a12c5abea6
feat(05-02): 3-tab layout + files templates + router + main.go S3 wiring
- tablos.templ: TabloDetailPage gains files+activeTab params, 3-tab nav with hx-push-url
- tablos.templ: TabloOverviewTabFragment + TasksTabFragment (wraps KanbanBoard) added
- files.templ: FilesTabFragment, FileUploadForm (hx-encoding=multipart/form-data),
  FileListRow, FileListEmpty, FileRowGone, UploadErrorFragment
- files_helpers.go: formatBytes() converts int64 bytes to human-readable string
- router.go: fileDeps FilesDeps param added; TabloTasksTabHandler + file routes wired
- handlers_tablos.go: both TabloDetailPage call sites updated (nil, 'overview')
- main.go: S3_ENDPOINT/S3_BUCKET/S3_REGION env vars read; files.NewStore constructed;
  fileDeps wired; nil filesStore allowed when S3 env unset (503 from handlers)
- All test routers updated to pass FilesDeps{} in new param position
2026-05-15 12:28:33 +02:00
Arthur Belleville
f50836fa31
feat(05-02): implement FilesDeps + FileUploadHandler + TabloFilesTabHandler + TabloTasksTabHandler
- FilesDeps struct with Queries, Files FileStorer, MaxUploadMB
- loadOwnedTabloForFile helper (mirrors loadOwnedTabloForTask)
- TabloFilesTabHandler: nil guard first, loadOwnedTablo, list files, HTMX/full-page dispatch
- TabloTasksTabHandler: loadOwnedTablo, list tasks, HTMX/full-page dispatch
- FileUploadHandler: nil guard, MaxBytesReader before ParseMultipartForm, S3 key files/{uuid}, InsertTabloFile, list + redirect
- FileDownloadHandler/FileDeleteConfirmHandler/FileDeleteHandler: 501 stubs for Plan 03
- Security: D-04 S3 key isolation, T-05-02-02 size guard, T-05-02-04 ownership
2026-05-15 12:28:07 +02:00
Arthur Belleville
cc0d6cfd4e
test(05-02): add RED test scaffold for file upload and tab handlers
- TestFileUpload: POST /tablos/{id}/files → 303 redirect + DB row + S3 key check
- TestFileUploadTooLarge: oversized file → 422 + 'too large' message
- TestFilesList: GET /tablos/{id}/files lists pre-inserted file with filename + size
- TestFilesTab: HTMX fragment vs full-page rendering
- stubbedFileStorer records uploadedKey for assertion
- TestFileDownload/Delete/Ownership remain t.Skip (Plan 03)
2026-05-15 12:24:40 +02:00
Arthur Belleville
74af9d7052
docs(phase-05): update tracking after wave 1 2026-05-15 12:21:26 +02:00
Arthur Belleville
848dca9263
docs(05-01): complete files foundation plan SUMMARY
- Wave 0: aws-sdk-go-v2 modules, 0005_files migration, sqlc queries, files.Store, RED test scaffold, MinIO in compose
- go build ./... passes; all 6 TestFile* stubs SKIP; TestStoreImplementsFileStorer PASS
2026-05-15 12:20:05 +02:00
Arthur Belleville
3327a4286d
test(05-01): add RED test scaffold for FILE-01..06 and MinIO in compose.yaml
- Create handlers_files_test.go: six TestFile* stubs (all t.Skip), stubbedFileStorer no-op implementing files.FileStorer
- Create store_test.go: compile-time interface assertion, TestNewStore_SkipIfNoEndpoint skips when S3_ENDPOINT unset
- Update compose.yaml: add minio (port 9000/9001) and minio-init services; minio-init uses restart: no (Pitfall 7); add minio_data volume
2026-05-15 12:19:23 +02:00
Arthur Belleville
e0d72747e0
feat(05-01): add aws-sdk-go-v2 modules, 0005_files migration, sqlc queries, and files.Store
- Add four aws-sdk-go-v2 modules: core, config, credentials, service/s3
- Write 0005_files.sql migration (tablo_files table with ON DELETE CASCADE)
- Write internal/db/queries/files.sql with InsertTabloFile, ListFilesByTablo, GetTabloFileByID, DeleteTabloFile
- Implement internal/files/store.go: FileStorer interface, Store struct, NewStore (UsePathStyle for MinIO), Upload (sniff+stream+bytecount), Delete, PresignDownload
- sqlc generate produces files.sql.go + TabloFile model (gitignored, regeneratable)
2026-05-15 12:18:16 +02:00
Arthur Belleville
5ce8b70f69
docs(05): create phase plan
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 12:14:48 +02:00
Arthur Belleville
f115082bd5
docs(05): create phase 5 file upload plans (4 plans, 4 waves)
Adds 4 PLAN.md files for Phase 5 — Files. Wave 1 lays the S3
foundation (aws-sdk-go-v2, migration, FileStorer, MinIO compose).
Wave 2 delivers the upload + list vertical slice with 3-tab tablo
layout. Wave 3 closes download + delete. Wave 4 is the browser
verify checkpoint.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 11:58:52 +02:00
Arthur Belleville
261c85ae73
docs(05): research phase files domain 2026-05-15 11:46:09 +02:00
Arthur Belleville
740d367dc4
docs(state): record phase 5 context session 2026-05-15 10:58:24 +02:00
Arthur Belleville
1c02de475e
docs(05): capture phase context 2026-05-15 10:58:19 +02:00
Arthur Belleville
9e6ab6d5aa
docs(04): mark review findings fixed
All 6 in-scope findings (CR-01, CR-02, WR-01, WR-02, WR-03, WR-04)
have been applied and verified. Updated status from issues_found to fixed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 10:19:54 +02:00
Arthur Belleville
61e6e778e0
fix(04-WR-04): guard against int32 overflow in TaskCreateHandler position arithmetic
maxPos + 100 could silently overflow to a negative value when maxPos
approached MaxInt32. Added a maxAllowedPosition guard that returns a
validation error before the InsertTask call if the column position space
is exhausted.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 10:19:48 +02:00
Arthur Belleville
f6ab318f4e
fix(04-WR-03): trim whitespace from description in TaskUpdateHandler
A description of spaces-only was being stored as a valid non-null DB value
because the empty-string check ran before trimming. Now consistent with how
other nullable text fields are handled.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 10:19:30 +02:00
Arthur Belleville
3dafba72cc
fix(04-WR-02): set HX-Retarget/HX-Reswap on 422 path in TaskCreateHandler
Without these headers, HTMX used the form's own hx-target="#column-{status}"
+ hx-swap="beforeend", appending the error form into the task column and
destroying all visible task cards. The error form now lands back in the
add-task slot where it belongs.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 10:19:10 +02:00
Arthur Belleville
3d32f2d92f
fix(04-WR-01): check UpdateTask errors in TaskReorderHandler instead of discarding
Both the single-task branch and the main loop were using _, _ = to
discard UpdateTask errors. Now both log the error and return 500 so
the client is never shown a false success when DB writes fail.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 10:18:53 +02:00
Arthur Belleville
392b5321be
fix(04-CR-02): replace fmt.Fprintf in TaskDeleteHandler with TaskCardGone templ component
The raw fmt.Fprintf bypassed templ's auto-escaping pipeline and was
inconsistent with every other handler. Added TaskCardGone(taskID uuid.UUID)
to tasks.templ and updated TaskDeleteHandler to use it. Ran just generate.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 10:18:34 +02:00
Arthur Belleville
e97f4988bd
fix(04-CR-01): add r.ParseForm() to TaskCreateHandler and TaskUpdateHandler
Both handlers were missing the mandatory ParseForm call before reading
PostFormValue. This caused gorilla/csrf (which reads the body for CSRF
token validation) to consume the body, leaving PostFormValue to return
empty strings. TaskReorderHandler was used as the correct reference.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 10:17:49 +02:00
Arthur Belleville
142ca547ea
docs(04): add code review report 2026-05-15 10:10:41 +02:00
Arthur Belleville
879f7b1161
docs(phase-04): complete phase execution — tasks kanban
7/7 TASK requirements verified. Human browser verification done in
plan 04-04 checkpoint (badge count + DnD bugs fixed before approval).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 10:08:02 +02:00
Arthur Belleville
06bbb49a7f
docs(04-04): human checkpoint approved — phase 4 complete
All 7 TASK requirements verified in browser. Two bugs found and fixed
during verification (badge count, drag-and-drop draggable selector).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 10:04:21 +02:00
Arthur Belleville
131c9fd6b3
fix(04): draggable:.task-card-zone — move wrapper not inner card
Sortable.js draggable must match direct children of .sortable-column.
Using .task-card (grandchild) caused Sortable to detach it from its
.task-card-zone wrapper, breaking HTMX OOB swap targets and making
drag appear to do nothing. Changed to .task-card-zone so the full
wrapper moves, keeping id= attributes intact for HTMX round-trips.

Also removed redundant form.dispatchEvent() before htmx.trigger()
which could cause a double submit on reorder.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 10:01:34 +02:00
Arthur Belleville
68f2ccdea3
fix(04): badge count + DnD init — use DOMContentLoaded/htmx:afterSettle
Replace htmx.onLoad (requires htmx at parse time) with native
document.addEventListener('DOMContentLoaded') + 'htmx:afterSettle'
so Sortable.js is guaranteed loaded before init runs.

Add task-count-badge-{status} wrapper IDs and updateBadges() that
recounts .task-card elements on every HTMX settle so badge counts
stay in sync after create, delete, and reorder operations.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 09:56:28 +02:00
Arthur Belleville
8ac0a51969
docs(04-03): complete plan 03 — inline edit + reorder fully implemented
- 04-03-SUMMARY.md created with full execution details
- STATE.md updated: decisions, metrics, notes for plan 03
- ROADMAP.md updated with plan progress (3/4 summaries for phase 4)
2026-05-15 09:40:35 +02:00
Arthur Belleville
f6deb8709b
feat(04-03): remove t.Skip from TestTaskOrderPersists — all 9 TestTask* tests active
- Remove t.Skip("handlers_tasks not yet implemented") from TestTaskOrderPersists
- Full test suite green: go test ./... exits 0, no FAIL lines
- All 9 TestTask* tests active (skip on missing TEST_DATABASE_URL per existing pattern)
2026-05-15 09:38:53 +02:00
Arthur Belleville
5f87d7e0ea
feat(04-03): implement TaskReorderHandler + remove t.Skip from reorder tests
- TaskReorderHandler: POST /tablos/{id}/tasks/reorder updates status+position
- Fetches existing task via GetTaskByID before UpdateTask (T-04-08 mass assignment guard)
- Supports both array form (task_id[]/task_col[]) and single-value form (task_id/status/position)
- Invalid UUIDs silently skipped (D-05); tasks from other tablos skipped (T-04-10)
- Returns updated KanbanBoard outerHTML for HTMX swap
- Remove t.Skip from TestTaskReorderCrossColumn and TestTaskReorderSameColumn
2026-05-15 09:38:28 +02:00
Arthur Belleville
2b299e21f4
feat(04-03): implement TaskEditHandler, TaskUpdateHandler, TaskEditFragment
- TaskEditHandler: GET /tablos/{id}/tasks/{task_id}/edit returns TaskEditFragment pre-filled with existing title+description
- TaskUpdateHandler: POST validates title (required, max 255), updates title+description preserving status+position (T-04-12)
- TaskEditFragment: outer .task-card-zone wrapper with outerHTML round-trip, discard restores via /show
- Sortable.js htmx.onLoad init script added to KanbanBoard (Pitfall 2 protection)
- TaskEditFragment added to tasks.templ; remove t.Skip from TestTaskUpdate
2026-05-15 09:37:46 +02:00
Arthur Belleville
5158eb09af
docs(04-02): complete kanban vertical slice plan — handlers, templates, test activation
- Add 04-02-SUMMARY.md with full decisions and deviation documentation
- Update STATE.md: Plans 01-02 done, add decisions, metrics, notes
- Update ROADMAP.md phase 4 progress (2/4 plans complete)
2026-05-15 09:35:03 +02:00
Arthur Belleville
92ebb5f5fe
feat(04-02): activate task integration tests (RED stubs to GREEN-ready)
- Remove t.Skip from TestTasksKanbanRenders, TestTaskCreate, TestTaskCreateValidation, TestTaskDelete, TestTaskOwnership
- Fix column header strings: 'To do'/'In progress'/'In review' to match TaskColumnLabels
- Add kanban-board id assertion and non-owner 404 check to TestTasksKanbanRenders
- TestTaskUpdate, TestTaskReorder*, TestTaskOrderPersists remain SKIP for Plan 03
2026-05-15 09:33:19 +02:00