From b846c2c58e2750985e5c188452b30ce0cd757f75 Mon Sep 17 00:00:00 2001 From: Sam Rolfe Date: Wed, 5 Aug 2026 12:51:44 +1000 Subject: [PATCH] =?UTF-8?q?openspec:=20frontend-foundation=20change=20?= =?UTF-8?q?=E2=80=94=20proposal,=20design,=20specs=20(4=20capabilities),?= =?UTF-8?q?=20tasks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend-foundation/.openspec.yaml | 2 + .../changes/frontend-foundation/README.md | 3 + .../changes/frontend-foundation/design.md | 62 +++++++++++++++++++ .../changes/frontend-foundation/proposal.md | 32 ++++++++++ .../specs/database/spec.md | 33 ++++++++++ .../specs/public-tag-page/spec.md | 41 ++++++++++++ .../specs/tag-management/spec.md | 44 +++++++++++++ .../specs/user-auth/spec.md | 34 ++++++++++ openspec/changes/frontend-foundation/tasks.md | 33 ++++++++++ 9 files changed, 284 insertions(+) create mode 100644 openspec/changes/frontend-foundation/.openspec.yaml create mode 100644 openspec/changes/frontend-foundation/README.md create mode 100644 openspec/changes/frontend-foundation/design.md create mode 100644 openspec/changes/frontend-foundation/proposal.md create mode 100644 openspec/changes/frontend-foundation/specs/database/spec.md create mode 100644 openspec/changes/frontend-foundation/specs/public-tag-page/spec.md create mode 100644 openspec/changes/frontend-foundation/specs/tag-management/spec.md create mode 100644 openspec/changes/frontend-foundation/specs/user-auth/spec.md create mode 100644 openspec/changes/frontend-foundation/tasks.md diff --git a/openspec/changes/frontend-foundation/.openspec.yaml b/openspec/changes/frontend-foundation/.openspec.yaml new file mode 100644 index 0000000..1c37182 --- /dev/null +++ b/openspec/changes/frontend-foundation/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-08-05 diff --git a/openspec/changes/frontend-foundation/README.md b/openspec/changes/frontend-foundation/README.md new file mode 100644 index 0000000..c84c479 --- /dev/null +++ b/openspec/changes/frontend-foundation/README.md @@ -0,0 +1,3 @@ +# frontend-foundation + +GOAT front-end foundation: scaffold, Postgres schema, auth, tag setup, public tag page, account CRUD diff --git a/openspec/changes/frontend-foundation/design.md b/openspec/changes/frontend-foundation/design.md new file mode 100644 index 0000000..f5d0576 --- /dev/null +++ b/openspec/changes/frontend-foundation/design.md @@ -0,0 +1,62 @@ +## Context + +WhereWoof (return-tag platform, see `where_woof.md`) has no code yet. This change builds the GOAT front-end foundation: a Go application serving the owner experience (auth, tag setup/management) and the public tag page, against a single shared Postgres database. The Laravel admin, scan flow (geolocation/SMS), billing, and deployment are later phases — this change deliberately excludes them. + +## Goals / Non-Goals + +**Goals:** +- A working Go app (`frontend/`) with register/login/logout, My Tags CRUD, tag setup, and the public `/t/:tag_code` page. +- Postgres schema (`db/schema.sql`) matching the settled data model (users, tags, scans). +- The GOAT experience: server-rendered templates + HTMX partial swaps + Alpine for small client behaviour + Tailwind styling. +- Clean seams so Phase 2 (scan flow) and Phase 4 (Laravel) can build on the same schema. + +**Non-Goals:** +- Scan flow (geolocation, SMS alerts, `tel:`/`sms:` links) — Phase 2. +- Photo uploads (URL field only) — Phase 6. +- Laravel admin, billing, plans, scan-limit enforcement — later phases. +- Deployment/rsync/Caddy — Phase 7. +- Production tag provisioning UI (tags are pre-coded at manufacture; admin seeds them). + +## Decisions + +1. **Go stdlib `net/http` with Go 1.22+ ServeMux — no web framework.** + The enhanced mux supports method + wildcard patterns (`GET /t/{tag_code}`). Rationale: zero dependencies, the plan explicitly says "no framework", and it keeps the skills-update surface small. *Alternative considered:* chi — rejected as unnecessary for ~10 routes. + +2. **Postgres access via `pgx` (pool) + `sqlc` (generated queries).** + `sqlc` generates type-safe query code from SQL files, so schema and queries stay in sync and compile-checked. Rationale: settled decision; sqlc is also a good skills-update item. *Alternative:* raw `database/sql` — more boilerplate, no compile-time safety. + +3. **Sessions: signed cookie sessions (gorilla/sessions, cookie store).** + Cookie holds the user id, signed + encrypted with a server secret from env. Rationale: no session table needed in Phase 1, stateless, simple. *Alternative:* DB-backed sessions — better revocation control, but an extra table and lookup; revisit under anti-abuse (Phase 9). + +4. **Passwords: bcrypt** (`golang.org/x/crypto/bcrypt`). Standard, no discussion. + +5. **Tag code: opaque random string (URL-safe, ~10 chars), stored as TEXT, unique.** + Printed on QR + NFC at manufacture. Phase 1 uses a seed script with test codes (e.g. `TEST000001`); the public tag-creation UI belongs to the Laravel admin later. No public endpoint creates tags — that's the anti-abuse posture from day one. + +6. **Schema ownership: `db/schema.sql` is canonical until Laravel exists.** + Idempotent (`CREATE TABLE IF NOT EXISTS`) and applied via a `make db-up` target against `DATABASE_URL`. Laravel takes over migration ownership in Phase 4 (settled decision), generating migrations from this file. + +7. **Templates: `html/template` with a base layout + per-page templates; HTMX partials for the account list/edit flows; Alpine for the tag page's small behaviours; Tailwind via CDN.** + Rationale: matches the plan's UI sketch philosophy and keeps Phase 1 dependency-light. CDN Tailwind is fine now; a build step can replace it under Phase 3 polish. + +## Risks / Trade-offs + +- [Cookie-session tampering/revocation limits] → Secret from env, `HttpOnly` + `SameSite=Lax`; DB-backed sessions listed under Phase 9 hardening. +- [Tag-code enumeration — public page accepts any code] → Codes are long random strings; unknown codes return a generic "not found" page; rate limiting deferred to anti-abuse phase. +- [sqlc codegen adds a build step] → `make generate` target; queries kept in one `queries.sql`. +- [No framework = more wiring (routing, parsing, validation)] → Small surface (~12 routes, 4 forms); helpers in `handlers/`. +- [Tailwind CDN in production is a network dependency] → Accepted for Phase 1; replace with a compiled build under Phase 3. +- [Schema drift if Laravel later re-migrates] → Phase 4 generates Laravel migrations from `db/schema.sql`; drift caught by matching table definitions. + +## Migration Plan + +1. Apply `db/schema.sql` to a dev Postgres (`make db-up`); create the DB if missing. +2. Seed test tag codes (`make seed`) for manual verification. +3. Run the app (`DATABASE_URL=... go run .`), verify register → add tag → edit details → public page. +4. No rollback path needed beyond dropping the dev DB; production rollout is Phase 7. + +## Open Questions + +- Exact tag code format/length (manufacturer decision) — placeholder: 10-char URL-safe base32. +- Production Postgres hosting (likely a Docker PG on `.13`) — Phase 7. +- Session store choice revisited under anti-abuse hardening (Phase 9). diff --git a/openspec/changes/frontend-foundation/proposal.md b/openspec/changes/frontend-foundation/proposal.md new file mode 100644 index 0000000..ff135bc --- /dev/null +++ b/openspec/changes/frontend-foundation/proposal.md @@ -0,0 +1,32 @@ +## Why + +WhereWoof is a return-item tag platform: buyers get pre-coded QR/NFC tags, a finder scans the tag on a lost item and sees how to return it. The product plan (`where_woof.md`) is settled; nothing of it is buildable yet. This change creates the front-end foundation — the GOAT (Go + HTMX + Alpine + Tailwind + Postgres) app that carries the owner-facing experience: tag setup, account, and the public tag page. + +## What Changes + +- New Go application in `frontend/` (Go `net/http`, HTMX, Alpine.js, Tailwind via CDN) talking to **Postgres** via `pgx`/`sqlc`. +- Shared database schema (`db/schema.sql`): `users`, `tags`, `scans` tables. Laravel will own migrations later; until then `schema.sql` is the source of truth. +- **User auth**: register / login / logout with email + password, session cookie. +- **Tag management (account CRUD)**: add a tag to the account by entering its `tag_code`, list my tags, edit details (item type, description, photo URL, phone, address, notes), remove a tag. Limit of 20 tags per account enforced. +- **Public tag page** `GET /t/:tag_code`: shows a "not set up yet" prompt when the tag is unset; shows the item's return details (with an edit affordance for a logged-in owner) when set. +- **Scan flow is NOT in this change** (Phase 2): no geolocation, no SMS alerts, no `tel:`/`sms:` links yet — the page renders details only. + +## Capabilities + +### New Capabilities + +- `user-auth`: register, login, logout, session handling for owners. +- `tag-management`: bind a tag_code to an account, list/edit/remove tags, 20-tag limit, item details fields. +- `public-tag-page`: unauthenticated view of a tag by code — setup prompt or return details. +- `database`: shared Postgres schema (users, tags, scans), unique tag codes, tag status lifecycle. + +### Modified Capabilities + + + +## Impact + +- **New code**: `frontend/` Go module (main.go, db, handlers, templates, static). No framework beyond stdlib. +- **New schema**: `db/schema.sql` + Postgres database (dev: Supabase Postgres on `.27:5434`). +- **Config**: `DATABASE_URL` env var; port 3020 default. +- **No changes** to admin (Laravel — later phase), deployment (rsync/Caddy — Phase 7), or billing (Phase 5). diff --git a/openspec/changes/frontend-foundation/specs/database/spec.md b/openspec/changes/frontend-foundation/specs/database/spec.md new file mode 100644 index 0000000..8716a82 --- /dev/null +++ b/openspec/changes/frontend-foundation/specs/database/spec.md @@ -0,0 +1,33 @@ +## ADDED Requirements + +### Requirement: Shared Postgres database +The system SHALL use a single shared PostgreSQL database for all data. The canonical schema SHALL live in `db/schema.sql` until the Laravel admin takes over migration ownership. + +#### Scenario: Schema applied to a fresh database +- **WHEN** `db/schema.sql` is applied to an empty database +- **THEN** the `users`, `tags`, and `scans` tables exist with the defined columns and constraints + +### Requirement: Users table +The `users` table SHALL store id, unique email, password hash, name, phone, and created_at. + +#### Scenario: Unique emails +- **WHEN** a second row is inserted with an email that already exists +- **THEN** the database rejects the insert + +### Requirement: Tags table +The `tags` table SHALL store a unique public `tag_code`, owner reference, status (`unset`, `active`, `suspended`), item type, description, photo URL, phone, address, notes, and timestamps. + +#### Scenario: Unique tag codes +- **WHEN** a second row is inserted with a tag_code that already exists +- **THEN** the database rejects the insert + +#### Scenario: Status values +- **WHEN** a row is inserted with a status not in the allowed set +- **THEN** the database rejects the insert + +### Requirement: Scans table +The `scans` table SHALL record tag scans with timestamp, optional latitude/longitude, whether location was shared, optional scanner phone, and whether an alert was sent. + +#### Scenario: Recording a scan +- **WHEN** a scan of a tag is recorded +- **THEN** a row exists with tag reference, timestamp, and location flags diff --git a/openspec/changes/frontend-foundation/specs/public-tag-page/spec.md b/openspec/changes/frontend-foundation/specs/public-tag-page/spec.md new file mode 100644 index 0000000..9535762 --- /dev/null +++ b/openspec/changes/frontend-foundation/specs/public-tag-page/spec.md @@ -0,0 +1,41 @@ +## ADDED Requirements + +### Requirement: Public tag page by code +The system SHALL serve an unauthenticated page at `/t/` for any valid tag code. + +#### Scenario: Valid code +- **WHEN** a visitor opens `/t/` for a tag that exists +- **THEN** the tag page is rendered + +#### Scenario: Unknown code +- **WHEN** a visitor opens `/t/` for a code that does not exist +- **THEN** a friendly "tag not found" page is shown + +### Requirement: Unset tag shows setup prompt +When the tag is not yet bound to an account (status `unset`), the tag page SHALL tell the visitor the tag is not set up and show how to claim it. + +#### Scenario: First scan of an unset tag +- **WHEN** a visitor opens the page of a tag with status `unset` +- **THEN** the page shows a "this tag is not set up yet" message and a link to register/log in + +### Requirement: Set tag shows return details +When the tag is bound to an account and has details (status `active`), the tag page SHALL show the item's return details: item type, description, photo, phone, address, notes — for any visitor without login. + +#### Scenario: Finder views an active tag +- **WHEN** a visitor opens the page of a tag with status `active` +- **THEN** the page shows the item's return details without requiring login + +#### Scenario: Suspended tag +- **WHEN** a visitor opens the page of a tag with status `suspended` +- **THEN** the page shows only that the tag is unavailable + +### Requirement: Owner edit affordance +A logged-in owner viewing their own tag page SHALL see an edit link to the account CRUD page for that tag. + +#### Scenario: Owner is logged in +- **WHEN** the authenticated owner opens the page of their own tag +- **THEN** an edit link to the tag's edit page is shown + +#### Scenario: Visitor is not the owner +- **WHEN** a visitor who is not the owner opens the page +- **THEN** no edit link is shown diff --git a/openspec/changes/frontend-foundation/specs/tag-management/spec.md b/openspec/changes/frontend-foundation/specs/tag-management/spec.md new file mode 100644 index 0000000..e50773b --- /dev/null +++ b/openspec/changes/frontend-foundation/specs/tag-management/spec.md @@ -0,0 +1,44 @@ +## ADDED Requirements + +### Requirement: Add a tag to the account +An authenticated owner SHALL be able to bind a tag to their account by entering its tag code. A tag that is already bound to another account SHALL be rejected. + +#### Scenario: Bind an unset tag +- **WHEN** an owner enters the code of a tag that is not yet bound to any account +- **THEN** the tag becomes owned by that account and is listed under My Tags + +#### Scenario: Bind an already-owned tag +- **WHEN** an owner enters the code of a tag already bound to another account +- **THEN** the operation is rejected with an error message + +### Requirement: List my tags +The account page SHALL list all tags owned by the authenticated owner, with their status and a link to edit each. + +#### Scenario: Owner with tags +- **WHEN** an owner opens the account page +- **THEN** all of their tags are shown with status (unset/active) and an edit link + +### Requirement: Edit tag details +An owner SHALL be able to set or edit the return details of a tag they own: item type (dog, cat, baggage, skis, other), description, photo URL, phone number, address, and notes. + +#### Scenario: Complete tag setup +- **WHEN** an owner fills in all detail fields for a tag and saves +- **THEN** the tag status becomes active and the public page shows the details + +#### Scenario: Partial details saved +- **WHEN** an owner saves a tag with only some fields filled +- **THEN** the tag is saved and the public page shows only the filled fields + +### Requirement: Remove a tag +An owner SHALL be able to remove a tag from their account. Removing a tag SHALL revert it to the unset state so it can be bound to another account. + +#### Scenario: Remove and re-bind +- **WHEN** an owner removes a tag and another account later enters its code +- **THEN** the second account can bind it + +### Requirement: Tag ownership limit +An account SHALL be limited to 20 owned tags. + +#### Scenario: Limit reached +- **WHEN** an owner who already owns 20 tags tries to bind another tag +- **THEN** the operation is rejected with an error message diff --git a/openspec/changes/frontend-foundation/specs/user-auth/spec.md b/openspec/changes/frontend-foundation/specs/user-auth/spec.md new file mode 100644 index 0000000..40ae20c --- /dev/null +++ b/openspec/changes/frontend-foundation/specs/user-auth/spec.md @@ -0,0 +1,34 @@ +## ADDED Requirements + +### Requirement: Account registration +The system SHALL allow a new owner to register with a unique email address and a password. The password SHALL be stored only as a salted hash. + +#### Scenario: Successful registration +- **WHEN** a visitor submits a new email and password on the register page +- **THEN** an account is created and the visitor is logged in + +#### Scenario: Duplicate email +- **WHEN** a visitor registers with an email that already exists +- **THEN** registration is rejected with an error message + +### Requirement: Login and logout +The system SHALL allow an owner to log in with email and password and to log out, maintaining an authenticated session via a signed session cookie. + +#### Scenario: Successful login +- **WHEN** an owner submits the correct email and password +- **THEN** a session cookie is set and the owner is authenticated + +#### Scenario: Incorrect password +- **WHEN** an owner submits a wrong password +- **THEN** login is rejected with an error message and no session is created + +#### Scenario: Logout +- **WHEN** an authenticated owner clicks logout +- **THEN** the session is destroyed and the owner is redirected to the home page + +### Requirement: Session-protected routes +Routes that manage tags SHALL require an authenticated session and redirect unauthenticated visitors to the login page. + +#### Scenario: Unauthenticated access to account +- **WHEN** a visitor who is not logged in opens the account page +- **THEN** they are redirected to the login page diff --git a/openspec/changes/frontend-foundation/tasks.md b/openspec/changes/frontend-foundation/tasks.md new file mode 100644 index 0000000..43f1c63 --- /dev/null +++ b/openspec/changes/frontend-foundation/tasks.md @@ -0,0 +1,33 @@ +## 1. Scaffold & Database + +- [ ] 1.1 Initialize Go module `frontend/` (go 1.22+), directory layout: `internal/db`, `internal/auth`, `internal/handlers`, `templates/`, `static/` +- [ ] 1.2 Create `db/schema.sql` (users, tags, scans per database spec) — idempotent `CREATE TABLE IF NOT EXISTS` +- [ ] 1.3 Add `Makefile` targets: `db-up` (apply schema), `seed`, `generate` (sqlc), `run` +- [ ] 1.4 Add `sqlc.yaml` + `internal/db/queries.sql` covering: create user, get user by email/id, bind tag, list tags by owner, get tag by code, update tag details, remove tag; run `sqlc generate` +- [ ] 1.5 Implement config (DATABASE_URL, SESSION_SECRET, port 3020) and `main.go` wiring pgx pool + router + static/template serving + +## 2. Auth (user-auth spec) + +- [ ] 2.1 Register: handler + template (email, password, name), bcrypt hash, unique-email error, auto-login after register +- [ ] 2.2 Login/logout: handlers + gorilla/sessions cookie store (HttpOnly, SameSite=Lax), redirect to account after login +- [ ] 2.3 RequireAuth middleware for account routes + `currentUser` helper injected into all templates + +## 3. Tag Management (tag-management spec) + +- [ ] 3.1 Add-tag handler: bind by tag code — reject already-bound tags, enforce 20-tag limit, show errors +- [ ] 3.2 My Tags page: list owned tags with status + edit links +- [ ] 3.3 Tag edit page + form: item type (dog/cat/baggage/skis/other), description, photo URL, phone, address, notes; saving sets status `active` +- [ ] 3.4 Remove-tag handler: reverts tag to `unset` (re-bindable) + +## 4. Public Tag Page (public-tag-page spec) + +- [ ] 4.1 `GET /t/{tag_code}` handler with tag lookup + friendly "tag not found" page for unknown codes +- [ ] 4.2 Render logic: `unset` → setup prompt with register/login links; `active` → return details; `suspended` → unavailable message +- [ ] 4.3 Owner edit affordance: edit link shown only when logged-in user owns the tag +- [ ] 4.4 Base layout + home page with Tailwind (CDN) styling; wire static assets + +## 5. Seed & Verification + +- [ ] 5.1 Seed script inserting test tag codes (e.g. TEST000001..TEST000010) +- [ ] 5.2 End-to-end manual verification of every spec scenario: register, duplicate email, login/logout, unauth redirect, bind unset tag, bind owned tag rejected, 20-tag limit, edit → active, remove → re-bind, unknown code page, suspended tag +- [ ] 5.3 Run `openspec validate frontend-foundation`; commit all artifacts and code