diff --git a/.gitattributes b/.gitattributes index d9df1e0..a1ea335 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,7 +11,3 @@ # and normalizing a CRLF source on `git add` would change it. /raw/** -text /incoming/** -text - -# Real documents for the `raw derive` tests are compared byte for byte (an .eml with CRLF -# included), so nothing under testfiles/ is ever normalized. -/testfiles/** -text diff --git a/.gitea/workflows/converters.yml b/.gitea/workflows/converters.yml new file mode 100644 index 0000000..73e16cb --- /dev/null +++ b/.gitea/workflows/converters.yml @@ -0,0 +1,87 @@ +# The converters against real documents. +# +# `ci.yml` proves the filters on documents built to be easy (`test_raw_derive.py`, +# `test_raw_derive_binary.py`). This workflow derives the real ones: the test corpus and its +# harness live in the private repository `torben/chemenu-testfiles`, cloned into `testfiles/` +# (gitignored in this repository) and run with this checkout's `tools/.venv`. The corpus is large +# (some 76 MB, one book needs 48 s per derivation) and holds material that may not sit in a public +# repository, which is why none of it runs in the standard CI. +# +# When it runs: +# - on a push to `main` that touches the converter surface (the `paths:` list below). Gitea +# evaluates `paths:` the way GitHub does - observed for `paths-ignore` in `ci.yml` and in use for +# `paths:` in `release.yml`; do not re-derive it; +# - by hand (`workflow_dispatch`), which is the way to run it after a change in the private +# repository alone: a push there starts nothing here, and a commit that reaches the converters +# only indirectly is not in the list; +# - before a release without suffix: `release.yml` carries the same steps as its job `converters` +# and the release job waits for it. A candidate (`-beta.N`) does not run it. +# +# It always tests `main` of the private repository. A converter change that alters an expected +# output (`.expect`) is therefore two commits, one per repository, the private one first. +# +# Runner shape: identical to ci.yml, and for the same reason - see its header for `nodejs`. +# +# Access to the private repository: the per-job token (`${{ gitea.token }}`), as `release.yml` +# uses it for this repository. If this instance does not let it read a second, private repository +# of the same owner, the clone step fails with an authentication error; the way out is a read token +# in 1Password, loaded with `1password/load-secrets-action` the way `ci.yml` loads its secret +# reference (the service account sees only the CI-CD vault). + +name: Converters + +on: + push: + branches: [main] + paths: + - 'tools/chemenu/filters/**' + - 'tools/chemenu/commands/raw/derive.py' + - 'tools/chemenu/commands/raw/accept.py' + - 'tools/chemenu/tests/test_raw_derive*.py' + - 'tools/chemenu/tests/binary_formats.py' + - 'tools/requirements.txt' + - '.gitea/workflows/converters.yml' + workflow_dispatch: + +jobs: + converters: + runs-on: linux-docker + timeout-minutes: 30 + container: + image: debian:trixie-slim + env: + WIKITOOL_SESSION_ID: converters-${{ github.run_id }} + WIKI_TRACE_DIR: /tmp/wikitool-trace + + steps: + - name: System dependencies + run: | + set -eu + apt-get update -qq + apt-get install -y --no-install-recommends \ + python3 python3-venv git nodejs ripgrep ca-certificates + rm -rf /var/lib/apt/lists/* + + - uses: actions/checkout@v7 + + - name: The private test corpus + uses: actions/checkout@v7 + with: + repository: torben/chemenu-testfiles + token: ${{ gitea.token }} + path: testfiles + + - name: Tool environment + run: | + set -eu + git config --global --add safe.directory "$GITHUB_WORKSPACE" + tools/preflight.sh + tools/.venv/bin/python -m pip install --quiet pytest + + - name: Real documents + # `-c pytest.ini` applies the suite's own configuration to a harness that lives outside + # `tools/`; the harness conftest pulls in the suite's conftest (testfiles/README.md). + run: | + set -eu + cd tools + .venv/bin/python -m pytest -c pytest.ini ../testfiles/harness -q --durations=5 diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 9613f43..0984183 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -24,6 +24,13 @@ # tag). It is a separate job so that a failing mirror leaves the release and its # tag standing: the release is the product, the wiki a view of it. It does not # run for a `-beta.N` candidate, for which `release` sets `skip`. +# +# `converters` is the first job: the converter tests against the real documents of the private +# repository `torben/chemenu-testfiles` (converters.yml has the reasoning and is the reference for +# the steps; the two are kept in step by hand, because this instance's support for calling one +# workflow from another is not something this repository relies on). `release` waits for it with +# `needs:`, so a red harness stops the run before a release or a tag exists. For a candidate it +# decides `skip` the way `release` does and ends after its first steps. name: Release @@ -39,7 +46,65 @@ on: workflow_dispatch: jobs: + converters: + runs-on: linux-docker + timeout-minutes: 30 + container: + image: debian:trixie-slim + env: + WIKITOOL_SESSION_ID: release-converters-${{ github.run_id }} + WIKI_TRACE_DIR: /tmp/wikitool-trace + + steps: + - name: System dependencies + run: | + set -eu + apt-get update -qq + apt-get install -y --no-install-recommends \ + python3 python3-venv git nodejs ripgrep ca-certificates + rm -rf /var/lib/apt/lists/* + + - uses: actions/checkout@v7 + + - name: A candidate needs no converter run + id: version + run: | + set -eu + case "$(tr -d '[:space:]' < VERSION)" in + *-beta.*) + echo "VERSION is a running candidate - the converter tests run for a release only." + echo "skip=true" >> "$GITHUB_OUTPUT" + ;; + *) + echo "skip=false" >> "$GITHUB_OUTPUT" + ;; + esac + + - name: The private test corpus + if: steps.version.outputs.skip != 'true' + uses: actions/checkout@v7 + with: + repository: torben/chemenu-testfiles + token: ${{ gitea.token }} + path: testfiles + + - name: Tool environment + if: steps.version.outputs.skip != 'true' + run: | + set -eu + git config --global --add safe.directory "$GITHUB_WORKSPACE" + tools/preflight.sh + tools/.venv/bin/python -m pip install --quiet pytest + + - name: Real documents + if: steps.version.outputs.skip != 'true' + run: | + set -eu + cd tools + .venv/bin/python -m pytest -c pytest.ini ../testfiles/harness -q --durations=5 + release: + needs: converters runs-on: linux-docker container: image: debian:trixie-slim diff --git a/.gitignore b/.gitignore index e790f5e..6f7b0ad 100644 --- a/.gitignore +++ b/.gitignore @@ -192,6 +192,12 @@ npm-debug.log* # anchored back open by the content backstop below, same as `incoming/`. /quarantine/ +# The private test corpus and converter harness (Gitea #232): a clone of +# torben/chemenu-testfiles, made by hand (instructions/dev/dev-setup.md) and run by +# .gitea/workflows/converters.yml. It is its own repository, so this one never tracks it; a clone +# without access simply has no such directory, and nothing in the stack's suite needs it. +/testfiles/ + # Content backstop - keep this block last. Nothing under raw/, kb/, work/ or sites/ may # be excluded by a pattern above; see the header note for why directory patterns # still have to be anchored rather than relying on these negations. diff --git a/CHANGES.md b/CHANGES.md index bad6d2c..8b60262 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,14 +14,19 @@ place only: `instructions/dev/version-parts.md` § The candidate model. --- -## 10.1.0-beta.1 - 2026-10-13 - raw derive PDF: Gliederung mit Seite und Zeile im Kopf, Hinterlegungen, Durchstreichungen und abweichende Textfarben als Auszeichnung im Text, Bildseiten als CHECK +## 10.1.0-beta.2 - 2026-10-14 - Konverter-Testkorpus und Harness ziehen in das private Repository chemenu-testfiles; neuer Workflow Converters, Freigabe-Gate im Release **Author:** Torben Nehmer - raw derive PDF: Gliederung mit Seite und Zeile im Kopf, Hinterlegungen, Durchstreichungen und abweichende Textfarben als Auszeichnung im Text, Bildseiten als CHECK +- Konverter-Testkorpus und Harness ziehen in das private Repository chemenu-testfiles; neuer Workflow Converters, Freigabe-Gate im Release +### Konverter-Tests gegen echte Dokumente im privaten Repository + +Der Testkorpus mit echten Dokumenten (PDF, Mail, Office, ZIP) und sein Test liegen nicht mehr in diesem Repository, sondern in dem privaten `torben/chemenu-testfiles`, das ein Entwickler als ignoriertes `testfiles/` neben den Stack klont (kein Submodul; ohne Zugriff fehlt nur dieser Lauf). Der neue Workflow `Converters` führt den Harness bei einem Push auf `main` aus, der die Konverter berührt, und auf Zuruf; `release.yml` führt ihn vor jedem Release ohne Suffix aus und bricht bei Rot vor dem Tag ab. Die gewöhnliche CI enthält den Test nicht mehr, die Abdeckungsgrenze hält. Eine Instanz merkt davon nichts. + ### Große PDFs: Gliederung und Auszeichnungen Bei einem PDF schreibt `raw derive` die Gliederung als Feld `outline` in den Kopf, je Eintrag mit Ebene, Seite und der Zeile der Seitenmarke in `derived.md`; so findet ein Leser einen Abschnitt, ohne das ganze Dokument zu lesen. Im Text stehen drei Auszeichnungen für das, was die Seite nur durch ihre Form sagt: `…` für eine Hinterlegung, `~~…~~` für eine Durchstreichung und `*…*` für eine vom Fließtext abweichende Textfarbe ohne Link. Sie geben das Aussehen wieder, nicht die Bedeutung; ohne sie ist der Text derselbe wie zuvor. Das Feld `markings` im Kopf ist die Legende mit Anzahl und Seiten, kein Tag überschreitet eine Zeile oder Seitenmarke, und Bildseiten melden einen CHECK. Das Layout liest `pdfminer.six`, das neu in `requirements.txt` steht; ein PDF ohne Gliederung und Auszeichnungen wird wie bisher abgeleitet, vorhandene `derived.md` bleiben unberührt. diff --git a/VERSION b/VERSION index 73295a5..2386dcd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -10.1.0-beta.1 +10.1.0-beta.2 diff --git a/docs/dev/development.md b/docs/dev/development.md index 11d2041..a16ba2f 100644 --- a/docs/dev/development.md +++ b/docs/dev/development.md @@ -40,8 +40,20 @@ Then have the agent run `instructions/bootstrap.md`: the preflight, then and how `dist export` runs as a test tool - is [instructions/dev/dev-setup.md](../../instructions/dev/dev-setup.md). -Real documents for `raw derive` live in [testfiles/](../../testfiles/README.md) and are -committed, so CI derives them too. The repository is public: that README says what may go in. +Real documents for `raw derive` do not live in this repository. The corpus and its test harness +are the private repository `torben/chemenu-testfiles`, cloned by hand into `testfiles/` (gitignored +here, deliberately not a submodule, so a clone without access works unchanged): + +```bash +git clone ssh://git@gitea.nehmer.net:10101/torben/chemenu-testfiles.git testfiles +cd tools && .venv/bin/python -m pytest -c pytest.ini ../testfiles/harness +``` + +It is private because the corpus is large (some 76 MB; one book takes 48 s per derivation) and +holds material that may not sit in a public repository, such as publishers' books and vendors' +documents. Without the clone, `pytest` runs unchanged and the real-document tests are simply not +there; the README of the private repository says what is in it, what each file's origin is and +whether it could be public. The tests run in their own workflow, see [The CI half](#the-ci-half). What a clone has and an instance does not: @@ -181,6 +193,18 @@ the download. `.gitea/workflows/nightly.yml` is the drift check against time rat a commit. `.gitea/workflows/release.yml` is step 6 above, and `.gitea/workflows/wiki-mirror.yml` refills the Gitea wiki by hand (see below). +`.gitea/workflows/converters.yml` runs the converters against the real documents of the private +repository (the clone above, run with the checkout's `tools/.venv`). It starts by itself on a push +to `main` that touches the converter surface - `tools/chemenu/filters/`, `raw/derive.py`, +`raw/accept.py`, `tests/test_raw_derive*.py`, `tests/binary_formats.py`, `tools/requirements.txt` +and the workflow itself - and by hand (`workflow_dispatch`), which is how to run it after a change +in the private repository alone: a push there starts nothing here. It always tests `main` of the +private repository, so a converter change that alters an expected output is two commits, the +private one first. Before a release without suffix, `release.yml` runs the same steps as a job its +release job waits for; a red harness ends the run before a release or a tag exists, and a +`-beta.N` candidate skips it. The job reads the private repository with the per-job token; if +that instance ever stops allowing this, the workflow's header names the 1Password fallback. + Three more workflows carry the live tests of the tracker adapters (Super Productivity, CalDAV): `ci.yml` runs the CalDAV half in a step of its own against Radicale as a process, `tracker-live.yml` runs nightly and covers both providers, and `sp-live-image.yml` builds the diff --git a/instructions/dev/dev-setup.md b/instructions/dev/dev-setup.md index 7869e8a..ed896d4 100644 --- a/instructions/dev/dev-setup.md +++ b/instructions/dev/dev-setup.md @@ -40,6 +40,7 @@ development material under `instructions/dev/` and `commonplace/`, and no | `USER.md`/`SOUL.md` describe a demo operator and persona | Written by the operator during setup | | `tools/wikitool dist upgrade` refuses: there is no stamp to compare against. The checkout follows `main` with `tools/wikitool sync` | Updated with `dist upgrade --latest` | | `instructions/dev/`, `docs/dev/`, `commonplace/` and `.gitea/` are present | Never shipped | + | `testfiles/` may hold the private test corpus (step 6) | Never present | 5. **Use `dist export` as a build and test tool.** It writes exactly the tree a release ships, so it is how a change to the shipped surface is looked at before it is released: @@ -56,6 +57,23 @@ development material under `instructions/dev/` and `commonplace/`, and no `.gitea/workflows/ci.yml` is that replay and the reference for it. Keep scratch trees outside this checkout. +6. **Clone the private test corpus, if you have access.** The real documents the converters are + tested against, and the harness that runs them, are a repository of their own and not part of + this one: + + ```bash + git clone ssh://git@gitea.nehmer.net:10101/torben/chemenu-testfiles.git testfiles + cd tools && .venv/bin/python -m pytest -c pytest.ini ../testfiles/harness + ``` + + The clone sits in `testfiles/`, which `.gitignore` excludes; it is deliberately not a + submodule. Without access nothing breaks: `pytest` in `tools/` needs none of it, and a + session that changes a converter leaves the real-document run to the `converters` workflow + ([publish-and-ci.md](publish-and-ci.md) step 3). What is missing without the clone is the + local run of that harness, nothing else. The corpus holds files that may not be public, so + nothing from it is copied into this repository, an issue or a bug report without its README + saying the file could be public. + ## Decision points - **Asked to set up an instance from this checkout** (`dist export` into the user's folder, or a diff --git a/instructions/dev/publish-and-ci.md b/instructions/dev/publish-and-ci.md index 05234f7..ca36980 100644 --- a/instructions/dev/publish-and-ci.md +++ b/instructions/dev/publish-and-ci.md @@ -7,7 +7,8 @@ description: How a stack change is published and how its CI run is waited for - The local checks cover what they cover on this machine; CI covers the same checks plus a full `setup-instance.md` replay against a fresh `dist export` (`.gitea/workflows/ci.yml`). A green run -on the published commit is therefore the end of the checked stretch of a work package, not a +on the published commit - and, for a converter change, a green `Converters` run (step 3) - is +therefore the end of the checked stretch of a work package, not a formality after it - which is why waiting for it belongs to the phase that wrote the code (`stack-build`), and a red run sends the work back there rather than into the closing phase. @@ -54,6 +55,17 @@ formality after it - which is why waiting for it belongs to the phase that wrote - Keep the polling out of the main context where the harness allows it - a background command or a fork - so a long wait does not fill the session with run listings. + **Converter changes have a second run.** When the published commit touches the converter + surface - `tools/chemenu/filters/`, `tools/chemenu/commands/raw/derive.py` or `accept.py`, + `tools/chemenu/tests/test_raw_derive*.py`, `tools/chemenu/tests/binary_formats.py`, + `tools/requirements.txt` or `.gitea/workflows/converters.yml` - the `Converters` workflow + (`converters.yml`) starts by itself beside CI and derives the real documents of the private + test corpus. It takes longer than CI (allow **10 minutes**); wait for it the same way, with + the same give-up time, and count a red run like a red CI run (step 4). Start it by hand + (`actions_run_write`, `dispatch_workflow` on `main`) when only the private repository changed + or when the commit reaches the converters only indirectly. A release without suffix runs the + same steps as the first job of `release.yml`, which the release waits for. + 4. **A red run is not waited past.** Read the failing job's log, fix the cause, and go back to step 1: this is still build work, whichever skill published. The phase that published ends only on a green run, and the issue stays open until there is one. diff --git a/instructions/dev/testing-conventions.md b/instructions/dev/testing-conventions.md index b7b0866..5189e9f 100644 --- a/instructions/dev/testing-conventions.md +++ b/instructions/dev/testing-conventions.md @@ -187,7 +187,15 @@ Whenever you add or change a test under `tools/chemenu/tests/`. ## Scope -Applies to `tools/chemenu/tests/` only. It says nothing about what to test - the test/review +Applies to `tools/chemenu/tests/` only. Tests against **real documents** are not among them: +they live in the private repository `chemenu-testfiles` (`harness/`, with the corpus beside it), +cloned into the gitignored `testfiles/` ([dev-setup.md](dev-setup.md) step 6) and run with this +checkout's `tools/.venv` by the `converters` workflow. A new file for the corpus is a case for +that harness, not a fixture here; the synthetic filter tests (`test_raw_derive.py`, +`test_raw_derive_binary.py`) stay and are what the suite relies on. The harness borrows this +suite's `conftest.py`, so a change to a fixture there reaches it too. + +This file says nothing about what to test - the test/review expectations for a stack change are the local checks in [publish-and-ci.md](publish-and-ci.md) (`docs verify`, `instructions verify`, pytest), run from `stack-build`. CI runs the suite once, unhardened, because the fixture makes a second hardened run redundant; see the note on the Tests diff --git a/testfiles/README.md b/testfiles/README.md deleted file mode 100644 index 10d73c0..0000000 --- a/testfiles/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# testfiles - -Real documents (PDF, mail, Office, ZIP) that CI feeds to `raw derive`, beyond the synthetic -fixtures in `tools/chemenu/tests/binary_formats.py`. `tools/chemenu/tests/test_raw_derive_testfiles.py` -picks up every file under this directory, so adding a file is adding a test case - no code change. -The directory exists only in a development clone: `dist export` does not ship it. - -## What goes in - -**Everything here is committed to a public repository.** A file belongs here only when it can be -public: - -- a document you wrote for the purpose, or one whose licence allows redistribution; -- no real people, customers, employers, addresses or internal URLs - in the text *and* in the - metadata (PDF author and producer, Office author and company, mail headers, ZIP entry names); -- nothing that was received in confidence. - -The author names of a published work (a NIST publication, a statute) are part of the work, not a -real person in the sense of the second item. Links that address an account - unsubscribe, -settings, tracking - count as personal data even when no name is in them: they let anyone holding -the file reach the recipient's account at the sender. Replace such text with filler of the same -length, so a mail keeps its structure. - -Removing a file later does not remove it from the history. - -## Where the files come from - -| Files | Origin and basis for publishing them | -|-------|--------------------------------------| -| `pdf/nist-ai-rmf.pdf`, `pdf/nist-ssdf-genai.pdf` | NIST AI 600-1 and NIST SP 800-218A: works of the US federal government, public domain. The author in the PDF metadata is the author of the published work. | -| `pdf/ifg.pdf` | Informationsfreiheitsgesetz as published on gesetze-im-internet.de: an official work (§ 5 UrhG), free of copyright. | -| `pdf/einstein-dissertation.pdf` | Einstein's dissertation (1905), a public-domain work. The file is a scan from a web download; the digitizer's terms of use were not checked. Kept deliberately by the maintainer for the scanned pages (OCR coverage). | -| `mail/*` (GitHub, GOG and TLDR newsletters, each as `.eml` and `.msg`) | Mails received by the maintainer; promotional and informational content of the senders, released by the maintainer as test fixtures. Recipient address, names, host names and recipient-bound tokens are replaced by filler of the same length. | -| `office/*`, `zip/archive.zip` | Written by the maintainer for this purpose, with fictitious data; release confirmed by the maintainer. Author fields in the Office metadata are empty. The archive holds copies of six files of this directory (among them two mails) and is rebuilt from the sanitized copies. | - -## Layout - -| Folder | For | -|--------|-----| -| `pdf/` | PDFs, including scanned ones without a text layer | -| `mail/` | `.eml` and `.msg`, with and without attachments, with attached mails | -| `office/` | `.docx`, `.pptx`, `.xlsx` (tracked changes, comments, hidden sheets) | -| `zip/` | archives, including mail-in-ZIP cases | - -The folders are a convention; the test reads every file below this directory. Keep file names -short and free of spaces - a name that is too long for the `raw/` path budget fails the test for -the right reason, but it is a nuisance to find. - -## What the test checks - -For each file, in a throwaway instance: `raw derive` succeeds; the original comes back byte for -byte (a ZIP is unpacked and gets a `.md` beside its documents instead); a second run over the same -bytes gives the same text apart from `derived:`; and `raw accept` takes the folder. The text -compared is every `.md` of the result plus the sheet files of a workbook (`.sheets/*.tsv`, -cells and formula lists). - -A file may carry a sidecar `.expect` (`offer.pdf.expect` for `offer.pdf`): one line per -piece of text that must appear in the derived text - in any `.md` or sheet TSV of the result - -empty lines and lines starting with `#` ignored. A formula is named by its bare text, such as -`SUM(Sales!L6:L29)`, not by a tab-separated line. Without one the file only has to derive, be stable and be accepted. A document that is -meant to be refused (encrypted, damaged) does not belong here; the synthetic tests cover refusals. - -## Trying a file by hand - -`raw derive` moves what it is given, so work on a copy in a throwaway instance, never on -`incoming/` of this clone. diff --git a/testfiles/mail/github-outlook.msg b/testfiles/mail/github-outlook.msg deleted file mode 100644 index f4c9c49..0000000 Binary files a/testfiles/mail/github-outlook.msg and /dev/null differ diff --git a/testfiles/mail/github-roundcube.eml b/testfiles/mail/github-roundcube.eml deleted file mode 100644 index a24abd2..0000000 --- a/testfiles/mail/github-roundcube.eml +++ /dev/null @@ -1,279 +0,0 @@ -Return-Path: -Delivered-To: tester@mail.local -Received: from localhost (localhost [127.0.0.1]) - by relay1.mail.local (Postfix) with ESMTP id 1FBDF3C79 - for ; Fri, 9 Oct 2026 11:33:17 +0200 (CEST) -Authentication-Results: relay1.mail.local (amavis); dkim=pass (2048-bit key) - header.d=github.com -Received: from relay1.mail.local ([127.0.0.1]) - by localhost (relay1.mail.local [127.0.0.1]) (amavis, port 10026) with LMTP - id qUuEj-q26P2w for ; - Fri, 9 Oct 2026 11:33:15 +0200 (CEST) -Received: from gw1.mail.local (gw1.mail.local [192.0.2.12]) - by relay1.mail.local (Postfix) with ESMTPS id EDEF21322 - for ; Fri, 9 Oct 2026 11:33:15 +0200 (CEST) -Received: from gw1.mail.local (localhost [127.0.0.1]) - by gw1.mail.local (Proxmox) with ESMTP id D3AC64175D - for ; Fri, 09 Oct 2026 09:33:15 +0000 (UTC) -Received-SPF: pass (github.com: 192.30.252.200 is authorized to use 'noreply@github.com' in 'mfrom' identity (mechanism 'ip4:192.30.252.0/22' matched)) receiver=gw1.mail.local; identity=mailfrom; envelope-from="noreply@github.com"; helo=out-17.smtp.github.com; client-ip=192.30.252.200 -Received: from out-17.smtp.github.com (out-17.smtp.github.com [192.30.252.200]) - (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) - key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) - (No client certificate requested) - by gw1.mail.local (Proxmox) with ESMTPS - for ; Fri, 09 Oct 2026 09:33:12 +0000 (UTC) -DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=github.com; - s=pf2026; t=1791538385; - bh=PUBUAPrF+j4smoQFdA2YzmimF06eina+b1lMnXVkNSk=; - h=Date:From:Reply-To:To:Cc:Subject:List-ID:List-Archive:List-Post: - From; - b=Glbp4FV+ONO7KVD75/XQI32S6nNkgMDQFHE88AhWhiFTBqjvc7Nw0brNmPpI7QiBH - 1XUObBokb3bzlcyCfF6GjccclB4rMdaJQN5sPHlhI5XgNdf1y/u1E+3TMBbegldNVi - fcyAbLCfs5YBofGXvZZRIka2l4OPLhmKTIHhhfnCFBmwfWJQ8DrxdXprVpsWTlss2A - e2D10Cntxq5yeMZM6/ogKBUBlkRcWM3tyqmBxoNYZxYwlQhCrGb851oM8mg5VWGOuW - SzD44XgjIoNFbYIGD1FalbbNNaPM9DD02b4sEiKf7KTxIgfIqCiUdCFmE/rmJbp8V7 - Z4W0FfTcgnejg== -Received: from github.com (hubbernetes-node-b6fc28b.va3-iad.github.net [10.48.68.62]) - by smtp.github.com (Postfix) with ESMTPA id 6DC44501B32 - for ; Fri, 9 Oct 2026 02:33:05 -0700 (PDT) -Date: Fri, 09 Oct 2026 02:33:05 -0700 -From: Boliang Zhang -Reply-To: microsoft/PowerToys -To: microsoft/PowerToys -Cc: Subscribed -Message-ID: -Subject: [microsoft/PowerToys] Pre-release v0.102.2814.0 - Preview - v0.102.2814.0 -Mime-Version: 1.0 -Content-Type: multipart/alternative; - boundary="--==_mimepart_6ac8b4d169f87_11712283000d9"; - charset=UTF-8 -Content-Transfer-Encoding: 7bit -Precedence: list -X-GitHub-Sender: LegendaryBlair -X-GitHub-Recipient: testermuster -X-GitHub-Reason: subscribed -List-ID: microsoft/PowerToys -List-Archive: https://github.com/microsoft/PowerToys -List-Post: -X-GitHub-Notify-Platform: newsies -X-Auto-Response-Suppress: All -destinations: tester@mail.local -X-GitHub-Recipient-Address: tester@mail.local - - -----==_mimepart_6ac8b4d169f87_11712283000d9 -Content-Type: text/plain; - charset=UTF-8 -Content-Transfer-Encoding: 7bit - - -# PowerToys Preview v0.102.2814.0 - -This is a preview-channel build for the **PowerToys 0.102 release train**, produced from the `stable` branch. It follows [v0.102.2803.0](https://github.com/microsoft/PowerToys/releases/tag/v0.102.2803.0); the notes below describe changes since that preview, not the complete 0.102 versus 0.101 changelog. This is not the final stable release. - -## Installer Hashes - -| Description | Filename | sha256 hash | -| --- | --- | --- | -| Per user - x64 | [PowerToysUserSetup-0.102.2814.0-x64.exe][ptUserX64] | 0E0BC3040FAFA85373410BB9221F869DCD6B355B1D95E1B56A11E94FC757D170 | -| Per user - ARM64 | [PowerToysUserSetup-0.102.2814.0-arm64.exe][ptUserArm64] | ECA42FE6646ACD8BE81E8BD3C03B46D6AD8CA8F8D04A080280A01209CD4E07A8 | -| Machine wide - x64 | [PowerToysSetup-0.102.2814.0-x64.exe][ptMachineX64] | 1AB750116360130C7A5B9B7DF1BD6795FD4BB3BF9538DADD64892F40D9DCDAD4 | -| Machine wide - ARM64 | [PowerToysSetup-0.102.2814.0-arm64.exe][ptMachineArm64] | DD37E0895EE43F306883F08B9ADB5367AFF5087C2A90E67C84D24FF345B72C2E | - -[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.102.2814.0/PowerToysUserSetup-0.102.2814.0-x64.exe -[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.102.2814.0/PowerToysUserSetup-0.102.2814.0-arm64.exe -[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.102.2814.0/PowerToysSetup-0.102.2814.0-x64.exe -[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.102.2814.0/PowerToysSetup-0.102.2814.0-arm64.exe - -## Highlights - -- **Mouse Button Lock**: Added opt-in left, right, and middle button locking so dragging and selection can continue without holding a button down. -- **Light Switch**: Added a CLI for status, theme switching, and schedule control, with JSON output for automation. -- **Settings**: Added a CLI to inspect module states and save enable/disable changes while PowerToys is closed. -- **Command Palette**: Fixed dragging packaged apps onto the dock and displaying their loaded icons. -- **Quick Accent**: Restored the horizontal scrollbar for overflowing character lists. -- **Shortcut Guide**: Improved startup and indexing, including fixes for rapid-reopen crashes and first-activation flicker. -- **Settings**: Replaced animated welcome-page GIFs with muted, looping H.264 videos and a fallback image. -- **ZoomIt**: Fixed DSC configuration to use actual registry-backed settings and notify the running utility of changes. - -## Command Palette - -- Fixed dragging packaged apps onto the dock so pinned entries launch correctly and their icons appear when loaded in [#51018](https://github.com/microsoft/PowerToys/pull/51018) by [@jiripolasek](https://github.com/jiripolasek) - -## Light Switch - -- Added a CLI to query status, set or toggle configured theme targets, and enable or disable schedules on the running Light Switch service, with JSON output for automation in [#50459](https://github.com/microsoft/PowerToys/pull/50459) - -## Mouse Button Lock - -- Added opt-in Mouse Button Lock for left, right, and middle buttons so dragging and selection can continue after a long press without continuously holding a button in [#49279](https://github.com/microsoft/PowerToys/pull/49279) by [@MuyuanMS](https://github.com/MuyuanMS) and [@owenpkent](https://github.com/owenpkent) - -## Quick Accent - -- Restored the horizontal scrollbar for overflowing character lists so Quick Accent shows the current position without clipping the scrollbar in [#50924](https://github.com/microsoft/PowerToys/pull/50924) by [@daverayment](https://github.com/daverayment) - -## Settings - -- Added a Settings CLI to inspect module states in text or JSON and save enable/disable changes while PowerToys is closed in [#50507](https://github.com/microsoft/PowerToys/pull/50507) by [@Copilot](https://github.com/Copilot) and [@MuyuanMS](https://github.com/MuyuanMS) -- Replaced animated welcome-page GIFs with muted, looping H.264 videos, with a PowerToys logo fallback when playback is unavailable in [#51057](https://github.com/microsoft/PowerToys/pull/51057) by [@Copilot](https://github.com/Copilot) - -## Shortcut Guide - -- Improved startup and shortcut indexing, preventing rapid-reopen crashes and initial overlay flicker while picking up PowerToys shortcut changes on the next launch in [#50609](https://github.com/microsoft/PowerToys/pull/50609) by [@daverayment](https://github.com/daverayment) - -## ZoomIt - -- Fixed the ZoomIt DSC resource to read and update actual registry-backed settings, detect configuration drift, and notify a running ZoomIt instance to reload changes in [#50551](https://github.com/microsoft/PowerToys/pull/50551) by [@Gijsreyn](https://github.com/Gijsreyn) - -## Development - -- Removed unused central package entries and documented retained dependency pins without changing the versions resolved by existing projects in [#50880](https://github.com/microsoft/PowerToys/pull/50880) by [@Copilot](https://github.com/Copilot) -- Moved local build logs into artifacts\logs so context-menu packaging no longer fails when trying to include open MSBuild log files in [#51056](https://github.com/microsoft/PowerToys/pull/51056) by [@Copilot](https://github.com/Copilot) -- Removed obsolete analyzer suppressions for retired template code to keep shared build diagnostics easier to maintain in [#51061](https://github.com/microsoft/PowerToys/pull/51061) by [@Copilot](https://github.com/Copilot) -- Promoted IL2081 trimming diagnostics to build errors in the shared AOT configuration while retaining explicit project-level exceptions in [#51073](https://github.com/microsoft/PowerToys/pull/51073) by [@Copilot](https://github.com/Copilot) -- Aligned main's checked-in release train with stable's existing 0.102 setting; the previous preview already used this release train in [#51111](https://github.com/microsoft/PowerToys/pull/51111) by [@Copilot](https://github.com/Copilot) and [@LegendaryBlair](https://github.com/LegendaryBlair) -- Enabled nullable reference checking across File Explorer preview and thumbnail projects and their unit tests, with annotations and guards that preserve existing preview behavior in [#51099](https://github.com/microsoft/PowerToys/pull/51099) by [@Copilot](https://github.com/Copilot) -- Updated PowerToys Run architecture documentation to point contributors to the current plugin-management and query-processing code in [#50519](https://github.com/microsoft/PowerToys/pull/50519) by [@ElenaGe216](https://github.com/ElenaGe216) and [@Gavin-Yau](https://github.com/Gavin-Yau) -- Enabled warnings-as-errors for the shared UI-test framework after resolving its analyzer warnings and documenting narrowly scoped exceptions in [#51085](https://github.com/microsoft/PowerToys/pull/51085) by [@Copilot](https://github.com/Copilot) -- Corrected developer documentation links so readers can reach the referenced Settings, PowerToys Run, and Group Policy files in [#51108](https://github.com/microsoft/PowerToys/pull/51108) by [@xThreeh](https://github.com/xThreeh) -- Updated Image Resizer observable models for WinRT and AOT compatibility and made the corresponding MVVM generator diagnostic fail the build while preserving settings serialization in [#51067](https://github.com/microsoft/PowerToys/pull/51067) by [@Copilot](https://github.com/Copilot) -- Corrected native build configuration warnings, removed an unused COM reference, and separated intermediate output folders without changing shipped filenames or output locations in [#51090](https://github.com/microsoft/PowerToys/pull/51090) by [@Copilot](https://github.com/Copilot) - -## Changes needing final review - -Two direct integration commits merged the reviewed main changes into stable without separate PRs: [2646ff0204b8](https://github.com/microsoft/PowerToys/commit/2646ff0204b82fb0ddcaf9810f1ef2841ec5009c) and [1fca166ef506](https://github.com/microsoft/PowerToys/commit/1fca166ef506f0f7391615879c13fa3769bdaafa). Both preserve the existing stable-only PowerDisplay diagnostics omission; no new branch-transition removals were identified. - - - - --- -View it on GitHub: -https://github.com/microsoft/PowerToys/releases/tag/v0.102.2814.0 -You are receiving this because you are subscribed to this thread. - -Message ID: -----==_mimepart_6ac8b4d169f87_11712283000d9 -Content-Type: text/html; - charset=UTF-8 -Content-Transfer-Encoding: 7bit - -

-

Preview v0.102.2814.0

Repository: microsoft/PowerToys · Tag: v0.102.2814.0 · Commit: 1fca166 · Released by: LegendaryBlair

- - -

PowerToys Preview v0.102.2814.0

-

This is a preview-channel build for the PowerToys 0.102 release train, produced from the stable branch. It follows v0.102.2803.0; the notes below describe changes since that preview, not the complete 0.102 versus 0.101 changelog. This is not the final stable release.

-

Installer Hashes

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
DescriptionFilenamesha256 hash
Per user - x64PowerToysUserSetup-0.102.2814.0-x64.exe0E0BC3040FAFA85373410BB9221F869DCD6B355B1D95E1B56A11E94FC757D170
Per user - ARM64PowerToysUserSetup-0.102.2814.0-arm64.exeECA42FE6646ACD8BE81E8BD3C03B46D6AD8CA8F8D04A080280A01209CD4E07A8
Machine wide - x64PowerToysSetup-0.102.2814.0-x64.exe1AB750116360130C7A5B9B7DF1BD6795FD4BB3BF9538DADD64892F40D9DCDAD4
Machine wide - ARM64PowerToysSetup-0.102.2814.0-arm64.exeDD37E0895EE43F306883F08B9ADB5367AFF5087C2A90E67C84D24FF345B72C2E
-

Highlights

-
    -
  • Mouse Button Lock: Added opt-in left, right, and middle button locking so dragging and selection can continue without holding a button down.
  • -
  • Light Switch: Added a CLI for status, theme switching, and schedule control, with JSON output for automation.
  • -
  • Settings: Added a CLI to inspect module states and save enable/disable changes while PowerToys is closed.
  • -
  • Command Palette: Fixed dragging packaged apps onto the dock and displaying their loaded icons.
  • -
  • Quick Accent: Restored the horizontal scrollbar for overflowing character lists.
  • -
  • Shortcut Guide: Improved startup and indexing, including fixes for rapid-reopen crashes and first-activation flicker.
  • -
  • Settings: Replaced animated welcome-page GIFs with muted, looping H.264 videos and a fallback image.
  • -
  • ZoomIt: Fixed DSC configuration to use actual registry-backed settings and notify the running utility of changes.
  • -
-

Command Palette

-
    -
  • Fixed dragging packaged apps onto the dock so pinned entries launch correctly and their icons appear when loaded in #51018 by @jiripolasek
  • -
-

Light Switch

-
    -
  • Added a CLI to query status, set or toggle configured theme targets, and enable or disable schedules on the running Light Switch service, with JSON output for automation in #50459
  • -
-

Mouse Button Lock

-
    -
  • Added opt-in Mouse Button Lock for left, right, and middle buttons so dragging and selection can continue after a long press without continuously holding a button in #49279 by @MuyuanMS and @owenpkent
  • -
-

Quick Accent

-
    -
  • Restored the horizontal scrollbar for overflowing character lists so Quick Accent shows the current position without clipping the scrollbar in #50924 by @daverayment
  • -
-

Settings

-
    -
  • Added a Settings CLI to inspect module states in text or JSON and save enable/disable changes while PowerToys is closed in #50507 by @Copilot and @MuyuanMS
  • -
  • Replaced animated welcome-page GIFs with muted, looping H.264 videos, with a PowerToys logo fallback when playback is unavailable in #51057 by @Copilot
  • -
-

Shortcut Guide

-
    -
  • Improved startup and shortcut indexing, preventing rapid-reopen crashes and initial overlay flicker while picking up PowerToys shortcut changes on the next launch in #50609 by @daverayment
  • -
-

ZoomIt

-
    -
  • Fixed the ZoomIt DSC resource to read and update actual registry-backed settings, detect configuration drift, and notify a running ZoomIt instance to reload changes in #50551 by @Gijsreyn
  • -
-

Development

-
    -
  • Removed unused central package entries and documented retained dependency pins without changing the versions resolved by existing projects in #50880 by @Copilot
  • -
  • Moved local build logs into artifacts\logs so context-menu packaging no longer fails when trying to include open MSBuild log files in #51056 by @Copilot
  • -
  • Removed obsolete analyzer suppressions for retired template code to keep shared build diagnostics easier to maintain in #51061 by @Copilot
  • -
  • Promoted IL2081 trimming diagnostics to build errors in the shared AOT configuration while retaining explicit project-level exceptions in #51073 by @Copilot
  • -
  • Aligned main's checked-in release train with stable's existing 0.102 setting; the previous preview already used this release train in #51111 by @Copilot and @LegendaryBlair
  • -
  • Enabled nullable reference checking across File Explorer preview and thumbnail projects and their unit tests, with annotations and guards that preserve existing preview behavior in #51099 by @Copilot
  • -
  • Updated PowerToys Run architecture documentation to point contributors to the current plugin-management and query-processing code in #50519 by @ElenaGe216 and @Gavin-Yau
  • -
  • Enabled warnings-as-errors for the shared UI-test framework after resolving its analyzer warnings and documenting narrowly scoped exceptions in #51085 by @Copilot
  • -
  • Corrected developer documentation links so readers can reach the referenced Settings, PowerToys Run, and Group Policy files in #51108 by @xThreeh
  • -
  • Updated Image Resizer observable models for WinRT and AOT compatibility and made the corresponding MVVM generator diagnostic fail the build while preserving settings serialization in #51067 by @Copilot
  • -
  • Corrected native build configuration warnings, removed an unused COM reference, and separated intermediate output folders without changing shipped filenames or output locations in #51090 by @Copilot
  • -
-

Changes needing final review

-

Two direct integration commits merged the reviewed main changes into stable without separate PRs: 2646ff0204b8 and 1fca166ef506. Both preserve the existing stable-only PowerDisplay diagnostics omission; no new branch-transition removals were identified.

- - -—

This release has 9 assets:

  • GroupPolicyObjectFiles-0.102.2814.0.zip
  • PowerToysSetup-0.102.2814.0-arm64.exe
  • PowerToysSetup-0.102.2814.0-x64.exe
  • PowerToysUserSetup-0.102.2814.0-arm64.exe
  • PowerToysUserSetup-0.102.2814.0-x64.exe
  • symbols-arm64.zip
  • symbols-x64.zip
  • Source code (zip)
  • Source code (tar.gz)

Visit the release page to download them.

- -

—
You are receiving this because you are watching this repository.
View it on GitHub or unsubscribe from all notifications for this repository.

- -----==_mimepart_6ac8b4d169f87_11712283000d9-- - diff --git a/testfiles/mail/gog-outlook.msg b/testfiles/mail/gog-outlook.msg deleted file mode 100644 index 29db105..0000000 Binary files a/testfiles/mail/gog-outlook.msg and /dev/null differ diff --git a/testfiles/mail/gog-roundcube.eml b/testfiles/mail/gog-roundcube.eml deleted file mode 100644 index 141f87a..0000000 --- a/testfiles/mail/gog-roundcube.eml +++ /dev/null @@ -1,637 +0,0 @@ -Return-Path: -Delivered-To: tester@mail.local -Received: from localhost (localhost [127.0.0.1]) - by relay1.mail.local (Postfix) with ESMTP id E7A713D1B - for ; Fri, 9 Oct 2026 12:10:57 +0200 (CEST) -Authentication-Results: relay1.mail.local (amavis); dkim=pass (2048-bit key) - header.d=smgrid.eu header.b="GW2zRKwt"; dkim=pass (2048-bit key) - header.d=email3.gog.com header.b="eFv0TUK7" -Received: from relay1.mail.local ([127.0.0.1]) - by localhost (relay1.mail.local [127.0.0.1]) (amavis, port 10026) with LMTP - id WujfPgrqimYD for ; - Fri, 9 Oct 2026 12:10:57 +0200 (CEST) -Received: from gw1.mail.local (gw1.mail.local [192.0.2.12]) - by relay1.mail.local (Postfix) with ESMTPS id 719511322 - for ; Fri, 9 Oct 2026 12:10:57 +0200 (CEST) -Received: from gw1.mail.local (localhost [127.0.0.1]) - by gw1.mail.local (Proxmox) with ESMTP id 5876C41760 - for ; Fri, 09 Oct 2026 10:10:57 +0000 (UTC) -Received-SPF: pass (emsgrid.com: Sender is authorized to use 'sm.#.3f5ada29ba85a3ae55-newsletter=email3.gog.com@emsgrid.com' in 'mfrom' identity (mechanism 'include:_spf.jupiter.salesmanago.pl' matched)) receiver=gw1.mail.local; identity=mailfrom; envelope-from="sm.#.3f5ada29ba85a3ae55-newsletter=email3.gog.com@emsgrid.com"; helo=amalthea.emgrid.pl; client-ip=89.25.191.77 -Received: from amalthea.emgrid.pl (amalthea.emgrid.pl [89.25.191.77]) - (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) - key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) - (No client certificate requested) - by gw1.mail.local (Proxmox) with ESMTPS - for ; Fri, 09 Oct 2026 10:10:56 +0000 (UTC) -Received: from s12 (_local_) - by smgrid.pl (mta04) with ESMTP id 4j1N0n4lGxzJtb5T - for ; Fri, 09 Oct 2026 10:10:49 +0000 (UTC) -DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=smgrid.eu; - s=sm-trackid; t=1791540649; x=1791585649; - bh=gpu7ZaQ1XIQKWRN0rsfuWvDWyeFeTBF+wOJbmRwqrPE=; - h=Date:From:To:Subject:List-Unsubscribe:List-Unsubscribe-Post:Date: - From:To:Subject:List-Id:List-Unsubscribe:List-Unsubscribe-Post:CC; - b=GW2zRKwtIA4UQOhiuIUky5HB0uL1Psi4DZ2ftjurUdK8bOACUxDwYj8819gVPKbHi - SzVltCunE1hRyUfXil3qqFVuzO8SaKigpIfXECPuxzFSJJAQ+RGQeMwPVRNB43MCqd - oA7C094/1Q0sX5a+KAYAEtoaRXJ7HUK1plKyQrl2gAYlIdhC5/Hqb338nLePYf79iZ - DXw1XciZns9ZEjQXQhhTYSFNUxu1Gp2C9frzs1vDe6a8pyW61WfsMOw0TVuhLFGOpx - kovUlNK7QCg283mQ+ZpNCLpAM1vb8SadJs0SwbC7vzIOGPVmaqyusBefaSDqbIK9R7 - EFY0488QZRhDg== -DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=email3.gog.com; - s=salesmanago; t=1791540649; x=1791585649; - bh=gpu7ZaQ1XIQKWRN0rsfuWvDWyeFeTBF+wOJbmRwqrPE=; - h=Date:From:To:Subject:List-Unsubscribe:List-Unsubscribe-Post:Date: - From:To:Subject:List-Id:List-Unsubscribe:List-Unsubscribe-Post:CC; - b=eFv0TUK7rrzF5OMLnsbYs0/Llxja3xiP57DsvZfbbh0e+fDY14eLLGaf6021fm+cP - wc5w//FRTjPV5Q4912AWJjNlLK1WdXibM6aLKQc6o13HipWX+DP5MsWPsmoxcDtxQc - ro5P+sPVEvkn06+xKj+NF03DCqPrH1ae5QZ0cWm3McLyb4L9YVsJZe+/LzoJXJicFY - GrXyk7GdIcGSr9t37r8f6N9vZf7qt/60Y7Wtth/EU0yDiUfWn3eI/aYGE96Ac/CURC - V8CHnicop8uUHBGj9GMuDo+FDk9FGy87+dSx4+0O1ms+Xs+mvT2b9+zr6Csfr96Vwd - l/nxTSL6BvW7w== -Date: Fri, 9 Oct 2026 12:10:49 +0200 (CEST) -From: "GOG.COM" -To: testermuster -Message-Id: <5404404998.40649541@emsgrid.com5404404998> -Subject: =?UTF-8?Q?Der_Albtraum_beginnt:_SILENT_HIL?= - =?UTF-8?Q?L:_Townfall_ist_jetzt_erh=C3=A4ltlich!?= -X-Abuse: Please report abuse here: abuse@manago.ai -X-Campaign: 000xx0x0-0x0x-0000-00xx-xx0xxxx00x0x -Feedback-ID: 000xx0x0-0x0x-0000-00xx-xx0xxxx00x0x:3f5ada29ba85a3ae:ManagoAI -List-Unsubscribe: , - -List-Unsubscribe-Post: List-Unsubscribe=One-Click -Precedence: bulk -X-Entity-Ref-ID: none -MIME-Version: 1.0 -Content-Type: multipart/alternative; - boundary="----=_Part_4135371_1142483364.1791540649541" - -------=_Part_4135371_1142483364.1791540649541 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 7bit - -Plain text version not available -------=_Part_4135371_1142483364.1791540649541 -Content-Type: text/html; charset=UTF-8 -Content-Transfer-Encoding: quoted-printable - -Der Albtraum beginnt: SILENT HILL: = -Townfall ist jetzt erh=C3=A4ltlich!= -
= -
Betritt St. Amelia,&n= -bsp;stelle dich dem, was geblieben ist, und&n= -bsp;finde heraus, warum alle verschwunden sind. &n= -bsp;‌ ‌ ‌ ‌ ‌ ‌ = -;‌ ‌ ‌ ‌ ‌ ‌  &= -nbsp;‌ ‌ ‌ ‌ ‌ ‌&nbs= -p;‌ ‌ ‌ ‌ ‌ ‌ &= -zwnj;   ‌ ‌ ‌ ‌ ‌&nb= -sp;‌ ‌ ‌ ‌ ‌ ‌ = -‌ ‌   ‌ ‌ ‌ ‌&n= -bsp;‌ ‌ ‌ ‌ ‌ ‌ = -;‌ ‌ ‌   ‌ ‌ ‌&= -nbsp;‌ ‌ ‌ ‌ ‌ ‌&nbs= -p;‌ ‌ ‌ ‌   ‌ ‌= - ‌ ‌ ‌ ‌ ‌ ‌&nb= -sp;‌ ‌ ‌ ‌ ‌   &zwnj= -; ‌ ‌ ‌ ‌ ‌ ‌&n= -bsp;‌ ‌ ‌ ‌ ‌ ‌ = -;  ‌ ‌ ‌ ‌ ‌ ‌&= -nbsp;‌ ‌ ‌ ‌ ‌ ‌&nbs= -p;‌   ‌ ‌ ‌ ‌ ‌= - ‌ ‌ ‌ ‌ ‌ ‌&nb= -sp;‌ ‌   ‌ ‌ ‌ &zwnj= -; ‌ ‌ ‌ ‌ ‌ ‌&n= -bsp;‌ ‌ ‌   ‌ ‌ &zwn= -j; ‌ ‌ ‌ ‌ ‌ ‌&= -nbsp;‌ ‌ ‌ ‌   ‌ &zw= -nj; ‌ ‌ ‌ ‌ ‌ ‌= - ‌ ‌ ‌ ‌ ‌   &z= -wnj; ‌ ‌ ‌ ‌ ‌ &zwnj= -; ‌ ‌ ‌ ‌ ‌ ‌&n= -bsp;  ‌ ‌ ‌ ‌ ‌ &zwn= -j; ‌ ‌ ‌ ‌ ‌ ‌&= -nbsp;‌   ‌ ‌ ‌ ‌ &zw= -nj; ‌ ‌ ‌ ‌ ‌ ‌= - ‌ ‌   ‌ ‌ ‌ &z= -wnj; ‌ ‌ ‌ ‌ ‌ &zwnj= -; ‌ ‌ ‌ 
<= -tbody>
= -
3D"GOG.com
= -
3D"Silent

ist jetzt = -erh=C3=A4ltlich. Und es ruft nach dir.

= -

WILLKOMMEN au= -f ST.  AMELIA, 1996

<= -table role=3Dpresentation border=3D0 cellpadding=3D0 cellspacing=3D0 width= -=3D"100%" style=3D"width:100%;">
3D"Tiled

=E2=80=8BEs ist 1996, du kommst in der abgelege= -nen, vom Nebel verschlungenen Inselstadt vor der K=C3=BCste Schottlands an.= - Erkunde die leeren Stra=C3=9Fen, verlassenen H=C3=A4user und verfallenden = -Wahrzeichen, w=C3=A4hrend Simon Ordell an einen Ort zur=C3=BCckkehrt, der u= -nheimlich vertraut wirkt.

<= -a target=3D_blank style=3D"display:block;width:100%;max-width:566px;" id=3D= -smlid_309 href=3D"https://gog.salesmanago.com/smrd.htm?url=3Dhttps%3A%2F%2F= -www.gog.com%2Fen%2Fgame%2Fsilent_hill_townfall%3Futm_source%3Dsalesmanago%2= -6utm_medium%3Demail%26utm_campaign%3D20261009_nl_SILENT_HILL_TOWNFALL_exclu= -sive_DE&track=3DXxxXXX00XXxxXxXXXXxxXXXXXXxxXXXXXXXXXxXxXXxXXXX0XXxXXx0= -XXX0XXXX0XXXxXXXxXXxXXXXXXXxXXxXXXXXxXXXxXXXXXXXXXXXxXXXXXXxxXx0xXXxxXX00XX= -XxXxXxXXxxxXXXXXxxxXXXXXXxXXX0XXXxxXXXXXxxXxXxXXxxXX0XXXXxx0XXXXxxXXXXXXXxX= -XXXXXXXXXXxXXxxXXXXXXxXXX00XXxxXxX0XXxxXXXXXXx">3D"Collage

D= -ie Stadt mag verlassen wirken, aber du bist alles andere als allein. Mit be= -grenzten Waffen und Werkzeugen bedeutet =C3=9Cberleben zu wissen, wann du d= -ich versteckst, wann du rennst und wann du k=C3=A4mpfst, wenn Schrecken aus= - der Stille auftauchen.

-=3D"Repeated
<= -tr>

Das gr=C3=B6=C3=9Fte R= -=C3=A4tsel der Stadt ist, was mit den Menschen passiert ist, die einst dort= - lebten. Folge den Spuren, die sie hinterlassen haben, l=C3=B6se storygetri= -ebene R=C3=A4tsel und finde heraus, warum du zur=C3=BCckgerufen wurdest.

= -

=E2= -=80=8BHole dir die De= -luxe Edition=E2=80=8B mit dem digitalen Artbook= -, dem Soundtrack und einem alternativen Outfit f=C3=BCr Simon und tauche no= -ch tiefer in die Welt des Spiels ein.

3D"Silent
= -
= -
<= -tr>
3D"Was

P= -robleme bei der Darstellung dieser Email? Klick bitte hier.<= -/p>

=E2=80=8B

Du erh=C3=A4ltst diese Email= -, weil sich deine Email-Adresse auf unserer Empf=C3=A4ngerliste befindet.&n= -bsp;Wenn du keine Emails mehr von uns erhalten m=C3=B6chtest, kannst du dic= -h = -hier abmelden.&nbs= -p;

=E2=80=8B

Diese Email dient ledi= -glich der Information und unterbreitet nicht das Angebot. Preise und Angebo= -te k=C3=B6nnen sich =C3=A4ndern. Weitere Informationen zu den einzelne= -n Angeboten finden sich auf GOG.COM. 

 = -

GOG.COM = -=C2=A9 GOG sp. z o.o., ul. Jagiello=C5=84ska 74, 03-301 Warschau, Polen. Alle anderen Marken si= -nd Eigentum ihrer jeweiligen Inhaber.

= - -------=_Part_4135371_1142483364.1791540649541-- - diff --git a/testfiles/mail/gog-roundcube.eml.expect b/testfiles/mail/gog-roundcube.eml.expect deleted file mode 100644 index 257a77e..0000000 --- a/testfiles/mail/gog-roundcube.eml.expect +++ /dev/null @@ -1,3 +0,0 @@ -# the text is in the HTML part only; the plain-text part is a one-line placeholder -Silent Hill: Townfall -JETZT KAUFEN diff --git a/testfiles/mail/tldr-outlook.msg b/testfiles/mail/tldr-outlook.msg deleted file mode 100644 index d6334d7..0000000 Binary files a/testfiles/mail/tldr-outlook.msg and /dev/null differ diff --git a/testfiles/mail/tldr-roundcube.eml b/testfiles/mail/tldr-roundcube.eml deleted file mode 100644 index e4198e5..0000000 --- a/testfiles/mail/tldr-roundcube.eml +++ /dev/null @@ -1,1400 +0,0 @@ -Return-Path: <000000x00000000x-0x0000x0-0000-0000-0000-000x00x00000-000000@dailyupdate.tldrnewsletter.com> -Delivered-To: tester@mail.local -Received: from localhost (localhost [127.0.0.1]) - by relay1.mail.local (Postfix) with ESMTP id 4B3A33D1B - for ; Fri, 9 Oct 2026 12:12:50 +0200 (CEST) -Authentication-Results: relay1.mail.local (amavis); dkim=pass (1024-bit key) - header.d=tldrnewsletter.com header.b="iNeJEoFI"; dkim=pass (1024-bit key) - header.d=amazonses.com header.b="hatOc5VA" -Received: from relay1.mail.local ([127.0.0.1]) - by localhost (relay1.mail.local [127.0.0.1]) (amavis, port 10026) with LMTP - id WaXwor7Xq29b for ; - Fri, 9 Oct 2026 12:12:48 +0200 (CEST) -Received: from gw1.mail.local (gw1.mail.local [192.0.2.12]) - by relay1.mail.local (Postfix) with ESMTPS id C4FB91322 - for ; Fri, 9 Oct 2026 12:12:48 +0200 (CEST) -Received: from gw1.mail.local (localhost [127.0.0.1]) - by gw1.mail.local (Proxmox) with ESMTP id A9F2041760 - for ; Fri, 09 Oct 2026 10:12:48 +0000 (UTC) -Received-SPF: pass (dailyupdate.tldrnewsletter.com: 54.240.77.27 is authorized to use '010001a12026838a-8b2742b8-#-#-#-914b29c02105-#@dailyupdate.tldrnewsletter.com' in 'mfrom' identity (mechanism 'ip4:54.240.77.27' matched)) receiver=gw1.mail.local; identity=mailfrom; envelope-from="010001a12026838a-8b2742b8-#-#-#-914b29c02105-#@dailyupdate.tldrnewsletter.com"; helo=a77-27.smtp-out.amazonses.com; client-ip=54.240.77.27 -Received: from a77-27.smtp-out.amazonses.com (a77-27.smtp-out.amazonses.com [54.240.77.27]) - (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) - key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) - (No client certificate requested) - by gw1.mail.local (Proxmox) with ESMTPS - for ; Fri, 09 Oct 2026 10:12:45 +0000 (UTC) -DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/simple; - s=4fjbpw2ycutwr4bnoiraulemncpkibbj; d=tldrnewsletter.com; - t=1791540757; - h=From:To:Subject:List-Unsubscribe:List-Unsubscribe-Post:MIME-Version:Date:Message-ID:Content-Type; - bh=reKuGdDr29RCDLzjsTvTTpwTXbg7WzxPwkIUtr1KaQQ=; - b=iNeJEoFINA+s30i47q+t2Lb91YwI+XMFovIYeiEFz6AzBpdB6T+FIZA3qfc5ywHG - Ecf7O95lP9iZQS1EFszSyxUvNWyEX84JRvCB/+QD5HaH2if8ONXCNaVc6szVuHzU/IH - R9LDqkKOloBg+PmqbwrUFUPLrJBb67rNKOdLUmHQ= -DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/simple; - s=224i4yxa5dv7c2xz3womw6peuasteono; d=amazonses.com; t=1791540757; - h=From:To:Subject:List-Unsubscribe:List-Unsubscribe-Post:MIME-Version:Date:Message-ID:Content-Type:Feedback-ID; - bh=reKuGdDr29RCDLzjsTvTTpwTXbg7WzxPwkIUtr1KaQQ=; - b=hatOc5VATYl5hTEzu5Aktu1AsjQiJwBw+kDPoMxD+sehOzigqPZ3m4O92TA+dL5x - Z469pt/pHmRQ5SoCFxuC9g231UdWeqCTvruczaksSsGUDiV/ptIgQXVnZF9Zy8g7Jss - OxA9QuPol/Yl3/c5OLCs7u5FRT74eqQM5vaZm6Ng= -From: TLDR Product -To: tester@mail.local -Subject: Will agents have network effects? =?utf-8?Q?=F0=9F=8C=90=2C?= your - moat is dead =?utf-8?Q?=F0=9F=8F=B0=2C?= when AI agents shop for us - =?utf-8?Q?=F0=9F=9B=92?= -X-Entity-Ref-Id: 0x0000x0-xx00-000x-0000-x0000x00000x -X-EmailOctopus-Parent-Id: 0x000xx0-x0xx-00x0-00xx-x0x0xx0000x0 -X-EmailOctopus-Parent-Type-Id: campaign -X-EmailOctopus-List-Id: x0x00x00-0x00-00xx-0x00-0000x0000000 -X-EmailOctopus-List-Contact-Id: 00000xx0-0xxx-00x0-x00x-00xxx0x00x00 -X-EmailOctopus-Started-Preparing-At: 2026-10-09T10:00:49+00:00 -X-EmailOctopus-Sent-At: 2026-10-09T10:12:37+00:00 -X-EmailOctopus-Team-Id: 66987ef2-4f4c-11e9-a3c9-06b79b628af2 -X-EmailOctopus-Version: 5 -List-Unsubscribe: -List-Unsubscribe-Post: List-Unsubscribe=One-Click -X-EmailOctopus-Ses-Configuration-Set-Name: emailoctopus-a9f7b12f74f3c968d9408897fb35539c -MIME-Version: 1.0 -Date: Fri, 9 Oct 2026 10:12:37 +0000 -Message-ID: <000000x00000000x-0x0000x0-0000-0000-0000-000x00x00000-000000@email.amazonses.com> -Content-Type: multipart/alternative; boundary=LB8Q1ZSP -Feedback-ID: ::1.us-east-1.Dj5/7DYmgk+n1sSGGXSQSLIthSjNQ0MGtV5Dro1Tr8U=:AmazonSES -X-SES-Outgoing: 2026.10.09-54.240.77.27 - ---LB8Q1ZSP -Content-Type: text/plain; charset=utf-8 -Content-Transfer-Encoding: quoted-printable - -While current AI agents act as standalone tools, future agents can -develo= -p network effects through acquisition and engagement -loops.=C2=A0= -=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2= -=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C= -=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80= -=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0= -=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2= -=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0= -=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2= -=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C= -=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80= -=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0= -=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2=A0=E2=80=8C=C2= -=A0 - - - Sign Up [1] |Advertise [2]|View Online [3]=20 - -=09=09TLDR = - - -=09=09TOGETHER WITH [Plaid] [4] - -TLDR PRODUCT MANAGEMENT 2026-10-= -09 - - FINANCIAL UNDERSTANDING CAN'T BE PROMPTED. IT HAS TO BE BUILT -(S= -PONSOR) [4]=20 - - The wheel needed roads. Electricity needed the grid. Eve= -ry leap -forward has needed infrastructure underneath it. AI in finance is= - no -different. - -Plaid [4] is the infrastructure that makes permission= -ed financial data -not just accessible, but understood. A paycheck read as= - income, or an -irregular pattern read as potential fraud.=C2=A0 - -That= - understanding can't be prompted. It has to be built. Plaid's -proprietary= - models are built to specifically understand financial -behavior at scale.= - - -Meet the infrastructure for AI innovation in finance. [4] - -= -=F0=9F=93=B1=20 - -NEWS & TRENDS - - WILL AGENTS HAVE NETWORK EFFECTS? (1= -1 MINUTE READ) [5]=20 - - While current AI agents act as standalone tools, = -future agents can -develop network effects through acquisition and engagem= -ent loops. By -turning artifacts into viral objects and building centraliz= -ed identity -layers, agent platforms could create winner-take-all dynamics= -.=20 - - YOUR MOAT IS DEAD. ANYONE CAN REBUILD YOUR PRODUCT (13 MINUTE READ= -) -[6]=20 - - Falling code generation costs and AI agent reimplementations= - have -eliminated traditional software feature and UI moats. Defensibility= - -has shifted toward accountability, proprietary data, regulatory trust,= - -hardware coupling, and customer outcomes, forcing companies to focus -o= -n operational moats and server-side APIs.=20 - -=F0=9F=9A=80=20 - -OPINIONS= - & TUTORIALS - - WHY AGENT-WRITTEN CODE NEEDS PRODUCT VERIFICATION (6 MIN= -UTE READ) [7] - - - When AI agents write both code and tests, they encod= -e identical -misunderstandings, producing green builds that fail real user= - needs. -Teams must establish independent verification based on user -req= -uirements. Lower maintenance costs and faster repair cycles make -comprehe= -nsive end-to-end product verification feasible.=20 - - TESLER'S LAW: COMPLE= -XITY MOVED TO A DIFFERENT PART OF THE JOURNEY (10 -MINUTE READ) [8]=20 - -= - Generative AI interfaces do not eliminate software complexity - they -shi= -ft it to users, developers, and reviewers. While AI automates -routine tas= -ks, it adds hidden burdens in prompt specification and -verification. Desi= -gners must address this by creating clear -constraints and verification wo= -rkflows.=20 - -=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB=20 - -RESOURCES & TOOLS -= - - 4 FREE DATADOG RESOURCES TO HELP YOU UNDERSTAND YOUR USERS (SPONSOR) -= -[9]=20 - - Juggling tools to try and get a sense of your UX? This PM Toolki= -t [9] -by Datadog shows you how to extract real meaning from product data = -and -build stronger pre-launch hypotheses. Learn how to visually replay -= -user sessions to uncover friction and prioritize updates that move the -ne= -edle. Get the toolkit [9]=20 - - PRODUCT DEFINITION (8 MINUTE READ) [10] = - - - Product thinking and "Product Definition" are essential in the AI -= -era, where agents execute code rapidly given clear direction. Formerly -kn= -own as feature specification, Product Definition shapes requirements -and = -context into actionable artifacts like prototypes and PRDs. This -competen= -cy evolves from tactical execution to building systems that -align teams a= -nd AI agents.=20 - - WHEN DOES ONBOARDING BECOME A PRODUCT PROBLEM? (8 MINU= -TE READ) [11]=20 - - Customer onboarding friction often signals a hidden pr= -oduct issue. -Product managers should address this by creating a repeatabl= -e "Golden -Path," mapping user tasks from purchase to production to replac= -e -manual steps with automated features. Tracking time to first value and= - -completion rates reduces delays and builds long-term product value.=20 -= - -=F0=9F=8E=81=20 - -MISCELLANEOUS - - PRODUCT MANAGER, APPLIED AI AT TL= -DR ($225K BASE + $60K BONUS, FULLY -REMOTE) [12]=20 - - TLDR is hiring its= - first PM to help build the agent-first operating -layer used across the c= -ompany. We're looking for a builder who has -shipped real products/systems= - with LLMs. Click here to learn more -[12].=20 - - INTRODUCING: SPOTIFY TE= -CHNOLOGY. PROVEN AT SPOTIFY, NOW YOURS (6 -MINUTE READ) [13]=20 - - Spotif= -y Technology is a unified suite of developer and media tools -including Po= -rtal, Confidence, Xirp, and Spotify for Backstage. Tested -across Spotify'= -s internal engineering teams to cover the entire -development lifecycle, t= -he offering aims to help external R&D -organizations balance team autonomy= - with architectural alignment.=20 - - HOW SNYK TURNED AN INTERNAL SUPPORT A= -GENT INTO A CUSTOMER FEATURE (8 -MINUTE READ) [14]=20 - - Snyk turned its = -internal AI support agent, Snyk Assist, into a -customer-facing feature. T= -he agent answers user queries within -permission boundaries, utilizing off= -line and online evaluations. Since -launch, it has resolved over 85% of su= -pport sessions without needing a -ticket.=20 - -=E2=9A=A1=20 - -QUICK LINK= -S - - EARN YOUR MBA 100% ONLINE FROM THE OHIO STATE UNIVERSITY (SPONSOR)= - -[15]=20 - - Advance your career with Ohio State's online MBA for Working= - -Professionals-our nationally ranked part-time MBA with the flexibility= - -of an online program that can be completed from anywhere. - -EXPLORE T= -HE PROGRAM [15] - - THE MOST COMMON REASONS WHY AI PILOTS FAIL, EVEN AT T= -HE 'BEST' -COMPANIES (1 MINUTE READ) [16]=20 - - This thread outlines thir= -teen common pitfalls that cause enterprise -AI pilots to fail, such as aut= -omating isolated steps instead of full -workflows, lacking baseline metric= -s, ignoring edge cases, and failing -to engage legal and security teams ea= -rly.=20 - - WHAT HAPPENS WHEN AI AGENTS SHOP FOR US (3 MINUTE READ) [17] = - - - As AI shopping agents use wallets to browse catalogs and complete -= -checkouts automatically, online merchants risk losing direct customer -con= -tact, while users must retain final purchase approval to prevent -accident= -al transactions.=20 - -Love TLDR? Tell your friends and get rewards! - - = -Share your referral link below with friends to get free TLDR swag!=20 - - h= -ttps://refer.tldr.tech/x00000x0/9 [18]=20 - -=09=09 Track your referrals he= -re. [19]=20 - -Want to advertise in TLDR? =F0=9F=93=B0 - - If your compan= -y is interested in reaching an audience of product -management professiona= -ls and decision makers, you may want to -ADVERTISE WITH US [20].=20 - -Wan= -t to work at TLDR? =F0=9F=92=BC - - APPLY HERE [21], CREATE YOUR OWN ROLE= - [22] or send a friend's resume -to jobs@tldr.tech and get $1k if we hire = -them! TLDR is one of INC.'S -BEST BOOTSTRAPPED BUSINESSES [23] of 2025. = - - - If you have any comments or feedback, just respond to this email! = - - -Thanks for reading,=20 -Ellen Le [24]=20 - - Manage your subscriptions= - [25] to our other newsletters on tech, -startups, and programming. Or if = -TLDR Product Management isn't for -you, please unsubscribe [26].=20 - - = - - -Links: ------- -[1] https://tldr.tech/product?utm_source=3Dtldrprod= -uct -[2] https://advertise.tldr.tech/?utm_source=3Dtldrproduct&utm_medium= -=3Dnewsletter&utm_campaign=3Dadvertisetopnav -[3] https://a.tldrnewsletter= -.com/web-version?ep=3D1&lc=3D00000xx0-0xxx-00x0-x00x-00xxx0x00x00&p=3D0x000= -xx0-x0xx-00x0-00xx-x0x0xx0000x0&pt=3Dcampaign&t=3D1791540757&s=3Dx0xxx000x0= -0x000x0000000xx0000x0000x0x0x0000x0x0x000xx0xxx000xx00 -[4] https://plaid.= -com/intelligent-finance?utm_source=3DTLDRProduct&utm_medium=3DPaidNewslette= -r&utm_campaign=3DTLDRProduct_Paid_Newsletter_Ad_Buy&utm_content=3DIntellige= -ntFinanceLP -[5] https://andrewchen.substack.com/p/will-agents-have-networ= -k-effects?utm_source=3Dtldrproduct -[6] https://www.techfounderstack.com/p= -/your-moat-is-dead-anyone-can-rebuild?utm_source=3Dtldrproduct -[7] https:= -//stack72.dev/why-agent-written-code-needs-product-verification/?utm_source= -=3Dtldrproduct -[8] https://links.tldrnewsletter.com/j1OCPG -[9] https://= -www.datadoghq.com/resources/product-manager-toolkit/?utm_source=3Dtldrnewsl= -etter&utm_medium=3Dnewsletter&utm_campaign=3Ddg-content-toolkit-2026product= -manager-dem-productanalytics-ww-en-701vy00000meylhyak-tldrpm-secondary-1009= -26 -[10] https://blog.ravi-mehta.com/p/product-definition?utm_source=3Dtld= -rproduct -[11] https://amycmitchell.substack.com/p/product-management-deve= -loper-experience?utm_source=3Dtldrproduct -[12] https://jobs.ashbyhq.com/t= -ldr.tech/897f4391-66ab-44ca-a8eb-65dd9809200d?utm_source=3Dtldr&sp=3Dxxx0x0= -XX00xxxxXxXXXXXXXx -[13] https://engineering.atspotify.com/2026/10/introdu= -cing-spotify-technology-proven-at-spotify-now-yours?utm_source=3Dtldrproduc= -t -[14] https://www.langchain.com/blog/how-snyk-turned-an-internal-support= --agent-into-a-customer-feature?utm_source=3Dtldrproduct -[15] https://onli= -ne.osu.edu/masters-degree/master-business-administration-working-profession= -als/?lead_source=3DResponse-Solutions&utm_campaign=3Doaa_oso_wpmba-student-= -recruitment_fy27_tldr_product&channel=3DOther-Paid&utm_source=3D3rd-Party-D= -isplay&utm_medium=3DENewsletter -[16] https://threadreaderapp.com/thread/2= -107530654741348354.html?utm_source=3Dtldrproduct -[17] https://flaviocopes= -.com/agent-checkout/?utm_source=3Dtldrproduct -[18] https://refer.tldr.tec= -h/x00000x0/9 -[19] https://hub.sparklp.co/xxx_xxx00x00xxxx/9 -[20] https:= -//advertise.tldr.tech/?utm_source=3Dtldrproduct&utm_medium=3Dnewsletter&utm= -_campaign=3Dadvertisecta -[21] https://jobs.ashbyhq.com/tldr.tech -[22] h= -ttps://jobs.ashbyhq.com/tldr.tech/c227b917-a6a4-40ce-8950-d3e165357871 -[2= -3] https://www.linkedin.com/feed/update/urn:li:activity:7401699691039830016= -/ -[24] https://www.linkedin.com/in/ellenle/ -[25] https://tldr.tech/prod= -uct/manage?email=3Dtester%40mail.local -[26] https://a.tldrnewsletter.com/= -unsubscribe?ep=3D1&l=3Dx0x00x00-0x00-00xx-0x00-0000x0000000&lc=3D00000xx0-0= -xxx-00x0-x00x-00xxx0x00x00&p=3D0x000xx0-x0xx-00x0-00xx-x0x0xx0000x0&pt=3Dca= -mpaign&pv=3D5&spa=3D1791540049&t=3D1791540757&s=3Dxx000000000000x000000x000= -x0xxx00x000x000000xx0x0000000x00x00xx0x ---LB8Q1ZSP -Content-Type: text/html; charset=utf-8 -Content-Transfer-Encoding: quoted-printable - -TLDR Product - - - -
While curr= -ent AI agents act as standalone tools, future agents can develop network ef= -fects through acquisition and engagement loops. =E2=80=8C =E2=80= -=8C =E2=80=8C =E2=80=8C =E2=80=8C =E2=80=8C =E2=80= -=8C =E2=80=8C =E2=80=8C =E2=80=8C =E2=80=8C =E2=80= -=8C =E2=80=8C =E2=80=8C =E2=80=8C =E2=80=8C =E2=80= -=8C =E2=80=8C =E2=80=8C =E2=80=8C =E2=80=8C =E2=80= -=8C =E2=80=8C =E2=80=8C =E2=80=8C =E2=80=8C  = -=E2=80=8C =E2=80=8C =E2=80=8C =E2=80=8C =E2=80=8C = -=E2=80=8C =E2=80=8C =E2=80=8C =E2=80=8C =E2=80=8C = -=E2=80=8C =E2=80=8C =E2=80=8C =E2=80=8C =E2=80=8C = -=E2=80=8C =E2=80=8C =E2=80=8C =E2=80=8C =E2=80=8C = -=E2=80=8C =E2=80=8C =E2=80=8C =E2=80=8C =E2=80=8C = -=E2=80=8C 
- -
-
-
- -
- -
- - -
- -
- - - -
- -
- -
- -
TLDR -
-
- -
- - - - -
Together With -3D"Plaid"
-
- -

TLDR Product Management 2026-10-09

-
-
- -
-
- - - - Financial understanding can= -'t be prompted. It has to be built (Sponsor) - - -
-
- - The wheel needed roads. Electricity nee= -ded the grid. Every leap forward has needed infrastructure underneath it. A= -I in finance is no different.

<= -span>Plaid is the infrastructure that makes permissioned financi= -al data not just accessible, but understood. A paycheck read as income, or = -an irregular pattern read as potential fraud. 

- -

That understanding can't be prompted. It has to be built. Plaid's propri= -etary models are built to specifically understand financial behavior at sca= -le.

- -

Meet the infrastructure for AI = -innovation in finance. - - -

-
-
-
-
-
- -
- -
-
-
=F0=9F= -=93=B1
-
- -
-
-
- -

News & Trends

-
-
-
- -
- -
-
- - - - Will agents have network ef= -fects? (11 minute read) - - -
-
- - While current AI agents act as standalo= -ne tools, future agents can develop network effects through acquisition and= - engagement loops. By turning artifacts into viral objects and building cen= -tralized identity layers, agent platforms could create winner-take-all dyna= -mics. - -
-
-
- -
-
- - - - Your Moat Is Dead. Anyone C= -an Rebuild Your Product (13 minute read) - - -
-
- - Falling code generation costs and AI ag= -ent reimplementations have eliminated traditional software feature and UI m= -oats. Defensibility has shifted toward accountability, proprietary data, re= -gulatory trust, hardware coupling, and customer outcomes, forcing companies= - to focus on operational moats and server-side APIs. - -
-
-
- -
- -
-
-
=F0=9F= -=9A=80
-
-
- -
-
-
- -

Opinions & Tutorials

-
-
-
- - -
- -
-
- - - - Why Agent-Written Code Need= -s Product Verification (6 minute read) - - -
-
- - When AI agents write both code and test= -s, they encode identical misunderstandings, producing green builds that fai= -l real user needs. Teams must establish independent verification based on u= -ser requirements. Lower maintenance costs and faster repair cycles make com= -prehensive end-to-end product verification feasible. - -
-
-
- -
-
- - - - Tesler's Law: complexity mo= -ved to a different part of the journey (10 minute read) - - -
-
- - Generative AI interfaces do not elimina= -te software complexity - they shift it to users, developers, and reviewers.= - While AI automates routine tasks, it adds hidden burdens in prompt specifi= -cation and verification. Designers must address this by creating clear cons= -traints and verification workflows. - -
-
-
- -
- -
-
-
=F0=9F= -=A7=91=E2=80=8D=F0=9F=92=BB
-
-
- -
-
-
- -

Resources & Tools

-
-
-
- -
- -
-
- - - - 4 free Datadog resources to= - help you understand your users (Sponsor) - - -
-
- - Juggling tools to try and get a sense o= -f your UX? This PM Toolkit by Datado= -g shows you how to extract real meaning from product data and build stronge= -r pre-launch hypotheses. Learn how to visually replay user sessions to unco= -ver friction and prioritize updates that move the needle. Get the toolkit - -
-
-
- -
-
- - - - Product definition (8 minut= -e read) - - -
-
- - Product thinking and "Product Definitio= -n" are essential in the AI era, where agents execute code rapidly given cle= -ar direction. Formerly known as feature specification, Product Definition s= -hapes requirements and context into actionable artifacts like prototypes an= -d PRDs. This competency evolves from tactical execution to building systems= - that align teams and AI agents. - -
-
-
- -
-
- - - - When Does Onboarding Become= - a Product Problem? (8 minute read) - - -
-
- - Customer onboarding friction often sign= -als a hidden product issue. Product managers should address this by creatin= -g a repeatable "Golden Path," mapping user tasks from purchase to productio= -n to replace manual steps with automated features. Tracking time to first v= -alue and completion rates reduces delays and builds long-term product value= -. - -
-
-
- -
- -
-
-
=F0=9F= -=8E=81
-
- -
-
-

Miscellaneous

<= -/div> -
-
- -
- -
-
- - - - Product Manager, Applied AI= - at TLDR ($225k base + $60k bonus, Fully Remote) - - -
-
- - TLDR is hiring its first PM to help bui= -ld the agent-first operating layer used across the company. We're looking f= -or a builder who has shipped real products/systems with LLMs. Click here to learn more. - -
-
-
- -
-
- - - - Introducing: Spotify Techno= -logy. Proven at Spotify, now yours (6 minute read) - - -
-
- - Spotify Technology is a unified suite o= -f developer and media tools including Portal, Confidence, Xirp, and Spotify= - for Backstage. Tested across Spotify's internal engineering teams to cover= - the entire development lifecycle, the offering aims to help external R&= -;D organizations balance team autonomy with architectural alignment. - -
-
-
- -
-
- - - - How Snyk turned an internal= - support agent into a customer feature (8 minute read) - - -
-
- - Snyk turned its internal AI support age= -nt, Snyk Assist, into a customer-facing feature. The agent answers user que= -ries within permission boundaries, utilizing offline and online evaluations= -. Since launch, it has resolved over 85% of support sessions without needin= -g a ticket. - -
-
-
- -
- - - -
-
-
=E2=9A= -=A1
-
- -
-
-
- -

Quick Links

-
-
-
- -
- -
-
- - - - Earn your MBA 100% online f= -rom The Ohio State University (Sponsor) - - -
-
- - Advance your career with Ohio State's o= -nline MBA for Working Professionals-our nationally ranked part-time MBA wit= -h the flexibility of an online program that can be completed from anywhere.= -

= -Explore the program - - -

-
-
- -
-
- - - - The most common reasons why= - AI pilots fail, even at the 'best' companies (1 minute read) - - -
-
- - This thread outlines thirteen common pi= -tfalls that cause enterprise AI pilots to fail, such as automating isolated= - steps instead of full workflows, lacking baseline metrics, ignoring edge c= -ases, and failing to engage legal and security teams early. - -
-
-
- -
-
- - - - What happens when AI agents= - shop for us (3 minute read) - - -
-
- - As AI shopping agents use wallets to br= -owse catalogs and complete checkouts automatically, online merchants risk l= -osing direct customer contact, while users must retain final purchase appro= -val to prevent accidental transactions. - -
-
-
- -
- - - - - - - - -
- -

-Love TLDR? Tell your friends and get rewards! -

-
-
-Share your referral link below with friends to get free TLDR swag! -
-
- -
-Track your referrals here. -
- - - - -
- -

-Want to advertise in TLDR? =F0=9F=93=B0 -

-
-If your company is interested in reaching an audience of product management= - professionals and decision makers, you may want to advertise with us. -
-
- - - -

-Want to work at TLDR? =F0=9F=92=BC -

-
-Apply here, -create your own role or send a friend's resume to jobs@tldr.tech an= -d get $1k if we hire them! TLDR is one of Inc.'s Best Bootstrapped businesses = -of 2025. -
-
- -
-If you have any comments or feedback, just respond to this email! -
-
Thanks for reading, -
-Ellen L= -e -
-
-
-
-
- -
-
-Manage your subscriptions to our other newsletters on tech= -, startups, and programming. Or if TLDR Product Management isn't for you, p= -lease unsubscribe. -
- -
-
-
-
-
-
- - -3D"" - ---LB8Q1ZSP-- - diff --git a/testfiles/office/excel-artificial.xlsx b/testfiles/office/excel-artificial.xlsx deleted file mode 100644 index 1a0467a..0000000 Binary files a/testfiles/office/excel-artificial.xlsx and /dev/null differ diff --git a/testfiles/office/excel-business.xlsx b/testfiles/office/excel-business.xlsx deleted file mode 100644 index 1b40022..0000000 Binary files a/testfiles/office/excel-business.xlsx and /dev/null differ diff --git a/testfiles/office/excel-business.xlsx.expect b/testfiles/office/excel-business.xlsx.expect deleted file mode 100644 index e4b109a..0000000 --- a/testfiles/office/excel-business.xlsx.expect +++ /dev/null @@ -1,2 +0,0 @@ -# the bare formula text: it is in a .formulas.tsv only, not in derived.md and not in a cell TSV -SUM(Sales!L6:L29) diff --git a/testfiles/office/excel-science.xlsx b/testfiles/office/excel-science.xlsx deleted file mode 100644 index 9f2d9b5..0000000 Binary files a/testfiles/office/excel-science.xlsx and /dev/null differ diff --git a/testfiles/office/pptx-baseline.pptx b/testfiles/office/pptx-baseline.pptx deleted file mode 100644 index 8f743f2..0000000 Binary files a/testfiles/office/pptx-baseline.pptx and /dev/null differ diff --git a/testfiles/office/pptx-master.pptx b/testfiles/office/pptx-master.pptx deleted file mode 100644 index b18d6a6..0000000 Binary files a/testfiles/office/pptx-master.pptx and /dev/null differ diff --git a/testfiles/office/pptx-objects.pptx b/testfiles/office/pptx-objects.pptx deleted file mode 100644 index 88f260d..0000000 Binary files a/testfiles/office/pptx-objects.pptx and /dev/null differ diff --git a/testfiles/office/word-complex.docx b/testfiles/office/word-complex.docx deleted file mode 100644 index ed21fde..0000000 Binary files a/testfiles/office/word-complex.docx and /dev/null differ diff --git a/testfiles/office/word-simple.docx b/testfiles/office/word-simple.docx deleted file mode 100644 index e03d1c4..0000000 Binary files a/testfiles/office/word-simple.docx and /dev/null differ diff --git a/testfiles/pdf/einstein-dissertation.pdf b/testfiles/pdf/einstein-dissertation.pdf deleted file mode 100644 index ff9171b..0000000 Binary files a/testfiles/pdf/einstein-dissertation.pdf and /dev/null differ diff --git a/testfiles/pdf/ifg.pdf b/testfiles/pdf/ifg.pdf deleted file mode 100644 index fbaecd2..0000000 Binary files a/testfiles/pdf/ifg.pdf and /dev/null differ diff --git a/testfiles/pdf/nist-ai-rmf.pdf b/testfiles/pdf/nist-ai-rmf.pdf deleted file mode 100644 index b25cede..0000000 Binary files a/testfiles/pdf/nist-ai-rmf.pdf and /dev/null differ diff --git a/testfiles/pdf/nist-ssdf-genai.pdf b/testfiles/pdf/nist-ssdf-genai.pdf deleted file mode 100644 index f9bc373..0000000 Binary files a/testfiles/pdf/nist-ssdf-genai.pdf and /dev/null differ diff --git a/testfiles/zip/archive.zip b/testfiles/zip/archive.zip deleted file mode 100644 index 16da089..0000000 Binary files a/testfiles/zip/archive.zip and /dev/null differ diff --git a/tools/chemenu/tests/test_raw_derive_testfiles.py b/tools/chemenu/tests/test_raw_derive_testfiles.py deleted file mode 100644 index 8fcc3bd..0000000 --- a/tools/chemenu/tests/test_raw_derive_testfiles.py +++ /dev/null @@ -1,120 +0,0 @@ -"""`wikitool raw derive` against the real documents in `testfiles/`. - -The synthetic fixtures in `binary_formats.py` and `test_raw_derive.py` prove the filters on -documents built to be easy; the files in `testfiles/` are whatever the world produced. Every file -there is derived, compared with itself on a second run and accepted into `raw/`, and an optional -`.expect` next to it names the text that must come out. A distributed instance has no -`testfiles/`, so the whole module is then skipped. -""" -from __future__ import annotations - -import importlib.util -import shutil -from pathlib import Path - -import pytest - -from chemenu import filters -from chemenu.commands.raw.accept import raw_accept_command -from chemenu.tests.test_raw_derive import ( # noqa: F401 (tree is a fixture) - _derive, - _header_and_text, - tree, -) - -TESTFILES = Path(__file__).resolve().parents[3] / "testfiles" -SIDECAR = ".expect" - -pytestmark = pytest.mark.skipif( - importlib.util.find_spec("pypdf") is None or importlib.util.find_spec("olefile") is None, - reason="pypdf and olefile are needed for real PDFs, MSGs and archives holding them", -) - - -def _documents() -> list[Path]: - if not TESTFILES.is_dir(): - return [] - return sorted( - path - for path in TESTFILES.rglob("*") - if path.is_file() - and not path.name.startswith(".") - and path.name != "README.md" - and path.suffix != SIDECAR - ) - - -def _id(path: Path) -> str: - return path.relative_to(TESTFILES).as_posix() - - -def _texts(folder: Path) -> dict[str, str]: - texts = {} - for md in sorted(folder.rglob("*.md")): - lines = md.read_text(encoding="utf-8").splitlines() - texts[md.relative_to(folder).as_posix()] = "\n".join( - line for line in lines if not line.startswith("derived:") - ) - for tsv in sorted(folder.rglob("*.sheets/*.tsv")): - texts[tsv.relative_to(folder).as_posix()] = tsv.read_text(encoding="utf-8") - return texts - - -def _expected(document: Path) -> list[str]: - sidecar = document.with_name(document.name + SIDECAR) - if not sidecar.is_file(): - return [] - lines = sidecar.read_text(encoding="utf-8").splitlines() - return [line for line in lines if line.strip() and not line.startswith("#")] - - -def _put_and_derive(tree: Path, document: Path) -> Path: - (tree / "incoming" / document.name).write_bytes(document.read_bytes()) - _derive(f"incoming/{document.name}") - return tree / "incoming" / document.stem - - -@pytest.mark.parametrize("document", _documents(), ids=_id) -def test_a_real_document_derives_stably_and_is_accepted(tree, document): - assert filters.filter_for(document.name) is not None, ( - f"no import filter takes {document.name}; a file in testfiles/ must be one `raw derive` reads" - ) - - folder = _put_and_derive(tree, document) - assert not (tree / "incoming" / document.name).exists() - - if document.suffix.lower() == ".zip": - assert any(folder.rglob("*.md")), "an unpacked archive gets a .md beside its documents" - else: - original = folder / f"original{document.suffix.lower()}" - assert original.read_bytes() == document.read_bytes() - header, _text = _header_and_text(folder / "derived.md") - assert header["derived_by"] == "wikitool raw derive" - assert header["original_name"] == document.name - - first = _texts(folder) - for needle in _expected(document): - assert any(needle in text for text in first.values()), f"{needle!r} not in the derived text" - - shutil.rmtree(folder) - assert _texts(_put_and_derive(tree, document)) == first, "the same bytes gave a different text" - - raw_accept_command( - files=[Path(f"incoming/{document.stem}")], fidelity="verbatim", authority="reporting", - page=None, replaces=None, dry_run=False, - ) - assert not (tree / "incoming" / document.stem).exists() - - -def test_texts_covers_the_sheet_tsvs_beside_the_markdown(tmp_path): - (tmp_path / "x.sheets").mkdir() - (tmp_path / "derived.md").write_text("derived: now\nbody\n", encoding="utf-8") - cells = tmp_path / "x.sheets" / "01-a.tsv" - cells.write_text("a\tb\n", encoding="utf-8") - - before = _texts(tmp_path) - assert before["derived.md"] == "body" - assert before["x.sheets/01-a.tsv"] == "a\tb\n" - - cells.write_text("a\tc\n", encoding="utf-8") - assert _texts(tmp_path) != before