AGENTS.md gains the rules the day's debugging produced: never put the config in the install dir (the build script wipes it, silently costing all local-only fields), never branch app_dir() on sys.frozen (two diverging configs), the consequences that come with a frameless window, and why the resize grip must be placed rather than packed. Also records that Claude's Bash and PowerShell tools can see different files under AppData, which made "the notes are in the file" and "the app shows no notes" both true at once -- with the diagnostic-module approach that finally settled it. README: config now lives in %APPDATA%\VersaPadViewer, plus the frameless window and notes in the feature list.
369 lines
22 KiB
Markdown
369 lines
22 KiB
Markdown
# AGENTS.md
|
||
|
||
## Project Goal
|
||
|
||
Eigenständiges Begleit-Tool für das VersaPad-Makropad (Grovy311-Kontext,
|
||
nicht Teil der fremden jappel-Repos VersaGUI/VersaMCU). Zeigt die
|
||
Steuermatrix (4×5-Button-Grid + 4 Encoder) an, synchronisiert sie live mit
|
||
dem Board und kann sie komplett neu programmieren — inzwischen ein
|
||
schlankes Python-Pendant zur offiziellen VersaGUI (C#/.NET), plus ein
|
||
MCP-Server, der dieselbe Programmierung per KI-Tool-Aufruf ermöglicht.
|
||
Nutzerorientierte Einführung: [`README.md`](README.md).
|
||
|
||
## Aktuell unterstützte Architektur
|
||
|
||
**Read-only-Schicht (JSON, beide Frontends):**
|
||
- `versapad_data.py` — Decoding für Anzeige: JSON laden, HID-Keycode/
|
||
Consumer-Usage/Modifier → lesbarer Text, Grid-Geometrie
|
||
(`index = spalte*5+reihe`), `hid_key_choices()`/`consumer_choices()`.
|
||
`app_dir()` liefert das Verzeichnis für die eigene Config
|
||
(`versapad_combined.DEFAULT_PATH`) — bei der `.exe` der Installations-
|
||
ordner, sonst der Projektordner, siehe Installierbarkeit-Notiz unten.
|
||
`CONFIG_PATHS` bleibt bewusst auf dem OneDrive-Desktop hartkodiert (Lese-
|
||
Interop mit einem JSON-Export der offiziellen VersaGUI, kein von diesem
|
||
Tool geschriebenes Format, siehe dort).
|
||
- `server.py` — stdlib `http.server`, generiert HTML pro Request neu,
|
||
Profil-Wechsel über `?profile=0|1|2`, Auto-Reload alle 4s. Rein lesend,
|
||
kein Programmiermodus (bewusst einfach gehalten).
|
||
|
||
**Binär-/Serial-Schicht (Programmiermodus + MCP):**
|
||
- `versapad_protocol.py` — pack/unpack für `SDeviceConfig` (740B, alle 3
|
||
Profile) und `SMacroTable` (512B, 32 Slots) + CRC16 (Poly 0x1021, Init
|
||
0xFFFF), 1:1 aus den Firmware-Structs. **Gegen echtes Board validiert:**
|
||
read → unpack → pack ist byte-identisch zum Original, inkl. CRC.
|
||
- `versapad_serial.py` — `VersaPadLink`: liest/schreibt komplette Config +
|
||
Makros per 8-Byte-Paket-Protokoll, Board-Identifikation per VID/PID
|
||
`239A:0042`. Schreiben ist sicher im Sinne von "kann NVM nicht zerlegen"
|
||
— Firmware prüft Magic/CRC/Keycode-Bereich vor jedem Save, antwortet
|
||
sonst nur mit NACK. `read_active_profile()` (für Live-Sync-Polling)
|
||
nutzt seit 2026-08-07 `CMD_READ_STATUS`/`EVT_STATUS` (0x06/0x86, ein
|
||
Antwortpaket) statt eines vollen `CONFIG_READ`-Dumps (124 Pakete) —
|
||
Letzterer blockiert die Firmware in `poll_vendor()` lang genug, dass
|
||
laufende LED-Pulse-Animationen sichtbar stottern (Root Cause + Fix in
|
||
VersaMCU-Commit "Add lightweight READ_STATUS command..."). **Braucht
|
||
entsprechend neue Firmware auf dem Board** — mit altem `versapad`-Firmwarestand
|
||
liefert `READ_STATUS` schlicht Timeout, kein Absturz.
|
||
- `versapad_combined.py` — Ein-Datei-Format (alle 3 Profile + Makros +
|
||
**nur lokal gespeicherte** Profilnamen), Default-Pfad
|
||
`versapad_data.app_dir()\versapad_config_all.json` (neben der
|
||
Installation, nicht mehr hartkodiert auf einer bestimmten Maschine).
|
||
Passt zum Wire-Protokoll: `CONFIG_BEGIN/COMMIT` überträgt ohnehin immer
|
||
den kompletten 740B-Block. `load_or_fetch()` legt bei fehlender Datei
|
||
automatisch eine neue an — erst Versuch per Serial vom Board, sonst als
|
||
leere `default_combined()` (kein Board noetig fuer die Erstbenutzung).
|
||
`read_profile_names()` liest nur die Namen (kein Board-Zugriff, fuer
|
||
Tab-Beschriftungen im Nur-Lese-Modus, siehe Installierbarkeit-Notiz).
|
||
|
||
**UI:**
|
||
- `desktop_viewer.py` — Tkinter, flaches Design, drei unabhängige Modi
|
||
(Nur lesen / Live-Sync / Programmiermodus, siehe Kritische Domänenregeln),
|
||
Tray-Icon statt Taskleisten-Minimierung, Info-Button mit MCP-Doku.
|
||
- `action_dialog.py` — Bearbeiten-Dialoge (`ActionEditDialog`,
|
||
`MacroStepsDialog`) für den Programmiermodus.
|
||
|
||
**MCP-Server:**
|
||
- `versapad_mcp_server.py` — registriert als User-Scope-MCP-Server
|
||
"versapad" (`claude mcp add -s user versapad -- <python> versapad_mcp_server.py`).
|
||
Nutzt MCP-SDK v2 (Paket `mcp`, Klasse `mcp.server.mcpserver.MCPServer` —
|
||
**nicht** `FastMCP` aus `mcp.server.fastmcp`, das existiert in dieser
|
||
SDK-Version nicht mehr, wurde umbenannt). `@mcp.tool()`-dekorierte
|
||
Funktionen bleiben direkt aufrufbar (kein `.fn`-Unterschied wie bei
|
||
älteren FastMCP-Versionen). Tool-Liste: siehe README oder
|
||
`MCP_INFO_TEXT` in `desktop_viewer.py`.
|
||
|
||
Vollständige Modulübersicht mit Zeilenreferenzen bei Bedarf direkt im Code
|
||
nachschlagen — die Dateien sind klein genug, dass eine separate
|
||
`docs/current-architecture.md` hier keinen Mehrwert hätte (siehe
|
||
Dokumentation und Verifikation unten für die Größeneinschätzung).
|
||
|
||
## Kritische Domänenregeln
|
||
|
||
- Geometrie: `index = spalte*5 + reihe`, Reihe 0 = oben, Reihe 4 = unten
|
||
(Firmware-Reihenfolge). Nicht raten, nicht neu herleiten.
|
||
- Encoder 0 `sw` ist auf allen 3 Profilen der Profilwechsel — nicht ohne
|
||
Rücksprache mit dem User ändern.
|
||
- Makros sind eine EINE globale 32-Slot-Tabelle, nicht pro Profil. Slot 0-19
|
||
= MX-Button-Index, Slot 20-31 = `20 + enc*3 + act_idx`. Zwei Profile, die
|
||
denselben Slot referenzieren, spielen dieselben Schritte ab.
|
||
- Ein Makro-Schritt mit `keycode=0` beendet die Sequenz (Firmware-Konvention)
|
||
— keine Lücken vor dem letzten belegten Schritt lassen.
|
||
- Makro-Schritte erlauben nur Strg/Shift/Alt als Modifier, kein Win (passend
|
||
zum Original-`ActionDialog.cs`-Verhalten).
|
||
- Profilnamen leben NUR lokal in `versapad_config_all.json`
|
||
(`profile_names`) — die Firmware-Structs haben keinen Platz für einen
|
||
String (Header exakt 32B, jedes Profil exakt 236B, alles verplant). Sie
|
||
landen nie aufs Board, egal welcher Schreibpfad benutzt wird.
|
||
- **Notizen** (freier Text je Button/Encoder-Aktion, seit 2026-08-15) sind
|
||
aus demselben Grund rein lokal: leben im `"note"`-Feld JEDER Action
|
||
(`{"type","data","note"}`), nicht in einer separaten Struktur. `to_binary`/
|
||
`pack_config` ignorieren das Feld beim Schreiben (liest nur type/data),
|
||
`from_binary` liefert frisch vom Board immer `note=""` (Board kennt keine
|
||
Notizen) — `versapad_combined.merge_notes(neu, alt)` kopiert bestehende
|
||
Notizen nach jedem `load_from_board()`/`fetch_from_board()` zurück, sonst
|
||
gingen sie bei jedem Board-Refresh verloren. Action-Typ wechseln
|
||
(`set_button_key` etc.) darf die Notiz NICHT loeschen (baut die neue
|
||
Action ueber `_replace_action()`/`dlg.action["note"]` mit der alten Notiz),
|
||
nur `set_button_note()`/`set_encoder_note()`/das Notiz-Feld im
|
||
Programmiermodus-Dialog aendern sie gezielt.
|
||
- Der COM-Port ist exklusiv. Live-Sync und Programmiermodus schalten sich
|
||
gegenseitig aus (ein `VersaPadLink` kann nicht von zwei Konsumenten
|
||
gleichzeitig genutzt werden); VersaGUI läuft als Tray-App dauerhaft im
|
||
Hintergrund weiter, auch wenn nur ihr Konfigurationsfenster geschlossen
|
||
wird — für Parallelbetrieb mit diesem Tool muss sie über ihr eigenes
|
||
Tray-Menü ("Beenden") wirklich beendet werden.
|
||
- Programmiermodus-Änderungen werden bei jeder Bearbeitung automatisch in
|
||
`versapad_config_all.json` gespeichert (nicht erst bei explizitem
|
||
"Datei speichern"). Der Nur-Lese-Modus bevorzugt dieselbe Datei, falls
|
||
vorhanden, und fällt sonst auf die klassischen `versapad_config{1,2,3}.json`
|
||
zurück — beide Ansichten müssen dieselbe Quelle zeigen, sonst wirkt eine
|
||
Bearbeitung "verschwunden".
|
||
- **Config-Pfad darf NIE im Installationsverzeichnis liegen (2026-08-15,
|
||
Datenverlust-Bug):** `build_and_deploy.ps1` räumt sein Zielverzeichnis vor
|
||
jedem Deploy komplett ab (`Remove-Item $localDir -Recurse -Force`). Nachdem
|
||
`app_dir()` am selben Tag auf genau dieses Verzeichnis gezeigt hatte, hat
|
||
**jeder Rebuild die Nutzer-Config mitgelöscht**; die App hat sie danach
|
||
kommentarlos leer vom Board neu aufgebaut (`load_or_fetch()` →
|
||
`fetch_from_board()`), wodurch alle Notizen weg waren. Besonders tückisch:
|
||
Board-Bindings und LEDs sahen danach völlig normal aus, nur die rein
|
||
lokalen Felder (Notizen, Profilnamen) fehlten -- der Schaden ist also
|
||
unsichtbar, wenn man nur aufs Grid schaut. Fix: `app_dir()` liefert jetzt
|
||
`%APPDATA%\VersaPadViewer` (Roaming), getrennt vom Installationsordner in
|
||
`%LOCALAPPDATA%`; zusätzlich rettet `build_and_deploy.ps1` vorgefundene
|
||
`versapad_config*.json` aus dem Zielverzeichnis über den Deploy hinweg
|
||
(für Altinstallationen). **Regel für künftige Änderungen: Programm- und
|
||
Datenverzeichnis nie zusammenlegen, egal wie praktisch "alles an einem
|
||
Ort" klingt.**
|
||
- **Nur EIN Config-Pfad, unabhängig vom Startweg (2026-08-15):** `app_dir()`
|
||
hat bewusst KEINEN `sys.frozen`-Zweig mehr. Vorher sah die gebaute `.exe`
|
||
eine andere Datei als der aus dem Quellcode gestartete MCP-Server, was zu
|
||
zwei auseinanderlaufenden Configs führte (die `.exe` zeigte ein anderes
|
||
Profil 0 als der MCP-Server meldete). Wer hier wieder nach Startweg
|
||
unterscheidet, baut denselben Bug erneut ein.
|
||
- **Installierbarkeit verbessert 2026-08-14:** Drei Probleme beim
|
||
Weitergeben an andere Leute behoben. (1) `build_and_deploy.ps1` starb bei
|
||
fehlenden Paketen (pyinstaller/pystray/pillow) kommentarlos, v.a. bei
|
||
Doppelklick im Explorer, weil das Fenster sich sofort schließt. Fix:
|
||
`requirements.txt` (alle Pakete an einer Stelle, README und Skript nutzen
|
||
dieselbe Datei), Skript installiert sie selbst per `pip install -r`,
|
||
läuft komplett in try/catch, prüft `$LASTEXITCODE` nach jedem nativen
|
||
Aufruf, und pausiert am Ende (Erfolg wie Fehler) auf Tastendruck --
|
||
`-NoPause` für CI/Automation. (2) `versapad_combined.DEFAULT_PATH` war
|
||
hartkodiert auf `~\OneDrive\Desktop` einer bestimmten Maschine -- für
|
||
andere Nutzer unbrauchbar. Fix: `versapad_data.app_dir()` (neue
|
||
Funktion) liefert bei der gebauten `.exe` deren Installationsordner
|
||
(`sys.executable`-Verzeichnis), sonst den Projektordner (`__file__`-
|
||
Verzeichnis) -- `DEFAULT_PATH` hängt jetzt daran, landet also immer neben
|
||
der laufenden Installation. **Diese app_dir()-Variante ist seit
|
||
2026-08-15 überholt und war aktiv schädlich, siehe unten "Config-Pfad
|
||
darf nie im Installationsverzeichnis liegen".**
|
||
`versapad_data.CONFIG_PATHS` (Lese-Interop
|
||
mit der C#-VersaGUI) bleibt bewusst auf dem Desktop, siehe oben. (3) Fehlte
|
||
die Config UND war kein Board erreichbar, blockierte das Tool mit einer
|
||
Fehlermeldung statt zu starten. Fix: `load_or_fetch()` legt jetzt bei
|
||
Board-Fehlschlag eine leere `default_combined()` an statt `RuntimeError`
|
||
zu werfen -- Erstbenutzung ganz ohne vorhandene Config oder Board
|
||
funktioniert jetzt. (4) Profilnamen waren zusätzlich hartkodiert in
|
||
`versapad_data.PROFILE_NAMES` (Dict, jetzt entfernt, ersetzt durch
|
||
`NUM_PROFILES = 3`) und liefen der JSON-`profile_names` parallel --
|
||
Programmiermodus zeigte umbenannte Profile, Lesemodus/Browser-Ansicht
|
||
weiterhin die alten Namen. Fix: `server.py` und `desktop_viewer.py` lesen
|
||
Namen jetzt immer aus der kombinierten JSON (`combined["profile_names"]`
|
||
bzw. `versapad_combined.read_profile_names()`), eine einzige Quelle der
|
||
Wahrheit für alle Frontends.
|
||
- **Herkunft der drei Punkte oben + Tab-Umbenennen-Fix:** per Cherry-Pick aus
|
||
Julian Appels eigenem `dev/jappel`-Branch (`git.jappel.io/jappel/
|
||
VersaGUI-py`) übernommen, der sich zeitgleich mit unserem eigenen
|
||
COM-Port-Fix entwickelt hat (kein Fork-Sync-Automatismus -- manuell
|
||
gegengelesen und übernommen, siehe Sessionlog). **Eine Abweichung vom
|
||
Original:** dort landet der Build jetzt in `$projectDir\dist\
|
||
VersaPadViewer` statt `%LOCALAPPDATA%\VersaPadViewer` -- hier bewusst
|
||
NICHT übernommen, weil das die tatsächlich installierte/genutzte Instanz
|
||
wäre und bestehende Verknüpfungen sonst ins Leere zeigen würden.
|
||
`build_and_deploy.ps1` hier weiterhin `%LOCALAPPDATA%\VersaPadViewer`.
|
||
Bestehende `versapad_config_all.json` vom alten Desktop-Pfad wurde einmalig
|
||
nach `%LOCALAPPDATA%\VersaPadViewer\` migriert (kopiert, Original bleibt).
|
||
Nicht übernommen: die `.mcp.json`-Registrierung (hartkodierter Pfad auf
|
||
Julians Maschine, `C:\Users\Julian\...`) -- dafür stattdessen ein Issue in
|
||
seinem Repo (git.jappel.io/jappel/VersaGUI-py) angelegt, damit relative/
|
||
portable Pfadauflösung dort nachgezogen werden kann; die drei
|
||
`docs/*.md`-Referenzdateien (unser eigener Dokumentationsstandard hält für
|
||
dieses Tool bewusst bei README + AGENTS.md ohne vollen `docs/`-Baum).
|
||
- **Bug behoben 2026-08-08:** `server.py` (`vp.load_profile()`) und
|
||
`desktop_viewer.py` (`_current_profile_view()`) lasen im Nur-Lese-Modus
|
||
hart von den Desktop-JSONs -- fehlten sie (z.B. User loescht sie), gab es
|
||
eine ungefangene `FileNotFoundError` bzw. "Config-Datei fehlt"-Anzeige,
|
||
obwohl das Board die Config laengst dauerhaft im NVM haelt. Fix: neue
|
||
`versapad_combined.fetch_from_board()`/`load_or_fetch()` -- fehlt die
|
||
Kombi-JSON, wird sie automatisch per Serial vom Board neu aufgebaut und
|
||
als Cache gespeichert (self-healing), nur bei unerreichbarem Board (Port
|
||
belegt/kein Board) bleibt der Fallback auf die alten Einzel-JSONs bzw.
|
||
eine Klartext-Fehlermeldung. In `desktop_viewer.py` nur versucht, wenn
|
||
Live-Sync aus ist (sonst haelt der Serial-Hintergrundthread den
|
||
COM-Port -- zwei gleichzeitige Zugriffe auf denselben `self.ser` waeren
|
||
eine Race Condition). Die Desktop-JSONs sind damit reiner Lesecache, kein
|
||
Pflegeaufwand mehr fuers Board-Backup.
|
||
- **Bug behoben 2026-08-07:** `_on_toggle_editing()` initialisierte
|
||
`self.combined` beim ersten Aktivieren des Programmiermodus mit
|
||
`vcomb.default_combined()` — das seedet ALLE 3 Profile aus den alten
|
||
Einzel-JSONs `versapad_config{1,2,3}.json`, nicht aus der aktuellen
|
||
`versapad_config_all.json` oder vom Board. Ein Klick auf "Zum Board
|
||
übertragen" hat dadurch beim Testen alle 3 Profile auf einen veralteten
|
||
Stand zurückgesetzt, obwohl nur ein Profil-Tab sichtbar bearbeitet wurde —
|
||
der Schaden an den anderen beiden Profilen blieb unbemerkt, bis explizit
|
||
jedes Profil einzeln gegengelesen wurde. Fix: lädt jetzt zuerst
|
||
`vcomb.DEFAULT_PATH`, fällt nur bei fehlender/kaputter Datei auf
|
||
`default_combined()` zurück. Bei jedem "komisches Layout"-Report hier immer
|
||
ALLE 3 Profile prüfen, nicht nur das gemeldete.
|
||
- **Randloses Fenster (seit 2026-08-15):** Die Titelleiste ist per reinem Tk
|
||
`overrideredirect(True)` ausgeblendet -- **niemals** per ctypes/WinAPI
|
||
nachhelfen (kein `SetWindowLongW`/`ShowWindow` auf das eigene Fenster),
|
||
siehe Speicher-Notiz "keine Selbstversteck-Fenstertricks": genau diese
|
||
Kombination hat in einem anderen Projekt Bitdefender-Fehlalarme
|
||
ausgelöst. Konsequenzen, die mitgebaut werden müssen: kein
|
||
Taskleisten-Eintrag (Rückweg nur über das Tray-Icon), kein Ziehen am
|
||
Rahmen (Kopfzeile ist deshalb per `<B1-Motion>` verschiebbar), keine
|
||
System-Buttons (eigene `✕`/`—` in der Kopfzeile, beide legen ins Tray)
|
||
und **keine Resize-Ränder**.
|
||
- **Der Größen-Anfasser hängt per `place()` an der Fensterecke, nicht
|
||
gepackt am Ende des Inhalts.** Gepackt verschwindet er, sobald das Fenster
|
||
kleiner als der Inhalt ist -- also exakt dann, wenn man ihn zum
|
||
Vergrößern bräuchte. `MIN_W/MIN_H` begrenzen das Verkleinern.
|
||
- Fensterhöhe muss zum Karteninhalt passen: seit die Karten eine Notizzeile
|
||
haben (`CARD_H` 84 → 114) braucht das Fenster ~960px, sonst liegen
|
||
Encoder-Bereich und Fußzeile unterhalb des Rands. Wer `CARD_H` ändert,
|
||
muss `geometry()` mit anpassen.
|
||
- HID-Tasten-Auswahl im Programmiermodus ist ein Dropdown, kein
|
||
Tastendruck-Capture (bewusst — kein WinAPI-Hook, um keinen AV-Fehlalarm
|
||
wie bei den Fensterverstecktricks in anderen Projekten zu riskieren).
|
||
- Zeichentasten-Labels zeigen eine US-Layout-Näherung, nicht das tatsächlich
|
||
aktive Windows-Tastaturlayout (die echte VersaGUI löst das über
|
||
`GetKeyNameText()`, das bilden wir ohne WinAPI-Call nicht nach).
|
||
- Tk-Aufrufe (`self.after()`, Widget-Konfiguration) NIE direkt aus einem
|
||
Fremdthread (Serial-Thread, pystray-Thread) — hat in einer früheren
|
||
Version einen stillen Absturz verursacht. Threads legen Ergebnisse nur in
|
||
eine `queue.Queue`, der Main-Thread holt sie per `after()`-Polling ab.
|
||
- `.exe` (PyInstaller) läuft NICHT vom Netzlaufwerk aus (`Z:\Git\...`,
|
||
SMB-Share) — Windows blockiert das Nachladen der `_internal`-DLLs von
|
||
einem Netzwerkpfad ohne jede Fehlermeldung. Bauen scheitert dort zusätzlich
|
||
an Tcl/Tk-`tzdata`-Pfadlängen. `build_and_deploy.ps1` kopiert den
|
||
Quellcode deshalb zuerst nach `%TEMP%`, baut nur dort, deployt nach
|
||
`%LOCALAPPDATA%`.
|
||
- `--onedir`, nicht `--onefile` beim PyInstaller-Build — Single-File-Bundles
|
||
lösen öfter AV-Fehlalarme aus (sehen strukturell wie ein Packer aus).
|
||
|
||
## Existing-Codebase-Regel
|
||
|
||
Es gibt bereits eine Codebasis mit validierter, gegen die echte Firmware
|
||
getesteter Logik (Binärformat, CRC, Protokoll). Nicht blind neu raten oder
|
||
umschreiben. Vor größeren Änderungen am Binärformat/Protokoll immer gegen
|
||
die Firmware- und GUI-Quellen abgleichen, nicht aus dem Gedächtnis
|
||
rekonstruieren:
|
||
- `VersaGUI/src/ActionDialog.cs` (HidKeyName, s_consumer, Modifier-Bits)
|
||
- `VersaGUI/src/Protocol.cs` (Paket-IDs, Chunk-Größen)
|
||
- `VersaMCU/src/config/nvm_config.h` + `.cpp` (SDeviceConfig-Layout, CRC)
|
||
- `VersaMCU/src/config/action.h` (SAction, ActionType-Enum-Werte)
|
||
- `VersaMCU/src/config/macro_config.h` + `.cpp` (SMacroTable-Layout)
|
||
- `VersaMCU/src/CButton.h` (LEDAnim-Enum-Werte)
|
||
- `VersaMCU/src/CMainController.cpp` (welche Commands die Firmware
|
||
*tatsächlich* behandelt — `Protocol.cs` definiert mehr Konstanten als
|
||
zwingend implementiert sind, immer hier gegenchecken)
|
||
|
||
Frühere falsche Annahme, die hier stand: Makros seien nur per SWD/JTAG
|
||
schreibbar. Falsch — `USB_CMD_MACRO_BEGIN/DATA/COMMIT` in
|
||
`CMainController.cpp` sind normal über USB implementiert. Korrigiert, auch
|
||
im `versapad`-Skill (`~/.claude/skills/versapad/SKILL.md`).
|
||
|
||
## Implementierungsdisziplin
|
||
|
||
In Phasen arbeiten, nicht mehrere unabhängige Features in einem Rutsch ohne
|
||
Zwischenverifikation bauen. Vor jeder Änderung am Binärformat/Protokoll:
|
||
gegen ein echtes Board testen (read → unpack → pack → Vergleich auf
|
||
Byte-Identität), nicht nur gegen Beispieldaten. Vor jedem Schreibvorgang
|
||
aufs Board: erst mit unveränderten Daten testen (Identity-Write), bevor
|
||
echte Änderungen geschrieben werden.
|
||
|
||
**Änderungen an der Config immer aus der Sicht prüfen, die die laufende App
|
||
hat (2026-08-15 gelernt):** Claudes Bash- und PowerShell-Werkzeuge sehen
|
||
unter `C:\Users\chris\AppData\...` teilweise *unterschiedliche* Dateien —
|
||
derselbe Pfad lieferte gleichzeitig 29826 Bytes (Bash/MCP-Server) und
|
||
28455 Bytes (PowerShell/`.exe`). Konkret heißt das: **Config-Schreibvorgänge
|
||
über den versapad-MCP-Server (`save_local()`) erreichen die installierte
|
||
`.exe` nicht zuverlässig.** Das hat eine Fehlersuche über viele Runden
|
||
verschleppt, weil "Notizen sind in der Datei" (Bash) und "App zeigt keine
|
||
Notizen" gleichzeitig stimmten. Vorgehen:
|
||
- Config-Dateien, die die installierte App lesen soll, über **PowerShell**
|
||
schreiben/prüfen (`Get-Item`, dort ausgeführtes `python`), nicht über Bash.
|
||
- Bei "Änderung wirkt nicht"-Symptomen als Erstes Dateigröße/mtime aus
|
||
*beiden* Sichten vergleichen, bevor Code-Ursachen gesucht werden.
|
||
- `Z:\Git\...` (SMB-Share) ist von diesem Effekt nicht betroffen — Quellcode
|
||
und Build verhalten sich normal.
|
||
|
||
Verlässlich zum Ziel führt bei solchen Widersprüchen ein temporäres
|
||
Diagnose-Modul, das im gebauten Bundle mitläuft und `app_dir()`,
|
||
`DEFAULT_PATH`, `os.stat()` und den tatsächlich geladenen JSON-Inhalt in eine
|
||
Datei schreibt — damit war die Ursache in einem Durchlauf sichtbar, nachdem
|
||
Vermutungen mehrfach danebenlagen.
|
||
|
||
## Naming
|
||
|
||
Code-Identifier englisch (Python-Konvention: `pack_config`, `read_macros`,
|
||
`ActionEditDialog`). UI-Text, Kommentare und Docstrings deutsch (Projekt-
|
||
und User-Konvention). Domänenbegriffe aus der Firmware direkt übernehmen,
|
||
nicht neu erfinden: `profile`, `action`, `led`, `slot`, `macro`, `encoder`,
|
||
`chunk`. Keine generischen Namen wie `data`/`item`/`entry`, wo eine
|
||
Bedeutung existiert — Ausnahme: `data` als Feldname ist durch die Firmware
|
||
selbst vorgegeben (`SAction.data`), dort beibehalten statt umzubenennen.
|
||
|
||
## Deferred Work
|
||
|
||
- Volle `docs/`-Baumstruktur (siehe Dokumentation und Verifikation unten —
|
||
Projektgröße rechtfertigt das aktuell nicht, kein DB-/API-Dienst)
|
||
- Board-seitiges Umschalten des aktiven Profils per Button in der GUI —
|
||
explizit vom User abgelehnt ("lass uns weg"), Live-Sync bleibt read-only
|
||
- Profilnamen aufs Board schreiben — technisch unmöglich (kein Platz im
|
||
Firmware-Struct), bleibt lokal
|
||
- Tastendruck-Capture statt Dropdown im Programmiermodus — bewusst
|
||
vermieden (WinAPI-Hook-Risiko)
|
||
- Hintergrund-Thread für "Vom Board laden"/"Zum Board übertragen" — laufen
|
||
aktuell synchron im UI-Thread (kurzzeitiges Einfrieren möglich)
|
||
- Vorgefertigte `.exe` im Repo/als Release-Asset — bewusst nicht committet
|
||
(Build-Artefakt), Anleitung zum Selbstbauen steht in `README.md`
|
||
|
||
Nicht an diesen Punkten arbeiten, ohne dass der User es explizit anfragt.
|
||
|
||
## Dokumentation und Verifikation
|
||
|
||
- `README.md` ist der Einstiegspunkt (Installation, Nutzung, Architektur-
|
||
Überblick).
|
||
- `AGENTS.md` (diese Datei) ist die agentenseitige Quelle der Wahrheit für
|
||
Domänenregeln und Architekturgrenzen — jede Session aktualisieren, die
|
||
daran etwas ändert oder etwas Wichtiges lernt.
|
||
- Größeneinschätzung nach Projekt-Dokumentationsstandard: kleines/mittleres
|
||
Tool ohne eigene Datenbank und ohne persistenten API-Dienst (der
|
||
Browser-Server ist ein einfacher lokaler Lese-Viewer, kein
|
||
Mehrbenutzer-Backend) → `README.md` + `AGENTS.md` sind Pflicht und
|
||
vorhanden, ein voller `docs/`-Baum ist nicht angemessen.
|
||
|
||
Prüfungen vor einem Commit an Binärformat/Protokoll:
|
||
```bash
|
||
python -m py_compile *.py
|
||
```
|
||
Danach ein Live-Testskript gegen ein angeschlossenes Board laufen lassen
|
||
(read → unpack → pack → Bytevergleich, siehe Existing-Codebase-Regel) —
|
||
es gibt keine automatisierten Unit-Tests dafür, die Verifikation läuft
|
||
manuell/interaktiv pro Session.
|
||
|
||
Committen: prägnante Commit-Message je abgeschlossenem, verifiziertem
|
||
Arbeitspaket. Nach jedem Push: alle bekannten Remotes prüfen (`origin` auf
|
||
GitHub, `jappel` auf git.jappel.io) — beide müssen synchron bleiben, siehe
|
||
Speicher-Notiz "Multi-Remote-Repos synchron halten".
|
||
|
||
**PR-Erstellung auf git.jappel.io per API/curl mit Access-Token wird vom
|
||
Bash-Classifier geblockt** (Auto-Mode, gilt auch für `git credential fill`),
|
||
siehe Speicher-Notiz "Bash-Klassifikator blockt Credential/Auth-Schreibzugriffe".
|
||
Branch pushen geht (nutzt den Git-eigenen Credential-Helper, kein Token im
|
||
Klartext im Bash-Aufruf), den fertigen PR muss der User über den von Forgejo
|
||
nach dem Push ausgegebenen Compare-Link selbst anlegen (oder Claude einen
|
||
Token geben, der dann NICHT wiederverwendbar im Bash-Aufruf landen darf,
|
||
sondern nur für den einen `curl`-Call — auch das kann der Classifier trotzdem
|
||
blocken, dann bleibt nur der manuelle Link).
|