- Web search with five services in turn, no key needed, pages read like a reader view
- A long video renders in the background, videos made in the chat play there, Wan 2.1 saves mp4
- A graphics card per task with two cards, AMD on Windows gets the current ROCm build, Intel Arc its own
- The LU Engine plans memory from the model file, says what a long context costs, keeps one copy of the model
- Settings from the chat with a confirmation card, Storage puts models, gallery and runtime in one folder
- Remove refusals from a local model in the Model Manager
- Native HiRes in Edit, LoRA stack with search and strength from -20 to 20
- Ask in Code asks before every external tool, REA shows only in Agent and Code
- Your phone on this computer (Remote), paired by QR code
- Wan 2.2 14B runs from Animate and Video, Train new on a Mac shows the trigger word field
- Temperature is held to 1.5, memories can be deleted, the onboarding runs once
- GitHub issues 187, 188, 190, 191, 193 and 194
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A trained character works in every part of Create and in chat, and a Mac can train one
- ComfyUI workflows import in the format ComfyUI saves, with subgraphs and the node packs they need
- Edit takes more reference images, as many as the graphics memory holds
- A chat can be duplicated or branched, edits keep the old answer as a version
- Character cards become personas, personas carry sampling and a reminder
- Several OpenAI-compatible servers side by side, group members that sit out
- Auto compact is on by default, digits and encoded text are counted at their real cost
- How it works: short films for the main parts of the app
- On a Mac, Create gains Talking Character, Motion Control, Music, Enhance Image, Remove Background and masks
- Reasoning effort and a reply timeout for local servers, the REA plugin for Agent and Code
- ComfyUI shared model folders are used, AMD and Intel cards get a clearer GPU path
- GitHub issues 156, 162, 165 to 177, 180, 181 and 183
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Voice (Beta): talk to LU and hear it answer
- Computer control (Beta): a model works in the apps you approve, off by default
- Local models with image input download with their image part
- Create in chat (Beta): ask for an image or a clip in a Cloud chat
- Ten more chat models on LU Cloud
- A running task says what it is doing, and dialogs have a new look
- The Linux packages carry the voice engine
- Ten security fixes in the new features
- A microphone field in the settings, and seven fixes from user reports
Co-authored-by: Alan Vo <101240385+ALANDVO@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The head of ComfyUI-RMBG (3.2.0, 30.09.2026) no longer loads on Windows:
its SAM3 node imports triton, which exists for Linux only, and since
54e62337 the pack's own loader dies on the first failing node file, so
ComfyUI reports IMPORT FAILED for the whole pack and has no RMBG node.
The registry now pins the pack to 58f1947a (21.08.2026), seen listing RMBG
on the Windows box. install_custom_node takes the pin, puts a fresh clone
and a clone that ran ahead on that commit, and never pulls a pinned pack.
Deleting the pin sends the next install back to the default branch.
When a pack still does not register its node, the message carries the
line ComfyUI printed about it instead of pointing at a console the app
does not have.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Files (applejames, Discord): the paperclip, drag and drop and paste now take
any file, not only images. The model receives a description inside the user
message: name, type read from the signature, size, SHA-256, a hex dump of
the first 256 bytes and the readable strings within a fixed budget. In Agent
and Code mode the file is also copied into the working folder through the
new chunked fs_write_bytes command, so the file tools can open it. Up to 3
files of 64 MB each. The history keeps the description and the chip, never
the bytes. The notice that turned non-images away is gone; documents still
point to Document Chat.
Group chat (samvenice, Discord): every participant can speak as its own
persona, picked on its row in the Plugins menu and stored on the
conversation. Each one gets its own system prompt that names it and the
others, hears the others under their persona names, and a line written in
another participant's name is cut. Without a pick a group behaves and is
worded exactly as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI typechecks e2e and runs the dev-server tests, which the local gate for
3.0.4 left out: four new specs passed platform as a string, and
local_api_usage and set_download_limit had no endpointMap entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Create (Discord, 25. bis 28.09.2026):
- Die Modellfamilie kommt aus dem safetensors-Kopf statt aus dem
Dateinamen (commands/model_sniff.rs), der Lader folgt dem Ordner, in dem
die Datei liegt. Eigene Pipelines fuer Chroma, HiDream I1, SD 3.5,
Lumina 2, Qwen-Image und Qwen-Image-Edit (api/family-components.ts,
dynamic-workflow.ts).
- Ein fehlender Text-Encoder oder VAE wird nach einer Frage geladen, ein zu
altes lokales ComfyUI nach einer Frage aktualisiert (lib/render-fixups.ts,
RenderFixupModal.tsx).
- Lose VAE- und Encoder-Dateien erscheinen nicht mehr als Modell.
- Edit behaelt die Quelle auch bei Staerke 1.00, der Regler endet bei 0.95.
- Galerie: Ziehen in Edit, Loeschen verschiebt die Datei in den Papierkorb
(commands/gallery_files.rs, Crate trash), dunkle Kacheln laden wieder,
sobald ComfyUI antwortet.
GitHub:
- 139: Der Composer misst an einer Kopie in einem geschlossenen
Shadow-Root, statt das Feld bei jeder Taste auf auto zu kollabieren
(lib/fit-textarea.ts).
- 140: Beide Stop-Knoepfe im Code-Reiter bekamen das Klickereignis als
Konversation. Ein vorzeitig beendeter /loop-Durchlauf gibt Leiste und
Arbeitsordner frei. Eine Ablehnung wegen Kontextgroesse kuerzt den
Verlauf und sendet den Schritt neu (hooks/codex/context-overflow.ts).
- 141: Die Liste der letzten Chats in der eingeklappten Leiste folgt dem
Modus.
- 142: Ganzkoerper ohne Groessenangabe bekommt das Hochformat
(lib/subject-framing.ts).
- 143: ComfyUI auf einem anderen Rechner. Downloads landen im
Model-Storage-Ordner in ComfyUIs Ordnerstruktur, die App sagt, was dort
noch zu tun ist.
Das CI-Tor lokal nachgefahren und drei Blocker behoben: Playwright griff
die Messkopie aus 139 im offenen Shadow-Root (jetzt geschlossen), ein
Importzyklus createStore > gallery-trash (jetzt lokaler Typ), sieben
Clippy-Funde mit rustc 1.98.1 (chat_attachments.rs, model_sniff.rs).
E2E auf Windows 11 (lu-box, 28.09.2026) bestanden, der Kontext-Retry aus
140 nur per Unit-Test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Jedes Bild lag inline im gespeicherten Chatverlauf. Ein paar Chats mit
Handyfotos machten den Verlauf so gross, dass die App aus dem Speicher
lief, und ueber 127 MiB lehnte WebView2 das Speichern ganz ab ("The
serialized keys and/or value are too large"). Ab da scheiterte jedes
Speichern still, und neuere Chats standen hoechstens noch in
store_backup.json.
Bilder liegen jetzt als eigene Dateien ausserhalb des Verlaufs
(commands/chat_attachments.rs, lib/chat-attachments.ts). Die Verweise
werden erst an der Anbietergrenze aufgeloest, nach dem Kuerzen des
Kontexts. Ein alter Verlauf zieht beim ersten Start einmal um. Bis dahin
haelt lib/backup-hold.ts das native Backup an, damit es nicht drei weitere
Kopien des groessten Werts anlegt. lib/chat-backup-recovery.ts holt einmal
zurueck, was nur das Backup noch hatte, und belebt keinen geloeschten Chat
wieder. Vorschaubilder laden nur nahe am sichtbaren Bereich
(ChatAttachment.tsx), und der Bildeingang liest die Abmessungen vor dem
Dekodieren (lib/chat-image-input.ts).
E2E auf Windows 11 (lu-box, 28.09.2026) bestanden.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Final review of 0716bb49 sent it back with five blockers and four small
points; this addresses all nine.
- B1: the running-instance guard now stops its own ComfyUI through
stop_comfyui_blocking, the same function Stop uses (taskkill /T /F on
Windows), instead of a bare child.kill() that could leave a grandchild
holding the port while the checkout underneath it changes. The port is
probed again after the stop; a ComfyUI that does not die refuses the
update instead of proceeding blindly.
- B2: own-vs-foreign no longer keys off processAlive, which reads LU's own
ComfyUI as foreign after every app restart (the T-68 orphan case).
comfyui_status gains an ownedByApp field backed by a new pure decision,
classify_comfyui_ownership, shared by the status panel and the update
guard so they cannot disagree about the same ComfyUI.
- B3: before stopping anything, the guard reads ComfyUI's own /queue and
refuses if a render is running or pending, instead of killing it mid-work
with no warning.
- B4: the confirm dialog now says plainly that LU stops its own running
ComfyUI first, and that a ComfyUI this app did not start blocks the
update instead.
- B5: git status --porcelain now ignores LU's own venv.lu-old-*/
venv.lu-new-*/venv.lu-failed-* rebuild siblings when judging whether the
checkout is clean, and the refusal message names the actual dirty files
instead of blaming the customer for LU's own leftovers.
- K2: a ref guard stops two fast clicks on the dialog's own Update button
from calling update_comfyui twice.
- K3: the own/orphan/foreign decision is now the pure, tested
classify_comfyui_ownership function instead of living untestably inside
the guard.
- K4: the guard now peeks the tracked child handle once and delegates the
actual stop to stop_comfyui_blocking's own single lock, instead of two
separate lock/unlock cycles with a race window between them.
- K1: acknowledged in the release line added separately, which now says
only what the code does (rolls the code back on cancel, does not promise
a clean rollback of already-installed packages).
Rust: 1592 tests green (was 1585), cargo clippy -D warnings clean. Frontend:
comfy-update-confirm-dialog.test.ts covers B4's wording, K2's double click,
and ownedByApp replacing processAlive in both directions.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
R2-44 added invokeCode's abort handling, which calls the Tauri-only
execute_code_cancel command by callId (mirrors shell_execute_cancel).
mlx-media-endpoint-coverage.test.ts's reachability scan does not know
about the isTauri() gate around it, so it flagged this as a newly
unmapped command. It needs no endpointMap entry or dev-server route,
same as shell_execute_cancel two lines below it: added to the same
documented pre-existing-gap list with a short cross-reference comment.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Punkt 7: lanOrigins() war eine lokale Closure in configureServer, der
Test baute eine eigene Kopie nach statt den echten Code zu pruefen. Jetzt
als benannte Funktion exportiert (LanOriginServer strukturell typisiert,
damit Tests ein einfaches Objekt statt eines echten ViteDevServer bauen),
lan-origin-k8.test.ts ruft sie direkt auf.
Punkt 6: beim Start unter --host gab es keine Meldung, dass damit
/shell-execute und /execute-code (beide hinter /local-api) aus dem LAN
erreichbar werden. hostWarningMessage() ist die reine, testbare Haelfte
der Warnung, devServerPlugin gibt sie einmal aus, sobald der HTTP-Server
tatsaechlich lauscht (httpServer.once('listening', ...)), damit
resolvedUrls/host-Zustand schon final sind.
Negativkontrollen gefahren: lanOrigins auf immer-true gesetzt liess 1 von
11 Tests fehlschlagen; hostWarningMessage auf immer-null gesetzt liess 2
von 3 fehlschlagen. Beide danach wiederhergestellt und gruen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Nachbesserung nach Haiku-Pruefung: Merge-Blocker, 77 hinzugefuegte Zeilen
mit Gedankenstrichen (U-2013, U-2014) in Kommentaren und Testnamen.
Saetze umgebaut statt Zeichen getauscht. Diese Commit deckt die reinen
Kommentar/Testname-Dateien ab, die verbleibenden Treffer stecken in
Dateien mit inhaltlichen K2/K8-Fixes und werden mit deren Commits
mitgefuehrt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
npm run dev --host rejected its own onboarding writes with "403
Forbidden: Invalid Origin (CSRF Protection)" (GH #134): the page loaded
from the machine's LAN IP (e.g. http://192.168.1.23:5273), which is
neither tauri://localhost nor loopback, so a deliberately LAN-exposed
server hit the same wall as a real cross-origin attacker.
createLocalApiGuard(port) now takes an optional getLanOrigins callback,
called PER REQUEST (never cached at guard-creation time, since Vite's
resolvedUrls.network only populates after listen() completes) and
returning only origins the SERVER itself resolved it is bound to
(server.config.server.host + server.resolvedUrls.network in
dev-server/index.ts) - never anything derived from a request header.
That preserves the exact DNS-rebinding lesson the Origin check's own
comments already document: a value the caller supplies (Host header)
must never be what authorizes the request. Omitted (or empty) when
--host was not passed, so a plain npm run dev is unaffected.
The GH issue also named a second break in the same LAN-exposed session
(auto-detect settling on "not found" for a running, reachable ComfyUI):
find_comfyui was already mapped in endpointMap but pointed at a
dev-server/comfy.ts route that did not exist, and
detect_all_comfyui_installs (ComfyStep.tsx's primary auto-detect call)
had neither a mapping nor a route. Both /local-api/find-comfyui and
/local-api/detect-all-comfyui-installs now exist in comfy.ts and are
mapped in backend.ts, independent of --host.
Test: dev-server/__tests__/lan-origin-k8.test.ts (guard, 11 cases),
dev-server/__tests__/find-comfyui-k8.test.ts (routes, 6 cases), both new.
Negative control (guard): replaced the isOwnLan computation with false,
reran lan-origin-k8.test.ts - 2 of 11 tests failed, restored, 11/11
green again. Negative control (routes): renamed both route paths with a
-DISABLED suffix, reran find-comfyui-k8.test.ts - failed entirely
("kein Handler fuer /local-api/find-comfyui"), restored, all green again.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
install_mlx_diffusion / install_mlx_diffusion_status threw "Unknown
backend command" under npm run dev (GH #135): the MLX image/video Rust
module (commands::media_cmds) had no dev-server route at all, and
unlike Remote Access (remote-stubs.ts) no frontend pre-guard either -
every invokeMedia(...) call in src/api/mlx-image.ts / mlx-video.ts is
unconditionally reached the moment MlxMediaSettings renders on a Mac.
Measured the actual extent of the gap with a script comparing every
registered Tauri command (src-tauri/src/main.rs) against every literal
command name passed to backendCall(...)/invokeMedia(...) in src/, then
against endpointMap's keys - the same method is now a live test
(mlx-media-endpoint-coverage.test.ts) so a future call site that gets
added without an endpointMap entry fails it instead of shipping
silently. Fixed the MLX/video module (22 commands, the one this issue
named and its full sibling set) with the same desktop-only-stub pattern
install_tts already uses (dev-server/mlx-media-stubs.ts, mirrors
remote-stubs.ts): an honest "desktop only" status instead of a raw
Unknown-command throw.
Deliberately NOT a fix for the ~85 other legitimately-Tauri-only or
never-reported call sites the same script found (keychain, native file
dialogs, OAuth loopback, window management, ...) - inventing dev-mode
behavior for features nobody reported broken would be guessing, not
fixing. That remaining set is named explicitly in the test's
PRE_EXISTING_UNMAPPED_GAPS with a self-check that it never goes stale,
so it stays visible instead of silently ignored; a few entries
(local_api_status, restart_remote_server, revoke_remote_memory) look
like real drift worth a closer look by the orchestrator/David.
Test: dev-server/__tests__/mlx-media-endpoint-coverage.test.ts (new).
Negative control: removed the install_mlx_diffusion endpointMap entry,
reran the test - 2 of 4 tests failed, restored, 4/4 green again.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Der Grund, warum die Unterauftraege leer zurueckkamen und der Hauptagent
danach eine Tabelle erfand. Zwei Ursachen, beide heute nachgemessen:
1. DuckDuckGo antwortet auf die HTML-Endpunkte mit HTTP 202 und einer
Bot-Sperrseite — 14 kB, kein einziges Ergebnis, sowohl html. als auch
lite.. Die freie Web-Stufe ist tot; das kann dieser Commit nicht
reparieren, nur benennen.
2. Die Stufe darunter, ueber der im Code 'always works' stand, fragte
fest en.wikipedia.org:
en.wikipedia 'Transparenzgesetz Hamburg Antragsfristen' → 0
de.wikipedia 'Transparenzgesetz Hamburg' → 31
Fuer einen deutschsprachigen Nutzer hatte die Kette damit gar keine
funktionierende Stufe mehr.
Die Wikipedia-Stufe entscheidet jetzt anhand der Frage, welche Sprachwiki
zuerst drankommt — und versucht die andere trotzdem noch, denn eine
Vermutung darf nichts verschliessen. Dazu kuerzt sie von hinten: Wikipedia
UND-verknuepft die Begriffe, und der letzte ist ueblicherweise die engste
Einschraenkung. Nie unter zwei Begriffe, hoechstens vier Versuche — die
letzte Rettungsstufe darf keine Suchmaschine werden.
Dabei fiel ein Fehler heraus, der ohne die erste Aenderung nie aufgefallen
waere: die Trefferadressen wurden fest mit en.wikipedia.org gebaut. Ein
Treffer aus de.wikipedia unter einer en-Adresse ist ein 404, das wie ein
Beleg aussieht — nachgemessen: alter Link 404, neuer 200.
Und die Fehlermeldung sagt jetzt, was man tun kann, statt nur dass es
nicht ging: ohne Schluessel weist sie auf Brave/Tavily unter Settings →
Agent → Search Provider hin. Die Persona konnte aus 'All search tiers
failed' nicht schliessen, dass ein Schluessel die Kette wieder ganz macht
— das Modell konnte es auch nicht.
Beide Oberflaechen, gleiche Regel: Dev-Server (TypeScript) und die
gepackte App (Rust). Am laufenden Dev-Server nachgeprueft: vorher
{"results":[]}, jetzt echte deutsche Treffer mit Adressen, die aufgehen.
tsc 0 · 8781 gruen · cargo test 868 gruen.
Nicht der Kaefig war kaputt, sondern der Ort, an dem die Tests ihre Kulisse
aufstellten: `os.tmpdir()` liegt unter Windows in `$HOME\AppData\Local\Temp`,
und `AppData` steht in `forbiddenRootPrefixes`. Das ERSTE Tor lehnte die
Wurzel mit 403 ab, bevor irgendein Pfad geprueft war — fuenf 403-Faelle waren
dort also gruen, ohne die Aussage zu treffen, die ihre Ueberschrift verspricht.
`kaefig-kulisse.ts` biegt HOME und USERPROFILE fuer die Dauer EINER Anfrage
auf ein Wegwerf-Heim um. Damit liegt der Arbeitsordner per Konstruktion im
Normalfall, fuer den der Kaefig geschrieben ist — keine Plattform-
Fallunterscheidung, kein skipIf, auf beiden Maschinen dieselbe Aussage. Das
Heim ist Pflichtargument und steht vorne: die drei Aufrufstellen, die es als
optionales drittes Argument vergassen, waren genau die roten.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
KF-12, reproduziert statt vermutet. `POST /local-api/fs-write` mit dem
Koerper `{}` antwortete 200 "saved" und machte aus der Kaefigwurzel eine
0-Byte-DATEI. Kein Ausbruch — der Pfad liegt im Kaefig — aber ein
Schreibvorgang dort, wo eine Wurzel hingehoert.
Die schaerfere Form stand im Befund noch nicht drin und ist die eigentlich
unangenehme: `{"workingDirectory":"<ordner-der-noch-nicht-existiert>",
"content":"..."}` legte diesen Ordner als Datei MIT DEM INHALT DES AUFRUFERS
an. Der Aufrufer bestimmt also Ort und Inhalt. Die Quelle solcher Anfragen
ist real: `executeFileWrite` reicht `args.path` des Modells ungeprueft
weiter, und laesst das Modell das Feld weg, faellt es bei JSON.stringify
aus dem Koerper.
Alle sechs fs-Tueren mit `{}` gemessen: fuenf lesen und legen nichts an,
nur die sechste schreibt und geriet in die Lage. Jetzt weist sie mit 400 ab
— nicht 403, weil 403 in dieser Datei genau eine Bedeutung hat
(JailEscapeError, "der Pfad verlaesst den Arbeitsordner"), und die trifft
hier nicht zu. 400 ist, was die Geschwister fuer eine unbrauchbare Angabe
ohnehin antworten. Geprueft wird der AUFGELOESTE Pfad, nicht der String:
Rusts Textpruefung kennt "" und "." aber nicht "unterordner/..".
Ausdruecklich NICHT gewaehlt: "die Wurzel anlegen statt sie zu beschreiben".
Der normale Weg tut das bereits (`{"path":"notiz.txt"}` legt die Wurzel als
ORDNER an) — eine Anfrage ohne Datei haette davon nichts und bekaeme nur ein
stilles zweites Verhalten.
KF-13: die zwei wirkungslosen Port-Eintraege der Origin-Liste sind weg, die
weitere Loopback-Regel bleibt unveraendert — es wurde nichts verengt. Der
Port hat jetzt die einzige ehrliche Wirkung, die ihm bleibt: er steht im
Text der Ablehnung, die damit ihre eigene Liste aufzaehlt. Waechst sie wieder
um einen toten Eintrag, steht er in der Antwort — aus einer Textprobe wird
eine Verhaltensprobe. Zwei falsche Zusicherungen in Kopfkommentaren
mitkorrigiert.
Bemerkenswert am Vorgehen des Agenten: seine eigene Sonde D fand einen
Sproedigkeitsfehler in seiner eigenen Pruefung (sie hing am Namen
`allowedOrigins`), bevor sie eingecheckt war.
Meine Nachpruefung: vitest dev-server 85/85 gruen, Gesamtlauf ohne
Regression. Der Test prueft BEIDE Richtungen — die Wurzel wird beim
normalen Schreiben als Ordner angelegt, tiefe Unterordner funktionieren,
und `fs-list` mit "." bleibt erlaubt. Eine Sperre, die auch Erlaubtes
abweist, waere schlimmer als der Befund.
Gemeldet und NICHT mitrepariert (folgt als eigenes Paket): dasselbe Loch
steckt im Rust-`fs_write` (`filesystem.rs:637`) — dort ist
`is_workspace_root_path` vorhanden und wird in `fs_list` benutzt, in
`fs_write` nicht. Selbst nachgesehen und bestaetigt: resolve_path ->
create_dir_all(parent) -> write_atomic(full), ohne Wurzelpruefung. Das
betrifft den AUSGELIEFERTEN Build, nicht nur den Dev-Server.
`vite.config.ts`: **2536 -> 141 Zeilen**. Der Dev-Server liegt jetzt in
`dev-server/` (17 Module) mit 44 eigenen Tests. Die Build-Konfiguration eines
Projekts ist die eine Datei, die jeder anfassen muss und niemand testen kann;
darin steckte ein vollstaendiger HTTP-Server mit Prozessverwaltung.
Die Schnitte folgen GETEILTEM ZUSTAND, nicht Zeilenzahl — `comfy.ts` haelt
einen veraenderlichen Kindprozess samt Logpuffer, `downloads.ts` eine Map
laufender Downloads, `whisper.ts` Prozess und Antwortschlange, `web-search.ts`
einen Verfuegbarkeitsstatus, den Status, Installer und Suchkette teilen. Was
zwei Unabhaengige brauchen (`ssrf.ts`, `python.ts`, `paths.ts`), liegt
daneben statt in einem von beiden. `index.ts` traegt nur noch die Reihenfolge.
Der Port ist ein Parameter: `LU_DEV_PORT` -> `devServerPlugin({ port })`.
Ein Server, dessen Port fest verdrahtet ist, laesst sich nicht zweimal
starten und damit auch nicht pruefen.
Die 15 Lint-Fehler der Datei sind bezahlt, nicht unterdrueckt: 12
`require()`, von denen jedes einen Import SCHATTETE, der oben in derselben
Datei schon stand, und 3 leere `catch` mit dem Grund im Block (Hausstil,
Vorbild `src/api/discover.ts:657`). eslint . faellt damit von 60 auf 45 —
die verbleibenden liegen alle in `src/components`.
Geprueft im eigenen Worktree auf HEAD (nur diese Dateien, node_modules
kopiert statt verlinkt): tsc -b 0 · vite build 0 · madge 0 Zyklen ueber
`src` UND `dev-server` · eslint 45 · vitest 7907 gruen / 0 rot.
Zwei eigene Gegenproben zusaetzlich zu den 12 Sonden des Agenten:
1. ENDPUNKT-PARITAET, mengenweise statt gezaehlt: alle `/local-api/`-Pfade
aus HEADs vite.config.ts gegen alle aus dev-server/ — 46 zu 46, und die
Differenz ist in BEIDE Richtungen leer. Kein Endpunkt verloren, keiner
erfunden.
2. Die REIHENFOLGE, die der Agent als folgenlos meldet. Drei Pfade sind
Praefix eines anderen (`download-model` < `download-model-to-path`,
`fs-read` < `fs-read-bytes`, `transcribe` < `transcribe-status`) — dort
waere ein zu weit greifender Mount ein echter Fehler. Am laufenden Server
auf Port 5401 gemessen: `/transcribe-status` -> **200** (eigener Handler),
`/transcribe` -> 405 (nur POST). Haette der Praefix-Mount zugegriffen,
stuende dort 405. Ohne CSRF-Kopfzeile -> 403, die Sperre greift.