From b0dcfbcc5e88ea661779a85c79dc63b9e9ed38f4 Mon Sep 17 00:00:00 2001 From: ijbo Date: Thu, 14 May 2026 20:00:59 +0900 Subject: [PATCH] feat(calculator): live expression on top line, paste into expression line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Top expression line now shows the full typed expression including the current operand, e.g. (2+3)^4*4 โ€” was previously only the committed chain (so the in-progress operand was missing while typing) - Bottom result line shows a live partial-evaluation result on every keystroke; falls back to the current operand when the partial is not yet valid (half-open bracket, trailing operator) - Pasting a full expression directly into the small top expression line now evaluates immediately โ€” previously the handler appended the pasted text onto the rendered "0", producing "0(2+3)^4*4" โ†’ Error - commitExprEdit() dedupes against the latest history entry, so the post-paste blur no longer creates a duplicate history row --- README.md | 1 + .../CHANGELOG-calculator-display-paste.md | 41 +++++++++++++++++++ js/templates/tools.js | 33 +++++++-------- 3 files changed, 59 insertions(+), 16 deletions(-) create mode 100644 changelogs/CHANGELOG-calculator-display-paste.md diff --git a/README.md b/README.md index ad456b0..4ffa70e 100644 --- a/README.md +++ b/README.md @@ -549,6 +549,7 @@ TextAgent has undergone significant evolution since its inception. What started | Date | Commits | Feature / Update | |------|---------|-----------------:| +| **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 `
.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` |
 | **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 |
diff --git a/changelogs/CHANGELOG-calculator-display-paste.md b/changelogs/CHANGELOG-calculator-display-paste.md
new file mode 100644
index 0000000..11cc8a5
--- /dev/null
+++ b/changelogs/CHANGELOG-calculator-display-paste.md
@@ -0,0 +1,41 @@
+# Calculator โ€” Live-Expression Display + Paste-Into-Top-Line
+
+- Top expression line now shows the **full expression** as you type โ€” every digit, operator and bracket appears immediately (e.g. `(2+3)^4*4` is fully visible before pressing `=`)
+- Bottom result line shows a **live running total** evaluated from the partial expression on every keystroke; falls back to the current operand when the partial isn't yet evaluable (half-open bracket, trailing operator)
+- Pasting into the small top expression line now **replaces** that line and **evaluates immediately** โ€” no more append-to-`0` artefact, no follow-up Enter/blur required
+- `commitExprEdit` skips re-running `applyPasted` if the line already matches the latest history entry, preventing duplicate "edit" rows when the user clicks away after a paste
+- Top + bottom lines stay in sync through paste, button input, keyboard input, and history-row edits
+
+---
+
+## Summary
+Two small but visible behaviour changes to the Tools โ†’ Calculator template that ship as a single `html-autorun` edit:
+
+1. **Live expression on the top line.** Previously the small dim line above the result only showed the *committed chain* (e.g. `(2+3)^4*` while the user was typing `4`). It now shows the entire expression including the current operand (`(2+3)^4*4`), matching how an iOS-style calculator would render a complete typed expression before `=` is pressed. The bottom big line continues to show a live partial-evaluation result.
+
+2. **Paste into the small top line.** Pasting a full expression like `(2+3)^4*4` directly onto the small expression line now evaluates instantly. The previous handler concatenated pasted text onto whatever was rendered there (often `"0"`), producing `"0(2+3)^4*4"` โ†’ `Error`. Paste is now treated as a complete replacement and routed straight to `applyPasted` (same code path as paste-into-result and the keyboard Enter handler).
+
+---
+
+## 1. Live Expression Display
+**Files:** `js/templates/tools.js`
+**What:** Collapsed the `pending` and "typing an operand" branches of `show()` into a single branch that writes `chain.join('') + (pending ? '' : cur)` to the top line. The bottom line shows `safeEval(partial)` where `partial` is `chain.slice(0,-1).join('')` when an operator is pending, otherwise the full `topExpr` itself โ€” giving a live partial result on every keystroke.
+**Impact:** Users see the full expression they are typing, matching the iOS Calculator visual model. Bug squashed: previously the top line "lost" the current operand whenever a digit was typed (because `chain.join('')` excluded it).
+
+## 2. Paste-Into-Expression-Line Fix
+**Files:** `js/templates/tools.js`
+**What:** The `exprLine.addEventListener('paste', โ€ฆ)` handler used to write `(exprLine.textContent || '').replace(/=\s*$/, '') + txt` into the DOM and rely on a later blur to commit. That concatenated the pasted text onto the initial-state `"0"` (since `show()` had set the line to the running expression). Replaced with `applyPasted(txt); exprLine.blur()` โ€” the pasted clipboard text is routed straight to the safe evaluator, in line with how the bottom result line already handled paste.
+**Impact:** Paste a complete expression into the small expression line and it evaluates instantly (`(2+3)^4*4` โ†’ top `(2+3)^4*4 =`, bottom `2500`). Whitespace, commas and unicode operators (`ร—`, `รท`, `โˆ’`) are normalised via the same `normalize()` call. Unsafe inputs (e.g. `alert(1)`) still get rejected as `Error` by the existing strict allowlist.
+
+## 3. Blur Dedup
+**Files:** `js/templates/tools.js`
+**What:** `commitExprEdit()` now early-returns when the line's trimmed text already equals the most recent history entry's `expr`. This prevents the post-paste blur from re-running `applyPasted` and creating a duplicate history row (paste โ†’ display shows `"expr ="` โ†’ user clicks elsewhere โ†’ blur fires โ†’ re-evaluate that same `"expr"`).
+**Impact:** History stays clean โ€” one entry per logical calculation, not one per paste + one per blur.
+
+---
+
+## Files Changed (1 total)
+
+| File | Lines Changed | Type |
+|------|:---:|------|
+| `js/templates/tools.js` | +17 โˆ’16 | `show()` simplification + paste-replaces-line + blur dedup |
diff --git a/js/templates/tools.js b/js/templates/tools.js
index 63112f4..a5d587f 100644
--- a/js/templates/tools.js
+++ b/js/templates/tools.js
@@ -143,24 +143,22 @@ A **history panel** on the right lists every calculation. Edit the expression *o
 
     function show() {
       // iOS-style 2-line display:
-      //   top (small, dim)  = full typed expression so far
-      //   bottom (big)      = current operand, running result, or final = result
+      //   top (small, dim)  = full expression as you type it
+      //   bottom (big)      = live running result while typing, final result after =
       var topExpr, bottomNum;
       if (justEvaled && chain.length === 0) {
         // Just pressed =: top = "expr =", bottom = result
         var last = history[history.length - 1];
         topExpr   = last ? (last.expr + ' =') : '';
         bottomNum = cur;
-      } else if (pending) {
-        // Operator just pressed: top = chain so far, bottom = running total of chain
-        topExpr = chain.join('');
-        var partial = chain.slice(0, -1).join('');
+      } else {
+        // Typing โ€” top shows the entire expression so far (chain + current operand).
+        // Bottom shows a live partial-evaluation result; falls back to the current operand
+        // when the expression isn't yet complete (e.g. half-open brackets, trailing operator).
+        topExpr = chain.join('') + (pending ? '' : cur);
+        var partial = pending ? chain.slice(0, -1).join('') : topExpr;
         var rv = partial ? safeEval(partial) : null;
         bottomNum = (rv === null || !isFinite(rv)) ? cur : String(rv);
-      } else {
-        // Typing an operand
-        topExpr   = chain.join('');
-        bottomNum = cur;
       }
       if (exprLine) exprLine.textContent = topExpr;
       if (resultLine) resultLine.textContent = bottomNum;
@@ -432,17 +430,20 @@ A **history panel** on the right lists every calculation. Edit the expression *o
     function commitExprEdit() {
       var raw = (exprLine.textContent || '').replace(/=\\s*$/, '').trim();
       if (!raw) return;
+      // Skip if the line already matches the latest history entry's "expr =" view
+      // (i.e. user blurred without actually editing anything).
+      var last = history[history.length - 1];
+      if (last && last.expr === raw) return;
       applyPasted(raw);
     }
     exprLine.addEventListener('paste', function (e) {
       e.preventDefault();
       var txt = (e.clipboardData || window.clipboardData).getData('text');
-      exprLine.textContent = (exprLine.textContent || '').replace(/=\\s*$/, '') + txt;
-      // Place caret at end (best-effort)
-      try {
-        var r = document.createRange(); r.selectNodeContents(exprLine); r.collapse(false);
-        var s = window.getSelection(); s.removeAllRanges(); s.addRange(r);
-      } catch (_) {}
+      if (!txt) return;
+      // Replace the line entirely and evaluate immediately โ€” pasting a full
+      // expression should not require a follow-up Enter/blur.
+      applyPasted(txt);
+      exprLine.blur();
     });
     exprLine.addEventListener('keydown', function (e) {
       if (e.key === 'Enter') {