go-htmx-gsd #1
1 changed files with 140 additions and 0 deletions
140
.planning/phases/05-files/05-02-SUMMARY.md
Normal file
140
.planning/phases/05-files/05-02-SUMMARY.md
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
---
|
||||
phase: 05-files
|
||||
plan: "02"
|
||||
subsystem: files-upload-tabs
|
||||
tags: [htmx, s3, multipart, tab-navigation, templ, router]
|
||||
dependency_graph:
|
||||
requires:
|
||||
- 05-01 (FileStorer interface, sqlc TabloFile model, files.sql.go queries)
|
||||
provides:
|
||||
- backend/internal/web/handlers_files.go (FilesDeps, TabloFilesTabHandler, FileUploadHandler, TabloTasksTabHandler)
|
||||
- backend/templates/files.templ (FilesTabFragment, FileUploadForm, FileListRow, FileRowGone, UploadErrorFragment)
|
||||
- backend/templates/files_helpers.go (formatBytes)
|
||||
- backend/templates/tablos.templ (TabloDetailPage 6-arg, TasksTabFragment, TabloOverviewTabFragment)
|
||||
- backend/internal/web/router.go (fileDeps parameter, file + tasks-tab routes)
|
||||
- backend/cmd/web/main.go (S3 env vars, files.NewStore, fileDeps wired)
|
||||
affects:
|
||||
- backend/internal/web/handlers_tablos.go (TabloDetailPage call sites updated)
|
||||
- backend/internal/web/handlers_auth_test.go (NewRouter signature updated)
|
||||
- backend/internal/web/handlers_tablos_test.go (NewRouter signature updated)
|
||||
- backend/internal/web/handlers_tasks_test.go (NewRouter signature updated)
|
||||
- backend/internal/web/handlers_test.go (NewRouter signature updated)
|
||||
- backend/internal/web/csrf_test.go (NewRouter signature updated)
|
||||
tech_stack:
|
||||
added: []
|
||||
patterns:
|
||||
- multipart upload via http.MaxBytesReader + ParseMultipartForm (Pitfall 2 guard)
|
||||
- S3 key isolation: "files/{tablo_id}/{uuid}" — filename never reaches S3 key (D-04)
|
||||
- nil FileStorer guard as FIRST statement in TabloFilesTabHandler + FileUploadHandler (T-05-02-06)
|
||||
- hx-push-url on all tab links for browser URL sync (D-08)
|
||||
- HTMX fragment vs full-page dispatch on HX-Request header
|
||||
key_files:
|
||||
created:
|
||||
- backend/internal/web/handlers_files.go
|
||||
- backend/templates/files.templ
|
||||
- backend/templates/files_helpers.go
|
||||
modified:
|
||||
- backend/internal/web/handlers_files_test.go (activated from stub scaffold)
|
||||
- backend/templates/tablos.templ (TabloDetailPage restructured, fragment components added)
|
||||
- backend/internal/web/router.go (fileDeps param, new routes)
|
||||
- backend/internal/web/handlers_tablos.go (call sites updated)
|
||||
- backend/cmd/web/main.go (S3 env + fileDeps wiring)
|
||||
- backend/internal/web/handlers_auth_test.go
|
||||
- backend/internal/web/handlers_tablos_test.go
|
||||
- backend/internal/web/handlers_tasks_test.go
|
||||
- backend/internal/web/handlers_test.go
|
||||
- backend/internal/web/csrf_test.go
|
||||
decisions:
|
||||
- "FileStorer type alias in handlers_files.go (= files.FileStorer) — avoids import of files package in test files while keeping interface stable"
|
||||
- "itoa helper inlined in handlers_files.go — avoids strconv import in web package for single use"
|
||||
- "formatBytes in templates/files_helpers.go — non-templ Go file in templates package; formatBytes called directly from templ files"
|
||||
- "TabloTasksTabHandler takes FilesDeps (not TasksDeps) — tab wiring is a Phase 5 concern; FilesDeps has same Queries pointer; avoids dual-deps threading"
|
||||
- "nil filesStore allowed in main.go when S3_ENDPOINT unset — dev/test mode without MinIO; handlers return 503 via nil guard"
|
||||
- "UploadErrorFragment re-renders full FilesTabFragment with error in FileUploadForm — simpler than OOB error injection; form + list always consistent"
|
||||
metrics:
|
||||
duration: ~25min
|
||||
completed: "2026-05-15"
|
||||
tasks: 2
|
||||
files: 14
|
||||
---
|
||||
|
||||
# Phase 05 Plan 02: File Upload + Tab Navigation Summary
|
||||
|
||||
Vertical slice 1 for the files feature. A user can now visit `/tablos/{id}/files`, see an upload form and any existing file list, upload a file that gets stored in S3 with a DB row, and navigate the Overview/Tasks/Files tab bar with URL-sync via `hx-push-url`. The tab restructuring covers D-07/D-08 and unlocks the files workflow.
|
||||
|
||||
## Tasks Completed
|
||||
|
||||
| # | Name | Commit | Files |
|
||||
|---|------|--------|-------|
|
||||
| RED | File handler test scaffold | cc0d6cf | handlers_files_test.go |
|
||||
| 1 | FilesDeps + FileUploadHandler + TabloFilesTabHandler + TabloTasksTabHandler | f50836f | handlers_files.go |
|
||||
| 2 | 3-tab layout + files templates + router + main.go S3 wiring | a12c5ab | tablos.templ, files.templ, files_helpers.go, router.go, handlers_tablos.go, main.go, 5 test files |
|
||||
|
||||
## Verification Results
|
||||
|
||||
- `go build ./...` exits 0
|
||||
- `go test ./...` exits 0 — all packages pass; TestFile* SKIP (integration tests without DB — expected)
|
||||
- `backend/internal/web/handlers_files.go` contains `type FilesDeps struct`, `func TabloFilesTabHandler(`, `func FileUploadHandler(`, `func TabloTasksTabHandler(`
|
||||
- `FileUploadHandler` first statement is `deps.Files == nil` nil guard returning 503
|
||||
- `TabloFilesTabHandler` first statement is `deps.Files == nil` nil guard returning 503
|
||||
- `FileUploadHandler` contains `http.MaxBytesReader` before `ParseMultipartForm`
|
||||
- `FileUploadHandler` contains `"files/"` string for S3 key (D-04)
|
||||
- `FileUploadHandler` contains `http.MaxBytesError` for size violation detection
|
||||
- `backend/templates/tablos.templ` contains `activeTab string` in TabloDetailPage, `hx-push-url`, `tab-content`, `TasksTabFragment`
|
||||
- `backend/templates/files.templ` contains `FilesTabFragment`, `FileUploadForm`, `hx-encoding`, `FileRowGone`
|
||||
- `backend/internal/web/router.go` contains `fileDeps FilesDeps` in NewRouter, `TabloFilesTabHandler`, `TabloTasksTabHandler`
|
||||
- `backend/cmd/web/main.go` contains `files.NewStore` and `fileDeps := web.FilesDeps`
|
||||
- Both `TabloDetailPage` call sites in `handlers_tablos.go` end with `nil, "overview"`
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
None — plan executed exactly as written with one minor implementation adaptation:
|
||||
|
||||
**1. [Rule 2 - Missing critical functionality] itoa helper in handlers_files.go**
|
||||
- **Found during:** Task 1 implementation
|
||||
- **Issue:** formatMBError needed integer-to-string conversion; importing `strconv` only for this single use would add a dependency unnecessarily
|
||||
- **Fix:** Added private `itoa(n int) string` helper in handlers_files.go; eliminates strconv import
|
||||
- **Files modified:** backend/internal/web/handlers_files.go
|
||||
- **Commit:** f50836f
|
||||
|
||||
**2. [Rule 3 - Blocking issue] All test routers needed FilesDeps parameter**
|
||||
- **Found during:** Task 2 — after NewRouter signature change, 5 test files failed to build
|
||||
- **Fix:** Updated all 5 test routers (handlers_auth_test.go, handlers_tablos_test.go, handlers_tasks_test.go, handlers_test.go, csrf_test.go) to pass `FilesDeps{Queries: q}` or `FilesDeps{}` in the new position
|
||||
- **Files modified:** 5 test files
|
||||
- **Commit:** a12c5ab
|
||||
|
||||
## Known Stubs
|
||||
|
||||
| File | Description | Resolved by |
|
||||
|------|-------------|-------------|
|
||||
| backend/internal/web/handlers_files.go | FileDownloadHandler returns 501 | Plan 03 |
|
||||
| backend/internal/web/handlers_files.go | FileDeleteConfirmHandler returns 501 | Plan 03 |
|
||||
| backend/internal/web/handlers_files.go | FileDeleteHandler returns 501 | Plan 03 |
|
||||
| backend/internal/web/handlers_files_test.go | TestFileDownload t.Skip — Plan 03 | Plan 03 |
|
||||
| backend/internal/web/handlers_files_test.go | TestFileDelete t.Skip — Plan 03 | Plan 03 |
|
||||
| backend/internal/web/handlers_files_test.go | TestFileOwnership t.Skip — Plan 03 | Plan 03 |
|
||||
|
||||
These stubs are intentional. The stub handlers return 501 and are referenced in FileListRow download/delete links — they will be replaced in Plan 03 (file download + delete).
|
||||
|
||||
## Threat Surface Scan
|
||||
|
||||
All new surface is covered by the plan's threat model:
|
||||
- `POST /tablos/{id}/files` (T-05-02-01..06) — covered
|
||||
- `GET /tablos/{id}/files` (T-05-02-04, T-05-02-06) — covered
|
||||
- Stub routes for download/delete return 501 with no data exposure
|
||||
|
||||
No new unplanned trust boundaries introduced.
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
Files verified:
|
||||
- FOUND: backend/internal/web/handlers_files.go
|
||||
- FOUND: backend/templates/files.templ
|
||||
- FOUND: backend/templates/files_helpers.go
|
||||
|
||||
Commits verified:
|
||||
- FOUND: cc0d6cf (test(05-02): add RED test scaffold...)
|
||||
- FOUND: f50836f (feat(05-02): implement FilesDeps...)
|
||||
- FOUND: a12c5ab (feat(05-02): 3-tab layout...)
|
||||
Loading…
Reference in a new issue