openspec: frontend-foundation change — proposal, design, specs (4 capabilities), tasks
This commit is contained in:
2
openspec/changes/frontend-foundation/.openspec.yaml
Normal file
2
openspec/changes/frontend-foundation/.openspec.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-08-05
|
||||
3
openspec/changes/frontend-foundation/README.md
Normal file
3
openspec/changes/frontend-foundation/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# frontend-foundation
|
||||
|
||||
GOAT front-end foundation: scaffold, Postgres schema, auth, tag setup, public tag page, account CRUD
|
||||
62
openspec/changes/frontend-foundation/design.md
Normal file
62
openspec/changes/frontend-foundation/design.md
Normal file
@@ -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).
|
||||
32
openspec/changes/frontend-foundation/proposal.md
Normal file
32
openspec/changes/frontend-foundation/proposal.md
Normal file
@@ -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
|
||||
|
||||
<!-- none — no existing specs yet -->
|
||||
|
||||
## 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).
|
||||
33
openspec/changes/frontend-foundation/specs/database/spec.md
Normal file
33
openspec/changes/frontend-foundation/specs/database/spec.md
Normal file
@@ -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
|
||||
@@ -0,0 +1,41 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Public tag page by code
|
||||
The system SHALL serve an unauthenticated page at `/t/<tag_code>` for any valid tag code.
|
||||
|
||||
#### Scenario: Valid code
|
||||
- **WHEN** a visitor opens `/t/<tag_code>` for a tag that exists
|
||||
- **THEN** the tag page is rendered
|
||||
|
||||
#### Scenario: Unknown code
|
||||
- **WHEN** a visitor opens `/t/<tag_code>` 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
|
||||
@@ -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
|
||||
34
openspec/changes/frontend-foundation/specs/user-auth/spec.md
Normal file
34
openspec/changes/frontend-foundation/specs/user-auth/spec.md
Normal file
@@ -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
|
||||
33
openspec/changes/frontend-foundation/tasks.md
Normal file
33
openspec/changes/frontend-foundation/tasks.md
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user