grosse-dateien
Große und merkwürdige Dateien in zid (Binärdateien, Riesenzeilen, 5-MB-Dateien, Glyph-Batching, Spalten als Codepoints), E2E-Betrieb/Windows-Eigenheiten, plus die Use-after-free-Regel für Render-Comma
SKILL.md.claude/skills/grosse-dateien/SKILL.md
GitHubGroße und merkwürdige Dateien (Binärdateien, Riesenzeilen)
gpu_renderer.renderTextarbeitet mit Stack-Puffern vonglyph_layout.max_batch_glyphs(256) Glyphen.shapeTextIntoliefert für Runs über 256 Glyphen einen owned Heap-Slice beliebiger Länge; die Glyphen werden deshalb blockweise verarbeitet, die Stiftposition läuft über Blockgrenzen weiter (glyph_layout.computeGlyphDevicePositionsgibt sie zurück, unit-getestet).- Headless rendert keinen GPU-Text (
renderFrameWithTextläuft nur mit Fenster). Glyph-Logik nachsrc/text/glyph_layout.zigziehen und dort unit-testen (types.zigimportiertplatform/mod.zig(wio), deshalb nimmt das Modul die Glyphen alsanytype). Ob die Text-Strings der Render-Commands überhaupt noch gültig sind, prüft headless die Text-Probe (siehe „Use-after-free in Render-Commands“). - Binärdateien öffnen keinen Buffer:
file_types.detectFileKind(Endung zuerst, dann die ersten 1024 Bytes durchlooksBinary, Heuristik wie Zedsanalyze_byte_content: bekannte Header, NUL-Anteil ≥ 1/16, sonst ≥ 8 % nicht textartige Bytes; unit-getestet) liefert.binary, der Tab zeigt nurbinary_view.zig(Name, Größe). Bewusst ohne „Trotzdem öffnen“ (VS Code hat das, Zed nicht). UTF-16 gilt als binär, es gibt keinen Decoder. Bild/PDF entscheidet weiter die Endung. Eingaben auf einem Binär-Tab gehen wie bei Bild-Tabs an den Editor des vorherigen Buffers — bekanntes Verhalten (Tastatur-Fokus pro Tab-Art ist nicht umgesetzt). - Textstücke über
ShapedRunCache.MAX_TEXT_LEN(2048 Bytes) passen nicht in den Shape-Cache und gehen ungecacht über den Heap (shapeTextInto→shapeText); erst übermax_draw_run_bytes(256 KiB, Schutz gegen kaputte Zeiger) werden sie verworfen, gezählt (ui_state.text_runs_dropped) und einmal geloggt. Der Editor gibt pro Zeile trotzdem nur den sichtbaren Spaltenausschnitt an Clay (CodeEditor.visibleSliceOf: abview.col,view.cols + 2Spalten;view.colskommt ausvisibleColCount= Editor-Breite minus Gutter durch Zeichenbreite). Highlight-Tags, Auswahl und Cursor rechnen mit dem Byte-Offset des Ausschnitts.View.clamp(flow-core) ziehtview.coldem Cursor nach; Shift+Mausrad bzw.scroll_horizontalscrollt Spalten (scrollColumns). RPCeditor_stateliefertview_col/view_cols. - Spalten sind Codepoints (Tab = 4).
CodeEditor.metrics.egc_lengthliefert die Länge der UTF-8-Sequenz bei einer Spalte;cursor.col,view.col,find_ops,wrap_ops,renderRowOverlaysund die LSP-Positionen rechnen alle so. Bytes bekommt man nur überget_line_width_to_pos(Spalte → Byte) undpos_to_width(Byte → Spalte); tree-sitter-Edits (pushEditForChange) und die Highlight-Tags sind Byte-Offsets. Wer Bytes als Spalten zählt, zerschneidet UTF-8-Sequenzen (Cursor, Tippen,visibleSliceOf; Symptomwarning(shaper): invalid UTF-8 text). Wer Metriken schreibt, mussreparseFromBuffer(flow-core) im Blick behalten: tree-sitter meldet Byte-Spalten, der Wrapper dort liest byteweise, unabhängig von der Spaltendefinition. python3 scripts/e2e_odd_files.pylegt untertmp/eine 3-KB-Binärdatei ohne Zeilenumbruch, eine 5000-Zeichen-Zeile und eine 5-MB-Datei an, öffnet sie headless, tippt und misst die Latenz bis das Zeichen ineditor_statesteht (gemessen 0,06 s bei der langen Zeile, 1,2–2,4 s bei der 5-MB-Datei); die Binärdatei muss als Tab-Artbinaryohne Buffer erscheinen. Logs mit Binärinhalt nur mitgrep -alesen, sonst schweigt grep.- Der RPC-Socket bindet nur mit SO_REUSEADDR, nie mit SO_REUSEPORT (
Address.listenmitreuse_addresssetzt beides). Sonst lauscht eine verwaiste Instanz weiter, der Kernel verteilt die Verbindungen, und ein Teil der RPC-Antworten kommt aus dem alten Prozess mit altem Zustand. Ein zweiter Start meldetPort 9999 ist belegt. - E2E-Skripte starten zid nie über
zig build run, sondern überstart_zidausscripts/e2e_open_folder.py: erstzig build, dann das Binary als direktes Kind. Beizig build runist zid ein Enkel, einkillauf zig lässt zid auf dem Port zurück; Prozessgruppen (start_new_session/killpg) als Ausweg gibt es unter Windows nicht.stop_zidbeendet per RPC, notfalls hart. - Nicht alle Suiten bauen selbst. Nur wer
start_zidnimmt, ruft vorherzig buildauf.e2e_scm_changes.py,e2e_scm_graph.py,e2e_timeline.py,e2e_git_diff.py,e2e_lsp.py,e2e_editor.py,e2e_picker.pyu. a. startenzig-out/bin/ziddirekt: vor dem Laufzig build, sonst testet die Suite still das alte Binary. - Suiten nacheinander, nie parallel — auch nicht als zwei Hintergrund-Tasks. Alle nutzen Port 9999; ein zweiter Lauf redet mit der Instanz des ersten, und beide scheitern ohne erkennbaren Grund.
- Zeitfehler erst messen, dann erklären. Kommt ein Tooltip oder Toast zu spät, zuerst mit einem 100-ms-Polling-Probe die echte Dauer bestimmen, nicht am Timeout des Tests schrauben.
- Fenster nur nach Rückfrage. Auch Reproduktionen und Messungen laufen
--headless. Braucht ein Befund zwingend ein Fenster (Present, Swapchain, DPI, Maximieren), das begründen und den User fragen oder ihn selbst starten lassen und das Log auswerten. Frame-Vergleiche per Screenshot-RPC zeigen kein Present-Flackern, das nur am Monitor sichtbar ist. - Meldungen aus dem Fenster am echten Dokument nachstellen. „Funktioniert nicht" hat oft eine
andere Ursache als vermutet. Die Datei des Users aus dem Log holen (die
Ausgabe von
zig build runzeigt geöffnete Pfade), headless öffnen, Zustand per RPC messen, notfalls in Zeilenbereiche schneiden, bevor eine Theorie entsteht. Ein Test muss den Effekt an dem prüfen, was der User sieht (Fliesstext), nicht nur an einem Sonderfall (Codeblock). - Verwaiste Headless-Prozesse: Linux
pkill -f '[v]ulkan-ed --headless'— ohne die Klammer trifft das Muster die eigene Shell, die den Befehl enthält. Windows: nietaskkill /IM zid.exe— das beendet auch die Fenster des Users samt ungespeicherter Änderungen. Nur Headless-Instanzen gezielt per PID:Get-CimInstance Win32_Process -Filter "Name='zid.exe'" | ? CommandLine -match '--headless' | % { Stop-Process -Id $_.ProcessId -Force }. - Windows: Die Suiten laufen headless genauso (
python scripts/e2e_*.py, kein Fenster). RPC-Antworten mit Pfaden immer überstd.json.fmt/Stringifybauen, nie"{s}": Backslashes ergeben sonst ungültiges JSON und jede Suite scheitert beim erstenui_state. Die Text-Probe (memfd) und der Test auf unlesbare Dateien (chmod 0) greifen nur unter Linux;e2e_symlink_dir.pynimmt ohne Symlink-Recht eine Junction (mklink /J). - Explorer sortiert nicht: Reihenfolge ist die des Dateisystems (ext4 Hash-Reihenfolge, NTFS
alphabetisch). Tests navigieren deshalb per berechneter Anzahl ↑/↓ (
cursor_toine2e_explorer.py), nie mit fest angenommenen Nachbarn. - File-Watcher Windows (
src/async/file_watcher_win.zig):ReadDirectoryChangesWauf die Wurzel mit ganzem Baum, überlappende I/O mit Event (Stop-Flag alle 100 ms). Gleiche Ergebnisse und Filter wie Linux (versteckte Pfadteile,zig-out,node_modules,.gguf, 100-ms-Dedupe). Atomares Speichern (Rename) meldetfile_created, nur Überschreibenfile_changed— wie inotify.src/async/file_watcher.zigist ein alter, nicht eingebundener Stub.bWatchSubtreefolgt keinen Reparse-Points: der Watcher-Thread sucht deshalb nach dem Start Symlink-/Junction-Ordner mit Ziel außerhalb der Wurzel (GetFileAttributesW, dennDir.iteratemeldet sie als.directory) und öffnet je Ziel ein eigenes Handle (höchstens 63), Ereignisse kommen unter dem Link-Pfad; gewartet wird mitWaitForMultipleObjects. Ziele innerhalb braucht es nicht (realpath inbufferKeyForPath). Nicht abgedeckt: Links, die nach dem Start entstehen, und Links innerhalb eines Link-Ziels. E2Ee2e_external_change.py. - Testdaten der Suiten kommen aus
scripts/fixtures/undscripts/e2e_fixtures.py(PDF, PNG werden erzeugt), nicht aus dem ignoriertentest_data/. Git-Fixtures löschte2e_open_folder.rmtree(setzt Rechte auf.git/objects, sonst bleibt das Fixture unter Windows still stehen); die Suiten stellen stdout auf UTF-8 (Pfeile in Meldungen). - Zeilenenden: Git for Windows setzt systemweit
core.autocrlf=true. Neue Git-Fixtures in Zig-Tests und Suiten setzencore.autocrlf false, Python-Suiten schreiben Dateien mitnewline="\n". - Umgebungsvariablen nur über
src/platform/env.ziglesen, niestd.posix.getenv— das gibt es unter Windows nicht, und der Windows-Build bricht still, weil hauptsächlich unter Linux entwickelt wird. Bei neuen Windows-Buildfehlern zuerstzig version(muss 0.15.x sein) und die MuPDF-Archive prüfen, bevor Code angefasst wird (README, Abschnitt Windows). - Ein langsamer Test lässt sich unter Windows nicht mit
timeoutaus Git Bash begrenzen (dort teils gesperrt,Permission denied); Runner in Python mitsubprocess.run(timeout=). - Terminal unter Windows (ConPTY,
src/terminal/conpty.zig): die Shell (cmd.exeaus COMSPEC) startet mitSTARTF_USESTDHANDLESund leeren Std-Handles. Ohne das erbte sie zids stdout, sobald der umgeleitet war (Log-Datei der E2E, Pipe), schrieb Prompt und Ausgabe dorthin und der Terminal-Tab blieb leer.terminal_state.screenzeigt den Bildschirmtext; Suiten nehmen unter Windows cmd-Befehle (for /L), keinseq. - Windows-Build der Engine: clang + ninja,
-DGGML_VULKAN=OFF, dazu-D_WIN32_WINNT=0x0A00in C- und CXX-Flags (cpp-httplib verlangt Windows 10). - Pfade in Git-Status (
/) und LSP-URIs (file:///C:/…) werden auf Windows-Trenner umgesetzt (updateGitStatus,lsp_proto.pathToUri/uriToPath). Dasselbe gilt für die Repo-Wurzel ausgit rev-parse --show-toplevel:git_worker.repoRootsetzt sie um, bevor sie in Timeline- und Graph-Antworten geht — sonst stimmt kein Vergleich gegen einen Editor-Pfad.
Use-after-free in Render-Commands (Segfault in hashText/renderText)
- Frame-Regel: Die Render-Commands aus
renderExamplezeigen auf fremden Speicher (Explorer-Knotennamen, Tab-Namen, Dialog-Nachricht, Frame-Arena). Der GPU-Renderer liest sie erst nachrenderExample. Nichts, worauf sie zeigen, darf zwischenendLayoutund dem nächstenrenderExamplefreigegeben werden — „nach endLayout“ ist nicht sicher. Aufräumen gehört vor das nächste Layout:UI.update,processPending, oder der Anfang vonrenderExample(applyPendingDialogResult). applyDeferredLayoutActionsläuft am Anfang vonrenderExampleund wendet an, was das vorige Layout angefordert hat: Dialog-Klick/Enter (pending_dialog_result),pending_split,pending_tab_closes, leere Panes. Direkt nachendLayoutwürden Dialog-Nachricht, Knotennamen (loadDirectory) und Tab-Namen freigegeben, während der Frame noch gezeichnet wird →Segmentation faultintext_system.hashText.- Kein
clay.text(&.{byte}, …): Zeiger auf ein Stack-Temporary, beim Zeichnen längst überschrieben. Statische Literale nehmen (Git-Status-Buchstaben inrenderTreeEntry). - Dasselbe für
var buf: [N]u8 = undefinedin einerrender-Funktion mitbufPrint→clay.text: der Puffer gehört in die Frame-Arena (arena.alloc(u8, N)) oder das Ergebnis wirdarena.duped. Symptom im Fenster (Debug-Build): der Text besteht aus 0xAA-Bytes (Zigsundefined-Muster, ein späterer Stack-Frame hat den Puffer neu initialisiert), im Logwarning(shaper): invalid UTF-8 text … hex=aaaa…, gezeichnet als lauter U+FFFD. Headless zeigt das nicht (kein GPU-Text; der memfd-Probe prüft nur Mapping, nicht Inhalt).SimpleShaper.shapedekodiert verlustbehaftet, undrenderClayLayout-Fehler werden alswarning(rendering)gemeldet, statt den Frame abzubrechen. Platform.setCursormeldet nur Formwechsel an wio: unter Windows macht wio je AufrufGetCursorPos+SetCursorPos, und bei gedrückter Maustaste erzeugt das einWM_MOUSEMOVE, das den nächsten Frame weckt, und der Loop liefe beim Ziehen ohne Pause. Die Debug-Zeilenmouse:(main.zig, nur beim Ziehen),splitter:(Breite vorher/nachher) undcursor:(Formwechsel) bleiben für solche Diagnosen.- Werkzeug: Headless fasst jeden Text-Command per
pwritein ein memfd an (src/debug/text_probe.zig;/dev/nullliest den Puffer nicht, EFAULT bleibt aus). Zeigt ein Command auf unmapped Speicher, panict der Loop mit Command-Index, Bounding-Box und dem vorigen Text. Mit--page-allocoderZID_PAGE_ALLOC=1läuft alles übersrc/debug/free_log.zig(page_allocator: jede Freigabe = munmap, kein In-Place-Remap) und die Meldung enthält den Stack-Trace der Freigabe. So laufen lassen:ZID_PAGE_ALLOC=1 python3 scripts/e2e_explorer.py(jede E2E-Suite geht) oderpython3 scripts/e2e_repro_text_uaf.py --page-alloc(Ordnerwechsel in einen erzeugten Baum untertmp/e2e_uaf, Bilder, Tooltip, Picker-Klicks, Tab-Schließen). Der GPA unmappt kleine Buckets erst, wenn sie ganz leer sind, darum fällt der Fehler im Fenster nur sporadisch auf.