# El paquete de UI (@joeblew999/remy-ui) (/dev/es/remy-ui)



La interfaz que comparten todas las apps de Remy\: componentes y tema de shadcn de fábrica, fuentes,
catálogos de Paraglide y ayudantes de locale, las páginas del sitio y de la app, el pegamento de
TanStack Start y las comprobaciones de Playwright que lo demuestran. Los consumidores son remy-auth
(renderizado en servidor) y [remy-auth-app](https\://github.com/joeblew999/remy-auth-app)
(prerrenderizado); ambos ejecutan las mismas comprobaciones.

## shadcn, de fábrica [#shadcn-stock]

Todo lo que hay en `src/components`, `src/hooks` y `src/styles/globals.css` es lo que escribe la CLI
de shadcn fijada en una versión concreta (diseño de monorepo\: el `components.json` de la app
encamina `shadcn add` hacia aquí), incluido `cn`, del propio paquete `cn` de shadcn; nada de eso se
edita a mano. `vp run ui:components` y `vp run ui:theme` los regeneran, y `vp run check:shadcn` (el hook de pre-commit, cuando un commit incluye alguno) falla ante cualquier desviación (ver
[ejecutar y probar](./run-and-test.md#shadcn-stock)). Los bloques son
copias propias en `src/blocks`.

## Uso [#usage]

Importa la hoja de estilo del paquete en el CSS de la app (aquí `src/styles.css`), y luego indica a
Tailwind dónde viven las clases propias de la app. `tailwind.css` importa `globals.css`, `fonts.css`
y `text.css` en ese orden y declara el `@source` propio del paquete\:

```css
@import "@joeblew999/remy-ui/tailwind.css";
@source "../src";
```

`fonts.css` nombra las fuentes de reserva (fallback) de fontaine, que genera `remyApp()` (fontaine se ejecuta ahí antes de Tailwind), de modo que el
[`vite.config.ts`](https\://github.com/joeblew999/remy-auth/blob/main/apps/remy-auth/vite.config.ts) de una app no añade
nada para ellas.

Cada página es de uno de dos tipos, y nunca se mezclan (`isAppPath` de `paths` es la regla; cada
app lista las suyas)\:

* **Páginas del sitio** (las `sitePaths` de la app), enmarcadas por `SiteShell`\: completas sin
  JavaScript, indexadas, en el sitemap, evaluadas por Lighthouse y Core Web Vitals.
* **Páginas de la app** (sus `appPaths`, bajo `/app`), enmarcadas por `AppShell` (el bloque
  sidebar-16 de shadcn)\: necesitan JavaScript y `pageHead` las marca `noindex`. `AppShell` tiene su
  propia exportación para que una página del sitio nunca lo descargue.
* **Navegación de la app**\: la lista propia de la app, en su configuración `defineRemyApp` (`app-config`).

Las páginas propias de remy-auth (las páginas de inicio y de formatos, las de formatos, reloj, demo, ubicación, ajustes y
cuenta de la app, sus listas de navegación, la reserva de demostración y sus comprobaciones) no son de la plataforma\: son `@joeblew999/remy-showcase`
(`packages/remy-showcase`, su README), para las apps que las muestran. Las tabletas y los ordenadores
obtienen la barra lateral (que se contrae a iconos); los teléfonos obtienen la barra inferior
(`blocks/bottom-nav`, piezas de shadcn de fábrica, ya que shadcn no tiene navegación inferior)
con las páginas principales y Más, que abre la barra lateral. Por qué\: [el plan](https\://github.com/joeblew999/remy-auth/blob/main/.plans/done/mobile-navigation.md).

Lo que tiene toda app basada en el paquete, para que las tareas y comprobaciones compartidas funcionen en ella
(remy-auth-app es el ejemplo prerrenderizado)\:

* **Sus ajustes de marco, una sola vez, en `defineRemyApp`** (`app-config`)\: su nombre (el del producto, [escrito una sola vez](./run-and-test.md#the-products-name)), el código fuente, los enlaces de la cabecera del sitio y
  de la barra lateral de la app (cada uno escrito con `linkOptions` de TanStack, de modo que se comprueba contra las propias
  rutas de la app donde está escrito). El marco compartido no nombra ninguna ruta salvo `/`, de modo que una app con otras páginas
  distintas a las de remy-auth tipa correctamente tal cual.
* **Su raíz renderiza las páginas dentro de `AppProviders`** (`providers`), con esa configuración y el idioma `preferred` del
  loader raíz\: dirección de lectura, el tema (el interruptor de la cabecera y la página Ajustes lo necesitan),
  los ajustes de marco y el idioma a ofrecer, que lee cada marco, de modo que ninguna página lo pasa. Las
  `themeChecks` compartidas fallan sin él.
* **Una ruta para cada path de `appPaths` y `sitePaths`**, cada una de pocas líneas sobre la página compartida (el
  Reloj de la demostración (showcase) propaga `clockRouteOptions` desde `@joeblew999/remy-showcase/clock-route`).
* **`tests/smoke.spec.ts`**, una sola llamada a `smokeChecks(...)`\: `vp run test:browser --grep smoke` y la prueba de humo contra un despliegue
  (`pnpm remy test live <origin>`) lo ejecutan, y fallan con «No tests found» si no existe.
* **`.plans/now.md`**, la única lista ordenada del trabajo pendiente (`remy plans check`, en `vp run ready`).
* **Instalaciones y actualizaciones mediante los comandos del paquete**, nunca a mano\: `vp run setup` (instalar,
  skills, MCP, las comprobaciones de la instalación) y `vp run upgrade <version>` (remy-ui y remy-cli en esa versión, el
  workflow de CI en el mismo tag, los scripts, hooks y configuración de la app en blanco, y después las mismas comprobaciones). Ambos toman el token de GitHub Packages de
  `gh auth token` y lo escriben en la configuración de pnpm de la máquina.

## Exportaciones [#exports]

Todas bajo `@joeblew999/remy-ui/`, como TSX y CSS para consumidores de Vite y Tailwind.

| Export                                                                         | Qué contiene                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tailwind.css`                                                                 | Las tres hojas de estilo de abajo en orden, más `@source` para las clases propias del paquete\: una sola importación para una app                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `globals.css`, `fonts.css`, `text.css`                                         | La hoja de estilo de shadcn tal como la escribe la CLI; las fuentes para cada idioma; cómo se corta el texto en cada idioma (guionización por `lang`, cortes de frase en japonés)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `components/*`, `hooks/*`, `button`                                            | Componentes y hooks de shadcn (`button` es también una ruta corta)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `paths`                                                                        | `isAppPath`\: si una ruta sin localizar es una página de app                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `shell`                                                                        | El marco del sitio\: `SiteShell` (alias `Shell`), `Intro`, `ZoneBadge`, `SkipLink`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `app-shell`                                                                    | Solo el marco de la app\: `AppShell` (la barra lateral, la barra inferior del teléfono), para las páginas de app propias de una app. Su pie de página tiene el sello de build (`versions`)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `app-config`                                                                   | `defineRemyApp` y sus tipos (`RemyApp`, `NavItem`)\: el nombre, el código fuente y la navegación de la app para cada marco; `useRemyApp`, `usePreferredLocale`. `brand` es [el nombre del producto](./run-and-test.md#the-products-name), el único lugar donde se escribe\: todo mensaje que lo menciona lo recibe como `{product}`; `pageTitle(title, brand)` es el título de una página tal como lo muestra la pestaña del navegador                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `root`                                                                         | `remyRoot(app, { devtools })`\: las opciones de la ruta raíz (el documento en el idioma y dirección de la página, `AppProviders`, el idioma a ofrecer, las páginas de problema); `RemyRouterContext`, `preferredLocale`. El `__root.tsx` de una app es `createRootRouteWithContext<RemyRouterContext>()(remyRoot(remyApp, { devtools: <TanStackDevtools … /> }))`\: las devtools permanecen en el archivo de la app, donde el plugin de Vite de TanStack las elimina de los builds de producción                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `router`                                                                       | `remyRouter(routeTree)`\: el router que crea cada app (la reescritura de locale, un QueryClient por solicitud con la integración SSR de Query, precarga por intención, el nonce de CSP)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `app/vite`                                                                     | `remyApp({ plugins, start, cloudflare, port, build, root })`\: la configuración de Vite de una app, y solo eso (TanStack Devtools, las partes, el sello de build, Cloudflare, Fontaine, Tailwind, Start, React; el catálogo propio de la app cuando tiene `project.inlang`); el `vite.config.ts` de la app la propaga y escribe sus tareas junto a ella                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `docs/vite`                                                                    | `remyDocs(docsConfig, { root, app })`\: la configuración de Vite del Worker de docs sobre el `docs/` de una app, igualmente solo la de Vite                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `vite-plus`                                                                    | Ajustes compartidos simples para un `vite.config.ts`\: `style` (el formateador, el linter con su comprobación de tipos, el paso del hook de commit; se propaga en la raíz del workspace) y `network` (las variables de proxy y certificado que necesita una tarea en caché que descarga, sin seguimiento en su clave de caché)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `providers`                                                                    | `AppProviders`\: dentro de lo que la raíz de una app renderiza sus páginas (dirección, tema, la configuración `defineRemyApp` de la app, el idioma `preferred`)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `language`                                                                     | `LanguageSwitcher` (enlaces simples, páginas del sitio), `LanguageMenu` (DropdownMenu de shadcn, páginas de la app), `LanguageHint`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `messages`, `runtime`                                                          | Mensajes y runtime compilados de Paraglide                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `locale`                                                                       | `getLocale`, `setLocale`, `localizeHref`, `localizeUrl`, `deLocalizeHref`, `cookieName` de Paraglide y más, además de `direction`, `localeName` y `followLocale(runtime)` (un segundo catálogo, el de la app o el de un paquete, en el idioma de la plataforma)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `locale-info`                                                                  | Calendarios, dígitos, reloj y convenciones de semana de Intl Locale Info; `formatLocale` (la etiqueta que usa cada formateador, que indica el calendario y los dígitos propios del idioma), `weekOrder`, `words` (Intl.Segmenter)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `matching`                                                                     | La estrategia `custom-chinese` de Paraglide (las etiquetas de chino tradicional llegan a `zh-TW`), `matchChinese`, `preferredFromHeader`, `preferredFromNavigator`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `seo`                                                                          | Canonical y alternativas `hreflang`; `sitemapXml({ origin, paths, extra })` (las páginas del sitio de la app en cada locale, y luego las entradas propias de la app), `robotsTxt(origin)`, `sitemapType`, `robotsType` para las dos rutas de servidor de la app                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `prerender`                                                                    | `prerenderPages({ notFoundPath, paths })`\: el `prerender.pages` de TanStack Start para una app prerrenderizada (cada página sin locale y por locale, robots.txt, sitemap.xml, el 404.html de cada locale)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `tanstack`                                                                     | `localizedWorker` (punto de entrada del Worker\: observabilidad, el middleware de Paraglide y redirecciones de entrada alrededor de TanStack Start), `localeRewrite`, `pageHead`, `suggestedLocale`, `suggestedLocaleInBrowser`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `client`                                                                       | `useSuggestedLocale`, `DeviceTime` para apps prerrenderizadas                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `worker`                                                                       | `withObservability` y los ayudantes de ID de solicitud; su `/healthz` indica qué despliegue respondió (el `Deployment` de `build`)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `build`, `build/vite`                                                          | [Qué versión está desplegada](./run-and-test.md#which-version-is-deployed). `build/vite`\: `remyBuild({ packages })`, el plugin de Vite detrás de `virtual:remy-build` (ya presente en `remyApp()` y `remyDocs()`), y `buildStamp()`. `build`\: las formas de Zod `stamp` y `deployment` con sus tipos `Build` y `Deployment`, `askDeployment(origin)`, `deploymentQuery(origin)` (opciones de TanStack Query\: consultado en el navegador, de nuevo cada cinco minutos), `sameBuild`, `shortCommit`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `versions`                                                                     | `BuildStamp` (una línea discreta\: el entorno cuando no es producción, el nombre del producto, el commit, y un control de recarga cuando el despliegue ha avanzado bajo la página) y `Versions` (esta app, su Worker de docs y los `deployments` que la app lista en `defineRemyApp`, cada uno respondiendo por sí mismo, y los paquetes de este build). Piezas de shadcn de fábrica; coloca cualquiera de las dos en cualquier lugar dentro de `AppProviders`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `cloudflare`                                                                   | `placeFromCloudflare`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `problem`                                                                      | Las páginas de problema localizadas\: `Problem`, `NotFound`, `ErrorPage`, `problemPages` (una línea de propagación por cada ruta de página)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `parts`, `parts/vite`, `parts/checks`                                          | Partes ([plan](https\://github.com/joeblew999/remy-auth/blob/main/.plans/done/parts.md))\: una app las lista en `src/parts.json`, una por línea\: las de la plataforma por nombre, las de otro paquete como `<package>/<name>`. `remyApp()` (mediante el `parts/vite` del paquete, `remyParts()`\: su `plugin` entre los plugins, sus `routes` como `tanstackStart({ router: { virtualRouteConfig } })`) monta las rutas de cada parte listada junto a `src/routes` y genera `virtual:remy-parts` (`parts`, `hasPart`); `partChecks()` en el archivo de pruebas ejecuta las comprobaciones de cada parte listada. La parte de la plataforma es `seo-routes` (robots.txt y el sitemap); la demostración (showcase) ofrece `time-zones`, `deferred-place` y `status-card` (`@joeblew999/remy-showcase/<name>`); ver [Escribir una parte](#writing-a-part)                                                                                                                                                                                                                                                                   |
| `rows`, `zod-csp`                                                              | `Group`, `Row`, `NameList`\: filas de etiqueta/valor; el interruptor jitless de Zod (lo importa AppProviders)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `invalidate`                                                                   | `invalidateEverything(router, queryClient)`\: todos los loaders y consultas marcados como obsoletos y recargados (tras cerrar sesión o un cambio de rol; la actualización de la tarjeta de estado)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `samples`                                                                      | Los valores fijos que renderizan las páginas                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `checks`, `problem.checks`, `build-boundaries.checks`, `code-splitting.checks` | Comprobaciones de Playwright compartidas\: páginas públicas, URLs de entrada (con la estrategia china), texto (`textChecks`\: 320 px, guionización, capitalización por idioma), fuentes (`fontChecks`\: la fuente que dibuja cada idioma es la que `fonts.css` nombra para su escritura), zonas, observabilidad, Lighthouse y Core Web Vitals, las páginas de problema, lo que descarga el navegador (`buildBoundaryChecks`) y la división de código (`codeSplittingChecks`)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `app-checks`                                                                   | Una llamada por tipo de app para el conjunto de comprobaciones compartidas sobre las páginas propias de la app\: `serverAppChecks({ service, sitePaths, appPaths, home, oneLanguage, cspEnforced })` (renderizada en servidor\: URLs de entrada con redirección, CSP, fuentes) y `prerenderedAppChecks({ service, sitePaths, appPaths, home })` (páginas de entrada estáticas), ambas con el sello de build y el nombre del producto (`versions.checks`); la app solo añade comprobaciones para lo que ella añade. Una app que muestra las páginas de demostración (showcase) también llama a `showcaseChecks({ product })` (`@joeblew999/remy-showcase/showcase.checks`; `product` es su `brand`); `problemChecks` es `problem.checks`                                                                                                                                                                                                                                                                                                                                                                                   |
| `playwright`                                                                   | `playwrightConfig()`, la configuración compartida de Playwright                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `api/server`                                                                   | `apiHandlers` (el OpenAPIHandler de oRPC como los handlers de una ruta de servidor de Start, con la página de referencia en `/api/doc` y el documento generado en `/api/openapi.json`; `errorStatuses`, el estado HTTP de cada uno de los códigos de error propios de la API, ya que oRPC mantiene los estados fuera de los errores; `origins`, los orígenes exactos de las apps registradas que permite el CORSHandlerPlugin de oRPC, ninguno por defecto; una respuesta a una solicitud con credenciales, y todo 401, es `no-store`), `generateSpec(router, info, errorStatuses)` (el documento OpenAPI 3.1), `ApiInfo`, `ErrorStatuses`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `api/guard`                                                                    | El guard para la versión de oRPC instalada, que importa el router de una app\: `guard()` (el middleware raíz del router\: `implement(contract).$context<...>().use(guard<User>())`), `GuardContext` (el `getSession()` del contexto\: la sesión de quien llama o null, que se pide solo cuando una política lo necesita, tal como lo comparte la guía de Better Auth de oRPC; y `relations`, el motor de relaciones de la app, para los procedimientos cuya política es una acción), `once` (esa búsqueda, ejecutada como máximo una vez por llamada), `signedIn(context)` (la persona para la que se ejecuta un procedimiento `session` o de acción), `noSession` (el contexto de una app que no da sesión a nadie); y todo lo de `api/guard-core` y `api/policy`. JavaScript plano, porque las comprobaciones de una app cargan su router en Node                                                                                                                                                                                                                                                                       |
| `api/guard-core`                                                               | El propio guard, sin ninguna importación de oRPC, de modo que funciona tanto en oRPC 1 como en 2\: `Policy` (`public`\: cualquiera, y la respuesta no nombra a ninguna persona; `session`\: una persona con sesión iniciada, y la respuesta es la suya propia; `{ action }`\: una persona con sesión iniciada que posee una relación a la que el vocabulario de la app concede esa acción, sobre el objeto que nombra el `id` de la entrada), `guardMiddleware({ ORPCError })` (impone la `meta.policy` de cada procedimiento; para una acción pregunta al motor de relaciones, responde 404 para un objeto inexistente antes que 403, y un procedimiento que no declara ninguna política nunca se ejecuta), `guardProblems(router, { personFields, vocabulary })` (qué está mal, una frase por cada problema\: sin política, sin guard por delante, una respuesta pública que nombra a una persona, una respuesta personal sin `meta.personal` que diga quién la recibe, una acción que el vocabulario no define o cuya entrada no puede nombrar su objeto), `policyOf`, `actionOf`, `isGuarded`, `walk`, `personFields` |
| `api/policy`                                                                   | Lo que un procedimiento del contrato declara para el guard, como metadatos de oRPC\: `policy('public' \| 'session')`, `personal('<quién la recibe>')` y `actions(vocabulary)`, que da `may('EDIT_NOTE')` exactamente para las acciones que define el vocabulario de la app, como en `oc.meta(may('EDIT_NOTE'))`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `api/relations`                                                                | El motor de relaciones\: quién puede hacer qué se responde mediante relaciones, a partir de las tablas propias de la app en su propia D1. `defineVocabulary({ objectTypes, relations, actions, grants })` (el vocabulario de la app como datos, con las formas de fila de remy-sport\: una relación se deriva de una fila de tabla, a través de un padre, de un rol de la plataforma, o la tiene todo el mundo), `relationEngine(vocabulary, db)` (`can`, `canAll`, `canFor` para los mapas de permisos de una lista entera, `holds`, `heldAmong`, `objectsHeldBy`, `usersHolding`, `audienceFor`, `objectExists`), `vocabularyProblems` y `schemaProblems` (el vocabulario contrastado consigo mismo y con el esquema que construyen sus migraciones), y los tipos `Can`, `ActionCode`, `ActionOn`, que llevan los nombres de acción de una app a su contrato y a sus páginas                                                                                                                                                                                                                                            |
| `api/client`                                                                   | `contractClient(contract, { origin })` (un cliente tipado para cualquier contrato\: OpenAPILink con ResponseValidationLinkPlugin, el idioma de la página como Accept-Language; sin `origin`, el propio de la página), `isomorphicClient` (el cliente propio de una app\: el router en el servidor, `contractClient` en el navegador, mediante `createIsomorphicFn`)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `api/coverage`                                                                 | `coverageProblems(router, { errorStatuses })` (cada procedimiento tiene una ruta bajo `/api/`, una política, una salida y errores documentados, cada uno con un mensaje y un estado HTTP), `procedures`, `routeOf` (los metadatos `openapi()` de un procedimiento), `statusesOf`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `api/checks`                                                                   | `apiChecks({ router, title, origins, errorStatuses, personFields, vocabulary })` (cobertura, el documento servido y la página de referencia, CORS exactamente para los `origins` registrados, y el guard\: `guardProblems` está vacío, y todo procedimiento que necesita una sesión responde 401 a un desconocido, sin caché)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `versions.checks`, `build.checks`                                              | `buildChecks({ service })` (`/healthz` indica el entorno y el build; el sello de una ejecución local es el commit de este checkout), `buildStampChecks({ path })` (el sello del marco, y el control de recarga solo cuando el despliegue responde con otro build), `productNameChecks({ paths })` (todo título termina con el nombre de la app; una app con otro nombre nunca muestra «Remy»). Los conjuntos de comprobación por app ejecutan los tres                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `allowed`, `allowed.checks`                                                    | `<Allowed can={row.can} action="EDIT_NOTE">`\: muestra sus hijos solo cuando el servidor permitió esa acción para este visitante sobre este objeto (`can` es el mapa que el servidor envió junto con la fila; solo tipa una acción que la fila lleva), de modo que una página nunca calcula los permisos por sí misma. `offeredActions(locator)` y `allowedActions(can)` permiten que la comprobación de una app compare lo que una página ofrece con lo que permite el servidor                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `environment`                                                                  | `environments({ production: {...}, local: {...} })`\: la tabla de una app de lo que permite cada entorno, una fila por capacidad; `permits(env, 'capability')`, `policyFor`, `environmentOf`. El entorno se declara (`ENVIRONMENT`), y todo lo ausente o desconocido es producción                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `mail`                                                                         | `mailerFor({ capture, binding, from })`\: correo mediante Cloudflare Email Service (el binding `send_email` del Worker), o guardado en el buzón de salida del Worker donde el entorno lo captura (`readOutbox`, `clearOutbox`); un rechazo dice para quién era el correo y por qué; un mensaje a una dirección a la que no puede llegar ningún correo (`unreachable`\: los dominios reservados de prueba y ejemplo) nunca se envía                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |

Las dependencias del paquete, en las versiones del catálogo, son lo que importa y ejecuta\: el framework (React,
TanStack Start, Router y Query), la cadena de herramientas (Vite, Wrangler, Tailwind, TypeScript) y las herramientas de las
comprobaciones (Playwright, Lighthouse); las herramientas que ejecutan las tareas compartidas (Wrangler, Playwright) son también las de `@joeblew999/remy-cli`.
Una app lista lo que importa ella misma, en las mismas versiones del catálogo; las que se rompen si se instalan dos veces se mantienen en
una sola copia mediante los `overrides` del `pnpm-workspace.yaml` de la app
([proyectos](./projects.md#choosing-the-version-released-development-or-local)).

## Idioma [#language]

El comportamiento de idioma es el de Paraglide\: estrategias `url`, `cookie`, `custom-chinese` (el
propio hook de estrategia personalizada de Paraglide, en `matching.js`), `preferredLanguage`,
`baseLocale`, con cada locale prefijado en la URL, configurado una sola vez en `paraglide.mjs`, que
compila `messages/*.json` durante la generación de tipos y el build de Vite. Pasa `{ locale }`
explícitamente en cada llamada de mensaje; no existe un locale global para todo el proceso. Una app
renderizada en el servidor ejecuta el middleware y propaga la preferencia del visitante; una app
prerrenderizada la resuelve en el navegador tras la hidratación; ambas renderizan los mismos
componentes. Los idiomas son los `locales` de `project.inlang/settings.json`; todos los catálogos
salvo inglés y español (árabe, persa, hebreo, tailandés, japonés, chino tradicional, hindi, amárico,
polaco, turco y alemán) están escritos por un agente y sin revisar.

## Publicación [#publishing]

El paquete es `@joeblew999/remy-ui` en GitHub Packages (ahí el scope debe coincidir con el
propietario de GitHub), publicado con `@joeblew999/remy-cli` bajo la misma versión (el grupo `fixed` de Changesets en
`.changeset/config.json`); la demostración (showcase) y el contrato llevan su propia versión y se publican cuando un changeset
las subió. Ninguno de los dos se publica todavía en esta forma.

Todo cambio en un paquete publicado lleva un changeset, escrito junto con él\: `pnpm changeset` (qué paquetes, patch,
minor o major, y una línea para el registro de cambios), confirmado como `.changeset/<name>.md`. `vp run release`, desde esta
máquina, se detiene en el primer fallo\: `changeset version` (las versiones nuevas y la sección de `CHANGELOG.md` de cada paquete
a partir de los changesets pendientes), `scripts/version.ts` (las versiones fijadas de la app en blanco, `template/pnpm-workspace.yaml` y
el tag de `template/.github/workflows/ci.yml`, llevados a la nueva versión de remy-ui, y después el lockfile), las traducciones
completas (`remy i18n check --strict`), todas las comprobaciones (`vp run ci`), la prueba de la app nueva y, por último, `vp run release:push`
(el commit «Release vX.Y.Z», subido a main). Sus pasos están en [Comandos](./commands.mdx).
Un push a main ejecuta un solo job en GitHub, la publicación (nunca una sesión en la nube, que no puede subir ningún tag), y ninguna comprobación, ya que el hook del push las ejecutó\:
`vp run release:publish` (`changeset publish`, después de generar y construir los paquetes y de crear la skill `remy`),
y luego, una vez por versión, el tag `vX.Y.Z` y `gh release create` con las notas generadas por GitHub. Los workspaces dependen unos de otros con `workspace:*`, que el
publish de pnpm escribe como la versión, y el rango de peer del contrato es abierto. Core Web Vitals se miden sobre un
despliegue (producción, o el Preview de una rama)\: las páginas tal como las reciben los visitantes desde Cloudflare.

El `.npmrc` de un consumidor solo nombra el registro\:

```
@joeblew999:registry=https://npm.pkg.github.com
```

pnpm no lee ningún token del `.npmrc` de un repositorio, así que el `vp run setup` de una app escribe un token con `read:packages`, tomado
de `gh auth token`, en la configuración de pnpm de la propia máquina (`pnpm config set --location global`); el workflow de GitHub pone
su token ahí del mismo modo antes de `vp install --frozen-lockfile`. Las versiones publicadas están en el
[registro de cambios](https\://github.com/joeblew999/remy-auth/blob/main/packages/remy-ui/CHANGELOG.md), que escribe Changesets.

## Escribir una parte [#writing-a-part]

Una parte es una funcionalidad que una app activa o desactiva con una línea en su `src/parts.json`. Las partes de la plataforma se
listan por nombre (`"seo-routes"`); cualquier paquete puede ofrecer partes también, listadas como `"<package>/<name>"`
(`"@joeblew999/remy-showcase/time-zones"`), y funcionan de la misma manera.

1. **Carpeta\:** `src/parts/<name>/` en el paquete, con cualquiera de estos\:
   * `routes/`\: archivos de ruta de TanStack, montados junto a las `src/routes` propias de la app cuando la parte está listada;
   * módulos de entrada (p. ej. `ui.tsx`, `place.ts`) que la app importa como `virtual:remy-parts/<name>/<entry>`\:
     las exportaciones reales cuando está listada, `undefined` cuando no, de modo que la app escribe `{Card && <Card />}` o
     `getPlace?.()` y no se envía nada cuando la parte está desactivada. Un módulo por entrada mantiene la división de código;
   * `checks.js`, cuya exportación por defecto `(options, listed) => void` ejecuta sus comprobaciones de Playwright\: `partChecks()` la
     ejecuta solo cuando la parte está listada, con las `options[<name>]` propias de la app.
2. **Catálogo\:** las de la plataforma son `catalog` en `src/parts/list.js`; las de otro paquete son su
   `src/parts/catalog.json`, exportado como `<package>/parts/catalog.json`, con la misma forma\: `routes`, `requires` (partes que también deben
   estar listadas), `entries` (entrada → nombres de exportación), `app` (opciones que la app aporta desde su propio
   `src/parts/<name>.ts`, leído como `virtual:remy-parts/<name>/app`) y `sitePaths` (páginas del sitio que añade,
   para el sitemap y sus comprobaciones).
3. **Tipos\:** declara sus módulos virtuales en el `src/parts/virtual.d.ts` propio del paquete (el de otro paquete referencia
   el de la plataforma con `/// <reference types="@joeblew999/remy-ui/parts/virtual" />`). Una parte solo enlaza con
   sus propias rutas, o con las páginas de la app mediante sus opciones `app` (la miga de pan de la parte time-zones toma
   `parent`), nunca con una página que otra app pueda no tener\: el fixture consumidor lista una parte de la demostración (showcase) y
   la tipa sin las páginas de la demostración (showcase).
4. **Demuéstralo\:** `vp run ready` pasa con la parte listada, sin ella y con la lista
   vacía (ejecuta un build una vez tras editar `src/parts.json`, lo que regenera el árbol de rutas).

Lo que en cambio sigue siendo un módulo normal del paquete\: el código que toda app necesita (observabilidad), un hook
que una app debe llamar siempre (leave-guard) o los controles propios de una página compartida (search-params).
