140 lines
8.2 KiB
Markdown
140 lines
8.2 KiB
Markdown
# Desktop
|
||
|
||
GNOME-Shell-Extension (GNOME 46, Ubuntu 24.04): Kalender, Todos und Notizen direkt auf dem Desktop,
|
||
unter allen Fenstern, über dem Wallpaper.
|
||
|
||
## Bedienung
|
||
|
||
- **Woche:** startet mit der aktuellen Uhrzeit in der Mitte, rote Linie markiert jetzt.
|
||
- **Termin anlegen:** in der Wochenansicht auf eine Stunde drücken und bis zur Endstunde ziehen,
|
||
auch über mehrere Tage. Klick ohne Ziehen = eine Stunde. Ziehen in der Zeile „ganz“ = ganztägig.
|
||
Oder Rechtsklick auf einen Tag (Monat), eine Stunde (Woche) oder die Tagesfläche: „Termin erstellen“.
|
||
Popover: Titel, Von und Bis je mit Datum und Uhrzeit. Ohne Ende gilt eine Stunde, ohne Uhrzeit ganztägig.
|
||
- **Todo anlegen:** Rechtsklick ins Todo-Panel oder in den Kalender, „Todo erstellen“. Text plus Datum und Uhrzeit.
|
||
- **Notiz:** Rechtsklick irgendwo im Notiz-Panel, „Notiz erstellen“ fragt den Namen und öffnet den Editor.
|
||
Klick in die Notizfläche bearbeitet. Mit Fokus steht der rohe Markdown-Text, ohne Fokus die formatierte Ansicht.
|
||
Gespeichert wird alle 5 Sekunden und beim Klick woanders hin.
|
||
Enter in einer Liste setzt den nächsten Punkt, Enter auf einem leeren Punkt beendet die Liste.
|
||
- **Maustasten:** links bearbeitet, Mitte hakt ein Todo ab oder benennt Notiz/Ordner um, rechts öffnet das Menü.
|
||
Alle Popover öffnen am Mauszeiger.
|
||
- **Rechtsklick auf freie Fläche:** Erstellen (Todos, Tagesansicht, Notizbaum im Wurzelordner).
|
||
- **Ordner:** Klick klappt auf und zu, der Pfeil zeigt den Zustand wie in Obsidian.
|
||
- **Rechtsklick auf Ordner:** Notiz oder Unterordner darin erstellen, Umbenennen, Löschen.
|
||
- **Rechtsklick auf Eintrag:** Umbenennen, Löschen.
|
||
- Popover: Enter speichert, Esc oder Klick daneben bricht ab. Keine Buttons.
|
||
|
||
## Aufbau
|
||
|
||
- `desktop@marha.de/` – die Extension
|
||
- `extension.js` – Layout auf dem Arbeitsbereich, Stapelung über Desktop-Icons
|
||
- `calendar.js` – Monat, Woche (Drag), Tag, Termin-Popover
|
||
- `todos.js` – Todo-Liste, Todo-Popover
|
||
- `notes.js` – Ordnerbaum, Markdown-Anzeige, Inline-Editor, Kontextmenü
|
||
- `widgets.js` – Popover (Grab, Esc, Tab, Klick daneben), Kontextmenü, Datums- und Zeitauswahl
|
||
- `markdown.js`, `dates.js` – reine Logik, ohne Shell testbar
|
||
- `store.js` – Ablage, Tombstones, Übernahme vom Server
|
||
- `sync.js` – Abgleich mit dem Server (Soup, Timer)
|
||
- `backend/` – Sync-Server (FastAPI, SQLite), `Dockerfile`, `docker-compose.yml`
|
||
- `android/` – native Android-App mit denselben drei Bereichen, siehe `android/README.md`
|
||
- `dev/` – Test-Werkzeuge
|
||
|
||
## Daten
|
||
|
||
`~/.local/share/desktop-app/`
|
||
|
||
- `events.json` – Termine
|
||
- `todos.json` – Todos
|
||
- `notes/` – Notizen als `.md`, Ordner beliebig verschachtelt (Obsidian kann den Ordner öffnen)
|
||
- `deleted-notes.json` – gelöschte Notizen mit Zeitpunkt, damit der Sync sie auch woanders löscht
|
||
- `folders.json` – Ordner mit Zeitpunkt und deleted-Flag, damit auch leere Ordner mitlaufen
|
||
- `sync.json` – Server-Zugang, siehe Sync
|
||
- `sync-state.json` – letzter Server-Stand
|
||
|
||
Gelöschte Termine und Todos bleiben mit `deleted: true` in der Datei und werden nur ausgeblendet.
|
||
|
||
## Sync
|
||
|
||
Der Server (`backend/`, FastAPI + SQLite) hält den Kern. Jedes Gerät gleicht regelmäßig ab und sofort
|
||
nach jeder eigenen Änderung: eigene Änderungen hoch, fremde Änderungen runter. Der Takt richtet sich nach
|
||
der Aktivität: nach einer Änderung (eigene oder vom Server) eine Minute lang jede Sekunde, dann alle
|
||
5 Sekunden, nach 5 Minuten Ruhe alle 10 Sekunden. Bei Konflikten gewinnt der Eintrag mit dem
|
||
neueren `updated` (Notizen: Änderungszeit der Datei, ganze Datei). Offline geht weiter, beim
|
||
Wiederverbinden wird nachgeliefert. Eine gerade offene Notiz wird nicht von außen überschrieben.
|
||
|
||
### Claude-Code-Sitzungen (nur PCs)
|
||
|
||
Die Extension gleicht auch `~/.claude/projects/` ab, also Sitzungen und Memory von Claude Code,
|
||
im selben Takt wie die Einträge. Jede Änderung geht beim nächsten Lauf hoch, also nach 1 bis 10
|
||
Sekunden. Sitzungen wachsen nur am Ende, deshalb geht meist nur der neue Rest:
|
||
der Client nennt Größe und Hash der letzten 64 KB des Standes, den der Server haben müsste, und der
|
||
Server hängt an. Passt das nicht, geht die ganze Datei. Vom Server kommt eine Datei, wenn sie dort
|
||
neuer ist, ebenfalls nur ab dem eigenen Stand, mit ihrer Änderungszeit. Gelöschte Dateien
|
||
verschwinden auch auf dem anderen PC. Die App fragt diese Dateien nie an.
|
||
Auf dem Server liegen die Inhalte unter `data/files/`, in SQLite nur Pfad, Zeitpunkt und Größe.
|
||
Eine Sitzung darf nur auf einem PC gleichzeitig laufen, sonst gewinnt die neuere Datei als Ganzes.
|
||
Bedingung: gleicher Pfad auf beiden PCs, der Ordnername enthält ihn.
|
||
`"files": false` in `sync.json` schaltet es ab, ein Pfad statt `false` ersetzt den Ordner.
|
||
`make sync-files` stößt den Abgleich sofort an und wartet, bis er durch ist.
|
||
|
||
Zugang auf jedem Gerät in `~/.local/share/desktop-app/sync.json`:
|
||
|
||
```
|
||
{"url": "https://planer.marha.de", "token": "<SYNC_TOKEN aus .env>"}
|
||
```
|
||
|
||
Ohne diese Datei läuft die Extension rein lokal. Optional `"interval"`: Ruhetakt in Sekunden statt 10.
|
||
|
||
Server-API (alle Aufrufe mit `Authorization: Bearer <SYNC_TOKEN>`):
|
||
|
||
- `POST /api/sync` – `{"since": <cursor>, "events": [...], "todos": [...], "notes": [...], "folders": [...], "files": [...]}`; Antwort: Änderungen seit `since` in den angefragten Sammlungen plus neuer `cursor`. Ohne Sammlungen kommen die vier ersten, `files` nur auf Anfrage. Ein `files`-Eintrag ist `{"id", "updated", "content"}` (ganz), `{"id", "updated", "base": {"size", "hash"}, "append"}` (nur der Rest) oder ein Tombstone; zurück kommt nur der Steckbrief `{"id", "updated", "size"}`, bei Ablehnung mit `hash`.
|
||
- `GET /api/files/<pfad>?from=<größe>&hash=<hash>` – Inhalt einer Datei. Passt der eigene Stand, kommt nur der Rest ab `from`, sonst alles; `X-From` sagt, ab wo.
|
||
- `GET /api/events`, `/api/todos`, `/api/notes` – aktueller Stand ohne Gelöschte, für andere Anwendungen
|
||
|
||
### Server starten
|
||
|
||
```
|
||
cp .env.example .env # SYNC_TOKEN setzen
|
||
make dev # lokal, http://localhost:8040 (anderer Port: make dev PORT=8050)
|
||
make prod # auf dem Server: Docker Compose hinter Traefik, https://planer.marha.de
|
||
make stop | make logs | make down
|
||
make test # Server-Tests
|
||
make apk # Android-App bauen, make apk-install schickt sie aufs Handy (android/README.md)
|
||
```
|
||
|
||
## Installation
|
||
|
||
```
|
||
cp .env.example .env # SYNC_TOKEN eintragen (derselbe wie auf dem Server)
|
||
make install # kopiert die Extension nach ~/.local/share/gnome-shell/extensions und aktiviert sie
|
||
make update # überschreibt eine installierte Extension mit dem Stand aus diesem Ordner
|
||
```
|
||
|
||
Beide legen `~/.local/share/desktop-app/sync.json` aus der `.env` an, falls sie fehlt. Ohne `.env`
|
||
läuft die Extension rein lokal. Danach ab- und wieder anmelden. Unter Wayland lädt die Shell neuen
|
||
Code erst nach einem neuen Login. `make update` startet die Extension neu, damit ein neuer Sync-Zugang
|
||
sofort greift.
|
||
Voraussetzung: GNOME 46 (Ubuntu 24.04).
|
||
|
||
## Entwicklung
|
||
|
||
```
|
||
gjs -m dev/tests/run.js # Unit-Tests für dates.js und markdown.js
|
||
dev/restart.sh # Test-Shell als Fenster starten (zum Selbst-Ausprobieren)
|
||
dev/restart.sh headless # Test-Shell unsichtbar, für simulierte Eingaben
|
||
dev/tests/input.sh # Ablauf-Tests mit echter Maus und Tastatur (headless)
|
||
dev/tests/sync.sh # Sync-Test: lokaler Server plus headless Shell, beide Richtungen
|
||
dev/eval.sh dev/seed.js # Testdaten einspielen
|
||
dev/eval.sh 'js' # JavaScript in der Test-Shell ausführen (oder Dateipfad)
|
||
dev/shot.sh [datei.png] # Screenshot der Test-Shell
|
||
dev/input.sh click X Y # Maus und Tastatur simulieren (nur headless gefahrlos)
|
||
dev/press.sh "Text" # Button mit diesem Text direkt auslösen
|
||
dev/stop.sh # Test-Shell beenden
|
||
```
|
||
|
||
Die Test-Shell läuft mit `GSETTINGS_BACKEND=keyfile` und eigenem `XDG_DATA_HOME`
|
||
unter `/tmp/desktop-app-nested/`. Die echte Session bleibt unberührt.
|
||
|
||
Im Fenstermodus (nested, X11-Backend) würden virtuelle Eingabegeräte auf dem echten Desktop
|
||
landen. Deshalb nutzt `dev/input.sh` nur den headless Modus (natives Backend).
|
||
`dev/fake-ding.py` startet ein Fenster wie Desktop Icons, um die Stapelung zu prüfen.
|