Making-of: JPKCom Desktop — mein Desktop im Browser, klein und offen

JPKCom Desktop im Making-of: warum ich einen eigenen Desktop für den Browser gebaut habe — klein, schnell, barrierefrei, sicher und Open Source unter MIT.

von ·

Eine Oberfläche wie bei einem Betriebssystem, nur im Browser — das war für mich schon immer die schönste Form, die eine Website annehmen kann. Fenster, die ich verschieben kann, eine Menüleiste, ein Dock, ein Terminal, das wirklich etwas tut: So eine Oberfläche wollte ich seit Langem haben, für meine eigene Website und für meinen Arbeitsalltag. Mit dem, was es dafür gab, bin ich nie richtig glücklich geworden. Also habe ich sie selbst gebaut: den JPKCom Desktop, seit Oktober 2026 Open Source unter der MIT-Lizenz.

Der JPKCom Desktop ist eine Desktop-Oberfläche für Websites — Fenster, Menüleiste, Dock, Suche, Terminal und Apps — aus nativen ES-Modulen, ohne Framework, ohne Build-Schritt und ohne Laufzeit-Abhängigkeiten; er läuft als statische Dateien auf jedem Webserver. Hier zeige ich dir, warum ich ihn gebaut habe und was ihn klein, schnell, sicher und barrierefrei macht. Den Quellcode findest du im Repository auf GitHub (öffnet in neuem Tab), ausprobieren kannst du ihn sofort in der Live-Demo (öffnet in neuem Tab).

Wenn du wenig Zeit hast: Lies Mehr Ordnung im Arbeitsalltag und Klein, schnell, effizient. Wie du daraus deine eigene Seite machst, steht Schritt für Schritt in meiner Anleitung zum JPKCom Desktop.

Zur Methodik: Alle Zahlen stammen aus dem Repository des Desktops, Stand Version 1.4.0, aus dem Testlauf mit npm test und aus meinen eigenen Messungen während der Entwicklung; die Ladezeiten habe ich für Version 1.1.0 gemessen. Wo ich gemessen habe, sage ich dazu, wie.

Warum überhaupt ein eigener Desktop

Die Idee eines Desktops im Browser ist alt, und ich habe mir über die Jahre viele Umsetzungen angesehen. Gestört hat mich fast immer dasselbe: ein schweres Framework unter der Haube, eine Build-Kette, die gepflegt werden will, Code von Dritten, der beim Laden fremde Server anfragt, Fenster, die sich nur mit der Maus bedienen lassen, oder ein Ergebnis, das ich nicht an meine Website anpassen konnte, ohne es auseinanderzunehmen. Das ist kein Vorwurf. Viele dieser Projekte sind als Vorführung gedacht und funktionieren als solche. Ich wollte aber ein Werkzeug, mit dem ich jeden Tag arbeite.

Einen eigenen Desktop gibt es auf meiner Website schon länger — anfangs bewusst als Alpha: eine Machbarkeitsstudie und ein interner Test. Mit ihm habe ich den finalen Plan entwickelt, Fehler früh gefunden und behoben und die Bedienung Schritt für Schritt weiterentwickelt. Für GitHub und Open Source war er noch nicht gedacht, unter anderem, weil er kommerziell lizenzierte Icons nutzte, die nicht in ein öffentliches Repository dürfen. Für die offene Fassung habe ich die Architektur deshalb neu aufgesetzt.

Als ich daraus ein offenes Projekt machen wollte, standen meine Kriterien fest:

  • Klein. Keine Laufzeit-Abhängigkeiten, kein Framework, kein Build-Schritt.
  • Schnell. Der Browser lädt nur, was er gerade braucht.
  • Sicher. Eine strikte Content Security Policy (CSP), kein Code von Dritten, kein Tracking.
  • Bedienbar. Mit Tastatur, Screenreader und auf dem Handy, in beliebig vielen Sprachen.
  • Offen. MIT-Lizenz, und alles, was du für deine Seite änderst, liegt in einem Ordner.

Mehr Ordnung im Arbeitsalltag

Ein Desktop im Browser ist nicht nur hübsch. Für mich ist er vor allem ein Ordnungssystem. Bei der Administration von Netzwerken, Servern und Websites brauche ich ständig dieselben Ziele: die Oberfläche eines Routers, das Panel eines Servers, die Dokumentation eines Webservers, meine Notizen zur letzten Änderung. Im Desktop liegt das an einem Ort, und ich komme mit wenigen Tastendrücken hin.

Lesezeichen als Sammlungen

Lesezeichen sind im Desktop keine lose Liste, sondern Sammlungen mit Gruppen, Icons und Beschreibungen in jeder Sprache. Definiert werden sie in site/apps.js. So sieht die mitgelieferte Beispielsammlung aus:

site/apps.js (Auszug)
	collections: [
		{
			id: 'bookmarks', prefix: 'link', icon: 'ti-bookmarks', tint: 'indigo', sort: 'alpha', itemKind: 'link',
			name: { en: 'Bookmarks', de: 'Lesezeichen' },
			allLabel: { en: 'All bookmarks', de: 'Alle Lesezeichen' },
			// …
			groups: [
				{ id: 'servers', icon: 'ti-server', tint: 'teal',
					name: { en: 'Web servers', de: 'Webserver' },
					desc: { en: 'Documentation of the servers the desktop runs on', de: 'Dokumentation der Server, auf denen der Desktop läuft' } },
				// …
			],
			items: [
				{ slug: 'apache', group: 'servers', icon: 'ti-feather', name: 'Apache HTTP Server', url: 'https://httpd.apache.org/docs/current/',
					desc: { en: 'Documentation of the Apache web server', de: 'Dokumentation des Apache-Webservers' } },
				// …

Das Schöne daran: Jeder Eintrag wird zu einer eigenen App. Ich kann ihn suchen und ins Dock heften. Der Katalog zeigt die ganze Sammlung mit Gruppen in der Seitenleiste, einem Suchfeld und einem Symbolraster, das sich komplett mit den Pfeiltasten bedienen lässt. Im Terminal erscheinen Sammlungen als Verzeichnisse, durch die ich mit ls, cd und open laufe.

Für die Administration wichtig: Grundsätzlich nimmt der Desktop für Lesezeichen nur https-Adressen an. Mit allowHttp erlaubst du für eine Sammlung oder einen einzelnen Eintrag ausdrücklich auch http:// — genau für das Intranet-Lesezeichen auf den Router oder das NAS ohne Zertifikat. Links, die niemand sehen soll, gehören in den Tresor, dazu mehr im Abschnitt zur Sicherheit.

Die Suche führt schnell zum Ziel

Das Herz meines Alltags ist die Suche. Mit Strg+K (auf dem Mac ⌘+K) oder einfach / auf dem Schreibtisch öffnet sie sich unter der Menüleiste. Durchsucht werden alle Apps, jede Sammlung als eigene Gruppe und die Beiträge der Module. Jedes Wort, das ich tippe, muss vorkommen, und ein Treffer am Anfang des Namens zählt mehr als einer in der Beschreibung. Drei Buchstaben wie „ngi“ genügen, und die nginx-Dokumentation steht oben; Enter öffnet sie.

Weil die Suche eine eigene Adresse hat, lässt sie sich auch als Browser-Lesezeichen ablegen: #search= mit den Suchwörtern dahinter öffnet den Desktop direkt mit dem passenden Ergebnis. Für Screenreader ist das Suchfeld eine Combobox, und die Zahl der Treffer wird angesagt, sobald ich mit dem Tippen innehalte.

Fenster statt Tab-Wechsel

Der zweite große Gewinn sind die Fenster. Im Browser habe ich für jedes Werkzeug einen Tab, sehe aber immer nur einen davon. Im Desktop stehen mehrere Werkzeuge nebeneinander in einem einzigen Tab:

Ein Tab pro Werkzeug — oder ein Tab für alles
 Vorher: ein Tab pro Werkzeug       Jetzt: ein Tab, viele Fenster
┌──────────────────────────────┐   ┌──────────────────────────────┐
│[Router][NAS][Server][Notiz]… │   │ Ξ  Menüleiste   Strg+K  12:04│
├──────────────────────────────┤   ├──────────────────────────────┤
│                              │   │┌────────────┐┌──────────────┐│
│  sichtbar ist genau einer    │   ││ Terminal   ││ Notizen      ││
│                              │   ││ $ dig …    ││ Firewall: …  ││
│  den Rest behalte ich im     │   │└────────────┘└──────────────┘│
│  Kopf, suche den richtigen   │   │┌────────────────────────────┐│
│  Tab, wechsle, verliere den  │   ││ Katalog: Lesezeichen       ││
│  Faden …                     │   │└────────────────────────────┘│
│                              │   ├──────────────────────────────┤
│                              │   │  Dock  ◇ ◇ ◇ ◇ ◇             │
└──────────────────────────────┘   └──────────────────────────────┘

Rechts siehst du, wie ich arbeiten will: Terminal, Notizen und Katalog gleichzeitig im Blick. Fenster lassen sich verschieben, in der Größe ändern, zoomen und am Rand an einer Hälfte einrasten. Mit F3 oder Strg+↑ zeigt die Fensterübersicht alle offenen Fenster nebeneinander. Und am nächsten Tag stellt der Desktop bis zu 20 Fenster an ihren alten Plätzen wieder her, wenn ich das nicht abgeschaltet habe.

Eine Grenze gibt es: Viele Verwaltungsoberflächen von Routern und Servern verbieten, in einem fremden Rahmen angezeigt zu werden, und Seiten anderer Herkunft bettet der Desktop ohnehin nur ein, wenn du das in der CSP erlaubst. Solche Lesezeichen öffnen deshalb in einem neuen Tab. Alles, was der Desktop selbst mitbringt — Terminal, Editor, Notizen, Aufgaben, Rechner, Katalog, die Seiten deiner Website im Seitenfenster (Reader) —, läuft aber nebeneinander in Fenstern.

Mit 1.2.0 ist der Desktop für meine Art zu arbeiten noch aufgeräumter geworden. Jeder Eintrag meiner Sammlungen in site/apps.js kann ein eigenes Handbuch haben, in jeder Sprache, und man zeigt es im Terminal an. Und Webfenster mit Seiten außerhalb des Desktop-Ordners, etwa einem Wiki unter /wiki/, stehen nach dem Neuladen wieder auf ihrer letzten Unterseite. Und seit 1.3.0 bleiben Apps im Dock, auch wenn ich sie umbenenne und die alte ID als Alias behalte.

Die Architektur: ein kleiner Kern, alles andere Module

Die wichtigste Erkenntnis aus der Machbarkeitsstudie steht im Architektur-Vertrag des Projekts — der Datei docs/ARCHITECTURE.md, die jede Schnittstelle verbindlich festlegt — als Grundsatz: Alles Optionale ist ein Modul. Der Kern kennt keine konkrete App, keine Website, keinen Anbieter und keine Sprache. Module reden über einen Bus, über Registries und über Services miteinander, nie über die Interna eines anderen Moduls. Fehlt ein optionales Modul, wirft nichts einen Fehler.

So sind die Schichten verteilt:

Die Schichten des Desktops
┌──────────────────────────────────────────────────────────────┐
│ site/     config.js · apps.js · theme.css · content/ · data/ │
│           vault/ · wallpapers/ · modules/   ← gehört dir     │
├──────────────────────────────────────────────────────────────┤
│ Apps      editor · notes · todo · calc · terminal · media ·  │
│           fortune                       (src/apps/<id>/)     │
├──────────────────────────────────────────────────────────────┤
│ Module    reader · viewer · catalog · search · calendar ·    │
│           holidays · weather · notify · vault                │
├──────────────────────────────────────────────────────────────┤
│ Core-     wm (Fenster) · shell (Menüleiste, Dock, Alle Apps) │
│ Parts     panels (Einstellungen, Papierkorb, Hilfe …)        │
├──────────────────────────────────────────────────────────────┤
│ Core      config · env · store · bus · i18n · dom · icons ·  │
│           a11y · registry · router · net · consent · …       │
└──────────────────────────────────────────────────────────────┘
   ▲  Querverbindung nur über Bus · Registries · Services
   └─ nie über Imports fremder Interna

Unten liegen 23 Kerndateien, darauf die drei Kernteile Fenstermanager, Shell und Panels, dann neun optionale Module und sieben App-Pakete. Das Prinzip „site/ ist deins, src/ ist das Projekt“ macht Updates einfach: Du tauschst die Projektdateien aus, deine Inhalte bleiben unberührt.

Jedes Teil beschreibt sich selbst mit einem Deskriptor, einem schlichten Objekt als Default-Export. Die mitgelieferte Beispiel-App „Hello“ zeigt, wie wenig dazu gehört:

site/modules/hello/index.js (Auszug)
export default {
	id: 'hello',
	kind: 'app',
	i18n: ['hello'],
	locales: 'locales/',            // the texts: locales/<lang>/hello.js next to this file
	styles: ['hello.css'],

	app: { icon: 'ti-mood-smile', tint: 'green', size: [420, 360], name: '@hello.appName', desc: '@hello.appDesc' },

	storage: {
		[KEY]: { type: 'json', backup: true, reset: KEY, label: '@hello.appName', validate: clean }
	},
	// …
	terminal: {
		[KEY]: { run: command, help: '@hello.cmd', usage: 'hello [name]', man: '@hello.cmdMan' }
	},

	mount,
	// …
};

Der Deskriptor meldet einen gespeicherten Wert an, der in die Sicherung wandert, zurückgesetzt werden kann und beim Lesen geprüft wird, und er bringt ein eigenes Terminal-Kommando mit. Schlägt das setup() eines Moduls fehl, rollt der Kern zurück, was es bis dahin angemeldet hatte. Ein kaputtes Modul reißt also nicht den ganzen Desktop mit.

Klein, schnell, effizient

„Klein“ ist bei mir eine Liste von Dingen, die nicht im Projekt sind: null Laufzeit-Abhängigkeiten, kein Bundler, kein CDN. Der Desktop besteht aus nativen ES-Modulen, die der Browser so lädt, wie sie im Repository liegen. In der package.json stehen genau zwei Entwicklungswerkzeuge: die Quellen der Tabler-Icons und playwright-core für die Browser-Prüfungen. Beide braucht nur, wer am Projekt arbeitet. Von den Tabler-Icons landen nur die 97 tatsächlich genutzten in einer generierten Datei von 17 KB.

Die ES-Module haben einen Preis, den ich bewusst bezahle: Der Desktop braucht einen Webserver, file:// reicht nicht. Dafür hat jede Datei ausdrückliche Abhängigkeiten, und kein Build-Schritt steht zwischen meinem Code und deinem Browser. Datei ändern, Seite neu laden, fertig.

Ohne Build-Schritt ist der Desktop aber nicht automatisch schnell, wie eine Messung vor 1.1.0 gezeigt hat: Beim ersten Besuch lud der Desktop 167 Dateien, davon 135 JavaScript-Module, rund 1,2 MB unkomprimiert. Schlimmer war die Tiefe. Bis zu 13 Ebenen statischer Imports hingen hintereinander, und jede Ebene kostet eine Rundreise zum Server.

Für 1.1.0 habe ich drei Dinge geändert. Erstens kommt der Code eines Fensters erst, wenn es zum ersten Mal geöffnet wird:

src/apps/notes/index.js (Auszug)
export default {
	id: 'notes',
	kind: 'app',
	i18n: ['notes', 'kit'],
	windowStyles: ['notes.css'],

	app: {
		icon: 'ti-note', tint: 'orange', size: [780, 520], name: '@notes.appName', desc: '@notes.appDesc',
		load: () => import('./window.js')
	},
	// …

Der kleine Deskriptor kommt beim Start, das eigentliche Fenster samt CSS erst beim Klick. Editor, Terminal, Player, Notizen, Aufgaben, Rechner, Glückskeks, die Inhalte der Panels, Reader, Bildbetrachter und Katalog laden jetzt alle so. Zweitens erzeugt ein Werkzeug die Datei src/boot/preload.js, die dem Browser den ganzen Startgraphen auf einmal ankündigt. Drittens laden Sprachdateien, Site-Daten und die Icon-Sets der Website parallel. Der Start sieht heute so aus:

Vom Aufruf bis zum fertigen Desktop
index.html
 │
 ├─► CSS: layers · tokens · base · components · site/theme.css
 ├─► site/config.js       klassisch: window.DESKTOP_CONFIG
 ├─► src/boot/preload.js  generiert: alle Startdateien auf einmal
 ├─► src/boot/theme.js    Farbschema vor dem ersten Paint
 └─► src/boot/main.js
      │
      ├─ initEnv
      ├─ initI18n ───┐
      ├─ Site-Daten ─┼─ parallel
      ├─ Icon-Sets ──┘
      ├─ expose() → window.JPKDesk
      ├─ modules.loadAll: Kernteile → Module → Apps (Deskriptoren)
      └─ body.is-ready + 'desk:ready'
           → Sitzung wiederherstellen, Deep-Links

 Erster Klick aufs Dock-Symbol
 ──► Fenster erscheint sofort, mit Ladeanzeige
 ──► import('./window.js') + windowStyles
 ──► mount() → 'window:ready'

Dazu kommt ein Schnellstart im Service Worker: Beim nächsten Besuch antwortet er mit dem Code aus seiner Offline-Kopie, prüft im Hintergrund auf eine neue Version und bietet dann an, neu zu laden. Feeds und Daten holt er seit 1.2.0 trotzdem zuerst frisch vom Server. Gemessen habe ich für 1.1.0 mit derselben Konfiguration in einem Headless-Chromium, unkomprimiert, jeweils als Median aus drei Läufen:

Messung vorher nachher
Anfragen und Datenmenge beim Start 167 / 1,15 MB 140 / 0,83 MB
HTTP/2 mit 150 ms Latenz 2,4 s 0,94 s
HTTP/2 mit 150 ms und 1,6 Mbit/s 7,8 s 5,3 s
Wiederholungsbesuch mit Service Worker, 150 ms und 1,6 Mbit/s 1,44 s 0,63 s

Auf der langsamen Leitung bremst vor allem die Datenmenge; ein echter Webserver mit gzip oder Brotli schneidet besser ab. Das sind Laborwerte, keine Feldmessung — aber die Richtung ist eindeutig.

Sicherheit: strikte CSP und kein fremder Code

Ein Desktop, in dem ich Notizen und private Links aufbewahre, muss dicht sein. Die Grundlage ist eine strikte Content Security Policy (öffnet in neuem Tab): default-src 'self'; script-src 'self'; style-src 'self', dazu object-src 'none' und frame-ancestors 'self'. Skripte und Styles kommen nur vom eigenen Server; Inline-Skripte, eval und style-Attribute verbietet die CSP.

HTML aus Zeichenketten schließt der Code selbst aus: Ich baue das DOM nur mit einer kleinen Hilfsfunktion namens h(). Die Funktion verweigert Schlüssel wie innerHTML, outerHTML oder insertAdjacentHTML mit einem Fehler und nimmt Styles nur als Objekt an, das sie über das CSSOM setzt. So sieht das in der Beispiel-App aus:

site/modules/hello/index.js (Auszug)
const field = h('input', { type: 'text', class: 'hello-input', id: fieldId, name: 'name', maxlength: String(MAX_NAME),
	autocomplete: 'off', props: { value: data.name } });
const label = h('label', { class: 'hello-label', for: fieldId });
const button = h('button', { type: 'submit', class: 'btn btn-primary' });
const output = h('output', { class: 'hello-greeting', for: fieldId, 'aria-live': 'polite' });
const opens = h('p', { class: 'hello-opens' });
const form = h('form', { class: 'hello-form' }, label, h('div', { class: 'hello-row' }, field, button));
body.append(h('div', { class: 'hello' }, form, output, opens));

Texte setzt der Desktop danach über textContent, nie als HTML. Im ganzen Quellcode wird innerHTML kein einziges Mal benutzt — es steht nur in der Liste der verbotenen Schlüssel und in einem Kommentar dazu.

Wie weit dieses Misstrauen reicht, zeigt der Bildbetrachter: Dateien vom eigenen Gerät zeigt er nur als Bild an, nie als eigenes Dokument im Ursprung des Desktops. Eine präparierte SVG-Datei könnte dort sonst als Dokument laufen und auf den lokalen Speicher zugreifen, also auf Notizen und den Schlüssel des Tresors.

Mit 1.2.0 habe ich dieses Misstrauen weiter ausgebaut. Seit eine Website eigene Icon-Sets mitbringen kann, läuft jede Icon-Definition durch eine Positivliste aus einfachen SVG-Formen und Darstellungsattributen — keine Ereignis-Attribute, keine Verweise, kein url(): Icon-Daten führen nie Code aus. Und Webfenster, die jetzt überall auf der Website stehen dürfen, kommen nie an den Ordner des Tresors oder an den Desktop selbst heran, auch nicht über kodierte oder anders geschriebene Pfade.

Mit 1.3.0 behält der Reader die Farben der Syntaxhervorhebung, aber nur aus einer kleinen Positivliste mit einfachen Farbwerten, die er selbst neu setzt — den Style-Text einer Seite übernimmt er nie. Und die Zeile des Tresors unter „Zurücksetzen“ sieht nur, wer ihn entsperrt hat.

Online-Dienste wie Wetter oder DNS-Abfragen sind standardmäßig aus. Damit eine Anfrage nach draußen geht, muss die Website den Dienst einschalten und du musst zustimmen. Anfragen an fremde Server gehen ohne Cookies und ohne Referrer raus. Tracking und Analytics gibt es nicht.

Für private Lesezeichen gibt es den Tresor. Ein Skript versiegelt eine JSON-Datei mit AES-256-GCM; der Schlüssel entsteht aus Benutzername und Passwort über PBKDF2 mit standardmäßig 600.000 Iterationen. Aus denselben Zugangsdaten leitet der Desktop auch den Dateinamen ab — falsche Zugangsdaten fragen also nach einer Datei, die es gar nicht gibt. Gedacht ist der Tresor für private Links, nicht für Geheimnisse.

Auch die Werkzeuge sind eine Angriffsfläche. Deshalb gelten im Repository dieselben Regeln wie in meinen anderen Projekten: Installationen über die Socket Firewall Free (sfw), eine .npmrc mit ignore-scripts, save-exact und engine-strict, exakte Versionen, GitHub Actions per Commit-SHA und npm audit signatures in der CI. Warum, steht in Sicheres Arbeiten mit Node.js und npm.

Usability und Barrierefreiheit

Ein Desktop, der nur mit der Maus funktioniert, wäre für mich kein guter Desktop. Barrierefreiheit (A11y, kurz für „Accessibility“) war deshalb von Anfang an Teil des Vertrags. Die Menüleiste ist eine echte Menüleiste mit Pfeiltasten-Navigation, jedes Fenster ein benannter Dialog. Der Fokus wird beim Öffnen gesetzt und beim Schließen dorthin zurückgegeben, wo er herkam. Kontextmenüs erreichst du per Rechtsklick, langem Druck oder Umschalt+F10. Was sich ändert, sagt der Desktop über eine gemeinsame Live-Region an.

Bewegung respektiert prefers-reduced-motion, in CSS und in JavaScript. Fokusringe brauchen mindestens 3:1 Kontrast, und ist eine eigene Akzentfarbe auf dem Hintergrund zu blass, mischt der Desktop sie für Fokusringe und Markierungen nach, bis sie dieses Verhältnis erreicht.

Sprachen behandelt der Desktop nicht als Paar, sondern als Liste. Deutsch und Englisch sind dabei, jede weitere Sprache ist ein Ordner unter locales/. Pluralformen folgen den CLDR-Regeln, Schriften von rechts nach links werden unterstützt, und fällt ein Text auf eine Ersatzsprache zurück, bekommt er das passende lang-Attribut. Wie Landmarks eine Oberfläche für Screenreader ordnen, beschreibe ich in HTML-Landmarks in der Praxis.

Open Source unter MIT

Ein Werkzeug, das nur bei mir läuft, hilft nur mir. Deshalb steht der JPKCom Desktop unter der MIT-Lizenz: Du darfst ihn nutzen, verändern, weitergeben und auch kommerziell einsetzen, ohne dass ich dir Bedingungen für deine eigene Website auferlege.

Damit „jeder kann es anpassen“ kein leeres Versprechen bleibt, liegt fast alles, was du als Betreiberin oder Betreiber änderst, in site/ — nur deine Icons und ein paar feste Zeilen in index.html und manifest.webmanifest kommen dazu. Das Repository ist ein Template auf GitHub, und der Schnellstart „Deine eigene Website in 10 Minuten“ (öffnet in neuem Tab) führt in fünf Schritten von der Kopie bis zur veröffentlichten Seite. Lokal startest du den Desktop so:

Lokal starten
git clone https://github.com/JPKCom/jpkcom-desktop.git
cd jpkcom-desktop
sfw npm ci         # nur Entwicklungswerkzeuge: Tabler-Quellen, Headless-Browser-Prüfung
npm run serve      # http://127.0.0.1:8080/ mit den Sicherheits-Headern der Produktion

Fürs Veröffentlichen liegen getestete Konfigurationen für Apache, nginx, Caddy, Ferron 2 und 3 sowie Static Web Server bei, dazu ein Workflow für GitHub Pages.

Eine Ausnahme ist mir wichtig: Das JPK-Monogramm und das JPKCom-Logo sind seit 1996 mein persönliches Logo. Keine eingetragene Marke, aber auch nicht MIT-lizenziert — alle Rechte bleiben bei mir. Der Code, der beides zeichnet, ist dagegen MIT. Details stehen im Abschnitt Lizenz und Brand-Assets der Anleitung. Um eines bitte ich dich: Lass die Credit-Zeile „JPKCom Desktop by Jean Pierre Kolb“ stehen.

Von der Machbarkeitsstudie zur Version 1.4.0

Am Anfang standen vier Grundsatzentscheidungen: Tabler-Icons statt der kommerziellen Icons der Machbarkeitsstudie. Native ES-Module statt klassischer Skripte, obwohl Letztere auch per file:// liefen. Die MIT-Lizenz, mit meinem Logo als Ausnahme. Und neutrale Namen: Funktionen heißen „Suche“, „Fensterübersicht“, „Katalog“ und „Alle Apps“, nicht wie ihre Gegenstücke in bekannten Betriebssystemen.

Gebaut habe ich den Desktop mit KI: Die Idee, die Richtung, jede Entscheidung und jede Freigabe kamen von mir, die Umsetzung übernahm Claude Code — von der Bestandsaufnahme der Machbarkeitsstudie über den Architektur-Vertrag bis zu Code, Tests und Reviews. Ergebnisse habe ich nicht ungeprüft übernommen. Wie ich generell mit KI arbeite, steht auf meiner Seite zur KI-Transparenz.

Danach kamen Live-Demo, Template, Schnellstart und Beispiel-App. Beim Haus-Theme hatte ich eine klare Bedingung: Die Umstellung auf 108 Design-Tokens durfte nichts Sichtbares verändern. Der Vergleich von 268 Ansichten vorher und nachher ergab null Unterschiede in den berechneten Styles und null Pixel Abweichung. Mit 1.1.0 kamen ein schnellerer Start und eine gehärtete Lieferkette dazu, mit 1.2.0 Handbücher pro Eintrag, eigene Icon-Sets und Webfenster überall auf der Website, mit 1.3.0 eigene Glyphen für die Symbole des Desktops und Code-Farben im Reader, mit 1.4.0 eine robustere Offline-Kopie und getrennte Speicher für mehrere Desktops auf einer Website. Heute laufen 675 Tests aus 40 Dateien grün, und die CI prüft jede Änderung auf Tests, Sprachen, Icons, Preload-Datei und Manifest.

Was bewusst fehlt

Genauso wichtig wie das, was drinsteckt, ist das, was ich weggelassen habe. Kein Framework und kein Bundler, weil beides mehr Wartung bedeutet hätte als Nutzen. Kein Konto und kein Server-Teil: Was du im Desktop speicherst, bleibt im Browser auf deinem Gerät. Kein Paketsystem für Themes und keine Theme-Auswahl für Besucherinnen und Besucher — Hell, Dunkel und Akzentfarbe wählen sie weiterhin selbst. Stattdessen passt du im Haus-Theme in site/theme.css Rundungen, Glas und Schatten über Tokens in einer eigenen Kaskadenebene an. Das reicht für eine eigene Handschrift, ohne dass ein zweites Produkt im Produkt entsteht.

Wie es weitergeht

Der Desktop auf jpkc.com/desktop läuft inzwischen mit diesem Projekt: Die bewusste Alpha aus der Machbarkeitsstudie habe ich auf Version 1.4.0 aktualisiert. Verloren ging nichts, weder meine Lesezeichen, die jetzt im verschlüsselten Tresor liegen, noch das Login für meine Werkzeuge. Meine privaten Inhalte liegen in einer eigenen site/-Konfiguration, die ein Skript beim Bauen auf genau ein festgelegtes Release setzt — ins öffentliche Repository gelangt davon nichts. Was mir dabei an allgemein Nützlichem auffiel, ist ins Projekt zurückgeflossen, in die Versionen 1.2.0 bis 1.4.0. Weitere Versionen kommen, wo der echte Einsatz nach ihnen fragt — bei mir und gern auch bei dir.

FAQ

Brauche ich Node.js, um den Desktop zu betreiben?

Nein. Der Desktop besteht nur aus statischen Dateien, die jeder Webserver ausliefern kann. Node.js 24 oder neuer brauchst du nur für die Entwicklungswerkzeuge wie Tests und Browser-Prüfung. Per Doppelklick auf index.html startet er allerdings nicht; lokal genügt npm run serve.

Funktioniert der Desktop auf dem Handy?

Ja. Auf schmalen Bildschirmen schaltet er in eine kompakte Ansicht, in der jedes Fenster als Karte den Platz über dem Dock füllt. Kontextmenüs öffnest du dort mit langem Druck. Als Progressive Web App (öffnet in neuem Tab) lässt er sich außerdem installieren und offline nutzen.

Darf ich den Desktop für meine Firma oder Kunden einsetzen?

Ja. Der Code steht unter der MIT-Lizenz und darf auch kommerziell genutzt, verändert und weitergegeben werden. Ausgenommen sind nur das JPK-Monogramm und das JPKCom-Logo, mein persönliches Logo. Dein eigenes Logo trägst du in site/config.js ein und ersetzt die Icons unter assets/icons/; wie, zeigt Schritt 2 des Schnellstarts (öffnet in neuem Tab).

Wie füge ich eine eigene App hinzu?

Kopiere den Ordner site/modules/hello/ und benenne alles um, was „hello“ heißt. Danach trägst du die App mit einer Zeile in site/config.js ein und lässt npm run preload laufen. Die Schritte im Detail stehen im Abschnitt Eine eigene App schreiben der Anleitung.

Fazit

Der JPKCom Desktop ist das Werkzeug, das ich mir lange gewünscht habe: ein Desktop im Browser, der mir im Alltag Ordnung gibt, mit Tastatur und Screenreader funktioniert, nichts Fremdes nachlädt und so klein bleibt, dass ich jede Zeile verstehe. Dass er jetzt offen ist, freut mich am meisten. Probier die Live-Demo (öffnet in neuem Tab) aus, mach mit „Use this template“ deine eigene Seite daraus — und wenn dir etwas fehlt, freue ich mich über ein Issue im Repository (öffnet in neuem Tab).

Weiterlesen

Alles zum Einrichten, Anpassen und Veröffentlichen findest du in meiner Anleitung zum JPKCom Desktop, von der Konfiguration in site/config.js über den Tresor bis zum Veröffentlichen. Was sich von Version zu Version geändert hat, steht im Changelog zum JPKCom Desktop; den Architektur-Vertrag liest du in docs/ARCHITECTURE.md (öffnet in neuem Tab) (englisch).

Die Grundlage des Haus-Themes erkläre ich in CSS @layer in der Praxis, und warum wenige bewegliche Teile auch nachhaltig sind, in Nachhaltige Websites. Mehr zur Ladezeit findest du in Core Web Vitals & Performance, und wie ich ein anderes eigenes Werkzeug gebaut habe, zeigt das Making-of meines SEO- und GEO-Tools.

Für den eigenen Server helfen die Cheat-Sheets zu Apache, .htaccess, nginx, Caddy, Ferron und zu den MIME-Typen.