Zen Apps — PWA Scaffold + CSS Build + Protection

Date: 2026-05-18

Problem

22 standalone mini-apps (apps/zen*.html) needed to be published as installable PWAs at the site root, each with:

Approaches considered

One Tailwind build for all zen apps — rejected. Any class used in any zen app would leak into every other app’s stylesheet. Purge must be scoped per-app.

CDN Tailwind in production — rejected. CDN adds an external runtime dependency, fails offline (PWAs should work offline), and blocks display: standalone from looking native.

Fold into build-apps-css.js — rejected. That script has no Cloudflare skip gate and runs unconditionally. We needed a separate entry point with an env-var kill switch.

What was built

_scripts/pwa-zen-apps.js

Reads every apps/zen*.html, creates <site-root>/<slug>/ with:

Also writes apps/registry.json (all apps as JSON) and apps/index.html (gallery page that fetches registry).

Key decision: PWA_DIR = ROOT (site root), not apps/. The /apps/ path is only for the source files.

_scripts/build-zen-css.js

Runs after pwa-zen-apps.js. For each zen*/ dir at site root:

  1. Checks BUILD_ZEN_APPS env var — exits immediately if false or 0
  2. Runs npx tailwindcss -i assets/css/tailwind.src.css -o <slug>/style.css --content <slug>/index.html --minify
  3. Removes the Tailwind CDN <script> tag from index.html (idempotent — safe to run twice)
  4. Injects <link rel="stylesheet" href="./style.css"> before </head> if not already present
  5. Writes .no-app-css marker so build-apps-css.js skips the dir (no double-processing)

_data/domain_protection.json

All 22 zen slugs added to protected_folders. inject-protection.js reads this during build:protect and applies the domain-lock snippet to _site/<slug>/index.html post-build.

package.json

"build:zen:css": "node _scripts/build-zen-css.js"

Inserted into the main build pipeline between build:css:apps and build:jekyll.

Full pipeline:

build:css → build:css:apps → build:zen:css → build:jekyll → build:protect → build:pwa

Mistakes and corrections

Apps created in apps/zen*/ instead of site root — initial script had PWA_DIR = APPS_DIR. User caught it. Fixed by moving dirs with mv apps/zen*/ . and updating PWA_DIR = ROOT in the script.

zencalc nested into itself — existing zencalc/ at site root caused mv apps/zencalc/ . to create zencalc/zencalc/. Fixed by rm -rf zencalc/zencalc. The pre-existing zencalc/ was already a proper PWA and was left untouched.

Reader prerequisites

Cloudflare usage

Set BUILD_ZEN_APPS=false in Cloudflare Pages environment variables to skip zen CSS builds on every main-site deploy. Run locally with npm run build:zen:css when zen app HTML actually changes.