Build
loci and Qloo: architecture
Overview
- App: Next.js 16 App Router, React 19 server components with small client islands, TypeScript, Tailwind CSS 4, Motion.
- Data: PostgreSQL 18 (
loci_care). All queries insrc/lib/server/repo.ts, access re-checked per request. - Auth: email and password with Argon2, random session tokens stored as SHA-256 hashes, HttpOnly cookie.
- Hosting: pm2 process behind nginx with a Let's Encrypt certificate.
- Tests: Vitest unit tests for the pure logic; Playwright end-to-end at 1440 and 390 against a mock Qloo, failing on console errors or horizontal overflow.
The Qloo layer
favorites (entity IDs) + city point + ticked tags
|
v
comfort.ts -- buildComfort(transport, input)
|-- mediaFor(artist | tv_show + movie | book + podcast) POST /v2/insights, weighted signals, explainability
|-- placesFor(restaurants, tags intersection, radius) GET /v2/insights, filter.location + radius, widening
|-- tagAffinity(cuisine) GET /v2/insights, filter.type=urn:tag
|-- placesFor(park | garden | museum | library | cafe) GET /v2/insights, one per kind
|
v
normalize -> Pick { name, image, because[], affinity, tags }
|
+--> goodday.ts composeGoodDay(plan doses + appointments + picks)
+--> goodday.ts composeRituals(picks) -> welcome card
+--> trace[] shown on /app/comfort
- Transport (
qloo-http.ts): concurrency 4, retry with backoff on 429 and 5xx, Postgres cache keyed by request hash for 24 hours. Raw payloads never leave the server. - Filters (
filters.ts): each maps to a real Qloo tag (dietary options, delivery, takeout, soup, wheelchair-accessible entrance, quiet) or a local rule (skip bars, family-rated content). - Isolation: the comfort module receives only favorites, city and filters. The rule engine and dashboard never read taste data.
Rule engine
rules.ts evaluates today's check-in against the plan: ticked warning signs, and thresholds {metric, gte|lte, value, days, action} where multi-day thresholds need a check-in on each previous day. Output is a list of flags with the clinician's words, emergency first. Saved with the check-in; re-saving replaces open flags.
Two sides
- Clinician workspace
src/app/clinic/(ws)with its own layout (requireClinician), and auth pages insrc/app/clinic/(auth). Patient appsrc/app/app/(shell). - A clinician creates a journey with
org_id,plan_source = 'clinician'andconsent_org = true(a "team plan").sendPlansetsplan_sent_atand issues an 8-character single-use code (no look-alike characters) valid 30 days. - On redeem the first patient or caregiver becomes the owner, chooses whether the team can see progress (on by default), and
plan_seen_atstarts. - Every plan edit by the team on a sent plan writes a
plan_updateactivity row andplan_updated_at; the patient sees updates newer than theirplan_seen_at. messages(side patient or team, kind message or question): a team reply resolves open questions.- Both sides poll with
router.refresh()every few seconds while visible, so the other side's actions show up live.
Access model
Circle roles (patient, caregiver, family, carer, clinician) decide who edits, checks in or invites. Org clinicians see a journey only while the patient's consent_org switch is on. On team plans only the care team edits the plan; the patient follows it.
Demo
Two real, short-lived accounts paired with users.demo_pair: the patient (Daniel, orthopedics, New York) and Dr. Maya Chen, admin of the demo clinic with nine journeys across nine specialties and eight cities, two weeks of history generated by the same rule engine, seeded messages and one plan update. "See it as a clinician" and "See it as a patient" start the same world on either side, and the banner switches sessions between the pair. Every flow in the demo is the production code path.