From 6203459ba8ff436cff0071acc610b3bc962032d0 Mon Sep 17 00:00:00 2001 From: ijbo Date: Mon, 22 Jun 2026 17:02:31 +0900 Subject: [PATCH 1/3] feat: open single file from disk with linked autosave MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an "Open File" button to the workspace sidebar that opens one file via showOpenFilePicker and keeps it linked, so edits autosave back to that exact file on disk independently of folder-workspace mode. New single-file handle API in disk-workspace.js (openSingleFile, linkSingleFile, writeSingleFile, unlinkSingleFile, unlinkAllSingleFiles, restoreSingleFiles) with IndexedDB-persisted handles that survive reloads, plus M.wsOpenDiskFile() in workspace.js. Fixes found via multi-agent review: - critical: edits never reached disk β€” per-keystroke autosave (cloud-share.js saveToLocalStorage) only wrote in folder mode; added single-file write-back branch + pagehide/beforeunload flush - major: request readwrite permission in-gesture on open; per-id write serialization queue; guard delete/rename so single-linked files never trigger folder-mode removeEntry/renameFileInPath; don't truncate the last file; unlink single-file handles on folder connect/reconnect instead of orphaning them - minor: no redundant write on open, orphan-handle pruning, persistence-failure warning, preserve .txt/.log extension on rename, no auto cloud copy for local files Verified live + 30 passing disk-workspace.spec.js tests, no regressions. Co-Authored-By: Claude Opus 4.8 --- README.md | 3 +- changelogs/CHANGELOG-open-file-from-disk.md | 48 +++++ index.html | 4 + js/cloud-share.js | 41 ++++ js/disk-workspace.js | 199 ++++++++++++++++++++ js/workspace.js | 135 ++++++++++++- 6 files changed, 423 insertions(+), 7 deletions(-) create mode 100644 changelogs/CHANGELOG-open-file-from-disk.md diff --git a/README.md b/README.md index 1e9507a..a23473f 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ | **❓ Help Mode** | Interactive learning mode β€” click ❓ Help to highlight all buttons, click any button for description + keyboard shortcut + animated demo video; 50% screen demo panel with fullscreen expand; 16 dedicated demo videos mapped to every toolbar button | | **🧠 Context Memory** | `{{@Memory:}}` tag for workspace intelligence β€” **hybrid search**: SQLite FTS5 keyword search (40%) + EmbeddingGemma 300M semantic cosine similarity (60%) with heading-aware chunking (~1500 chars/chunk); πŸ’Ž Semantic toggle auto-downloads embedding model (~150MB WASM) on first file attach; three storage modes: browser-only (IndexedDB), disk workspace (`.textagent/memory.db`), external folders (IndexedDB); **file conversion**: binary formats (DOCX, XLSX, XLS, Numbers, PDF) auto-converted to markdown via Mammoth.js/SheetJS/PDF.js before indexing; `@use: workspace, my-docs` in AI/Think/Agent tags for multi-source context retrieval; Memory Selector dropdown on AI/Think/Agent cards; amber-accented Memory card with Folder/Files/Rebuild buttons + stats; auto-discovery of workspace files; `Use: none` opt-out; reuses existing sql.js WASM (zero bundle increase) | | **βœ‰οΈ Email to Self** | Send documents directly to your inbox from the share modal β€” email address input with `.md` file attached + share link; powered by Google Apps Script (free, 100 emails/day); Cloudflare Turnstile CAPTCHA verification; dual rate limiting (100/day global + 7/day per recipient); loading state + success/error feedback; email persisted in localStorage; zero third-party dependencies | -| **πŸ’Ύ Disk Workspace** | Folder-backed storage via File System Access API β€” "Open Folder" in sidebar header; `.md` files read/written directly to disk; `.textagent/workspace.json` manifest; debounced autosave ("πŸ’Ύ Saved to disk" indicator); refresh from disk for external edits; disconnect to revert to localStorage; auto-reconnect on reload via IndexedDB handles; unified action modal for rename/duplicate/delete with confirmation; Chromium-only (hidden in unsupported browsers) | +| **πŸ’Ύ Disk Workspace** | Folder-backed storage via File System Access API β€” "Open Folder" in sidebar header; `.md` files read/written directly to disk; `.textagent/workspace.json` manifest; debounced autosave ("πŸ’Ύ Saved to disk" indicator); refresh from disk for external edits; disconnect to revert to localStorage; auto-reconnect on reload via IndexedDB handles; unified action modal for rename/duplicate/delete with confirmation; **"Open File"** β€” open a single file from disk (`showOpenFilePicker`) and keep it linked so edits autosave back to that exact file independently of folder mode, with per-file write serialization, in-gesture read/write permission, IndexedDB-persisted handles across reloads, and folder-mode coexistence guards; Chromium-only (hidden in unsupported browsers) | | **πŸ“ˆ Finance Dashboard** | Stock/crypto/index dashboard templates with live TradingView charts; dynamic grid via `data-var-prefix` (add/remove tickers in `@variables` table, grid auto-adjusts); configurable chart range (`1M`, `12M`, `36M`), interval (`D`, `W`, `M`), EMA period (default 52), and card size via `data-height`; single cards auto-expand to full width; interactive 1M/1Y/3Y range + 52D/52W/52M EMA toggle buttons; `@variables` table persists after ⚑ Vars for re-editing; JS code block generates grid HTML from variables | | **Extras** | Auto-save (localStorage + cloud), table of contents, image paste, 137+ templates (16 categories: AI, Agents, API Explorer, Coding, Creative, Documentation, Finance, Games, Maths, PPT, Project, Quiz, Science, Skills, Tables, Technical), AI Model Manager template (local model reference with sizes, privacy, and capabilities), template variable substitution (`$(varName)` with auto-detect), table spreadsheet tools (sort, filter, stats, chart, add row/col, inline cell edit, CSV/MD export), content statistics, modular codebase (13+ JS modules), fully responsive mobile UI with scrollable Quick Action Bar (Files, Search, TOC, Share, Copy, Tools, AI, Model, Upload, Help) and formatting toolbar, multi-file workspace sidebar, compact header mode with collapsible Tools dropdown (Presentation, Zen, Word Wrap, Focus, Voice, Dark Mode, Preview Theme), Clear All / Clear Selection buttons (undoable via Ctrl+Z), auto-naming (Untitled files derive name from first 10 content characters) | | **Dev Tooling** | ESLint + Prettier (lint, format:check), Playwright test suite β€” 592 tests across smoke, feature, integration, dev, regression, performance, quality, and security categories (import, export, share, view-mode, editor, email-to-self, secure share, startup timing, export integrity, persistence, module loading, disk workspace, context memory, exec engine, exec-jsx, build validation, load-time, accessibility, video player, TTS, STT, file converters, stock widget, embed grid, model registry, model tag, game tag, draw docgen, readonly mode, excalidraw library, help mode, page view, table tools, API tag, Linux tag, template loading, inline rename, presentation, static analysis, code smell, XSS hardening, Florence-2 model, Docling model, GLM-OCR model, TTS download), Firestore rules validation (21 tests), automated security scanner (13 checks, 3 severity tiers), pre-commit changelog + security enforcement, GitHub Actions CI | @@ -549,6 +549,7 @@ TextAgent has undergone significant evolution since its inception. What started | Date | Commits | Feature / Update | |------|---------|-----------------:| +| **2026-06-22** | β€” | πŸ“„ **Open File From Disk** β€” new "Open File" button (`ws-open-file`) in the workspace sidebar opens a single file via `showOpenFilePicker` and keeps it **linked** so edits autosave back to that exact file, independently of folder mode; single-file handle API in `disk-workspace.js` (`openSingleFile`/`linkSingleFile`/`writeSingleFile`/`unlinkSingleFile`/`unlinkAllSingleFiles`/`restoreSingleFiles`) with IndexedDB-persisted handles (`file:`) surviving reloads; `M.wsOpenDiskFile()` creates a deduped, manually-named workspace entry. **Fixed (critical):** edits never reached disk β€” the per-keystroke autosave (`cloud-share.js` β†’ `saveToLocalStorage`) had only a folder-mode disk branch and never called `writeSingleFile`; added a single-file write-back branch (before the folder branch) + `pagehide`/`beforeunload` flush. **Fixed (major):** in-gesture `readwrite` permission request on open; per-id write serialization queue (prevents out-of-order corruption); delete/rename guards so single-linked files never trigger folder-mode `removeEntry`/`renameFileInPath` (could destroy a same-named file in a connected folder); deleting the last file no longer truncates the linked file; folder connect/reconnect unlinks single-file handles instead of orphaning them. **Fixed (minor):** no redundant identical-content write on open, orphaned-handle pruning, persistence-failure warning, `.txt`/`.log` extension preserved on rename, no auto-published cloud copy for local files. Verified live + 30 passing `disk-workspace.spec.js` tests, no regressions | | **2026-04-15** | β€” | πŸŽ™οΈ **Podcast Generation System** β€” new `{{@Podcast:}}` document tag for AI-powered multi-speaker podcast creation; 3-phase pipeline (web research via Jina API β†’ AI script generation with `[Speaker]` markers β†’ Kokoro TTS multi-speaker audio synthesis); configurable styles (debate, interview, chat, lecture, storytelling); `parseScript()` speaker segmentation; `createWavBlob()` Float32Arrayβ†’WAV encoder; real-time progress UI with phase indicators; WAV audio download; **Podcast Marketplace** with 15+ curated templates across 5 categories (Tech, Science, Business, Creative, Education); search/filter, template cards with metadata; `podcast-docgen.js` (~1046 lines) + `podcast-marketplace.js` (~923 lines) + `css/podcast-docgen.css` + `css/podcast-marketplace.css` + `js/templates/podcasts.js` | | **2026-04-15** | β€” | πŸ”§ **TTS Worker Multi-Speaker Fix** β€” fixed critical bug where Web Worker silently dropped `speak-multi` messages after async `init` handler completed; root cause: service worker (`sw.js`) used cache-first strategy for `.js` files, serving stale `tts-worker.js` indefinitely; fix: (1) extracted `processMultiSegments()` as standalone async function, (2) bundled segments with `init` message via `pendingSegments` field for same-handler-execution processing, (3) added cache-busting `?v=` param to worker URL, (4) excluded worker files from service worker caching, (5) bumped `CACHE_NAME` v2β†’v3, (6) added `worker.onerror` handler, (7) per-chunk 90s timeout, event loop yields, voice pre-fetch phase, heartbeat logger, version stamping | | **2026-04-08** | β€” | ⭐ **Star on GitHub Button** β€” new gold/amber gradient pill button in header next to Issues button linking to the GitHub repo for starring; `.star-github-pill` CSS class with dark mode variant; fixed Issues button inline styles that prevented proper `.help-mode-pill` rendering | diff --git a/changelogs/CHANGELOG-open-file-from-disk.md b/changelogs/CHANGELOG-open-file-from-disk.md new file mode 100644 index 0000000..3ccacdd --- /dev/null +++ b/changelogs/CHANGELOG-open-file-from-disk.md @@ -0,0 +1,48 @@ +# Open File From Disk β€” Single-File Disk Linking + +- Added **"Open File"** button (πŸ“„β¬†, `ws-open-file`) to the workspace sidebar header, next to "Open Folder" β€” opens a single file from disk via `showOpenFilePicker` and keeps it **linked** so edits autosave back to that exact file, independently of folder/disk-workspace mode +- New single-file handle API in `disk-workspace.js`: `openSingleFile`, `linkSingleFile`, `hasSingleFile`, `writeSingleFile`, `unlinkSingleFile`, `unlinkAllSingleFiles`, `restoreSingleFiles`; handles persisted to IndexedDB (`file:` keys) so links survive page reloads +- New `M.wsOpenDiskFile()` in `workspace.js` β€” reads the picked file into a new workspace entry, links its handle, dedupes the display name, and marks it manually-named so auto-naming can't rename the linked file +- Button auto-hides in browsers without the File System Access API (same constraint as "Open Folder"); Chromium-only +- **Fixed (critical):** edits to a linked file never reached disk β€” the per-keystroke autosave path (`cloud-share.js` β†’ `saveToLocalStorage`) only had a folder-mode disk branch and never called `writeSingleFile`. Added a single-file write-back branch (checked before the folder branch) plus a `pagehide`/`beforeunload` flush so the last keystrokes before reload/close still land on disk +- **Fixed (major):** picked handle was read-only β€” `openSingleFile` now requests `readwrite` permission within the original click gesture, so autosave writes don't prompt out-of-context or silently fail +- **Fixed (major):** `writeSingleFile` had no serialization β€” added a per-id write queue so rapid edits/switches can't land out of order and corrupt the file +- **Fixed (major):** deleting a linked file no longer routes through the folder delete (could `removeEntry` a same-named file inside a connected folder); renaming a linked file no longer writes/deletes inside the connected folder; deleting the *last* file no longer truncates the linked disk file to empty +- **Fixed (major):** connecting/reconnecting a folder now unlinks single-file handles instead of orphaning them in IndexedDB +- **Fixed (minor):** open no longer performs a redundant identical-content write (mtime bump); orphaned IndexedDB handles are pruned on load; persistence failure surfaces a warning; renaming a linked non-`.md` file (`.txt`/`.log`) preserves its extension; opening a local disk file no longer auto-publishes a new encrypted cloud copy + +--- + +## Summary + +Added an "Open File" action so users can open a single file from disk and have their edits autosave straight back to it β€” without connecting an entire folder. The single-file handle is held in memory and persisted to IndexedDB, so the link survives reloads. The feature also coexists safely with the existing folder-backed workspace: a deep multi-agent review surfaced one critical defect (edits never actually reached disk because autosave runs through a different code path than the one originally hooked) and a cluster of data-integrity bugs where the single-file path collided with folder mode and could destroy unrelated real files. All are fixed and verified. + +--- + +## 1. Single-File Open Button & UI +**Files:** `index.html`, `js/disk-workspace.js` +**What:** Added the `ws-open-file` button to the sidebar header. `disk.updateUI()` shows it whenever `showOpenFilePicker` is supported (independent of folder connection); `disk.wireUI()` binds it to `M.wsOpenDiskFile`. +**Impact:** Users get a discoverable, folder-free way to open and edit a single file from disk. + +## 2. Single-File Handle API +**Files:** `js/disk-workspace.js` +**What:** `openSingleFile()` opens via `showOpenFilePicker` and requests `readwrite` permission in-gesture; `linkSingleFile()`/`unlinkSingleFile()`/`unlinkAllSingleFiles()` manage the `id β†’ FileSystemFileHandle` map and its IndexedDB persistence; `writeSingleFile()` writes back through a per-id serialized queue with lazy permission re-request; `restoreSingleFiles(validIds)` repopulates handles on load and prunes orphans. +**Impact:** Edits to a linked file write back to the exact on-disk file, in order, across reloads β€” and stale handles don't accumulate. + +## 3. Open Flow & Workspace Integration +**Files:** `js/workspace.js` +**What:** `M.wsOpenDiskFile()` saves the current doc, creates a deduped workspace entry, links the handle, and caches content to localStorage directly (no redundant disk echo). `setFileContent`/`removeFileContent` route through the single-file path; `performDelete`, `wsDeleteFile`, `wsRenameFile`, and the inline rename are all guarded so single-linked files never trigger folder-mode I/O. `wsConnectFolder`/`wsReconnectFolder` unlink all single files before rebuilding the workspace. +**Impact:** The single-file feature coexists with folder mode without deleting, truncating, or misrouting real files. + +## 4. Critical Fix: Edits Now Autosave to Disk +**Files:** `js/cloud-share.js` +**What:** The real per-keystroke autosave (`input` β†’ `debouncedAutosave` β†’ `saveToLocalStorage`) only wrote to disk in folder mode. Added a single-file branch (checked before the folder branch) calling `writeSingleFile`, with a clear "⚠️ Not saved to disk" indicator on failure, plus a `pagehide`/`beforeunload` flush of any pending debounced save. Also suppressed first-time encrypted cloud publication for single-linked files. +**Impact:** Typing into an opened file now actually persists to that file on disk β€” the feature's core promise. + +--- + +## Testing + +- Verified live (browser preview): keystroke autosave writes to the linked file; write serialization preserves order under slow I/O; open performs zero redundant writes; delete/rename guards behave; permission requested on open. +- `disk-workspace.spec.js` Playwright suite: 30 passing, no regressions (the single pre-existing modal-click flake is unrelated and fails identically on baseline). +- ESLint: no new errors/warnings on changed files. diff --git a/index.html b/index.html index 6f4ec45..f7a711b 100644 --- a/index.html +++ b/index.html @@ -996,6 +996,10 @@
Menu
title="Disconnect folder" style="display:none"> +