diff --git a/README.md b/README.md
index ae80904..05115ef 100644
--- a/README.md
+++ b/README.md
@@ -1,114 +1,234 @@
-# Next
+# Next — Real-Time Queue System
-> A calm, persistent real-time queue for small service teams.
+> A calm, persistent queue for small service teams, built as a production-ready portfolio project by Oniel Alejo Feliz.
-Next uses a transactional Supabase PostgreSQL engine. Anonymous browser identities can create or join queues; a one-time queue capability grants staff membership; customer, staff, and public-display clients converge through filtered Realtime invalidations followed by authoritative revisioned snapshots.
+[Live application](https://next-queue-omega.vercel.app) · [Try the demo](https://next-queue-omega.vercel.app/demo) · [Watch the 27-second demo](docs/assets/video/next-v1-demo.mp4) · [Read the v1.0 release notes](docs/releases/v1.0.0.md)
-The production application is live at [next-queue-omega.vercel.app](https://next-queue-omega.vercel.app). It runs on Vercel Hobby with one Supabase Free project in `us-east-1`; no paid service, trial, analytics, storage add-on, or custom domain is part of the deployment.
+
-## Product surfaces
+## Product overview
-- `/demo` — create a persistent queue and receive its staff code once
-- `/q/[slug]` — customer check-in, number, position, and service state
-- `/q/[slug]/staff` — capability claim and authorized queue commands
-- `/q/[slug]/display` — public-safe current and upcoming numbers
-- `/` and `/about` — product and project context
+Next gives customers, staff, and a public display one synchronized answer to three questions: who is waiting, who is being served, and who is next. It replaces paper lists and shouted names with an intentionally small workflow that works without permanent accounts, contact details, tracking, or paid infrastructure.
-## Identity and privacy
+The v1.0 product includes:
-Supabase anonymous sign-in creates a unique user UUID without email, phone, password, social identity, address, or demographics. That user uses PostgreSQL's `authenticated` role; it is different from the public publishable key and the unauthenticated `anon` database role. The session is cookie-backed through `@supabase/ssr` and normally survives refreshes and same-profile tabs. Clearing site data, using another device, or signing out loses the anonymous identity.
+- anonymous queue creation with a one-time staff capability
+- customer check-in, stable queue number, position, and turn status
+- a staff board for call, complete, skip, pause, reopen, and close commands
+- a distance-readable public display containing queue numbers only
+- persistent PostgreSQL state and multi-client Realtime convergence
+- responsive light/dark presentation, reduced motion, and accessible status feedback
-Optional customer names live in `queue_entry_private`. Public Realtime tables and public snapshots contain number labels only. No tracking, analytics, advertising, or visitor profiling is installed.
+## Live demo
-## Local requirements
+Open the [production demo](https://next-queue-omega.vercel.app/demo), create a synthetic queue, and save the one-time staff code privately. The creator is authorized immediately. Open customer, staff, and display routes in separate tabs or browser profiles to see changes converge without refresh.
-- Node.js 22
-- npm
-- Docker Desktop or another Docker-compatible runtime with at least 7 GB available
+Use synthetic information only. An optional customer first name is visible only to authorized staff. Clearing browser data loses that anonymous identity, and a lost staff code cannot be recovered through the product.
-```bash
-npm install
-npm run db:start
-npm run db:reset
+## Screenshots
+
+| Queue creation | Customer status |
+| ------------------------------------------------------------------------ | --------------------------------------------------------------------- |
+|  |  |
+
+| Staff board | Public display |
+| ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
+|  |  |
+
+| Mobile | Dark theme |
+| ------------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
+|  |  |
+
+All portfolio media was captured from the final production build with a temporary synthetic queue. The queue, related rows, anonymous identities, and one-time capability were removed after capture.
+
+## Architecture
+
+Next.js serves the interface from Vercel. The browser uses only a Supabase project URL and publishable key; authorization and state transitions remain inside PostgreSQL.
+
+```mermaid
+flowchart LR
+ subgraph Clients["Browser clients"]
+ Customer["Customer"]
+ Staff["Staff"]
+ Display["Public display"]
+ end
+
+ Vercel["Vercel Hobby Next.js 16"]
+
+ subgraph Supabase["Supabase Free · us-east-1"]
+ Auth["Anonymous Auth"]
+ API["Data API + RPC"]
+ DB[("PostgreSQL RLS + constraints")]
+ RT["Filtered Realtime queues + queue_entries"]
+ end
+
+ Vercel -->|"serves UI"| Clients
+ Clients -->|"public configuration"| Auth
+ Clients -->|"authenticated commands / snapshots"| API
+ API --> DB
+ DB -->|"committed public changes"| RT
+ RT -->|"invalidation"| Clients
```
-Copy `.env.example` to `.env.local`, then use the local API URL and **publishable** key reported by the CLI. Do not place a secret/service-role key in the browser environment.
+The database has eight application tables. Public state lives in `queues` and `queue_entries`; private names, ownership, staff membership, access hashes, rate-limit attempts, idempotency receipts, and events remain outside the Realtime publication. See the [data model](docs/architecture/data-model.md) and [architecture decision record](docs/architecture/adr-002-supabase-realtime.md).
+
+## Data flow
+
+Customer, staff, and display clients subscribe independently. Realtime messages are treated as invalidations, not authoritative state: every signal produces a fresh revisioned snapshot.
+
+```mermaid
+sequenceDiagram
+ participant Staff
+ participant RPC as PostgreSQL RPC
+ participant RT as Supabase Realtime
+ participant Customer
+ participant Display
+
+ Staff->>RPC: call_next(request_id)
+ activate RPC
+ RPC->>RPC: authorize, lock, transition, increment revision
+ RPC-->>Staff: authoritative snapshot
+ deactivate RPC
+ RPC-->>RT: committed queues / queue_entries changes
+ par Customer refreshes
+ RT-->>Customer: filtered invalidation
+ Customer->>RPC: get_queue_snapshot(slug)
+ RPC-->>Customer: latest revision
+ and Staff confirms
+ RT-->>Staff: filtered invalidation
+ Staff->>RPC: get_queue_snapshot(slug)
+ RPC-->>Staff: latest revision
+ and Display refreshes
+ RT-->>Display: filtered invalidation
+ Display->>RPC: get_public_queue_snapshot(slug)
+ RPC-->>Display: display-safe latest revision
+ end
+```
-```bash
-npm run dev
+## Database command flow
+
+Clients cannot write tables directly. Every mutation crosses an explicit security-definer RPC with a caller-generated request UUID.
+
+```mermaid
+flowchart TD
+ A["Client intent + request UUID"] --> B["Require auth.uid()"]
+ B --> C{"Authorized for command?"}
+ C -->|"No"| X["Typed safe error"]
+ C -->|"Yes"| D{"Receipt already exists?"}
+ D -->|"Same actor + command"| R["Return current authoritative snapshot"]
+ D -->|"Mismatched reuse"| X
+ D -->|"New request"| E["Lock queue / entry rows"]
+ E --> F["Validate domain transition"]
+ F --> G["Apply one atomic state change"]
+ G --> H["Increment monotonic queue revision"]
+ H --> I["Insert command receipt + append-only event"]
+ I --> J["Commit and return fresh snapshot"]
```
-Open [http://localhost:3000/demo](http://localhost:3000/demo). Create a queue, save the one-time staff code privately, then open the customer, staff, and display routes in separate browser contexts. The deterministic `north-star-cafe` seed is public-state visual data only and intentionally has no recoverable staff code; automated tests create isolated queues and consume their one-time code.
+## Security and privacy
-The local stack is development-only, uses default local credentials, and must not be exposed to public traffic.
+- Supabase anonymous Auth creates a browser-scoped UUID without email, phone, password, or social identity.
+- The application runtime contains no service-role key, database password, or Supabase access token.
+- All eight public-schema tables have RLS; direct writes are revoked from browser roles.
+- Staff access is a queue-scoped capability. Only a `pgcrypto` hash is stored; the raw code is returned once and never enters a URL, event, log, seed, or Realtime payload.
+- Optional names live in `queue_entry_private`. Public snapshots and displays expose number labels only.
+- Failed staff claims are throttled per anonymous identity and queue. This is basic abuse resistance, not enterprise bot protection.
+- No analytics, advertising, uploads, contact collection, or visitor profiling is enabled.
-## Live demo
+The full threat and authorization model is documented in [authorization and security](docs/security/authorization.md).
-Open the [production demo](https://next-queue-omega.vercel.app/demo), create a queue, and save the staff code when it appears: the raw code is shown once and cannot be recovered. The creator is immediately authorized. To test synchronization, open the customer, staff, and display links in separate tabs or isolated browser profiles. A second staff profile must claim access with the code. Queue state persists remotely, while staff membership and customer ownership follow each profile's anonymous session.
+## Concurrency and idempotency
-Clearing site data, signing out, or changing browsers loses that anonymous identity. Losing both the creating staff session and the one-time code means staff access cannot be recovered through the product. Do not enter sensitive or regulated personal information; a display name is optional.
+Commands execute in database transactions with queue/entry row locks. A partial unique index enforces at most one `SERVING` entry per queue, while unique `(queue_id, sequence)` and `(queue_id, number_label)` constraints prevent duplicate or reused numbers.
-## Commands
+Every intent receives a request UUID. Automatic retry reuses that UUID; the database records one command receipt, returns the existing result for a valid replay, and rejects mismatched reuse. Concurrent `call_next` requests therefore produce one winner and a typed conflict without violating the one-serving invariant.
-```bash
-npm run db:start
-npm run db:stop
-npm run db:status
-npm run db:reset
-npm run db:lint
-npm run db:test
-npm run db:types
-npm run db:types:check
-npm run test:integration
-npm run test:realtime
-npm run test:e2e
-npm run test:production
-npm run format
-npm run format:check
-npm run lint
-npm run typecheck
-npm run test
-npm run build
-npm audit
-```
+## Realtime synchronization
-`npm run test:production` is a read-only smoke check against the public URL. Set `PRODUCTION_BASE_URL` to test another deployment. `PRODUCTION_QUEUE_SLUG` optionally adds HTTP health checks for an existing synthetic queue; browser checks remain on public pages so the suite never creates an anonymous identity or mutates remote records.
+Only display-safe `queues` and `queue_entries` rows are published. Each client subscribes with a queue-ID filter, debounces related transaction messages for 75 ms, fetches a full snapshot, and ignores stale revisions. A refresh already in flight queues at most one follow-up.
-`db:reset` drops only the local database, replays every migration, and reapplies safe seed data. `db:types` regenerates `src/lib/supabase/database.types.ts`; do not edit that file manually.
+Clients resynchronize after initial subscription, browser `online`, channel recovery, and a meaningful visibility return. The visible state returns to connected only after an authoritative refresh succeeds. Healthy clients do not poll. See the [Realtime protocol](docs/architecture/realtime-protocol.md).
-## Command and authorization model
+## Testing
-Clients have no direct mutation grants. Explicit security-definer RPCs implement create, staff claim, join, call, complete, skip, pause, reopen, and close. Each validates `auth.uid()`, uses a fixed empty `search_path`, locks queue/entry rows, increments the queue revision once, records an idempotency receipt and append-only event, and returns a fresh snapshot. RLS separately limits table reads.
+The release evidence contains 131 passing automated checks without double-counting the Realtime unit subset:
-One partial unique index permits at most one `SERVING` entry per queue. `(queue_id, sequence)` and `(queue_id, number_label)` are unique, queue numbers are never reused, and position is calculated from ordered waiting rows.
+| Layer | Passing checks | What it covers |
+| ---------------------- | -------------: | ------------------------------------------------------------------------------------------------------- |
+| Vitest unit/component | 34 | transitions, UI behavior, session handling, revision convergence; includes 9 focused Realtime tests |
+| pgTAP database | 62 | RLS, grants, constraints, functions, privacy, publication, concurrency, idempotency |
+| Supabase integration | 11 | persistent commands and authorization through the public client |
+| Playwright application | 15 | end-to-end workflows and multi-client synchronization |
+| Production smoke | 9 | public health, metadata, accessibility, console cleanliness, responsive overflow; remote-data read-only |
-## Realtime and reconnection
+CI runs formatting, lint, type generation/typecheck, unit tests, production build, dependency audit, a clean local Supabase migration replay, database lint, pgTAP, generated-type verification, integration tests, and browser tests.
-Only `queues` and display-safe `queue_entries` are in `supabase_realtime`. Each surface subscribes with a queue-ID filter. A change is an invalidation signal: related messages are debounced for 75 ms, a snapshot RPC is fetched, stale revisions are ignored, and the new authoritative state replaces local state. The client resynchronizes after subscription, browser online, channel recovery, and a meaningful visibility return. Healthy connections do not poll.
+## Accessibility
-## Cost boundary
+The interface uses semantic landmarks, one page heading, labeled forms, keyboard-reachable controls, visible focus, skip links, readable connection states, and status announcements. Queue state never relies on color alone. Automated Axe checks report zero serious or critical findings across public and application routes.
-Production uses one Supabase Free project and one Vercel Hobby project only: no card, trial, compute upgrade, paid backup, PITR, log drain, paid analytics, paid storage, support plan, custom domain, or usage-based add-on. Free projects have finite database, bandwidth, build, function, and connection quotas and Supabase may pause an inactive project. No keep-alive is used to evade pausing. Re-check provider limits before relying on the service.
+Manual production QA covered keyboard-only navigation, focus retention during Realtime updates, 200% zoom, 320–1440 px layouts, a 1920×1080 public display, dark theme, and reduced motion. Details are in the [production validation report](docs/releases/story-3-production-validation.md).
-This portfolio deployment is provided as-is, without a commercial SLA. It has database-enforced authorization and basic access-attempt throttling, but not enterprise abuse protection, guaranteed recovery, or capacity for unbounded traffic.
+## Deployment
-## Production deployment
+- **Application:** Vercel Hobby, production branch `main`, Node.js 22.x
+- **Database/Auth/Realtime:** one Supabase Free project in `us-east-1`
+- **Canonical URL:** [next-queue-omega.vercel.app](https://next-queue-omega.vercel.app)
+- **Production browser variables:** `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY` only
+- **Migrations:** forward-only SQL in `supabase/migrations`, replayed from a clean database in CI
-Vercel is connected to `XonkelX/next-queue`, uses `main` as the production branch, Node.js 22.x, and the standard Next.js build. Only these Production variables are configured:
+No custom domain, paid analytics, paid storage, add-on, trial, payment method, or usage-based service is active.
-```text
-NEXT_PUBLIC_SUPABASE_URL
-NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY
+## Free-tier limitations
+
+This portfolio deployment has no commercial SLA. Supabase Free may pause an inactive project and both providers impose finite compute, database, bandwidth, build, function, and connection limits. The design is appropriate for small queues, not unbounded traffic or enterprise operations.
+
+Anonymous ownership is lost when browser data is cleared. A lost one-time staff code is unrecoverable without an already-authorized session. Automated anonymous-user retention, capability recovery/rotation, distributed rate limiting, monitoring, backups, and formal support are outside v1.0.
+
+## Local setup
+
+Requirements: Node.js 22, npm, and Docker Desktop (or a compatible Docker runtime with roughly 7 GB available).
+
+```bash
+git clone https://github.com/XonkelX/next-queue.git
+cd next-queue
+npm install
+npm run db:start
+npm run db:reset
+```
+
+Copy `.env.example` to `.env.local`, then use the local API URL and **publishable** key shown by `npm run db:status`. Never put a service-role key in a browser variable.
+
+```bash
+npm run dev
```
-Deployments are produced by pushing reviewed commits to `main`; a manual equivalent is `npx vercel deploy --prod`. The browser uses only public Supabase configuration—never a service-role key or database password. See the [Story 3 production validation](docs/releases/story-3-production-validation.md) for the release evidence and remaining risks.
+Open [http://localhost:3000/demo](http://localhost:3000/demo). The deterministic `north-star-cafe` seed contains display-safe visual data and intentionally has no recoverable staff code.
+
+Useful validation commands:
+
+```bash
+npm run format:check
+npm run lint
+npm run typecheck
+npm test
+npm run build
+npm audit
+```
+
+Database and browser validation additionally use `npm run db:test`, `npm run test:integration`, and `npm run test:e2e`. `npm run db:reset` affects the local database only.
## Documentation
-- [ADR 002](docs/architecture/adr-002-supabase-realtime.md)
+- [Product scope](docs/product/scope.md)
- [Data model](docs/architecture/data-model.md)
+- [Realtime architecture decision](docs/architecture/adr-002-supabase-realtime.md)
- [Realtime protocol](docs/architecture/realtime-protocol.md)
- [Authorization and security](docs/security/authorization.md)
- [Motion system](docs/design/motion-system.md)
-- [Product scope](docs/product/scope.md)
-- [Story 3 production validation](docs/releases/story-3-production-validation.md)
+- [Production validation](docs/releases/story-3-production-validation.md)
+- [v1.0.0 release notes](docs/releases/v1.0.0.md)
+
+## License
+
+No open-source license has been granted. The repository is presented as a portfolio project; all rights are reserved by Oniel Alejo Feliz.
diff --git a/docs/architecture/adr-002-supabase-realtime.md b/docs/architecture/adr-002-supabase-realtime.md
index a56cca7..9ce1957 100644
--- a/docs/architecture/adr-002-supabase-realtime.md
+++ b/docs/architecture/adr-002-supabase-realtime.md
@@ -1,6 +1,6 @@
# ADR 002: Supabase PostgreSQL and Realtime
-**Status:** implemented locally; remote Free validation recorded in the pull request and final Story 2 report
+**Status:** accepted and implemented in Story 2; production validated in Story 3
## Context and requirements
@@ -36,6 +36,6 @@ Every successful mutation increments `queues.revision` exactly once. Entry rows
## Free-plan constraints and risks
-As verified on 2026-07-17, [Supabase Free pricing](https://supabase.com/pricing) lists two active projects, 500 MB database size, 5 GB egress, and pausing after roughly one week of low activity. [Realtime pricing](https://supabase.com/docs/guides/realtime/pricing) and [limits](https://supabase.com/docs/guides/realtime/limits) remain quota-bound. No keep-alive, paid add-on, trial, or public deployment is part of Story 2.
+As verified on 2026-07-17, [Supabase Free pricing](https://supabase.com/pricing) listed two active projects, 500 MB database size, 5 GB egress, and pausing after roughly one week of low activity. [Realtime pricing](https://supabase.com/docs/guides/realtime/pricing) and [limits](https://supabase.com/docs/guides/realtime/limits) remain quota-bound and should be rechecked before operational use. No keep-alive, paid add-on, or trial is part of the project; Story 3 subsequently validated the public deployment on the existing Free project.
Remaining hardening includes CAPTCHA or stronger anonymous abuse protection, cleanup of abandoned anonymous identities, capability rotation/recovery, more durable distributed attempt throttling, operational monitoring, data retention policy, and reassessment of Broadcast at scale. The current throttle is five failed attempts per user/queue/15-minute window with a 15-minute block; it is deliberately modest, not commercial brute-force protection.
diff --git a/docs/architecture/realtime-evaluation.md b/docs/architecture/realtime-evaluation.md
index 05129a1..b8c7f8d 100644
--- a/docs/architecture/realtime-evaluation.md
+++ b/docs/architecture/realtime-evaluation.md
@@ -1,6 +1,6 @@
# Real-time architecture evaluation
-**Decision status:** proposed for Story 2
+**Decision status:** accepted and implemented in Story 2; production validated in Story 3
**Last verified against official documentation:** 2026-07-17
@@ -33,7 +33,7 @@ Spike **Supabase Postgres + Realtime** behind `QueueRealtimeAdapter` in Story 2.
5. Track a monotonic queue revision or `updated_at` plus stable event ordering so stale updates are discarded.
6. Exercise concurrent `call next`, network interruption, background-tab reconnect, free-project wake behavior, and quota exhaustion before any deployment decision.
-Supabase is not installed and the adapter is not implemented in Story 1. This document is a recommendation, not a claim of production synchronization.
+At the Story 1 decision point, Supabase was not installed and the adapter was not implemented. Story 2 subsequently implemented this recommendation, and Story 3 validated it in production. The comparison remains as the decision record rather than current implementation guidance.
## Cost controls
@@ -47,7 +47,7 @@ Supabase is not installed and the adapter is not implemented in Story 1. This do
## Rejected assumptions and unresolved risks
- Free tiers and beta capabilities change; the figures above are dated evidence, not a permanent guarantee.
-- Supabase hosted Free projects may have wake/suspension behavior that harms an always-ready service counter. Current behavior must be measured in the Story 2 spike.
+- Supabase hosted Free projects may have wake/suspension behavior that harms an always-ready service counter. Story 3 observed and documented the expected inactive-project pause and restore behavior.
- Anonymous Version 1 staff control needs a safe capability model. With no authentication, a high-entropy staff capability or another narrow authorization scheme must prevent public clients from issuing staff commands without collecting personal data.
- Postgres-change delivery is not itself a durable client event log. Reconnect must always resync a snapshot, and commands need idempotency keys.
- The 200-connection free quota is sufficient for a portfolio demonstration and very small deployments, not broad production scale.
diff --git a/docs/assets/screenshots/customer-status.png b/docs/assets/screenshots/customer-status.png
new file mode 100644
index 0000000..6b70bb8
Binary files /dev/null and b/docs/assets/screenshots/customer-status.png differ
diff --git a/docs/assets/screenshots/dark-theme.png b/docs/assets/screenshots/dark-theme.png
new file mode 100644
index 0000000..05f28c7
Binary files /dev/null and b/docs/assets/screenshots/dark-theme.png differ
diff --git a/docs/assets/screenshots/landing-page.png b/docs/assets/screenshots/landing-page.png
new file mode 100644
index 0000000..70493e0
Binary files /dev/null and b/docs/assets/screenshots/landing-page.png differ
diff --git a/docs/assets/screenshots/mobile-view.png b/docs/assets/screenshots/mobile-view.png
new file mode 100644
index 0000000..233c564
Binary files /dev/null and b/docs/assets/screenshots/mobile-view.png differ
diff --git a/docs/assets/screenshots/public-display.png b/docs/assets/screenshots/public-display.png
new file mode 100644
index 0000000..00c772c
Binary files /dev/null and b/docs/assets/screenshots/public-display.png differ
diff --git a/docs/assets/screenshots/queue-creation.png b/docs/assets/screenshots/queue-creation.png
new file mode 100644
index 0000000..035a67f
Binary files /dev/null and b/docs/assets/screenshots/queue-creation.png differ
diff --git a/docs/assets/screenshots/staff-board.png b/docs/assets/screenshots/staff-board.png
new file mode 100644
index 0000000..897327a
Binary files /dev/null and b/docs/assets/screenshots/staff-board.png differ
diff --git a/docs/assets/video/next-v1-demo.mp4 b/docs/assets/video/next-v1-demo.mp4
new file mode 100644
index 0000000..039c737
Binary files /dev/null and b/docs/assets/video/next-v1-demo.mp4 differ
diff --git a/docs/design/story-4/01-home.png b/docs/design/story-4/01-home.png
new file mode 100644
index 0000000..103324b
Binary files /dev/null and b/docs/design/story-4/01-home.png differ
diff --git a/docs/design/story-4/02-demo-create.png b/docs/design/story-4/02-demo-create.png
new file mode 100644
index 0000000..350904f
Binary files /dev/null and b/docs/design/story-4/02-demo-create.png differ
diff --git a/docs/design/story-4/03-customer-join.png b/docs/design/story-4/03-customer-join.png
new file mode 100644
index 0000000..50b8bbf
Binary files /dev/null and b/docs/design/story-4/03-customer-join.png differ
diff --git a/docs/design/story-4/04-customer-status.png b/docs/design/story-4/04-customer-status.png
new file mode 100644
index 0000000..02a5857
Binary files /dev/null and b/docs/design/story-4/04-customer-status.png differ
diff --git a/docs/design/story-4/05-staff-board.png b/docs/design/story-4/05-staff-board.png
new file mode 100644
index 0000000..d15ba44
Binary files /dev/null and b/docs/design/story-4/05-staff-board.png differ
diff --git a/docs/design/story-4/06-public-display.png b/docs/design/story-4/06-public-display.png
new file mode 100644
index 0000000..fedb962
Binary files /dev/null and b/docs/design/story-4/06-public-display.png differ
diff --git a/docs/design/story-4/07-about.png b/docs/design/story-4/07-about.png
new file mode 100644
index 0000000..252d58f
Binary files /dev/null and b/docs/design/story-4/07-about.png differ
diff --git a/docs/design/story-4/README.md b/docs/design/story-4/README.md
new file mode 100644
index 0000000..ab6a6b2
--- /dev/null
+++ b/docs/design/story-4/README.md
@@ -0,0 +1,24 @@
+# Story 4 visual direction
+
+These seven boards document the approved ticket-led direction for the v1.0
+portfolio presentation. They are design references rather than screenshots of
+the running application.
+
+The system follows the selected red-ticket reference: condensed black display
+type, warm paper surfaces, cobalt information bands, visible rules, generous
+queue numerals, functional iconography, and dense but legible operational
+layouts. The same language is applied across marketing and product surfaces.
+
+| Board | View |
+| ------------------------ | ---------------------------------------- |
+| `01-home.png` | Landing page and product positioning |
+| `02-demo-create.png` | Demo and queue creation |
+| `03-customer-join.png` | Customer check-in before joining |
+| `04-customer-status.png` | Customer ticket after joining |
+| `05-staff-board.png` | Staff operations and waiting list |
+| `06-public-display.png` | Distance-readable public display |
+| `07-about.png` | Project principles and portfolio context |
+
+Production captures live under `docs/assets/screenshots/`; the silent walkthrough
+lives under `docs/assets/video/`. Keeping references and captures separate makes
+the design intent reviewable without presenting concept art as shipped UI.
diff --git a/docs/product/scope.md b/docs/product/scope.md
index c62fca2..4d0ff37 100644
--- a/docs/product/scope.md
+++ b/docs/product/scope.md
@@ -33,4 +33,4 @@ Permanent email/password or social accounts, organizations, teams, billing, subs
Version 1 is done when all three surfaces are polished and understandable, domain invariants hold under concurrent commands, connected screens synchronize and recover, accessibility and narrow responsive QA pass, free-tier operation is verified without payment information, CI is green, production security is reviewed, and documentation does not overstate capabilities.
-Story 1 satisfies the visual and domain foundation. Story 2 implements persistent PostgreSQL state, anonymous identity, transactional commands, database authorization, filtered Realtime invalidation, revisioned snapshots, reconnect convergence, and local multi-client validation. Public application deployment and production operations remain explicitly out of scope.
+Story 1 established the visual and domain foundation. Story 2 implemented persistent PostgreSQL state, anonymous identity, transactional commands, database authorization, filtered Realtime invalidation, revisioned snapshots, reconnect convergence, and multi-client validation. Story 3 completed the reviewed production deployment and remote acceptance testing. Story 4 prepares the finished system for its public v1.0 portfolio release without expanding product scope.
diff --git a/docs/releases/v1.0.0.md b/docs/releases/v1.0.0.md
new file mode 100644
index 0000000..01c5d59
--- /dev/null
+++ b/docs/releases/v1.0.0.md
@@ -0,0 +1,56 @@
+# v1.0.0 — Next Real-Time Queue System
+
+Next v1.0.0 is the first portfolio release of a production-deployed, persistent queue for small service teams. Customers, staff, and a public display stay synchronized through transactional PostgreSQL commands, filtered Supabase Realtime invalidation, and authoritative revisioned snapshots.
+
+## Highlights
+
+- Anonymous queue creation and customer check-in without permanent accounts or contact details
+- Queue-scoped one-time staff capability with database-enforced membership
+- Customer number, position, and turn status
+- Staff call, complete, skip, pause, reopen, and close workflows
+- Distance-readable public display with queue numbers only
+- Responsive light/dark UI, reduced motion, keyboard support, and accessible live status
+- Reconnect recovery and multi-client convergence without healthy-state polling
+
+## Reliability and security
+
+- All writes use explicit security-definer RPCs; browser roles have no direct table mutation grants.
+- RLS covers all eight application tables, and only `queues` and display-safe `queue_entries` are published to Realtime.
+- Row locks, state constraints, and a partial unique index preserve the one-serving invariant under concurrent commands.
+- Request UUID receipts make retries idempotent and reject mismatched reuse.
+- Staff codes are hashed, returned once, and excluded from URLs, logs, events, seeds, bundles, and retained release media.
+- Optional customer names are separated from public queue state and visible only to the owner or authorized staff.
+
+## Validation
+
+- 34 Vitest unit/component checks, including 9 focused Realtime tests
+- 62 pgTAP database checks
+- 11 Supabase integration checks
+- 15 Playwright application checks
+- 9 read-only production smoke checks
+- 131 passing automated checks in total without double-counting the focused Realtime subset
+- Formatting, lint, typecheck, production build, database reset/lint/type generation, and dependency audit pass
+- Production responsive, keyboard, focus, zoom, reduced-motion, dark-theme, privacy, reconnect, concurrency, and multi-client QA pass
+
+## Deployment
+
+- Production: https://next-queue-omega.vercel.app
+- Vercel Hobby with Node.js 22.x
+- One Supabase Free project in `us-east-1`
+- No paid service, trial, custom domain, analytics, storage add-on, or payment method
+
+## Known limitations
+
+- Free-tier projects have finite capacity and Supabase may pause after inactivity.
+- Anonymous identities cannot be recovered after browser data is cleared.
+- A lost one-time staff code cannot be recovered without an authorized session.
+- Enterprise abuse protection, monitoring, backup/recovery guarantees, automated retention, and a commercial SLA are outside v1.0.
+
+## Release checklist
+
+- [x] Story 3 production deployment merged and green
+- [x] Recruiter-facing documentation and release media prepared
+- [x] Synthetic release-capture data removed from application tables and Auth
+- [ ] Story 4 pull request reviewed and merged
+- [ ] Tag `v1.0.0` created from the final `main` commit
+- [ ] GitHub release published with these notes and the demo video attached
diff --git a/docs/security/authorization.md b/docs/security/authorization.md
index 193112b..6cd250f 100644
--- a/docs/security/authorization.md
+++ b/docs/security/authorization.md
@@ -26,4 +26,4 @@ Every exposed function revokes public/`anon` execution and grants only `authenti
## Known limitations
-Anonymous identity cannot be recovered after site-data clearing or transferred automatically to another device. Automatic cleanup of abandoned anonymous Supabase users is not built in. Capability rotation/recovery, stronger abuse prevention, operational alerting, formal retention/deletion policy, penetration testing, and a production SLA are outside Story 2. This project claims no security certification.
+Anonymous identity cannot be recovered after site-data clearing or transferred automatically to another device. Automatic cleanup of abandoned anonymous Supabase users is not built in. Capability rotation/recovery, stronger abuse prevention, operational alerting, formal retention/deletion policy, penetration testing, and a production SLA are outside v1.0. This project claims no security certification.
diff --git a/package-lock.json b/package-lock.json
index c511223..bcf264a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "next-queue",
- "version": "0.1.0",
+ "version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "next-queue",
- "version": "0.1.0",
+ "version": "1.0.0",
"dependencies": {
"@supabase/ssr": "0.12.3",
"@supabase/supabase-js": "2.110.7",
@@ -1607,9 +1607,9 @@
}
},
"node_modules/@oxc-project/types": {
- "version": "0.139.0",
- "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz",
- "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==",
+ "version": "0.142.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz",
+ "integrity": "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==",
"dev": true,
"license": "MIT",
"funding": {
@@ -1633,9 +1633,9 @@
}
},
"node_modules/@rolldown/binding-android-arm64": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz",
- "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.2.tgz",
+ "integrity": "sha512-l7x215OGvo1s52JWmR8U/DAVzEDWBCIbTm28aeJV/WDTSHgcKXaZTuBT0hJMs5NggilfJTW3clZVvd24yfKJxA==",
"cpu": [
"arm64"
],
@@ -1650,9 +1650,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz",
- "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.2.tgz",
+ "integrity": "sha512-9u9Xv6c1AJZT0FfwH5vrMG5Jjcwhc1MlyrPu0XfTqkzsmqfks2M6W/o5XwAJgVVN/jHpqqngC1WevHKKTIUtIA==",
"cpu": [
"arm64"
],
@@ -1667,9 +1667,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz",
- "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.2.tgz",
+ "integrity": "sha512-9W1mbGZAfW3oqd85bhBkmpyHCCzL1TeG/zFFP3vg7b0rlly8cxOcre5nXwz+LHazCwac2MNWgPdPCHndABjpWQ==",
"cpu": [
"x64"
],
@@ -1684,9 +1684,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz",
- "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.2.tgz",
+ "integrity": "sha512-0p1lhiCSCyaerFwtrdZQUx7NqGk6LQnaRKWX7tFQqwQgvX0rjM15cIkm3pax1UpEakK14C4mOxx/jSqCBdBRqQ==",
"cpu": [
"x64"
],
@@ -1701,9 +1701,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz",
- "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.2.tgz",
+ "integrity": "sha512-e+cOJXrJ2L3zx6YzqPg+f6Wbk3V1cKB8bOhbaYdVYN3DdquzNdRAmrbETz1qnt5yp/c7JNlNjmITiA2cVneQ7w==",
"cpu": [
"arm"
],
@@ -1718,9 +1718,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz",
- "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.2.tgz",
+ "integrity": "sha512-JsSMsj6sNat/MuhG5fnBD7QgbtpHKVe30x5/bAVirDHdhoQRXJkF6xc0Jqk8O4fiCUQAzMOoH9wZi3m60c8wtg==",
"cpu": [
"arm64"
],
@@ -1735,9 +1735,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz",
- "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.2.tgz",
+ "integrity": "sha512-B5G/zJdHaoJn9vD50eGHWkiWfmq8Uhi3IiLPJTzmZTrAalk1bztUikSXo0qga18ibE0IXboyeMUnhPjhAJ45wQ==",
"cpu": [
"arm64"
],
@@ -1752,9 +1752,9 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz",
- "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.2.tgz",
+ "integrity": "sha512-6mC/awzKka8W6EoekjegpfGkjz8jXWDX63pqu/HYVpyKtZfu65Jsh4QAH3Kej3CAv/c1oGX7psTmFEbr0mDxLA==",
"cpu": [
"ppc64"
],
@@ -1769,9 +1769,9 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz",
- "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.2.tgz",
+ "integrity": "sha512-412MX9fJLdA1IK28EZnc8jYv2HRTleOZgfLQumJ5zy7OeJLZlg/CETwFaXjNmGVxG51cFHpKLqb5LKvBC+HsHA==",
"cpu": [
"s390x"
],
@@ -1786,9 +1786,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz",
- "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.2.tgz",
+ "integrity": "sha512-Q/+HI/ToJafZ1iCqGgVQXUEkIjufHCTF0gBQ2a5o3cg7GJ2h0qyq3nvvSmU+bGda2/7ygXpTY4TM6gO9OhQ0ZA==",
"cpu": [
"x64"
],
@@ -1803,9 +1803,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz",
- "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.2.tgz",
+ "integrity": "sha512-ZKp/w41n6wCvxzxQHtQSbuphfX3Y4cCvbjkKHusrLx4lh+JWLTU7StSltO/DKARISzbj368d+qUaCjI8K2wzXw==",
"cpu": [
"x64"
],
@@ -1820,9 +1820,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz",
- "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.2.tgz",
+ "integrity": "sha512-pxE6xD4KS3eAROkKK5yrhB9/3+vhlhVGMvlQLbdpzrBGDbKrnzx3RLwPaHvasLo6jgaiBLn7e04Df9C4tYhjmA==",
"cpu": [
"arm64"
],
@@ -1836,63 +1836,10 @@
"node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@rolldown/binding-wasm32-wasi": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz",
- "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==",
- "cpu": [
- "wasm32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/core": "1.11.1",
- "@emnapi/runtime": "1.11.1",
- "@napi-rs/wasm-runtime": "^1.1.6"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
- "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/wasi-threads": "1.2.2",
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
- "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
- "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
"node_modules/@rolldown/binding-win32-arm64-msvc": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz",
- "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.2.tgz",
+ "integrity": "sha512-4MqEue5re+xIZzAWsB8sj0P1kqZySWqIuN4t6QaIO/YA6SFwySOLruvWQFzfmqk8LBK2P30KCSJwf6mJCZZ5/A==",
"cpu": [
"arm64"
],
@@ -1907,9 +1854,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz",
- "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.2.tgz",
+ "integrity": "sha512-NweNxxD0Nf9t8v7kodun45Ijp3EIwYY+uydPP6qBEYvfBqhIjN6dZMzlQja3tqX/aLs3F3Uz+AxDpKgRhpOZQg==",
"cpu": [
"x64"
],
@@ -3667,9 +3614,9 @@
}
},
"node_modules/brace-expansion": {
- "version": "5.0.8",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
- "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
+ "version": "5.0.9",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
+ "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7036,9 +6983,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.19",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz",
- "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==",
+ "version": "8.5.23",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz",
+ "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==",
"funding": [
{
"type": "opencollective",
@@ -7055,7 +7002,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.12",
+ "nanoid": "^3.3.16",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -7322,13 +7269,13 @@
}
},
"node_modules/rolldown": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
- "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.2.tgz",
+ "integrity": "sha512-opwpo1tQBAcpSUJDt94B7hhLNGOKjCdE//XXjeLrnx9b83bjnw45tXdg1b09yEw/VLFBJGZpwRULMmOZo7ol+A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@oxc-project/types": "=0.139.0",
+ "@oxc-project/types": "=0.142.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
@@ -7338,21 +7285,20 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
- "@rolldown/binding-android-arm64": "1.1.5",
- "@rolldown/binding-darwin-arm64": "1.1.5",
- "@rolldown/binding-darwin-x64": "1.1.5",
- "@rolldown/binding-freebsd-x64": "1.1.5",
- "@rolldown/binding-linux-arm-gnueabihf": "1.1.5",
- "@rolldown/binding-linux-arm64-gnu": "1.1.5",
- "@rolldown/binding-linux-arm64-musl": "1.1.5",
- "@rolldown/binding-linux-ppc64-gnu": "1.1.5",
- "@rolldown/binding-linux-s390x-gnu": "1.1.5",
- "@rolldown/binding-linux-x64-gnu": "1.1.5",
- "@rolldown/binding-linux-x64-musl": "1.1.5",
- "@rolldown/binding-openharmony-arm64": "1.1.5",
- "@rolldown/binding-wasm32-wasi": "1.1.5",
- "@rolldown/binding-win32-arm64-msvc": "1.1.5",
- "@rolldown/binding-win32-x64-msvc": "1.1.5"
+ "@rolldown/binding-android-arm64": "1.2.2",
+ "@rolldown/binding-darwin-arm64": "1.2.2",
+ "@rolldown/binding-darwin-x64": "1.2.2",
+ "@rolldown/binding-freebsd-x64": "1.2.2",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.2",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.2",
+ "@rolldown/binding-linux-arm64-musl": "1.2.2",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.2",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.2",
+ "@rolldown/binding-linux-x64-gnu": "1.2.2",
+ "@rolldown/binding-linux-x64-musl": "1.2.2",
+ "@rolldown/binding-openharmony-arm64": "1.2.2",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.2",
+ "@rolldown/binding-win32-x64-msvc": "1.2.2"
}
},
"node_modules/run-parallel": {
@@ -8304,9 +8250,9 @@
}
},
"node_modules/undici": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz",
- "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
+ "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -8400,16 +8346,16 @@
}
},
"node_modules/vite": {
- "version": "8.1.5",
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz",
- "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==",
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.0.tgz",
+ "integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "lightningcss": "^1.32.0",
+ "lightningcss": "^1.33.0",
"picomatch": "^4.0.5",
- "postcss": "^8.5.17",
- "rolldown": "~1.1.5",
+ "postcss": "^8.5.23",
+ "rolldown": "~1.2.0",
"tinyglobby": "^0.2.17"
},
"bin": {
@@ -8426,7 +8372,7 @@
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
- "@vitejs/devtools": "^0.3.0",
+ "@vitejs/devtools": "^0.4.0",
"esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
@@ -8492,6 +8438,267 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/vite/node_modules/lightningcss": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
+ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.33.0",
+ "lightningcss-darwin-arm64": "1.33.0",
+ "lightningcss-darwin-x64": "1.33.0",
+ "lightningcss-freebsd-x64": "1.33.0",
+ "lightningcss-linux-arm-gnueabihf": "1.33.0",
+ "lightningcss-linux-arm64-gnu": "1.33.0",
+ "lightningcss-linux-arm64-musl": "1.33.0",
+ "lightningcss-linux-x64-gnu": "1.33.0",
+ "lightningcss-linux-x64-musl": "1.33.0",
+ "lightningcss-win32-arm64-msvc": "1.33.0",
+ "lightningcss-win32-x64-msvc": "1.33.0"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-android-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
+ "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-darwin-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
+ "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-darwin-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
+ "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-freebsd-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
+ "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
+ "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
+ "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
+ "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
+ "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
+ "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
+ "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
+ "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
"node_modules/vite/node_modules/picomatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
diff --git a/package.json b/package.json
index ca49acf..ee72d29 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "next-queue",
- "version": "0.1.0",
+ "version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev",
@@ -58,7 +58,7 @@
},
"overrides": {
"minimatch": "10.2.5",
- "postcss": "8.5.19",
+ "postcss": "8.5.23",
"sharp": "0.35.3"
}
}
diff --git a/public/images/lifestyle/cafe-owner.webp b/public/images/lifestyle/cafe-owner.webp
new file mode 100644
index 0000000..5376ca5
Binary files /dev/null and b/public/images/lifestyle/cafe-owner.webp differ
diff --git a/public/images/lifestyle/public-display.webp b/public/images/lifestyle/public-display.webp
new file mode 100644
index 0000000..3e0a5d0
Binary files /dev/null and b/public/images/lifestyle/public-display.webp differ
diff --git a/public/images/lifestyle/service-handoff.webp b/public/images/lifestyle/service-handoff.webp
new file mode 100644
index 0000000..17dcca4
Binary files /dev/null and b/public/images/lifestyle/service-handoff.webp differ
diff --git a/public/images/lifestyle/staff-workflow.webp b/public/images/lifestyle/staff-workflow.webp
new file mode 100644
index 0000000..7c9f8ce
Binary files /dev/null and b/public/images/lifestyle/staff-workflow.webp differ
diff --git a/public/images/lifestyle/wait-anywhere.webp b/public/images/lifestyle/wait-anywhere.webp
new file mode 100644
index 0000000..d0cc080
Binary files /dev/null and b/public/images/lifestyle/wait-anywhere.webp differ
diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx
index a7db824..a8e48e0 100644
--- a/src/app/about/page.tsx
+++ b/src/app/about/page.tsx
@@ -1,59 +1,95 @@
+import {
+ Accessibility,
+ BadgeDollarSign,
+ Network,
+ ShieldCheck,
+} from 'lucide-react';
+import Image from 'next/image';
import { SiteFooter } from '@/components/site-footer';
import { productConfig } from '@/config/product';
+const principles = [
+ {
+ eyebrow: 'Privacy',
+ title: 'Less data is better.',
+ icon: ShieldCheck,
+ description:
+ 'No email, phone number, password, analytics, advertising, or profiling. Optional first names stay private.',
+ },
+ {
+ eyebrow: 'Technology',
+ title: 'One synchronized system.',
+ icon: Network,
+ description:
+ 'Next.js, PostgreSQL transactions, Row Level Security, and Realtime keep every view aligned.',
+ },
+ {
+ eyebrow: 'Accessibility',
+ title: 'Clarity in every mode.',
+ icon: Accessibility,
+ description:
+ 'Keyboard operation, high contrast, large targets, calm announcements, and reduced-motion alternatives.',
+ },
+ {
+ eyebrow: 'Cost',
+ title: 'Designed to remain $0.',
+ icon: BadgeDollarSign,
+ description:
+ 'Built and validated within practical free-tier limits, without paid add-ons or usage-based billing.',
+ },
+];
+
export default function AboutPage() {
return (
<>
-
+
About the project
-
{productConfig.statement}
+
Clear queues. Less friction.
- Next is a focused portfolio project exploring how thoughtful
- interface design can make a small, time-sensitive service system
- feel clear and humane.
+ {productConfig.statement} Next explores how thoughtful interface
+ design can make a small, time-sensitive service system feel clear
+ and humane.
-
-
-
Privacy
-
Less data is better.
+
+
+
+
+
+
Made for real service
+
Technology should support the room.
- Anonymous browser identities require no email, phone number,
- address, password, analytics, advertising, or profiling. A first
- name is optional, isolated from public queue state, and visible
- only to its owner and authorized staff.
+ NEXT keeps the operational work visible and the interface quiet,
+ so small teams can focus on the person in front of them.
-
-
-
Technology
-
Built around one small domain.
-
- Next.js, strict TypeScript, Supabase PostgreSQL, transactional RPC
- commands, Row Level Security, Realtime invalidation, Motion,
- Vitest, pgTAP, and Playwright form the engineering foundation.
-
-
-
-
Accessibility
-
Clarity in every mode.
-
- Semantic landmarks, visible focus, keyboard operation, high
- contrast, stable queue numerals, restrained live announcements,
- large touch targets, and reduced-motion alternatives are product
- requirements.
-
-
-
-
Cost
-
Designed to remain $0.
-
- The local stack uses Docker and the Supabase CLI. Hosted
- validation is limited to one Free project with no trial, payment
- method, paid add-on, analytics, or application deployment.
-
+
+ Quiet technology. Clear progress.
+
>
diff --git a/src/app/demo/page.tsx b/src/app/demo/page.tsx
index 35cf7de..7d33b74 100644
--- a/src/app/demo/page.tsx
+++ b/src/app/demo/page.tsx
@@ -1,4 +1,14 @@
-import { ArrowRight, Monitor, Smartphone, Users } from 'lucide-react';
+import {
+ ArrowRight,
+ Eye,
+ Monitor,
+ Radio,
+ ShieldCheck,
+ Smartphone,
+ Users,
+ Wifi,
+} from 'lucide-react';
+import Image from 'next/image';
import Link from 'next/link';
import { demoRoutes } from '@/config/product';
import { CreateQueuePanel } from '@/features/queue/create-queue-panel';
@@ -7,21 +17,26 @@ export const dynamic = 'force-dynamic';
const demos = [
{
- title: 'Customer',
- description: 'Join the queue and see a personal ticket and position.',
+ index: '01',
+ title: 'Customer ticket',
+ description: 'Join the seeded sample queue and see a personal ticket.',
+ action: 'Open customer view',
href: demoRoutes.customer,
icon: Smartphone,
},
{
- title: 'Staff',
- description:
- 'Claim access, then call, complete, skip, pause, reopen, or close.',
+ index: '02',
+ title: 'Staff board',
+ description: 'Call, complete, pause, and reopen the seeded sample queue.',
+ action: 'Open staff view',
href: demoRoutes.staff,
icon: Users,
},
{
- title: 'Display',
- description: 'View the distance-readable public service board.',
+ index: '03',
+ title: 'Public display',
+ description: 'View the seeded sample queue on the public service board.',
+ action: 'Open display view',
href: demoRoutes.display,
icon: Monitor,
},
@@ -29,43 +44,79 @@ const demos = [
export default function DemoPage() {
return (
-
+
-
Story 2 persistent prototype
-
See the queue from every side.
+
Live product demo
+
One queue. Three points of view.
- Create a persistent queue, then open each synchronized surface in a
- separate browser context.
+ Step into a working service flow as the customer, the team, or the
+ room—or issue a private queue of your own.