diff --git a/CHANGELOG.md b/CHANGELOG.md
index b62bd21b0..87c88a6d2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,8 @@ All notable changes to Bambuddy will be documented in this file.
## [0.2.5b1] - Unreleased
### Fixed
+- **FTP: P2S upload truncates / 426 "Failure reading network stream" on Python 3.13 (#1401, reported and root-caused by @iitazz)** — Reporter on a P2S running firmware 01.02.00.00 saw every Bambuddy-initiated print fail with the printer's on-screen "unable to parse 3mf file" error ~30 s in; downloading the file back off the printer's SD card confirmed it was truncated at exactly 7 × 64 KB (clean chunk-boundary cut). Initial #1417 follow-up tightened our 426 handling so we'd surface upload failures instead of silently dispatching a print of a partial 3MF — but that only stopped Bambuddy from hiding the problem; the actual upload still failed. The reporter then dug into it with Gemini and identified the real cause: Python 3.13's default `ssl.create_default_context()` negotiates TLS 1.3 when both peers support it, but the printer's vsFTPd build implements session reuse on the FTPS data channel against an old OpenSSL that doesn't tolerate TLS 1.3's asynchronous session-ticket model. The control-channel handshake completes, the data channel tries to resume the session, the resumption races, the data channel gets torn down mid-stream — first ~448 KB of bytes already in the TCP buffer land on the SD card, the rest never make it, printer's vsFTPd replies 426 instead of 226. **Fix** caps the SSL context's `maximum_version` to TLS 1.2 so session resumption is synchronous and the upload completes normally. Implementation follows the pattern just established by `camera_profiles.py` in the #1395 follow-up: a new `backend/app/services/ftp_profiles.py` module with an `FTPProfile` frozen dataclass (one field today, `cap_tls_v1_2: bool = False`) and a per-model registry. Default profile keeps the historical TLS-1.3 negotiation; P2S (display name + internal SSDP code N7) overrides with `cap_tls_v1_2=True`. `ImplicitFTP_TLS.__init__` gains a matching `cap_tls_v1_2` kwarg; `BambuFTPClient.connect()` looks up the profile and threads the flag through. **Deliberately scoped to P2S only** — X1C / P1S / H2D installs that work today stay on the negotiated TLS 1.3; flipping a future model to the capped path is a one-line entry in `_PROFILES` when a new reporter surfaces the same symptom. Considered but rejected the reporter's second proposed change (revert manual `transfercmd` + `sendall` back to `storbinary`) — the stated rationale ("raw sendall breaks OpenSSL 3.x framing") is incorrect (CPython's `storbinary` itself uses `sendall` internally; the actual socket-level behaviour is identical), the move to manual `transfercmd` was deliberate to dodge A1 hanging in `storbinary`'s synchronous `voidresp()`, and the #1417 SIZE-check escape for the "data is intact on the SD card despite the 426" race lives in the manual-transfer path — a switch to `storbinary` would lose that protection. **Tests**: 9 new in `test_ftp_profiles.py` (default profile doesn't cap; unknown / empty model falls back; P2S display name and N7 SSDP code both resolve to capped; lookup is case-insensitive; X1C / H2D / P1S / A1 stay uncapped; dataclass is frozen; **integration test pins the wiring** — `ImplicitFTP_TLS(cap_tls_v1_2=True)` actually sets `ssl_context.maximum_version == TLSVersion.TLSv1_2`, guards against a future refactor that drops the profile→context wiring while keeping the registry looking correct). 87 existing `test_bambu_ftp.py` tests still green; ruff clean.
+
- **Library 3D preview: complex multi-part 3MFs no longer freeze the page (#1412, reported by @anthonyma94)** — Reporter opened the 3D preview on a multi-color parted MakerWorld statue ("Mecha Mewtwo No AMS Multi Color Parted Statue") and the whole Bambuddy UI locked up — modal close button unresponsive, had to kill the tab. Root cause was in `frontend/src/components/ModelViewer.tsx`: the 3MF parse runs entirely on the browser main thread (JSZip extract + DOMParser + `getElementsByTagName('vertex')` / `('triangle')` iteration + `mergeGeometries`), with no yield points between iterations. Bambu Studio's external-component shape (`` per part) compounds this — each component triggers another async file extract + DOM parse + vertex/triangle loop, all chained without surrendering control to the event loop between phases. For trivial models (the towel hook and Bambu scraper the reporter cited as working) the total wall-clock is short enough that the freeze isn't visible; for parted statues with dozens of components and high-poly meshes, the main thread is pegged for tens of seconds → browser shows "page unresponsive" and the close button can't fire. **Stopgap that shipped here** adds explicit `nextTick()` yields (`await new Promise(r => setTimeout(r, 0))`) at four hot spots: every 20 000 vertex iterations, every 20 000 triangle iterations, once per top-level `