openspec: tag-lifecycle change (close/move/legacy redirect)
This commit is contained in:
2
openspec/changes/tag-lifecycle/.openspec.yaml
Normal file
2
openspec/changes/tag-lifecycle/.openspec.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-08-10
|
||||||
3
openspec/changes/tag-lifecycle/README.md
Normal file
3
openspec/changes/tag-lifecycle/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# tag-lifecycle
|
||||||
|
|
||||||
|
Owner can close a tag (status 'closed') and move tag data to a new tag (broken-tag replacement); old-URL compat redirect for physical QR tags
|
||||||
41
openspec/changes/tag-lifecycle/design.md
Normal file
41
openspec/changes/tag-lifecycle/design.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
Physical tags break; owners need to retire a tag and move its data to a replacement. Also, legacy printed URLs (`?x=5&productid=…`) must still reach tag pages so old physical tags work with the new system.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
- Owner-facing Close and Move actions on the account page.
|
||||||
|
- Closed tags: page shows unavailable, no alerts.
|
||||||
|
- Legacy `productid` URL redirect.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
- Tag transfer between accounts (admin can reassign owner later) — not in scope.
|
||||||
|
- NFC/QR re-printing (future production slice).
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
1. **Status `closed`** (new enum value): schema CHECK updated via `ALTER TABLE tags DROP CONSTRAINT IF EXISTS tags_status_check` + re-add including `closed` (idempotent; CREATE TABLE also updated for fresh DBs).
|
||||||
|
2. **Alert gating**: `shouldAlert` returns false when `tag.Status == "closed"` (and "suspended" — already handled? currently suspended isn't gated in shouldAlert either — add both for safety; the public page already handles suspended).
|
||||||
|
3. **Close**: `CloseTag` (POST /account/tags/{id}/close) — owner-only (loadOwnedTag) → `SetTagStatus(id, 'closed')` → HTMX account-panel response.
|
||||||
|
4. **Move**: `MoveTag` (POST /account/tags/{id}/move, form `new_tag_code`) — load source (owner) → `GetTagByCode(target)` must exist, be unset, not same → `BindTag(owner, target)` → `UpdateTagDetails(target, source fields)` → `SetTagStatus(source, 'closed')` → panel (with error on failure).
|
||||||
|
5. **Compat redirect**: in `Home`, `productid` query param → 302 to `/t/{productid}`. (The QR likely encodes `/?x=5&productid=…`; the `x=5` param is ignored.)
|
||||||
|
6. **UI**: tag-list rows gain "Close" (confirm) and a small "Move" form (new tag code input). Public page: `closed` renders the same "unavailable" treatment as suspended, with owner-specific copy.
|
||||||
|
7. **Admin**: TagResource status Select + badge include `closed`.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [Constraint drop/re-add could race] → dev-only, single migrator; fine.
|
||||||
|
- [Move copies photo_url] → the photo object key is preserved (MinIO) — works.
|
||||||
|
- [Old URL path weirdness (`/x=5&…` without `?`)] → if the QR literally encodes that malformed path, it 404s; the standard `?x=5&productid=` form is handled. Verify with the physical tags.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
1. Schema ALTER (constraint) + `make db-up`.
|
||||||
|
2. Go handlers + redirect + templates; build.
|
||||||
|
3. Admin status option.
|
||||||
|
4. Verify (close, move, redirect, gating); regressions; deploy.
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- None blocking.
|
||||||
28
openspec/changes/tag-lifecycle/proposal.md
Normal file
28
openspec/changes/tag-lifecycle/proposal.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
Physical tags break (the user has a broken tag to test with). Owners need to **close** a tag (deactivate it, keeping data) and **move** a tag's details to a new tag (replacement tag) without re-entering everything. Also, the user's physical tags from the old system print URLs in the old format (`?x=5&productid=…`) — those need to redirect to the new `/t/{code}` pages so old tags work as-is.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- **`closed` tag status**: `tags.status` gains `closed` (constraint updated idempotently). Owner can close their own tag → public page shows "closed by its owner", data kept. Admin sees it in the status options.
|
||||||
|
- **Move**: owner enters a new tag code → the new tag (must exist in the registry, be unset) is bound and **copies all details** (item type, description, photo, phone, address, notes, sms toggle) from the old tag; the old tag is closed. Ideal for broken-tag replacement.
|
||||||
|
- **Old-URL redirect**: `/` with a `productid` query param redirects to `/t/{productid}` (legacy physical QR URLs).
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
|
||||||
|
- `tag-lifecycle`: owner close + move actions.
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
|
||||||
|
- `database`: ADDED — `closed` in the tags status CHECK.
|
||||||
|
- `tag-management`: ADDED — close + move handlers; account UI actions.
|
||||||
|
- `public-tag-page`: ADDED — closed state renders "unavailable".
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- **Schema**: status CHECK gains `closed` (drop/re-add idempotent).
|
||||||
|
- **Go**: `CloseTag`, `MoveTag` handlers; Home compat redirect; account panel + tag-list template actions.
|
||||||
|
- **Admin**: TagResource status options + badge include `closed`.
|
||||||
|
- No changes to the alert pipeline (closed tags are gated by status via existing suspended/closed page behaviour — note: alert gating currently keys on `sms_enabled`/order; a closed tag should also stop alerts — add a closed check in shouldAlert).
|
||||||
30
openspec/changes/tag-lifecycle/specs/tag-lifecycle/spec.md
Normal file
30
openspec/changes/tag-lifecycle/specs/tag-lifecycle/spec.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Close a tag
|
||||||
|
An owner SHALL be able to close one of their tags. Closing sets status `closed`: the public page shows the tag is unavailable, data is kept, and scans do not alert.
|
||||||
|
|
||||||
|
#### Scenario: Owner closes a tag
|
||||||
|
- **WHEN** the owner closes their tag
|
||||||
|
- **THEN** the tag status is `closed`, the public page shows it unavailable, and no alerts are sent
|
||||||
|
|
||||||
|
#### Scenario: Closed tag page
|
||||||
|
- **WHEN** a visitor opens a closed tag
|
||||||
|
- **THEN** they see an "unavailable / closed" message
|
||||||
|
|
||||||
|
### Requirement: Move tag data
|
||||||
|
An owner SHALL be able to move a tag's details to another tag code. The target must exist in the registry and be unset; after the move the target is active with all copied details, and the source tag is closed.
|
||||||
|
|
||||||
|
#### Scenario: Move to a replacement tag
|
||||||
|
- **WHEN** the owner moves tag A to unset tag B
|
||||||
|
- **THEN** tag B is bound with all of A's details, and tag A becomes `closed`
|
||||||
|
|
||||||
|
#### Scenario: Invalid target
|
||||||
|
- **WHEN** the target code does not exist, is already owned, or is the same tag
|
||||||
|
- **THEN** the move is rejected with an error
|
||||||
|
|
||||||
|
### Requirement: Legacy URL redirect
|
||||||
|
`GET /` with a `productid` query parameter SHALL redirect to `/t/{productid}`.
|
||||||
|
|
||||||
|
#### Scenario: Old QR URL
|
||||||
|
- **WHEN** a visitor opens `/?x=5&productid=CODE`
|
||||||
|
- **THEN** they are redirected to `/t/CODE`
|
||||||
20
openspec/changes/tag-lifecycle/tasks.md
Normal file
20
openspec/changes/tag-lifecycle/tasks.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
## 1. Schema
|
||||||
|
|
||||||
|
- [ ] 1.1 `db/schema.sql`: status CHECK gains `closed` (update CREATE + idempotent drop/re-add ALTER); `make db-up`
|
||||||
|
|
||||||
|
## 2. Go
|
||||||
|
|
||||||
|
- [ ] 2.1 `shouldAlert`: return false when status `closed` or `suspended`
|
||||||
|
- [ ] 2.2 `tags.go`: `CloseTag` + `MoveTag` handlers (owner-only, HTMX panel responses)
|
||||||
|
- [ ] 2.3 `Home` compat redirect: `productid` query → `/t/{productid}`
|
||||||
|
- [ ] 2.4 Templates: tag-list Close + Move actions; tag-public closed state; routes in main.go
|
||||||
|
|
||||||
|
## 3. Admin
|
||||||
|
|
||||||
|
- [ ] 3.1 TagResource status options + badge include `closed`
|
||||||
|
|
||||||
|
## 4. Verification
|
||||||
|
|
||||||
|
- [ ] 4.1 New suite: close → page unavailable + no alert; move → target bound w/ copied details + source closed; move errors (owned/unset-missing/same); `/?x=5&productid=CODE` → 302 `/t/CODE`
|
||||||
|
- [ ] 4.2 Regressions (72) green; deploy
|
||||||
|
- [ ] 4.3 `openspec validate tag-lifecycle`; commit
|
||||||
Reference in New Issue
Block a user