@fsaldivar.dev/planning
v0.6.0
Published
Composable planning components, block Markdown editor and read-only documents with Kairo, domain API, agent snapshots and transactional AI CLI.
Downloads
304
Readme
@fsaldivar.dev/planning
Planificación local y documentación viva para herramientas e IA. Incluye una API TypeScript y el CLI codaru-planning: ideas, épicas, historias, subtareas, criterios de aceptación, dependencias, revisiones documentales y entregas.
Este paquete contiene el modelo, las operaciones, el CLI y, desde la versión 0.2.0, la interfaz como piezas independientes: el editor por bloques con Kairo y cada zona de la aplicación de escritorio (barra lateral, barra de ventana, tablero, pestañas, propiedades, relaciones, criterios, listas). Son el mismo marcado y los mismos estilos que usa la aplicación. Cada pieza se monta por separado en tu propia aplicación y hereda su apariencia; consulta Componentes de interfaz. La aplicación Tauri completa está en CodaruPlanning; el paquete npm no instala un ejecutable macOS.
El CLI y la API de dominio requieren Node.js 22.12 o superior. No necesitan claves de IA ni servicios remotos. El CLI no ejecuta instrucciones encontradas dentro de los documentos.
Instalar
npm install @fsaldivar.dev/planning
npx @fsaldivar.dev/planning --helpTambién puedes instalarlo globalmente: npm install -g @fsaldivar.dev/planning.
Leer contexto sin cargar el espacio completo
codaru-planning path
codaru-planning index --limit 20
codaru-planning search "autenticación"
codaru-planning neighbors ID
codaru-planning read ID "Cómo funciona"
codaru-planning export ID
codaru-planning schemaPor defecto lee el espacio de la aplicación de escritorio. Para otro espacio, añade --workspace /ruta/workspace.json. index y search devuelven workspaceRevision, IDs, resúmenes, secciones y paginación (--limit, --offset, nextOffset). read devuelve Markdown; --format markdown imprime solo el documento. Los borradores de conocimiento se señalan, pero no se presentan como contenido verificado.
Escribir desde una IA
Toda escritura requiere --workspace explícito. Para comenzar en un espacio independiente:
codaru-planning init --workspace .codaru/workspace.json --name "Mi producto"
codaru-planning apply --workspace .codaru/workspace.json --file cambios.json --dry-run
codaru-planning apply --workspace .codaru/workspace.json --file cambios.jsoncambios.json, para un espacio recién creado con revisión 1:
{
"expectedRevision": 1,
"operations": [
{ "op": "create", "ref": "epic", "kind": "epic", "title": "Primera entrega" },
{
"op": "create", "ref": "story", "kind": "story", "parentId": "@epic",
"title": "Acceso al producto",
"markdown": "## Qué queremos lograr\nPermitir el acceso.\n\n## Cómo lo vamos a resolver\nValidar las credenciales.\n\n```mermaid\nflowchart LR\n A[Entrada] --> B[Validación]\n```",
"criteria": [{ "text": "Rechaza credenciales incorrectas", "checked": false }]
},
{ "op": "create", "ref": "doc", "kind": "knowledge", "title": "Autenticación", "markdown": "## Qué hace\nControla el acceso.\n\n## Cómo funciona\nPendiente de verificar." },
{ "op": "link", "source": "@story", "target": "@doc", "type": "modifies" }
]
}La salida devuelve los IDs asignados a refs, las fichas afectadas y la nueva revisión. Las referencias @nombre solo existen dentro del lote; usa los IDs devueltos en llamadas posteriores. --file - lee JSON de stdin. --dry-run valida sin escribir; sus IDs son provisionales.
| Operación | Campos principales |
| --- | --- |
| create | kind, title, ref opcional, parentId, summary, markdown o content, criteria |
| update | id, patch: título, resumen, prioridad, padre, criterios, evidencia, contenido de trabajo, design, labels o file |
| status | id, status: todo, doing, review, done; note opcional con el motivo |
| link, unlink | source, target, type: depends, modifies, references; desde nodos covers, implements, uses |
| node, unnode | Crea, actualiza o quita una pantalla, archivo de código o token (ver Grafo de construcción) |
| draft | id de conocimiento, markdown o content, evidence opcional |
| publish | id de conocimiento, evidence, contenido opcional; publica el borrador pendiente si existe |
| archive, restore | id; archivar incluye descendientes, restaurar requiere el padre activo |
| deliver | title, items terminados, notes opcional |
| rename, settings | name o patch de preferencias |
El lote acepta actor («FranPlanner», «persona»…): firma cada entrada de actividad que produzcan sus operaciones.
schema devuelve el contrato JSON completo. Markdown admite párrafos, títulos, listas, casillas, tablas, citas, separadores, enlaces, énfasis y bloques de código, incluidos mermaid. HTML arbitrario, imágenes y tachado se rechazan porque no están representados por el editor actual. También se admite el árbol content de ProseMirror, validado con el mismo esquema del editor. No se envían markdown y content juntos.
El CLI escribe Mermaid dentro de los bloques documentales; Kairo lo previsualiza y permite editarlo en la app. La creación de documentos Kairo independientes de esos bloques no forma parte de esta versión del CLI.
Para agentes: diseño, etiquetas, actividad y vista compacta
Pensado para agentes que crean y mueven tarjetas a partir de mockups aprobados y del código.
design: pantallas de un archivo de diseño,[{ file, screen, name?, image? }].fileeimageson rutas relativas (nuncahttp:nidata:), hasta 20 referencias sin repetirfile+screen.itemsByDesign(ws, file, screen)devuelve las fichas activas de una pantalla.labels: hasta 8 por ficha; se normalizan a minúsculas con guiones ("Ya construida"→ya-construida), máximo 24 caracteres. En el tablero se filtran conlabels: [...]o escribiendo#etiquetaen la búsqueda.file(solo conocimiento): ruta del archivo que contiene el documento;contentpuede ser un extracto. Las listas muestran la ruta y avisan cononOpenFile.- Actividad:
status,updatededesign/labels/criteria/file,archive,restore,linkyunlinkañaden una entrada{ at, op, actor?, note?, from?, to?, fields? }aitem.activity(máximo 200, se descartan las más antiguas). El historial la muestra junto a las revisiones. agentSnapshot(ws, { focus?, include? }): el tablero en pocos caracteres por tarjeta (id,kind,status,title,parent,labels,criteria: "2/5",design: n) máscountsyrelations; solo la ficha enfocusva completa (Markdown, criterios, evidencia, diseño, actividad). Con 70 tarjetas ocupa menos de un tercio que el espacio completo.workspaceDiff(prev, next):{ created, archived, restored, status: [{ id, from, to }], updated: [{ id, fields }] }para contar al agente solo lo que cambió.- Los enlaces Markdown relativos (
[0001](adr/0001-x.md),../datos.md#campos) se aceptan tal cual;javascript:,data:y cualquier otro esquema distinto dehttp,httpsymailtose rechazan.
{
"expectedRevision": 7, "actor": "FranPlanner",
"operations": [
{ "op": "create", "ref": "login", "kind": "story", "title": "Pantalla de acceso",
"design": [{ "file": ".codaru/Mockups.codarumockup", "screen": "s-login", "name": "Inicio de sesión", "image": "mockups/login.png" }],
"labels": ["propuesta", "iPad"], "criteria": [{ "text": "Contraste AA", "checked": false }] },
{ "op": "status", "id": "@login", "status": "doing", "note": "construida según Contraste" }
]
}Grafo de construcción
Une las tarjetas con lo que se construye a partir de ellas: pantallas aprobadas, archivos de código y tokens de diseño. Todo es opcional y aditivo; un espacio sin nodos funciona igual que antes.
- Nodos (
ws.nodes):{ id, kind: "screen" | "code" | "token", ref, label?, approvedHash?, hash? }. Elides estable y lo elige el host (p. ej. el id de la pantalla) y nunca coincide con el de una ficha.refes{ file, screen }en pantallas, una ruta relativa en código y el nombre en tokens.approvedHashsolo existe en pantallas y es la única fuente de «aprobada».hashes la versión actual de código y tokens. - Aristas:
covers(pantalla → tarjeta),implements(código → tarjeta; un archivo tiene una sola dueña) yuses(código → código o token). Se crean conlinky se quitan conunlink;unnodeborra el nodo y sus aristas. - Tarjeta:
paths(globs de su territorio),owns(archivos del cambio, los sella el host al cerrar) ybuiltAgainst(línea base con que se construyó;null= sin línea base).
Señales derivadas
Ninguna señal se guarda: se calculan de los hechos almacenados y se apagan solas cuando la tarjeta vuelve a coincidir con su línea base (re-aprobar al mismo hash, reconstruir). Nada reabre una tarjeta.
| Función | Devuelve |
| --- | --- |
| impact(ws, nodo) | A quién llega un cambio: changeset (la dueña del archivo; es su propio cambio), affected (una dependencia o el diseño cambió) y visual (pantallas a revisar, señal ligera). El radio de API/uses se detiene en la primera dueña de cada rama; el de tokens pasa y llega hasta las pantallas. |
| markAffected(ws, origen) | Desde un nodo, lo mismo que impact sin cambiar nada. Desde una ficha, como antes: marca la documentación que modifica como «por revisar» y la devuelve (docs). |
| baselineFor(ws, tarjeta) | { idDeNodo: hash } de lo que la tarjeta usa hoy: pantallas aprobadas que la cubren y lo que usa su código, sin entrar en el código de otras dueñas salvo para llegar a tokens. Guárdalo en builtAgainst al cerrar. |
| baselineFingerprint(baseline) | Una huella corta, si prefieres guardar builtAgainst como texto. Con una sola pantalla es su approvedHash. |
| cardSignals(ws, tarjeta) | Qué no coincide: design (pantalla), dependency (código o token) o baseline (con huella de texto no se sabe qué nodo). |
| verificationProposals(ws, cubierta?) | Tarjetas terminadas con señales y sin prueba que las cubra (cubierta(id) lo decide el host): proponer verificación, nunca reabrir. |
neighbors, contextRecords, exportItem y el CLI incluyen los nodos (node en lugar de item). agentSnapshot añade nodes y stale por tarjeta, y workspaceDiff añade nodes: { created, removed, updated }. Al cambiar el approvedHash de una pantalla, cada tarjeta que cubre registra una entrada approval en su actividad. En la interfaz, la tarjeta y las propiedades muestran «Diseño cambió» o «Dependencia cambió», y las relaciones con nodos se ven sin abrirse.
{
"expectedRevision": 12, "actor": "Codaru",
"operations": [
{ "op": "node", "id": "s-login", "kind": "screen", "ref": { "file": ".codaru/Mockups.codarumockup", "screen": "s-login" }, "approvedHash": "d1" },
{ "op": "node", "id": "code-login", "kind": "code", "ref": "src/auth/login.ts", "hash": "c1" },
{ "op": "link", "source": "s-login", "target": "CARD_ID", "type": "covers" },
{ "op": "link", "source": "code-login", "target": "CARD_ID", "type": "implements" }
]
}Reglas y concurrencia
expectedRevisiondebe coincidir con la última lectura. Un conflicto requiere volver a leer y revisar el lote; no se reintenta silenciosamente.- El lote se valida completo antes de guardar. Un fallo no deja cambios parciales en el espacio.
- Las dependencias circulares se rechazan. Para terminar trabajo se requieren criterios, evidencia, hijos y dependencias resueltos, y documentación vigente.
- Editar trabajo marca su documentación vinculada para revisión. Publica la documentación después del último cambio del trabajo.
publishexige evidencia y conserva la revisión anterior. Escribir conocimiento medianteupdateno puede saltarse ese historial.- El guardado usa bloqueo compartido con Tauri, reemplazo atómico y una copia anterior. Los archivos
context/son exportaciones reconstruibles; el archivo canónico esworkspace.json. - Usa una compilación de escritorio compatible con este CLI, incluida en este repositorio. Cierra las compilaciones anteriores antes de escribir desde el CLI. La app actualiza el contenido cuando está inactiva y no tiene cambios pendientes ni un diálogo abierto; un conflicto conserva los cambios locales y pide exportarlos antes de recargar.
- Si un proceso termina de forma abrupta puede quedar un directorio vacío
workspace.json.write-lock. No se elimina por antigüedad: verifica que no haya escritores activos antes de retirarlo. - Límite actual: 24 MiB por espacio y 500 operaciones por lote. Archivar conserva los datos; no es borrado permanente.
API para otro sistema
import { emptyWorkspace, applyOperations } from '@fsaldivar.dev/planning';
const original = emptyWorkspace('Mi producto');
const result = applyOperations(original, {
expectedRevision: 0,
operations: [{ op: 'create', kind: 'idea', title: 'Una nueva idea', ref: 'idea' }],
});
console.log(result.refs.idea, result.workspace);applyOperations es una transformación pura: clona y valida sin mutar original ni incrementar la revisión de almacenamiento. En Node usa initializeWorkspace, readWorkspace y applyToFile desde @fsaldivar.dev/planning/node para persistir con bloqueo, respaldo e incremento de revisión. Otro host debe mantener el mismo control de concurrencia.
Componentes de interfaz
Las piezas visuales se importan por separado y no dependen de la aplicación Tauri, de una ventana ni de un sistema de guardado. El host conserva los datos y decide qué monta.
| Entrada | Contenido |
| --- | --- |
| @fsaldivar.dev/planning/editor | createBlockEditor, VisualEditor, schema, blockTypes |
| @fsaldivar.dev/planning/document | mountDocument: documento de solo lectura con el mismo render que el editor |
| @fsaldivar.dev/planning/editor/commands | Comandos ProseMirror de bloques: changeBlock, moveBlock, duplicateBlock, deleteBlock, createTable |
| @fsaldivar.dev/planning/components | Cada zona de la aplicación como pieza independiente: mountSidebar, mountBoard, mountProperties… y su marcado (sidebarMarkup…) |
| @fsaldivar.dev/planning/mermaid | mountMermaidPreview, openMermaidDesigner, parseMermaidPreview, applyMermaidEdit |
| @fsaldivar.dev/planning/navigation | bindNavigation, zoomFromWheel, zoomActiveSurface |
| @fsaldivar.dev/planning/markdown | fromMarkdown, markdown, plain, validateDocument, sin interfaz |
| @fsaldivar.dev/planning/editor.css, /components.css | Estilos de cada grupo |
El editor necesita estas dependencias en el host; el CLI y la API de dominio no las cargan:
npm install @fsaldivar.dev/planning prosemirror-state prosemirror-view prosemirror-commands prosemirror-history prosemirror-keymap highlight.js @fsaldivar.dev/diagramhighlight.js colorea los bloques de código y @fsaldivar.dev/diagram (Kairo) dibuja y edita los diagramas Mermaid. components no necesita ninguna de ellas, solo su hoja de estilos.
Editor por bloques
import { createBlockEditor } from '@fsaldivar.dev/planning/editor';
import '@fsaldivar.dev/planning/editor.css';
const editor = createBlockEditor(document.getElementById('editor')!, {
markdown: '## Hola\n\nEscribe / para insertar un bloque.',
toolbar: false, // tus propios controles
tableToolbar: document.getElementById('tabla')!, // o monta los incluidos donde quieras
footer: false,
onChange(content) { /* guarda el árbol en tu almacenamiento */ },
});
negrita.onclick = () => editor.execute('bold');
tabla.onclick = () => editor.insertBlock('table');
editor.subscribe(state => { negrita.disabled = !editor.can('bold'); });- Contenido inicial:
content(árbol ProseMirror) omarkdown. - Controles:
toolbar,tableToolbaryfooteraceptanfalsepara ocultarlos o un elemento para montarlos fuera del documento.blockGutter: falseoculta el asa lateral. - Comandos:
executeycanaceptanbold,italic,inline-code,undo,redo,row,column,delete-row,delete-column,delete-tableyheader. - Bloques:
insertBlockaceptaparagraph,h1,h2,h3,blockquote,horizontal_rule,bullet_list,ordered_list,task_list,table,mermaidycode_block.openBlockMenu(ancla)abre el menú junto a tu botón. - Lectura y escritura:
getJSON,getMarkdown,setContent,setMarkdown. Reemplazar el documento reinicia el historial de deshacer y no emiteonChangesalvo con{ emit: true }. - Estado:
getStateysubscribeentreganreadOnly,canUndo,canRedo,inTable,bold,italic,inlineCode,words,charactersyzoom. - Otros:
readOnly,placeholder,label,setZoom,focusydestroy.
Cada editor mantiene su propio estado; puedes montar varios en la misma página.
- Bloques personalizados:
enhancers: [{ language, render(code, ctx) }]pinta con tu código las cercas que el paquete no conoce (ver Documentos de solo lectura). En el editor, el botón «Fuente» del bloque muestra y edita el texto. - Enlaces: nunca navegan solos.
onOpenLink(href)recibe los seguros (http, https, mailto y rutas relativas).
Documentos de solo lectura
mountDocument muestra un documento con el mismo render que el editor (tablas, listas de tareas, código, Mermaid con Kairo) y sin ningún control de edición: ni barra, ni pie, ni asa de bloques, ni «Editar en Kairo». Acepta Markdown en texto o el árbol content.
import { mountDocument, type BlockEnhancer } from '@fsaldivar.dev/planning/document';
import '@fsaldivar.dev/planning/editor.css';
const mockup: BlockEnhancer = {
language: 'codaru-mockup', label: 'Pantallas',
async render(code, ctx) {
const file = await ctx.load('Mockups.codarumockup'); // lo resuelve tu host
const element = renderScreens(code, file, ctx.theme); // tu renderizador
element.onclick = () => ctx.onOpen({ screen: 's-login' });
return element;
},
};
const view = mountDocument(contenedor, {
markdown: textoDelArchivo,
enhancers: [mockup],
theme: { accent: 'var(--codaru-accent)', surface: 'var(--codaru-surface)' },
load: (file, language) => leerArchivo(file),
onOpen: (target, language) => abrirDiseño(target),
onOpenLink: href => abrirEnlace(href),
});
view.update({ markdown: nuevoTexto }); // también theme, enhancers…
view.destroy();- Enhancers:
render(code, ctx)devuelve unHTMLElemento una promesa.ctxtraelanguage,theme("light"/"dark"en ese momento),readOnly,load(file),onOpen(target)ysignal(se aborta si el bloque cambia o desaparece). Tienen prioridad sobre los renderizadores propios, Mermaid incluido, así que sirven también paradot,d2oplantuml. Los mismosenhancers,loadyonOpenfuncionan encreateBlockEditor. - Fallos acotados: si un renderizador lanza un error, su promesa falla o no devuelve un elemento, el bloque muestra su texto original con una nota y el resto del documento sigue igual. Un diagrama Mermaid que no se puede dibujar también deja visible su fuente.
- Tema:
"light","dark"o un objeto de variables. Las claves sin--se convierten en--planning-*(accent→--planning-accent,documentFontSize→--planning-document-font-size); las que empiezan por--se aplican tal cual. Valores comovar(--codaru-accent)siguen al host en vivo. Si el host cambia claro/oscuro en un ancestro, Mermaid y los bloques personalizados se vuelven a pintar con el esquema nuevo. - Seguridad: el contenido nunca ejecuta scripts. Se rinde con el esquema del editor (sin HTML crudo); el Markdown con HTML o imágenes se rechaza, y un
contentcon enlacesjavascript:,data:u otros esquemas lanza un error. Lo único que inserta HTML es el elemento que devuelve tu enhancer.
Piezas de la aplicación
La aplicación de escritorio se construye con estas mismas piezas, así que se ven y se comportan igual. Ninguna depende de otra: monta solo las que necesites, en el contenedor que quieras.
| Pieza | Qué muestra | Intenciones |
| --- | --- | --- |
| mountWindowToolbar | Nombre del espacio, subtítulo, buscador, nueva tarjeta, configuración | onSearch, onNew, onSettings, onToggleSidebar |
| mountSidebar | Vistas, épicas, borrador y estado de guardado; secciones configurables (ver abajo) | onView, onFilterEpic, onOpen, onNewEpic, onDraft, onRecover, onSearch |
| mountViewToolbar | Título de la vista, Estados/Épicas, menú «Nueva tarjeta» | onBoardMode, onCreate |
| mountBoard | Tablero con filtro por épica y etiquetas, columnas y arrastre | onOpen, onCreate, onStatusChange, onEpicFilter, onDismissWelcome, onOpenDesign |
| mountCard | Una tarjeta, con etiquetas y distintivo de diseño | onOpen, onOpenDesign |
| mountKnowledgeList, mountDeliveries, mountArchive | Listas de conocimiento (con la ruta del archivo), entregas y archivo | onOpen, onCreate, onRestore, onOpenFile |
| mountItemHeader | Volver, archivar, título y resumen | onBack, onArchive, onTitle, onSummary |
| mountDetailTabs | Contenido, Subtareas, Contexto, Diagrama, Historial | onTab |
| mountProperties | Tipo, estado, prioridad, padre, fecha, etiquetas, archivo, sección «Diseño» (miniatura con resolveAsset) y relaciones | onStatus, onPriority, onParent, onRestore, onOpen, onLink, onUnlink, onOpenDesign, onOpenFile |
| mountRelations | Relaciones de una ficha | onOpen, onLink, onUnlink |
| mountCriteria | Criterios de aceptación | onToggle, onEdit, onRemove, onAdd |
| mountEvidence | Resultado y verificación | onChange, onPublish |
| mountSubtasks, mountContext, mountHistory, mountDocumentState | Subtareas, contexto para IA, revisiones y actividad en orden, y estado del documento | onOpen, onCreate, onCopy, onExport, onOpenRevision |
import { mountSidebar, mountProperties } from '@fsaldivar.dev/planning/components';
import '@fsaldivar.dev/planning/components.css';
const sidebar = mountSidebar(document.getElementById('mi-panel')!, {
workspace, view: 'board',
onView(view) { /* navega en tu aplicación */ sidebar.update({ view }); },
});
const properties = mountProperties(document.getElementById('mi-inspector')!, {
workspace, item,
relations: false, // las relaciones van en otro sitio con mountRelations
onStatus(status) { /* aplica { op: 'status', id: item.id, status } y llama a properties.update */ },
});- Controladas: las piezas no modifican el espacio. Emiten la intención; el host aplica el cambio con
applyOperationsy llama aupdatecon los datos nuevos. Un selector o una casilla vuelve a su valor anterior hasta que el host confirma. - Opcionales por control: un control sin su callback se oculta o queda de solo lectura. Sin
onSearchno hay buscador; sinonLinkno hay botón «Vincular». update(parcial)acepta solo lo que cambió y conserva el cursor si se está escribiendo.destroy()retira la pieza.classNameañade tus clases al contenedor de la pieza;iconsustituye los iconos.- Diálogos: crear tarjeta, vincular y configuración pertenecen al host; las piezas solo avisan (
onNew,onLink,onSettings). - Marcado: cada pieza tiene su función
…Markup(sidebarMarkup,propertiesMarkup,boardViewMarkup…) por si prefieres componer el HTML y delegar los eventos tú mismo, como hace la aplicación de escritorio.
Barra lateral configurable
mountSidebar y sidebarMarkup aceptan, además del estado, cómo se compone la barra. Todo funciona también por update():
const sidebar = mountSidebar(panel, {
workspace, view: 'board',
sections: ['search', 'views', 'epics'], // orden; lo que no aparece no se pinta. Default: views, epics, status
views: ['knowledge', 'board'], // orden de las vistas dentro de «Espacio». Default: las cinco
collapsible: ['views'], // con control de plegado, abiertas
collapsed: ['epics'], // con control de plegado, cerradas al inicio
slots: { views: miArbolDeDocumentos }, // tu propio elemento al final de una sección
onSearch(query) { /* solo con la sección "search" */ },
});sections:"search"(buscador dentro de la barra, conqueryyonSearch),"views","epics"(incluye el borrador sin terminar) y"status"(estado de guardado).collapsibleycollapsedconvierten la cabecera de la sección en un control de plegado. Lo que la persona abre o cierra se conserva en los siguientesupdate().searchystatusno tienen cabecera, así que no se pliegan.slots: el paquete adopta tu elemento tal cual (appendChild), dentro de la sección, y lo mantiene en su sitio en cadaupdate(); no lo clona ni lo vuelve a crear. Si la sección se pliega, tu contenido se pliega con ella.
La disposición es tuya: las piezas no fijan su posición en la página. La barra lateral y las propiedades traen su ancho de la aplicación (208 px y 238 px); cámbialo con CSS sobre .sidebar o .inspector.
Apariencia
Los estilos se limitan al contenedor .codaru-planning que crea cada pieza y nunca tocan body ni :root. Sin configurar nada se ven como la aplicación de escritorio. Para el tema oscuro de la aplicación, pon data-theme="dark" en cualquier ancestro. Para tu propio tema, define las variables en cualquier ancestro; cada instancia puede tener el suyo:
#mi-ide {
--planning-accent: #7563c4;
--planning-surface: #202024;
--planning-text: #eeeef4;
--planning-color-scheme: dark;
}| Variable | Uso |
| --- | --- |
| --planning-accent, --planning-accent-ink | Color de acento y texto sobre el acento |
| --planning-surface, --planning-background | Fondo del contenido y de los paneles |
| --planning-sidebar, --planning-toolbar, --planning-field | Fondo de la barra lateral, de la barra de ventana y de los campos |
| --planning-success, --planning-warning, --planning-shadow | Estados «vigente» y «por revisar», y sombra de tarjetas y botones |
| --planning-text, --planning-muted | Texto principal y secundario |
| --planning-border, --planning-divider | Bordes y separadores |
| --planning-hover, --planning-selection | Estados al pasar el cursor y de selección |
| --planning-font, --planning-font-size, --planning-document-font-size | Tipografía y tamaño base (todas las piezas escalan con él) y tamaño del documento |
| --planning-gutter-width | Ancho del asa lateral de bloques |
| --planning-code-keyword, --planning-code-title, --planning-code-string, --planning-code-number | Colores del código |
| --planning-color-scheme | light o dark para los controles nativos |
| --planning-label-1 … --planning-label-8 | Tonos de las etiquetas; cada etiqueta elige uno por hash |
Para usar tus propios iconos, pasa icon: nombre => '<svg…>' al editor o a cualquier pieza; iconNames lista los nombres que se piden. El resultado se inserta como HTML de confianza: no interpoles en él texto del documento ni del usuario.
examples/composable-ui reconstruye la pantalla de la aplicación montando cada pieza en un contenedor del host, con tema claro y oscuro, otro acento y las propiedades cambiadas de lado. Las fichas de conocimiento se abren con mountDocument y un bloque ```codaru-mockup de ejemplo. Usa únicamente el paquete instalado.
Licencia BSD-3-Clause. Autor: fsaldivar-dev.
