Cualquier agente
Cloná seba-seb/okapi-sanity en /tmp/okapi y seguí su INSTALL.md para sumar Okapi a este proyecto.
Sanity + editor visual para Astro · open source
Okapi se suma a un proyecto Astro, nuevo o ya empezado, y le da dos cosas: Sanity como base de datos y un editor visual propio donde el equipo y los clientes arman y editan las páginas sobre el diseño real.
Lo instala tu agente de código, el que uses, siguiendo una guía del repo. Esta vista explica cómo pedírselo y cómo se trabaja después.
Producto
No es un template de sitio. Okapi es la capa de contenido y edición que se suma a un proyecto Astro. Tu proyecto sigue siendo tuyo: tus páginas, tu diseño y tu deploy. Okapi agrega:
Login, roles, borradores, historial, imágenes y CDN. No lo mantenemos nosotros. El Studio queda en /studio de tu sitio.
Nuestro editor visual en /studio#/editor. Se escribe sobre la página, se arrastran bloques, se estila con tus tokens y se arman componentes.
Las rutas que elijas se arman en el editor y se sirven como HTML estático. Solo las secciones con slider o logo 3D cargan JavaScript.
Okapi usa los nombres de rol de Sanity tal cual. A cada persona le das un rol cuando la invitás al proyecto de Sanity, y src/okapi.policy.ts decide qué ve ese rol en el editor. Esto es lo que viene por defecto; la agencia lo cambia por proyecto. Un Administrator puede ver cualquier rol con View as.
| Puede… | Administrator | Editor | Contributor |
|---|---|---|---|
| Editar textos, rich text, links e imágenes | Sí | Sí | Sí |
| Reordenar bloques dentro de lo diseñado | Sí | Sí | Sí |
| Insertar, duplicar y borrar bloques | Sí | Algunos bloques | No |
| Panel de estilo (espaciado, color, radio, breakpoints) | Sí | No | No |
| Crear, editar y separar componentes | Sí | No | No |
| Usar el chat con IA (si el proyecto lo tiene) | Sí | Sí | No |
| Publicar | Sí | Sí | No |
Lo habitual: la agencia es Administrator, un cliente que publica es Editor, y alguien que solo deja borradores es Contributor. Viewer (y cualquier otro rol) solo mira. Editor y Contributor existen desde el plan Growth de Sanity; ver Roles e invitaciones.
Con tu agente
La instalación está escrita en el INSTALL.md del repo, pensado para que lo siga cualquier agente de código: Claude Code, Codex, Cursor u otro. Abrí tu agente en la carpeta del proyecto y pedíselo.
Cloná seba-seb/okapi-sanity en /tmp/okapi y seguí su INSTALL.md para sumar Okapi a este proyecto.
Sumá Okapi a este proyecto.
Antes de tocar nada te muestra el plan (qué bloques se van, qué dependencias se sacan, qué roles quedan) y espera tu OK. El chat y los componentes se pueden prender o apagar después en src/okapi.config.json.
Lo que necesita tu cuenta. El agente te pasa el comando o los pasos exactos:
La guía está escrita para que ningún agente los necesite. Si te pide uno, no se lo des: decile que siga INSTALL.md.
Con tu agente
Con Okapi instalado, esto es lo que más se pide. Sirve para cualquier agente: las reglas que sigue están en el AGENTS.md que queda en tu proyecto.
Conectar tu proyecto
Pasá el Hero de la home a un bloque de Okapi: título, bajada, botón e imagen editables desde el editor. El diseño tiene que quedar igual.
Asigná nuestros colores, espaciados y radios a los tokens de Okapi, sin cambiar los nombres, para que el panel de estilo ofrezca nuestra paleta.
Que las páginas del editor usen nuestro header y footer en vez de los de Okapi.
Que /servicios salga del editor: armala en Sanity con bloques de Okapi y sacá la página hardcodeada.
Bloques
Creá un bloque Testimonios: foto, cita, nombre y cargo. Grid de 3 en desktop y una columna en mobile. La cita se edita sobre la página.
Borrá los bloques de ejemplo de Okapi que no usamos en este proyecto.
Permisos y chat
Que los clientes puedan agregar títulos, párrafos e imágenes, pero no cambiar estilos ni borrar secciones.
Prendé el chat con IA del editor con Anthropic, ofrecé solo Sonnet y Haiku, y decime exactamente qué cargar en Vercel.
Publicar y mantener
Conectá publicar en el editor con el deploy de Vercel y decime exactamente qué tengo que configurar yo.
¿Cómo invito a Ana (ana@cliente.com) para que edite textos pero no publique?
Traé la última versión de Okapi a este proyecto: actualizá src/core desde el repo, avisame si cambió algún schema y corré verify.
En el editor, al arrastrar una card dentro del grid se va al final. Esto es de Okapi: decime qué cambiar en el repo.
Con tu agente
La división que más ordena el trabajo:
| Se hace en el editor | Se le pide al agente |
|---|---|
| Textos, links e imágenes | Bloques nuevos o hacer editable algo que ya existe |
| Agregar, ordenar y duplicar secciones y bloques | Cómo se ve un bloque |
| Estilo de un bloque dentro de los tokens | Los valores de los tokens (tu diseño) |
| Armar y reusar componentes | Rutas, layout, header y footer de las páginas del editor |
| Publicar | Conectar publicar con el deploy |
src/core es igual en todos los proyectos: si algo del editor falla, el arreglo va al repo y después se trae a cada proyecto.Producto
La idea central: el mismo componente PageBuilder dibuja la página en el editor y en el sitio. Lo que ves mientras editás es el sitio real, con sus estilos.
useEditStateuseDocumentOperationloadSite() + una query por página, sin CDN[data-island] + Studio en /studioEl canvas vive en su propia ruta (/edit-canvas), separado del Studio:
postMessage del mismo origen, un editor ya autenticado.Cada bloque lleva en su raíz data-sanity-path con su ruta GROQ dentro del documento, por ejemplo:
sections[_key=="a1f3"].content[_key=="9c2e"].items[_key=="77d0"]
Esa ruta une el clic en el canvas, la capa seleccionada, el panel de propiedades y la escritura en Sanity. Las operaciones de estructura (core/editor/ops.ts) son funciones puras sobre esas rutas, validadas contra el registro de bloques. El historial de deshacer es una pila de snapshots del documento.
Producto
Las páginas son estáticas (output: "static"). Si el chat con IA está prendido y el hosting corre funciones (Vercel, Netlify, Cloudflare Pages), Astro agrega una sola función de servidor, /api/editor-chat; todo lo demás sigue prerenderizado. Con el chat apagado no hay adaptador. En el build, cada página pide su documento a Sanity y loadSite() trae settings, navegación, el mapa de rutas y la librería de componentes. Las lecturas del build salen sin la CDN de Sanity, a propósito, y un build de producción se frena si Sanity no responde o falta el env, así un fetch fallido nunca deploya un sitio vacío.
El PageBuilder se renderiza en el servidor. Cada sección de primer nivel que contiene un bloque con js en el registro se envuelve como isla:
<!-- display: contents → no altera el layout -->
<div data-island='{"section":{…},"ctx":{…},"components":{…}}'>
<section class="ok-section" data-ok-theme="dark">…HTML ya renderizado…</section>
</div>Base.astro incluye un script mínimo. Si la página tiene al menos una isla, importa core/render/hydrate.tsx, que hace hydrateRoot sobre cada isla con los mismos props. Si no tiene ninguna, la página no carga React.
| Quién descarga qué | JS (gzip) | Cuándo |
|---|---|---|
Página sin bloques con js | ≈1 KB | Siempre (solo el script que busca islas) |
| Página con islas: React DOM, bloques, Portable Text | 91 KB | Si hay al menos una isla (slider, logo 3D) |
| three.js del bloque 3D Logo | 140 KB | Solo si la página tiene ese bloque |
| Studio + Okapi Editor | ≈1.650 KB | Solo quien entra a /studio |
| Canvas del editor | 144 KB | Solo dentro del editor |
npm run verify controla esto en cada build: ningún HTML público referencia código del editor ni del Studio, y la home no supera 250 KB de JS.
metaTitle, metaDescription, ogImage y noindex. Si están vacíos, usan los valores de Settings, y si tampoco hay ahí, los del código.og:*, Twitter card, sitemap.xml, robots.txt y JSON-LD Organization en la home.pageHref() y resolveLink(). Los links internos son referencias a páginas, no paths escritos a mano. Agregar idiomas más adelante es cambiar esas dos funciones.Producto
Es la primera pestaña del Studio. Tiene tres columnas: páginas y capas, canvas y propiedades. Arriba están deshacer y rehacer, + Add, el selector de dispositivo y Publish. La columna derecha tiene dos pestañas, Properties y ✦ Chat. Las dos columnas laterales se agrandan arrastrando el borde (doble clic las resetea).
| Atajo | Acción |
|---|---|
| ⌘ Z · ⌘ ⇧ Z | Deshacer · rehacer (el tipeo se agrupa) |
| ⌘ D | Duplicar la selección |
| ⌘ C · ⌘ V | Copiar · pegar bloques (también entre páginas) |
| ⌫ | Borrar la selección |
| ⌥ ↑ · ⌥ ↓ | Mover un lugar arriba · abajo |
| Esc | Seleccionar el bloque padre |
| ⌘ B · ⌘ I · ⌘ K | Negrita · itálica · link, mientras editás rich text |
Todo lo que hacés se guarda solo, como borrador. Nadie lo ve hasta que tocás Publish. Después de publicar, el sitio se actualiza en el próximo build: un par de minutos si el proyecto tiene el webhook configurado.
El editor usa el almacén de documentos del propio Studio, sin otro SDK. Los cambios se aplican al instante en local y se guardan como borrador. Publish publica el documento. Como el sitio es estático, publicar dispara un rebuild (ver Hosting y publicación).
Mientras escribís, el canvas retiene los renders que llegan del editor. Al terminar, restaura el DOM exacto que React espera y recién ahí aplica el valor nuevo. El rich text se convierte entre Portable Text y HTML editable en core/editor/richtext.ts, y mantiene los destinos de los links existentes por su clave.
Son mensajes postMessage del mismo origen, tipados en core/editor/protocol.ts.
| Dirección | Mensaje | Para qué |
|---|---|---|
| Editor → canvas | render | Documento, rutas y componentes a dibujar |
| Editor → canvas | select | Resaltar la selección, con scroll opcional |
| Canvas → editor | ready | El iframe cargó |
| Canvas → editor | select | Clic en un bloque (con add para multi-selección) |
| Canvas → editor | text | Texto editado (string o Portable Text) |
| Canvas → editor | move | Arrastre en el canvas: antes o después de otro bloque |
| Canvas → editor | drop | Archivo de imagen soltado sobre un bloque |
| Canvas → editor | key | Atajos presionados con el foco en el canvas |
Producto
Todo el diseño sale de src/theme/tokens.css, el primer archivo que se toca en cada proyecto. Ahí están los colores por tema (light, dark, brand), los radios, la escala tipográfica, el espaciado y el ancho de página. Tailwind 4 los expone como utilities (bg-ok-surface text-ok-fg gap-ok-md rounded-ok-main), y como los colores leen el data-ok-theme de la sección, cada bloque se adapta solo al tema. La paleta por defecto de Tailwind no está, a propósito: un color nuevo se agrega como token. Todo lo que define Okapi lleva prefijo (variables --ok-*, clases ok-*, utilities ok-), así que no choca con el Tailwind, shadcn o DaisyUI del proyecto.
Cada bloque tiene un campo design que el editor muestra como panel de estilo. Hay solo tokens, no CSS libre, y se puede definir para todas las pantallas, tablet y mobile.
| Propiedad | Valores |
|---|---|
| Margin, padding | none xs sm md lg xl 2xl 3xl |
| Fondo, color de texto | bg surface surface-2 fg muted brand brand-contrast border |
| Radio | none sm md lg main pill |
| Ancho máximo | prose 65ch · narrow 40rem · page · full |
| Alineación de texto, ocultar | left center right · ocultar por breakpoint |
| Breakpoints | tablet ≤ 1023px · mobile ≤ 767px (desktop-first) |
core/style/stylesheet.ts convierte esos valores en una sola hoja de estilos por página, con un selector por bloque. Los valores del panel van con !important porque el panel es la autoridad final sobre ese bloque:
[data-b="9c2e"][data-b] { padding-top: var(--ok-space-lg) !important; }
@media (max-width: 767px) {
[data-b="9c2e"][data-b] { padding-top: var(--ok-space-sm) !important; }
}Es el contenedor libre: apila, pone en fila o arma una grilla, con gaps de token, alineación y wrap. Se puede anidar. En tablet las grillas pasan a 2 columnas y en mobile a 1; las filas se apilan en mobile.
Producto
Un componente es un documento component con secciones. Una página lo usa con un bloque componentRef (una instancia). Si editás el componente, cambian todas sus instancias.
Si hacés doble clic en un texto o soltás una imagen dentro de una instancia, el cambio se guarda solo en esa instancia, en su lista overrides. El original no se toca. Cada override apunta a "<claveDelBloque>.<campo>", se aplica al renderizar (core/render/overrides.ts) y el panel los lista con un botón Reset.
Los componentes son secciones enteras y solo viven en el primer nivel de una página. No se anidan ni se insertan adentro de otra sección. Ver Lo que sigue.
Producto
Cada proyecto decide quién puede qué en src/okapi.policy.ts: cada rol de Sanity tiene una lista de permisos y, opcionalmente, los tipos de bloque que puede agregar. El editor y el chat lo respetan. El chat nunca puede más que la persona que lo usa.
El rol se pone en Sanity, no en Okapi. Las personas se invitan en sanity.io/manage → el proyecto → Members → Invite, eligiendo el rol ahí (se cambia después desde la misma lista), o desde la terminal: npx sanity users invite ana@cliente.com --role editor.
| Rol de Sanity | Plan de Sanity | Qué permite Sanity | En Okapi (por defecto) |
|---|---|---|---|
administrator | Todos | Todo, incluida la configuración del proyecto | Todo |
editor | Growth o superior | Leer, escribir y publicar | Textos, imágenes, reordenar, algunos bloques, publicar, chat |
contributor | Growth o superior | Escribir borradores, sin publicar | Textos, imágenes, reordenar |
viewer | Todos (asiento gratis) | Solo leer | Solo mirar |
developer y otros | Growth o superior | Según el rol | Solo mirar, salvo que la política lo nombre |
Solo existen administrator y viewer, así que un cliente que edite tiene que ser administrator y ve todo. Para separar agencia y cliente hace falta el plan Growth.
| Permiso | Permite |
|---|---|
text | Editar textos, rich text y links |
images | Cambiar imágenes |
style | El panel de estilo (solo tokens) |
settings | Ajustes de bloque: variantes, tamaños, layout |
reorder | Mover bloques arriba y abajo |
structure | Agregar, duplicar y borrar bloques (limitado por blocks) |
components | Crear, separar e insertar componentes de la librería |
publish | Publicar |
chat | Usar el chat con IA |
El límite real es el rol de Sanity: lo que la API le deja escribir a esa persona. Alguien técnico puede escribir lo que su rol permita sin pasar por el editor. Dale a cada cliente el rol de Sanity más chico que alcance.
/api/editor-chat.AI_MODELS limita la lista; el primero es el predeterminado.features.chat: false en src/okapi.config.json el sitio queda 100% estático.Producto
Okapi trae estos bloques de ejemplo. Al instalar, npm run okapi:setup saca los opcionales que no elegiste (Slider, Logo 3D); después cada proyecto suma los suyos. El registro único está en src/blocks/meta.ts. Define qué tipos existen, qué pueden contener, cuáles necesitan JS y qué ofrece el editor en cada lugar. schemas.ts y components.tsx están tipados contra el registro: si falta algo, el compilador lo marca.
| Bloque | Contiene | JS | En el canvas |
|---|---|---|---|
| Section | Cualquier bloque | No | Tema, espaciado, fondo (soltar imagen) |
Component componentRef | Una instancia de la librería (si los componentes están prendidos) | Según contenido | Overrides de texto e imagen |
| Eyebrow | — | No | Texto en el lugar |
| Heading | — | No | Rich text en el lugar |
| Paragraph | — | No | Rich text en el lugar |
| Button Group | Button | No | — |
| Button | — | No | Label en el lugar |
| Image | — | No | Soltar imagen |
| Looping Video | — | No | — |
| Content | Eyebrow, Heading, Paragraph, Button Group | No | — |
| Card | — | No | Título y cuerpo en el lugar, soltar imagen |
| Grid | Card, Content, Image, 3D Logo | No | — |
| Box | Cualquier bloque | No | — |
| Slider | Card, Image | Sí | Opcional al instalar |
| 3D Logo | — | Sí · three.js aparte | Opcional al instalar |
Todos los bloques tienen además el panel de estilo. "JS" indica si hacen que su sección se hidrate en el sitio público.
Integración
La guía completa es INSTALL.md, en la raíz del repo, escrita para que la siga cualquier agente. Hay dos caminos y los dos terminan en los mismos pasos comunes: Sanity, .env, arranque, roles, producción y verify.
El repo de Okapi ya es un proyecto Astro completo: se clona, se le saca el .git y se corre npm install.
Primero el agente analiza el proyecto y muestra un informe con ✅, ⚠️ y 🛑. 🛑 es que no instala: Astro anterior al 7.3, Preact o Solid (Okapi pone el JSX del proyecto en React), Tailwind 3 (Okapi necesita Tailwind 4 y un proyecto no puede tener los dos) o Sanity ya montado. ⚠️ es que sigue solo si lo aceptás: Vue o Svelte, varios idiomas, rutas dinámicas en la raíz. El [slug] de Okapi le gana al [...slug] de un blog o de Starlight, así que los slugs del proyecto van en reservedSlugs, o las páginas del editor pasan a /p/<slug> con pagesBase. Un proyecto sin Tailwind no tiene problema: el Tailwind de Okapi va sin preflight y solo en sus layouts. El trailingSlash del proyecto queda como está: las rutas, links, canonicals y sitemap de Okapi lo siguen.
Después se copian piezas al proyecto sin pisar nada que ya exista. Si una ruta choca, el agente frena y pregunta.
| Se copia | Qué es |
|---|---|
src/core/ | El producto: editor, render, estilos, Sanity. No se modifica por proyecto |
src/blocks/ | Registro y bloques de ejemplo. Se borran o se suman bloques |
src/documents/, src/schema.ts | page, component, settings, navigation |
src/theme/ | Tokens (nombres que usa el editor) y CSS de los bloques. Solo lo cargan las páginas del editor |
src/layouts/Base.astro | Layout de las páginas del editor (o OkapiBase.astro si ya hay un Base) |
src/pages/[slug].astro, edit-canvas.astro | Páginas del editor y el canvas. index.astro solo si la home sale del editor |
sanity.config.ts, sanity.cli.ts | El Studio |
scripts/verify.mjs, check-editor-schema.ts | Chequeos |
src/okapi.config.json, src/okapi.policy.ts, scripts/okapi-setup.mjs | Qué usa el proyecto, quién puede qué, y el script que aplica las respuestas de la instalación |
src/chrome/, public/placeholder.svg | Header y footer de las páginas del editor (reemplazables) e imagen por defecto de bloques nuevos |
AGENTS.md | Reglas para agentes. Si el proyecto ya tiene uno, se agrega como sección |
Probado sobre el template de blog oficial de Astro: build y verify en verde, el blog intacto y src/core sin cambios.
Antes de instalar, el agente pregunta qué módulos quiere la persona y corre npm run okapi:setup con las respuestas: primero con --dry-run para mostrar el plan, después con --yes. Si falta una respuesta, el script no hace nada y dice cuál. Corrido en una terminal sin flags, hace las preguntas solo. Se niega a correr dentro del repo de Okapi.
| Flag | Pregunta | Si es no |
|---|---|---|
--slider | ¿Slider / carrusel? | Se borran el bloque y embla-carousel-react |
--logo3d | ¿Logo 3D? (~140 KB, solo en las páginas que lo usan) | Se borran el bloque y three |
--components | ¿Componentes reutilizables? | features.components: false: sin librería ni instancias |
--chat | ¿Chat con IA en el editor? | features.chat: false: sin adaptador ni función de servidor, 100% estático |
--roles | ¿Quién edita? administrator · administrator+editor · administrator+editor+contributor · keep (los nombres de rol de Sanity) | Escribe okapi.policy.ts con esos roles |
--demo | ¿Contenido de ejemplo? | Sin archivo de seed; la primera página se crea en el editor |
Los bloques se borran, así el sitio carga menos código. El chat y los componentes son flags que lee el core (core/config.ts), así el core es igual en todos los proyectos y se pueden cambiar después. Los chequeos pasan con cualquier combinación: probado con todo apagado, el JS de la página bajó y un build para Vercel salió 100% estático.
package.json: dependencias de Sanity, React 19 y Tailwind 4 (más Embla y three si quedan el slider y el logo 3D, y los adaptadores de hosting si el chat está prendido), engines: node >=22.12 y el script okapi:setup. Sanity en sus versiones actuales: sanity ^6.19, @sanity/client 8, @sanity/ui 4, @sanity/icons 5. Si el proyecto tiene más nuevas, se respetan; las más viejas se suben dentro de la misma mayor.astro.config: no se pega nada. Importa okapi(), okapiSanity(), okapiAdapter() y okapiSite() de src/core/astro/okapi.ts y los suma junto a react() y el plugin de Tailwind. okapi() trae la ruta del chat, el trailingSlash final del proyecto, el CDN de imágenes de Sanity y la lista de optimizeDeps, fusionados con lo del proyecto. El output y el site del proyecto se respetan.tsconfig: JSX de React y el alias @/* → ./src/*.Integración
Lo que tu código tiene que respetar para que el editor funcione. Todo lo demás es libre.
| Regla | Por qué |
|---|---|
Todo lo editable es un bloque registrado en src/blocks/meta.ts, schemas.ts y components.tsx | El registro define qué existe, qué contiene qué y qué ofrece el editor |
Un bloque recibe { block, path, ctx }, nunca hace fetch y pone {...editable(…)} en su raíz | El canvas dibuja el documento que recibe y selecciona por data-sanity-path |
js en meta.ts si el bloque corre algo en el navegador | Si no, el sitio lo sirve como HTML y nunca se hidrata |
Cada layout que renderiza <PageBuilder islands /> incluye <Islands /> | Es el que hidrata las secciones que necesitan JS (slider, logo 3D), y en dev instala el preámbulo de React |
Los nombres de tokens.css no cambian; los valores sí | El panel de estilo ofrece esos nombres |
Links con resolveLink() y pageHref() | Una sola fuente de URLs: links, sitemap, canonical, futuros idiomas |
| Okapi no trae librería de animación | Si el proyecto ya anima (GSAP, Motion, CSS), sigue con lo suyo y bajo su licencia. El editor no tiene controles de animación |
Las elecciones del proyecto viven en src/okapi.config.json y src/okapi.policy.ts | El core las lee, así nunca hay que editarlo por proyecto |
Íconos desde su ruta propia (@sanity/icons/Cube) y piezas de UI movidas desde la suya (@sanity/ui/tooltip) | Así las exportan las versiones actuales de Sanity |
Un proyecto no toca src/core: el texto editable y el soltar imagen se declaran por bloque en meta.ts. La única excepción es pageHref() si las páginas del editor se montan bajo un prefijo.
Trabajar con él
La regla que sostiene el producto: src/core es igual en todos los proyectos. Un arreglo en core se hace en el repo de Okapi y después se trae a cada proyecto. Lo propio de cada proyecto vive en blocks, theme y sus layouts. Cuando haya 3 o más proyectos en producción, core pasa a ser un paquete privado y los arreglos llegan con npm update.
{...editable(…)} en su raíz. Nunca hacen fetch: el canvas los dibuja con el documento que recibe.Foo.tsx y foo.ts en la misma carpeta.Trabajar con él
src/blocks/<tipo>/schema.ts con defineType y un ícono de @sanity/icons, más <Tipo>.tsx, que recibe { block, path, ctx }.
Entrada en meta.ts: título, slot si contiene otros bloques, margin, y js si corre algo en el navegador.
Registrarlo en schemas.ts y components.tsx. El compilador marca error hasta que esté.
Contenido al insertarlo en defaults.ts, texto editable (text) y soltar imagen (imageField) en la entrada del bloque en meta.ts. Si un rol tiene lista blocks, sumá el tipo ahí también.
// src/blocks/meta.ts quote: { title: "Quote", margin: true }, ticker: { title: "Ticker", js: true }, gallery: { title: "Gallery", js: (b) => b.layout === "carousel" },
// src/blocks/quote/Quote.tsx
export function Quote({ block, path, ctx }: BlockProps<QuoteData>) {
return (
<blockquote className="text-ok-h-lg text-ok-fg"
{...editable(ctx.documentId, ctx.documentType, path, block._type)}>
{block.text}
</blockquote>
);
}Si el componente ya existe en tu proyecto, es el mismo camino: el componente pasa a leer todo de block en vez de props o texto fijo, y si era .astro se pasa a .tsx.
Para borrar un bloque: su carpeta, su entrada en meta.ts, las dos entradas de los mapas y cualquier accepts que lo nombre. El compilador te lleva a cada una.
Trabajar con él
Sirve cualquier hosting. Build con npm run build, salida dist, Node 22.12+ y npm. Con el chat con IA prendido, usá Vercel o Netlify (incluidos) o Cloudflare Pages (npm i @astrojs/cloudflare wrangler una vez): Okapi elige el adaptador según dónde corre el build y suma la única función del chat. En otro hosting el sitio anda y el chat aparece como no disponible.
Sin el webhook, el cambio queda publicado en Sanity pero el sitio sigue mostrando la versión anterior. El webhook se crea en sanity.io/manage → API → Webhooks, con filtro _type in ["page","settings","navigation","component"].
| Variable | Para qué |
|---|---|
PUBLIC_SANITY_PROJECT_ID | Proyecto de Sanity |
PUBLIC_SANITY_DATASET | Normalmente production |
PUBLIC_SANITY_API_VERSION | Fecha de la API, por ejemplo 2024-10-01 |
PUBLIC_SITE_URL | Origen para canonical, og:url y sitemap. En Vercel, Netlify y Cloudflare usa la URL del deploy si falta. Vacía cuenta como no definida. |
SANITY_API_READ_TOKEN | Solo si el dataset es privado: token Viewer, leído en el build |
ANTHROPIC_API_KEY · OPENAI_API_KEY · OPENROUTER_API_KEY | Solo con el chat prendido: la clave del proyecto por proveedor, nunca PUBLIC_. Ponele tope de gasto en el proveedor |
AI_MODELS | Opcional: los modelos que ofrece el chat, por ejemplo anthropic:claude-sonnet-5-5, anthropic:claude-haiku-5-5. El primero es el predeterminado |
Cada dominio desde el que se abra /studio tiene que estar en los orígenes CORS del proyecto, con credenciales. Sin eso, el Studio pide "Connect this studio to your project".
La URL del deploy hook es un secreto: quien la tenga puede disparar builds. Guardala solo en el webhook de Sanity y en el hosting.
Trabajar con él
npm run verifyCorre antes de cada entrega y después de cualquier cambio en core. Revisa lo que se rompe sin avisar:
routes.check).check-editor-schema).ops.check).stylesheet.check).overrides.check).chat.check).richtext.check).npm run verify -- https://dominio), además responden /, /studio y /edit-canvas.Los flujos que necesitan login no se pueden automatizar sin credenciales. Se prueban a mano, en una página descartable, antes de entregar un sitio:
npm run build, y el cambio está en la página estática.okapi.policy.ts; el chat rechaza el resto.Trabajar con él
Todos ya aparecieron alguna vez y tienen solución.
| Síntoma | Causa | Arreglo |
|---|---|---|
| "Connect this studio to your project" | El origen no está en CORS. Cada puerto cuenta como un origen distinto. | Agregar el origen exacto con credenciales. En local se usa :4321. |
Studio o canvas en blanco + 504 (Outdated Optimize Dep) | Vite re-optimizó dependencias con la pestaña abierta. | Parar el server, rm -rf node_modules/.vite, npm run dev, ⌘⇧R. |
| Studio en blanco justo después del login | La pestaña pide chunks de un server o deploy anterior. | staleChunkReload recarga una vez sola. Si sigue: ⌘⇧R. |
| Studio en blanco solo en Brave | Shields y el routing. | Resuelto con hash routing y basePath: "/". |
| La home muestra la pantalla de bienvenida | Falta .env o el contenido. | Completar .env e importar el seed. |
El build se frena con [loadQuery] … | Sanity no respondió o falta el env. Frena a propósito para no deployar un sitio vacío. | Revisar PUBLIC_SANITY_* en el hosting y el estado de Sanity, y volver a buildear. |
| "can't detect preamble" en dev | El layout no incluye <Islands />. | Agregarlo al final del <body>. |
| Publiqué y el sitio no cambió | El sitio es estático. | Revisar el webhook al deploy hook o correr un build. |
| La pestaña ✦ Chat dice que no está disponible | No hay clave de IA en el hosting, el hosting no corre funciones, o features.chat está apagado. | Cargar una clave (ANTHROPIC_API_KEY…), usar Vercel/Netlify/Cloudflare y redeployar. |
Contexto
Por qué está hecho así. Antes de cambiar alguna de estas, conviene leer el motivo.
Problema. Presentation era lento para editar: había que hacer clic en el overlay, buscar el campo en un formulario y esperar la ida y vuelta. No se podía escribir sobre la página y dependía de 7 parches.
Decisión. Reemplazar solo la UI de edición, con APIs públicas del Studio. Presentation, /preview, el draft mode y visual-editing se eliminaron.
Problema. Login, roles, borradores, historial, tiempo real e imágenes son meses de infraestructura.
Decisión. No construir eso: Sanity lo resuelve y el Studio sigue disponible para settings, menús y colecciones.
Problema. El CSS del sitio y el del Studio no pueden mezclarse, y una ruta del canvas con datos expondría borradores.
Decisión. Un iframe del mismo origen que solo dibuja lo que le manda el editor autenticado. Además da breakpoints reales.
Problema. Hidratar toda la página costaba 138 KB de JS aunque casi nada se moviera.
Decisión. Sin Presentation ya no hace falta hidratar todo: HTML puro y React solo en las secciones con js.
Problema. Okapi fijaba sanity@6.3.0 por parches escritos para Presentation. Los proyectos con un Sanity más nuevo no podían instalarlo limpio.
Decisión. Presentation ya no está, así que los parches también se fueron. Okapi sigue las versiones actuales de Sanity; typecheck y verify detectan los cambios que rompen.
Problema. El CSS libre y el posicionamiento absoluto equivalen a construir un page builder entero, y rompen la consistencia de la marca.
Decisión. El panel solo ofrece tokens. Los bloques nuevos se escriben en código y el editor compone y estila con ellos.
Problema. Un paquete privado desde el día uno agrega versionado antes de saber qué cambia por proyecto.
Decisión. Instalar copiando con una guía para agentes, con src/core intacto. Pasarlo a paquete cuando haya 3 o más sitios en producción.
Problema. El equipo no usa un solo agente, y una instalación atada a uno se desactualiza en los demás.
Decisión. Un INSTALL.md y un AGENTS.md en markdown plano. La skill de Claude Code solo los trae y los sigue.
Problema. Ningún proyecto actual lo pide.
Decisión. Todas las URLs pasan por pageHref() y resolveLink(). Agregar idiomas se hace en un solo lugar.
Problema. Traer GSAP y bloques animados ataba cada proyecto a una librería y a sus condiciones de licencia para constructores visuales, y sumaba peso.
Decisión. Okapi no anima. Los proyectos que ya animan siguen con sus herramientas. El JS de una página con islas pasó de 139 KB a 91 KB.
Problema. Una clave en el navegador la puede leer cualquier script del Studio, y el cliente la vería.
Decisión. Una función por sitio, /api/editor-chat, con la clave del proyecto en el hosting. Verifica la sesión de Sanity y el rol antes de preguntarle al modelo.
Problema. Instalar todo por defecto dejaba proyectos con módulos y dependencias que nadie pidió.
Decisión. El agente pregunta primero y okapi:setup aplica las respuestas siempre igual.
Contexto
Los hitos, agrupados. El detalle está en los commits del repo.
core, blocks y theme, con el registro tipado de bloques.link, Settings y Navigation, header y footer, SEO completo, npm run verify y CLAUDE.md./lab con sus tres sitios de estrés./preview y visual-editing. El sitio pasa a ser 100% estático.INSTALL.md y AGENTS.md para cualquier agente. Esta documentación.okapi.policy.ts, con View as.seba-seb/okapi-sanity, licencia MIT, marca y sitio en inglés y español.npm run okapi:setup deja solo esos y escribe okapi.config.json.patches/.Con tu agente
Contale a tu agente lo que ves, tal cual. Estos ya pasaron y tienen solución.
| Ves | Lo más probable | Decile al agente |
|---|---|---|
| "Connect this studio to your project" | Entraste por otro puerto. El local es :4321. | "Me aparece Connect this studio en localhost:<puerto>" |
| El Studio queda en blanco | La pestaña quedó vieja después de reiniciar el server. | Primero ⌘⇧R. Si sigue: "El Studio queda en blanco, mirá la consola" |
| La home muestra "Your visual page builder is ready" | Falta la configuración o el contenido. | "La home muestra la pantalla de bienvenida" |
| La pestaña ✦ Chat dice que no está disponible | El proyecto no tiene clave de IA en el hosting, o el chat está apagado. | "El chat dice que no está disponible" |
| Publiqué y el sitio no cambió | Falta el webhook o el build todavía no terminó. | "Publiqué y el sitio no cambió" |
| Una página mía dejó de verse igual | El CSS de Okapi se cargó fuera de sus páginas. | "Desde que sumamos Okapi, /<ruta> se ve distinta" |
Con tu agente
Pedile al agente "corré verify". Después, con tu usuario y en una página descartable:
okapi.policy.ts; el chat rechaza el resto.Con tu agente
/studio. Adentro está el editor.CLAUDE.md.okapi-astro trae INSTALL.md y lo sigue.src/core, el código del editor. Es igual en todos los proyectos y se arregla en el repo de Okapi.Contexto
Ordenado por impacto. Ninguno bloquea el uso actual.
| Qué | Por qué | Tamaño |
|---|---|---|
| Galería de la librería | Ver los componentes con una miniatura en vivo y arrastrarlos al canvas. Hoy son una lista de nombres. | Chico |
| Componentes de cualquier tamaño | Guardar una card o un botón como componente e insertarlo dentro de secciones, grids o boxes. | Medio |
| Insertar como copia o enlazado | Elegirlo al insertar desde la librería. | Chico |
| UI propia del Studio | Reemplazar la barra y el marco de Sanity con la API oficial (studio.components) para que el editor sea la app entera. | Medio |
| JS por bloque | Que una sección con solo un slider no cargue todos los bloques. Baja los 91 KB en páginas con poco JS. | Medio |
| Paquete npm privado | Instalar y actualizar con npm en vez de copiar src/core. Cuando haya 3 o más sitios en producción. | Medio |
| Idiomas | Cuando un cliente lo pida. La base está en pageHref y resolveLink. | Medio |
| Gizmo 3D en el canvas | Rotar el 3D Logo directamente en el canvas. Postergado. | Pausado |