pwa-app-builder

Construisez une PWA (React 18 + Vite + Tailwind, Express, node:sqlite, service worker manuel, Web Push) prête pour la production à partir de rien, simplement d'une idée de projet.

Qu'est-ce que pwa-app-builder ?

Cette compétence vous permet de transformer n'importe quelle idée de projet en une application web progressive (PWA) entièrement fonctionnelle, prête pour la production, installable sur mobile et fonctionnant hors ligne. Elle est spécialement conçue pour les environnements à ressources limitées (pas de compilateur, ~300 Mo de RAM, pas de PostgreSQL/MongoDB, pas de sudo) et utilise une pile technique non négociable : React 18 + Vite + Tailwind pour le frontend, Express + node:sqlite pour le backend, un service worker manuscrit, et Web Push pour les notifications. La compétence commence par une phase de reconnaissance de l'hôte, puis interroge l'utilisateur sur ses préférences, génère un squelette d'application complet via un script de scaffolding, remplace le domaine de démonstration par le domaine réel, et vous guide à travers la vérification et la livraison, y compris la génération de liens publics via Cloudflare Tunnel. Elle est compatible avec Node.js 22.5+ et Python 3.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/AhmedAttia3/agent-skills/tree/HEAD/skills/pwa-app-builder

Installed? Explore more Recherche et analyse de données skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Générateur d'Application PWA

Transformez une idée de projet en une application réelle dès le premier jour : elle s'exécute localement, s'installe sur un téléphone, fonctionne hors ligne, envoie des notifications et peut être partagée via un lien public. La pile ci-dessous est délibérément fixe — c'est la seule combinaison qui survive aux contraintes de l'hôte sur lequel ces projets s'exécutent (pas de compilateur, ~300 Mo de RAM, pas de PostgreSQL/MongoDB, pas de sudo). Ce qui change d'un projet à l'autre est le domaine ; ce qui reste est le squelette.

Lisez ce fichier en entier avant de faire quoi que ce soit. Ensuite, suivez les phases dans l'ordre — elles ont des portes.


0. La pile (non négociable)

CoucheChoixPourquoi celui-ci et pas l'alternative évidente
BackendExpress 4, CommonJS, 'use strict'Un seul processus sert l'API + l'interface construite + les images → pas de CORS, une seule chose à redémarrer, la mémoire la plus faible
Base de donnéesnode:sqlite (DatabaseSync)Intégré dans Node ≥22.5. Zéro modules natifs. better-sqlite3 ne peut même pas s'installer (pas de gcc)
Mots de passecrypto.scrypt + timingSafeEqualPas de bcrypt (natif). Ne jamais stocker en texte brut
SessionsJeton signé HMAC dans un cookie HttpOnlyPas de dépendance jsonwebtoken, pas d'exposition localStorage aux XSS
FrontendReact 18 + Vite 5 + Tailwind 3, construit dans server/publicConstruction rapide à faible mémoire, sortie statique servie par Express, pas besoin de SSR
PWAsw.js manuscrit + manifest.webmanifestPas de Workbox / vite-plugin-pwa ; contrôle total de la politique de cache
Pushweb-push + clés VAPID générées localementGratuit, pas de compte Firebase
Imagescompressées dans le navigateur avec Canvas avant le téléchargementPas de sharp/multer (natif), moins de RAM serveur
i18npetit dictionnaire maison, pas de i18nextCommutation instantanée RTL/LTR, bundle minuscule

N'ajoutez jamais : better-sqlite3, mongodb, pg, bcrypt, argon2, jsonwebtoken, sharp, canvas, multer, i18next, Workbox, vite-plugin-pwa, socket.io, ou tout package avec code natif. Si une tâche semble en nécessiter un, résolvez-la avec la bibliothèque standard ou dans le navigateur.


1. Phase 0 — Reconnaissance de l'hôte (faites ceci d'abord)

bash <skill>/scripts/check-host.sh      # node, compilateur, RAM, swap, CPUs, ports, cloudflared

Cela affiche un verdict. Deux choses que vous devez intégrer dans votre plan :

  • Pas de compilateur → la règle JS uniquement ci-dessus est un fait dur, pas une préférence.
  • Faibles ressources (⚠️ FAIBLES RESSOURCES) → npm install et npm run build doivent s'exécuter en arrière-plan avec un fichier journal que vous interrogez ; une commande longue au premier plan se fait tuer. Voir references/host-constraints.md.

Règle du navigateur (importante, et elle est conditionnelle)

Par défaut, vérifiez avec curl et le test de fumée de l'API — c'est plus rapide et plus économique. Mais la règle est "ne lancez pas un navigateur sans demander la permission", pas "n'en lancez jamais un" :

  • Sur un hôte confortable (≥4 CPUs, swap présent, beaucoup de RAM libre) : demander est une formalité. Dites que vous souhaitez ouvrir un navigateur sans tête pour vérifier l'interface rendue et laissez l'utilisateur décider.

  • Sur un hôte à faibles ressources (comme 2 vCPU / ~300 Mo / pas de swap) : dites à l'utilisateur pourquoi dès le départ, dans la même phrase que la demande. Une instance Chromium peut consommer toute la machine, faire tuer le serveur par OOM et nécessiter un redémarrage. Quelque chose comme :

    «تحتاج للتأكد البصري من الواجهة؟ تشغيل المتصفح على هذا السيرفر (٢ كور وبدون swap) قد يستهلك الموارد بالكامل ويوقف السيرفر فيحتاج إعادة تشغيل. أفحص بـcurl واختبارات الـAPI بدلاً منه، أم تفضّل أن أشغّل المتصفح؟»

Respectez ensuite la réponse. Ne lancez jamais un navigateur en silence.


2. Phase 1 — Comprendre l'idée, puis interroger l'utilisateur (porte dure)

2a. Reformulez ce que vous avez compris — en 5 lignes, pas 50

Couvrez : qui l'utilise, la boucle principale (l'action qu'ils répètent le plus), les données qu'ils stockent, et à quoi ressemble le "fini" pour une première version. Ne planifiez pas encore l'architecture.

2b. Demandez des préférences avant de construire — c'est obligatoire, pas facultatif

L'utilisateur sait ce qu'il veut voir bien avant de savoir ce qu'est un service worker. Obtenir ces réponses à l'avance est la différence entre une application qu'ils adorent du premier coup et trois tours de "changez les couleurs". Utilisez l'outil system_ask_user pour que l'utilisateur puisse répondre d'une simple touche — au maximum 5 questions, chacune avec exactement 3 options. Choisissez celles qui changent le plus la construction ; sautez ce que vous pouvez raisonnablement déduire ou décider vous-même.

Banque de questions (adaptez le libellé au domaine du projet ; demandez dans la langue de l'utilisateur) :

#QuestionOptions
1Langue d'interface par défautArabe (RTL) avec bascule EN · Anglais (LTR) avec bascule AR · Arabe uniquement
2Identité colorimétriqueChaude (orange/ambre) · Calme (turquoise/bleu) · Élégante (violet/indigo) — ou selon le domaine du projet
3Ambiance de conceptionSimple, fonctionnel et dense en informations · Moderne avec coins arrondis et animations · Sombre élégant par défaut
4Disposition mobile d'abord ou bureauApplication mobile avec barre inférieure · Tableau de bord bureau avec barre latérale · Les deux ensemble (réactif par défaut)
5ComptesInscription + mode invité sans inscription · Invité uniquement (mise à niveau ultérieure) · Pas de comptes du tout
6NotificationsAlertes planifiées + Push · Alertes dans l'application uniquement · Pas de notifications
7Contenu public prêtCatalogue de contenu alimenté par des fichiers JSON · Contenu utilisateur uniquement · Plus tard
8Nom et logoNom arabe + anglais avec emoji · Un seul nom avec icône dessinée · Choisissez vous-même le meilleur

Règles pour l'entretien : ne demandez jamais plus de 5 ; ne demandez jamais quelque chose que vous pouvez décider avec du bon goût (espacement, courbe d'animation) ; si l'utilisateur dit «اختر أنت», décidez et passez à autre chose — ne redemandez pas. Enregistrez les réponses : elles deviennent les indicateurs d'échafaudage dans la Phase 3.

2c. Répondez au plan et attendez

Donnez un plan court (pile déjà décidée ; écrans ; modèle de données en une ligne ; les phases) et arrêtez-vous. La plupart des utilisateurs dans ce flux de travail veulent approuver avant que le code soit écrit — une ligne «موافق، ابدأ» vaut plus qu'une construction rapide et erronée.


3. Phase 2 — Concevoir le domaine (petit, concret)

Avant d'écrire du code, notez dans le chat :

  1. Entités + champs — chaque champ dont la boucle principale de l'utilisateur a besoin ; marquez requis vs facultatif. Gardez les champs requis au strict minimum (un nom et une catégorie valent mieux qu'un formulaire à dix champs).
  2. Écrans — listez-les ; chaque écran correspond à un fichier dans web/src/pages/.
  3. Routes — un groupe REST par entité plus l'authentification.
  4. Quels modules facultatifs s'appliquent — comptes/invité, alarmes+push, liens de partage public, catalogue de contenu public. Chacun est déjà implémenté dans les modèles ; vous choisissez, vous n'inventez pas.

4. Phase 3 — Échafaudage (une commande, puis vérifiez)

node <skill>/scripts/scaffold.mjs \
  --dir <workspace> --name "اسم التطبيق" --name-en "AppName" \
  --slug appname --prefix ap --palette orange --ink warm [--with-catalog]

Cela plante ~68 fichiers : le squelette complet et fonctionnel (auth, une entité CRUD de démonstration, shell PWA, push, i18n, kit UI, scripts d'exploitation) avec l'identité de l'application, le préfixe de cookie et la palette de marque déjà substitués partout. Palettes : orange amber rose violet indigo sky teal emerald; encres : warm cool stone.

Ensuite, avant de toucher à quoi que ce soit d'autre — prouvez que le pipeline fonctionne :

export PATH="$HOME/node-v24.20.0-linux-x64/bin:$PATH"   # Node n'est pas dans le PATH sur ces hôtes
npm install            # hôte à faible RAM ? exécutez en arrière-plan avec un journal et interrogez-le
npm run build
./scripts/run.sh start
curl -s http://127.0.0.1:3000/api/health
npm run test:api       # attend "110 ناجح، 2 فاشل" sur un échafaudage de démonstration frais

Les 2 échecs sont les assertions de catalogue qui nécessitent de vrais fichiers image (npm run catalog:images les récupère ; nécessite un réseau). Tout le reste doit être vert avant de commencer à changer le domaine — sinon vous ne saurez pas si un échec ultérieur est le vôtre.

Lisez references/templates-guide.md pour savoir ce qu'est chaque fichier planté et quelles parties sont le domaine de démonstration que vous êtes sur le point de remplacer.


5. Phase 4 — Remplacer le domaine de démonstration par le vrai

L'échafaudage livre un exemple de travail complet (une application de repas) précisément pour que vous puissiez copier sa forme. Travaillez vers l'extérieur à partir de la couche de données pour que rien ne pende :

  1. server/db.js — réécrivez les tables de démonstration pour vos entités. Toujours additif : nouvelles tables dans le bloc CREATE TABLE IF NOT EXISTS, nouvelles colonnes ajoutées au tableau de migration. Ensuite, écrivez les fonctions de requête.
  2. server/routes/<entity>.js — copiez routes/meals.js comme routeur CRUD canonique (optionalFields, un mappeur toEntity(), INSERT/PATCH). Faites correspondre le style d'exportation à la façon dont server/index.js les importe (module.exports = router vs { router } — le dépôt est incohérent, vérifiez avant d'écrire).
  3. Montez le routeur dans server/index.js ; n'ajoutez le préfixe API à rien d'autre (le fallback SPA exclut déjà /api).
  4. web/src/lib/api.js — une méthode par route. Pas de fetch dans les pages ou les composants, jamais.
  5. web/src/pages/* + routes dans web/src/App.jsx.
  6. scripts/smoke-test.js — réécrivez les assertions de démonstration en utilisant les assistants existants check(name, condition, extra) + call(path, {method, body}). C'est votre preuve.

Supprimez ce dont votre domaine n'a pas besoin (le routeur, la page, les méthodes API et les clés i18n de l'entité de démonstration) plutôt que de laisser du code mort.


6. Phase 5 — Frontend : RTL d'abord, les deux langues, un seul système de conception

  • Chaque chaîne destinée à l'utilisateur va dans les deux dict.ar et dict.en dans web/src/lib/i18n.js, lue avec t('key'), interpolation via les espaces réservés {name}. Une clé dans un seul dictionnaire est un bogue.
  • Utilisez des utilitaires logiques (start-*/end-*/ps-*/pe-*/ms-*/me-*) pour que RTL et LTR fonctionnent tous les deux. L'exception délibérée : les champs de mot de passe sont dir="ltr" et utilisent donc les valeurs physiques pr-12 / right-3.
  • Réutilisez les classes de composants dans web/src/styles.css (.card, .btn-primary, .input, .label, .chip) et les primitives dans web/src/components/ui.jsx avant d'écrire du Tailwind dispersé.
  • Le thème est basé sur les classes sur documentElement, persistant dans localStorage, et appliqué par un script en ligne dans web/index.html avant le premier affichage (tue le flash et corrige la barre d'état iOS).
  • Réactif : mobile-first avec une navigation en bas qui devient un rail latéral à partir de md: (AppShell.jsx).
  • Vérifiez RTL/LTR sur : formulaires, listes, barre d'application, modales et position des toasts.

7. Phase 6 — PWA + notifications

sw.js et manifest.webmanifest sont plantés. Deux choses à ne pas oublier :

  1. Incrémentez VERSION dans web/public/sw.js chaque fois que vous modifiez ce fichier, ou les clients installés garderont le cache obsolète pour toujours.
  2. Les clés VAPID sont générées au premier démarrage dans data/vapid.json. Le sub VAPID doit être un vrai domaine résoluble (mailto:[email protected]) — Apple rejette *.local/@localhost avec 403 BadJwtToken tandis que Google l'accepte, ce qui produit le classique "fonctionne sur Android, échoue silencieusement sur iPhone". Remplacez avec VAPID_SUBJECT ; ne codez jamais en dur une adresse personnelle.
  3. Ne signalez jamais le succès d'un push sans la réponse réelle du fournisseur : sendToUser retourne {total, sent, failed:[{status, reason}]} et élimine les abonnements morts (404/410/403).
  4. iOS nécessite que l'application soit installée sur l'écran d'accueil et que la permission soit demandée à partir d'une tape. Gardez le fallback de notification locale dans l'application (useAlarmWatcher dans App.jsx) fonctionnel.

Les détails et les limites de la plateforme (images dans les notifications, couleur de la barre d'état, letterboxing autonome) se trouvent dans references/pwa-and-push.md — lisez-le avant de promettre quoi que ce soit sur iOS.


8. Phase 7 — Vérifier (c'est la preuve de la livraison)

npm run test:api                                    # données isolées test/data.db sur le port 3100, base de données live intacte
npm run build && ./scripts/run.sh restart
curl -s http://127.0.0.1:3000/api/health
curl -s http://127.0.0.1:3000/ | head -3            # la SPA construite est servie
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3000/manifest.webmanifest
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3000/sw.js

Exercez également le chemin utilisateur réel avec curl (inscription/invité → créer → lire → mettre à jour → supprimer) pour pouvoir signaler une sortie réelle, pas "devrait fonctionner". Ensuite, si un navigateur est autorisé, utilisez-le pour la vérification visuelle sur une fenêtre de taille téléphone.

Signalez les commandes exactes et leur sortie réelle. Si quelque chose a échoué, dites-le avec l'erreur.


9. Phase 8 — Livrer

  1. Dépôt git local + premier commit (git init -b main), pour avoir un état de référence connu auquel revenir avant toute expérience risquée. Ne jamais push sauf demande. Commits conventionnels avec un sujet arabe : feat(meals): إضافة فلتر الفئات.
  2. Lien public via le tunnel rapide Cloudflare (./scripts/tunnel.sh start, puis lisez l'URL depuis tunnel.log). Cette étape est facultative et nécessite deux approbations distinctes :
    1. Demandez s'ils veulent un lien public du tout. S'ils possèdent déjà un domaine, un proxy inverse ou un hébergement sur lequel ils peuvent déployer, ils n'ont pas besoin de tunnel — n'en poussez pas un.
    2. Vérifiez cloudflared avant de promettre quoi que ce soit (scripts/check-host.sh le signale). S'il est manquant, dites-le et demandez la permission de l'installer avant d'exécuter un installateur ; n'installez jamais un binaire en silence. Proposez `brew

…

[tronqué pour la traduction]

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

Skills associés