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
|
|
|
/**
|
|
|
|
|
* `createDatingApp` — opinionated `createActiveApp` composition for the
|
|
|
|
|
* Nexo demo. Picks a fixed service schema so consumer components can
|
|
|
|
|
* type their `App` prop as `DatingApp` and get autocomplete on
|
|
|
|
|
* `App.lang`, `App.session`, `App.cache`, … without falling back to
|
|
|
|
|
* `unknown`.
|
|
|
|
|
*
|
|
|
|
|
* Contract intentionally narrower than the README's full list: `auth`,
|
|
|
|
|
* `perm` and `connection` need port wiring that lives outside Phase 0
|
|
|
|
|
* scope (auth flows go through `DatingApiClient` directly while the
|
|
|
|
|
* artifact-level `App.auth` waits for a Nexo-shaped HTTP port). They
|
|
|
|
|
* will be added by their respective phases — see
|
|
|
|
|
* `src/web/routes/dating/plan-implementacion.md`.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { createActiveApp, type ActiveApp } from '$active-app';
|
|
|
|
|
import {
|
|
|
|
|
defineActiveCache,
|
|
|
|
|
defineActiveFormat,
|
|
|
|
|
defineActiveFrontend,
|
|
|
|
|
defineActiveLang,
|
|
|
|
|
defineActiveSession,
|
|
|
|
|
defineActiveStorage,
|
|
|
|
|
defineEngineHttp,
|
|
|
|
|
defineEngineSium
|
|
|
|
|
} from '$active-app/services';
|
Prefs as schema-based core + lowercase App.* surface
Two structural changes that were overdue and got bundled because
they touched the same set of files.
## Prefs is now a schema, not a fixed shape
Previously every preference had to be declared in a closed
`PrefsCapabilities` interface (`languages`, `locales`, `currencies`,
`themes`, `densities`, `motions`, `timezones`, `unitSystems`). Adding
a new pref required forking `$libs/prefs` — bad framework design.
The redesign replaces the fixed shape with a schema:
PrefsSchema = Record<string, PrefsDimension<TIntent, TEffective>>
Each dimension owns its own validator (`validate`), environment-fed
resolver (`resolve`) and optional sibling-derived value (`derive`).
The engine is generic over the schema and iterates it; it knows
nothing about "locale" or "theme" specifically.
Built-in dimensions live in `arts/prefs/dimensions/*` (locale,
language, theme, density, motion, timezone, currency, unit-system,
direction, plus boolean / enum / string / number primitives). The
`standardPrefsDimensions(catalog)` preset composes the canonical
set; apps spread it and add their own:
const schema = {
...standardPrefsDimensions({ languages, locales, currencies }),
sidebarCollapsed: booleanDimension({ default: false }),
notificationLevel: enumDimension(
['all', 'mentions', 'none'] as const,
{ default: 'mentions' }
)
};
Active surface exposes one slot per schema key with uniform verbs:
App.prefs.locale.get()
App.prefs.locale.set('es-ES')
App.prefs.locale.clear()
App.prefs.locale.onChange((v) => …)
App.prefs.sidebarCollapsed.set(true)
`setIntent('locale', value)` stays available as a low-level pass-
through (storage bridge consumes it generically) but UI code uses
the dimension surface.
## Lowercase core surface
`App.Logger`, `App.Bus`, `App.Timers`, `App.Orca`, `App.Prefs` are
gone. The "PascalCase for core, lowercase for services" rule was
visual signalling against JS convention, no technical benefit, and
created an asymmetry on the same object. All core members are now
lowercase, matching services:
App.logger
App.bus
App.timers
App.orca
App.prefs
`createPrefsStorageBridge` keeps its old responsibilities; sources
helpers (`prefsLocaleSource`, …) are gone — the dimension API
replaces them.
## What changed
- `$libs/prefs`: fully generic schema-based types + resolver. Old
fixed `PrefsCapabilities` / `PrefsIntent` / `PrefsEffective`
removed; replaced by `PrefsDimension`, `PrefsSchema`,
`PrefsEffectiveOf<S>`, `PrefsIntentOf<S>`.
- `arts/prefs`: engine + active wrapper rewritten to schema. Per-
dimension active surface auto-built from schema keys. Sources file
deleted (replaced by dimension surface). New
`arts/prefs/dimensions/*` and `arts/prefs/standard.ts`. Storage
bridge made schema-generic.
- `arts/active-app`: lowercase `CoreServices` / `ActiveAppCore`,
`prefs?: ActiveAppPrefsOptions<S>` root option carrying the
schema. `defineActivePrefs` deleted (prefs is core, not service).
`lang` / `format` / `frontend` factories migrated to read
`core.prefs.<dim>` directly via defensive `readSlot()` helpers
(each dimension is optional from the factory's POV; if the app's
schema omits one, the integration degrades gracefully).
- Presets, demos, web routes, README docstrings, `check-aliases.mjs`
guards, marketing snippets all migrated.
- Tests: `engine-prefs`, `active-prefs`, `storage-bridge`,
`resolve-prefs`, `validate-intent`, `prefs-consumer-wiring`,
`service-factories` rewritten for the schema-based API.
`sources.test.ts` deleted (sources file is gone).
## Verification
- `npm run check`: 0 errors, 0 warnings (1527 files).
- `npm test`: 1645 tests across 139 files, all green.
- `node scripts/check-aliases.mjs`: clean (lowercase enforced for
every member of `App.*`, including `Logger`/`Bus`/`Timers`/`Orca`/
`Prefs` which now flag as forbidden capitals).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
import { standardPrefsDimensions } from '$prefs';
|
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 type { DatingUser } from './types.ts';
|
|
|
|
|
import { createDatingApiClient, type DatingApiClient } from './api.ts';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Bootstrap lang schema. Phase 0 ships only the strings the shell
|
|
|
|
|
* needs; later phases register their own namespaces via
|
|
|
|
|
* `App.lang.extend(...)` rather than threading more keys through
|
|
|
|
|
* here.
|
|
|
|
|
*/
|
|
|
|
|
export const NEXO_LANG_SCHEMA = {
|
|
|
|
|
nexo: {
|
|
|
|
|
title: { es: 'Nexo', en: 'Nexo' },
|
|
|
|
|
tagline: {
|
|
|
|
|
es: 'Demo del ecosistema Active sobre un producto real.',
|
|
|
|
|
en: 'Active ecosystem demo running on a real product.'
|
|
|
|
|
},
|
|
|
|
|
nav: {
|
|
|
|
|
discover: { es: 'Descubre', en: 'Discover' },
|
|
|
|
|
matches: { es: 'Matches', en: 'Matches' },
|
|
|
|
|
profile: { es: 'Perfil', en: 'Profile' },
|
|
|
|
|
safety: { es: 'Seguridad', en: 'Safety' },
|
|
|
|
|
devtools: { es: 'Devtools', en: 'Devtools' }
|
|
|
|
|
},
|
|
|
|
|
auth: {
|
|
|
|
|
login: { es: 'Iniciar sesión', en: 'Sign in' },
|
|
|
|
|
register: { es: 'Crear cuenta', en: 'Sign up' },
|
|
|
|
|
logout: { es: 'Cerrar sesión', en: 'Sign out' }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} as const;
|
|
|
|
|
|
Prefs as schema-based core + lowercase App.* surface
Two structural changes that were overdue and got bundled because
they touched the same set of files.
## Prefs is now a schema, not a fixed shape
Previously every preference had to be declared in a closed
`PrefsCapabilities` interface (`languages`, `locales`, `currencies`,
`themes`, `densities`, `motions`, `timezones`, `unitSystems`). Adding
a new pref required forking `$libs/prefs` — bad framework design.
The redesign replaces the fixed shape with a schema:
PrefsSchema = Record<string, PrefsDimension<TIntent, TEffective>>
Each dimension owns its own validator (`validate`), environment-fed
resolver (`resolve`) and optional sibling-derived value (`derive`).
The engine is generic over the schema and iterates it; it knows
nothing about "locale" or "theme" specifically.
Built-in dimensions live in `arts/prefs/dimensions/*` (locale,
language, theme, density, motion, timezone, currency, unit-system,
direction, plus boolean / enum / string / number primitives). The
`standardPrefsDimensions(catalog)` preset composes the canonical
set; apps spread it and add their own:
const schema = {
...standardPrefsDimensions({ languages, locales, currencies }),
sidebarCollapsed: booleanDimension({ default: false }),
notificationLevel: enumDimension(
['all', 'mentions', 'none'] as const,
{ default: 'mentions' }
)
};
Active surface exposes one slot per schema key with uniform verbs:
App.prefs.locale.get()
App.prefs.locale.set('es-ES')
App.prefs.locale.clear()
App.prefs.locale.onChange((v) => …)
App.prefs.sidebarCollapsed.set(true)
`setIntent('locale', value)` stays available as a low-level pass-
through (storage bridge consumes it generically) but UI code uses
the dimension surface.
## Lowercase core surface
`App.Logger`, `App.Bus`, `App.Timers`, `App.Orca`, `App.Prefs` are
gone. The "PascalCase for core, lowercase for services" rule was
visual signalling against JS convention, no technical benefit, and
created an asymmetry on the same object. All core members are now
lowercase, matching services:
App.logger
App.bus
App.timers
App.orca
App.prefs
`createPrefsStorageBridge` keeps its old responsibilities; sources
helpers (`prefsLocaleSource`, …) are gone — the dimension API
replaces them.
## What changed
- `$libs/prefs`: fully generic schema-based types + resolver. Old
fixed `PrefsCapabilities` / `PrefsIntent` / `PrefsEffective`
removed; replaced by `PrefsDimension`, `PrefsSchema`,
`PrefsEffectiveOf<S>`, `PrefsIntentOf<S>`.
- `arts/prefs`: engine + active wrapper rewritten to schema. Per-
dimension active surface auto-built from schema keys. Sources file
deleted (replaced by dimension surface). New
`arts/prefs/dimensions/*` and `arts/prefs/standard.ts`. Storage
bridge made schema-generic.
- `arts/active-app`: lowercase `CoreServices` / `ActiveAppCore`,
`prefs?: ActiveAppPrefsOptions<S>` root option carrying the
schema. `defineActivePrefs` deleted (prefs is core, not service).
`lang` / `format` / `frontend` factories migrated to read
`core.prefs.<dim>` directly via defensive `readSlot()` helpers
(each dimension is optional from the factory's POV; if the app's
schema omits one, the integration degrades gracefully).
- Presets, demos, web routes, README docstrings, `check-aliases.mjs`
guards, marketing snippets all migrated.
- Tests: `engine-prefs`, `active-prefs`, `storage-bridge`,
`resolve-prefs`, `validate-intent`, `prefs-consumer-wiring`,
`service-factories` rewritten for the schema-based API.
`sources.test.ts` deleted (sources file is gone).
## Verification
- `npm run check`: 0 errors, 0 warnings (1527 files).
- `npm test`: 1645 tests across 139 files, all green.
- `node scripts/check-aliases.mjs`: clean (lowercase enforced for
every member of `App.*`, including `Logger`/`Bus`/`Timers`/`Orca`/
`Prefs` which now flag as forbidden capitals).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
/**
|
|
|
|
|
* Nexo prefs schema. Composes the canonical built-in dimensions
|
|
|
|
|
* (`language`, `locale`, `currency`, `theme`, …) around the demo's
|
|
|
|
|
* catalogs. App-specific prefs would join the spread; today the demo
|
|
|
|
|
* doesn't have any beyond the standard set.
|
|
|
|
|
*/
|
|
|
|
|
export const NEXO_PREFS_SCHEMA = {
|
|
|
|
|
...standardPrefsDimensions({
|
|
|
|
|
languages: ['es', 'en'],
|
|
|
|
|
locales: ['es-ES', 'en-US'],
|
|
|
|
|
currencies: ['EUR', 'USD'],
|
|
|
|
|
defaults: {
|
|
|
|
|
language: 'es',
|
|
|
|
|
locale: 'es-ES',
|
|
|
|
|
currency: 'EUR',
|
|
|
|
|
timezone: 'Europe/Madrid'
|
|
|
|
|
}
|
|
|
|
|
})
|
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
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export interface CreateDatingAppOptions {
|
|
|
|
|
/**
|
|
|
|
|
* Override the Nexo API base URL. Defaults to the standalone server
|
|
|
|
|
* at `http://127.0.0.1:8787` (see `servers/dating/README.md`).
|
|
|
|
|
*/
|
|
|
|
|
readonly apiBase?: string;
|
|
|
|
|
/**
|
|
|
|
|
* Optional `fetch` override — typically `event.fetch` during SSR or
|
|
|
|
|
* a mocked transport in tests.
|
|
|
|
|
*/
|
|
|
|
|
readonly fetch?: typeof fetch;
|
|
|
|
|
/**
|
|
|
|
|
* Frontend mount target. SvelteKit pages pass the layout root here
|
|
|
|
|
* so theme / density / direction get applied to the right node.
|
|
|
|
|
*/
|
|
|
|
|
readonly frontendTarget?: () => HTMLElement | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Concrete `ActiveApp` shape exposed by `createDatingApp`. Components
|
|
|
|
|
* accept `App: DatingApp` to keep autocomplete and type-checked
|
|
|
|
|
* service access; using a generic `ActiveApp` here would degrade
|
|
|
|
|
* every service slot to `unknown`.
|
|
|
|
|
*/
|
|
|
|
|
export type DatingApp = ReturnType<typeof composeApp>;
|
|
|
|
|
|
|
|
|
|
function composeApp(options: CreateDatingAppOptions) {
|
|
|
|
|
return createActiveApp({
|
Prefs as schema-based core + lowercase App.* surface
Two structural changes that were overdue and got bundled because
they touched the same set of files.
## Prefs is now a schema, not a fixed shape
Previously every preference had to be declared in a closed
`PrefsCapabilities` interface (`languages`, `locales`, `currencies`,
`themes`, `densities`, `motions`, `timezones`, `unitSystems`). Adding
a new pref required forking `$libs/prefs` — bad framework design.
The redesign replaces the fixed shape with a schema:
PrefsSchema = Record<string, PrefsDimension<TIntent, TEffective>>
Each dimension owns its own validator (`validate`), environment-fed
resolver (`resolve`) and optional sibling-derived value (`derive`).
The engine is generic over the schema and iterates it; it knows
nothing about "locale" or "theme" specifically.
Built-in dimensions live in `arts/prefs/dimensions/*` (locale,
language, theme, density, motion, timezone, currency, unit-system,
direction, plus boolean / enum / string / number primitives). The
`standardPrefsDimensions(catalog)` preset composes the canonical
set; apps spread it and add their own:
const schema = {
...standardPrefsDimensions({ languages, locales, currencies }),
sidebarCollapsed: booleanDimension({ default: false }),
notificationLevel: enumDimension(
['all', 'mentions', 'none'] as const,
{ default: 'mentions' }
)
};
Active surface exposes one slot per schema key with uniform verbs:
App.prefs.locale.get()
App.prefs.locale.set('es-ES')
App.prefs.locale.clear()
App.prefs.locale.onChange((v) => …)
App.prefs.sidebarCollapsed.set(true)
`setIntent('locale', value)` stays available as a low-level pass-
through (storage bridge consumes it generically) but UI code uses
the dimension surface.
## Lowercase core surface
`App.Logger`, `App.Bus`, `App.Timers`, `App.Orca`, `App.Prefs` are
gone. The "PascalCase for core, lowercase for services" rule was
visual signalling against JS convention, no technical benefit, and
created an asymmetry on the same object. All core members are now
lowercase, matching services:
App.logger
App.bus
App.timers
App.orca
App.prefs
`createPrefsStorageBridge` keeps its old responsibilities; sources
helpers (`prefsLocaleSource`, …) are gone — the dimension API
replaces them.
## What changed
- `$libs/prefs`: fully generic schema-based types + resolver. Old
fixed `PrefsCapabilities` / `PrefsIntent` / `PrefsEffective`
removed; replaced by `PrefsDimension`, `PrefsSchema`,
`PrefsEffectiveOf<S>`, `PrefsIntentOf<S>`.
- `arts/prefs`: engine + active wrapper rewritten to schema. Per-
dimension active surface auto-built from schema keys. Sources file
deleted (replaced by dimension surface). New
`arts/prefs/dimensions/*` and `arts/prefs/standard.ts`. Storage
bridge made schema-generic.
- `arts/active-app`: lowercase `CoreServices` / `ActiveAppCore`,
`prefs?: ActiveAppPrefsOptions<S>` root option carrying the
schema. `defineActivePrefs` deleted (prefs is core, not service).
`lang` / `format` / `frontend` factories migrated to read
`core.prefs.<dim>` directly via defensive `readSlot()` helpers
(each dimension is optional from the factory's POV; if the app's
schema omits one, the integration degrades gracefully).
- Presets, demos, web routes, README docstrings, `check-aliases.mjs`
guards, marketing snippets all migrated.
- Tests: `engine-prefs`, `active-prefs`, `storage-bridge`,
`resolve-prefs`, `validate-intent`, `prefs-consumer-wiring`,
`service-factories` rewritten for the schema-based API.
`sources.test.ts` deleted (sources file is gone).
## Verification
- `npm run check`: 0 errors, 0 warnings (1527 files).
- `npm test`: 1645 tests across 139 files, all green.
- `node scripts/check-aliases.mjs`: clean (lowercase enforced for
every member of `App.*`, including `Logger`/`Bus`/`Timers`/`Orca`/
`Prefs` which now flag as forbidden capitals).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
// Prefs is core. Each schema key becomes a typed dimension on
|
|
|
|
|
// `App.prefs.<key>` (`App.prefs.locale.set('en-US')` etc.).
|
|
|
|
|
// Persistence is a follow-up: when we wire `arts/storage` →
|
|
|
|
|
// `PrefsIntentStorage`, attach `storage` here.
|
|
|
|
|
prefs: { schema: NEXO_PREFS_SCHEMA },
|
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
|
|
|
services: {
|
|
|
|
|
lang: defineActiveLang({
|
|
|
|
|
schema: NEXO_LANG_SCHEMA,
|
|
|
|
|
defaultLocale: 'es',
|
|
|
|
|
fallbackChain: ['en']
|
|
|
|
|
}),
|
|
|
|
|
storage: defineActiveStorage({ namespace: 'nexo' }),
|
|
|
|
|
frontend: defineActiveFrontend({
|
|
|
|
|
target: options.frontendTarget,
|
|
|
|
|
theme: 'base',
|
|
|
|
|
mode: 'auto',
|
|
|
|
|
density: 'comfortable'
|
|
|
|
|
}),
|
|
|
|
|
format: defineActiveFormat(),
|
|
|
|
|
cache: defineActiveCache({
|
|
|
|
|
defaultMemoryAdapter: { suppressProductionWarning: true }
|
|
|
|
|
}),
|
|
|
|
|
http: defineEngineHttp({
|
|
|
|
|
fetch: options.fetch,
|
|
|
|
|
timeout: 10_000,
|
|
|
|
|
retry: { limit: 1 }
|
|
|
|
|
}),
|
|
|
|
|
session: defineActiveSession<DatingUser>({ schemas: {} }),
|
|
|
|
|
sium: defineEngineSium()
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Compose a `DatingApp` plus the Nexo HTTP client. The two are
|
|
|
|
|
* returned together because almost every component needs both —
|
|
|
|
|
* `App` for ecosystem state and `api` for typed server calls — and
|
|
|
|
|
* keeping the construction in one helper means the demo only has one
|
|
|
|
|
* canonical wiring point.
|
|
|
|
|
*
|
|
|
|
|
* Disposing the wrapper tears the App down; the API client is
|
|
|
|
|
* stateless and needs no lifecycle.
|
|
|
|
|
*/
|
|
|
|
|
export interface DatingAppHandle {
|
|
|
|
|
readonly App: DatingApp;
|
|
|
|
|
readonly api: DatingApiClient;
|
|
|
|
|
dispose(): void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function createDatingApp(options: CreateDatingAppOptions = {}): DatingAppHandle {
|
|
|
|
|
const App = composeApp(options);
|
|
|
|
|
const api = createDatingApiClient({
|
|
|
|
|
base: options.apiBase,
|
|
|
|
|
fetch: options.fetch
|
|
|
|
|
});
|
|
|
|
|
return {
|
|
|
|
|
App,
|
|
|
|
|
api,
|
|
|
|
|
dispose: () => App.dispose()
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Marker re-export so consumers that only need the type don't pull
|
|
|
|
|
* the runtime in just for `ActiveApp`.
|
|
|
|
|
*/
|
|
|
|
|
export type { ActiveApp };
|