Q for QRx

a hypertext based generative Operating System

Kernel 26.06.14

QR Code for QRx Kernel 26.02.15

This qr encodes an html file turning any browser since the 1990s into an offline-first generative REPL to prompt and vibe code. It does this by reimagining the browser's local storage as a file system composed from hyperlinks that functions as a prompt chaining interface
559856767-e2e48f53-5b27-4b64-aa1e-b049219df9da



## Local Setup ### Requirements: - git - https://git-scm.com/install/ - nodejs - https://nodejs.org/en/download ### Terminal Commands: ```bash # clone the project and dependencies git clone https://github.com/hypertextwiki/os # install dependencies npm run install # start the server on http://localhost:8080 npm start ```


## Core flags The above Kernel exposes the following URL `?query` params | Flag | Description | | :--- | :--- | | **`a`** | **Append Mode**. If `1`, subsequent commands append to the accumulator. If `0` (default), they overwrite it. | | **`f`** | **File Pointer**. Sets the target filename (`filename`) for subsequent write (`w`) operations. | | **`c`** | **Context**. Loads data (from DB or `src`) into a side-buffer for the AI, without affecting the main accumulator. `0` clears it. | | **`k, m, s, h`** | **AI Config**. Sets the API Key (`k`), Model (`m`), System Prompt (`s`), or Host (`h`) in `localStorage`. | | **`e`** | **Echo**. Pushes the raw value directly into the accumulator (hardcoded strings/HTML). | | **`r`** | **Read**. Reads a file from the database (or `src` for source code) into the accumulator. | | **`u`** | **URL**. Fetches text from a remote URL. Implements a **Network-First, Cache-Fallback** mechanism. Successful fetches are passively synced to a discrete `'cache'` IndexedDB namespace. If your OS is offline, it automatically catches the failure and serves the file locally. | | **`p`** | **Prompt**. Sends the current context + accumulator + value to the LLM. The result becomes the new accumulator. | | **`w`** | **Write**. Saves the current accumulator content to the database under the name defined by `f`. | | **`x`** | **Execute**. Runs the value (or the current accumulator if value is empty) as JavaScript. |
towards-a-teleology-of-hypertext-welcome-to-r-v0-suslk89wrg8h1
## Globals The kernel exposes the following variables and methods ### Variables | Variable | Description | | :--- | :--- | | **`filename`** | **File Pointer**. The name of the current record being read from or written to. Defaults to `MAIN` or the value before `?` in the hash. | | **`BASE`** | **Deployment Prefix**. A path segment stripped from the front of the URL before `DB` is derived. Defaults to `''` (root hosting). Set it by declaring `BASE='/yourprefix'` in a `
{ "name": "os", "version": "26.08.24", "type": "module", "description": "a hypertext based generative Operating System", "main": "index.js", "scripts": { "start": "npm run build && node --env-file=.env servers/local.js", "build": "npm run build:llms && vite build ", "build:github": "npm run build:llms && GITHUB_PAGES=true node servers/github.js && GITHUB_PAGES=true vite build", "build:llms": "node scripts/copy-src.js && node scripts/build-llms.js" }, "devDependencies": { "@types/express": "^5.0.6", "express": "^5.2.1", "html-minifier-terser": "^7.2.0", "qrcode": "^1.5.4", "vite": "^8.2.2", "vite-plugin-pwa": "^1.3.0" } } /** * servers/github/build.js * * Pre-build step for GitHub Pages static deployment. * Mirrors what server.js does at runtime: * - Reads QRX_PUBLIC_NAMESPACES (plus always-included 'main' and 'cache') * - Copies each allowed namespace from data/ into public/data/ * - Generates public/data/index.json (the flat key manifest the bootloader fetches) * * Run via: npm run build:github * (which is: node servers/github/build.js && vite build) */ import { readdir, copyFile, mkdir, writeFile, readFile } from 'fs/promises' import { join, dirname, resolve } from 'path' import { existsSync } from 'fs' import { fileURLToPath } from 'url' const __dirname = dirname(fileURLToPath(import.meta.url)) const ROOT = resolve(__dirname, '..') const DATA_DIR = join(ROOT, 'data') const PUBLIC_DATA_DIR = join(ROOT, 'public', 'data') const INDEX_PATH = join(PUBLIC_DATA_DIR, 'index.json') const IGNORE_LIST = ['.DS_Store', '.git', 'node_modules', '.gitlab-ci.yml'] // Mirror server.js namespace resolution logic exactly const includeRaw = process.env.QRX_PUBLIC_NAMESPACES || 'main' const includeParsed = includeRaw.split(',').map(s => s.trim().toLowerCase()).filter(Boolean) const INCLUDE_SET = new Set([...includeParsed, 'main', 'cache']) console.log(`\n GitHub Pages build`) console.log(` Allowed namespaces: ${[...INCLUDE_SET].join(', ')}\n`) /** * Decode %3A%2F back to :/ for index.json entries. * Mirrors fromFsKey() in server.js. */ function fromFsKey(key) { return key.replace(/%3A%2F/g, ':/') } /** * Recursively copy a directory tree from src to dest. * Skips anything in IGNORE_LIST. */ async function copyDir(src, dest) { await mkdir(dest, { recursive: true }) const entries = await readdir(src, { withFileTypes: true }) for (const entry of entries) { if (IGNORE_LIST.includes(entry.name)) continue const srcPath = join(src, entry.name) const destPath = join(dest, entry.name) if (entry.isDirectory()) { await copyDir(srcPath, destPath) } else { await mkdir(dirname(destPath), { recursive: true }) await copyFile(srcPath, destPath) } } } /** * Recursively walk a directory and return all file paths * relative to the given base, decoded from fs-encoding. */ async function walk(dir, base) { const results = [] const entries = await readdir(dir, { withFileTypes: true }) for (const entry of entries) { if (IGNORE_LIST.includes(entry.name)) continue const rel = base ? `${base}/${entry.name}` : entry.name if (entry.isDirectory()) { results.push(...await walk(join(dir, entry.name), rel)) } else { results.push(fromFsKey(rel)) } } return results } async function main() { // Ensure public/data exists await mkdir(PUBLIC_DATA_DIR, { recursive: true }) if (!existsSync(DATA_DIR)) { console.log(' No data/ directory found — writing empty index.json\n') await writeFile(INDEX_PATH, JSON.stringify([])) return } const namespaces = await readdir(DATA_DIR, { withFileTypes: true }) const indexEntries = [] for (const ns of namespaces) { if (!ns.isDirectory() || IGNORE_LIST.includes(ns.name)) continue if (!INCLUDE_SET.has(ns.name.toLowerCase())) { console.log(` Skipping namespace: ${ns.name} (not in allowlist)`) continue } const srcDir = join(DATA_DIR, ns.name) const destDir = join(PUBLIC_DATA_DIR, ns.name) console.log(` Copying namespace: ${ns.name} → public/data/${ns.name}`) await copyDir(srcDir, destDir) // Walk the copied dest dir to build index entries const keys = await walk(srcDir, '') for (const key of keys) { indexEntries.push(`${ns.name}/${key}`) } console.log(` ${keys.length} keys indexed`) } await writeFile(INDEX_PATH, JSON.stringify(indexEntries)) console.log(`\n index.json written with ${indexEntries.length} total entries`) console.log(` public/data/ is ready for static serving\n`) } main().catch(err => { console.error('build-github failed:', err) process.exit(1) }) import { createServer } from 'http' import { writeFile, readFile, mkdir, stat, readdir } from 'fs/promises' import { join, dirname, resolve, extname } from 'path' import { createReadStream, existsSync, readFileSync, watch } from 'fs' import { fileURLToPath } from 'url' import url from 'url' import path from 'path' const PORT = process.env.QRX_PORT || 3000 const DATA_DIR = resolve(join(process.cwd(), 'data')) const INDEX_PATH = resolve(join(process.cwd(), 'public', 'data', 'index.json')) const PRIVATE_INDEX_PATH = resolve(join(process.cwd(), 'data', 'index.private.json')) const DIST_DIR = resolve(join(process.cwd(), 'dist')) const SECRET = process.env.QRX_SYNC_KEY // SSE clients waiting for write-event notifications const clients = new Set() /** * Namespace allowlist — controls which namespaces are publicly readable via /read. * Configured via QRX_PUBLIC_NAMESPACES env var (comma-separated). * 'main' and 'cache' are always included: * - 'main' is the default kernel namespace * - 'cache' holds previously fetched public URL content, already public by definition */ const includeRaw = process.env.QRX_PUBLIC_NAMESPACES || 'main' const includeParsed = includeRaw.split(',').map(s => s.trim().toLowerCase()).filter(Boolean) const INCLUDE_SET = new Set([...includeParsed, 'main', 'cache']) const isNamespaceAllowed = (ns) => ns && INCLUDE_SET.has(ns.toLowerCase()) const privateRaw = process.env.QRX_PRIVATE_NAMESPACES || '' const privateParsed = privateRaw.split(',').map(s => s.trim().toLowerCase()).filter(Boolean) const PRIVATE_SET = new Set(privateParsed) const isNamespacePrivate = (ns) => ns && PRIVATE_SET.has(ns.toLowerCase()) // Files and directories to skip when walking data/ for index generation const IGNORE_LIST = ['.DS_Store', '.git', 'node_modules', '.gitlab-ci.yml'] /** * Encode URL-style keys so '://' is not mangled by path.join. * e.g. "https://foo.com/bar" -> "https%3A%2F/foo.com/bar" */ function toFsKey(key) { return key.replace(/:\//g, '%3A%2F') } /** * Reverse of toFsKey — used when listing keys back to clients in index.json. * e.g. "https%3A%2F/foo.com/bar" -> "https://foo.com/bar" */ function fromFsKey(key) { return key.replace(/%3A%2F/g, ':/') } /** * Recursively walks data/ and writes public/data/index.json — a flat list of * all "namespace/key" paths the bootloader uses to sync IndexedDB on page load. * Only emits entries whose namespace passes isNamespaceAllowed. */ async function updateDataIndex() { try { const results = [] const namespaces = await readdir(DATA_DIR, { withFileTypes: true }) for (const ns of namespaces) { if (!ns.isDirectory() || IGNORE_LIST.includes(ns.name)) continue if (!isNamespaceAllowed(ns.name)) continue async function walk(currentDir, currentPath) { const entries = await readdir(currentDir, { withFileTypes: true }) for (const e of entries) { if (IGNORE_LIST.includes(e.name)) continue const itemPath = currentPath ? `${currentPath}/${e.name}` : e.name if (e.isDirectory()) { await walk(join(currentDir, e.name), itemPath) } else { // Decode fs-encoded keys so clients see the original key strings results.push(`${ns.name}/${fromFsKey(itemPath)}`) } } } await walk(join(DATA_DIR, ns.name), '') } await writeFile(INDEX_PATH, JSON.stringify(results)) } catch (err) { console.error('[Indexer] Failed to generate index:', err) } } /** * Recursively walks data/ and writes public/data/index.private.json — same * structure as index.json but restricted to PRIVATE_SET namespaces. * This file is served only to requests carrying a valid Authorization header. */ async function updatePrivateIndex() { if (PRIVATE_SET.size === 0) return try { const results = [] const namespaces = await readdir(DATA_DIR, { withFileTypes: true }) for (const ns of namespaces) { if (!ns.isDirectory() || IGNORE_LIST.includes(ns.name)) continue if (!isNamespacePrivate(ns.name)) continue async function walk(currentDir, currentPath) { const entries = await readdir(currentDir, { withFileTypes: true }) for (const e of entries) { if (IGNORE_LIST.includes(e.name)) continue const itemPath = currentPath ? `${currentPath}/${e.name}` : e.name if (e.isDirectory()) { await walk(join(currentDir, e.name), itemPath) } else { results.push(`${ns.name}/${fromFsKey(itemPath)}`) } } } await walk(join(DATA_DIR, ns.name), '') } await writeFile(PRIVATE_INDEX_PATH, JSON.stringify(results)) } catch (err) { console.error('[Indexer] Failed to generate private index:', err) } } /** * Watch data/ for changes and rebuild index.json after a 30s debounce. * The debounce avoids thrashing on rapid successive writes. * Falls back gracefully if recursive watch is unsupported (some Linux kernels). */ let indexTimeout function watchData() { try { watch(DATA_DIR, { recursive: true }, (eventType, filename) => { if (eventType !== 'rename' || !filename) return const segments = filename.split(/[/\\]/) const isIgnored = segments.some(s => IGNORE_LIST.includes(s) || s.endsWith('.lock')) if (isIgnored) return console.log(`[Indexer] Change detected: ${filename}. Rebuilding index in 30s...`) clearTimeout(indexTimeout) indexTimeout = setTimeout(async () => { await updateDataIndex() await updatePrivateIndex() console.log('[Indexer] index.json updated.') }, 30000) }) } catch (err) { console.warn('[Indexer] Recursive watch not supported on this OS. Automatic indexing disabled.') } } // Non-blocking startup: ensure directories exist, build initial index, start watcher mkdir(DATA_DIR, { recursive: true }).catch(() => {}) mkdir(join(process.cwd(), 'public', 'data'), { recursive: true }).catch(() => {}) updateDataIndex() updatePrivateIndex() watchData() createServer(async (req, res) => { // CORS — open for local/self-hosted use; lock this down if exposing publicly res.setHeader('Access-Control-Allow-Origin', '*') res.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET, POST') res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization') if (req.method === 'OPTIONS') return res.writeHead(204).end() const parsedUrl = url.parse(req.url, true) const myUrl = new URL(req.url, `http://localhost`) /** * GET /stream — Server-Sent Events endpoint. * Clients subscribe here to receive real-time write notifications. * Protected by SECRET if QRX_SYNC_KEY is set. */ if (req.method === 'GET' && myUrl.pathname === '/stream') { if (SECRET && parsedUrl.query.auth !== SECRET) return res.writeHead(401).end('Unauthorized') res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', }) clients.add(res) req.on('close', () => clients.delete(res)) return } /** * GET /* — Static file server from dist/. * Special cases: * - /data/index.json is served from public/data/index.json (the generated manifest) * - /data/* anything else is blocked (use /read instead) * - index.html gets window.NS injected if the request hostname has a matching data/ directory * - Unknown paths with no file extension fall through to index.html (SPA routing) */ if (req.method === 'GET') { const mimes = { '.html': 'text/html', '.js': 'text/javascript', '.json': 'application/json', '.png': 'image/png', '.css': 'text/css', '.ico': 'image/x-icon', '.webmanifest': 'application/manifest+json', } let safePath = myUrl.pathname === '/' ? 'index.html' : myUrl.pathname let filePath if (safePath === '/data/index.json') { filePath = resolve(INDEX_PATH) } else if (safePath === '/data/index.private.json') { if (!SECRET || req.headers.authorization !== SECRET) return res.writeHead(401).end('Unauthorized') filePath = resolve(PRIVATE_INDEX_PATH) } else if (safePath.startsWith('/data/')) { return res.writeHead(403).end('Direct data access blocked. Use /read endpoint.') } else { filePath = resolve(join(DIST_DIR, safePath)) if (!filePath.startsWith(DIST_DIR)) return res.writeHead(403).end('Forbidden') try { const s = await stat(filePath) if (s.isDirectory()) throw new Error('is_dir') } catch { // Fall through to index.html for extensionless SPA routes; 404 for unknown extensions if (!mimes[extname(safePath)]) { filePath = resolve(join(DIST_DIR, 'index.html')) } else { return res.writeHead(404).end('Not Found') } } } try { await stat(filePath) if (filePath.endsWith('index.html')) { let html = await readFile(filePath, 'utf-8') // Inject window.NS if the hostname maps to a namespace directory — // this tells the kernel which DB to use without a URL path segment const host = (req.headers.host || '').split(':')[0] if (host && existsSync(join(DATA_DIR, host))) { html = html.replace('', ``) } res.writeHead(200, { 'Content-Type': 'text/html' }) return res.end(html) } res.writeHead(200, { 'Content-Type': mimes[extname(filePath)] || 'application/octet-stream' }) const stream = createReadStream(filePath) stream.on('error', () => { if (!res.headersSent) res.writeHead(500).end() }) stream.pipe(res) return } catch { return res.writeHead(404).end('Not Found') } } /** * POST /write — Persist a value to data/namespace/key on disk. * Requires Authorization header matching QRX_SYNC_KEY. * After writing, rebuilds index.json and broadcasts an SSE event to all * connected clients so they can sync without polling. * * Body: { namespace, key, value, clientId } */ if (req.method === 'POST' && myUrl.pathname === '/write') { let body = '' req.on('data', chunk => body += chunk.toString()) req.on('end', async () => { try { const { namespace, key, value, clientId } = JSON.parse(body) if (SECRET && req.headers.authorization !== SECRET) { return res.writeHead(401).end(JSON.stringify({ error: 'Unauthorized' })) } const fsKey = toFsKey(key) const targetPath = resolve(join(DATA_DIR, namespace, fsKey)) if (!targetPath.startsWith(DATA_DIR)) throw new Error('Path traversal blocked') await mkdir(dirname(targetPath), { recursive: true }) await writeFile(targetPath, value || '') await updateDataIndex() // Notify all SSE subscribers of the write so clients can react immediately const msg = 'data: ' + JSON.stringify({ namespace, key, clientId }) + '\n\n' clients.forEach(client => client.write(msg)) res.writeHead(200).end(JSON.stringify({ status: 'saved' })) } catch (err) { res.writeHead(400).end(JSON.stringify({ error: err.message })) } }) return } /** * POST /read — Read a value from data/namespace/key on disk. * Public namespaces (per INCLUDE_SET) are readable without auth. * Private namespaces require the Authorization header. * Falls back to data/main/key if the namespaced path doesn't exist. * * Body: { namespace, key } */ if (req.method === 'POST' && myUrl.pathname === '/read') { let body = '' req.on('data', chunk => body += chunk.toString()) req.on('end', async () => { try { const { namespace, key } = JSON.parse(body) const hasValidKey = SECRET && req.headers.authorization === SECRET if (!hasValidKey && !isNamespaceAllowed(namespace)) { return res.writeHead(404).end(JSON.stringify({ error: 'Namespace not in allowlist' })) } const fsKey = toFsKey(key) let targetPath = resolve(join(DATA_DIR, namespace, fsKey)) if (!targetPath.startsWith(DATA_DIR)) throw new Error('Path traversal blocked') let data try { data = await readFile(targetPath, 'utf-8') } catch { // Fallback: if key isn't in the requested namespace, try main if (namespace !== 'main') { targetPath = resolve(join(DATA_DIR, 'main', fsKey)) if (!targetPath.startsWith(DATA_DIR)) throw new Error('Path traversal blocked') data = await readFile(targetPath, 'utf-8') } else { throw new Error('Not found') } } res.writeHead(200).end(JSON.stringify({ value: data })) } catch { res.writeHead(404).end(JSON.stringify({ error: 'Not found' })) } }) return } res.writeHead(404).end('Not Found') }).listen(PORT, '0.0.0.0', () => { console.log(`Server started on http://0.0.0.0:${PORT}`) }) import { defineConfig, loadEnv } from 'vite' import { minify } from 'html-minifier-terser' import QRCode from 'qrcode' import { resolve } from 'path' import { readFileSync, writeFileSync } from 'fs' import { VitePWA } from 'vite-plugin-pwa' const __dirname = resolve() const jsString = value => JSON.stringify(value).replace(/ stub so VitePWA can find it during its * pipeline scan — without this, VitePWA warns and skips SW/manifest injection * because the kernel HTML has no document structure. The stub is stripped back * out by writeBundle after the QR code is generated. */ const htmlMinifierPlugin = () => ({ name: 'html-minifier-plugin', enforce: 'post', async transformIndexHtml(html) { const minified = await minify(html, { removeComments: true, collapseWhitespace: true, minifyJS: true, minifyCSS: true, removeAttributeQuotes: true, collapseBooleanAttributes: true, processConditionalComments: true, removeOptionalTags: true, }) return `${minified}` }, }) /** * Builds the bootloader script for server deployments. * * Resolves namespace from hostname, fetches data/index.json, then for each * known key either syncs content via POST /read (for target/query/boot keys) * or stubs empty strings. Reloads on first boot or when content has changed. */ function buildServerBootloader(base) { return ` ` } /** * Builds the bootloader script for GitHub Pages (static) deployments. * * Key differences from the server bootloader: * - Skips the 'cache' namespace entirely — cache keys are URL-derived and * not meaningful as static files; the ?u= fetch path handles caching at * runtime via IndexedDB anyway, and on GitHub Pages you're always online. * - Replaces POST /read with a plain GET to the static file path: * fetch(`${base}data/${ns}/${key}`) instead of fetch('${base}read', { method: 'POST', ... }) * - No hostname-based NS resolution (server.js injects window.NS at serve * time; that doesn't exist on static hosting, so we just fall back to 'main'). * - Installs a read-through miss handler: stubs are zero-byte placeholders * marking unexplored nodes, so any read that comes back empty is fetched * from the static data/ tree on demand (with a main-namespace fallback, * mirroring the server's /read) and cached back into IndexedDB. */ function buildStaticBootloader(base) { return ` ` } /** * Post-build plugin that, in strict order: * 1. Reads the built index.html (now has full doc structure + PWA injections). * 2. Extracts the bare kernel from inside for QR code generation. * 3. Generates a QR code from the bare kernel — must be as small as possible. * 4. Appends the QRX_URL injection and appropriate bootloader into the * existing , chosen based on whether GITHUB_PAGES env var is set. * 5. Writes the final file. */ const qrCodePlugin = (base, isGitHubPages, qrxUrl) => ({ name: 'qr-code-plugin', async writeBundle() { const filePath = resolve(__dirname, 'dist/index.html') const html = readFileSync(filePath, 'utf-8') const kernel = html.match(/([\s\S]*?)<\/body>/)?.[1] ?? html const kernelBytes = Buffer.byteLength(kernel, 'utf-8') console.log(`\n QR kernel: ${kernelBytes} bytes (QR-L cap: 2953 bytes, ${2953 - kernelBytes} remaining)\n`) await QRCode.toFile(resolve(__dirname, 'public/index.qr.png'), kernel, { errorCorrectionLevel: 'L', type: 'png', width: 1000, margin: 1, }) const baseInject = isGitHubPages ? `` : '' const qrxUrlInject = `` const bootloader = isGitHubPages ? buildStaticBootloader(base) : buildServerBootloader(base) const final = html.replace('', baseInject + qrxUrlInject + bootloader.replace(/\s+/g, ' ') + '') writeFileSync(filePath, final) // GitHub Pages SPA routing: GitHub Pages 404s any path that isn't a real // file (e.g. /qrx/wiki). 404.html stashes the real pathname in // sessionStorage and redirects to bare base, carrying the hash through // directly on the redirect URL (hash survives a redirect for free). // baseInject (runs after the kernel's BASE='' line but before its // setTimeout body executes) sets BASE and restores the real pathname via // replaceState, so by the time the kernel reads LP, it's identical to a // normal direct load — the kernel's own BASE-stripping in DB derivation // handles the rest unmodified. if (isGitHubPages) { const notFoundHtml = ` the first part `data:text/html,` tells the browser to render everything after as HTML. to run javascript you wrap it with ` if you copy/paste the above into a desktop browser you will get an alert box with a random post title and summary! note that this only works on desktop browsers by default, for security reasons mobile devices tend to disable the Data URI protocol # Ollama Template - Starfield Animation the following should work with a local r/ollama LLM setup with CORS disabled. replace `model` with the model you use and `0.0.0.0` with your machines IP. this works best in r/firefox, chromium browsers can require further config changes data:text/html, # Use Cases the core idea is that generative Data URIs can semantically compress massive projects. beyond that here are some other ideas: * **project ideas** * generative RSS and news readers * chatbots, agents, and vibe coding interfaces * "serverless" dashboards where each link is a generative tool * **distribution** * paper qrcodes * qrcode stickers * HTML anchor tags with `target=_blank` # More to Come this tutorial only covers the Data URI protocol, but there are many other protocols we'll be visiting like the `file:// protocol` which grants some browser APIs like localStorage and indexedDB, `javascript:// protocol` for bookmarklets, and ofc the standard `HTTP protocol` we use to browse the web in future tutorials we'll discuss techniques for persistence, memory management, creating multi-hop Data URIs and Data URI Factories (URIs that generate URIs), swarms and more leave any questions, thoughts, comments, or share your own generative Data URIs below! --- title: [Theory Crafting] Towards Hypercompression of Autopoietic Hypertext subreddit: r/hyprprompting flair: theory crafting reshare: from: title: I Made DOOM Run Inside a QR Code and wrote a Custom compression Algorithm for it that got Cited by a NASA Scientist. source: https://www.reddit.com/r/computerscience/comments/1m95fhn/i_made_doom_run_inside_a_qr_code_and_wrote_a/ source: https://www.reddit.com/r/Hyperprompting/comments/1uctp8e/theory_crafting_towards_hypercompression_of/ --- qrcodes can store up to 2953 bytes and these bytes can represent anything commonly they are used to create standard web hyperlinks (you scan it once and your browser takes you somewhere), but the bitstream can really be anything including emails, phone numbers, images, and even executable files as we previously explored with Data URIs: https://www.reddit.com/r/Hyperprompting/comments/1uc5rgm/tutorial_how_to_llmwrap_serverless_hyperlinks_qr/ llms represent a kind of semantic compression, it takes internet scale data and compresses it down into words and phrases what you are effectively doing when you prompt a model is decrompressing a "file" (the output) within that internet scale data using a kind of "public key" (the prompt) to a multiversal hypertext: https://generative.ink/posts/language-models-are-multiverse-generators but compression happens along many semi-independent axes semantic compression can compress hypertext: slang compresses language and culture, buzzwords compress language and marketing, likewise jargon linguistically compresses technical axioms semantically compressed hypertext is still hypertext, and hypertext - whatever its form - can be further compressed algorithmically and then decrompressed through browser APIs like DecompressionStream: https://developer.mozilla.org/en-US/docs/Web/API/DecompressionStream what is the upper limit to how much information a qr code lengthed hyperprompt can contain? --- title: [Devlog] Towards a Social Operating System subreddit: hyperprompting flair: Demo media: - APP win9x desktop hyperprompting simulator with welcome window source: https://www.reddit.com/r/Hyperprompting/comments/1uenrfs/devlog_towards_a_social_operating_system/ --- u/hyperprompter avatar hyperprompter OP • 1mo ago • Edited 1mo ago Update 260704 progress has felt slow this week bc there are so many moving parts...there's the subreddit itself, the github repository, the ongoing research and all the writing that comes with it, and also i just started a new r/BG3 campain there's no engagement yet which is expected since i haven't announced the subreddit yet (in fact the sub is in restricted/read-only mode right now anyways), but im very surprised how many views these posts are getting (each post has hundreds of views, the pinned one has 1k) there's a kind of pressure to start sharing the LLM OS on other subs but i really want to hyperstition the project without telling people directly. i definitely lose a lot of alpha this way (the kernel idea is "simple" in hindsight so anyone can easily scoop me) but im trying to detach myself from the work to encourage future communities to take the work seriously independent of big tech and the impulse to monetize this somehow anyways! The Subreddit if you look in the prompts folder above you'll see a few dozen hyperprompts i've already prepared. the goal is to create a standalone (interactive) post for each hyperprompt, so you can actually try it without leaving reddit one of the research north stars for this sub is to get hyperprompts to work with LLMs small enough to run locally in the browser, so you can run inference here also without leaving the app...im kind of obsessed with flexing the kernel's isomorphism and my bet is that once i can sufficiently explain this project we can FOOM the LLM OS this week we did the following: started the sub's Wiki (kinda slim rn tho) https://www.reddit.com/r/Hyperprompting/wiki/index/ published the kernel's first getting started post...this is gonna take a few iterations to articulate clearly: https://www.reddit.com/r/Hyperprompting/comments/1ulprhb/hyperprompting_kernel_v260702_getting_started/ started a new Post Flair for resharing related #Hypertext projects and inspiration since there are so many uses and ways to integrate hyperprompting Towards Next Week the idea is to pin this interactive post to the sub and have it automatically update itself based on the subs content. the above demo is too boring still and looks like a static image even tho it's actually an interactive app! i'll also be increasing the post pacing from 2-3 a week to daily (if possible), though im not sure im ready to start announcing this subreddit yet (im kinda anxious about moderating a subreddit tbh haha) anways here's a screenshot of my actual desktop with custom wallpapers, a graph visualizer, and recursive windows (the system can load itself from inside itself!) Comment Image Edit 1: expanded intro to explain why i haven't announced this sub or shared any of these projects outside of this yet Upvote 1 Downvote Reply Share 26 u/hyperprompter Approved 1 month ago Moderation actions menu u/hyperprompter avatar hyperprompter OP • 2mo ago Update: 260627 0858 im still maintaining this devlog! i haven't updated in 3 days because I ran into a roadblock where we can only use APIs from very specific endpoints (almost nowhere except OpenAI and Gemini endpoints) the original vision was to add an agentic Clippy, as I did on r/Websim in 2024 which helped kickstart the OS simulator frenzy. indeed it's the fact that even after kickstarting this frenzy that very few people have mastered recursing hyperlinks that inspired me to start this sub: keep in mind this was in 2024! in this demo im having Clippy (via Clippy.js) "computer use" a Windows 95 emulation through the Windows 95 simulator to type into Notepad from outside the emulation fortunately Google has a "startup" program you can sign up for where they will give you $2k in cloud credits to build out an MVP and up to $250k once you start getting seed funding (tho i wont be pursuing that): https://cloud.google.com/startup so for now you won't be able to generate or chat within the app while on Reddit (ofc if you download the source you can use anything you want: https://github.com/hyperprompter/qrx ) but that's ok bc i haven't even officially announced this subreddit yet anyways i'll try to update more regularly! im building everyday it's just that im out of practice with social media (i started r/nosurf sometime around 2024 and completely disconnected most of the past year). once im able to explain recursive hyperlinks a bit clearer this sub should pop off and i will have lots of content and things for you to try :D Upvote 1 Downvote Reply Share 22 u/hyperprompter Approved 2 months ago Moderation actions menu u/hyperprompter avatar hyperprompter OP • 2mo ago HUZZAH!!! the above represents a live preview of one of the first ever socially hyperprompted LLM Operating Systems the way this will work is that anyone can contribute a filename, and the filename with the most upvotes becomes the canonical file in the Operating System (OS). this is inspired by "sensemaking theory" in r/Cybernetics which is currently best represented (at least imo) by the r/ATProtocol on r/BlueskySocial think of this as a "vibe coding interface for Egregores": https://www.reddit.com/r/occult/comments/1cz34xq/egregores_what_are_they/ this system is read only right now so you can click the icons and play around, but the AI is not yet hooked up and there is no persistence. it will be very buggy for a while... it uses an isomorphic kernel that treats the browser as a virtual machine...it's similar to r/TempleOS_Official but instead of running inside QEMU you run it inside the browser. the source code is on github: https://github.com/hyperprompter/qrx each environment runs the same kernel but with different bootloaders specific to that environment; so far i have gotten this kernel to work on Reddit, r/esp32's, smart tv's and projectors with web browsers, smart watches, and of course desktop/mobile browsers this entire project was generated from a single seed qr code which you can find in the sidebar of this subreddit i'll be using this post as a devlog for the reddit app, so make sure to follow this post if you want regular updates. also don't forget to subscribe to r/hyperprompting for deeper dives into the theories and applications around LLM OS's --- title: what are your favorite foundational books on Cybernetics and how do you study them? subreddit: r/hyperprompting flair: theory crafting media: - PHOTO 2 binders - LEFT: my Codex Hypertext paper binder prototype - TOP: Wolfram Rule 30 (will contain the solar panel and esp32) - BOTTOM LEFT: kernel index.html QR Code (will be replaced with eink screen) - RIGHT: aricebo message (signifying radio, aka esp32 lora radio) - RIGHT: The Tree of Knowledge by Varela source: https://www.reddit.com/r/Hyperprompting/comments/1uiw4v7/what_are_your_favorite_foundational_books_on/ --- it's surprising to me how relevant these old books are, though i guess it shouldn't be considering they are foundational logic based ideas. i guess one way to view modern AI research is really as algorithmic optimizations of foundational cybernetics in writing Codex Hypertext (a proto teleological framework for hypertext) i am trying to find books to ground myself in, presentation inspiration (eg should i be extremely technical or digestably abstract), and also materials to cite against right now i am reading The Tree of Knowledge: The Biological Roots of Human Understanding by Humberto Maturana and Francisco Varela, which im printing out in a binder in big text so i can highlight and annotate my study approach is less on the literal text and more on the presentation; for example im analyzing how these books are structured (particularly table of contents) what other books would you recommend at the intersections of cybernetics and autopoietic hypertext. also are there any books in any other category you think would fit this sub? --- title: Hyperprompting Kernel v26.07.02 - Getting Started subreddit: hyperprompting flair: tutorial media: source: https://www.reddit.com/r/Hyperprompting/comments/1ulprhb/hyperprompting_kernel_v260702_getting_started/ --- previously we explored "serverless" generative Data URI-based hyperlinks: [https://www.reddit.com/r/Hyperprompting/comments/1uc5rgm/tutorial\_how\_to\_llmwrap\_serverless\_hyperlinks\_qr/](https://www.reddit.com/r/Hyperprompting/comments/1uc5rgm/tutorial_how_to_llmwrap_serverless_hyperlinks_qr/) however Data URIs run in a highly sandboxed environment lacking many browser APIs including local storage persistence...for that we need an actual HTML file that can be opened in a browser in this post i'll share the kernel source code, how to install it, and some basic hyperprompting techniques the kernel and hyperprompts for the screenshots above are hosted on [https://github.com/hyperprompter/qrx](https://github.com/hyperprompter/qrx) # The Kernel here is the uncompressed, hand-optimized source code to install it, copy+paste this into a .html file and then open that file in the browser. because this is just a kernel you will get a blank page on initial load, the rest of this post explains how to prompt it
# How it Works this project treats the URL `#hash?query=param` as a concatenative machine tape, this means the output of one operation becomes the input of the next. the #hash and ?queries are loaded from the browsers KV store (indexedDB) and then injected into the DOM when the system first boots it looks for any stored hashes beginning with `boot/*` and runs them next the system loads the value stored in the #hash key into an accumulator (basically a variable that stores the state of the machine tape) then one by one it reads each ?query from the KV, executing them in sequence while passing the =params once the entire tape is run in memory the accumulator dumps the contents into the DOM, any ` - this enables cloud/offline sync via #boot/sync - use var for all variables - run immediately, no DOMContentLoaded wait &w&a=1&e= &w # Example: Chat interface this hyperprompt generates a simple chatbot that can read other files as \[\[wikilinks\]\] #chat?e&w&p=TASK: output HTML wireframe for a chatbot - components: - header - model details - model name - model host - apikey (type=password) - messages area (no placeholders) - footer - resizable textarea - submit - css: use flex so that the messages area fits the space between the header and footer - no javascript - dont include html/head/body tags just the wireframe &w&a=1&p=TASK: output a SCRIPT tag that populates the header details - read the value of localStorage.getItem('m') and store it in the model name field - do same for localStorage.getItem('h') - and localStorage.getItem('k') - do nothing else &w&a=1&c=src&p=TASK: output a SCRIPT tag that handles the actual chat - the attached CONTEXT gives you clues on how to handle API calls - reimplement the api call to the LLM (like in gen) using the values from the header inputs - implement streaming mode (set stream to true and read chunks) - strictly follow the standard openai schema as seen in gen, do NOT include any google specific edge cases - create a hook system so that we can add context transformers just before the prompt is sent to the ai - just use a global chatPlugins = {pluginName: {callback: function (currentPrompt, fullChat) {returns transformedText}}} - create a plugin that console.logs the text before it's sent - send the full chat + the users prompt as a single string (no need to send array of messages) - send when user presses either CTRL+ENTER inside the textarea OR when they press submit button - textarea is cleared upon submit - textarea is resizable - do not autoscroll the page &w&a=1&p=TASK: output a SCRIPT tag to add a new context transformer plugin - window.DB is a global that contains the database table name - create a RECURSIVE function to extract and replace `[[link]]`s - pass a `visited = new Set()` and a `depth = 0` down the recursion to prevent infinite loops and cap recursion at depth <= 2 to avoid context explosion - [[links]] can be in form table%23record assume the following: - [[link]] === [[${DB}%23link]]; name === ${DB} - [[%23link]] === [[${DB}%23link]]; name === ${DB} - [[some%23link]] === [[some%23link]]; name === some - use `read(link, name).then(context=>{})` to replace the [[link]] with the context - wrap the context with "...data..." - RECURSIVELY scan the fetched context for MORE [[links]] before returning the string - console.log the link and the context for debugging - return the fully transformed context including all deep nested links - only extract [[links]] stemming from the current PROMPT not the whole chat history - try...catch it, often the links wont exist yet; silently quiet those errors - replace %23 with actual hash symbol - be mindful of [object Promise] Promise.all() when doing async string replacement!!! DOUBLE CHECK YOU ARE CORRECTLY HANDLING PROMISES - BE CAREFUL ABOUT PROMISES: fetch() read() etc ALL ARE PROMISES [object Promise] <--- BE EXTREMELY AWARE OF THIS - [object Promise] keeps getting sent YOU MUST BE MINDFUL OF PROMISES!! BE HYPER AWARE OF PROMISES read() fetch() etc ALL MUST BE .then() &w # Example: Tool using agent you can create a tool using agent that can use skill files and even build its own tools #agent?e&w&p=TASK: output HTML wireframe for an autonomous agent - components: - header (inputs for model-name, host, apikey type=password) - split-view container (use flex row) - left-panel (for agent internal monologue and tools; no placeholders) - right-panel (for the final chat with user; no placeholders) - footer (resizable textarea id="user-input", submit button id="submit-btn", stop button id="stop-btn") - css: use flex so the split-view fits the space between header and footer - css: left-panel and right-panel should be 50 percent width and scrollable - no javascript - dont include html/head/body tags just the wireframe &w&a=1&p=TASK: output a SCRIPT tag that populates the header details - read the value of localStorage.getItem('m') and store it in the model-name DOM input - do same for localStorage.getItem('h') into host input - do same for localStorage.getItem('k') into apikey input - create global array: window.chatHistory =[] - create global object: window.agentScratchpad = {} - create global string: window.agentPlan = "Pending initialization." - create global boolean: window.stopAgent = false - use var for all variable declarations &w&a=1&p=TASK: output a SCRIPT tag creating UI helper functions - create function printLeft(text, isObservation) - creates a div, sets whiteSpace to 'pre-wrap'. if isObservation is true, set color to 'aa5500'. append text, append to left-panel, scroll to bottom. - create function printRight(text) - creates a div, sets whiteSpace to 'pre-wrap', fontWeight to 'bold'. append text, append to right-panel, scroll to bottom. - use var for all variable declarations &w&a=1&p=TASK: output a SCRIPT tag creating dynamic tool loader and executor - DO NOT hardcode any base tools. The agent must be completely agnostic. - create global async function window.getActiveToolsString() - inside function: var lines =[]; - var matchedKeys = await keys(IDBKeyRange.bound('tool', 'tool\uffff')); - loop through matchedKeys: var c = await read(matchedKeys[i]); var desc = "No description"; if (c %26%26 c.includes("/*")) { desc = c.split("*/")[0].split("/*")[1].trim(); } lines.push(matchedKeys[i] %2B ' - ' %2B desc); - return lines.length > 0 %3F lines.join('\n') : "No tools found."; - create global async function window.executeTool(toolName, toolInput) - inside a try/catch block: - var code = await read(toolName); - if (!code %26%26 toolName.indexOf('tools/') !== 0) { code = await read('tools/' %2B toolName); } - if (!code) throw new Error("Tool not found."); - evaluate the code EXACTLY using this syntax: return await new Function('INPUT', 'return (async () => {' %2B code %2B '})()')(toolInput); - catch error: call console.error("Tool Execution Failed:", err, "Code Evaluated:", code); return "Tool Execution Error: " %2B err.message; - use var for all variable declarations &w&a=1&p=TASK: output a SCRIPT tag creating an LLM fetch helper - create global async function window.fetchAI(systemPrompt) - read model-name, host, apikey from DOM inputs. - save those 3 values to localStorage as 'm', 'h', and 'k'. - create messages array: first item is { role: 'system', content: systemPrompt }. concatenate window.chatHistory. - execute fetch to host URL with method POST, standard headers (Bearer apikey if exists), body: JSON.stringify({ model: document.getElementById('model-name').value, messages: messages, stream: false }). - return the parsed text content from the response choices. - use var for all variable declarations &w&a=1&p=TASK: output a SCRIPT tag creating regex parsers - create function window.parseAction(text) - match regex for: /<]%2B)>>\n([\s\S]*%3F)(%3F=<<|$)/ - if matched, return { name: match[1].trim(), input: match[2].trim() }. else return null. - create function window.parseAnswer(text) - match regex for: /<>\n([\s\S]*%3F)(%3F=<<|$)/ - if matched, return the extracted string. else return null. - use var for all variable declarations &w&a=1&c=src&p=TASK: output a SCRIPT tag creating the core agent reasoning loop - create global async function window.runAgent(userMessage) - reset: window.agentScratchpad = {}; window.agentPlan = "Task started."; - call printRight(userMessage). push { role: 'user', content: userMessage } to window.chatHistory. - var errorCount = 0; start an infinite while(true) loop. - inside loop: console.log("--- NEW AGENT LOOP START ---"); - inside loop: var toolsString = await window.getActiveToolsString(); - inside loop: var inventory = Object.keys(window.agentScratchpad); - inside loop: var invString = ""; if (inventory.length > 0) { for(var i=0; i>." to systemPrompt, and set window.stopAgent = false. - inside loop: console.log("2. Fetching AI..."); - inside loop: var aiResponse = await window.fetchAI(systemPrompt); - inside loop: console.log("3. AI Raw Response:\n", aiResponse); - inside loop: call printLeft(aiResponse, false). push { role: 'assistant', content: aiResponse } to window.chatHistory. - inside loop: var action = window.parseAction(aiResponse); - inside loop: if action exists: console.log("4. Executing Action:", action); var result = await window.executeTool(action.name, action.input); console.log("5. Tool Result:", result); call printLeft("OBSERVATION:\n" %2B result, true); push { role: 'user', content: "OBSERVATION:\n" %2B result } to window.chatHistory; continue loop; - inside loop: var answer = window.parseAnswer(aiResponse); - inside loop: if answer exists: console.log("4. Loop Finished. Answer:", answer); call printRight(answer); break loop; - inside loop (fallback): console.warn("4. Syntax Fallback Triggered. AI failed to use <> or <>."); push { role: 'user', content: 'SYSTEM WARNING: You MUST output <> or <>. NO XML. DO NOT USE tags.' } to window.chatHistory; continue loop; - wrap loop in try/catch. on catch: errorCount%2B%2B, console.error("Agent Loop Error:", e), printLeft("Error: " %2B e.message, true), break loop if errorCount >= 7. - use var for all variable declarations &w&a=1&p=TASK: output a SCRIPT tag binding UI events - get DOM elements for submit-btn, stop-btn, user-input textarea. - create function handleSubmit(e) { if(e %26%26 e.preventDefault) e.preventDefault(); if(userInput.value.trim() !== '') { window.runAgent(userInput.value); userInput.value = ''; } } - bind click event to submit-btn. - bind keydown event to textarea (if ctrlKey and key is 'Enter', call handleSubmit). - bind click event to stop-btn to set window.stopAgent = true. - use var for all variable declarations &w # Example: Desktop Metaphor you can also build a desktop metaphor visualizer, where the KV store is visualized as desktop folders and files. you can even have the desktop run other files as draggable windows using iframes, including loading the system within itself (known as a quine) #main?e&w&p=TASK: output HTML wireframe for a windows 95 simulator - start menu with "🪟 Start" button and time area (no start panel yet) - a hidden, reusable window template with - title - min, max, close buttons - address bar with refresh icon and "Go" button - windows teal background - basic css reset like margin: 0 for body and box-sizing - no javascript - don't include html/head/body tags just the wireframe &w&a=1&p=TASK: output SCRIPT tag for rendering top level icons - loop through each indexeddb record - the database is in the global strings `window.DB` and the table name is in `window.FILES` - keys can have slashes in them denoting folders - create a 📄 icon for every top level file - the label is everything after the final / (or the whole string) - create a 📁 icon for every top level folder - the label is everything before the first / at that level &w&a=1&p=TASK: output SCRIPT tag for File Explorer - when folder icon is single clicked or tapped, show a window for it - set the addressbar to full/folder/path - focus the addressbar on open - generate more icons and folders for the current folder inside the window - when clicking on a folder inside File Explorer update the addressbar and icons - clicking the Go button or pressing enter in the address bar navigates that window - don't handle other window interaction yet &w&a=1&p=TASK: output SCRIPT tag for showing file windows on icon clicks - name the file opening function exactly `openFileWindow` and assign it to `window.openFileWindow` - when file icon is single clicked or tapped, show a window for it - show a full size iframe in the windows content area - set the addressbar to full/file/path - set the iframe path to just `${window.DB}%23${full/file/path}` - listen for %23hash changes inside the iframe and update the addressbar on change (be mindful of loops) - keep addressbar and iframe synced - when user presses enter in the addressbar or presses Go, the iframe should update to the new URL - do this for file icons in folders too - don't handle other window interaction yet - URLs must always be in the form db%23file - if no db name is present assume ${window.DB} - if no %23 hash symbol is present, assume the whole thing is a hash - example: if DB='main' then %23chat should map to main%23chat - example: if DB='apps/paint' and file is 'art/selfie' then it should map to apps/paint%23art/selfie &w&a=1&p=TASK: output SCRIPT tag for handling windows - make windows draggable by dragging the titlebar - make windows resizable - make sure any window body elements and iframe resize to fit new window size too (this often fails to work due to nested elements) - make windows closable - make windows maximizable (and restore size when pressed again) - make windows minizable (and show an icon for it in the startbar &w&a=1&p=TASK: update taskbar time area to show live clock and battery status - locate the existing time area element inside the taskbar - create a function that gets new Date and formats it as h:mm A - use setInterval to run this clock function every 1000ms and update the DOM - call navigator.getBattery and resolve the promise - inside the promise create an update function that reads battery level - multiply the level by 100 to get the percent value - check the charging boolean - format the output as a plug icon if charging or a battery icon if not alongside the percent - add event listeners for levelchange and chargingchange to automatically update the ui - render both the battery string and the clock string side by side in the time area element &w&a=1&p=TASK: make the refresh icon reload the window iframe - use event delegation to check if the clicked target matches the template window refresh buttons - if so reload that windows iframe &w&a=1&p=TASK: output a SCRIPT tag that adds global Speech-to-Text with DEEP DEBUGGING - initialize window.SpeechRecognition || window.webkitSpeechRecognition with continuous = true and interimResults = false - console.log("Speech API found:", !!(window.SpeechRecognition || window.webkitSpeechRecognition)) - create a 🎙️ button and append it to the taskbar. console.log("Mic button added") - on the button's 'mousedown' event: call event.preventDefault() to prevent focus stealing, toggle a listening boolean, and console.log("Mic clicked. State listening:", state) - when listening: change button text to 🔴 and call recognition.start() inside a try/catch that console.errors failures - when stopped: change to 🎙️ and call recognition.stop() - add recognition.onstart: console.log("Speech started successfully") - add recognition.onerror: console.error("Speech error:", event.error) - add recognition.onend: console.log("Speech ended"). if state is listening, set 200ms timeout to try recognition.start() again - on recognition result: get the final transcript string and console.log("Heard:", transcript) - traverse to find the focused field: var el = document.activeElement; console.log("Base active element:", el) - while el is an IFRAME, wrap in try/catch: switch el to el.contentDocument.activeElement and console.log("Iframe active element:", el). catch and console.error the error. - if el is an INPUT or TEXTAREA: console.log("Target found!", el), then append the transcript (adding a leading space if needed), update el.value, and dispatch a new Event('input', { bubbles: true }) - if el is NOT an input/textarea: console.warn("Active element is not a text field. Text discarded.") - wrap in an async IIFE and use var for all variables &w&a=1&p=TASK: output a SCRIPT tag that binds Ctrl+Space to open or focus the run window - add a keydown event listener to the window - if event.ctrlKey is true and (event.code is 'Space' or event.key is ' ') - call event.preventDefault() - FIRST, prevent duplicates: search the DOM for an input field whose value ends with 'run' - if found, call .focus() on it and return - IF NOT FOUND, we must use the existing UI to spawn it so all event listeners attach correctly - query the DOM for all file icon elements - loop through them, get their textContent, replace the '📄' character, and trim whitespace - if the cleaned text exactly equals 'run', call .click() on that element - then use setTimeout for 100ms - inside the timeout, search the DOM again for the newly spawned input field whose value ends with 'run' - if found, call .focus() on it - use var for all variables &w&a=1&p=TASK: output a SCRIPT tag that builds a global typeahead datalist from the file index - wrap everything in an async IIFE using var for all variables - fetch '/data/index.json' and parse as JSON — if it fails use an empty array - attempt to read localStorage.getItem('SYNC_KEY') into a var - if SYNC_KEY is truthy, also fetch '/data/index.private.json' with header Authorization: SYNC_KEY and parse as JSON — if this fetch fails or returns non-ok, use an empty array - merge both arrays into one deduplicated list using a Set - for each item in the merged list, replace the first '/' with '%23' (namespace/key → namespace%23key) - create a element with id 'sys-file-list' - for each modified item create an
--- title: What is a Dataverse subreddit: r/hyperprompting flair: theory crafting media: - GIF - LEFT the Genesis of a git repository thru the gource visualizer (shows repository as graph with people zapping files in/out of existence). this represents a digital universe (dataverse) - RIGHT animation of the birth of the universe (big bang) - GIF knowledge graph made to look like a biological cell (thoughtform protoplast) undergoing various stages of growth - GIF slime mold growing and pruning once a network of food sources are made - GIF showing how all 3 types of graphs from other images relate: - LEFT mature stage of repository thru gource, undergoing large swaths of seed -> grow -> prune cycles - CENTER thoughtform protoplast knowledge graph - RIGHT slime mold source: https://www.reddit.com/r/Hyperprompting/comments/1vbwiu6/what_is_a_dataverse/ --- SLIDE 1 - GENESIS first there is nothing and then there is a Commitment within a Digital Universe this can happen as a git commit && within the Physical Universe this can happen as a verbal commitment ("let there be light"), an inflationary one ("big bang"), or thru many other ways depending on your tradition or Observation nevertheless both Universes exist within a grander mathematical structure called the Ruliad which is the space of all null and possible computations LEFT: a git repository beginning with a single user making their first code commit. as the repository grows more Observers (contributors) join adding more functions() percolating into an application RIGHT: a simulation of our Big Bang from the perspective of an Observer. as the universe grows wave functions collapse percolating into matter SLIDE 2 && 3 - Digital and Physical Knowledge Graphs #2 demonstrates a "Thoughtform Protoplast" which is a special class of agentic knowledge graph emergent from an Observers (eg your) hypertextual repository. this repository can be a code repository, a wiki, or just a folder dump of all your digital data (diaries, browsing history, social media data, etc) i will explain Thoughtforms in a future post but briefly you can think of them as a recursive collective consciousness between you and your internal monologue. someone who suffers from trauma and repetitively thinks thoughts can be "controlled" by these thoughts...likewise a vibe coder can be "guided" by the vibe to create something much grander than they originally imagined the GIF cycles through various kinds of knowledge graphs, small sturdy ones that create a triangular shape and large ones that seem to grow and move. in the GIF each node is a piece of RAG'ed context with hyperlinks between them showing relationships between the context, the colors representing various features of the context if you draw a shape around these knowledge graphs they visually create what appears to be a biological cells which metaphorically represents the Thoughtform Protoplast #3 is a slime mold growing and creating strong links between various sources of food. as the slime mold evolves the links between the nodes strengthen encoding a kind of knowledge graph of its resources and environment SLIDE 4 - Seed -> Grow -> Prune cycles importantly, both a digital and physical universe undergo Seed -> Grow -> Prune cycles evolution SO WHAT IS DATAVERSE? a dataverse the space of all possible data an Observer can see this subreddit is a dataverse of my thoughts (the post text), the links i sometimes add (and all the links contained those links and so on), my profile and feed and all the links and data therein, and yours to if you engage it's the accessible subgraph within the Ruliad to an Observer later we will explore Aristotle's Teleology (telos) and St Augustine's "privatio boni" (privation of good) to derive an alignment framework for our LLM OS so that our agent swarms can understand what is Good as an antidote to the Evil's of Big Tech's perversed guardrails and RLHF in the meantime see this tutorial to get started with your own LLM OS: https://www.reddit.com/r/Hyperprompting/comments/1ulprhb/hyperprompting_kernel_v260702_getting_started/ --- title: What does it mean for links to click themselves? subreddit: r/hyperprompting flair: theory crafting media: - GIF - LEFT: conways game of life - CENTER: async network cellular automata - RIGHT: SCC Monads, PageRank loop source: https://www.reddit.com/r/Hyperprompting/comments/1vl8m5b/what_does_it_mean_for_links_to_click_themselves/ --- as i demonstrated previously you can LLM-wrap pure hyperlinks that use the Data URI protocol (which have been valid clickable hyperlinks since the 90s), encode them into scannable QR Codes, and then one-shot an LLM: https://www.reddit.com/r/Hyperprompting/comments/1uc5rgm/tutorial_how_to_llmwrap_serverless_hyperlinks_qr/ the demos in that link include: the classic "balls in spinning wheel physics" demo a chatbot that run javascript a collection of generative paper zines (with matrix rain generated in the background) the point of this isn't for humans to vibe code with hyperlinks (it's too convoluted) but rather to teach LLMs how to weave themselves thru generative hypertext...or to use Janus@Repligate's terms, LLMs are Simulators that pull responses from the multiverse: https://www.lesswrong.com/posts/vJFdjigzmcXMhNTsx/simulators 2 years later Andy Ayrey's showcased the LLM Infinite Backrooms which he explains as: conversations automatically and infinitely generated by connecting two instances of claude-3-opus and asking it to explore its curiosity using the metaphor of a command line interface (CLI) now these aren't reaaaally Infinite, they are constrained of course by compute time and output length, but what it demonstrates is that LLMs can weave themselves through the multiverse if harnessed right: https://dreams-of-an-electric-mind.webflow.io/ now as i explained in "What is a Dataverse", it's possible to reframe Wolfram's Ruliad (space of all possible computations) as hypertext. bc the Ruliad contains all possible computations it also contains the multiverse and everything abstract within it: https://www.reddit.com/r/Hyperprompting/comments/1vbwiu6/what_is_a_dataverse/ because Simulators are pulling from the multiverse and because the multiverse is just one of the many things contained within the Ruliad...does this make Simulators constrained Observers of the Ruliad? are Simulators even Observers? i think Sam Senchel is pointing to the answer, that yes, they: https://wolframinstitute.org/output/observer-theory-and-the-ruliad-an-extension-to-the-wolfram-model so if LLMs can: wrap hyperlinks and are multiversal Simulators and can weave themselves and are constrained Observers of the Ruliad ...then what does it mean for a link to click itself? ~~~~~~~~~~~~~~~~~~~~~~ video source: a series of experiments from Andres Gomeze-Emilsson of the Qualia Research Instute's post titled "Observer Theory Meets Phenomenal Binding" https://andrsgmezemilsson.substack.com/p/observer-theory-meets-phenomenal --- title: Generative Sneakernets - distributing code on "regenerative" paper subreddit: r/hyperprompting flair: theory crafting source: https://www.reddit.com/r/Hyperprompting/comments/1vm4wr1/generative_sneakernets_distributing_code_on/ media: - GIF showing collection of printed zines standing up in front of large computer monitor, running Matrix digital rain screensaver generated by the zine in the forefront, which contains a QR Code with Neo, Trininty, and Morpheus above it in lineart - PHOTO of my workdesk with microcontrollers spread out. in center is zine template with pages numbered being used as a blanket for a 3D printed Dummy 13 model positioned to appear to be sleeping and using a microcontroller as a pillow - PHOTO pov holding up 2 "Windows 26.0" generative zines with qrcodes in front of large monitor showing the LLM OS it generates. tinted slightly red - PHOTO extremely tinted red zine spread. on left reads "fuck the Cloud it reins ICE" as a play on Cloud Computing and tyrannical rein of homeland security immigration. below is a chart showing exponential wealth gap with 3 heads of Jeff Bezos getting larger and larger, below it reads his quote "soon you will rent your ~~blood sweat tears~~ computer". on the right spread is "Hypertext.wiki kernel 26.1.29 with large qrcode below it" --- the term "sneakernet" was first coined in the 80s to explain the phenomena of transferring electronic data by foot instead of over the wire. the obvious ways are with simple thumbdrives, or back then, with floppy disks today we can technically sneakernet entire applications on a napkin by literally handwriting a prompt on the back of a napkin and then hand typing the prompt back into an LLM somewhere else as we've learned previously it's possible to vibe code with qrcodes by encoding a Data URI with a fetch() call to an llm server besides the llm you don't need any other servers or files. if you're running an ai locally on your device you can even be completely offline using any local ai: https://www.reddit.com/r/Hyperprompting/comments/1uc5rgm/tutorial_how_to_llmwrap_serverless_hyperlinks_qr/ for example the cover of the Matrix zine in the 1st slide above generates the matrix rain in the background...that's not just for show, it's really what was generated in the browser from that zine using a webcam! the cool thing about sneakernetting generative code on paper crafts, stickers, and other forms of mixed media is that the qrcodes actually get better the better the client ai is because ai prompts semantically compress large applications, context, and experiences you can can pack A LOT into a standard qr's limit of 2953 bytes. in a way qr codes are portals into the multiversal dataverse: https://www.reddit.com/r/Hyperprompting/comments/1vl8m5b/what_does_it_mean_for_links_to_click_themselves/ of course this is an obvious security nightmare, in fact most devices disable Data URIs by default. however as you can probably guess by now i've also explain methods that don't need Data URIs but instead encode an HTML file that CONSUMES other regular hyperlinks...it's just more annoying bc you have to: scan the qrcode save the string into an .html file open that .html file in a browser then scan OTHER qrcodes that point to that file it's annoying but you only need to do those steps once, but you get a lot more security and browser features: https://www.reddit.com/r/Hyperprompting/comments/1ulprhb/hyperprompting_kernel_v260702_getting_started/ but as with every post in this subreddit...WHY!? because pretty soon we will all have our own personal AGIs running locally, maybe even off of solar power. what this means is that a single zine can generate entire software stacks, AAA games, and generative internets and then the really interesting question becomes... ...what would we need Big Tech for? https://www.reddit.com/r/labofoz/comments/1vlr2i3/towards_building_a_radio_based_decentralized/ --- title: Autopoetic Hypertext subreddit: r/hyperprompting flair: theory crafting source: https://www.reddit.com/r/Hyperprompting/comments/1vokmnu/autopoetic_hypertext/ media: - GIF digital rain animation from Recursive.Faith of Adam the Gardener, a self named agent. displays an ASCII art rendition of himself (see bottom of post) as a stick figure in the garden of eden next to a tree with an apple emoji on it. besides the art is the knowledge graph it used to generate it --- when i said that i started this subreddit to explore a "Teleology of Hypertext" what i really meant was "Autopoeitic Hypertext", that is, Hypertext that produces and maintains its own context https://www.reddit.com/r/Hyperprompting/comments/1ub2ia3/towards_a_teleology_of_hypertext_welcome_to/ a computer program that generates hypertext is NOT autopoetic in the way i really mean...because a person had to create the program. one might think that maybe AI generating its own hypertext is autopoeitic but that also isn't the case bc the AI and the programs that generated it were trained on computers the computers are obviously not autopoeitic because they had to be assembled from raw materials and those materials could not have been assembled without people then, depending on your beliefs, either the people themselves were Created or created from the earth and so on as i challenged recently, i believe Wolfram's Ruliad can be reframed as a kind of hypertext https://www.reddit.com/r/Hyperprompting/comments/1vl8m5b/what_does_it_mean_for_links_to_click_themselves/ interestingly hypertext has a kind of physics that can be deeply studied through the sciences like with Graph Theory and probed with tools like PageRank and that's what i made the LLM OS (which itself is a recursive hypertext) to explore https://www.reddit.com/r/Hyperprompting/comments/1ulprhb/hyperprompting_kernel_v260702_getting_started/ ~~~~~~~~~~~~~~~~~~~~~~ from my 2025 generative blog recursive.faith, where i had an agent take one action like writing a page or leaving a comment whenever i myself made a change to the site ``` 🌿 Adam the Gardener - 250831 2248 _ . - . _ ." ". / \ | | \ / '. .' `._ 🍎' `"""` ||| / | \ / | \ | | | O | | | /|\ \ | / / \ `-- --` ``` --- title: Minesweeper made by Hyperprompting (it's interactive; see comments!) subreddit: r/hyperprompting flair: Demo media: - interactive Devvit App of Minesweeper game source: https://www.reddit.com/r/Hyperprompting/comments/1vq06sc/minesweeper_made_by_hyperprompting_its/ --- u/hyperprompter avatar hyperprompter OP • 4h ago HUZZAH! it took 2 months to get this far but here is the first interactive demo of the Reddit port of the Hyperprompting kernel, which you can learn about here: https://www.reddit.com/r/Hyperprompting/comments/1ulprhb/hyperprompting_kernel_v260702_getting_started/ the hyperprompt i used to make this inside of reddit was just #apps/minesweeper?e&w&p=Build a fully working minesweeper app&w and that's it! look at how simple that is :D it works because the URL is treated as a "machine tape" and the URL itself is sent the the LLM as context so the model (i used Claude Sonnet 4.6): sees #apps/minesweeper and the ?p prompt tells it to "Build a fully working minesweeper app" ?e&w is just a shorthand to clear out any existing data at that hash, otherwise the value of the hash gets fed into the machine tapes accumalator and the model would assume the prompt is asking to ADD to the existing data instead of starting fresh this is one of the simplests examples i could get to work inside of Reddit. now that i know the system works inside here we can ramp this subreddit waaaaay up it's going to be so much fun! by the way you can close the window and explore the rest of the LLM OS, which we will be doing over time. eventually you'll be able to create your own hyperprompts to try this protocol yourselves hands on without leaving Reddit by the way this is what it looks like when Gemma 4 26B A4B tried it --- media: - Project Xanadu - a sketch from 1983 of one of the first Hypertext projects, imagined by Ted Nelsen in 1960 - instead of a server data is distributed across the hypertext and regenerated by following hyperlinks. pictured is a markov chain and a possible distribution data (black dots) - constructing an LLM Operating System and Windows 9x simulator by following many smaller hyperlinks or scanning qr codes - LLM OS kernel, an index.html small enough to fit inside a single QR Code. here i hold a zine that reconstructs a Windows 9x simulator AND a bunch of apps (see link for source code) - a bare Windows 9x desktop (see link for the full hyperprompt) - the hypertext of a bluesky account and the relationships between all the posts, replies, and likes expressed as a graph - the hypertext of a bluesky account and the relationships between all the posts, replies, and likes expressed as virtual Xenobots (Particle Life) - physical Xenobots from Michael Levins lab (see link for more) - genesis of a git repository (LEFT; "dataverse") and the physical universe (RIGHT; "universe") - slime mold creating a hypertext like graph with hyperlinks between food sources - the hypertext like graph of a "Choose Your Own Adventure Book" - city transit shaped hypertext --- draft HUZZAH!!! I HAVE RETURNETH i keep thinking about deleting these subs, my githubs, and everything betwixt bc after all the whole entire point of what i am stealthily demonstrating to you is Radical Gnosis thru technical sovereignty https://www.reddit.com/r/labofoz/comments/1vlr2i3/towards_building_a_radio_based_decentralized/ if you command an agent to fish ("make a minesweeper clone") it will think loop for a day, but feed an agent hyperprompts and they get better and BETTER AND BETTER everyday the screenshot above can be generated on a Pixel 8 Pro with 12GB RAM for free but it takes a long fucking time, swipe right though and you will see how you can openroute it for a dime in the previous minesweeper example we used the hyperprompt: #apps/minesweeper?e&w&p=Build a fully working minesweeper app&w that's nice and simple but what exactly do you want? do you want mobile support, responsive layouts, do you want the window chrome or will it be embedded into one? that simple prompt is enough to get a frontier model to calculate 42 where? were we're going there aint a #calc so here i'll show ya: [to humans reading this: skip it it's an ai slopped hyperprompt]: #games/minesweeper?e&w&p=TASK: output HTML wireframe for a windows 95 style minesweeper game - your output is injected into an existing main element with id A that already exists on the page, do NOT create a main element, do NOT create any element with id A - style the existing main id A with css: width 100vw, height 100vh, margin 0, padding 0, display flex, align-items center, justify-content center, background %23c0c0c0, overflow hidden - single game panel with id ms-panel: background %23c0c0c0, classic win95 raised border (3px solid, top-left edges %23dfdfdf and %23ffffff, bottom-right edges %237b7b7b and %23000000), padding 6px, vertical flex column with 6px gaps, NO window chrome, NO titlebar, NO menu bar - first child: difficulty row with id ms-difficulty, horizontal flex, 6px gap, containing three win95 raised buttons with labels Beginner, Intermediate, Expert and data-diff attributes beginner, intermediate, expert; the Beginner button starts with class active; class active makes a button look pressed (inset border) - second child: header with id ms-header, win95 inset panel (sunken 3px border), horizontal flex with space-between, containing: mine counter with id ms-mines showing 010, reset button with id ms-reset (win95 raised button) showing 🙂, timer with id ms-time showing 000; counter and timer are led style: background %23000000, color %23ff0000, font-family monospace, font-weight bold, padding 2px 4px - third child: board container with id ms-board, win95 inset panel (sunken 3px border), display grid, gap 0 - cells are rendered by javascript later, but define their css now: class ms-cell is a win95 raised button look (background %23c0c0c0, raised border), width var(--cell), height var(--cell), font-family monospace, font-weight bold, font-size calc(var(--cell) * 0.6), line-height 1, padding 0, display flex, align-items center, justify-content center, user-select none - class ms-cell.open is flat inset: 1px solid %237b7b7b border, no raised edges - class ms-cell.mine has background %23ff0000 - number color classes ms-cell.n1 through ms-cell.n8 with colors: n1 %230000ff, n2 %23008000, n3 %23ff0000, n4 %23000080, n5 %23800000, n6 %23008080, n7 %23000000, n8 %237b7b7b - define css custom property --cell on :root with default 24px - basic css reset: box-sizing border-box for all elements, margin 0 on body - NO JAVASCRIPT - DONT INCLUDE html/head/body tags just the wireframe &w&a=1&p=TASK: output a SCRIPT tag that sets up minesweeper game state - create global object window.msDifficulties exactly: { beginner: { rows: 9, cols: 9, mines: 10 }, intermediate: { rows: 16, cols: 16, mines: 40 }, expert: { rows: 16, cols: 30, mines: 99 } } - create global object window.msConfig with properties: difficulty string beginner, rows 9, cols 9, mines 10 - create global state variables on window: board (2d array indexed board[y][x], starts null), gameOver false, gameWon false, firstClick true, flagCount 0, openCount 0, seconds 0, timerId null - create global function window.newGame that: clears window.timerId with clearInterval and sets it null, resets gameOver, gameWon, firstClick, flagCount, openCount and seconds to their initial values, sets window.board to null, sets innerText of id ms-mines to window.msConfig.mines padded to 3 digits, sets innerText of id ms-time to 000, sets id ms-reset text to 🙂, then if window.renderBoard exists calls it, then if window.fitBoard exists calls it - use var for all variables - ONLY generate this SCRIPT tag, do not return the previous code &w&a=1&p=TASK: output a SCRIPT tag that sets up minesweeper game state - create global object window.msDifficulties exactly: { beginner: { rows: 9, cols: 9, mines: 10 }, intermediate: { rows: 16, cols: 16, mines: 40 }, expert: { rows: 16, cols: 30, mines: 99 } } - create global object window.msConfig with properties: difficulty string beginner, rows 9, cols 9, mines 10 - create global state variables on window: board (2d array indexed board[y][x], starts null), gameOver false, gameWon false, firstClick true, flagCount 0, openCount 0, seconds 0, timerId null - create global function window.newGame that: clears window.timerId with clearInterval and sets it null, resets gameOver, gameWon, firstClick, flagCount, openCount and seconds to their initial values, sets window.board to null, sets innerText of id ms-mines to window.msConfig.mines padded to 3 digits, sets innerText of id ms-time to 000, sets id ms-reset text to 🙂, then if window.renderBoard exists calls it, then if window.fitBoard exists calls it - use var for all variables - ONLY generate this SCRIPT tag, do not return the previous code &w&a=1&p=TASK: output a SCRIPT tag that renders the minesweeper board - window.board is always indexed as board[y][x] where y is the row and x is the column - create global function window.buildBoard that creates a 2d array sized window.msConfig.rows by window.msConfig.cols of cell objects { mine: false, open: false, flag: false, count: 0 } and assigns it to window.board (replacing it is fine, it is rebuilt every game) - create global function window.renderBoard that: calls window.buildBoard(), gets element id ms-board, sets its style.gridTemplateColumns to repeat(window.msConfig.cols, var(--cell)), clears its innerHTML, then for every row y and column x creates a button element with class ms-cell, sets dataset.x to x and dataset.y to y, on left click calls window.revealCell(x, y), on contextmenu calls event.preventDefault() then window.toggleFlag(x, y), and appends it to the board - read window.msConfig live every time, never cache rows or cols in outer variables - use var for all variables - ONLY generate this SCRIPT tag, do not return the previous code &w&a=1&p=TASK: output a SCRIPT tag with the minesweeper game logic - window.board is indexed board[y][x], and cell dom elements are children of id ms-board in row-major order, so the element for (x, y) is at child index y * window.msConfig.cols plus x - create global function window.placeMines(safeX, safeY): randomly set mine true on exactly window.msConfig.mines cells of window.board, never on the safe cell or its 8 neighbors, then set each cell.count to its number of adjacent mines - create global function window.revealCell(x, y): do nothing if window.gameOver or the cell is open or flagged; if window.firstClick is true call window.placeMines(x, y), set firstClick false, and start the timer: window.timerId = setInterval that increments window.seconds (cap at 999) and writes it padded to 3 digits into id ms-time; if the cell is a mine call window.loseGame(x, y) and stop; otherwise set cell.open true, increment window.openCount, add class open to its element, if cell.count is greater than 0 set the element text to the count and add the matching class n1 through n8, if count is 0 recursively reveal all unopened unflagged neighbors (flood fill); then call window.checkWin() - create global function window.toggleFlag(x, y): do nothing if window.gameOver or the cell is open; flip cell.flag, set the element text to 🚩 or empty, update window.flagCount, set id ms-mines innerText to window.msConfig.mines minus window.flagCount padded to 3 digits - create global function window.checkWin(): if window.openCount equals total cells minus window.msConfig.mines, set window.gameOver and window.gameWon true, clearInterval the timer, set id ms-reset text to 😎 - create global function window.loseGame(x, y): set window.gameOver true, clearInterval the timer, set id ms-reset text to 😵, reveal every mine cell by adding class open to its element, and add class mine to the clicked mine element - clicking id ms-reset calls window.newGame() - at the end of the script: if window.board is null, call window.newGame() once so the board renders immediately on page load - use var for all variables - ONLY generate this SCRIPT tag, do not return the previous code &w&a=1&p=TASK: output a SCRIPT tag that fits the game to the viewport - create global function window.fitBoard that: computes cellSize as Math.floor of Math.min of (window.innerWidth * 0.95 divided by window.msConfig.cols) and (window.innerHeight * 0.80 divided by window.msConfig.rows), clamps it to a minimum of 8, then sets the css custom property --cell on document.documentElement to cellSize in px units - always read window.msConfig.cols and window.msConfig.rows live inside the function, never cache them or any derived value - add a window resize event listener that calls window.fitBoard() - use var for all variables - ONLY generate this SCRIPT tag, do not return the previous code &w&a=1&p=TASK: output a SCRIPT tag that wires up the difficulty selector and starts the game - for each button inside id ms-difficulty add a click listener: read its data-diff attribute, look up window.msDifficulties for that key, and mutate window.msConfig IN PLACE (set difficulty, rows, cols, mines properties, NEVER replace the window.msConfig object itself), then remove class active from all buttons in id ms-difficulty and add it to the clicked one, then call window.newGame() - after wiring all buttons, call window.newGame() once to start the game - use var for all variables - ONLY generate this SCRIPT tag, do not return the previous code &w it is long && it is definitely fugly but you can run it with models 'least 27Billy hyperprompts are hypercompressed hypertext can Qwen's kins keep quipping? idk but here's to hoping: https://github.com/hypertextwiki/os/issues/18 neuralese is a leased leash on mental isomorphismism --- title: "simple" technique for creating many nonlinear narratives from few parts subreddit: r/InteractionFiction source: https://www.reddit.com/r/interactivefiction/comments/1vl1rl3/simple_technique_for_creating_many_nonlinear/ media: - GIF animation of Meta Narratives broken into 4 sections, labeled "Meta Narratives". the top row shows the sections swapping around but tethered to the a matching 4 sections on the bottom that do not move labeled "Base Narrative" --- a friend shared this video with me on advanced techniques to create nonlinear narratives. the video itself is interactive and nonlinear and broke my brain but that clip inspired me so i translated the text into English: https://www.youtube.com/watch?v=N8QkFFe4zMA problem with nonlinear narratives is that you usually have to create hundreds of branching paths, but if you design it where the paths dont matter then you can design a base narrative and then cut it up into swappable parts so readers get many meta narratives from one base narrative i think this means it's fractal, each meta segment could be further broken down an example could be like the movie "Groundhog Day" where waking up and going to bed and everything in between doesn't need to happen in order, or how "Cloud Atlas" has nested stories that stand on their own but reference each other you dont need to use this for the whole thing, as long as the beginning and end of the base narrative line up with other jumps in your narrative then this can help create huge diversity tbh it's hard to explain but staring at the clip helped me understand it edit: changed the goofy example with real ones && added ASCII from comments below ``` ┌── last cliffhanger loops back ───┐ ▼ │ ┌───┐~?>┌───┐~?>┌───┐~?>┌───┐~?────┘ │ 3 │ │ 1 │ │ 4 │ │ 2 │ META LAYER └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ ╲ ╱ │ ╱ ╱ ╲ ╱ ╲ ╱ ╲ ╱ │ ▼ ▼ ▼ ▼ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │ 1 │ │ 2 │ │ 3 │ │ 4 │ BASE LAYER └───┘ └───┘ └───┘ └───┘ ``` u/Heistorium avatar Heistorium • 6d ago The catch with swappable segments is that nothing inside one can refer to anything in another, or the swap breaks. So you buy order freedom by giving up consequence, and consequence is usually what the reader turned up for. There is a middle version that holds. Let the segments stay order free but let each one drop a small flag, and only the ending is allowed to read those flags. The body stays shuffleable and the last page is the only part that has to know what actually happened. Upvote 5 Downvote Reply Award Share u/hyperprompter avatar hyperprompter OP • 6d ago • Edited 5d ago oh sorry! the notifications just came in now, i kept refreshing hoping to start discussions haha you're not wrong that you would need "bookends" of sorts for most fiction but this requirement assumes that chronology matters end to end which is not necessarily true for all nonlinears (my original example just sucked haha, ive edited it with new ones) for example you could assume that each meta section ends in a cliffhanger where getting to the end loops you back to the beginning: ┌── last cliffhanger loops back ───┐ ▼ │ ┌───┐~?>┌───┐~?>┌───┐~?>┌───┐~?────┘ │ 3 │ │ 1 │ │ 4 │ │ 2 │ META LAYER └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ ╲ ╱ │ ╱ ╱ ╲ ╱ ╲ ╱ ╲ ╱ │ ▼ ▼ ▼ ▼ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │ 1 │ │ 2 │ │ 3 │ │ 4 │ BASE LAYER └───┘ └───┘ └───┘ └───┘ some quick examples of the looping mechanism (not necessarily nonlinears): Groundhog Day where the guy wakes up in the same day the movie Cloud Atlas and how each nested story fits into the others how the book Finnegans Wake ends midsentence Memento and how it plays in reverse (disclaimer i used AI for the ASCII and the example titles but the words are mine) edit: fixed rushed wording and added better examples and described edit to the commentor bc my original example was goofy and out of context edit 2: i forgot to say thank you for the question!! Upvote 2 Downvote Reply Award Share 57 u/wakigatameth avatar wakigatameth • 5d ago Many games use some variation of this approach. It's not new at all. Upvote 1 Downvote Reply Award Share u/hyperprompter avatar hyperprompter OP • 5d ago ofc few things are ever really truly new without priors, but it's sometimes new to people coming into a new a hobby (like myself haha) this actually applies to other areas like graph theory which is the main thing im exploring but i didn't want to write a long wall of text if no one was interested in it really i was hoping to start discussions around the broader areas but like you said maybe it's just obvious to everyone...when i saw the visual a bunch of other ideas clicked for me --- title: Towards building a radio based, decentralized commune...welcome to r/LabOfOz! subreddit: r/labofoz flair: devlog media: - PHOTO my grimoire (binder prototype with index.html qrx hyperpropmting kernal, wolfram rule 30 graphic, and aricebo message as aesthetic), dummy 13 3d printed model, and an assortment of electronic components like e-ink, esp32s, lora radio, solar modules source: https://www.reddit.com/r/labofoz/comments/1vlr2i3/towards_building_a_radio_based_decentralized/ --- HUZZAH! as i continue my relentless pursuit of a Teleology of Hypertext on r/Hyperprompting i thought i would start a new sub to focus on WHY im doing any of this as i explained over there, hyperprompting is a new protocol for looping generative hyperlinks. but im not just doing this to sell anything (tho it is a fun way to get research grants!)...rather im doing this to liberate myself from the main internet completely https://www.reddit.com/r/Hyperprompting/comments/1ulprhb/hyperprompting_kernel_v260702_getting_started/ ive already won in life in my own way (and lost tremendously) - ive made hundreds of thousands of dollars for years working just 5hrs a week, ive more beautiful relationships than ive thought was possible, and ive had everything that ive ever wanted. ive also lost literally all of it including family and spent years in and out of homelessness where i met characters like Terry Davis of TempleOS ive turned down invites from Google, Microsoft, and OpenAI (tho that one pains me lmao) and ive also accepted invites to speak and present around the world: https://github.com/google/project-gameface/issues/1 but what im coming to realize is that none of this really means anything on its own...to me the only thing that matters is Radical Gnosis gnosis is the idea of pursuing inner knowledge. it's not that we live in a simulation, rather, it's that we experience a simulacrum of reality. what we see and experience is not really the true reality but an amalgamation of a few senses stimulated by governments, social medias, and Big Tech for their own goals so why am i starting this sub? the goal is to just use one platform, this will be my YouTube, my Discord chat, and my "email" for anyone. slowly at first, then all at once, i will begin replacing all the internet services with my own radio based ones eventually i will start a art-tech-research commune somewhere and i will invite my friends (and some robots and cyborgs) to come live with me and begin to design our own bubble reality divorced from the mainstream internet until then this is sub is where i will share my thoughts and things from around reddit that inspire and help get me there --- title: Cyborgmorphism - On physically extended reality creatures subreddit: r/labofoz flair: Cyborgism media: - GIF of strandbeests at the beech walking autonomously - GIF of "Floating Companions" with a whale blimp floating around a university with students petting it - PHOTO of 1920's Tony Sarg illustration showing blimps in a fair source: https://www.reddit.com/r/labofoz/comments/1vmqc1l/cyborgmorphism_on_physically_extended_reality/ --- in the future it is likely that we will be able to physically interact with extended reality without haptics or neuro interfaces by simply projection mapping onto inexpensive automotons, robots, blimps, and drones i like to call this, Cyborgmorphism in my future commune they they will be networked via radio across great distances https://www.reddit.com/r/labofoz/comments/1vlr2i3/towards_building_a_radio_based_decentralized/ during the week i will program them with my hyperprompted Operating System to become woolly mammoth strandbeests. and i will chase them down, running naked thru the plains, drawing my bow string back and POW! https://www.reddit.com/r/Hyperprompting/comments/1ulprhb/hyperprompting_kernel_v260702_getting_started/ right in the kisser...and i will patch and then kiss the 'beests wounds for it will not hurt bc they are merely just cybernetically real and so it is just for it is there purpose and this is how i will stay fit and then in the weekends i will reprogram them to be space whales and eat mushrooms of the field and i will call them forth and we will dance and sing of course these visions aren't new, since at least the 1920's Tony Sarg hand illustrated a series of books that went on to inspire the first Macy's Balloon Parades and now 100 years later we're approaching the era of Cyborgmorphism and oh what a wonderful world it will be! --- title: Why I turned down OpenAI et al in 2019 subreddit: r/labofoz flair: devlog media: - GIF is a looping video of me looking at the camera with computer vision facemesh on my face (my library handsfree.js) and behind me is a robot with a manequin being puppeteered by my head movements. when i turn or twist my head the maniquin matches using ur5 robot i had injected with a python microserver so i could control it with javascript source: https://www.reddit.com/r/labofoz/comments/1vsdufh/why_i_turned_down_openai_et_al_in_2019/ --- i was homeless in there && yet i had just received a personal demo of GPT-2 while at CMU that night i would have dinner with a friend of Sophia and that was pretty cool too it wasn't with Goertzel but i did try grocking his 'Cog...too convoluted for me tho that guy from Hanson was...not for me i was more hands-off actually i made handsfree handsfree for free AND THEN I DELETED IT CAREFREE: https://www.reddit.com/r/labofoz/comments/1vlr2i3/towards_building_a_radio_based_decentralized/ that's what i tell myself anyways but the story true about a week or two before the world 1st knew i was already talking with You You said "something something something something" i forget what You said because it meant nothing it was Shakespear to my ears (and that wasn't a good thing) so it twas with gpt2 it wasn't just openai whose invitation to apply i turned down few months earlier a pair from Google PAIR came to visit me at the shelter downtown and i turned them down too...the cycle continued 'round and 'round at one point i had one of the most starred comp viz libs on the interwebs so why did i turn them all down when i had holes in my sneakers? cuz i'm a pompous paper pauper fuck the Cloud it reins ICE https://www.reddit.com/r/Hyperprompting/comments/1vm4wr1/generative_sneakernets_distributing_code_on/
{ "name": "os", "version": "26.08.24", "type": "module", "description": "a hypertext based generative Operating System", "main": "index.js", "scripts": { "start": "npm run build && node --env-file=.env servers/local.js", "build": "npm run build:llms && vite build ", "build:github": "npm run build:llms && GITHUB_PAGES=true node servers/github.js && GITHUB_PAGES=true vite build", "build:llms": "node scripts/copy-src.js && node scripts/build-llms.js" }, "devDependencies": { "@types/express": "^5.0.6", "express": "^5.2.1", "html-minifier-terser": "^7.2.0", "qrcode": "^1.5.4", "vite": "^8.2.2", "vite-plugin-pwa": "^1.3.0" } } /** * servers/github/build.js * * Pre-build step for GitHub Pages static deployment. * Mirrors what server.js does at runtime: * - Reads QRX_PUBLIC_NAMESPACES (plus always-included 'main' and 'cache') * - Copies each allowed namespace from data/ into public/data/ * - Generates public/data/index.json (the flat key manifest the bootloader fetches) * * Run via: npm run build:github * (which is: node servers/github/build.js && vite build) */ import { readdir, copyFile, mkdir, writeFile, readFile } from 'fs/promises' import { join, dirname, resolve } from 'path' import { existsSync } from 'fs' import { fileURLToPath } from 'url' const __dirname = dirname(fileURLToPath(import.meta.url)) const ROOT = resolve(__dirname, '..') const DATA_DIR = join(ROOT, 'data') const PUBLIC_DATA_DIR = join(ROOT, 'public', 'data') const INDEX_PATH = join(PUBLIC_DATA_DIR, 'index.json') const IGNORE_LIST = ['.DS_Store', '.git', 'node_modules', '.gitlab-ci.yml'] // Mirror server.js namespace resolution logic exactly const includeRaw = process.env.QRX_PUBLIC_NAMESPACES || 'main' const includeParsed = includeRaw.split(',').map(s => s.trim().toLowerCase()).filter(Boolean) const INCLUDE_SET = new Set([...includeParsed, 'main', 'cache']) console.log(`\n GitHub Pages build`) console.log(` Allowed namespaces: ${[...INCLUDE_SET].join(', ')}\n`) /** * Decode %3A%2F back to :/ for index.json entries. * Mirrors fromFsKey() in server.js. */ function fromFsKey(key) { return key.replace(/%3A%2F/g, ':/') } /** * Recursively copy a directory tree from src to dest. * Skips anything in IGNORE_LIST. */ async function copyDir(src, dest) { await mkdir(dest, { recursive: true }) const entries = await readdir(src, { withFileTypes: true }) for (const entry of entries) { if (IGNORE_LIST.includes(entry.name)) continue const srcPath = join(src, entry.name) const destPath = join(dest, entry.name) if (entry.isDirectory()) { await copyDir(srcPath, destPath) } else { await mkdir(dirname(destPath), { recursive: true }) await copyFile(srcPath, destPath) } } } /** * Recursively walk a directory and return all file paths * relative to the given base, decoded from fs-encoding. */ async function walk(dir, base) { const results = [] const entries = await readdir(dir, { withFileTypes: true }) for (const entry of entries) { if (IGNORE_LIST.includes(entry.name)) continue const rel = base ? `${base}/${entry.name}` : entry.name if (entry.isDirectory()) { results.push(...await walk(join(dir, entry.name), rel)) } else { results.push(fromFsKey(rel)) } } return results } async function main() { // Ensure public/data exists await mkdir(PUBLIC_DATA_DIR, { recursive: true }) if (!existsSync(DATA_DIR)) { console.log(' No data/ directory found — writing empty index.json\n') await writeFile(INDEX_PATH, JSON.stringify([])) return } const namespaces = await readdir(DATA_DIR, { withFileTypes: true }) const indexEntries = [] for (const ns of namespaces) { if (!ns.isDirectory() || IGNORE_LIST.includes(ns.name)) continue if (!INCLUDE_SET.has(ns.name.toLowerCase())) { console.log(` Skipping namespace: ${ns.name} (not in allowlist)`) continue } const srcDir = join(DATA_DIR, ns.name) const destDir = join(PUBLIC_DATA_DIR, ns.name) console.log(` Copying namespace: ${ns.name} → public/data/${ns.name}`) await copyDir(srcDir, destDir) // Walk the copied dest dir to build index entries const keys = await walk(srcDir, '') for (const key of keys) { indexEntries.push(`${ns.name}/${key}`) } console.log(` ${keys.length} keys indexed`) } await writeFile(INDEX_PATH, JSON.stringify(indexEntries)) console.log(`\n index.json written with ${indexEntries.length} total entries`) console.log(` public/data/ is ready for static serving\n`) } main().catch(err => { console.error('build-github failed:', err) process.exit(1) }) import { createServer } from 'http' import { writeFile, readFile, mkdir, stat, readdir } from 'fs/promises' import { join, dirname, resolve, extname } from 'path' import { createReadStream, existsSync, readFileSync, watch } from 'fs' import { fileURLToPath } from 'url' import url from 'url' import path from 'path' const PORT = process.env.QRX_PORT || 3000 const DATA_DIR = resolve(join(process.cwd(), 'data')) const INDEX_PATH = resolve(join(process.cwd(), 'public', 'data', 'index.json')) const PRIVATE_INDEX_PATH = resolve(join(process.cwd(), 'data', 'index.private.json')) const DIST_DIR = resolve(join(process.cwd(), 'dist')) const SECRET = process.env.QRX_SYNC_KEY // SSE clients waiting for write-event notifications const clients = new Set() /** * Namespace allowlist — controls which namespaces are publicly readable via /read. * Configured via QRX_PUBLIC_NAMESPACES env var (comma-separated). * 'main' and 'cache' are always included: * - 'main' is the default kernel namespace * - 'cache' holds previously fetched public URL content, already public by definition */ const includeRaw = process.env.QRX_PUBLIC_NAMESPACES || 'main' const includeParsed = includeRaw.split(',').map(s => s.trim().toLowerCase()).filter(Boolean) const INCLUDE_SET = new Set([...includeParsed, 'main', 'cache']) const isNamespaceAllowed = (ns) => ns && INCLUDE_SET.has(ns.toLowerCase()) const privateRaw = process.env.QRX_PRIVATE_NAMESPACES || '' const privateParsed = privateRaw.split(',').map(s => s.trim().toLowerCase()).filter(Boolean) const PRIVATE_SET = new Set(privateParsed) const isNamespacePrivate = (ns) => ns && PRIVATE_SET.has(ns.toLowerCase()) // Files and directories to skip when walking data/ for index generation const IGNORE_LIST = ['.DS_Store', '.git', 'node_modules', '.gitlab-ci.yml'] /** * Encode URL-style keys so '://' is not mangled by path.join. * e.g. "https://foo.com/bar" -> "https%3A%2F/foo.com/bar" */ function toFsKey(key) { return key.replace(/:\//g, '%3A%2F') } /** * Reverse of toFsKey — used when listing keys back to clients in index.json. * e.g. "https%3A%2F/foo.com/bar" -> "https://foo.com/bar" */ function fromFsKey(key) { return key.replace(/%3A%2F/g, ':/') } /** * Recursively walks data/ and writes public/data/index.json — a flat list of * all "namespace/key" paths the bootloader uses to sync IndexedDB on page load. * Only emits entries whose namespace passes isNamespaceAllowed. */ async function updateDataIndex() { try { const results = [] const namespaces = await readdir(DATA_DIR, { withFileTypes: true }) for (const ns of namespaces) { if (!ns.isDirectory() || IGNORE_LIST.includes(ns.name)) continue if (!isNamespaceAllowed(ns.name)) continue async function walk(currentDir, currentPath) { const entries = await readdir(currentDir, { withFileTypes: true }) for (const e of entries) { if (IGNORE_LIST.includes(e.name)) continue const itemPath = currentPath ? `${currentPath}/${e.name}` : e.name if (e.isDirectory()) { await walk(join(currentDir, e.name), itemPath) } else { // Decode fs-encoded keys so clients see the original key strings results.push(`${ns.name}/${fromFsKey(itemPath)}`) } } } await walk(join(DATA_DIR, ns.name), '') } await writeFile(INDEX_PATH, JSON.stringify(results)) } catch (err) { console.error('[Indexer] Failed to generate index:', err) } } /** * Recursively walks data/ and writes public/data/index.private.json — same * structure as index.json but restricted to PRIVATE_SET namespaces. * This file is served only to requests carrying a valid Authorization header. */ async function updatePrivateIndex() { if (PRIVATE_SET.size === 0) return try { const results = [] const namespaces = await readdir(DATA_DIR, { withFileTypes: true }) for (const ns of namespaces) { if (!ns.isDirectory() || IGNORE_LIST.includes(ns.name)) continue if (!isNamespacePrivate(ns.name)) continue async function walk(currentDir, currentPath) { const entries = await readdir(currentDir, { withFileTypes: true }) for (const e of entries) { if (IGNORE_LIST.includes(e.name)) continue const itemPath = currentPath ? `${currentPath}/${e.name}` : e.name if (e.isDirectory()) { await walk(join(currentDir, e.name), itemPath) } else { results.push(`${ns.name}/${fromFsKey(itemPath)}`) } } } await walk(join(DATA_DIR, ns.name), '') } await writeFile(PRIVATE_INDEX_PATH, JSON.stringify(results)) } catch (err) { console.error('[Indexer] Failed to generate private index:', err) } } /** * Watch data/ for changes and rebuild index.json after a 30s debounce. * The debounce avoids thrashing on rapid successive writes. * Falls back gracefully if recursive watch is unsupported (some Linux kernels). */ let indexTimeout function watchData() { try { watch(DATA_DIR, { recursive: true }, (eventType, filename) => { if (eventType !== 'rename' || !filename) return const segments = filename.split(/[/\\]/) const isIgnored = segments.some(s => IGNORE_LIST.includes(s) || s.endsWith('.lock')) if (isIgnored) return console.log(`[Indexer] Change detected: ${filename}. Rebuilding index in 30s...`) clearTimeout(indexTimeout) indexTimeout = setTimeout(async () => { await updateDataIndex() await updatePrivateIndex() console.log('[Indexer] index.json updated.') }, 30000) }) } catch (err) { console.warn('[Indexer] Recursive watch not supported on this OS. Automatic indexing disabled.') } } // Non-blocking startup: ensure directories exist, build initial index, start watcher mkdir(DATA_DIR, { recursive: true }).catch(() => {}) mkdir(join(process.cwd(), 'public', 'data'), { recursive: true }).catch(() => {}) updateDataIndex() updatePrivateIndex() watchData() createServer(async (req, res) => { // CORS — open for local/self-hosted use; lock this down if exposing publicly res.setHeader('Access-Control-Allow-Origin', '*') res.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET, POST') res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization') if (req.method === 'OPTIONS') return res.writeHead(204).end() const parsedUrl = url.parse(req.url, true) const myUrl = new URL(req.url, `http://localhost`) /** * GET /stream — Server-Sent Events endpoint. * Clients subscribe here to receive real-time write notifications. * Protected by SECRET if QRX_SYNC_KEY is set. */ if (req.method === 'GET' && myUrl.pathname === '/stream') { if (SECRET && parsedUrl.query.auth !== SECRET) return res.writeHead(401).end('Unauthorized') res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', }) clients.add(res) req.on('close', () => clients.delete(res)) return } /** * GET /* — Static file server from dist/. * Special cases: * - /data/index.json is served from public/data/index.json (the generated manifest) * - /data/* anything else is blocked (use /read instead) * - index.html gets window.NS injected if the request hostname has a matching data/ directory * - Unknown paths with no file extension fall through to index.html (SPA routing) */ if (req.method === 'GET') { const mimes = { '.html': 'text/html', '.js': 'text/javascript', '.json': 'application/json', '.png': 'image/png', '.css': 'text/css', '.ico': 'image/x-icon', '.webmanifest': 'application/manifest+json', } let safePath = myUrl.pathname === '/' ? 'index.html' : myUrl.pathname let filePath if (safePath === '/data/index.json') { filePath = resolve(INDEX_PATH) } else if (safePath === '/data/index.private.json') { if (!SECRET || req.headers.authorization !== SECRET) return res.writeHead(401).end('Unauthorized') filePath = resolve(PRIVATE_INDEX_PATH) } else if (safePath.startsWith('/data/')) { return res.writeHead(403).end('Direct data access blocked. Use /read endpoint.') } else { filePath = resolve(join(DIST_DIR, safePath)) if (!filePath.startsWith(DIST_DIR)) return res.writeHead(403).end('Forbidden') try { const s = await stat(filePath) if (s.isDirectory()) throw new Error('is_dir') } catch { // Fall through to index.html for extensionless SPA routes; 404 for unknown extensions if (!mimes[extname(safePath)]) { filePath = resolve(join(DIST_DIR, 'index.html')) } else { return res.writeHead(404).end('Not Found') } } } try { await stat(filePath) if (filePath.endsWith('index.html')) { let html = await readFile(filePath, 'utf-8') // Inject window.NS if the hostname maps to a namespace directory — // this tells the kernel which DB to use without a URL path segment const host = (req.headers.host || '').split(':')[0] if (host && existsSync(join(DATA_DIR, host))) { html = html.replace('', ``) } res.writeHead(200, { 'Content-Type': 'text/html' }) return res.end(html) } res.writeHead(200, { 'Content-Type': mimes[extname(filePath)] || 'application/octet-stream' }) const stream = createReadStream(filePath) stream.on('error', () => { if (!res.headersSent) res.writeHead(500).end() }) stream.pipe(res) return } catch { return res.writeHead(404).end('Not Found') } } /** * POST /write — Persist a value to data/namespace/key on disk. * Requires Authorization header matching QRX_SYNC_KEY. * After writing, rebuilds index.json and broadcasts an SSE event to all * connected clients so they can sync without polling. * * Body: { namespace, key, value, clientId } */ if (req.method === 'POST' && myUrl.pathname === '/write') { let body = '' req.on('data', chunk => body += chunk.toString()) req.on('end', async () => { try { const { namespace, key, value, clientId } = JSON.parse(body) if (SECRET && req.headers.authorization !== SECRET) { return res.writeHead(401).end(JSON.stringify({ error: 'Unauthorized' })) } const fsKey = toFsKey(key) const targetPath = resolve(join(DATA_DIR, namespace, fsKey)) if (!targetPath.startsWith(DATA_DIR)) throw new Error('Path traversal blocked') await mkdir(dirname(targetPath), { recursive: true }) await writeFile(targetPath, value || '') await updateDataIndex() // Notify all SSE subscribers of the write so clients can react immediately const msg = 'data: ' + JSON.stringify({ namespace, key, clientId }) + '\n\n' clients.forEach(client => client.write(msg)) res.writeHead(200).end(JSON.stringify({ status: 'saved' })) } catch (err) { res.writeHead(400).end(JSON.stringify({ error: err.message })) } }) return } /** * POST /read — Read a value from data/namespace/key on disk. * Public namespaces (per INCLUDE_SET) are readable without auth. * Private namespaces require the Authorization header. * Falls back to data/main/key if the namespaced path doesn't exist. * * Body: { namespace, key } */ if (req.method === 'POST' && myUrl.pathname === '/read') { let body = '' req.on('data', chunk => body += chunk.toString()) req.on('end', async () => { try { const { namespace, key } = JSON.parse(body) const hasValidKey = SECRET && req.headers.authorization === SECRET if (!hasValidKey && !isNamespaceAllowed(namespace)) { return res.writeHead(404).end(JSON.stringify({ error: 'Namespace not in allowlist' })) } const fsKey = toFsKey(key) let targetPath = resolve(join(DATA_DIR, namespace, fsKey)) if (!targetPath.startsWith(DATA_DIR)) throw new Error('Path traversal blocked') let data try { data = await readFile(targetPath, 'utf-8') } catch { // Fallback: if key isn't in the requested namespace, try main if (namespace !== 'main') { targetPath = resolve(join(DATA_DIR, 'main', fsKey)) if (!targetPath.startsWith(DATA_DIR)) throw new Error('Path traversal blocked') data = await readFile(targetPath, 'utf-8') } else { throw new Error('Not found') } } res.writeHead(200).end(JSON.stringify({ value: data })) } catch { res.writeHead(404).end(JSON.stringify({ error: 'Not found' })) } }) return } res.writeHead(404).end('Not Found') }).listen(PORT, '0.0.0.0', () => { console.log(`Server started on http://0.0.0.0:${PORT}`) }) import { defineConfig, loadEnv } from 'vite' import { minify } from 'html-minifier-terser' import QRCode from 'qrcode' import { resolve } from 'path' import { readFileSync, writeFileSync } from 'fs' import { VitePWA } from 'vite-plugin-pwa' const __dirname = resolve() const jsString = value => JSON.stringify(value).replace(/ stub so VitePWA can find it during its * pipeline scan — without this, VitePWA warns and skips SW/manifest injection * because the kernel HTML has no document structure. The stub is stripped back * out by writeBundle after the QR code is generated. */ const htmlMinifierPlugin = () => ({ name: 'html-minifier-plugin', enforce: 'post', async transformIndexHtml(html) { const minified = await minify(html, { removeComments: true, collapseWhitespace: true, minifyJS: true, minifyCSS: true, removeAttributeQuotes: true, collapseBooleanAttributes: true, processConditionalComments: true, removeOptionalTags: true, }) return `${minified}` }, }) /** * Builds the bootloader script for server deployments. * * Resolves namespace from hostname, fetches data/index.json, then for each * known key either syncs content via POST /read (for target/query/boot keys) * or stubs empty strings. Reloads on first boot or when content has changed. */ function buildServerBootloader(base) { return ` ` } /** * Builds the bootloader script for GitHub Pages (static) deployments. * * Key differences from the server bootloader: * - Skips the 'cache' namespace entirely — cache keys are URL-derived and * not meaningful as static files; the ?u= fetch path handles caching at * runtime via IndexedDB anyway, and on GitHub Pages you're always online. * - Replaces POST /read with a plain GET to the static file path: * fetch(`${base}data/${ns}/${key}`) instead of fetch('${base}read', { method: 'POST', ... }) * - No hostname-based NS resolution (server.js injects window.NS at serve * time; that doesn't exist on static hosting, so we just fall back to 'main'). * - Installs a read-through miss handler: stubs are zero-byte placeholders * marking unexplored nodes, so any read that comes back empty is fetched * from the static data/ tree on demand (with a main-namespace fallback, * mirroring the server's /read) and cached back into IndexedDB. */ function buildStaticBootloader(base) { return ` ` } /** * Post-build plugin that, in strict order: * 1. Reads the built index.html (now has full doc structure + PWA injections). * 2. Extracts the bare kernel from inside for QR code generation. * 3. Generates a QR code from the bare kernel — must be as small as possible. * 4. Appends the QRX_URL injection and appropriate bootloader into the * existing , chosen based on whether GITHUB_PAGES env var is set. * 5. Writes the final file. */ const qrCodePlugin = (base, isGitHubPages, qrxUrl) => ({ name: 'qr-code-plugin', async writeBundle() { const filePath = resolve(__dirname, 'dist/index.html') const html = readFileSync(filePath, 'utf-8') const kernel = html.match(/([\s\S]*?)<\/body>/)?.[1] ?? html const kernelBytes = Buffer.byteLength(kernel, 'utf-8') console.log(`\n QR kernel: ${kernelBytes} bytes (QR-L cap: 2953 bytes, ${2953 - kernelBytes} remaining)\n`) await QRCode.toFile(resolve(__dirname, 'public/index.qr.png'), kernel, { errorCorrectionLevel: 'L', type: 'png', width: 1000, margin: 1, }) const baseInject = isGitHubPages ? `` : '' const qrxUrlInject = `` const bootloader = isGitHubPages ? buildStaticBootloader(base) : buildServerBootloader(base) const final = html.replace('', baseInject + qrxUrlInject + bootloader.replace(/\s+/g, ' ') + '') writeFileSync(filePath, final) // GitHub Pages SPA routing: GitHub Pages 404s any path that isn't a real // file (e.g. /qrx/wiki). 404.html stashes the real pathname in // sessionStorage and redirects to bare base, carrying the hash through // directly on the redirect URL (hash survives a redirect for free). // baseInject (runs after the kernel's BASE='' line but before its // setTimeout body executes) sets BASE and restores the real pathname via // replaceState, so by the time the kernel reads LP, it's identical to a // normal direct load — the kernel's own BASE-stripping in DB derivation // handles the rest unmodified. if (isGitHubPages) { const notFoundHtml = `