Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix={{branch}}-
type=sha,prefix=sha-
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push Docker image
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ dist
tests/.tmp
test-results
playwright-report
.claude
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, 138+ templates (17 categories: AI, Agents, API Explorer, Coding, Creative, Documentation, Finance, Games, Maths, PPT, Project, Quiz, Science, Skills, Tables, Technical, Tools), 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 |
Expand Down Expand Up @@ -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:<id>`) 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-05-14** | — | 🖥️ **Calculator — Live Expression Display + Paste-Into-Top** — calculator's small top expression line now shows the **full expression as it is typed** (every digit, operator and bracket appears immediately, e.g. `(2+3)^4*4`), while the big bottom line shows a **live partial result** evaluated on every keystroke (falls back to the current operand when the partial isn't yet valid, e.g. half-open brackets / trailing operator); fixed a paste bug on the small expression line — pasting `(2+3)^4*4` used to concatenate onto the rendered `"0"` (producing `"0(2+3)^4*4"` → `Error`); the paste handler now treats clipboard text as a complete replacement and routes straight to `applyPasted` (same code path as paste-into-result), so a single paste evaluates instantly with no follow-up Enter/blur; `commitExprEdit` skips re-evaluating when the line already matches the latest history entry, eliminating duplicate "edit" rows after a paste; `js/templates/tools.js` only — `show()` simplified (`pending` and "typing operand" branches merged) and `exprLine.paste` handler rewritten |
| **2026-05-14** | — | 🧮 **Tools Category + iOS-Style BODMAS Calculator** — new **Tools** template category (`bi-tools` icon) with a fully working calculator as the first template; iOS-style two-line display (small dim expression line above, large result line below); both lines `contenteditable` — click the expression to edit it and re-evaluate live; full **BODMAS** support with visible buttons `(`, `)`, `xʸ`, `÷`, `×`, `−`, `+`, plus `AC`, `±`, `%`, `⌫`, `.`, `=`; live running total updates the result line while typing; **History side panel** lists every calculation as `expr = result` with both cells editable — edits re-evaluate and push the latest result back to the main display, `↩` button sends any row's result to the display, `Clear` empties history; paste support for numbers and full expressions (`12*7+3`, `(2+3)^2*4`, `1,234.5`); unicode operator normalization (`×`, `÷`, `−`, commas); strict allowlist `safeEval` (`^[-+*/().\d^]+$`) with `^` translated to JS `**` for right-associative exponent precedence — no `eval()`, no identifiers, `alert(1)` rejected as `Error`; operator-aware backspace (cancels pending operator or removes one char); keyboard support for digits, `.`, `+ - * / ^ ( )`, `Backspace`, `Enter`/`=`, `Escape`; renderer round-trip safe — embedded `escapeHtml` builds entity strings via `String.fromCharCode(38)` so they survive `<pre><code>.textContent` extraction; `js/templates/tools.js` (~475 lines, single `html-autorun` block) + `tools` category pill in `modal-templates.js` + icon/color mapping in `templates.js` + dynamic import in `src/main.js`; Playwright assertion for Tools pill + Calculator card |
| **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` |
Expand Down
11 changes: 11 additions & 0 deletions changelogs/CHANGELOG-docker-tag-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Fix invalid Docker image tag on PR builds

- Fixed the `build-and-push` GitHub Actions job failing on pull requests with `invalid tag "...:-<sha>": invalid reference format`
- Root cause: `docker/metadata-action` used `type=sha,prefix={{branch}}-`, but `{{branch}}` resolves to an empty string on `pull_request` events, producing a tag with a leading hyphen (`:-b0503a8`) — which Docker rejects
- Changed the SHA tag prefix to a static `sha-`, valid across branch pushes, PRs, and the default branch; branch/PR identity is still captured by the separate `type=ref,event=branch` and `type=ref,event=pr` tag rules

---

## Summary

The Docker publish workflow couldn't build on any pull request because the SHA-based image tag was constructed with `{{branch}}-`, and the `branch` template is empty on PR events — yielding an invalid `:-<sha>` tag. Using a constant `sha-` prefix fixes PR builds without losing branch/PR information (the ref-based tags already encode that).
9 changes: 9 additions & 0 deletions changelogs/CHANGELOG-gitignore-claude.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Ignore local Claude Code state

- Added `.claude` to `.gitignore` so local Claude Code config (settings, launch.json, scheduled-task lock, and session worktrees under `.claude/worktrees/`) is never accidentally committed

---

## Summary

The `.claude/` directory holds machine-specific Claude Code state and a nested git worktree from prior sessions. It does not belong in version control, so it is now ignored alongside `node_modules`, `dist`, and test artifacts.
48 changes: 48 additions & 0 deletions changelogs/CHANGELOG-open-file-from-disk.md
Original file line number Diff line number Diff line change
@@ -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:<id>` 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.
4 changes: 4 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,10 @@ <h5>Menu</h5>
title="Disconnect folder" style="display:none">
<i class="bi bi-x-circle"></i>
</button>
<button class="ws-new-btn" id="ws-open-file" title="Open file from disk"
style="display:none">
<i class="bi bi-file-earmark-arrow-up"></i>
</button>
<button class="ws-new-btn" id="ws-open-folder" title="Open folder from disk"
style="display:none">
<i class="bi bi-folder-symlink"></i>
Expand Down
Loading
Loading