Skip to content

Commit fa0f9da

Browse files
authored
Merge pull request #3 from Textagent/claude/calculator-paste-live-expr
feat(calculator): live expression on top line, paste into expression line
2 parents bc9c0d2 + b0dcfbc commit fa0f9da

3 files changed

Lines changed: 59 additions & 16 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,7 @@ TextAgent has undergone significant evolution since its inception. What started
549549

550550
| Date | Commits | Feature / Update |
551551
|------|---------|-----------------:|
552+
| **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 |
552553
| **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 |
553554
| **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` |
554555
| **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 |
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Calculator — Live-Expression Display + Paste-Into-Top-Line
2+
3+
- 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 `=`)
4+
- 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)
5+
- 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
6+
- `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
7+
- Top + bottom lines stay in sync through paste, button input, keyboard input, and history-row edits
8+
9+
---
10+
11+
## Summary
12+
Two small but visible behaviour changes to the Tools → Calculator template that ship as a single `html-autorun` edit:
13+
14+
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.
15+
16+
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).
17+
18+
---
19+
20+
## 1. Live Expression Display
21+
**Files:** `js/templates/tools.js`
22+
**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.
23+
**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).
24+
25+
## 2. Paste-Into-Expression-Line Fix
26+
**Files:** `js/templates/tools.js`
27+
**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.
28+
**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.
29+
30+
## 3. Blur Dedup
31+
**Files:** `js/templates/tools.js`
32+
**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"`).
33+
**Impact:** History stays clean — one entry per logical calculation, not one per paste + one per blur.
34+
35+
---
36+
37+
## Files Changed (1 total)
38+
39+
| File | Lines Changed | Type |
40+
|------|:---:|------|
41+
| `js/templates/tools.js` | +17 −16 | `show()` simplification + paste-replaces-line + blur dedup |

js/templates/tools.js

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -143,24 +143,22 @@ A **history panel** on the right lists every calculation. Edit the expression *o
143143
144144
function show() {
145145
// iOS-style 2-line display:
146-
// top (small, dim) = full typed expression so far
147-
// bottom (big) = current operand, running result, or final = result
146+
// top (small, dim) = full expression as you type it
147+
// bottom (big) = live running result while typing, final result after =
148148
var topExpr, bottomNum;
149149
if (justEvaled && chain.length === 0) {
150150
// Just pressed =: top = "expr =", bottom = result
151151
var last = history[history.length - 1];
152152
topExpr = last ? (last.expr + ' =') : '';
153153
bottomNum = cur;
154-
} else if (pending) {
155-
// Operator just pressed: top = chain so far, bottom = running total of chain
156-
topExpr = chain.join('');
157-
var partial = chain.slice(0, -1).join('');
154+
} else {
155+
// Typing — top shows the entire expression so far (chain + current operand).
156+
// Bottom shows a live partial-evaluation result; falls back to the current operand
157+
// when the expression isn't yet complete (e.g. half-open brackets, trailing operator).
158+
topExpr = chain.join('') + (pending ? '' : cur);
159+
var partial = pending ? chain.slice(0, -1).join('') : topExpr;
158160
var rv = partial ? safeEval(partial) : null;
159161
bottomNum = (rv === null || !isFinite(rv)) ? cur : String(rv);
160-
} else {
161-
// Typing an operand
162-
topExpr = chain.join('');
163-
bottomNum = cur;
164162
}
165163
if (exprLine) exprLine.textContent = topExpr;
166164
if (resultLine) resultLine.textContent = bottomNum;
@@ -432,17 +430,20 @@ A **history panel** on the right lists every calculation. Edit the expression *o
432430
function commitExprEdit() {
433431
var raw = (exprLine.textContent || '').replace(/=\\s*$/, '').trim();
434432
if (!raw) return;
433+
// Skip if the line already matches the latest history entry's "expr =" view
434+
// (i.e. user blurred without actually editing anything).
435+
var last = history[history.length - 1];
436+
if (last && last.expr === raw) return;
435437
applyPasted(raw);
436438
}
437439
exprLine.addEventListener('paste', function (e) {
438440
e.preventDefault();
439441
var txt = (e.clipboardData || window.clipboardData).getData('text');
440-
exprLine.textContent = (exprLine.textContent || '').replace(/=\\s*$/, '') + txt;
441-
// Place caret at end (best-effort)
442-
try {
443-
var r = document.createRange(); r.selectNodeContents(exprLine); r.collapse(false);
444-
var s = window.getSelection(); s.removeAllRanges(); s.addRange(r);
445-
} catch (_) {}
442+
if (!txt) return;
443+
// Replace the line entirely and evaluate immediately — pasting a full
444+
// expression should not require a follow-up Enter/blur.
445+
applyPasted(txt);
446+
exprLine.blur();
446447
});
447448
exprLine.addEventListener('keydown', function (e) {
448449
if (e.key === 'Enter') {

0 commit comments

Comments
 (0)