lumbda/www/repl/index.html
russell@unturf.com d8ffab5ea6
repl: /repl/ page with encrypted multi-tab sessions
Interactive REPL at lumbda.com/repl with:
  - multi-tab sessions (click + to add, × to close, double-click to rename)
  - per-tab tier selector (python/c/asm/all-three race)
  - persistent transcripts encrypted in localStorage via Web Crypto
    (PBKDF2 + AES-GCM, vault id = SHA-256(password || device-salt) —
    same pattern as unsandbox's vault-encryption-design.md, native
    crypto.subtle API instead of CryptoJS)
  - ephemeral mode (skip vault, transcripts vanish on reload)
  - one worker per (tab × tier) — state persists across evals in a tab
  - reboot tier button (terminate this tab's worker, fresh state next eval)
  - cancel button (kills the running worker in active tab)

Home page now links to both /playground/ and /repl/.

Tier state itself does NOT persist across reloads — the transcript does,
but defines/set!/hash-tables vanish with the worker. Portal save/resume
in WAT (deferred) will let a tier session survive close+reopen.
2026-06-14 12:50:35 -04:00

95 lines
3.9 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>lumbda repl — three tiers, persistent sessions</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="repl.css">
<script type="importmap">
{
"imports": {
"@codemirror/state": "https://esm.sh/*@codemirror/state@6.4.1",
"@codemirror/view": "https://esm.sh/*@codemirror/view@6.34.1",
"@codemirror/commands": "https://esm.sh/*@codemirror/commands@6.6.2",
"@codemirror/language": "https://esm.sh/*@codemirror/language@6.10.3",
"@codemirror/legacy-modes/mode/scheme": "https://esm.sh/*@codemirror/legacy-modes@6.4.1/mode/scheme",
"@codemirror/theme-one-dark": "https://esm.sh/*@codemirror/theme-one-dark@6.1.2",
"@lezer/highlight": "https://esm.sh/*@lezer/highlight@1.2.1",
"@lezer/common": "https://esm.sh/*@lezer/common@1.2.3",
"style-mod": "https://esm.sh/*style-mod@4.1.2",
"crelt": "https://esm.sh/*crelt@1.0.6",
"w3c-keyname": "https://esm.sh/*w3c-keyname@2.2.8"
}
}
</script>
</head>
<body class="repl">
<header>
<a class="brand" href="../" aria-label="lumbda home">
<img class="logo" src="lumbda-logo-green.png" alt="" aria-hidden="true">
<h1 aria-label="lumbda.">lumbda<span class="period" aria-hidden="true">.</span></h1>
</a>
<p class="tagline">three tiers, persistent sessions</p>
<p class="lede">
interactive repl — each tab keeps its own tier state. <kbd>enter</kbd>
sends, <kbd>shift+enter</kbd> for newline. transcripts persist in
encrypted localStorage; supply a password and the same password
unlocks them after a reload. for one-shot demos, see the
<a href="../playground/">playground</a>.
</p>
</header>
<div id="lock-screen" class="lock-screen">
<div class="lock-card">
<h2>unlock</h2>
<p>enter a password — transcripts are encrypted with it locally.
a fresh password starts a fresh vault.</p>
<input id="password" type="password" autocomplete="off" autofocus placeholder="password">
<div class="lock-row">
<button id="unlock" class="primary">unlock vault</button>
<button id="fresh" class="secondary" title="skip the vault, run ephemerally — transcripts vanish on reload">ephemeral</button>
</div>
<p class="lock-note" id="lock-note"></p>
</div>
</div>
<section class="tabbar" id="tabbar" hidden>
<div class="tabs" id="tabs"></div>
<button id="new-tab" class="ghost" title="new repl session">+</button>
<span class="spacer"></span>
<button id="reset-tier" class="ghost" title="reboot the worker for the current tab — state lost">↻ reboot tier</button>
<button id="clear-log" class="ghost" title="clear the transcript">clear log</button>
<button id="cancel" class="ghost" disabled>cancel</button>
<button id="lock" class="ghost" title="lock vault — passes back to lock screen">lock</button>
</section>
<main id="transcript-wrap" class="transcript-wrap" hidden>
<div id="transcript" class="transcript"></div>
</main>
<section class="prompt-bar" id="prompt-bar" hidden>
<span class="prompt-sigil">λ&gt;</span>
<textarea id="input" rows="1" spellcheck="false"
placeholder="(+ 1 2) — enter to send, shift+enter for newline"></textarea>
<select id="tier-select" title="which tier evaluates this input">
<option value="python">python (pyodide)</option>
<option value="c" selected>c (emcc)</option>
<option value="asm">asm (wat)</option>
<option value="all">all three (race)</option>
</select>
<button id="send" class="primary">send</button>
</section>
<footer>
<p>
transcripts saved encrypted with your password. tier state (defines,
set!, hash table contents) lives in the worker; lost on reload. lift
a session via portal save when that lands in WAT. <a href="../">lumbda.</a>
</p>
</footer>
<script type="module" src="repl.js"></script>
</body>
</html>