From b7cc06b7c1092c04cdd85f95e93317ab853cf9a5 Mon Sep 17 00:00:00 2001 From: marc Date: Sun, 30 Aug 2026 03:12:00 +0200 Subject: [PATCH] Widget-Rahmen: Widgets sind Beschreibungen, keine Kaesten in der Seite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit widgetAnmelden({ id, titel, wo, sichtbar, laden, leer, inhalt, takt }) — der Rahmen macht daraus ein Fenster in der Spalte (Terminal) oder ein Schild (Desktop), kennt lädt/fertig/nichts da/Fehler und haelt den Nachladetakt. Benutzer, Wetter und Systemzustand sind umgestellt; aus index.html sind vier feste Kaesten verschwunden und aus dem Raster die Einzelplaetze. Zwei Dinge, die erst dabei sichtbar wurden: - Der Server meldete 'kein Ort eingestellt' und 'Dienst nicht erreichbar' beide als 204. Ein Ausfall sah damit aus wie ein abgeschaltetes Widget. Ausfall ist jetzt 502, mit Test. - Die Reihenfolge haengt nicht mehr an der Modulauswertung: Seit glyphs.js seine Raster mit await auf oberster Ebene laedt, laufen Module ohne Symbole vorher durch — der Systemzustand stand deshalb ueber dem Benutzer. widgetsOrdnen() macht die Reihenfolge zur Angabe, und genau dort haengt spaeter die im Server gespeicherte ein. --- IDEAS.md | 113 ++++++++++++++++---------- README.md | 30 +++++++ internal/weather/faketest.go | 14 ++++ main.go | 7 +- web/css/style.css | 41 ++++++---- web/index.html | 35 ++------ web/js/os.js | 45 ++++++----- web/js/widgets/rahmen.js | 151 +++++++++++++++++++++++++++++++++++ web/js/widgets/system.js | 29 +++++++ web/js/widgets/user.js | 49 +++++------- web/js/widgets/weather.js | 106 ++++++++++++++---------- wetter_test.go | 21 +++++ 12 files changed, 461 insertions(+), 180 deletions(-) create mode 100644 web/js/widgets/rahmen.js create mode 100644 web/js/widgets/system.js diff --git a/IDEAS.md b/IDEAS.md index 27a82c0..e18dbf9 100644 --- a/IDEAS.md +++ b/IDEAS.md @@ -165,51 +165,36 @@ ist am 29.08.2026 weggefallen: Aether und InkNote liegen längst unter erst nach der Sitzung. **Die HoneyBar weiß dort jetzt, wer schaut.** Was bleibt, ist die Frage, ob es sich bei einem Nutzer lohnt. -### 1.4 HoneyOS selbst als PWA +### 1.4 Widget-Rahmen — ✅ gebaut (30.08.2026) -Heute ist HoneyOS eine Website: Es hat `theme-color`, aber kein Manifest, kein -Symbol und keinen Service Worker — installierbar ist es damit nicht. Am Handy -landet man also im Browser mit Adressleiste, während Aether und InkNote als -eigene Apps danebenliegen. Das ist genau der Bruch, den das Ganze eigentlich -verhindern soll: Der Schreibtisch sollte sich am wenigsten nach Website -anfühlen. +Ein Widget ist jetzt eine **Beschreibung**, kein Stück HTML im Dokument: -Was dazugehört, klein bis groß: +```js +widgetAnmelden({ + id, titel, + wo: ["terminal", "desktop"], // wo es erscheinen darf + sichtbar: () => …, // Schalter aus → gar nicht erst da + laden: async () => false|void, // false = es gibt nichts zu zeigen + leer: "verbergen" | "Text", // was dann passiert + inhalt: () => "", + takt: 600000, // Nachladen, optional +}) +``` -1. **Manifest** — Name, Startadresse, `display: standalone`, Hintergrund- und - Themenfarbe. Die Farben sollten der eingestellten Röhre folgen; sie liegen - im Server, das Manifest kann also erzeugt statt fest hinterlegt werden - (Aether macht das beim Symbol schon so). -2. **Symbol** — ein Pixelglyph in 192 und 512 px, im Amber-Look. Passt zum - Rest und ist ohne fremde Werkzeuge zu machen. -3. **Service Worker** — nötig für die Installierbarkeit. Hier ist zu - entscheiden, was er überhaupt soll (siehe unten). +Der Rahmen (`web/js/widgets/rahmen.js`) macht daraus im Terminal-Modus ein +Fenster in der Widgetspalte und im Desktop-Modus ein Schild, kennt die vier +Zustände (lädt · fertig · nichts da · Fehler) und hält den Nachladetakt. Ein +Widget, das seinen Server nicht erreicht, sagt das **in seinem eigenen Rahmen** +und reißt weder den Schreibtisch noch die anderen Widgets mit. -**Die eigentliche Frage ist der Offline-Fall.** HoneyOS zeigt fremde Apps und -später deren Daten — ohne Netz gibt es davon nichts. Ein Schreibtisch, der -offline mit leeren Kacheln dasteht, ist schlimmer als ein ehrlicher Hinweis. -Sinnvoll wäre: Oberfläche und App-Liste im Cache, damit man die Symbole sieht -und antippen kann, und ein deutlicher Vermerk „ohne Verbindung". Widgets -bleiben leer, statt Altes von gestern als aktuell auszugeben. +Umgestellt sind Benutzer, Wetter und der Systemzustand. Aus der `index.html` +sind damit vier fest verdrahtete Kästen verschwunden, und aus dem Raster die +Einzelplätze — vorher hätte das dritte Widget beides wieder angefasst. -**Aus dem Aether-Debakel gelernt und hier von Anfang an zu beachten:** Ein -Eintrag im Cache ist eine ganze Antwort **samt Kopfzeilen**. Wird die Startseite -vorab abgelegt, friert damit auch ihre CSP ein — und eine Änderung an der -Konfiguration erreicht den Browser nie wieder. Also Navigationen Netz zuerst, -Cache nur als Rückfall. - -**Reihenfolge:** Erst umziehen (siehe unten), dann installierbar machen. Ein -Ursprungswechsel macht aus einer installierten PWA eine fremde App — wer -vorher installiert, installiert zweimal. - -### 1.5 Widget-Rahmen - -Ein Fenstertyp, der einen Inhalt holt, ihn anzeigt und sich benimmt, wenn es -schiefgeht: lädt gerade, nichts da, nicht angemeldet, Fehler. Einmal richtig -gebaut, kostet jedes weitere Widget danach fast nichts. - -Muss in **beiden Betriebsarten** funktionieren — im Desktop als Fenster, im -Terminal als Kachel. +**Was dabei offen gelassen wurde:** die dritte Form. Widgets sollen später auch +als Aufklappfeld in der HoneyBar leben können, deshalb erzeugt `inhalt()` nur +den Kern; die Verpackung entsteht im Rahmen. Und die Reihenfolge ist eine +Liste — genau das, was „Widgets verschieben" braucht. ## 2 · Erste Widgets @@ -424,7 +409,7 @@ schon gebaut) oder am Handy Pfeiltasten statt Ziehen anbieten. Nicht vergessen: Ohne das ist die Funktion am Handy schlicht nicht da. Voraussetzung ist ein Widget-Verzeichnis — heute stehen Benutzer und Wetter -fest im HTML. Sinnvoll zusammen mit dem Widget-Rahmen (1.5) zu bauen, nicht +fest im HTML. Sinnvoll zusammen mit dem Widget-Rahmen (1.4) zu bauen, nicht davor. ### Widgets in der HoneyBar selbst @@ -434,9 +419,9 @@ ist aber die Seite, die man am seltensten offen hat — die HoneyBar dagegen ist überall. Naheliegend also: ein Knopf in der HoneyBar klappt dasselbe Widget als kleines Feld auf, ohne die App zu verlassen. -Technisch ist das dieselbe Sache wie 1.5, nur ein zweites Zuhause für denselben +Technisch ist das dieselbe Sache wie 1.4, nur ein zweites Zuhause für denselben Inhalt: Der Rahmen muss dann drei Formen können — Fenster, Kachel, Aufklappfeld. -Das ist beim **Bauen** von 1.5 zu bedenken, hinterher wird es teuer. +Das ist beim **Bauen** von 1.4 zu bedenken, hinterher wird es teuer. Für Widgets, die nur sich selbst brauchen (Wetter, Bild, Uhr), ist das geschenkt — sie fragen HoneyOS, nicht die App. @@ -445,6 +430,48 @@ geschenkt — sie fragen HoneyOS, nicht die App. ## 3 · Ausbau +### 3.x HoneyOS selbst als PWA — zurückgestellt (30.08.2026) + +Heute ist HoneyOS eine Website: Es hat `theme-color`, aber kein Manifest, kein +Symbol und keinen Service Worker — installierbar ist es damit nicht. Am Handy +landet man also im Browser mit Adressleiste, während Aether und InkNote als +eigene Apps danebenliegen. Das ist genau der Bruch, den das Ganze eigentlich +verhindern soll: Der Schreibtisch sollte sich am wenigsten nach Website +anfühlen. + +Was dazugehört, klein bis groß: + +1. **Manifest** — Name, Startadresse, `display: standalone`, Hintergrund- und + Themenfarbe. Die Farben sollten der eingestellten Röhre folgen; sie liegen + im Server, das Manifest kann also erzeugt statt fest hinterlegt werden + (Aether macht das beim Symbol schon so). +2. **Symbol** — ein Pixelglyph in 192 und 512 px, im Amber-Look. Passt zum + Rest und ist ohne fremde Werkzeuge zu machen. +3. **Service Worker** — nötig für die Installierbarkeit. Hier ist zu + entscheiden, was er überhaupt soll (siehe unten). + +**Die eigentliche Frage ist der Offline-Fall.** HoneyOS zeigt fremde Apps und +später deren Daten — ohne Netz gibt es davon nichts. Ein Schreibtisch, der +offline mit leeren Kacheln dasteht, ist schlimmer als ein ehrlicher Hinweis. +Sinnvoll wäre: Oberfläche und App-Liste im Cache, damit man die Symbole sieht +und antippen kann, und ein deutlicher Vermerk „ohne Verbindung". Widgets +bleiben leer, statt Altes von gestern als aktuell auszugeben. + +**Aus dem Aether-Debakel gelernt und hier von Anfang an zu beachten:** Ein +Eintrag im Cache ist eine ganze Antwort **samt Kopfzeilen**. Wird die Startseite +vorab abgelegt, friert damit auch ihre CSP ein — und eine Änderung an der +Konfiguration erreicht den Browser nie wieder. Also Navigationen Netz zuerst, +Cache nur als Rückfall. + +**Zurückgestellt (Marc, 30.08.2026): erst die Hauptfunktionen, dann das +Gehäuse.** Die PWA ist mehr Gefühl als Funktion — sie macht nichts möglich, was +heute nicht ginge, während der Widget-Rahmen darunter alles Weitere trägt. + +**Reihenfolge:** Erst umziehen (siehe unten), dann installierbar machen. Ein +Ursprungswechsel macht aus einer installierten PWA eine fremde App — wer +vorher installiert, installiert zweimal. + + - Welche Widgets wo liegen, **pro Nutzer im Server** merken — die Einstellungen liegen ohnehin schon dort, das ist eine Erweiterung, kein Neubau - Widgets im Terminal-Modus diff --git a/README.md b/README.md index 539c33d..d2d09b6 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,36 @@ in beiden: Menüleiste, Palette, Einstellungen, App-Liste. **Tasten:** `M` Betriebsart · `S` Einstellungen · `?` Hilfe · `Esc` schließt Startmenü, oberstes Fenster oder Dialog — in dieser Reihenfolge. +## Widgets + +Ein Widget ist eine **Beschreibung**, kein Kasten in der `index.html`: + +```js +widgetAnmelden({ + id: "wetter", titel: "wetter", + wo: ["terminal", "desktop"], // wo es erscheinen darf + sichtbar: () => state.settings.weatherDesk === "on", + laden: async () => …, // false = es gibt nichts zu zeigen + leer: "verbergen", // oder ein Satz, der dann dasteht + inhalt: () => "", + takt: 10 * 60 * 1000, // Nachladen, optional +}) +``` + +Den Rest macht `web/js/widgets/rahmen.js`: Im Terminal-Modus wird daraus ein +Fenster in der linken Spalte, im Desktop-Modus ein Schild oben rechts. Er kennt +vier Zustände — **lädt · fertig · nichts da · Fehler** — und hält den +Nachladetakt. + +**Ein Widget, das seinen Server nicht erreicht, sagt das in seinem eigenen +Rahmen.** Es verschwindet nicht (das sähe aus wie abgeschaltet) und reißt weder +den Schreibtisch noch die anderen Widgets mit. Wo „nichts da" schlicht heißt, +dass es nichts zu zeigen gibt, kann ein Widget mit `leer: "verbergen"` ganz +wegbleiben — so macht es das Wetter ohne eingestellten Ort. + +Eine neue Anzeige ist damit eine Datei in `web/js/widgets/` und ein Import in +`os.js`. Weder das Raster noch die Seite werden dafür angefasst. + ## Wetter Ein Pixelsymbol mit der Temperatur — wahlweise auf dem Schreibtisch, in der diff --git a/internal/weather/faketest.go b/internal/weather/faketest.go index 286cc96..c36b554 100644 --- a/internal/weather/faketest.go +++ b/internal/weather/faketest.go @@ -15,8 +15,22 @@ import ( // Open-Meteo an — das wäre langsam, unzuverlässig und benutzte einen fremden // Dienst für unsere Testläufe. func FakeFuerTest(t *testing.T, code int, grad float64) *Dienst { + return fake(t, code, grad, false) +} + +// KaputtFuerTest stellt einen Dienst, der nicht antwortet — für den Fall, den +// die Oberfläche seit dem Widget-Rahmen anders behandelt als „nichts da". +func KaputtFuerTest(t *testing.T) *Dienst { + return fake(t, 0, 0, true) +} + +func fake(t *testing.T, code int, grad float64, kaputt bool) *Dienst { t.Helper() srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if kaputt { + w.WriteHeader(http.StatusInternalServerError) + return + } if strings.Contains(r.URL.Path, "search") { json.NewEncoder(w).Encode(map[string]any{"results": []map[string]any{ {"name": "Köln", "country": "Deutschland", "admin1": "Nordrhein-Westfalen", diff --git a/main.go b/main.go index c4aaa85..c98a506 100644 --- a/main.go +++ b/main.go @@ -380,7 +380,12 @@ func (s *Server) handleWeather(w http.ResponseWriter, r *http.Request, sub strin wetter, err := s.wetter.Jetzt(r.Context(), set["weatherCity"], lat, lon) if err != nil { log.Printf("wetter: %v", err) - w.WriteHeader(http.StatusNoContent) + // 502, nicht 204: Ein Ort ist eingestellt, es ist also etwas zu zeigen — + // der Dienst ist nur gerade nicht da. Die Oberflaeche unterscheidet das + // seit dem Widget-Rahmen: „nichts eingestellt" verschwindet still, + // „nicht erreichbar" sagt es. Beides als 204 zu melden hiess, dass ein + // Ausfall aussah wie ein abgeschaltetes Widget. + writeJSON(w, http.StatusBadGateway, map[string]string{"error": "Wetterdienst nicht erreichbar"}) return } writeJSON(w, http.StatusOK, wetter) diff --git a/web/css/style.css b/web/css/style.css index bc0f08b..652f199 100644 --- a/web/css/style.css +++ b/web/css/style.css @@ -284,14 +284,11 @@ body { grid-template-columns: 320px minmax(0, 1fr); align-items: start; } - /* Ohne feste Plätze verteilt das Raster die drei Bereiche der Reihe nach — - das Wetter landete dann neben „system" und schob die Kacheln in die - schmale linke Spalte. Links stehen die Anzeigen untereinander, rechts - gehören die Apps, über die volle Höhe. */ - .win-user { grid-column: 1; grid-row: 1; } - .win-status { grid-column: 1; grid-row: 2; } - .win-weather { grid-column: 1; grid-row: 3; } - .tiles { grid-column: 2; grid-row: 1 / span 3; } + /* Zwei Bereiche, keine Einzelplätze mehr: Links die Widgetspalte, rechts + die Apps. Vorher hatte jedes Widget seinen festen Platz im Raster — beim + dritten hätte man das Raster wieder anfassen müssen. */ + .widgetspalte { grid-column: 1; } + .tiles { grid-column: 2; } } /* Fenster */ @@ -1028,9 +1025,7 @@ body { white-space: nowrap; } -/* Terminal-Modus: ein Fenster wie die anderen. Schmal gehalten — über die - volle Breite gezogen sähe eine Temperatur nach Bekanntmachung aus. */ -.win-weather { max-width: 320px; align-self: start; } + /* Schreibtisch-Modus: schwebendes Schild, rechts unter der Menüleiste. Nicht @@ -1101,16 +1096,30 @@ body { .uw-name { color: var(--p); font-size: 15px; } .uw-gruss { color: var(--p-mid); } .uw-host { color: var(--p-dim); font-size: 12px; } -.win-user { max-width: 320px; align-self: start; } +/* Die Spalte selbst hält die Breite — vorher trug sie jedes Widget einzeln, + und ein neues ohne diese Zeile stand quer. */ +.widgetspalte { + display: flex; + flex-direction: column; + gap: clamp(14px, 3vw, 28px); + align-self: start; + min-width: 0; +} +@media (min-width: 860px) { + .widgetspalte { max-width: 320px; } +} + +/* Die vier Zustände, die der Rahmen kennt. „lädt" und „nicht erreichbar" + stehen im selben Kasten wie der Inhalt — ein Widget, das bei einem Fehler + verschwindet, sieht aus wie eines, das man nie eingeschaltet hat. */ +.w-zustand { color: var(--p-dim); margin: 0; font-size: 12.5px; } +.w-fehler { color: var(--p-mid); } /* Einspaltig (Handy, schmales Fenster) sollen die Widgets so breit sein wie das Systemfenster. Drei verschieden breite Kästen untereinander sehen aus wie ein Fehler, nicht wie Absicht. Muss hinter den Regeln stehen, die es aufhebt — eine Medienabfrage erhöht die Spezifität nicht. */ -@media (max-width: 859px) { - .win-weather, - .win-user { max-width: none; } -} + /* Die Schilder auf dem Schreibtisch stapeln sich oben rechts. */ .deskwidgets { diff --git a/web/index.html b/web/index.html index eb9027e..9868deb 100644 --- a/web/index.html +++ b/web/index.html @@ -47,30 +47,10 @@
- -
-
system●●●
-
-
-
-
- - - - - - - - + +
@@ -78,11 +58,8 @@