BapsOS : Windows 98, reconstruit dans un onglet de navigateur
Un simulateur Windows 98 avec un shell nommé Fen98 et 27 applications qui fonctionnent, tournant sur un Patil Pentium II à 400 MHz avec 128,0 Mo de RAM.

- 27
- applications qui fonctionnent
Le contexte
BapsOS est un simulateur Windows 98 qui tourne dans un onglet de navigateur. Il a un shell qui porte un nom, Fen98, une séquence de démarrage complète, une séquence d’arrêt, et 27 applications qui fonctionnent vraiment au lieu d’ouvrir une image d’elles-mêmes.
La boîte de dialogue Propriétés système annonce un Patil Pentium II à 400 MHz avec 128,0 Mo de RAM. Aucun de ces composants n’existe. La boîte de dialogue en est sûre.
Le problème
Les recréations de bureaux rétro sur le web sont presque toujours des captures d’écran avec des états de survol. Vous cliquez sur Démarrer, un menu apparaît, vous cliquez sur Démineur, et vous obtenez un placeholder ou un lien vers le Démineur de quelqu’un d’autre. L’illusion s’effondre au deuxième clic, parce que le premier clic est le seul que quiconque ait testé.
Ce qui faisait qu’un système d’exploitation de bureau ressemblait à un lieu, ce n’était pas le fond d’écran. C’était que tout était vivant en même temps. Vous pouviez avoir un document ouvert derrière un jeu derrière un panneau de configuration, faire glisser une fenêtre à moitié hors de l’écran, la réduire, et la retrouver dans la barre des tâches là où vous l’aviez laissée. La profondeur, c’était le produit.
La question était donc de savoir si je pouvais construire la profondeur plutôt que la capture d’écran, seul, dans un navigateur.
Mon rôle
Seul. Design produit et ingénierie, les 27 applications, le shell, le gestionnaire de fenêtres, le design sonore, les fausses caractéristiques matérielles.
La moitié design, ce n’est pas reproduire au pixel près l’habillage des fenêtres, même s’il y a un peu de ça. C’est le design système en dessous : ce qu’est une fenêtre, ce qu’est une application, comment le shell en lance une, comment l’état persiste, et comment tout ça reste cohérent à travers 27 choses construites sur des mois. Ratez ça et la vingtième application est une réécriture de la première.
L’approche
J’ai construit le gestionnaire de fenêtres avant la moindre application, et j’ai refusé d’écrire une application tant qu’une fenêtre ne pouvait pas être ouverte, activée, déplacée, redimensionnée, réduite, agrandie, restaurée et fermée correctement. C’est un travail sans gloire qui ne donne rien à montrer pendant des semaines, et c’est toute la raison pour laquelle le projet est allé au bout.
Une fois qu’une fenêtre est une primitive résolue, une application n’est plus qu’un contenu accompagné d’un manifeste. Démineur, Tetris, Snake, un éditeur de texte enrichi TipTap, un tableur avec des formules qui marchent, un chat IA, Paint, une application webcam, le Gestionnaire des tâches, le Panneau de configuration et tout le reste se branchent sur le même contrat. Ajouter la vingt-septième a pris un après-midi. La première a pris un mois.
Le faux matériel et la séquence de démarrage relèvent de la même décision que le gestionnaire de fenêtres, simplement au niveau du ton. Un simulateur qui démarre instantanément est un site web. Un simulateur qui passe quelques secondes à compter de la mémoire imaginaire est une machine.
La construction
TanStack Start avec React 19, buildé par Vite, déployé sur Cloudflare. Tout tourne côté client. Il n’y a pas de serveur, pas de compte et pas de base de données, ce qui lui permet de ne rien coûter à héberger et de charger vite depuis l’edge.
Zustand détient tout l’état du système et le persiste dans localStorage. C’est la pièce qui donne la sensation d’un OS plutôt que d’une page. Les fenêtres ouvertes, leurs positions, leur ordre d’empilement, votre toile Paint, votre document, vos réglages du Panneau de configuration et votre fond d’écran survivent tous à un rechargement. Fermez l’onglet en plein Tetris et revenez sur la même grille. La couche de persistance est versionnée, sinon un changement de schéma se désérialiserait en bouillie chez chaque visiteur qui revient.
Le gestionnaire de fenêtres est une seule pile d’ordre d’empilement, avec le focus comme état dérivé plutôt que comme drapeau stocké sur chaque fenêtre, ce qui a supprimé toute une catégorie de bugs où deux fenêtres se croyaient toutes les deux actives. Le déplacement et le redimensionnement passent par @use-gesture, avec des pointer events plutôt que des mouse events pour qu’un téléphone fonctionne. Framer Motion anime les transitions de fenêtre, la réduction et la restauration. Radix UI fournit les menus et les boîtes de dialogue sous l’habillage rétro, ce qui est la décision la moins fidèle à l’époque de tout le projet et la raison pour laquelle le menu Démarrer est navigable au clavier et supportable au lecteur d’écran. Une interface de 1998 avec des primitives d’accessibilité de 2026, c’est un échange que je referais.
Les applications portent leur propre poids. TipTap est l’éditeur. Le tableur a un vrai parseur de formules avec références de cellules et recalcul ordonné par dépendances, parce qu’un tableur qui ne calcule pas est un tableau. Le chat IA fait couler les tokens dans une zone de texte d’époque et il est rate limité, parce qu’une fenêtre de chat publique et sans authentification est une facture en préparation. L’application webcam utilise getUserMedia en local et n’envoie rien. Le Gestionnaire des tâches liste des processus qui correspondent aux fenêtres ouvertes, donc en tuer un la ferme.
use-sound gère l’audio : démarrage, arrêt, sons d’erreur, clics. Lucide couvre les icônes sans équivalent d’époque. Microsoft Clarity est le seul outil d’analytics, et il y a une jolie symétrie à ce que Microsoft me raconte comment les gens utilisent mon Windows 98.
Le résultat
C’est en ligne sur os.bapspatil.com avec les 27 applications qui fonctionnent. Les gens tombent dessus, y perdent une heure, et viennent me dire qu’ils ont eu exactement cette machine.
C’est aussi devenu mon morceau d’ingénierie le plus réutilisable. Le gestionnaire de fenêtres, la couche de persistance et le contrat d’application sont des pièces sur lesquelles je reviens sans arrêt.
Ce que je ferais autrement
J’ai livré la persistance Zustand sans chemin de migration et j’ai dû ajouter le versionnage une fois que de vraies personnes avaient un vrai état. Ça aurait dû être dans le premier commit.
J’aurais aussi écrit le contrat de manifeste d’application sous forme de type avant de construire l’application numéro deux, plutôt que de l’extraire aux alentours de la huitième. Les applications du milieu portent encore la forme de cette transition.




