From 4fe483f68fc38d8ec774865d905b924c3cfdabaf Mon Sep 17 00:00:00 2001 From: Adolfo Reyna Date: Mon, 14 Sep 2026 22:40:15 -0400 Subject: [PATCH] daily backup 2026-09-14 --- INITIAL-APP.md | 45 ++++++++++++++++++ deploy/com.adolforeyna.kid-dash-dev.plist | 29 ++++++++++++ deploy/elias-caddy-route.json | 8 ++++ deploy/restore-elias-caddy.sh | 21 +++++++++ webapp/package.json | 1 + webapp/src/App.jsx | 4 +- webapp/src/index.css | 54 +++++++++++++++++----- webapp/src/main.jsx | 4 +- webapp/test/framed-screen-scaling.test.mjs | 14 ++++++ webapp/test/native-viewport.test.mjs | 18 ++++++++ webapp/test/public-hmr.test.mjs | 11 +++++ webapp/vite.config.js | 6 ++- 12 files changed, 199 insertions(+), 16 deletions(-) create mode 100644 INITIAL-APP.md create mode 100644 deploy/com.adolforeyna.kid-dash-dev.plist create mode 100644 deploy/elias-caddy-route.json create mode 100644 deploy/restore-elias-caddy.sh create mode 100644 webapp/test/framed-screen-scaling.test.mjs create mode 100644 webapp/test/native-viewport.test.mjs create mode 100644 webapp/test/public-hmr.test.mjs diff --git a/INITIAL-APP.md b/INITIAL-APP.md new file mode 100644 index 0000000..6f8ad27 --- /dev/null +++ b/INITIAL-APP.md @@ -0,0 +1,45 @@ +# KiddOs v3 — Initial App Log + +**Recorded:** 2026-08-24 22:31 EDT +**Repository:** `~/brain/projects/kid_dash` +**Source:** `https://git.reynafamily.com/adolforeyna/kid_dash` +**Initial revision:** `30f9148adb1f90b1821e86727e75e9fadbc9bd9b` + +## Purpose + +KiddOs v3 is an offline-first React/Vite browser kiosk for Elias. It targets an **800 × 480** children’s display and provides local-only apps backed by browser `localStorage`. + +## Initial app set + +- Journal +- CodeLab +- Scratch (offline information/app) +- Calculator +- Clock and timer +- Drawing / Play +- Files browser + +## Development setup + +- App source: `webapp/src/` +- Development command: `npm run dev` +- Test command: `npm test` +- Build command: `npm run build` +- The public development hostname used during initial work was `elias.reynafamily.com`. + +## Initial UI work + +- Added a centered, framed **800 × 480** preview composition for browser review. +- At an actual 800 × 480 device viewport, the frame is removed and KiddOs fills the display. +- Added CSS zoom configuration for the browser preview. +- Added Node tests for the native viewport frame and public HMR configuration. + +## Verification + +At the end of the initial work, the Node tests passed and the Vite production build succeeded. + +## Current operational status + +- The temporary Vite development server was stopped at the user’s request. +- The app-specific public Caddy proxy route and its reboot restore entry were removed at the same time. +- No public KiddOs server is currently running. diff --git a/deploy/com.adolforeyna.kid-dash-dev.plist b/deploy/com.adolforeyna.kid-dash-dev.plist new file mode 100644 index 0000000..dde8115 --- /dev/null +++ b/deploy/com.adolforeyna.kid-dash-dev.plist @@ -0,0 +1,29 @@ + + + + + Label + com.adolforeyna.kid-dash-dev + ProgramArguments + + /opt/homebrew/bin/npm + run + dev + + WorkingDirectory + /Users/adolforeyna/brain/projects/kid_dash/webapp + EnvironmentVariables + + PATH + /opt/homebrew/bin:/usr/bin:/bin + + RunAtLoad + + KeepAlive + + StandardOutPath + /Users/adolforeyna/Library/Logs/kid-dash-dev.log + StandardErrorPath + /Users/adolforeyna/Library/Logs/kid-dash-dev.error.log + + diff --git a/deploy/elias-caddy-route.json b/deploy/elias-caddy-route.json new file mode 100644 index 0000000..1dfd506 --- /dev/null +++ b/deploy/elias-caddy-route.json @@ -0,0 +1,8 @@ +{ + "match": [{"host": ["elias.reynafamily.com"]}], + "handle": [{ + "handler": "reverse_proxy", + "upstreams": [{"dial": "192.168.68.130:5173"}] + }], + "terminal": true +} diff --git a/deploy/restore-elias-caddy.sh b/deploy/restore-elias-caddy.sh new file mode 100644 index 0000000..c41496c --- /dev/null +++ b/deploy/restore-elias-caddy.sh @@ -0,0 +1,21 @@ +#!/bin/sh +set -eu + +HOST="elias.reynafamily.com" +ROUTE_JSON="/home/adolforeyna/elias-caddy-route.json" +API="http://127.0.0.1:2019/config/apps/http/servers/srv2/routes" + +INDEX=$(curl -fsS "$API" | python3 -c ' +import json, sys +host = sys.argv[1] +routes = json.load(sys.stdin) +for index, route in enumerate(routes): + if any(host in match.get("host", []) for match in route.get("match", [])): + print(index) + break +' "$HOST") + +if [ -n "$INDEX" ]; then + curl -fsS -X DELETE "$API/$INDEX" +fi +curl -fsS -X POST -H "Content-Type: application/json" --data-binary "@$ROUTE_JSON" "$API" diff --git a/webapp/package.json b/webapp/package.json index 87214ea..df83180 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -5,6 +5,7 @@ "type": "module", "scripts": { "dev": "vite --host 0.0.0.0 --port 5173", + "test": "node --test", "build": "vite build", "preview": "vite preview --host 0.0.0.0 --port 4173" }, diff --git a/webapp/src/App.jsx b/webapp/src/App.jsx index 50777f5..aa95204 100644 --- a/webapp/src/App.jsx +++ b/webapp/src/App.jsx @@ -90,7 +90,7 @@ export default function App(){ if(currentApp){ const Comp = COMPS[currentApp.comp] return ( -
+
@@ -114,7 +114,7 @@ export default function App(){ } return ( -
+
E
diff --git a/webapp/src/index.css b/webapp/src/index.css index 519614e..3fb1f12 100644 --- a/webapp/src/index.css +++ b/webapp/src/index.css @@ -15,16 +15,48 @@ --accent-purple: #9D7BD8; --accent-orange: #D08770; } -* { box-sizing: border-box; } -html, body { margin:0; padding:0; height:100%; overflow:hidden; } -body { background:var(--bg); color:var(--text); font-family: system-ui, -apple-system, sans-serif; -webkit-font-smoothing: antialiased; -webkit-tap-highlight-color: transparent; } -button { font-family: inherit; cursor:pointer; user-select:none; } -button:active { transform: scale(0.97); } -::-webkit-scrollbar { width:6px; height:6px; } -::-webkit-scrollbar-thumb { background:#4C566A; border-radius:3px; } -::-webkit-scrollbar-track { background:transparent; } -/* 800x480 specific */ -@media (max-width: 800px), (max-height: 480px) { - header { height:48px !important; } +* { box-sizing: border-box; } +html, body, #root { width: 100%; height: 100%; margin: 0; overflow: hidden; } +body { + background: #151923; + color: var(--text); + font-family: system-ui, -apple-system, sans-serif; + -webkit-font-smoothing: antialiased; + -webkit-tap-highlight-color: transparent; +} +button { font-family: inherit; cursor: pointer; user-select: none; } +button:active { transform: scale(0.97); } +::-webkit-scrollbar { width: 6px; height: 6px; } +::-webkit-scrollbar-thumb { background: #4C566A; border-radius: 3px; } +::-webkit-scrollbar-track { background: transparent; } + +.kiddos-stage { + width: 100%; + height: 100%; + min-height: 480px; + display: grid; + place-items: center; + padding: 24px; + overflow: auto; + background: radial-gradient(circle at 50% 20%, #3B4252 0, #232A36 48%, #151923 100%); +} + +.kiddos-screen { + width: 800px !important; + height: 480px !important; + /* Browser preview: enlarge the 800×480 composition to 160%. */ + zoom: 1.6; + overflow: hidden; + background: var(--bg); + border: 10px solid #11151D; + border-radius: 24px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.55), inset 0 0 0 1px #59647A; +} + +/* The DSI is the native screen: use every pixel and avoid a clipped frame. */ +@media (max-width: 800px), (max-height: 480px) { + .kiddos-stage { min-height: 0; padding: 0; background: var(--bg); } + .kiddos-screen { width: 100vw !important; height: 100vh !important; zoom: 1 !important; border: 0; border-radius: 0; box-shadow: none; } + header { height: 48px !important; } } diff --git a/webapp/src/main.jsx b/webapp/src/main.jsx index 5e8d112..1477707 100644 --- a/webapp/src/main.jsx +++ b/webapp/src/main.jsx @@ -5,6 +5,8 @@ import './index.css' ReactDOM.createRoot(document.getElementById('root')).render( - +
+ +
) diff --git a/webapp/test/framed-screen-scaling.test.mjs b/webapp/test/framed-screen-scaling.test.mjs new file mode 100644 index 0000000..df2b8ba --- /dev/null +++ b/webapp/test/framed-screen-scaling.test.mjs @@ -0,0 +1,14 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { readFile } from 'node:fs/promises' + +const css = await readFile(new URL('../src/index.css', import.meta.url), 'utf8') + +test('keeps the native screen size ahead of inline app dimensions', () => { + assert.match(css, /\.kiddos-screen\s*\{[^}]*width:\s*800px\s*!important/s) + assert.match(css, /\.kiddos-screen\s*\{[^}]*height:\s*480px\s*!important/s) +}) + +test('uses the requested 1.6 zoom for the browser preview', () => { + assert.match(css, /\.kiddos-screen\s*\{[^}]*zoom:\s*1\.6/s) +}) diff --git a/webapp/test/native-viewport.test.mjs b/webapp/test/native-viewport.test.mjs new file mode 100644 index 0000000..0d77b05 --- /dev/null +++ b/webapp/test/native-viewport.test.mjs @@ -0,0 +1,18 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { readFile } from 'node:fs/promises' + +const css = await readFile(new URL('../src/index.css', import.meta.url), 'utf8') + +test('defines a centered 800 by 480 KiddOs screen frame', () => { + assert.match(css, /\.kiddos-stage\s*\{[^}]*place-items:\s*center/s) + assert.match(css, /\.kiddos-screen\s*\{[^}]*width:\s*800px/s) + assert.match(css, /\.kiddos-screen\s*\{[^}]*height:\s*480px/s) + assert.match(css, /\.kiddos-screen\s*\{[^}]*border:/s) +}) + +test('uses the full physical display without a desktop frame at 800 by 480', () => { + assert.match(css, /@media\s*\(max-width:\s*800px\),\s*\(max-height:\s*480px\)/s) + assert.match(css, /\.kiddos-screen\s*\{[^}]*width:\s*100vw/s) + assert.match(css, /\.kiddos-screen\s*\{[^}]*height:\s*100vh/s) +}) diff --git a/webapp/test/public-hmr.test.mjs b/webapp/test/public-hmr.test.mjs new file mode 100644 index 0000000..3b83d64 --- /dev/null +++ b/webapp/test/public-hmr.test.mjs @@ -0,0 +1,11 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { readFile } from 'node:fs/promises' + +const viteConfig = await readFile(new URL('../vite.config.js', import.meta.url), 'utf8') + +test('routes HMR through the public HTTPS development host', () => { + assert.match(viteConfig, /host:\s*'elias\.reynafamily\.com'/) + assert.match(viteConfig, /protocol:\s*'wss'/) + assert.match(viteConfig, /clientPort:\s*443/) +}) diff --git a/webapp/vite.config.js b/webapp/vite.config.js index 4b25fcf..f1be872 100644 --- a/webapp/vite.config.js +++ b/webapp/vite.config.js @@ -7,8 +7,10 @@ export default defineConfig({ host: '0.0.0.0', port: 5173, hmr: { - clientPort: 5173, - // overlay shows errors on DSI too + // Caddy terminates TLS and proxies WebSocket upgrades from the public dev URL. + host: 'elias.reynafamily.com', + protocol: 'wss', + clientPort: 443, overlay: true }, cors: true