feat(v5): Update sichtbar machen — Meldung, Knopf und Changelog
Ampel / ampel (push) Successful in 1m33s
Ampel / ampel (push) Successful in 1m33s
Der Commander: "irgendwie fehlt in rippy selbst der Button 'Auf updates pruefen' oder auch direkt beim start ne meldung 'ey jo update verfuegbar' - waere noch cool wenn das kommt inkl. changelog". Zu Recht. Der Kanal arbeitete korrekt, war aber UNSICHTBAR: Der Update-Stand lag im Haupt und wurde nur beim Laden des Fensters EINMAL mitgeschickt. Fand die Pruefung etwas, aenderte sich am Bildschirm nichts; der einzige Hinweis war ein Satz unter "Update-Adresse", den man kennen musste, um ihn zu finden. Was jetzt da ist: * Eine goldene Karte OBEN in der Uebersicht, sobald etwas gefunden wird — mit neuer Versionsnummer, der laufenden Version, Ladefortschritt und den Aenderungsnotizen. Dazu ein Windows-Toast beim Fund. * Knopf "Jetzt auf Updates pruefen" in den Einstellungen. Waehrend eines Rips ist er AUS, mit Begruendung im Tooltip — Rippy sucht dann bewusst nicht (Paragraph 6.8). * Knopf "Jetzt neu starten und installieren" auf der Karte, sobald das Paket geladen ist. Sagt NEIN mit Grund statt still nichts zu tun (R4). * Der Stand meldet JEDE Aenderung sofort ans Fenster (setzen() ruft immer beiAenderung) — kein Zustand mehr, den niemand erfaehrt. Der Changelog kommt aus bau/release-notes.md. An der installierten Bibliothek geprueft statt aus dem Kopf (Regel D): app-builder-lib out/publish/updateInfoBuilder.js getReleaseInfo() liest genau diesen Dateinamen und legt den Inhalt als releaseNotes in latest.yml; builder-util-runtime deklariert UpdateInfo.releaseNotes als "string | Array<ReleaseNoteInfo> | null". neuerungenText() vertraegt beide Formen, wirft HTML-Marken raus und deckelt die Laenge. UpdateStand ist jetzt EINE Definition in gemeinsam/nachrichten.ts, die sich Haupt und Fenster teilen. 230 Tests gruen (vorher 225), Typpruefung sauber. Version 5.1.2. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7d496cf654
commit
c0a7618e33
@@ -782,6 +782,9 @@ function Einstellungen({ verbindung, haupt }: { verbindung: Verbindung; haupt: H
|
||||
const s = verbindung.schluessel
|
||||
const b = verbindung.beschaffung
|
||||
const empfehlungFuer = (typ: string): string => presetEmpfehlung(typ, backends, presets)
|
||||
// § 6.8: Der Prüf-Knopf ist während eines Rips aus — nicht, weil er
|
||||
// nicht könnte, sondern weil Rippy dann bewusst nicht sucht.
|
||||
const ripLaeuftGerade = Object.values(verbindung.rips).some((r) => LAUFENDE_PHASEN.includes(r.phase))
|
||||
const [aktiv, setAktiv] = useState('ablage')
|
||||
|
||||
const SEKTIONEN: Array<[string, string]> = [
|
||||
@@ -1032,6 +1035,25 @@ function Einstellungen({ verbindung, haupt }: { verbindung: Verbindung; haupt: H
|
||||
{verbindung.kern !== null && ` · Kern: Node ${verbindung.kern.nodeVersion} (PID ${verbindung.kern.pid})`}
|
||||
</p>
|
||||
<p className="text-nebel/70">Updates warten, solange ein Rip läuft — nie mitten im Vorgang.</p>
|
||||
<div className="mt-2 flex flex-wrap items-center gap-2">
|
||||
<button
|
||||
onClick={() => void window.rippy.updatePruefen()}
|
||||
disabled={ripLaeuftGerade || haupt.update.laeuft}
|
||||
className="rounded-lg border border-white/15 bg-samt/60 px-3 py-1 text-[11px] text-dunst hover:text-schnee disabled:opacity-35"
|
||||
title={
|
||||
ripLaeuftGerade
|
||||
? 'Während eines Rips wird nicht gesucht — das ist Absicht (§ 6.8).'
|
||||
: 'Fragt sofort beim Update-Server nach.'
|
||||
}
|
||||
>
|
||||
{haupt.update.laeuft ? 'sucht …' : 'Jetzt auf Updates prüfen'}
|
||||
</button>
|
||||
{haupt.update.neueVersion.length > 0 && (
|
||||
<span className="text-gold-hell">
|
||||
Version {haupt.update.neueVersion} gefunden — siehe Übersicht.
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</SektionsKarte>
|
||||
@@ -1296,6 +1318,9 @@ type Bereich = 'uebersicht' | 'einstellungen' | 'bibliothek'
|
||||
|
||||
export default function App() {
|
||||
const [haupt, setHaupt] = useState<HauptStatus | null>(null)
|
||||
// Sagt ein Update-Knopf NEIN, steht der Grund daneben (R4) — ein Knopf,
|
||||
// der nichts tut und nichts sagt, ist schlimmer als kein Knopf.
|
||||
const [updateHinweis, setUpdateHinweis] = useState('')
|
||||
// #einstellungen / #bibliothek als Startanker — der Smoke-Beweis
|
||||
// fotografiert damit auch die anderen Bereiche.
|
||||
const [bereich, setBereich] = useState<Bereich>(() => {
|
||||
@@ -1407,6 +1432,62 @@ export default function App() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* § 6.8: Ein verfügbares Update ist eine SICHTBARE Karte, kein
|
||||
Satz in den Einstellungen. Der Commander musste es am
|
||||
01.09.2026 suchen — gefunden hat er es nicht. */}
|
||||
{haupt !== null && haupt.update.neueVersion.length > 0 && (
|
||||
<div className="rounded-2xl border border-gold/40 bg-gold/10 p-4">
|
||||
<div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
|
||||
<span className="font-display text-lg text-gold-hell">
|
||||
Update verfügbar — Version {haupt.update.neueVersion}
|
||||
</span>
|
||||
<span className="font-mono text-xs text-nebel">du hast {haupt.version}</span>
|
||||
</div>
|
||||
<p className="mt-1 font-mono text-xs text-dunst">{haupt.update.text}</p>
|
||||
{haupt.update.fortschritt >= 0 && !haupt.update.bereit && (
|
||||
<div className="mt-2 h-1.5 w-full overflow-hidden rounded-full bg-white/10">
|
||||
<div
|
||||
className="h-full rounded-full bg-gold transition-all"
|
||||
style={{ width: `${Math.max(0, Math.min(100, haupt.update.fortschritt))}%` }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{haupt.update.neuerungen.length > 0 && (
|
||||
<div className="mt-3 rounded-xl border border-white/8 bg-buehne/60 p-3">
|
||||
<p className="text-[11px] uppercase tracking-wider text-nebel">Was neu ist</p>
|
||||
<p className="mt-1 whitespace-pre-wrap font-mono text-xs leading-5 text-dunst">
|
||||
{haupt.update.neuerungen}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{haupt.update.bereit && (
|
||||
<div className="mt-3 flex flex-wrap items-center gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setUpdateHinweis('')
|
||||
void window.rippy.updateInstallieren().then((urteil) => {
|
||||
if (!urteil.ok) setUpdateHinweis(urteil.grund)
|
||||
})
|
||||
}}
|
||||
disabled={einRipLaeuft}
|
||||
className="rounded-lg border border-gold/40 bg-gold/15 px-3 py-1.5 text-xs font-semibold text-gold-hell hover:bg-gold/25 disabled:opacity-35"
|
||||
title={
|
||||
einRipLaeuft
|
||||
? 'Erst wenn der Rip fertig ist — Rippy startet dafür neu.'
|
||||
: 'Rippy beendet sich, installiert und startet wieder.'
|
||||
}
|
||||
>
|
||||
Jetzt neu starten und installieren
|
||||
</button>
|
||||
<span className="font-mono text-[11px] text-nebel">
|
||||
… oder einfach nichts tun: Beim nächsten Beenden passiert es von selbst.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{updateHinweis.length > 0 && <p className="mt-2 font-mono text-xs text-warn">{updateHinweis}</p>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<section>
|
||||
{verbindung.laufwerke.length === 0 ? (
|
||||
<div className="flex items-center gap-5 rounded-2xl border border-white/10 bg-karte/50 p-6">
|
||||
|
||||
+2
@@ -9,6 +9,8 @@ declare global {
|
||||
melden(art: string): void
|
||||
ordnerWaehlen(titel: string): Promise<string | null>
|
||||
autostartSetzen(an: boolean): Promise<boolean>
|
||||
updatePruefen(): Promise<unknown>
|
||||
updateInstallieren(): Promise<{ ok: boolean; grund: string }>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user