Nexo P0 — dating client bootstrap: types, API client, app factory, shell
Phase 0 of the Nexo demo (`src/web/routes/dating/plan-implementacion.md`).
The standalone server lives at `servers/dating` and is already running;
this commit lands the SvelteKit client that consumes it.
`_lib/types.ts` — public contracts mirroring the `*View` functions in
`servers/dating/domain.mjs`: `DatingUser`, `DatingProfile`,
`DatingMatch`, `DatingMessage`, `DatingReport` plus enums
(`DatingRole`, `DatingStatus`, `DatingIntent`, …) and the structured
event names (`DATING_EVENTS.AUTH_LOGIN`, …) the bus and orca will
publish later. Request/response payloads are split into dedicated
interfaces (`DatingRegisterInput`, `DatingDiscoverFilters`,
`DatingLikeResponse`, …) so the API client doesn't grow ad-hoc shapes.
`_lib/api.ts` — typed wrapper over the standalone server. Always
sends `credentials: 'include'` (the dating session is an HTTP-only
`dating_session` cookie) and normalises error responses into a
`DatingApiError` carrying the server's structured `code` (e.g.
`weak_password`, `email_already_used`) so UI branches on a stable
identifier instead of message strings. Network / abort failures
surface as the same class with `code: 'network_error'`. The factory
takes `fetch` + `signal` overrides for SSR (`event.fetch`) and tests.
`_lib/app.ts` — `createDatingApp()` opinionated `createActiveApp`
composition that fixes the service schema (lang, prefs, frontend,
storage, format, cache, http, session, sium) so consumer components
can type their `App` prop as `DatingApp` and get autocomplete on
every slot. `auth`, `perm`, and `connection` are intentionally not
in this commit — they need port-level wiring (an
`AuthClientHttpPort` against the Nexo endpoints, a perm endpoint,
the connection transport) that belongs to Phases 2 / 4. The factory
returns `{ App, api, dispose }` so callers don't have to compose
the App and the HTTP client separately.
`_lib/context.ts` — symbol-keyed `setNexoApp` / `getNexoApp` bridge
so the layout sets the handle once and nested pages retrieve it
without rebuilding `createActiveApp`.
`+layout.ts` — `prerender = false` for the entire `/dating` subtree.
The demo authenticates via cookies against a runtime-only server,
so static prerender doesn't make sense.
`+layout.svelte` — Nexo shell: instantiates `DatingApp` once, wires
the frontend `target`, eagerly probes `/api/session`, renders the
top nav (Discover, Matches, Profile, Safety, Devtools) with auth
state on the right, and shows an offline banner pointing to
`npm run dating:server` when the API can't be reached. Disposes
the App on unmount.
`+page.svelte` — landing card grid that links every route the plan
will materialise (Auth & Session / Profile / Discover & Match /
Safety & Moderación / Diagnóstico). Plain hrefs (not the typed
`resolve(...)`) so the page compiles ahead of the targets being
created — the links 404 until each phase lands its `+page.svelte`.
Gates: 1695 tests + check (0 errors) + build + bundle smoke
(22.52 KB gzip) + aliases — all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
/**
|
Nexo P1+P2 — user-facing surface: auth, onboarding, discover, matches, chat, profile
Builds the actual product the user sees, on top of the P0 bootstrap.
The previous landing was a phase-card grid for navigating the plan;
that's been replaced with a real auth-aware router and the routes
the cards used to point at now exist as functional pages.
Layout / context (`_lib/context.ts`, `+layout.svelte`):
- Layout owns the session cell. `getNexoContext()` exposes
`session()` / `setSession()` / `refreshSession()` so pages don't
each issue their own `/api/session` and so a successful
`login` / `register` propagates the user to every gate without
a round-trip.
- Logout clears the cell even when `api.logout()` rejects, so the
UI never gets stuck on "authenticated" after a server hiccup.
Landing (`+page.svelte`):
- Pure router: anonymous → `/dating/login`, authenticated without a
completed profile → `/dating/onboarding`, otherwise →
`/dating/discover`. Renders a minimal pulse during the initial
session probe.
Auth (`login`, `register`, `reset`):
- `AuthLayout` + `Field` shared shell so every form has the same
centered card, focus ring and per-field error wiring.
- Login maps server error codes (`invalid_credentials`,
`account_restricted`, `mfa_required`) to localised messages and
redirects to `/dating/onboarding` when the user has no profile yet.
- Register validates locally (display name, email shape, ≥8-char
password, password match, adult + rules confirm) and surfaces
server codes (`email_already_used`, `weak_password`,
`password_mismatch`, `adult_confirmation_required`) on the right
field. Live password-strength meter.
- Reset is anti-enumeration: same UI whether the email exists or not
(matches the contract documented in
`auth-y-fotos.md`).
Onboarding (`/dating/onboarding`):
- Four-step flow (Identity → About → Preferences → Review) with a
progress stepper. Each step validates its own fields; clicking
"Publicar" re-runs validation across every step so the user lands
back on the broken one if anything regressed. On success the page
PUTs `/api/profile/me` with `completed: true` and refreshes the
session cell so guards downstream see the published profile.
Discover (`/dating/discover`):
- Filter sidebar (intent + min/max age) + card stack of profiles.
- `ProfileCard` component renders the canonical profile card (photo,
name + age, intent pill, location, bio, interests).
- Like / pass actions call `/api/likes`; a match opens a small modal
offering "Open chat" → `/dating/chat/{id}`.
- Auto-fetches the next page when the queue drops to two cards.
Matches (`/dating/matches`):
- List of active matches with avatar, name, last update, and the
start of the counterpart's bio. Each row links to the chat thread.
Chat (`/dating/chat/[matchId]`):
- Header with counterpart name + match state, scrollable timeline,
composer at the bottom.
- Optimistic send: the message lands as `queued` immediately, swapped
for the server response on success. On failure the bubble flips to
`failed` with retry/discard inline actions. Each provisional
message carries a `clientNonce` for idempotency on the server side.
Profile editor (`/dating/profile`):
- Full-form editor mirroring onboarding's fields plus visibility.
Tracks dirty state, surfaces server errors per-field, refreshes the
session cell after saving so the cache and Discover stay coherent.
- Header links to the photo manager.
Photo manager (`/dating/profile/photos`):
- Drop-zone + click-to-upload. Client validates format
(JPEG/PNG/WebP), 5 MB cap, and the 6-photo ceiling before sending.
Photos render in a grid with primary badge, order pills and per-photo
actions (set primary, move up/down, delete). Each mutation reuses the
API client and re-syncs the session cell.
All pages use the typed `DatingApiClient` from P0 — no ad-hoc fetch
calls — and route through `getNexoContext()` so the session is the
single source of truth across the demo.
Gates: 1695 tests + check (0 errors / 0 warnings) + build + bundle
smoke (22.52 KB gzip) + aliases — all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
* Svelte context bridge for the Nexo runtime: the `DatingAppHandle`
|
|
|
|
|
* (App + API) plus a session cell owned by the shell layout. Nested
|
|
|
|
|
* pages and components consume both via `getNexoContext()` instead of
|
|
|
|
|
* each rebuilding their own.
|
Nexo P0 — dating client bootstrap: types, API client, app factory, shell
Phase 0 of the Nexo demo (`src/web/routes/dating/plan-implementacion.md`).
The standalone server lives at `servers/dating` and is already running;
this commit lands the SvelteKit client that consumes it.
`_lib/types.ts` — public contracts mirroring the `*View` functions in
`servers/dating/domain.mjs`: `DatingUser`, `DatingProfile`,
`DatingMatch`, `DatingMessage`, `DatingReport` plus enums
(`DatingRole`, `DatingStatus`, `DatingIntent`, …) and the structured
event names (`DATING_EVENTS.AUTH_LOGIN`, …) the bus and orca will
publish later. Request/response payloads are split into dedicated
interfaces (`DatingRegisterInput`, `DatingDiscoverFilters`,
`DatingLikeResponse`, …) so the API client doesn't grow ad-hoc shapes.
`_lib/api.ts` — typed wrapper over the standalone server. Always
sends `credentials: 'include'` (the dating session is an HTTP-only
`dating_session` cookie) and normalises error responses into a
`DatingApiError` carrying the server's structured `code` (e.g.
`weak_password`, `email_already_used`) so UI branches on a stable
identifier instead of message strings. Network / abort failures
surface as the same class with `code: 'network_error'`. The factory
takes `fetch` + `signal` overrides for SSR (`event.fetch`) and tests.
`_lib/app.ts` — `createDatingApp()` opinionated `createActiveApp`
composition that fixes the service schema (lang, prefs, frontend,
storage, format, cache, http, session, sium) so consumer components
can type their `App` prop as `DatingApp` and get autocomplete on
every slot. `auth`, `perm`, and `connection` are intentionally not
in this commit — they need port-level wiring (an
`AuthClientHttpPort` against the Nexo endpoints, a perm endpoint,
the connection transport) that belongs to Phases 2 / 4. The factory
returns `{ App, api, dispose }` so callers don't have to compose
the App and the HTTP client separately.
`_lib/context.ts` — symbol-keyed `setNexoApp` / `getNexoApp` bridge
so the layout sets the handle once and nested pages retrieve it
without rebuilding `createActiveApp`.
`+layout.ts` — `prerender = false` for the entire `/dating` subtree.
The demo authenticates via cookies against a runtime-only server,
so static prerender doesn't make sense.
`+layout.svelte` — Nexo shell: instantiates `DatingApp` once, wires
the frontend `target`, eagerly probes `/api/session`, renders the
top nav (Discover, Matches, Profile, Safety, Devtools) with auth
state on the right, and shows an offline banner pointing to
`npm run dating:server` when the API can't be reached. Disposes
the App on unmount.
`+page.svelte` — landing card grid that links every route the plan
will materialise (Auth & Session / Profile / Discover & Match /
Safety & Moderación / Diagnóstico). Plain hrefs (not the typed
`resolve(...)`) so the page compiles ahead of the targets being
created — the links 404 until each phase lands its `+page.svelte`.
Gates: 1695 tests + check (0 errors) + build + bundle smoke
(22.52 KB gzip) + aliases — all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
*
|
Nexo P1+P2 — user-facing surface: auth, onboarding, discover, matches, chat, profile
Builds the actual product the user sees, on top of the P0 bootstrap.
The previous landing was a phase-card grid for navigating the plan;
that's been replaced with a real auth-aware router and the routes
the cards used to point at now exist as functional pages.
Layout / context (`_lib/context.ts`, `+layout.svelte`):
- Layout owns the session cell. `getNexoContext()` exposes
`session()` / `setSession()` / `refreshSession()` so pages don't
each issue their own `/api/session` and so a successful
`login` / `register` propagates the user to every gate without
a round-trip.
- Logout clears the cell even when `api.logout()` rejects, so the
UI never gets stuck on "authenticated" after a server hiccup.
Landing (`+page.svelte`):
- Pure router: anonymous → `/dating/login`, authenticated without a
completed profile → `/dating/onboarding`, otherwise →
`/dating/discover`. Renders a minimal pulse during the initial
session probe.
Auth (`login`, `register`, `reset`):
- `AuthLayout` + `Field` shared shell so every form has the same
centered card, focus ring and per-field error wiring.
- Login maps server error codes (`invalid_credentials`,
`account_restricted`, `mfa_required`) to localised messages and
redirects to `/dating/onboarding` when the user has no profile yet.
- Register validates locally (display name, email shape, ≥8-char
password, password match, adult + rules confirm) and surfaces
server codes (`email_already_used`, `weak_password`,
`password_mismatch`, `adult_confirmation_required`) on the right
field. Live password-strength meter.
- Reset is anti-enumeration: same UI whether the email exists or not
(matches the contract documented in
`auth-y-fotos.md`).
Onboarding (`/dating/onboarding`):
- Four-step flow (Identity → About → Preferences → Review) with a
progress stepper. Each step validates its own fields; clicking
"Publicar" re-runs validation across every step so the user lands
back on the broken one if anything regressed. On success the page
PUTs `/api/profile/me` with `completed: true` and refreshes the
session cell so guards downstream see the published profile.
Discover (`/dating/discover`):
- Filter sidebar (intent + min/max age) + card stack of profiles.
- `ProfileCard` component renders the canonical profile card (photo,
name + age, intent pill, location, bio, interests).
- Like / pass actions call `/api/likes`; a match opens a small modal
offering "Open chat" → `/dating/chat/{id}`.
- Auto-fetches the next page when the queue drops to two cards.
Matches (`/dating/matches`):
- List of active matches with avatar, name, last update, and the
start of the counterpart's bio. Each row links to the chat thread.
Chat (`/dating/chat/[matchId]`):
- Header with counterpart name + match state, scrollable timeline,
composer at the bottom.
- Optimistic send: the message lands as `queued` immediately, swapped
for the server response on success. On failure the bubble flips to
`failed` with retry/discard inline actions. Each provisional
message carries a `clientNonce` for idempotency on the server side.
Profile editor (`/dating/profile`):
- Full-form editor mirroring onboarding's fields plus visibility.
Tracks dirty state, surfaces server errors per-field, refreshes the
session cell after saving so the cache and Discover stay coherent.
- Header links to the photo manager.
Photo manager (`/dating/profile/photos`):
- Drop-zone + click-to-upload. Client validates format
(JPEG/PNG/WebP), 5 MB cap, and the 6-photo ceiling before sending.
Photos render in a grid with primary badge, order pills and per-photo
actions (set primary, move up/down, delete). Each mutation reuses the
API client and re-syncs the session cell.
All pages use the typed `DatingApiClient` from P0 — no ad-hoc fetch
calls — and route through `getNexoContext()` so the session is the
single source of truth across the demo.
Gates: 1695 tests + check (0 errors / 0 warnings) + build + bundle
smoke (22.52 KB gzip) + aliases — all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
* Symbol-keyed (instead of `setContext('app', ...)`) so it can't
|
|
|
|
|
* collide with whatever else the surrounding SvelteKit app registers.
|
|
|
|
|
*
|
|
|
|
|
* `session` is exposed as a getter — pages call `nexo.session()` so
|
|
|
|
|
* `$derived(...)` and `$effect(...)` track session-aware UI without
|
|
|
|
|
* each page re-issuing its own `/api/session` probe.
|
Nexo P0 — dating client bootstrap: types, API client, app factory, shell
Phase 0 of the Nexo demo (`src/web/routes/dating/plan-implementacion.md`).
The standalone server lives at `servers/dating` and is already running;
this commit lands the SvelteKit client that consumes it.
`_lib/types.ts` — public contracts mirroring the `*View` functions in
`servers/dating/domain.mjs`: `DatingUser`, `DatingProfile`,
`DatingMatch`, `DatingMessage`, `DatingReport` plus enums
(`DatingRole`, `DatingStatus`, `DatingIntent`, …) and the structured
event names (`DATING_EVENTS.AUTH_LOGIN`, …) the bus and orca will
publish later. Request/response payloads are split into dedicated
interfaces (`DatingRegisterInput`, `DatingDiscoverFilters`,
`DatingLikeResponse`, …) so the API client doesn't grow ad-hoc shapes.
`_lib/api.ts` — typed wrapper over the standalone server. Always
sends `credentials: 'include'` (the dating session is an HTTP-only
`dating_session` cookie) and normalises error responses into a
`DatingApiError` carrying the server's structured `code` (e.g.
`weak_password`, `email_already_used`) so UI branches on a stable
identifier instead of message strings. Network / abort failures
surface as the same class with `code: 'network_error'`. The factory
takes `fetch` + `signal` overrides for SSR (`event.fetch`) and tests.
`_lib/app.ts` — `createDatingApp()` opinionated `createActiveApp`
composition that fixes the service schema (lang, prefs, frontend,
storage, format, cache, http, session, sium) so consumer components
can type their `App` prop as `DatingApp` and get autocomplete on
every slot. `auth`, `perm`, and `connection` are intentionally not
in this commit — they need port-level wiring (an
`AuthClientHttpPort` against the Nexo endpoints, a perm endpoint,
the connection transport) that belongs to Phases 2 / 4. The factory
returns `{ App, api, dispose }` so callers don't have to compose
the App and the HTTP client separately.
`_lib/context.ts` — symbol-keyed `setNexoApp` / `getNexoApp` bridge
so the layout sets the handle once and nested pages retrieve it
without rebuilding `createActiveApp`.
`+layout.ts` — `prerender = false` for the entire `/dating` subtree.
The demo authenticates via cookies against a runtime-only server,
so static prerender doesn't make sense.
`+layout.svelte` — Nexo shell: instantiates `DatingApp` once, wires
the frontend `target`, eagerly probes `/api/session`, renders the
top nav (Discover, Matches, Profile, Safety, Devtools) with auth
state on the right, and shows an offline banner pointing to
`npm run dating:server` when the API can't be reached. Disposes
the App on unmount.
`+page.svelte` — landing card grid that links every route the plan
will materialise (Auth & Session / Profile / Discover & Match /
Safety & Moderación / Diagnóstico). Plain hrefs (not the typed
`resolve(...)`) so the page compiles ahead of the targets being
created — the links 404 until each phase lands its `+page.svelte`.
Gates: 1695 tests + check (0 errors) + build + bundle smoke
(22.52 KB gzip) + aliases — all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { getContext, setContext } from 'svelte';
|
|
|
|
|
import type { DatingAppHandle } from './app.ts';
|
|
|
|
|
import type { DatingRealtimeHandle } from './realtime.ts';
|
Nexo P1+P2 — user-facing surface: auth, onboarding, discover, matches, chat, profile
Builds the actual product the user sees, on top of the P0 bootstrap.
The previous landing was a phase-card grid for navigating the plan;
that's been replaced with a real auth-aware router and the routes
the cards used to point at now exist as functional pages.
Layout / context (`_lib/context.ts`, `+layout.svelte`):
- Layout owns the session cell. `getNexoContext()` exposes
`session()` / `setSession()` / `refreshSession()` so pages don't
each issue their own `/api/session` and so a successful
`login` / `register` propagates the user to every gate without
a round-trip.
- Logout clears the cell even when `api.logout()` rejects, so the
UI never gets stuck on "authenticated" after a server hiccup.
Landing (`+page.svelte`):
- Pure router: anonymous → `/dating/login`, authenticated without a
completed profile → `/dating/onboarding`, otherwise →
`/dating/discover`. Renders a minimal pulse during the initial
session probe.
Auth (`login`, `register`, `reset`):
- `AuthLayout` + `Field` shared shell so every form has the same
centered card, focus ring and per-field error wiring.
- Login maps server error codes (`invalid_credentials`,
`account_restricted`, `mfa_required`) to localised messages and
redirects to `/dating/onboarding` when the user has no profile yet.
- Register validates locally (display name, email shape, ≥8-char
password, password match, adult + rules confirm) and surfaces
server codes (`email_already_used`, `weak_password`,
`password_mismatch`, `adult_confirmation_required`) on the right
field. Live password-strength meter.
- Reset is anti-enumeration: same UI whether the email exists or not
(matches the contract documented in
`auth-y-fotos.md`).
Onboarding (`/dating/onboarding`):
- Four-step flow (Identity → About → Preferences → Review) with a
progress stepper. Each step validates its own fields; clicking
"Publicar" re-runs validation across every step so the user lands
back on the broken one if anything regressed. On success the page
PUTs `/api/profile/me` with `completed: true` and refreshes the
session cell so guards downstream see the published profile.
Discover (`/dating/discover`):
- Filter sidebar (intent + min/max age) + card stack of profiles.
- `ProfileCard` component renders the canonical profile card (photo,
name + age, intent pill, location, bio, interests).
- Like / pass actions call `/api/likes`; a match opens a small modal
offering "Open chat" → `/dating/chat/{id}`.
- Auto-fetches the next page when the queue drops to two cards.
Matches (`/dating/matches`):
- List of active matches with avatar, name, last update, and the
start of the counterpart's bio. Each row links to the chat thread.
Chat (`/dating/chat/[matchId]`):
- Header with counterpart name + match state, scrollable timeline,
composer at the bottom.
- Optimistic send: the message lands as `queued` immediately, swapped
for the server response on success. On failure the bubble flips to
`failed` with retry/discard inline actions. Each provisional
message carries a `clientNonce` for idempotency on the server side.
Profile editor (`/dating/profile`):
- Full-form editor mirroring onboarding's fields plus visibility.
Tracks dirty state, surfaces server errors per-field, refreshes the
session cell after saving so the cache and Discover stay coherent.
- Header links to the photo manager.
Photo manager (`/dating/profile/photos`):
- Drop-zone + click-to-upload. Client validates format
(JPEG/PNG/WebP), 5 MB cap, and the 6-photo ceiling before sending.
Photos render in a grid with primary badge, order pills and per-photo
actions (set primary, move up/down, delete). Each mutation reuses the
API client and re-syncs the session cell.
All pages use the typed `DatingApiClient` from P0 — no ad-hoc fetch
calls — and route through `getNexoContext()` so the session is the
single source of truth across the demo.
Gates: 1695 tests + check (0 errors / 0 warnings) + build + bundle
smoke (22.52 KB gzip) + aliases — all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
import type { DatingSessionResponse } from './types.ts';
|
|
|
|
|
|
|
|
|
|
export interface NexoContext extends DatingAppHandle {
|
|
|
|
|
/** Current session snapshot. `null` while the initial probe is in flight. */
|
|
|
|
|
session(): DatingSessionResponse | null;
|
|
|
|
|
/** Replace the session cell after a login / logout / refresh. */
|
|
|
|
|
setSession(next: DatingSessionResponse | null): void;
|
|
|
|
|
/** Re-issue `/api/session`; layout uses it on mount, pages on demand. */
|
|
|
|
|
refreshSession(): Promise<DatingSessionResponse | null>;
|
|
|
|
|
/**
|
|
|
|
|
* Shared WebSocket subscription to `/api/realtime`. Pages call
|
|
|
|
|
* `realtime().subscribe(...)` to react to server-pushed events
|
|
|
|
|
* (new chat messages, match updates, …). The layout owns the
|
|
|
|
|
* single underlying connection.
|
|
|
|
|
*/
|
|
|
|
|
realtime(): DatingRealtimeHandle;
|
Nexo P1+P2 — user-facing surface: auth, onboarding, discover, matches, chat, profile
Builds the actual product the user sees, on top of the P0 bootstrap.
The previous landing was a phase-card grid for navigating the plan;
that's been replaced with a real auth-aware router and the routes
the cards used to point at now exist as functional pages.
Layout / context (`_lib/context.ts`, `+layout.svelte`):
- Layout owns the session cell. `getNexoContext()` exposes
`session()` / `setSession()` / `refreshSession()` so pages don't
each issue their own `/api/session` and so a successful
`login` / `register` propagates the user to every gate without
a round-trip.
- Logout clears the cell even when `api.logout()` rejects, so the
UI never gets stuck on "authenticated" after a server hiccup.
Landing (`+page.svelte`):
- Pure router: anonymous → `/dating/login`, authenticated without a
completed profile → `/dating/onboarding`, otherwise →
`/dating/discover`. Renders a minimal pulse during the initial
session probe.
Auth (`login`, `register`, `reset`):
- `AuthLayout` + `Field` shared shell so every form has the same
centered card, focus ring and per-field error wiring.
- Login maps server error codes (`invalid_credentials`,
`account_restricted`, `mfa_required`) to localised messages and
redirects to `/dating/onboarding` when the user has no profile yet.
- Register validates locally (display name, email shape, ≥8-char
password, password match, adult + rules confirm) and surfaces
server codes (`email_already_used`, `weak_password`,
`password_mismatch`, `adult_confirmation_required`) on the right
field. Live password-strength meter.
- Reset is anti-enumeration: same UI whether the email exists or not
(matches the contract documented in
`auth-y-fotos.md`).
Onboarding (`/dating/onboarding`):
- Four-step flow (Identity → About → Preferences → Review) with a
progress stepper. Each step validates its own fields; clicking
"Publicar" re-runs validation across every step so the user lands
back on the broken one if anything regressed. On success the page
PUTs `/api/profile/me` with `completed: true` and refreshes the
session cell so guards downstream see the published profile.
Discover (`/dating/discover`):
- Filter sidebar (intent + min/max age) + card stack of profiles.
- `ProfileCard` component renders the canonical profile card (photo,
name + age, intent pill, location, bio, interests).
- Like / pass actions call `/api/likes`; a match opens a small modal
offering "Open chat" → `/dating/chat/{id}`.
- Auto-fetches the next page when the queue drops to two cards.
Matches (`/dating/matches`):
- List of active matches with avatar, name, last update, and the
start of the counterpart's bio. Each row links to the chat thread.
Chat (`/dating/chat/[matchId]`):
- Header with counterpart name + match state, scrollable timeline,
composer at the bottom.
- Optimistic send: the message lands as `queued` immediately, swapped
for the server response on success. On failure the bubble flips to
`failed` with retry/discard inline actions. Each provisional
message carries a `clientNonce` for idempotency on the server side.
Profile editor (`/dating/profile`):
- Full-form editor mirroring onboarding's fields plus visibility.
Tracks dirty state, surfaces server errors per-field, refreshes the
session cell after saving so the cache and Discover stay coherent.
- Header links to the photo manager.
Photo manager (`/dating/profile/photos`):
- Drop-zone + click-to-upload. Client validates format
(JPEG/PNG/WebP), 5 MB cap, and the 6-photo ceiling before sending.
Photos render in a grid with primary badge, order pills and per-photo
actions (set primary, move up/down, delete). Each mutation reuses the
API client and re-syncs the session cell.
All pages use the typed `DatingApiClient` from P0 — no ad-hoc fetch
calls — and route through `getNexoContext()` so the session is the
single source of truth across the demo.
Gates: 1695 tests + check (0 errors / 0 warnings) + build + bundle
smoke (22.52 KB gzip) + aliases — all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
}
|
Nexo P0 — dating client bootstrap: types, API client, app factory, shell
Phase 0 of the Nexo demo (`src/web/routes/dating/plan-implementacion.md`).
The standalone server lives at `servers/dating` and is already running;
this commit lands the SvelteKit client that consumes it.
`_lib/types.ts` — public contracts mirroring the `*View` functions in
`servers/dating/domain.mjs`: `DatingUser`, `DatingProfile`,
`DatingMatch`, `DatingMessage`, `DatingReport` plus enums
(`DatingRole`, `DatingStatus`, `DatingIntent`, …) and the structured
event names (`DATING_EVENTS.AUTH_LOGIN`, …) the bus and orca will
publish later. Request/response payloads are split into dedicated
interfaces (`DatingRegisterInput`, `DatingDiscoverFilters`,
`DatingLikeResponse`, …) so the API client doesn't grow ad-hoc shapes.
`_lib/api.ts` — typed wrapper over the standalone server. Always
sends `credentials: 'include'` (the dating session is an HTTP-only
`dating_session` cookie) and normalises error responses into a
`DatingApiError` carrying the server's structured `code` (e.g.
`weak_password`, `email_already_used`) so UI branches on a stable
identifier instead of message strings. Network / abort failures
surface as the same class with `code: 'network_error'`. The factory
takes `fetch` + `signal` overrides for SSR (`event.fetch`) and tests.
`_lib/app.ts` — `createDatingApp()` opinionated `createActiveApp`
composition that fixes the service schema (lang, prefs, frontend,
storage, format, cache, http, session, sium) so consumer components
can type their `App` prop as `DatingApp` and get autocomplete on
every slot. `auth`, `perm`, and `connection` are intentionally not
in this commit — they need port-level wiring (an
`AuthClientHttpPort` against the Nexo endpoints, a perm endpoint,
the connection transport) that belongs to Phases 2 / 4. The factory
returns `{ App, api, dispose }` so callers don't have to compose
the App and the HTTP client separately.
`_lib/context.ts` — symbol-keyed `setNexoApp` / `getNexoApp` bridge
so the layout sets the handle once and nested pages retrieve it
without rebuilding `createActiveApp`.
`+layout.ts` — `prerender = false` for the entire `/dating` subtree.
The demo authenticates via cookies against a runtime-only server,
so static prerender doesn't make sense.
`+layout.svelte` — Nexo shell: instantiates `DatingApp` once, wires
the frontend `target`, eagerly probes `/api/session`, renders the
top nav (Discover, Matches, Profile, Safety, Devtools) with auth
state on the right, and shows an offline banner pointing to
`npm run dating:server` when the API can't be reached. Disposes
the App on unmount.
`+page.svelte` — landing card grid that links every route the plan
will materialise (Auth & Session / Profile / Discover & Match /
Safety & Moderación / Diagnóstico). Plain hrefs (not the typed
`resolve(...)`) so the page compiles ahead of the targets being
created — the links 404 until each phase lands its `+page.svelte`.
Gates: 1695 tests + check (0 errors) + build + bundle smoke
(22.52 KB gzip) + aliases — all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
|
Nexo P1+P2 — user-facing surface: auth, onboarding, discover, matches, chat, profile
Builds the actual product the user sees, on top of the P0 bootstrap.
The previous landing was a phase-card grid for navigating the plan;
that's been replaced with a real auth-aware router and the routes
the cards used to point at now exist as functional pages.
Layout / context (`_lib/context.ts`, `+layout.svelte`):
- Layout owns the session cell. `getNexoContext()` exposes
`session()` / `setSession()` / `refreshSession()` so pages don't
each issue their own `/api/session` and so a successful
`login` / `register` propagates the user to every gate without
a round-trip.
- Logout clears the cell even when `api.logout()` rejects, so the
UI never gets stuck on "authenticated" after a server hiccup.
Landing (`+page.svelte`):
- Pure router: anonymous → `/dating/login`, authenticated without a
completed profile → `/dating/onboarding`, otherwise →
`/dating/discover`. Renders a minimal pulse during the initial
session probe.
Auth (`login`, `register`, `reset`):
- `AuthLayout` + `Field` shared shell so every form has the same
centered card, focus ring and per-field error wiring.
- Login maps server error codes (`invalid_credentials`,
`account_restricted`, `mfa_required`) to localised messages and
redirects to `/dating/onboarding` when the user has no profile yet.
- Register validates locally (display name, email shape, ≥8-char
password, password match, adult + rules confirm) and surfaces
server codes (`email_already_used`, `weak_password`,
`password_mismatch`, `adult_confirmation_required`) on the right
field. Live password-strength meter.
- Reset is anti-enumeration: same UI whether the email exists or not
(matches the contract documented in
`auth-y-fotos.md`).
Onboarding (`/dating/onboarding`):
- Four-step flow (Identity → About → Preferences → Review) with a
progress stepper. Each step validates its own fields; clicking
"Publicar" re-runs validation across every step so the user lands
back on the broken one if anything regressed. On success the page
PUTs `/api/profile/me` with `completed: true` and refreshes the
session cell so guards downstream see the published profile.
Discover (`/dating/discover`):
- Filter sidebar (intent + min/max age) + card stack of profiles.
- `ProfileCard` component renders the canonical profile card (photo,
name + age, intent pill, location, bio, interests).
- Like / pass actions call `/api/likes`; a match opens a small modal
offering "Open chat" → `/dating/chat/{id}`.
- Auto-fetches the next page when the queue drops to two cards.
Matches (`/dating/matches`):
- List of active matches with avatar, name, last update, and the
start of the counterpart's bio. Each row links to the chat thread.
Chat (`/dating/chat/[matchId]`):
- Header with counterpart name + match state, scrollable timeline,
composer at the bottom.
- Optimistic send: the message lands as `queued` immediately, swapped
for the server response on success. On failure the bubble flips to
`failed` with retry/discard inline actions. Each provisional
message carries a `clientNonce` for idempotency on the server side.
Profile editor (`/dating/profile`):
- Full-form editor mirroring onboarding's fields plus visibility.
Tracks dirty state, surfaces server errors per-field, refreshes the
session cell after saving so the cache and Discover stay coherent.
- Header links to the photo manager.
Photo manager (`/dating/profile/photos`):
- Drop-zone + click-to-upload. Client validates format
(JPEG/PNG/WebP), 5 MB cap, and the 6-photo ceiling before sending.
Photos render in a grid with primary badge, order pills and per-photo
actions (set primary, move up/down, delete). Each mutation reuses the
API client and re-syncs the session cell.
All pages use the typed `DatingApiClient` from P0 — no ad-hoc fetch
calls — and route through `getNexoContext()` so the session is the
single source of truth across the demo.
Gates: 1695 tests + check (0 errors / 0 warnings) + build + bundle
smoke (22.52 KB gzip) + aliases — all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
const NEXO_CONTEXT_KEY = Symbol('nexo.context');
|
Nexo P0 — dating client bootstrap: types, API client, app factory, shell
Phase 0 of the Nexo demo (`src/web/routes/dating/plan-implementacion.md`).
The standalone server lives at `servers/dating` and is already running;
this commit lands the SvelteKit client that consumes it.
`_lib/types.ts` — public contracts mirroring the `*View` functions in
`servers/dating/domain.mjs`: `DatingUser`, `DatingProfile`,
`DatingMatch`, `DatingMessage`, `DatingReport` plus enums
(`DatingRole`, `DatingStatus`, `DatingIntent`, …) and the structured
event names (`DATING_EVENTS.AUTH_LOGIN`, …) the bus and orca will
publish later. Request/response payloads are split into dedicated
interfaces (`DatingRegisterInput`, `DatingDiscoverFilters`,
`DatingLikeResponse`, …) so the API client doesn't grow ad-hoc shapes.
`_lib/api.ts` — typed wrapper over the standalone server. Always
sends `credentials: 'include'` (the dating session is an HTTP-only
`dating_session` cookie) and normalises error responses into a
`DatingApiError` carrying the server's structured `code` (e.g.
`weak_password`, `email_already_used`) so UI branches on a stable
identifier instead of message strings. Network / abort failures
surface as the same class with `code: 'network_error'`. The factory
takes `fetch` + `signal` overrides for SSR (`event.fetch`) and tests.
`_lib/app.ts` — `createDatingApp()` opinionated `createActiveApp`
composition that fixes the service schema (lang, prefs, frontend,
storage, format, cache, http, session, sium) so consumer components
can type their `App` prop as `DatingApp` and get autocomplete on
every slot. `auth`, `perm`, and `connection` are intentionally not
in this commit — they need port-level wiring (an
`AuthClientHttpPort` against the Nexo endpoints, a perm endpoint,
the connection transport) that belongs to Phases 2 / 4. The factory
returns `{ App, api, dispose }` so callers don't have to compose
the App and the HTTP client separately.
`_lib/context.ts` — symbol-keyed `setNexoApp` / `getNexoApp` bridge
so the layout sets the handle once and nested pages retrieve it
without rebuilding `createActiveApp`.
`+layout.ts` — `prerender = false` for the entire `/dating` subtree.
The demo authenticates via cookies against a runtime-only server,
so static prerender doesn't make sense.
`+layout.svelte` — Nexo shell: instantiates `DatingApp` once, wires
the frontend `target`, eagerly probes `/api/session`, renders the
top nav (Discover, Matches, Profile, Safety, Devtools) with auth
state on the right, and shows an offline banner pointing to
`npm run dating:server` when the API can't be reached. Disposes
the App on unmount.
`+page.svelte` — landing card grid that links every route the plan
will materialise (Auth & Session / Profile / Discover & Match /
Safety & Moderación / Diagnóstico). Plain hrefs (not the typed
`resolve(...)`) so the page compiles ahead of the targets being
created — the links 404 until each phase lands its `+page.svelte`.
Gates: 1695 tests + check (0 errors) + build + bundle smoke
(22.52 KB gzip) + aliases — all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
|
Nexo P1+P2 — user-facing surface: auth, onboarding, discover, matches, chat, profile
Builds the actual product the user sees, on top of the P0 bootstrap.
The previous landing was a phase-card grid for navigating the plan;
that's been replaced with a real auth-aware router and the routes
the cards used to point at now exist as functional pages.
Layout / context (`_lib/context.ts`, `+layout.svelte`):
- Layout owns the session cell. `getNexoContext()` exposes
`session()` / `setSession()` / `refreshSession()` so pages don't
each issue their own `/api/session` and so a successful
`login` / `register` propagates the user to every gate without
a round-trip.
- Logout clears the cell even when `api.logout()` rejects, so the
UI never gets stuck on "authenticated" after a server hiccup.
Landing (`+page.svelte`):
- Pure router: anonymous → `/dating/login`, authenticated without a
completed profile → `/dating/onboarding`, otherwise →
`/dating/discover`. Renders a minimal pulse during the initial
session probe.
Auth (`login`, `register`, `reset`):
- `AuthLayout` + `Field` shared shell so every form has the same
centered card, focus ring and per-field error wiring.
- Login maps server error codes (`invalid_credentials`,
`account_restricted`, `mfa_required`) to localised messages and
redirects to `/dating/onboarding` when the user has no profile yet.
- Register validates locally (display name, email shape, ≥8-char
password, password match, adult + rules confirm) and surfaces
server codes (`email_already_used`, `weak_password`,
`password_mismatch`, `adult_confirmation_required`) on the right
field. Live password-strength meter.
- Reset is anti-enumeration: same UI whether the email exists or not
(matches the contract documented in
`auth-y-fotos.md`).
Onboarding (`/dating/onboarding`):
- Four-step flow (Identity → About → Preferences → Review) with a
progress stepper. Each step validates its own fields; clicking
"Publicar" re-runs validation across every step so the user lands
back on the broken one if anything regressed. On success the page
PUTs `/api/profile/me` with `completed: true` and refreshes the
session cell so guards downstream see the published profile.
Discover (`/dating/discover`):
- Filter sidebar (intent + min/max age) + card stack of profiles.
- `ProfileCard` component renders the canonical profile card (photo,
name + age, intent pill, location, bio, interests).
- Like / pass actions call `/api/likes`; a match opens a small modal
offering "Open chat" → `/dating/chat/{id}`.
- Auto-fetches the next page when the queue drops to two cards.
Matches (`/dating/matches`):
- List of active matches with avatar, name, last update, and the
start of the counterpart's bio. Each row links to the chat thread.
Chat (`/dating/chat/[matchId]`):
- Header with counterpart name + match state, scrollable timeline,
composer at the bottom.
- Optimistic send: the message lands as `queued` immediately, swapped
for the server response on success. On failure the bubble flips to
`failed` with retry/discard inline actions. Each provisional
message carries a `clientNonce` for idempotency on the server side.
Profile editor (`/dating/profile`):
- Full-form editor mirroring onboarding's fields plus visibility.
Tracks dirty state, surfaces server errors per-field, refreshes the
session cell after saving so the cache and Discover stay coherent.
- Header links to the photo manager.
Photo manager (`/dating/profile/photos`):
- Drop-zone + click-to-upload. Client validates format
(JPEG/PNG/WebP), 5 MB cap, and the 6-photo ceiling before sending.
Photos render in a grid with primary badge, order pills and per-photo
actions (set primary, move up/down, delete). Each mutation reuses the
API client and re-syncs the session cell.
All pages use the typed `DatingApiClient` from P0 — no ad-hoc fetch
calls — and route through `getNexoContext()` so the session is the
single source of truth across the demo.
Gates: 1695 tests + check (0 errors / 0 warnings) + build + bundle
smoke (22.52 KB gzip) + aliases — all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
export function setNexoContext(value: NexoContext): NexoContext {
|
|
|
|
|
return setContext(NEXO_CONTEXT_KEY, value);
|
Nexo P0 — dating client bootstrap: types, API client, app factory, shell
Phase 0 of the Nexo demo (`src/web/routes/dating/plan-implementacion.md`).
The standalone server lives at `servers/dating` and is already running;
this commit lands the SvelteKit client that consumes it.
`_lib/types.ts` — public contracts mirroring the `*View` functions in
`servers/dating/domain.mjs`: `DatingUser`, `DatingProfile`,
`DatingMatch`, `DatingMessage`, `DatingReport` plus enums
(`DatingRole`, `DatingStatus`, `DatingIntent`, …) and the structured
event names (`DATING_EVENTS.AUTH_LOGIN`, …) the bus and orca will
publish later. Request/response payloads are split into dedicated
interfaces (`DatingRegisterInput`, `DatingDiscoverFilters`,
`DatingLikeResponse`, …) so the API client doesn't grow ad-hoc shapes.
`_lib/api.ts` — typed wrapper over the standalone server. Always
sends `credentials: 'include'` (the dating session is an HTTP-only
`dating_session` cookie) and normalises error responses into a
`DatingApiError` carrying the server's structured `code` (e.g.
`weak_password`, `email_already_used`) so UI branches on a stable
identifier instead of message strings. Network / abort failures
surface as the same class with `code: 'network_error'`. The factory
takes `fetch` + `signal` overrides for SSR (`event.fetch`) and tests.
`_lib/app.ts` — `createDatingApp()` opinionated `createActiveApp`
composition that fixes the service schema (lang, prefs, frontend,
storage, format, cache, http, session, sium) so consumer components
can type their `App` prop as `DatingApp` and get autocomplete on
every slot. `auth`, `perm`, and `connection` are intentionally not
in this commit — they need port-level wiring (an
`AuthClientHttpPort` against the Nexo endpoints, a perm endpoint,
the connection transport) that belongs to Phases 2 / 4. The factory
returns `{ App, api, dispose }` so callers don't have to compose
the App and the HTTP client separately.
`_lib/context.ts` — symbol-keyed `setNexoApp` / `getNexoApp` bridge
so the layout sets the handle once and nested pages retrieve it
without rebuilding `createActiveApp`.
`+layout.ts` — `prerender = false` for the entire `/dating` subtree.
The demo authenticates via cookies against a runtime-only server,
so static prerender doesn't make sense.
`+layout.svelte` — Nexo shell: instantiates `DatingApp` once, wires
the frontend `target`, eagerly probes `/api/session`, renders the
top nav (Discover, Matches, Profile, Safety, Devtools) with auth
state on the right, and shows an offline banner pointing to
`npm run dating:server` when the API can't be reached. Disposes
the App on unmount.
`+page.svelte` — landing card grid that links every route the plan
will materialise (Auth & Session / Profile / Discover & Match /
Safety & Moderación / Diagnóstico). Plain hrefs (not the typed
`resolve(...)`) so the page compiles ahead of the targets being
created — the links 404 until each phase lands its `+page.svelte`.
Gates: 1695 tests + check (0 errors) + build + bundle smoke
(22.52 KB gzip) + aliases — all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
}
|
|
|
|
|
|
Nexo P1+P2 — user-facing surface: auth, onboarding, discover, matches, chat, profile
Builds the actual product the user sees, on top of the P0 bootstrap.
The previous landing was a phase-card grid for navigating the plan;
that's been replaced with a real auth-aware router and the routes
the cards used to point at now exist as functional pages.
Layout / context (`_lib/context.ts`, `+layout.svelte`):
- Layout owns the session cell. `getNexoContext()` exposes
`session()` / `setSession()` / `refreshSession()` so pages don't
each issue their own `/api/session` and so a successful
`login` / `register` propagates the user to every gate without
a round-trip.
- Logout clears the cell even when `api.logout()` rejects, so the
UI never gets stuck on "authenticated" after a server hiccup.
Landing (`+page.svelte`):
- Pure router: anonymous → `/dating/login`, authenticated without a
completed profile → `/dating/onboarding`, otherwise →
`/dating/discover`. Renders a minimal pulse during the initial
session probe.
Auth (`login`, `register`, `reset`):
- `AuthLayout` + `Field` shared shell so every form has the same
centered card, focus ring and per-field error wiring.
- Login maps server error codes (`invalid_credentials`,
`account_restricted`, `mfa_required`) to localised messages and
redirects to `/dating/onboarding` when the user has no profile yet.
- Register validates locally (display name, email shape, ≥8-char
password, password match, adult + rules confirm) and surfaces
server codes (`email_already_used`, `weak_password`,
`password_mismatch`, `adult_confirmation_required`) on the right
field. Live password-strength meter.
- Reset is anti-enumeration: same UI whether the email exists or not
(matches the contract documented in
`auth-y-fotos.md`).
Onboarding (`/dating/onboarding`):
- Four-step flow (Identity → About → Preferences → Review) with a
progress stepper. Each step validates its own fields; clicking
"Publicar" re-runs validation across every step so the user lands
back on the broken one if anything regressed. On success the page
PUTs `/api/profile/me` with `completed: true` and refreshes the
session cell so guards downstream see the published profile.
Discover (`/dating/discover`):
- Filter sidebar (intent + min/max age) + card stack of profiles.
- `ProfileCard` component renders the canonical profile card (photo,
name + age, intent pill, location, bio, interests).
- Like / pass actions call `/api/likes`; a match opens a small modal
offering "Open chat" → `/dating/chat/{id}`.
- Auto-fetches the next page when the queue drops to two cards.
Matches (`/dating/matches`):
- List of active matches with avatar, name, last update, and the
start of the counterpart's bio. Each row links to the chat thread.
Chat (`/dating/chat/[matchId]`):
- Header with counterpart name + match state, scrollable timeline,
composer at the bottom.
- Optimistic send: the message lands as `queued` immediately, swapped
for the server response on success. On failure the bubble flips to
`failed` with retry/discard inline actions. Each provisional
message carries a `clientNonce` for idempotency on the server side.
Profile editor (`/dating/profile`):
- Full-form editor mirroring onboarding's fields plus visibility.
Tracks dirty state, surfaces server errors per-field, refreshes the
session cell after saving so the cache and Discover stay coherent.
- Header links to the photo manager.
Photo manager (`/dating/profile/photos`):
- Drop-zone + click-to-upload. Client validates format
(JPEG/PNG/WebP), 5 MB cap, and the 6-photo ceiling before sending.
Photos render in a grid with primary badge, order pills and per-photo
actions (set primary, move up/down, delete). Each mutation reuses the
API client and re-syncs the session cell.
All pages use the typed `DatingApiClient` from P0 — no ad-hoc fetch
calls — and route through `getNexoContext()` so the session is the
single source of truth across the demo.
Gates: 1695 tests + check (0 errors / 0 warnings) + build + bundle
smoke (22.52 KB gzip) + aliases — all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
export function getNexoContext(): NexoContext {
|
|
|
|
|
const value = getContext<NexoContext | undefined>(NEXO_CONTEXT_KEY);
|
|
|
|
|
if (value === undefined) {
|
|
|
|
|
throw new Error('Nexo context is not set. Did you mount /dating without its layout?');
|
Nexo P0 — dating client bootstrap: types, API client, app factory, shell
Phase 0 of the Nexo demo (`src/web/routes/dating/plan-implementacion.md`).
The standalone server lives at `servers/dating` and is already running;
this commit lands the SvelteKit client that consumes it.
`_lib/types.ts` — public contracts mirroring the `*View` functions in
`servers/dating/domain.mjs`: `DatingUser`, `DatingProfile`,
`DatingMatch`, `DatingMessage`, `DatingReport` plus enums
(`DatingRole`, `DatingStatus`, `DatingIntent`, …) and the structured
event names (`DATING_EVENTS.AUTH_LOGIN`, …) the bus and orca will
publish later. Request/response payloads are split into dedicated
interfaces (`DatingRegisterInput`, `DatingDiscoverFilters`,
`DatingLikeResponse`, …) so the API client doesn't grow ad-hoc shapes.
`_lib/api.ts` — typed wrapper over the standalone server. Always
sends `credentials: 'include'` (the dating session is an HTTP-only
`dating_session` cookie) and normalises error responses into a
`DatingApiError` carrying the server's structured `code` (e.g.
`weak_password`, `email_already_used`) so UI branches on a stable
identifier instead of message strings. Network / abort failures
surface as the same class with `code: 'network_error'`. The factory
takes `fetch` + `signal` overrides for SSR (`event.fetch`) and tests.
`_lib/app.ts` — `createDatingApp()` opinionated `createActiveApp`
composition that fixes the service schema (lang, prefs, frontend,
storage, format, cache, http, session, sium) so consumer components
can type their `App` prop as `DatingApp` and get autocomplete on
every slot. `auth`, `perm`, and `connection` are intentionally not
in this commit — they need port-level wiring (an
`AuthClientHttpPort` against the Nexo endpoints, a perm endpoint,
the connection transport) that belongs to Phases 2 / 4. The factory
returns `{ App, api, dispose }` so callers don't have to compose
the App and the HTTP client separately.
`_lib/context.ts` — symbol-keyed `setNexoApp` / `getNexoApp` bridge
so the layout sets the handle once and nested pages retrieve it
without rebuilding `createActiveApp`.
`+layout.ts` — `prerender = false` for the entire `/dating` subtree.
The demo authenticates via cookies against a runtime-only server,
so static prerender doesn't make sense.
`+layout.svelte` — Nexo shell: instantiates `DatingApp` once, wires
the frontend `target`, eagerly probes `/api/session`, renders the
top nav (Discover, Matches, Profile, Safety, Devtools) with auth
state on the right, and shows an offline banner pointing to
`npm run dating:server` when the API can't be reached. Disposes
the App on unmount.
`+page.svelte` — landing card grid that links every route the plan
will materialise (Auth & Session / Profile / Discover & Match /
Safety & Moderación / Diagnóstico). Plain hrefs (not the typed
`resolve(...)`) so the page compiles ahead of the targets being
created — the links 404 until each phase lands its `+page.svelte`.
Gates: 1695 tests + check (0 errors) + build + bundle smoke
(22.52 KB gzip) + aliases — all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
}
|
Nexo P1+P2 — user-facing surface: auth, onboarding, discover, matches, chat, profile
Builds the actual product the user sees, on top of the P0 bootstrap.
The previous landing was a phase-card grid for navigating the plan;
that's been replaced with a real auth-aware router and the routes
the cards used to point at now exist as functional pages.
Layout / context (`_lib/context.ts`, `+layout.svelte`):
- Layout owns the session cell. `getNexoContext()` exposes
`session()` / `setSession()` / `refreshSession()` so pages don't
each issue their own `/api/session` and so a successful
`login` / `register` propagates the user to every gate without
a round-trip.
- Logout clears the cell even when `api.logout()` rejects, so the
UI never gets stuck on "authenticated" after a server hiccup.
Landing (`+page.svelte`):
- Pure router: anonymous → `/dating/login`, authenticated without a
completed profile → `/dating/onboarding`, otherwise →
`/dating/discover`. Renders a minimal pulse during the initial
session probe.
Auth (`login`, `register`, `reset`):
- `AuthLayout` + `Field` shared shell so every form has the same
centered card, focus ring and per-field error wiring.
- Login maps server error codes (`invalid_credentials`,
`account_restricted`, `mfa_required`) to localised messages and
redirects to `/dating/onboarding` when the user has no profile yet.
- Register validates locally (display name, email shape, ≥8-char
password, password match, adult + rules confirm) and surfaces
server codes (`email_already_used`, `weak_password`,
`password_mismatch`, `adult_confirmation_required`) on the right
field. Live password-strength meter.
- Reset is anti-enumeration: same UI whether the email exists or not
(matches the contract documented in
`auth-y-fotos.md`).
Onboarding (`/dating/onboarding`):
- Four-step flow (Identity → About → Preferences → Review) with a
progress stepper. Each step validates its own fields; clicking
"Publicar" re-runs validation across every step so the user lands
back on the broken one if anything regressed. On success the page
PUTs `/api/profile/me` with `completed: true` and refreshes the
session cell so guards downstream see the published profile.
Discover (`/dating/discover`):
- Filter sidebar (intent + min/max age) + card stack of profiles.
- `ProfileCard` component renders the canonical profile card (photo,
name + age, intent pill, location, bio, interests).
- Like / pass actions call `/api/likes`; a match opens a small modal
offering "Open chat" → `/dating/chat/{id}`.
- Auto-fetches the next page when the queue drops to two cards.
Matches (`/dating/matches`):
- List of active matches with avatar, name, last update, and the
start of the counterpart's bio. Each row links to the chat thread.
Chat (`/dating/chat/[matchId]`):
- Header with counterpart name + match state, scrollable timeline,
composer at the bottom.
- Optimistic send: the message lands as `queued` immediately, swapped
for the server response on success. On failure the bubble flips to
`failed` with retry/discard inline actions. Each provisional
message carries a `clientNonce` for idempotency on the server side.
Profile editor (`/dating/profile`):
- Full-form editor mirroring onboarding's fields plus visibility.
Tracks dirty state, surfaces server errors per-field, refreshes the
session cell after saving so the cache and Discover stay coherent.
- Header links to the photo manager.
Photo manager (`/dating/profile/photos`):
- Drop-zone + click-to-upload. Client validates format
(JPEG/PNG/WebP), 5 MB cap, and the 6-photo ceiling before sending.
Photos render in a grid with primary badge, order pills and per-photo
actions (set primary, move up/down, delete). Each mutation reuses the
API client and re-syncs the session cell.
All pages use the typed `DatingApiClient` from P0 — no ad-hoc fetch
calls — and route through `getNexoContext()` so the session is the
single source of truth across the demo.
Gates: 1695 tests + check (0 errors / 0 warnings) + build + bundle
smoke (22.52 KB gzip) + aliases — all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
return value;
|
Nexo P0 — dating client bootstrap: types, API client, app factory, shell
Phase 0 of the Nexo demo (`src/web/routes/dating/plan-implementacion.md`).
The standalone server lives at `servers/dating` and is already running;
this commit lands the SvelteKit client that consumes it.
`_lib/types.ts` — public contracts mirroring the `*View` functions in
`servers/dating/domain.mjs`: `DatingUser`, `DatingProfile`,
`DatingMatch`, `DatingMessage`, `DatingReport` plus enums
(`DatingRole`, `DatingStatus`, `DatingIntent`, …) and the structured
event names (`DATING_EVENTS.AUTH_LOGIN`, …) the bus and orca will
publish later. Request/response payloads are split into dedicated
interfaces (`DatingRegisterInput`, `DatingDiscoverFilters`,
`DatingLikeResponse`, …) so the API client doesn't grow ad-hoc shapes.
`_lib/api.ts` — typed wrapper over the standalone server. Always
sends `credentials: 'include'` (the dating session is an HTTP-only
`dating_session` cookie) and normalises error responses into a
`DatingApiError` carrying the server's structured `code` (e.g.
`weak_password`, `email_already_used`) so UI branches on a stable
identifier instead of message strings. Network / abort failures
surface as the same class with `code: 'network_error'`. The factory
takes `fetch` + `signal` overrides for SSR (`event.fetch`) and tests.
`_lib/app.ts` — `createDatingApp()` opinionated `createActiveApp`
composition that fixes the service schema (lang, prefs, frontend,
storage, format, cache, http, session, sium) so consumer components
can type their `App` prop as `DatingApp` and get autocomplete on
every slot. `auth`, `perm`, and `connection` are intentionally not
in this commit — they need port-level wiring (an
`AuthClientHttpPort` against the Nexo endpoints, a perm endpoint,
the connection transport) that belongs to Phases 2 / 4. The factory
returns `{ App, api, dispose }` so callers don't have to compose
the App and the HTTP client separately.
`_lib/context.ts` — symbol-keyed `setNexoApp` / `getNexoApp` bridge
so the layout sets the handle once and nested pages retrieve it
without rebuilding `createActiveApp`.
`+layout.ts` — `prerender = false` for the entire `/dating` subtree.
The demo authenticates via cookies against a runtime-only server,
so static prerender doesn't make sense.
`+layout.svelte` — Nexo shell: instantiates `DatingApp` once, wires
the frontend `target`, eagerly probes `/api/session`, renders the
top nav (Discover, Matches, Profile, Safety, Devtools) with auth
state on the right, and shows an offline banner pointing to
`npm run dating:server` when the API can't be reached. Disposes
the App on unmount.
`+page.svelte` — landing card grid that links every route the plan
will materialise (Auth & Session / Profile / Discover & Match /
Safety & Moderación / Diagnóstico). Plain hrefs (not the typed
`resolve(...)`) so the page compiles ahead of the targets being
created — the links 404 until each phase lands its `+page.svelte`.
Gates: 1695 tests + check (0 errors) + build + bundle smoke
(22.52 KB gzip) + aliases — all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
}
|
Nexo P1+P2 — user-facing surface: auth, onboarding, discover, matches, chat, profile
Builds the actual product the user sees, on top of the P0 bootstrap.
The previous landing was a phase-card grid for navigating the plan;
that's been replaced with a real auth-aware router and the routes
the cards used to point at now exist as functional pages.
Layout / context (`_lib/context.ts`, `+layout.svelte`):
- Layout owns the session cell. `getNexoContext()` exposes
`session()` / `setSession()` / `refreshSession()` so pages don't
each issue their own `/api/session` and so a successful
`login` / `register` propagates the user to every gate without
a round-trip.
- Logout clears the cell even when `api.logout()` rejects, so the
UI never gets stuck on "authenticated" after a server hiccup.
Landing (`+page.svelte`):
- Pure router: anonymous → `/dating/login`, authenticated without a
completed profile → `/dating/onboarding`, otherwise →
`/dating/discover`. Renders a minimal pulse during the initial
session probe.
Auth (`login`, `register`, `reset`):
- `AuthLayout` + `Field` shared shell so every form has the same
centered card, focus ring and per-field error wiring.
- Login maps server error codes (`invalid_credentials`,
`account_restricted`, `mfa_required`) to localised messages and
redirects to `/dating/onboarding` when the user has no profile yet.
- Register validates locally (display name, email shape, ≥8-char
password, password match, adult + rules confirm) and surfaces
server codes (`email_already_used`, `weak_password`,
`password_mismatch`, `adult_confirmation_required`) on the right
field. Live password-strength meter.
- Reset is anti-enumeration: same UI whether the email exists or not
(matches the contract documented in
`auth-y-fotos.md`).
Onboarding (`/dating/onboarding`):
- Four-step flow (Identity → About → Preferences → Review) with a
progress stepper. Each step validates its own fields; clicking
"Publicar" re-runs validation across every step so the user lands
back on the broken one if anything regressed. On success the page
PUTs `/api/profile/me` with `completed: true` and refreshes the
session cell so guards downstream see the published profile.
Discover (`/dating/discover`):
- Filter sidebar (intent + min/max age) + card stack of profiles.
- `ProfileCard` component renders the canonical profile card (photo,
name + age, intent pill, location, bio, interests).
- Like / pass actions call `/api/likes`; a match opens a small modal
offering "Open chat" → `/dating/chat/{id}`.
- Auto-fetches the next page when the queue drops to two cards.
Matches (`/dating/matches`):
- List of active matches with avatar, name, last update, and the
start of the counterpart's bio. Each row links to the chat thread.
Chat (`/dating/chat/[matchId]`):
- Header with counterpart name + match state, scrollable timeline,
composer at the bottom.
- Optimistic send: the message lands as `queued` immediately, swapped
for the server response on success. On failure the bubble flips to
`failed` with retry/discard inline actions. Each provisional
message carries a `clientNonce` for idempotency on the server side.
Profile editor (`/dating/profile`):
- Full-form editor mirroring onboarding's fields plus visibility.
Tracks dirty state, surfaces server errors per-field, refreshes the
session cell after saving so the cache and Discover stay coherent.
- Header links to the photo manager.
Photo manager (`/dating/profile/photos`):
- Drop-zone + click-to-upload. Client validates format
(JPEG/PNG/WebP), 5 MB cap, and the 6-photo ceiling before sending.
Photos render in a grid with primary badge, order pills and per-photo
actions (set primary, move up/down, delete). Each mutation reuses the
API client and re-syncs the session cell.
All pages use the typed `DatingApiClient` from P0 — no ad-hoc fetch
calls — and route through `getNexoContext()` so the session is the
single source of truth across the demo.
Gates: 1695 tests + check (0 errors / 0 warnings) + build + bundle
smoke (22.52 KB gzip) + aliases — all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
|
|
|
|
|
// Back-compat aliases — pages that only need the App handle keep the
|
|
|
|
|
// terser names. New code uses `getNexoContext()` for the session
|
|
|
|
|
// surface too.
|
|
|
|
|
export const setNexoApp = setNexoContext;
|
|
|
|
|
export const getNexoApp = getNexoContext;
|