Monorepo para alojar múltiples miniapps PWA independientes en GitHub Pages usando un único repositorio.
Cada miniapp vive en apps/<slug> y se publica como una subruta estática del repositorio. El proyecto está diseñado para minimizar trabajo manual e incluye un launcher home, un generador de miniapps, validación estructural y un build agregador para GitHub Pages.
Ejemplos de URL finales:
https://<usuario>.github.io/<repo>/https://<usuario>.github.io/<repo>/nombre_miniapp1/https://<usuario>.github.io/<repo>/nombre_miniapp2/
pnpm workspacesVite + Preact + TypeScriptvite-plugin-pwaparamanifestyservice workerapps/homecomo launcher de miniapps- miniapp real de ejemplo:
planning-board - generador
pnpm new:miniapp - validación automática del monorepo
- build agregado para GitHub Pages
- previsualización local del artefacto final
- workflow de GitHub Actions para despliegue
miniapps/
├─ apps/
│ ├─ home/
│ └─ planning-board/
├─ assets/
│ └─ pwa/
├─ tooling/
│ └─ create-miniapp/
├─ scripts/
│ ├─ lib/
│ ├─ build-pages.mjs
│ ├─ generate-home-registry.mjs
│ ├─ validate-miniapps.mjs
│ └─ preview-pages.mjs
└─ .github/workflows/
- Git
- Node.js 20 o superior
- pnpm 10
Comprobación rápida:
node -v
pnpm -v
git --versionInstala dependencias y valida que el repositorio está consistente:
pnpm install
pnpm test:scripts
pnpm validate:miniapps
pnpm generate:homeEsto deja validado el generador, la estructura del monorepo y el launcher home.
Para levantar una app concreta en modo desarrollo:
pnpm --filter @miniapps/home dev
pnpm --filter @miniapps/planning-board dev
pnpm --filter @miniapps/md-converter dev
pnpm --filter @miniapps/sticky-board dev
pnpm --filter @miniapps/template-mgr devpnpm new:miniapp <slug> [opciones]pnpm new:miniapp shopping-listpnpm new:miniapp habit-tracker --title "Habit Tracker" --desc "Seguimiento de hábitos" --router --theme "#0f766e"pnpm new:miniapp habit-tracker \
--title "Habit Tracker" \
--desc "Seguimiento de hábitos" \
--router \
--theme "#0f766e" \
--background "#ffffff" \
--tags "habit,productivity" \
--icon "leaf"-
--title <texto>Nombre amigable de la miniapp. Se usa enhomey en elmanifestPWA. Ejemplo:--title "Habit Tracker" -
--desc <texto>Descripción corta de la app para el launcher y metadatos. Ejemplo:--desc "Seguimiento de hábitos" -
--routerActiva modo SPA con router cliente. El generador establecerárouter: trueenapp.config.jsony crearápublic/404.htmlpara restaurar rutas en GitHub Pages. Úsalo si la app necesita rutas internas, por ejemplo/settings. -
--no-pwaGenera una miniapp sin huella PWA. No añadevite-plugin-pwa, no copia iconos PWA y no generamanifestniservice worker. -
--theme <hex>Color primario en hexadecimal. Se aplica amanifest.theme_color, metatheme-colory variables CSS de acento. Por defecto:#004F87Ejemplo:--theme "#0f766e" -
--background <hex>Color de fondo en hexadecimal paramanifest.background_colory estilos de carga. Por defecto:#FFFFFFEjemplo:--background "#ffffff" -
--category <texto>Metadata opcional para clasificación futura.homeno depende de ello. Ejemplo:--category "productivity" -
--tags <csv>Metadata opcional separada por comas. Solo se guarda si se proporciona. Ejemplo:--tags "habit,productivity,offline" -
--icon <nombre>Metadata opcional del icono. Solo se guarda si se proporciona. Ejemplo:--icon "leaf" -
--listed=falseEvita que la app aparezca enhome. Por defecto las apps se listan. Es útil para apps privadas o en desarrollo. Ejemplo:--listed=false
- Valida el
slug. - Crea
apps/<slug>. - Genera los archivos base de la app.
- Copia iconos PWA solo si la app es PWA.
- Crea
404.htmlsi la app usa router. - Regenera el launcher
home. - Valida el resultado. Si falla por un problema global del repositorio, conserva la nueva app para revisión.
La plantilla base comparte estilos desde styles/base.css. La miniapp nueva solo redefine las variables necesarias para su identidad visual y ajustes locales.
Si prefieres usar el asistente/skill del repositorio para proponer y generar la miniapp, sigue este flujo resumido (la definición completa está en .agents/skills/miniapp-monorepo-builder/SKILL.md).
- Verifica el contrato del repo: asegúrate de tener
package.jsonconpnpm new:miniapp,tooling/create-miniapp/src/cli.js, y los scripts de validación (scripts/validate-miniapps.mjs,scripts/generate-home-registry.mjs). - Recoge los datos mínimos:
slug,title,desc, si necesita rutas cliente (--router) y si será PWA (--no-pwa). - Propón antes de ejecutar: la skill exige una propuesta que incluya el
slug, el comando exactopnpm new:miniapp ..., el alcance funcional inicial y los tests previstos; sólo se ejecuta tras tu aprobación. - Genera con el CLI del repo: la skill invoca
pnpm new:miniapp <slug> [flags](usa siempre el entrypointpnpmen vez de llamar al CLI pornodesalvo motivo concreto). - Implementa la primera versión: tras el scaffold, la skill sugiere reemplazar los placeholders por la primera funcionalidad (estructura recomendada:
src/features,src/lib,src/hooks). - Valida y documenta: ejecutar tests relevantes,
pnpm validate:miniappsy describir explícitamente qué fue generado por el CLI y qué se implementó manualmente.
Usa la skill cuando quieras que el agente proponga, genere y aterrice una miniapp coherente con las convenciones del monorepo; para detalles y criterios de decisión revisa el archivo de la skill enlazado arriba.
pnpm new:miniapp <slug> --title "..." --desc "..."
pnpm test:scripts
pnpm validate:miniapps
pnpm --filter @miniapps/<slug> dev
pnpm build:pagesChecklist sugerido:
- Crear la app con
pnpm new:miniapp. - Revisar
apps/<slug>/app.config.json. - Levantar la app en local.
- Ejecutar validaciones.
- Generar
dist-pages/. - Hacer commit y push.
pnpm validate:miniappsComprueba:
- nombres válidos
- ficheros obligatorios
- coherencia entre
app.config.jsonypackage.json - iconos requeridos solo para apps PWA
404.htmlsirouter=true- ausencia de lógica de redirect en apps sin router
pnpm generate:homepnpm build:pagespnpm preview:pagesDesde GitHub:
- Crea un repositorio nuevo.
- Asígnale un nombre, por ejemplo
miniapps. - Márcalo como público si quieres el caso más simple con GitHub Pages.
Después, vincula el repositorio local:
git init
git remote add origin https://github.com/<usuario>/<repo>.git
git add .
git commit -m "Initial commit"
git branch -M main
git push -u origin main- Ve a Settings > Pages.
- En Build and deployment, selecciona GitHub Actions.
- Verifica que la pestaña Actions esté habilitada.
- Haz push a
main.
El despliegue incluido vive en .github/workflows/deploy-pages.yml y no requiere secretos manuales en el caso estándar de GitHub Pages.
checkout- setup de Node y pnpm
pnpm install --frozen-lockfile- validación del repositorio
- build de Pages
- subida de
dist-pages/ - despliegue
Antes del primer push, ejecuta:
pnpm install
pnpm test:scripts
pnpm validate:miniapps
pnpm build:pagespnpm install
pnpm test:scripts
pnpm validate:miniapps
pnpm build:pages
git add .
git commit -m "Initial commit"
git push -u origin mainDespués del push:
- Abre Actions y espera a que termine
Deploy GitHub Pages. - Entra en Settings > Pages y confirma la URL publicada.
- Verifica
homeenhttps://<usuario>.github.io/<repo>/. - Verifica al menos una miniapp en su subruta.
GitHub Pages para repositorios de proyecto sirve el sitio bajo /<repo>/. Por eso cada miniapp debe construir con una base del tipo /<repo>/<app>/.
Este monorepo calcula esa base a partir de:
GITHUB_REPOSITORYen CIVITE_REPO_NAMEen local
GitHub Pages no resuelve rutas profundas de una SPA. Las apps con router incluyen public/404.html para restaurar la ruta al arrancar.
Revisa:
VITE_REPO_NAMEGITHUB_REPOSITORY- nombre real del repositorio en GitHub
Revisa:
- que exista
public/404.html - que
routeresté activado enapp.config.json
Revisa:
listed- ejecuta
pnpm generate:home - vuelve a ejecutar
pnpm validate:miniapps
Comprueba que estos comandos funcionan en local:
pnpm test:scripts
pnpm validate:miniapps
pnpm build:pagesAdemás, verifica:
- que Pages esté configurado con GitHub Actions
- que
.github/workflows/deploy-pages.ymlexista y no esté deshabilitado
pnpm new:miniapp weekly-planner --title "Weekly Planner" --desc "Planificador semanal offline" --theme "#7c3aed"
pnpm test:scripts
pnpm validate:miniapps
pnpm generate:home
pnpm --filter @miniapps/weekly-planner dev
pnpm build:pages
pnpm preview:pages
git add .
git commit -m "Add weekly-planner miniapp"
git pushTODO
- dir openspec al crear andamiaje
- skill con openspec
- md con imagenes
- versionar
Alta prioridad (impacto inmediato):
- Undo/Redo (
Ctrl+Z) — imprescindible para un tablero creativo - Duplicar nota — botón en el header o
Ctrl+D - Keyboard shortcuts —
Npara nueva nota,Delpara borrar seleccionada,Escpara deseleccionar - Selección múltiple — arrastrar para seleccionar un área, mover/borrar en bloque
Media prioridad:
- formato texto - permite ajustar formatos básicos tamaño, negrita
Ideas más ambiciosas:
- Tableros múltiples — el schema ya tiene
boardId, solo falta la UI de navegación entre tableros - Conectores entre notas — flechas SVG arrastrables para mapas mentales
- Plantillas de nota — nota de lista de tareas con checkboxes, nota de código con monoespaciado
① Formato de texto
La toolbar aparece en la nota al detectar selección de texto (mouseup + getSelection().toString() !== ''). Se puede anclar flotante sobre la selección usando getBoundingClientRect(). La implementación más robusta combina document.execCommand (bold, italic, underline) con CSS custom properties para el tamaño. Para proyectos nuevos, la alternativa moderna es la Selection API + Range para aplicar estilos directamente sobre nodos, evitando la deprecación de execCommand.
UX clave: la toolbar desaparece al hacer click fuera, y los botones se activan visualmente con document.queryCommandState() para reflejar el estado real del cursor.
② Conectores entre notas
Cada nota expone puntos de anclaje (connector-dot) en sus 4 bordes. Al arrastrar uno, se dibuja un path SVG Bézier cúbico (C) provisional. Al soltar sobre otra nota, el conector queda vinculado. La curva se recalcula en cada mousemove durante el drag de una nota, usando getBoundingClientRect() relativo al tablero. Los conectores se guardan en un array como { from: noteId, fromAnchor: 'right', to: noteId, toAnchor: 'left', label: '' }. La etiqueta central es un <foreignObject> o <text> clickeable para editar.
③ Plantillas de nota
La nota tiene un campo type en su modelo de datos (blank | todo | code). El selector aparece justo al crear la nota (bottom bar), antes de confirmar. Cada tipo renderiza un componente diferente: la todo-list guarda items[] con { text, done } y muestra checkboxes reactivos; la nota código activa fuente monoespaciada, un selector de lenguaje y puede integrar un syntax highlighter ligero como Prism.js sólo en ese contexto.