Current Web App¶
This page documents the consumer web app as it exists now. It is an implementation guide, not a second domain model. The canonical vocabulary and rules remain in the Domain Definition, and architecture-significant decisions remain in the ADRs.
Purpose¶
The SvelteKit app is the consumer-facing entry point for PackyTrace. Its current experience is:
The app is real-first: by default it calls the API gateway. An isolated in-memory adapter remains available for offline development and demo fallback.
Run It¶
Start the full platform and frontend:
make up
make web
The frontend runs at http://localhost:5173 and calls the gateway at
http://localhost:8080 by default.
Run only the frontend against the in-memory fixture catalog:
PUBLIC_SCAN_ADAPTER=memory make web
Useful frontend checks:
cd client/web-app
npm run check
npm run build
The public build-time settings are documented in client/web-app/.env.example:
| Setting | Default | Purpose |
|---|---|---|
PUBLIC_API_BASE_URL |
http://localhost:8080 |
API gateway origin |
PUBLIC_SCAN_ADAPTER |
http |
Selects http or memory scan adapter |
Current Routes¶
| Route | Current responsibility | Data source |
|---|---|---|
/ |
Manual GTIN lookup, recent scans, Compare entry point | Browser state |
/product/[id] |
Resolve and render a product passport; report unknown GTINs; add to Fridge | ScanGateway plus browser state |
/compare |
Select two or three recent fixture products and compare facts | In-memory fixture catalog |
/fridge |
View, recycle, or remove saved products | Browser localStorage |
/profile |
Show guest/mock-account state and open Health Profile | Browser state |
/preferences |
Edit Health Profile; currently mock-auth gated | Browser localStorage |
/setup |
Health Profile onboarding and explicit opt-in | Browser localStorage |
/auth |
Mock sign-in/register interaction | In-memory boolean store |
Navigation is intentionally scan-first: Scan, Fridge, and Profile. Mobile uses a bottom navigation bar; desktop uses a sidebar.
Scan Architecture¶
The UI depends on one frontend port instead of directly depending on HTTP or fixture data:
Relevant files:
| Path | Role |
|---|---|
src/lib/scan/port.ts |
Defines ScanGateway.resolve and ScanGateway.report |
src/lib/scan/index.ts |
Composition root; selects the adapter once |
src/lib/scan/http.adapter.ts |
Calls the real gateway; maps HTTP 404 to unknown product |
src/lib/scan/memory.adapter.ts |
Isolated fixture implementation and temporary mock Verdict |
src/lib/api/client.ts |
Adds Visitor ID, credentials, base URL, and normalized API errors |
src/lib/api/mappers.ts |
Anti-corruption layer from wire ResolvedPassport to UI types |
Manual GTIN flow¶
The landing page uses manual GTIN entry because camera capture is not implemented yet. This is a truthful temporary affordance and exercises the real scan path:
- The user enters an 8–14 digit GTIN.
- The app navigates to
/product/{gtin}. - The product route calls
scanGateway.resolve({ gtin }). - The HTTP adapter sends
POST /api/v1/scans. - The mapper converts the gateway response into the current UI model.
- The resolved product is recorded in the lightweight recent-scans cache.
An unknown GTIN renders the report flow. Reporting calls
POST /api/v1/products/{gtin}/requests through ScanGateway.report.
Visitor identity¶
src/lib/stores/visitor.ts creates a pseudonymous UUID on first API use and caches it
in localStorage. The API client sends it as X-Visitor-Id. This is ADR-004 visitor
identity, not authentication. The client also sends credentials so a gateway-set
visitor cookie can participate in same-origin or credentialed-CORS flows.
Product Result¶
The product page is the primary result surface:
- A personal Verdict hero appears first. Until the real personalization journey lands,
anonymous or absent Verdicts display as
Unknown, never silentlyGood. - Overview contains product identity, Digital Label, nutrition, and allergens.
- Journey and Eco are separate sections and can degrade independently.
- Unknown products can be reported.
- A resolved product can be added to the local Fridge.
- Product images use
ProductImage.svelte, which shows a neutral fallback when the real catalog image key has no bundled asset.
The current ScanResponse and Product interfaces are compatibility-oriented UI
models. They are not backend aggregates and must not override the canonical domain.
Browser State¶
Some journeys are intentionally browser-backed until their real backend phases land:
| Store | Persistence | Current limitation |
|---|---|---|
visitor.ts |
localStorage |
Pseudonymous ID cache only; not authentication |
recentScans.ts |
localStorage |
Lightweight UI history, not server ScanRecord |
healthProfile.ts |
localStorage |
Temporary profile and consent prototype |
fridge.ts |
localStorage |
Deduplicates by product ID; not physical-item identity |
auth.ts |
Memory only | Boolean mock session; Keycloak integration is not present |
Do not treat these stores as authoritative persistence. The backend-owned versions replace them as J2–J4 are completed.
Compare¶
Compare is currently a polished mock-only decision flow:
- It starts from recent scans when at least two match fixture products.
- The user selects two or three products.
- It highlights field-level differences such as eco score and listed sugar.
- It groups detailed facts into overview, nutrition, and sustainability.
- It does not claim an overall health winner.
Compare must remain fixture-backed until a real read model and owning backend boundary are decided. A future AI-generated comparison brief must be grounded in displayed structured facts and must never replace the deterministic personal Verdict.
UI Structure¶
client/web-app/src/
├── app.css semantic tokens and shared UI primitive classes
├── lib/
│ ├── api/ low-level HTTP client and response mapper
│ ├── scan/ ScanGateway port, composition root, adapters
│ ├── components/
│ │ ├── layout/ responsive app shell and navigation
│ │ └── product/ product-result presentation components
│ ├── data/ fixture catalog used only by the memory adapter/Compare
│ ├── stores/ temporary browser and mock state
│ ├── types/ compatibility-oriented UI types
│ └── utils/ class merging, expiry helpers, legacy GS1 hint
└── routes/ SvelteKit route components
Component-local state uses Svelte 5 runes. Shared reactive state uses Svelte stores.
Visual Language¶
The app uses Tailwind CSS with semantic HSL tokens defined in src/app.css.
The small shared primitive layer keeps high-traffic surfaces coherent:
| Class | Use |
|---|---|
.ui-button-primary |
Main action on a screen |
.ui-button-secondary |
Secondary or alternative action |
.ui-button-icon |
Back and compact icon actions |
.ui-card |
Static grouped content |
.ui-card-interactive |
Clickable product or option cards |
.ui-input |
Text and GTIN entry |
Use semantic colors such as primary, accent, success, warning, and
destructive; do not add one-off hard-coded colors. Prefer rounded-xl for controls,
rounded-2xl for content cards, and rounded-full only for pills, avatars, and
circular status indicators. Focus-visible and reduced-motion behavior are defined
globally.
Known Limitations¶
- Camera scanning is not implemented; manual GTIN entry is the current scan action.
- Authentication is mocked; Keycloak-backed identity arrives in J2.
- The real personal Verdict and backend Health Profile arrive in J3.
- Fridge persistence and physical-item identity arrive in J4.
- Compare is fixture-backed and has no backend read model.
- Journey and Eco data can be absent or degraded in real mode.
- Reviews remain in compatibility types/fixtures but are not part of the current product-result navigation.
Rules for New Frontend Work¶
- Keep routes and components dependent on ports, not concrete infrastructure.
- Add external response translation in an adapter or mapper, not in a component.
- Keep browser stores explicitly temporary; do not present them as backend truth.
- Preserve anonymous scan access and never treat Visitor ID as authentication.
- Render missing data honestly; do not invent sustainability, health, or origin facts.
- Use the shared UI primitives and semantic tokens before introducing new styles.
- Update this page whenever the shipped frontend behavior or structure changes.