# TASKS Use this board to coordinate handoff between planner, implementer, and reviewer. Status values: - `in_planning` - `ready_for_implement` - `in_implementation` - `ready_for_review` - `in_review` - `ready_to_commit` - `changes_requested` - `done` Command expectations: - planner moves tasks into `in_planning` and `ready_for_implement` - implementer moves tasks into `in_implementation`, `ready_for_review`, and `done`, and resumes work from `changes_requested` and `ready_to_commit` - reviewer moves tasks into `in_review`, `ready_to_commit`, or `changes_requested` - `status_cycle` should report deterministic task status, current owner role, and next recommended action based on this board | Task ID | Scope | Status | Acceptance Criteria | Evidence | Next Role | | --- | --- | --- | --- | --- | --- | | T-001 | Project scaffolding: Go module layout, root-config loading (file/env listing container paths + display names), Dockerfile skeleton, docker-compose example, `README.md` overview/setup skeleton | done | `go build ./...` succeeds; app boots and logs the configured roots from a sample config without panicking; `README.md` documents the config file/env format for declaring roots and the volume-mount + restart-to-add-root constraint | PASS: config source/precedence/validation tests; `go fmt ./...`, `go vet ./...`, `go test ./...`, and `go build -buildvcs=false ./...` in `golang:1.24`; Docker image build + sample-config startup-log smoke test | none | | T-002 | Data & auth foundation: SQLite schema/migrations for users + sessions, bcrypt password hashing, seed admin `boss@zecevic-ninja.com` / `Start1234!` with `must_change_password=true` on first startup, server-side session-cookie middleware (HTTP-only/Secure/SameSite, sliding expiry), login/logout routes, forced password-change gate that blocks every other route until the password is changed | done | Unauthenticated request to any non-login route (incl. file listing, download, stream, admin) returns redirect/401, never content; login with seed credentials succeeds and is immediately forced to the change-password screen and cannot reach any other page until changed; new password must differ from the temporary one; sessions persist across process restart via SQLite; unit tests cover auth middleware, forced-change gate, and password hashing | PASS: auth storage/bcrypt/persistence/sliding-session and HTTP login/logout/forced-change/protected-route tests; `go fmt ./...`, `go vet ./...`, `go test ./...` in `golang:1.24`; Docker image build and named-volume startup smoke test | none | | T-003 | Folder browsing UI: server-rendered root listing page, directory navigation with breadcrumbs, strict path confinement inside each configured root (block `..` traversal and symlink escapes), light/dark theme toggle, minimal vanilla JS/CSS (no SPA framework) | done | Authenticated user sees configured roots, can enter/exit subfolders via breadcrumbs; any crafted path that would resolve outside a root's filesystem boundary is rejected (test with `../` and a symlink pointing outside the root); theme toggle switches light/dark and persists per session/browser; `README.md` documents the browsing UI behavior | PASS: confined directory listing, traversal/symlink-escape rejection, authenticated root/folder/breadcrumb rendering, theme-local-storage markup; `go fmt ./...`, `go vet ./...`, `go test ./...` in `golang:1.24` | none | | T-004 | Download & video streaming: raw-byte download endpoint with correct `Content-Type`/`Content-Disposition`, full HTTP `Range` support for video files (seek/partial content/resume) with zero transcoding anywhere in the request path | done | Downloading any file returns identical bytes to the source file (checksum match); a `Range` request against a video file returns `206 Partial Content` with the correct byte range and headers; no encoding/thumbnailing/transcoding code path exists; `README.md` documents the reverse-proxy Range-header-passthrough requirement | PASS: root-confined regular-file opens; authenticated byte-identical attachment download; video `206` `Range` response; unauthenticated delivery gate; `go fmt ./...`, `go vet ./...`, `go test ./...` in `golang:1.24` | none | | T-005 | Admin user management: admin-only screens to list users, create a user (temporary password, `must_change_password=true`), reset a user's password, disable/delete a user; role-based route protection (403 for authenticated non-admins, not a redirect that leaks page existence) | done | Non-admin authenticated user gets 403 on all admin routes; admin can create a user who is then forced through the change-password gate on first login; admin can reset/disable a user; `README.md` documents the admin user-management flow | PASS: admin lifecycle/session-invalidation and HTTP role-protection/temporary-password tests; `go fmt ./...`, `go vet ./...`, `go test ./...` in `golang:1.24`; E2E not configured | none | | T-006 | Docker packaging & deployment docs: finalize multi-stage Dockerfile (small image, no build tools in final image), finalize docker-compose example with volume mounts + root config, complete `README.md` (build/run, root config format, first-login/admin credential flow, forced password change, reverse-proxy TLS + Range passthrough requirement) | done | `docker build` produces a working image; `docker run` with a sample volume mount and root config serves the login page only until authenticated, per Priority 1; `README.md` is complete enough for a new operator to deploy without reading source code | PASS: Compose configuration validation; production image build/run, non-root runtime, and unauthenticated login boundary smoke test; `go fmt ./...`, `go vet ./...`, `go test ./...` in `golang:1.24` | none | | T-007 | Local-testing insecure-cookie opt-in: add an off-by-default env var (e.g. `WEB_FILE_BROWSER_INSECURE_COOKIES`) that, when explicitly enabled, omits the `Secure` attribute from the session cookie so login works over plain `http://` (non-TLS, non-`localhost`-only origins) for local development; default behavior unchanged; no other session/auth behavior (`HttpOnly`, `SameSite`, expiry, forced password change) may be weakened; `README.md` gets a new "Local development over HTTP" section that explicitly warns this must never be enabled on a real/production/exposed deployment | done | With the var unset, session cookie still has `Secure` set and behavior is unchanged from T-002/T-005; with the var explicitly enabled, login over plain `http://` succeeds (no redirect loop back to `/login`), the cookie has `Secure` unset, and all other cookie/session attributes and the forced-password-change gate are unchanged; `README.md` documents local-only Docker Compose and Docker Run setup with an explicit warning | PASS: default-secure and plain-HTTP cookie-jar login/forced-change tests; Compose default/opt-in configuration validation; `go fmt ./...`, `go vet ./...`, `go test ./...` in `golang:1.24`; deployment E2E passed | none | | T-008 | Bug fix (regression against shipped T-004): `isVideoFile` and the delivered `Content-Type` currently rely on `mime.TypeByExtension`, which resolves to nothing for `.mp4`/`.mkv`/etc. inside the actual Alpine-based production image (no OS MIME database present, no video entries in Go's built-in fallback table) — verified by inspecting `alpine:3.21` directly. Replace with an app-internal extension → MIME-type table (`.mp4`, `.m4v`, `.mov`, `.webm`, `.mkv`, `.avi`, `.mpg`/`.mpeg`, `.wmv`, `.flv` at minimum) used consistently by both the "stream" link visibility check and the file-delivery `Content-Type` header, independent of any OS/container MIME database. `README.md` documents the supported video extensions and the `.mkv`-in-browser playback caveat (download always works; native in-browser streaming of the MKV container is browser-dependent, not guaranteed, and no transcoding is performed per the project's no-encoding requirement) | ready_for_implement | Inside the actual production Docker image (not the Go build image): requesting an `.mp4` returns `Content-Type: video/mp4` and the folder listing shows a "stream" link for it; requesting an `.mkv` returns the correct Matroska `Content-Type` and a "stream" link; a `Range` request against the `.mp4` still returns `206 Partial Content` with correct byte range; unit tests cover the new extension table directly (no dependency on host/OS MIME data) so the bug can't silently regress; `README.md` lists supported video extensions and the `.mkv` browser-support caveat | n/a | implement |