From 4fd638a5a55978eb475a83e7bb0ae200627ee176 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 02:59:19 +0000 Subject: [PATCH 01/12] Add OPFS + Pyodide cross-device test harness Single self-contained HTML file (no build step) for manually testing the Origin Private File System across desktop and mobile browsers. - Runs Pyodide in a dedicated Web Worker created from an inline Blob URL, so createSyncAccessHandle() (the only OPFS write API that works on iOS Safari) is available. - Capability panel: userAgent, isSecureContext, crossOriginIsolated, SharedArrayBuffer, OPFS presence, createSyncAccessHandle feature-detect (in worker), and storage.estimate() usage/quota. - Tools: save file (raw OPFS sync access handle), recursive list, load, run SQL via stdlib sqlite3 over mountNativeFS("/mnt") with syncfs() persistence, and clear OPFS. - Insecure-context banner, responsive tap-friendly layout, and a verbose activity log that surfaces full errors (name/message/stack) per op. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01CBcWE5Hk6s9a9NqZ1oS6Vt --- opfs-pyodide.html | 754 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 754 insertions(+) create mode 100644 opfs-pyodide.html diff --git a/opfs-pyodide.html b/opfs-pyodide.html new file mode 100644 index 0000000..ec9d2b1 --- /dev/null +++ b/opfs-pyodide.html @@ -0,0 +1,754 @@ + + + + + +OPFS + Pyodide test harness + + + + +
+

OPFS + Pyodide test harness

+

Manual cross-device testing for the Origin Private File System. Run it over HTTPS or http://localhost.

+
+ + + + +
+ +
+

Capabilities Pyodide: loading…

+ + +
Detecting…
+ +
+ + +
+

1. Save a test file

+

Writes via the raw OPFS sync access handle in the worker.

+ + + + + +
+ + +
+

2. List files

+

Recursively lists the OPFS root, re-read live on every click.

+ +
+
+ + +
+

3. Load a file

+ + + + +
+ + +
+

4. Run SQL

+

+ Runs against /mnt/test.db via Pyodide's stdlib sqlite3, + with the OPFS root mounted through mountNativeFS. Writes are persisted with + syncfs() so they survive a reload. +

+ + + +
+
+ + +
+

5. Clear OPFS

+

Deletes every entry in the OPFS root for a clean slate.

+ +
+ + +
+

Activity log

+
+ + Every operation, its duration, and the full result or error. +
+
+
+
+ + + + + + + + From a3fd0fa2e33d2e9bc76cf32bb8c3968735e383fc Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 03:07:06 +0000 Subject: [PATCH 02/12] Fix Pyodide load failure: load unvendored sqlite3 package `import sqlite3` failed at worker init with ModuleNotFoundError because sqlite3 is unvendored in Pyodide and ships as a separate package. Load it with `await pyodide.loadPackage("sqlite3")` before importing. Verified end-to-end with Rodney/headless Chrome: Pyodide loads, save/list/ load work, SQL create+insert+select runs, and after a full page reload both the saved file and the SQLite rows persist (syncfs). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01CBcWE5Hk6s9a9NqZ1oS6Vt --- opfs-pyodide.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/opfs-pyodide.html b/opfs-pyodide.html index ec9d2b1..7efd2c9 100644 --- a/opfs-pyodide.html +++ b/opfs-pyodide.html @@ -260,6 +260,10 @@

Activity log

const pyodideReady = (async () => { importScripts(PYODIDE_INDEX_URL + "pyodide.js"); pyodide = await loadPyodide({ indexURL: PYODIDE_INDEX_URL }); + // sqlite3 is UNVENDORED in Pyodide: the stdlib module is shipped as a + // separate package that must be loaded explicitly before `import sqlite3`, + // otherwise it raises ModuleNotFoundError. + await pyodide.loadPackage("sqlite3"); // Define the SQL helper once. It returns a JSON string so the result crosses // the worker boundary cleanly (default=str copes with bytes / odd types). pyodide.runPython(` From d69e93fda993d47fb9b856a65ee540777d9b86de Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 03:10:55 +0000 Subject: [PATCH 03/12] Flash an inline "Saved" confirmation on the save button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clicking Save only wrote to the activity log, with no at-a-glance feedback. Add a transient inline message next to the button (✓ Saved N bytes, or a red failure note) that fades after a couple of seconds. The activity log remains the full record. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01CBcWE5Hk6s9a9NqZ1oS6Vt --- opfs-pyodide.html | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/opfs-pyodide.html b/opfs-pyodide.html index 7efd2c9..c17b3a9 100644 --- a/opfs-pyodide.html +++ b/opfs-pyodide.html @@ -148,6 +148,18 @@ } .status-pill { font-size: 0.75rem; padding: 0.15rem 0.5rem; border-radius: 999px; background: #6c757d; color: #fff; margin-left: 0.5rem; } .status-pill.ready { background: #198754; } + /* Transient inline confirmation shown next to a button after an action */ + .flash { + display: inline-block; + margin-left: 0.5rem; + font-size: 0.9rem; + font-weight: 700; + opacity: 0; + transition: opacity 0.15s ease-in; + } + .flash.show { opacity: 1; } + .flash.ok { color: #198754; } + .flash.err { color: #dc3545; } .row { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; } @@ -183,6 +195,7 @@

1. Save a test file

+ @@ -558,6 +571,15 @@

Activity log

logEl.textContent = ""; }); +// Briefly show an inline message next to a control (immediate, visible feedback +// — the activity log is the full record, this is the at-a-glance confirmation). +function flash(el, message, ok) { + el.textContent = message; + el.className = "flash show " + (ok ? "ok" : "err"); + clearTimeout(el._flashTimer); + el._flashTimer = setTimeout(() => { el.classList.remove("show"); }, 2500); +} + // ---- Pyodide status pill ------------------------------------------------- const pyStatus = document.getElementById("py-status"); function handleStatus(data) { @@ -646,10 +668,16 @@

Activity log

document.getElementById("btn-refresh-caps").addEventListener("click", detectCapabilities); -document.getElementById("btn-save").addEventListener("click", () => { +document.getElementById("btn-save").addEventListener("click", async () => { const name = document.getElementById("save-name").value.trim(); const content = document.getElementById("save-content").value; - runOp("Save file: " + name, "saveFile", { name, content }); + const flashEl = document.getElementById("save-flash"); + const result = await runOp("Save file: " + name, "saveFile", { name, content }); + if (result) { + flash(flashEl, `✓ Saved ${result.bytesWritten} bytes`, true); + } else { + flash(flashEl, "✗ Save failed — see log", false); + } }); document.getElementById("btn-list").addEventListener("click", () => { From 11250f73cbb4188e348485f6c5d8f9179534c10a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 03:13:15 +0000 Subject: [PATCH 04/12] Add quick-query preset buttons to the SQL tool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Common operations (create table, insert, select all, count, list tables, drop table) are now one-tap buttons that fill the textarea and run, making the SQL tool much faster to exercise — especially on mobile. The SQL run logic is refactored into runSqlFromTextarea() shared by the presets and the Run SQL button. Presets are a simple editable array near the bottom of the script. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01CBcWE5Hk6s9a9NqZ1oS6Vt --- opfs-pyodide.html | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/opfs-pyodide.html b/opfs-pyodide.html index c17b3a9..3da3230 100644 --- a/opfs-pyodide.html +++ b/opfs-pyodide.html @@ -91,6 +91,8 @@ } button:active { filter: brightness(0.9); } button.danger { background: #c53030; } + /* Smaller, secondary-styled buttons for the SQL quick-query presets */ + button.preset { background: #4a5568; min-height: 2.25rem; padding: 0.4rem 0.8rem; margin: 0.25rem 0 0; font-size: 0.85rem; font-weight: 600; } button:disabled { opacity: 0.5; cursor: not-allowed; } .banner { padding: 0.85rem 1rem; @@ -223,6 +225,8 @@

4. Run SQL

with the OPFS root mounted through mountNativeFS. Writes are persisted with syncfs() so they survive a reload.

+ +