Un programa, tres apps — web, teléfono y escritorio desde el mismo archivo de Synsema
Una app de Synsema es un sitio web con HTTPS automático, una app instalable en el teléfono con push nativo y una app de escritorio en su propia ventana con tu ícono — desde un programa renderizado en el servidor y un módulo de API. Sin toolkit nativo, sin segundo código, sin tienda obligatoria.
La forma habitual de llegar a tres plataformas son tres bases de código, o un framework que envuelve un navegador y te pide que lo aprendas. Synsema toma el otro camino: la app es un programa renderizado en el servidor, y cada plataforma es una manera de distribuir el mismo programa. El sitio, la app del teléfono y la de escritorio comparten las rutas, las plantillas, las capacidades y los secretos, porque son un archivo con tres entradas.
Tres destinos, un archivo§
synsema init myapp --desktop && cd myapp # the scaffold: app.syn, desk.syn, api.syn, public/
synsema serve app.syn --domain app.example.com --tls-auto you@example.com # the WEBSITE, with Let's Encrypt
# the PHONE: the same site installs from the browser (PWA)
synsema build desk.syn -o desk --serve --no-console --icon public/icon.svg # the DESKTOP app (Windows: desk.exe)
synsema build desk.syn -o desk --serve --icon public/icon.svg --bundle # macOS .app / Linux folder + install.sh
El sitio es app.syn bajo serve: HTTPS con una bandera, la página renderizada en el servidor, cada ruta respondiendo también Markdown y JSON a los agentes. La app del teléfono es el mismo sitio: un manifiesto, un service worker y los íconos hacen que se instale en Android y en iOS con ícono en la pantalla de inicio, pantalla completa, un shell offline honesto y Web Push nativo — eso es lo que genera --pwa. La app de escritorio es desk.syn: la misma página y la misma API atadas a 127.0.0.1, abiertas como ventana de app del navegador que ya está en la máquina, con tu ícono en el ejecutable y sin consola; termina cuando se cierra la última ventana.
La misma API en todas partes§
La API vive una sola vez, en api.syn, como un grupo de rutas:
-- api.syn
export routes api
route "GET /api/ping"
give ok({"pong": true, "at": now()})
route "POST /api/push/subscribe"
rate_limit 10 per minute
expect body {endpoint: text, keys: map}
...
Las dos entradas lo montan — mount api.api — y cada una declara las capacidades que las rutas necesitan (require net("fcm.googleapis.com"), require secret("VAPID_PRIVATE_KEY")): un módulo no lleva require, lo lleva la entrada. Los límites de tasa y los timeouts por ruta viajan con el grupo, así que el servidor público y la app de escritorio local hacen cumplir las mismas reglas. En una construcción con --sandbox o --cap-set, el techo viaja con el binario y no se puede levantar desde adentro.
Qué recibe cada destino, con honestidad§
| Sitio web | Teléfono (PWA) | Escritorio | |
|---|---|---|---|
| Cómo corre | synsema serve en tu servidor | el mismo servidor, instalado desde el navegador | un binario en la máquina del usuario |
| Ícono | favicon | ícono de pantalla de inicio, del manifiesto | el ícono del ejecutable (--icon), el de la ventana desde la página |
| Ventana | pestaña del navegador | pantalla completa, sin cromo del navegador | ventana de app del navegador, sin barra de direcciones ni consola |
| Push | — | Web Push nativo (Android; iOS una vez instalada, sobre HTTPS) | las notificaciones del navegador y Web Push, mientras el proceso corre |
| Offline | — | el shell abre sin red | el servidor es local |
| Ciclo de vida | lo corrés vos | el teléfono corre el navegador | termina al cerrar la última ventana, o a los 30 s sin ventana |
| Verificado | producción | Android y Windows reales | en vivo en Windows 11; macOS por CI en Apple Silicon; Linux por tests |
Dónde se corta: no hay bandeja del sistema (no existe API web para eso), Firefox no tiene modo app (una pestaña), iOS necesita un certificado confiable para el service worker y el push, y un .app de macOS descargado sin Developer ID se encuentra con Gatekeeper como cualquier app sin firmar. Las tiendas son opcionales: la Microsoft Store acepta una PWA directamente, Play la acepta a través de una Trusted Web Activity y la App Store todavía quiere un envoltorio nativo — nada de lo cual el lenguaje pretende ocultar.
Por qué se sostiene§
Renderizar en el servidor significa que el teléfono y el escritorio muestran lo que decidió el servidor, no una copia de la lógica. La negociación de contenido significa que las mismas rutas son una API para agentes. Las capacidades deny-by-default significan que el binario de escritorio que le das a alguien solo toca lo que dicen sus líneas require, y los secretos quedan sellados dentro del proceso en todas las plataformas. Y synsema build significa que todo el conjunto — motor, programa, plantillas, public/ — es un archivo para regalar.
Probalo: instalá Synsema (0.6.19 o más nuevo), synsema init myapp --desktop, y después synsema serve app.syn para el sitio y synsema serve desk.syn para la ventana. Las dos guías — Tu app en el teléfono y Tu app en el escritorio — listan cada archivo que escribe el andamiaje y cada bandera, con qué está verificado en dónde.