From fa527774c2dfec90920d8b1befbefc3ac8241f22 Mon Sep 17 00:00:00 2001 From: Agando Date: Mon, 9 Nov 2026 15:36:11 +0100 Subject: fix(products): ship the reviewed 1.0.1 builds — the 1.0.0 pdf2txt never ran MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A review of all three products in Chromium found defects that only execution exposes. pdf2txt 1.0.0 DID NOT RUN. Both the packaged file and the live demo contained a duplicated const declaration inside fetchModels, so the inline script threw a SyntaxError at parse time and no handler was ever bound: no extraction, no translation, no config. Proven by extracting both inline scripts from the committed file and from spaces/chatstudio/pdf2txt.html and running node --check (SyntaxError: Identifier 'config' has already been declared). The two published versions of this product were dead on arrival. pdf2txt 1.0.1 also stops writing the API key and the whole extracted document to localStorage (sessionStorage for the key, nothing for the text), keeps paragraphs (hasEOL), splits chunks on word boundaries, reports a non-JSON 200 or a missing response field instead of writing "undefined" into the editor, re-enables Translate after a run, resets stopRequested, stops a workflow on a failed step, warns on a PDF with no text layer, recognises .PDF, fixes an unterminated mobile media query, stops the Fullscreen button covering line 1, and makes the drop zone and all 39 controls reachable and named. shiftplanner 1.0.1 fixes seven demonstrated defects: the settings modal recursed until the stack overflowed, "+ Worker" prefilled the last edited worker id and overwrote that person, date maths was off by one west of UTC (local-noon Date formatted with toISOString), same-day and overnight shift overlaps were never flagged, the generator sliders never reached state.settings, CSV export could execute a formula and broke on a quote, and the header never wrapped (390 px viewport gave a 1139 px document). Plus four data-integrity guards: unparseable saved plans are copied to a recovery key instead of silently overwritten, wrong-typed fields are normalised, unknown top-level fields survive a save, and a storage event syncs another tab. chat-widget 1.0.1 closes a critical attribute-injection XSS. escapeHtml() round-tripped through textContent, which does not escape a double quote, so a reply containing https://x.test/"onclick="alert(1)// produced a real and fired alert(1) when clicked. Proven both ways: the 1.0.0 file fired a dialog, the fixed one renders only href/target/rel and stays silent. The launcher also covered the Send button at 390 px, so the widget could not be used on a phone at all. Also in this commit: - Landing pages and demos are generated (build-product-pages.py). The "Try the demo" button used to point at spaces/chatstudio/pdf2txt.html (the dead copy above) and at a v2.1 shiftplanner with no AI panel: a demo that was a different build than the ZIP. Demos are now built into /demos// from products//, with the ../vendor/ prefix rewritten, and the publish wrapper regenerates them before every publish. - publish-public-repo.py refuses to publish a subtree with uncommitted changes. It reads git, so it silently republished the old tip while rc was 0 for every slug. --- CHANGELOG.md | 27 +++++ README.md | 12 +- VERSION | 2 +- docs/USAGE.md | 2 +- src/index.html | 359 +++++++++++++++++++++++++++++++++++++++++---------------- 5 files changed, 293 insertions(+), 109 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0684b9b..72d7731 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,30 @@ +## 1.0.1 — 2026-11-09 + +A review that drove the page in Chromium found that the 1.0.0 file **did not run at all**. Every fix +below was reproduced before it was made. + +- **P0: the console showed `SyntaxError: Identifier 'config' has already been declared`** — a + duplicated `const config = loadConfig()` inside `fetchModels`, so no handler was ever bound: no + extraction, no translation, no config. Both inline scripts are now syntax-checked at packaging time. +- **The API key was written to `localStorage`**, on an origin shared with the rest of the site, and the + **whole extracted document was stored** on every action. The key now lives in `sessionStorage` + (per tab, gone when the tab closes), old copies are scrubbed on load, and the document is never + persisted. +- Extraction joins pages with `hasEOL` instead of spaces, so paragraphs survive. +- Chunks split on word boundaries (they used to cut mid-word); the size is clamped to 500–5000. +- A 200 response that is not JSON, or a reply without a `response` field, now reports the real error + instead of writing the literal text `undefined` into the editor and logging success. +- Translate re-enables its button and hides the banner in a `finally`; `stopRequested` resets, so a + second run after Stop actually runs; a failed workflow step stops the workflow (the `break` used to + break only the `switch`). +- A PDF with no text layer says so, disables Download/Translate and keeps the previous text. +- `.PDF` (uppercase) is recognised as a PDF; the page count is shown after parsing. +- Mobile: the `@media (max-width: 600px)` block was unterminated and held no rules — fixed and filled + in; the Fullscreen button no longer overlaps the first line of the editor. +- Accessibility: the drop zone is a real `role="button"` with `tabindex="0"` and Enter/Space opens the + file picker; the file input stays in the accessibility tree; every visible label is associated and + the icon-only buttons carry `aria-label`s (39 controls, 0 unnamed). + ## [1.0.0] - 2026-11-09 First packaged release: single-file browser app plus local PDF.js vendor assets. diff --git a/README.md b/README.md index 566b335..a55d2ed 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,9 @@ server, no account, no sign-up and no tracking code in the app. `Clean → Validate`, `Translate Only`, `Clean Only`. - **Translate into 16 target languages** offered in the page: English, Spanish, French, German, Italian, Portuguese, Chinese, Japanese, Korean, Russian, Arabic, Hindi, - Turkish, Dutch, Polish, Swedish. Text is split into 1,500-character chunks and sent - one chunk at a time, with pause and stop buttons and a token counter. + Turkish, Dutch, Polish, Swedish. The text is split at word boundaries and sent one + chunk at a time (1,500 characters by default, 2,500 when a `:cloud` model is chosen), + with pause and stop buttons and a token counter. - **Settings tab** — API source (Proxied `/ollama`, Local `localhost:11434`, or a Custom URL), optional API key, *Refresh Models* (reads `/api/tags`), and chunk size. @@ -42,9 +43,10 @@ pdf2txt/ ## How to run it -The page loads PDF.js as a Web Worker, and browsers refuse to start a worker from a -`file://` URL. So double-clicking `src/index.html` will load but extraction will fail. -Serve the folder over plain HTTP instead — no dependencies, Python 3 is enough: +The page loads PDF.js as a Web Worker. Some browsers refuse to start a worker from a +`file://` URL (Chrome blocks it; a headless Chromium on this host happened to allow it), +so double-clicking `src/index.html` may load but extract nothing. If that happens, serve +the folder over plain HTTP — no dependencies, Python 3 is enough: ```bash cd products/pdf2txt diff --git a/VERSION b/VERSION index 3eefcb9..7dea76e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.0 +1.0.1 diff --git a/docs/USAGE.md b/docs/USAGE.md index df194b6..35c77be 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -90,7 +90,7 @@ Things worth knowing before you translate: - The package ships with **API Source** set to `Proxied (/ollama)` and **Translation Model** set to `glm-5.3-flash:cloud`. Both are cloud settings. For an offline run you must change them as in steps 3 and 4. -- Long documents are sent in 1,500-character pieces, one after another, so a big book +- Long documents are sent in pieces (1,500 characters, or 2,500 with a cloud model), one after another, so a big book takes a while. Use the pause/stop buttons if needed. - The **Clean** and **Validate** steps also use a model (`llama3.2:latest` by default). If you only want a translation, choose the workflow `Translate Only`. diff --git a/src/index.html b/src/index.html index ffb3dc8..1848aca 100644 --- a/src/index.html +++ b/src/index.html @@ -60,7 +60,15 @@ .drop-zone:hover, .drop-zone.dragover { border-color: var(--accent); background: rgba(0,255,0,0.05); } - .drop-zone input { display: none; } + /* The file input stays in the accessibility tree (not display:none) so it can + still be labelled and triggered from the keyboard; it is only visually hidden. */ + .visually-hidden { + position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; + overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; + } + .drop-zone:focus-visible, .log-header:focus-visible { + outline: 2px solid var(--accent); outline-offset: 2px; + } .file-info { display: flex; gap: 20px; flex-wrap: wrap; @@ -131,6 +139,10 @@ .fullscreen-btn:hover { color: var(--accent); border-color: var(--accent); } .preview-area textarea { width: 100%; height: 100%; min-height: 280px; + /* Top padding keeps the first line clear of the absolutely positioned + fullscreen button on desktop; on narrow screens the button is moved + into normal flow instead. */ + padding: 34px 4px 4px; background: transparent; color: var(--text); border: none; resize: none; font-family: 'SF Mono', monospace; font-size: 0.9em; line-height: 1.6; outline: none; @@ -171,21 +183,46 @@ input[type="number"]:focus { border-color: var(--accent); outline: none; } @media (max-width: 600px) { + body { padding: 12px; } + .container { max-width: 100%; } + h1 { font-size: 1.4em; margin-bottom: 16px; } + .card { padding: 12px; margin-bottom: 12px; } + .tabs { gap: 6px; padding-bottom: 8px; margin-bottom: 14px; } + .tab { padding: 8px 12px; font-size: 0.85em; } + .drop-zone { padding: 24px 12px; } + .form-row { gap: 8px; } + .form-row > label { flex: 1 0 100%; } + select, input[type="text"], input[type="password"], input[type="number"] { + min-width: 0; width: 100%; flex: 1 1 100%; + } + .btn { padding: 10px 14px; font-size: 0.9em; } + .btn-group { gap: 8px; } + .btn-group .btn { flex: 1 1 auto; } + .actions-row .btn { flex: 1 1 auto; } + .preview-area { padding: 10px; min-height: 260px; } + /* The fullscreen toggle must not float over the editor's first line: + on narrow screens it sits in normal flow above the text. */ + .fullscreen-btn { position: static; display: block; width: 100%; margin: 0 0 8px; } + .preview-area textarea { min-height: 240px; padding-top: 4px; } + .log-content { max-height: 120px; } + }
- - + +

1. Select File

-
- -
📂 Click to choose or drag & drop
PDF, TXT, MD, JSON
+
+ +
📂 Click to choose or drag & drop
PDF, TXT, MD, JSON
- - + +

Or paste text directly:

- - + +
- - + + 📊 Tokens: 0
- - + +
-
+
📋 Activity Log ▼
@@ -354,7 +391,7 @@

- +
- +
- - + Models: +
- +
- +
- +
- + (500-5000, auto-adjusts with cloud models)
- +