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.
+
+
+
+
+ ⚠️ This page is not a secure context, so OPFS will not work.
+ Serve it over https:// or http://localhost
+ (e.g. python3 -m http.server) and reopen.
+
+
+
+
+
+
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.