Commit graph

219 commits

Author SHA1 Message Date
Arthur Belleville
9c7b080f67
feat(15-03): restyle tablos.templ with AppLayout, TabloProjectCard, and EmptyState
- Updated TablosDashboard signature to accept activePath and tablos for AppLayout
- Replaced old @Layout call with @AppLayout (sidebar-based shell)
- Added TabloProjectCard component with project-card grid, colored avatar, tablo-title-zone, edit/delete icon buttons
- Replaced TablosEmptyState raw HTML with @ui.EmptyState component (ui-empty-state class)
- Updated TabloDetailPage signature with activePath and sidebarTablos params
- Updated TabloNotFoundPage signature with activePath and sidebarTablos params
- Both detail pages switch from @Layout to @AppLayout
2026-05-16 21:49:10 +02:00
Arthur Belleville
9b0d335329
feat(15-02): create app_layout.templ and app_layout_helpers.go with sidebar sub-components
- Create backend/templates/app_layout_helpers.go: sidebarNavItem struct, sidebarNavItemClass, isActivePath, sidebarNavItemID, sidebarPrimaryNavItems
- Create backend/templates/app_layout.templ: SidebarNavIcon, SidebarNavItemRow, SidebarProjectsSection, SidebarOrganizationFooter, DashboardSidebar, AppLayout
- templ generate and go build exit 0; all existing tests pass
2026-05-16 21:43:15 +02:00
Arthur Belleville
f533d53c74
feat(15-02): port sidebar + project-card CSS into app.css and register in tailwind
- Create backend/internal/web/ui/app.css with dashboard shell, sidebar, and project-card CSS ported verbatim from go-backend
- All color values use var(--...) design tokens — no hex colors
- Add @import "./internal/web/ui/app.css" to backend/tailwind.input.css
2026-05-16 21:41:58 +02:00
Arthur Belleville
7bea525c1b
test(15-01): add Wave 0 RED test stubs for DASH-01, DASH-02, DASH-03
- TestTablosDashboard_Sidebar: asserts dashboard-sidebar + sidebar-nav-shell in GET / body
- TestTablosDashboard_ProjectCards: asserts project-card in GET / body with a pre-inserted tablo
- TestTablosDashboard_EmptyState: asserts ui-empty-state in GET / body with zero tablos
- All three skip without TEST_DATABASE_URL; compile cleanly; existing TestTablos* tests unaffected
2026-05-16 21:38:50 +02:00
Arthur Belleville
dcbc05b642
test(14): add Nyquist validation tests for AUTH-UI-01/02/03
auth_login_test.go: LoginPage renders AuthLayout structure (login-screen,
auth-card-shell, brand-logo, h1, auth-body, divider-pill), HTMX form
attributes, password not echoed.

auth_components_test.go: AnimatedBackground exactly 35 elements,
GoogleButton configured/unconfigured variants, AuthDivider or-pill.

handlers_auth_test.go: extend configured provider tests to assert
class="gsi-material-button" on the anchor element (AUTH-UI-03).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-16 20:43:31 +02:00
Arthur Belleville
6e64cfb3ed
chore(dev): watch CSS files in air and include Tailwind in rebuild cmd
Add css to air's include_ext so edits to internal/web/ui/*.css trigger a
rebuild. Add Tailwind build step to the air cmd so static/tailwind.css is
always up to date on reload. static/ remains excluded so the generated
output file doesn't cause a loop.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-16 19:25:55 +02:00
Arthur Belleville
70fe3848fb
fix(14-02): shrink Google button to content width
width: auto on a block element still fills the parent. Use width: max-content
so the button wraps its icon + label + padding only. Remove flex-grow: 1 on
the label span so it doesn't stretch within the now-natural-width button.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-16 19:24:10 +02:00
Arthur Belleville
6a58b2970a
fix(14-02): shrink Google button to natural width per Google spec
Google's gsi-material-button uses width: auto, not 100%. Replace width: 100%
/ max-width: 400px with width: auto + margin: 0 auto so the button sizes to
its content (icon + label + padding) and stays centered in the card.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-16 19:21:41 +02:00
Arthur Belleville
4624fb305a
fix(14-02): load Roboto font and fix Google button SVG icon sizing
Per Google's branding guidelines, the gsi-material-button requires Roboto
Medium. Add Google Fonts preconnect + stylesheet link to AuthLayout head.
Also add display:block + 100% dimensions to the SVG inside the icon
container to prevent inline baseline gaps.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-16 19:18:58 +02:00
Arthur Belleville
65e3dbfd03
feat(14-02): migrate auth_signup.templ to AuthLayout with ui.FormField inputs
- Replace Layout+Card pattern with AuthLayout("Create your account", csrfToken)
- Wire GoogleButton and AuthDivider into SignupPage body
- Replace raw <input> elements with @ui.FormField/@ui.Input design system components
- Password placeholder "12 characters minimum", autocomplete="new-password"
- Add signup-copy nav link ("Already have an account? Sign in") pointing to /login
- Preserve HTMX swap: hx-post="/signup" hx-target="#signup-form" hx-swap="outerHTML"
- Remove signupCardBody helper
2026-05-16 19:10:12 +02:00
Arthur Belleville
808eaecc85
feat(14-02): migrate auth_login.templ to AuthLayout with ui.FormField inputs
- Replace Layout+Card pattern with AuthLayout("Sign in to Xtablo", csrfToken)
- Wire GoogleButton and AuthDivider into LoginPage body
- Replace raw <input> elements with @ui.FormField/@ui.Input design system components
- Add signup-copy nav link ("Don't have an account? Sign up")
- Preserve HTMX swap: hx-post="/login" hx-target="#login-form" hx-swap="outerHTML"
- Remove loginCardBody, AuthProviderButtonsBlock, AuthProviderButtonControl helpers
- Update test assertions for new GoogleButton labels ("Sign in with Google" / disabled attr)
2026-05-16 19:10:08 +02:00
Arthur Belleville
e4d5f96571
feat(14-01): create auth_components.templ and auth_layout.templ
- auth_components.templ: AnimatedBackground (35 elements, /static/logo_dark.png, no light/dark pairs), GoogleButton (a/button variant, English label 'Sign in with Google'), AuthDivider ('or' divider)
- auth_layout.templ: standalone HTML shell with .login-screen, @AnimatedBackground(), .card-wrap/.card-glow/.auth-card-shell, htmx.min.js only (no sortable/sse scripts)
- No auth.User param on AuthLayout (auth pages always unauthenticated)
- just generate exits 0, all Go tests pass
2026-05-16 19:06:07 +02:00
Arthur Belleville
cf116ff696
feat(14-01): copy logo assets and replace auth.css with full auth card + animation CSS
- Copy logo_dark.png and logo_white.png from go-backend/static into backend/static
- Replace minimal 62-line auth.css with 828-line extraction from go-backend/static/styles.css
- Sections: login-screen shell, animated background (bg-01..35, size/opacity utilities), card wrapper, card internals, gsi-material-button, legacy auth-provider controls, 66 keyframe definitions, animate-* utility classes
- Added display:block and text-decoration:none to .gsi-material-button for the anchor variant
- Does NOT include .app-shell or .dashboard-shell rules
2026-05-16 19:04:44 +02:00
Arthur Belleville
9d17acbe2b
fix(13-05): catalog air config watches css and rebuilds tailwind on change 2026-05-16 18:05:15 +02:00
Arthur Belleville
caba6c15ee
fix(13-05): icon-button hover changes icon color only, no background fill 2026-05-16 18:02:37 +02:00
Arthur Belleville
7ecc063fce
fix(13-05): run catalog via air for live-reload (-tags catalog) 2026-05-16 18:00:40 +02:00
Arthur Belleville
d2c5a0cc07
fix(13-05): add tone-aware hover colors to icon-button (danger/warning/success/neutral) 2026-05-16 17:59:17 +02:00
Arthur Belleville
cae5e7c785
fix(13-05): wire /static/tailwind.css into catalog page head 2026-05-16 14:17:46 +02:00
Arthur Belleville
4046783fd4
feat(13-05): create catalog package with route files and router wiring
- Add backend/internal/web/ui/catalog/catalog.templ: single-page layout
  with 240px sidebar nav (11 anchor links) and 11 component sections with
  section headings matching DS-XX requirement IDs
- Add backend/internal/web/ui/catalog/examples.go: Example struct + typed
  example functions for all 11 component types (badge/button/card/empty-state/
  form-field/icon-button/input/modal/select/table/textarea); modal renders
  panel-only (no backdrop wrapper, Pitfall 7)
- Add backend/internal/web/catalog_route_catalog.go (//go:build catalog):
  RegisterCatalogRoute mounts GET /ui-catalog via catalogPageHandler()
- Add backend/internal/web/catalog_route_stub.go (//go:build !catalog):
  no-op RegisterCatalogRoute for production builds
- Wire RegisterCatalogRoute(r) unconditionally in NewRouter after protected routes
- Add justfile catalog target: just generate + go run -tags catalog ./cmd/web
- go build ./... and go build -tags catalog ./... both pass; go test ./... green
2026-05-16 14:13:30 +02:00
Arthur Belleville
c80ebcb9b1
feat(13-04): port icon-button/space components; wire UIIcon into button.templ; update tailwind.input.css (GREEN)
- icon-button.css + icon_button.templ: UIIcon switch (plus/grid3x3/list/filter/search/calendar/pencil/trash + fallback span)
- spacing.css + space.templ: SpaceX/SpaceY with SpacingStep classes
- button.templ: wire @UIIcon(props.Icon) replacing Plan 02 placeholder comment
- tailwind.input.css: add modal, empty-state, table, icon-button, form-field, spacing imports (14 total)
- All 7 new tests pass; full go test ./... green
2026-05-16 14:07:07 +02:00
Arthur Belleville
fa2405938a
test(13-04): add failing tests for IconButton, UIIcon, Space, and Button icon wiring (RED) 2026-05-16 14:05:44 +02:00
Arthur Belleville
fbdf188f5f
feat(13-04): port modal, empty-state, table components with CSS and templ (GREEN)
- modal.css + modal.templ: backdrop/panel structure, Title/Body/Actions props
- empty-state.css + empty_state.templ: dashed border, nil-guarded Icon/Action
- table.css + table.templ: ui-table-shell wrapper, Head/Body typed props
- All 6 TestModal/TestEmptyState/TestTable tests pass; full suite green
2026-05-16 14:05:11 +02:00
Arthur Belleville
4bdb78debf
test(13-04): add failing tests for Modal, EmptyState, Table components (RED) 2026-05-16 14:04:19 +02:00
Arthur Belleville
52fb77d4f8
feat(13-03): port select + form-field components with CSS and helpers (GREEN)
- select_helpers.go: 9 helper functions verbatim from go-backend
- select.templ: SelectProps/SelectOption structs, inline JS with __uiSelectInitAll
  and htmx:afterSwap re-init listener (Pitfall 6)
- select.css: .ui-select-control (min-height 44px), .ui-select-menu (max-height 16rem)
- form_field.templ: FormFieldProps with Label/For/Field/Error/Hint; conditional regions
- form-field.css: .ui-form-field/.ui-form-label/.ui-form-hint/.ui-form-error
- tailwind.input.css: add @import for select.css and form-field.css
- All 6 TestSelect/TestFormField tests passing; full go test ./... is green
2026-05-16 14:00:51 +02:00
Arthur Belleville
50e3fb0021
test(13-03): add failing tests for Select and FormField components (RED)
- TestSelect_RendersControl: expects ui-select-control in output
- TestSelect_HasInlineScript: expects __uiSelectInitAll in script block
- TestSelect_HasHtmxListener: expects htmx:afterSwap re-init listener
- TestFormField_RendersLabel: expects ui-form-field and ui-form-label
- TestFormField_RendersError: expects ui-form-error when Error is set
- TestFormField_NoErrorWhenEmpty: expects no ui-form-error when Error is empty
2026-05-16 13:59:21 +02:00
Arthur Belleville
9556b20ade
feat(13-03): port input.templ/input.css and textarea.templ/textarea.css (GREEN)
- input.css: .ui-input with min-height 44px, border-radius 0.75rem, focus ring
- input.templ: InputProps with ID/Name/Type/Placeholder/Value/Disabled/Required/Attrs
- textarea.css: .ui-textarea with min-height 7rem, resize vertical, focus ring
- textarea.templ: TextareaProps with ID/Name/Value/Placeholder/Rows/Disabled/Required/Attrs
- tailwind.input.css: add @import for input.css and textarea.css
- All 7 TestInput/TestTextarea tests passing
2026-05-16 13:59:03 +02:00
Arthur Belleville
ace9f5bdc4
test(13-03): add failing tests for Input and Textarea components (RED)
- TestInput_DefaultType: expects type="text" for empty Type
- TestInput_EmailType: expects type="email" for explicit Type
- TestInput_IDFallback: expects id from Name when no ID set
- TestInput_ExplicitID: expects explicit ID to take precedence
- TestTextarea_RendersClass: expects class="ui-textarea"
- TestTextarea_DefaultRows: expects rows="4" for zero Rows
- TestTextarea_ExplicitRows: expects rows="6" for explicit Rows
2026-05-16 13:58:19 +02:00
Arthur Belleville
a30a6f9088
feat(13-02): replace CSS files, migrate card.templ to typed API, update all template hardcodes
- button.css: replaced with go-backend multi-class selector version + ghost variant rules
- badge.css: replaced with go-backend pill-shape version + primary variant
- card.css: replaced with go-backend token-based header/body/footer version
- card.templ: migrated from children passthrough to typed CardProps{Header/Body/Footer}
- ui_test.go: rewrote TestCard_RendersChildren -> TestCard_RendersTypedRegions; added TestBadge_PrimaryVariant; added textComponent helper + io import
- auth_login.templ, auth_signup.templ: migrated Card usage to typed CardProps API
- tablos.templ: migrated TabloCard to typed CardProps API with extracted tabloCardBody
- planning.templ, tasks.templ, events.templ, etapes.templ: all compound button class strings updated to multi-class pattern
- go test ./... passes (all packages green)
- just generate succeeds
2026-05-16 13:55:30 +02:00
Arthur Belleville
66f23bba77
feat(13-02): multi-class ButtonClass() + Icon field + buttonType helper in button.templ
- ButtonClass() now emits "ui-button ui-button-solid ui-button-default ui-button-md"
- Ghost variant special case: "ui-button ui-button-ghost ui-button-md" (tone omitted)
- button.templ: added Icon string field to ButtonProps, replaced inline btnType with buttonType() helper
- ui_test.go: updated TestButton_DefaultSolidMD to multi-class slice assertions
- ui_test.go: updated TestButtonClass_String and TestButtonClass_GhostVariant expectations
- All 18 ui package tests pass
2026-05-16 13:52:01 +02:00
Arthur Belleville
d1499659bf
feat(13-01): extend variants.go with new enums and helpers.go with helper functions (GREEN)
- Add ButtonVariantGhost to ButtonVariant enum and NormalizedButtonVariant switch
- Add BadgeVariantPrimary to BadgeVariant enum and NormalizedBadgeVariant switch
- Add IconButtonVariant type (Neutral/Warning/Success/Danger) with normalizer
- Add IconButtonTone type (Solid/Ghost) with normalizer
- Add SpacingStep type (XS/SM/MD/LG/XL) with normalizer
- Add IconButtonClass(), SpaceXClass(), SpaceYClass() exported class functions
- Add buttonType(), inputType(), inputID(), textareaRows() helper functions to helpers.go
- Fix TestButtonClass_GhostVariant assertion to match compound class format preserved for Plan 02
2026-05-16 13:46:30 +02:00
Arthur Belleville
8602eb10a1
test(13-01): add failing tests for new variant enums and class functions (RED)
- TestButtonVariantGhost_Normalizer, TestButtonClass_GhostVariant
- TestBadgeVariantPrimary_Normalizer, TestBadgeClass_PrimaryVariant
- TestIconButtonClass_GhostNeutral, TestIconButtonClass_SolidNeutral
- TestSpaceXClass_MD, TestSpaceYClass_LG
2026-05-16 13:44:36 +02:00
Arthur Belleville
59e39fe538
feat(13-01): replace base.css with 223-line token vocabulary and extract auth.css
- Replace 28-line backend/internal/web/ui/base.css stub with full go-backend token vocabulary (223 lines, --color-brand-primary: #804eec)
- Create backend/internal/web/ui/auth.css with auth-provider button styles extracted from button.css Phase 8 block
- Update backend/tailwind.input.css to import auth.css after base.css
2026-05-16 13:43:56 +02:00
Arthur Belleville
a0f62c261e
test(phase-09): add etape schema validation 2026-05-16 10:45:29 +02:00
Arthur Belleville
deab7a9aa2
test(phase-12): add discussion schema validation 2026-05-16 10:37:29 +02:00
Arthur Belleville
409245eac0
fix(12-03): reset discussion composer from htmx form 2026-05-16 10:32:28 +02:00
Arthur Belleville
1034efcec4
fix(12-03): clear discussion composer after suppressed swap 2026-05-16 10:29:38 +02:00
Arthur Belleville
426d89c43b
fix(12-03): use htmx request source for discussion posts 2026-05-16 10:27:56 +02:00
Arthur Belleville
6f17c3016f
fix(12-03): suppress sender duplicate discussion rows 2026-05-16 10:24:34 +02:00
Arthur Belleville
d15c3748e4
feat(12-03): add discussion SSE stream 2026-05-16 10:18:33 +02:00
Arthur Belleville
c6dcb680bd
test(12-03): add discussion SSE coverage 2026-05-16 10:17:23 +02:00
Arthur Belleville
3111b6e011
feat(12-02): implement discussion unread badges 2026-05-16 10:15:38 +02:00
Arthur Belleville
e3c8d51782
test(12-02): add discussion unread coverage 2026-05-16 10:12:56 +02:00
Arthur Belleville
c5477e4ceb
feat(12-01): implement discussion tab and posting 2026-05-16 10:11:14 +02:00
Arthur Belleville
39e21be126
test(12-01): add discussion handler coverage 2026-05-16 10:08:34 +02:00
Arthur Belleville
68884daf11
feat(12-01): add discussion schema and queries 2026-05-16 10:07:12 +02:00
Arthur Belleville
bb84d70569
fix(11-02): show planning event dates 2026-05-16 08:39:10 +02:00
Arthur Belleville
2989c0b917
feat(11-01): implement planning agenda page 2026-05-16 07:26:49 +02:00
Arthur Belleville
c5c3bbe2d0
test(11-01): add planning agenda red coverage 2026-05-16 07:24:23 +02:00
Arthur Belleville
1d760d5540
test(10-04): add final events regression coverage 2026-05-16 00:36:40 +02:00
Arthur Belleville
ebd31b5645
feat(10-03): add event navigation and user range query 2026-05-16 00:35:09 +02:00
Arthur Belleville
0692dd2d4f
test(10-03): add failing event navigation query tests 2026-05-16 00:34:31 +02:00
Arthur Belleville
614003f165
feat(10-02): add event edit and delete flows 2026-05-16 00:32:55 +02:00
Arthur Belleville
e5f083d2a8
test(10-02): add failing event mutation tests 2026-05-16 00:30:52 +02:00
Arthur Belleville
0bfe8cfbb4
feat(10-01): add events calendar creation slice 2026-05-16 00:27:58 +02:00
Arthur Belleville
4fbd960621
test(10-01): add failing events create calendar tests 2026-05-16 00:24:33 +02:00
Arthur Belleville
f9fc7a1e34
fix(09): honor selected etape on task create 2026-05-15 23:46:32 +02:00
Arthur Belleville
ee62ff9f9b
fix(09): refresh etape counts on task delete 2026-05-15 23:02:05 +02:00
Arthur Belleville
0c95049447
fix(09): refresh etape counts on task create 2026-05-15 22:59:01 +02:00
Arthur Belleville
cf07c29ae5
test(09-04): harden db-backed regressions 2026-05-15 22:54:46 +02:00
Arthur Belleville
3a3ecf5803
fix(09-04): preserve etape filter on reorder 2026-05-15 22:49:40 +02:00
Arthur Belleville
55263e4a52
test(09-04): add etape reorder regressions 2026-05-15 22:49:09 +02:00
Arthur Belleville
b22d79d972
feat(09-03): add task etape selector 2026-05-15 22:47:56 +02:00
Arthur Belleville
9f6c7eb044
test(09-03): add task etape assignment tests 2026-05-15 22:46:14 +02:00
Arthur Belleville
4af623a57b
feat(09-02): manage etapes 2026-05-15 22:44:50 +02:00
Arthur Belleville
9b89282692
test(09-02): add etape management tests 2026-05-15 22:42:39 +02:00
Arthur Belleville
565bb88df5
feat(09-01): add etape task slice 2026-05-15 22:40:25 +02:00
Arthur Belleville
a8a3e5f596
test(09-01): add failing etape slice tests 2026-05-15 22:33:40 +02:00
Arthur Belleville
90af9bdaef
feat(08): disable apple sign-in 2026-05-15 21:41:22 +02:00
Arthur Belleville
85b8c7bce1
chore(dev): load backend env in just recipes 2026-05-15 21:26:55 +02:00
Arthur Belleville
6e6583636f
feat(08-05): add linked providers view and provider docs 2026-05-15 21:10:45 +02:00
Arthur Belleville
59fd6b15b5
feat(08-04): show social sign-in controls on auth pages 2026-05-15 21:09:14 +02:00
Arthur Belleville
a8b6a03eac
feat(08-03): add apple social sign-in flow 2026-05-15 21:06:08 +02:00
Arthur Belleville
6779663c8a
feat(08-02): add google social sign-in flow 2026-05-15 21:03:30 +02:00
Arthur Belleville
2d004cd251
feat(08-01): add social identity schema foundation 2026-05-15 20:59:34 +02:00
Arthur Belleville
3998a5ab92
Made various improvements to the file management 2026-05-15 19:57:46 +02:00
Arthur Belleville
4ea4d28e6e
fix(07): WR-05 sanitize upload filename with filepath.Base and length cap
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 18:56:11 +02:00
Arthur Belleville
e7a66c44cf
fix(07): WR-03 add SetMaxOpenConns(2) on migration sql.DB pool
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 18:55:40 +02:00
Arthur Belleville
ab12bf0962
fix(07): WR-02 move rate limit check before validation in LoginPostHandler
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 18:55:27 +02:00
Arthur Belleville
b61f36f17e
fix(07): WR-01 NewRouter returns error instead of panicking on bad static FS
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 18:54:49 +02:00
Arthur Belleville
fbda7cbe5e
fix(07): CR-02 call cancel() explicitly after S3 Delete, not via defer
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 18:53:04 +02:00
Arthur Belleville
5fc4705bd3
fix(07): replace minioadmin placeholder creds and add worker->web migration gate 2026-05-15 18:46:30 +02:00
Arthur Belleville
f261fb39b8
docs(07-03): extend README with Deploy, Rollback, and Incident Runbook sections
- Deploy section: prerequisites, first-time setup, deploying new versions (DEPLOY-05)
- First-time setup documents DATABASE_URL internal URL, SESSION_SECRET generation,
  full S3/R2 var list, chmod 600 .env.prod reminder (T-07-10), TLS staging note
- Rollback section: image tag redeployment + break-glass schema rollback via goose CLI
- Incident Runbook: /readyz 503, Caddy TLS rate limits, log viewing, distroless debug
  (ephemeral busybox container technique for shell-less runtime image, RESEARCH Pitfall 7)
2026-05-15 18:25:03 +02:00
Arthur Belleville
273f0632be
feat(07-03): add docker-compose.prod.yaml and deploy/Caddyfile
- Production compose stack with postgres, web, worker, caddy services (D-01..D-04, D-08)
- postgres service has no host ports binding (internal network only, T-07-09 mitigated)
- web and worker use same image with different command: values (/app/web, /app/worker)
- Both web and worker depend_on postgres with service_healthy condition (T-07-12 mitigated)
- Caddy handles TLS via Let's Encrypt with persistent caddy_data and caddy_config volumes (D-04)
- Caddyfile uses {$DOMAIN} env var interpolation for the site block (RESEARCH Pattern 6)
- Caddyfile includes Let's Encrypt staging note to avoid rate limits (RESEARCH Pitfall 4)
2026-05-15 18:23:13 +02:00
Arthur Belleville
0781403f5c
feat(07-02): add S3/R2, DOMAIN, and MAX_UPLOAD_SIZE_MB vars to .env.example
- Add S3_ENDPOINT, S3_BUCKET, S3_REGION, S3_ACCESS_KEY, S3_SECRET_KEY with MinIO dev defaults
- Add S3_USE_PATH_STYLE (true for MinIO, false for R2 virtual-hosted)
- Add MAX_UPLOAD_SIZE_MB=25 with default note
- Add commented DOMAIN=app.yourdomain.com for Caddy TLS in docker-compose.prod.yaml (D-04)
- Clarify TEST_DATABASE_URL is dev/test only and must not appear in .env.prod
- All original vars (DATABASE_URL, SESSION_SECRET, PORT, ENV) preserved
2026-05-15 18:19:58 +02:00
Arthur Belleville
f29bf0c765
feat(07-02): multi-stage Dockerfile for web + worker binaries
- Stage 1 (assets): downloads Tailwind v4.0.0 CLI, HTMX@2, Sortable.js 1.15.7; compiles minified CSS
- Stage 2 (builder): runs templ generate @v0.3.1020; CGO_ENABLED=0 go build for /app/web and /app/worker
- Stage 3 (runtime): gcr.io/distroless/static-debian12:nonroot; no CMD per D-08
- No .env files COPY'd into any layer (T-07-05 mitigated)
2026-05-15 18:19:32 +02:00
Arthur Belleville
bdd3cba314
feat(07-01): wire embed.FS into NewRouter and RunMigrations into cmd/web/main.go
- backend/internal/web/router.go: staticDir string -> staticFS fs.FS; /healthz uses HealthzHandler(); /readyz registered with ReadyzHandler(pinger); embedded FS served via fs.Sub()
- backend/cmd/web/main.go: import assets "backend"; db.RunMigrations(ctx, pool, assets.Migrations) before router; web.NewRouter now receives assets.Static
- All *_test.go NewRouter call sites updated from "./static" to os.DirFS("./static"); "os" import added where missing
2026-05-15 18:14:33 +02:00
Arthur Belleville
77e37cb21b
feat(07-01): embed.go + RunMigrations + HealthzHandler()/ReadyzHandler() split
- backend/embed.go: package assets with //go:embed all:static and //go:embed migrations
- backend/internal/db/migrate.go: RunMigrations using pgx/v5/stdlib bridge to goose.Up()
- backend/internal/web/handlers.go: HealthzHandler() no-arg liveness + ReadyzHandler(pinger) readiness
- backend/internal/web/handlers_test.go: TestHealthz_OK (no pinger), TestReadyz_OK, TestReadyz_Down added; TestHealthz_Down deleted
2026-05-15 18:14:26 +02:00
Arthur Belleville
e202ad3a9e
feat(06-02): add just worker target and document worker in README
- justfile: worker target depends on db-up, passes MinIO dev defaults
  (DATABASE_URL, S3_ENDPOINT/BUCKET/REGION/ACCESS_KEY/SECRET_KEY/USE_PATH_STYLE)
- README: replace skeleton section with full "Running the Worker" docs
  (just worker command, expected logs, single-worker constraint, graceful shutdown,
   failed job retry observation)
2026-05-15 16:38:01 +02:00
Arthur Belleville
6e70478417
feat(06-02): replace cmd/worker skeleton with full river wiring
- rivermigrate at startup (idempotent, before client construction)
- S3 store init from env vars (S3_ENDPOINT/S3_BUCKET/S3_ACCESS_KEY/S3_SECRET_KEY/S3_REGION/S3_USE_PATH_STYLE)
- signal.NotifyContext created AFTER all startup I/O (PATTERNS.md critical ordering)
- HeartbeatWorker + OrphanCleanupWorker registered via river.AddWorker
- river.Client with slog.Default() Logger, SlogErrorHandler, MaxWorkers:10
- HeartbeatArgs periodic every 1 min (RunOnStart:true), OrphanCleanupArgs every 1 hr
- StopAndCancel(10s timeout) on shutdown; pool.Close after StopAndCancel
2026-05-15 16:37:20 +02:00
Arthur Belleville
a1c2828dc4
feat(06-01): implement internal/jobs package with workers and error handler
- HeartbeatArgs + HeartbeatWorker (logs slog.Info on each tick)
- OrphanCleanupArgs + OrphanCleanupWorker (S3 delete then DB delete loop)
- NewOrphanCleanupWorker constructor with pool + FileStorer injection
- SlogErrorHandler implementing river.ErrorHandler (HandleError + HandlePanic)
- fileQuerier interface for test injection without real DB
- Unit tests: 7 tests pass (pure mock-based, no DB required)
- go build ./... exits 0
2026-05-15 16:34:08 +02:00
Arthur Belleville
62e5e3eb60
feat(06-01): add river dependency and ListOrphanFiles sqlc query
- go get github.com/riverqueue/river@v0.37.0 + riverpgxv5@v0.37.0
- append ListOrphanFiles :many query to files.sql (orphan tablo_files rows)
- regenerate sqlc: ListOrphanFilesRow{ID, TabloID, S3Key} exported
- go build ./... exits 0
2026-05-15 16:32:48 +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
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
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
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
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