Compare commits

...

1 Commits

Author SHA1 Message Date
Adolfo Reyna 4fe483f68f daily backup 2026-09-14 2026-09-14 22:40:15 -04:00
12 changed files with 199 additions and 16 deletions
+45
View File
@@ -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.
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.adolforeyna.kid-dash-dev</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/npm</string>
<string>run</string>
<string>dev</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/adolforeyna/brain/projects/kid_dash/webapp</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/bin:/bin</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/adolforeyna/Library/Logs/kid-dash-dev.log</string>
<key>StandardErrorPath</key>
<string>/Users/adolforeyna/Library/Logs/kid-dash-dev.error.log</string>
</dict>
</plist>
+8
View File
@@ -0,0 +1,8 @@
{
"match": [{"host": ["elias.reynafamily.com"]}],
"handle": [{
"handler": "reverse_proxy",
"upstreams": [{"dial": "192.168.68.130:5173"}]
}],
"terminal": true
}
+21
View File
@@ -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"
+1
View File
@@ -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"
},
+2 -2
View File
@@ -90,7 +90,7 @@ export default function App(){
if(currentApp){
const Comp = COMPS[currentApp.comp]
return (
<div style={{height:'100vh', display:'flex', flexDirection:'column', background:'#2E3440'}}>
<div className="kiddos-screen" style={{height:'100%', display:'flex', flexDirection:'column', background:'#2E3440'}}>
<header style={{height:'52px', background:'#1E222E', borderBottom:'1px solid #4C566A', display:'flex', alignItems:'center', justifyContent:'space-between', padding:'0 12px', flexShrink:0}}>
<div style={{display:'flex', gap:'10px', alignItems:'center'}}>
<button onClick={()=>setCurrentApp(null)} style={{background:'#3B4252', border:'1px solid #4C566A', color:'#ECEFF4', borderRadius:'8px', padding:'0 10px', height:'36px', cursor:'pointer'}}>🏠 KiddOs</button>
@@ -114,7 +114,7 @@ export default function App(){
}
return (
<div style={{display:'flex', flexDirection:'column', height:'100vh', background:'#2E3440', overflow:'hidden'}}>
<div className="kiddos-screen" style={{display:'flex', flexDirection:'column', height:'100%', background:'#2E3440', overflow:'hidden'}}>
<header style={{height:'56px', background:'#1E222E', borderBottom:'1px solid #4C566A', display:'flex', alignItems:'center', justifyContent:'space-between', padding:'0 12px', flexShrink:0}}>
<div style={{display:'flex', gap:'10px', alignItems:'center'}}>
<div style={{width:'36px', height:'36px', background:'#434C5E', borderRadius:'10px', display:'flex', alignItems:'center', justifyContent:'center', fontWeight:'bold', fontSize:'16px', border:'1px solid #4C566A'}}>E</div>
+43 -11
View File
@@ -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; }
}
+3 -1
View File
@@ -5,6 +5,8 @@ import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
<div className="kiddos-stage">
<App />
</div>
</React.StrictMode>
)
@@ -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)
})
+18
View File
@@ -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)
})
+11
View File
@@ -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/)
})
+4 -2
View File
@@ -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