diff --git a/AGENTS.md b/AGENTS.md index 0604c07..05fd16f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,14 +29,7 @@ Nutzerorientierte Einführung: [`README.md`](README.md). 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. + sonst nur mit NACK. - `versapad_combined.py` — Ein-Datei-Format (alle 3 Profile + Makros + **nur lokal gespeicherte** Profilnamen), Default-Pfad `~\OneDrive\Desktop\versapad_config_all.json`. Passt zum Wire-Protokoll: @@ -93,32 +86,6 @@ Dokumentation und Verifikation unten für die Größeneinschätzung). 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". -- **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. - 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). @@ -222,13 +189,3 @@ 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). diff --git a/desktop_viewer.py b/desktop_viewer.py index 926337b..1b80b6d 100644 --- a/desktop_viewer.py +++ b/desktop_viewer.py @@ -391,13 +391,7 @@ class VersaPadViewer(tk.Tk): self._on_toggle_sync() self.sync_check.configure(state="disabled") if self.combined is None: - if os.path.exists(vcomb.DEFAULT_PATH): - try: - self.combined = vcomb.load_file(vcomb.DEFAULT_PATH) - except (OSError, ValueError, KeyError): - self.combined = vcomb.default_combined() - else: - self.combined = vcomb.default_combined() + self.combined = vcomb.default_combined() self.prog_row.pack(fill="x", padx=20, pady=(0, 14), after=self.tabs) self.header_sub.configure(text="Programmiermodus · Zelle anklicken zum Bearbeiten") else: @@ -526,13 +520,8 @@ class VersaPadViewer(tk.Tk): Bevorzugt die kombinierte Datei (versapad_config_all.json), falls vorhanden -- so zeigen im Programmiermodus gespeicherte Aenderungen sich auch hier, statt dass die alten Einzel-JSONs weiter durchscheinen. - Fehlt sie (z.B. versehentlich geloescht) und ist Live-Sync gerade aus - (COM-Port frei), wird sie automatisch per Serial vom Board neu - aufgebaut und als neuer Cache gespeichert -- das Board ist die - eigentliche Quelle der Wahrheit, kein Datei-Handling von Hand mehr - noetig. Nur wenn das nicht klappt (Board nicht erreichbar, Live-Sync - haelt den Port), weicht es zuletzt auf die klassischen - versapad_config{1,2,3}.json aus.""" + Faellt zurueck auf die klassischen versapad_config{1,2,3}.json, wenn + es noch keine kombinierte Datei gibt.""" if os.path.exists(vcomb.DEFAULT_PATH): try: data = vcomb.load_file(vcomb.DEFAULT_PATH) @@ -543,25 +532,8 @@ class VersaPadViewer(tk.Tk): }) return cfg, f"Quelle: {vcomb.DEFAULT_PATH}" except (KeyError, IndexError, ValueError): - pass # kaputte/unvollstaendige Datei -- weiter unten ausweichen - elif not self.live_sync.get(): - try: - data = vcomb.fetch_from_board(link=self._link) - vcomb.save_file(data, vcomb.DEFAULT_PATH) - raw = data["profiles"][self.profile] - cfg = vp.annotate_profile({ - "buttons": [dict(b) for b in raw["buttons"]], - "encoders": [dict(e) for e in raw["encoders"]], - }) - return cfg, "Quelle: Board (neu vom Geraet geladen)" - except RuntimeError: - pass # Board nicht erreichbar -- weiter unten ausweichen - try: - return vp.load_profile(self.profile), f"Quelle: {vp.CONFIG_PATHS[self.profile]}" - except FileNotFoundError as e: - hint = (" (Live-Sync ist an -- COM-Port belegt, zum automatischen " - "Neuladen vom Board erst ausschalten)") if self.live_sync.get() else "" - raise FileNotFoundError(f"{e}{hint}") from e + pass # kaputte/unvollstaendige Datei -- auf Einzel-JSONs ausweichen + return vp.load_profile(self.profile), f"Quelle: {vp.CONFIG_PATHS[self.profile]}" def _render(self): for w in self.grid_frame.winfo_children(): diff --git a/server.py b/server.py index d184a88..1090911 100644 --- a/server.py +++ b/server.py @@ -1,10 +1,7 @@ """ VersaPad Viewer -- Browser-Variante. -Liest bei jedem Request die kombinierte Config-JSON vom Desktop und -rendert die Steuermatrix (4x5 Grid + 4 Encoder) je Profil als HTML. Fehlt -die JSON (z.B. geloescht), wird sie automatisch per Serial vom Board neu -aufgebaut und als neuer Cache gespeichert -- das Board ist die eigentliche -Quelle der Wahrheit, siehe versapad_combined.load_or_fetch(). +Liest bei jedem Request live die 3 Config-JSONs vom Desktop und rendert +die Steuermatrix (4x5 Grid + 4 Encoder) je Profil als HTML. Kein Build-Schritt, kein externes Framework -- nur stdlib. Start: python server.py [--port 8765] @@ -15,7 +12,6 @@ import webbrowser from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from urllib.parse import urlparse, parse_qs -import versapad_combined as vcomb import versapad_data as vp PAGE_CSS = """ @@ -91,12 +87,7 @@ def render_encoder(enc): def render_page(profile): - combined = vcomb.load_or_fetch() - raw = combined["profiles"][profile] - cfg = vp.annotate_profile({ - "buttons": [dict(b) for b in raw["buttons"]], - "encoders": [dict(e) for e in raw["encoders"]], - }) + cfg = vp.load_profile(profile) tabs = "".join( f'{html.escape(vp.PROFILE_NAMES[p])}' for p in sorted(vp.PROFILE_NAMES) @@ -116,7 +107,7 @@ def render_page(profile):