Applis web fiables : fichier autonome, zéro dépendance externe, pas de bluff

Les modèles promettaient des libs qui ne se chargent jamais hors-ligne
(Stockfish, CDN…), éclataient le code en fichiers incohérents, puis
déclaraient « ça marche » sans rien vérifier.

- Contraintes « appli web » injectées pour toute tâche web (agent +
  moteur code) : UN seul index.html autonome (CSS/JS inline), AUCUNE
  ressource externe (implémenter en JS natif à la place), rendu réel au
  chargement, interdiction de « simuler » une lib en la disant réelle.
- check_html signale désormais les scripts/styles externes (CDN/WASM)
  qui ne se chargeront pas dans le sandbox hors-ligne, en plus des
  références locales cassées.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N
This commit is contained in:
Claude committed 2026-07-21 08:57:09 +00:00
1 parent 41f07b0ba8
commit 920f575507
3 files changed
+58 -4

No files matched your search

+9 -1
View File
@@ -425,6 +425,12 @@ async def chat(req: ChatRequest) -> StreamingResponse:
if cfg.get("ponytail", True) and is_code_like:
convo.insert(1, {"role": "system", "content": skills.PONYTAIL_GUIDANCE})
# Appli web : contraintes dures (fichier autonome, zéro dépendance
# externe, rendu réel) pour que ça marche vraiment hors-ligne.
want_web = is_code_like and skills.is_web_task(req.content)
if want_web:
convo.insert(1, {"role": "system", "content": skills.WEBAPP_GUIDANCE})
if plan:
yield _sse("plan", {"steps": plan})
@@ -443,7 +449,9 @@ async def chat(req: ChatRequest) -> StreamingResponse:
if recap:
extra = f"\n\n{recap}" + extra
# Le moteur code (Aider) ne voit pas convo : on lui redonne Ponytail
# directement dans la consigne.
# (et les contraintes web) directement dans la consigne.
if want_web:
extra = "\n\n" + skills.WEBAPP_GUIDANCE + extra
if cfg.get("ponytail", True):
extra = "\n\n" + skills.PONYTAIL_GUIDANCE + extra
code_files = _mentioned_files(req.content) or session_files[:8]
+37
View File
@@ -64,6 +64,43 @@ def _load() -> dict[str, dict]:
return out
_WEB_RE = re.compile(
r"\b(html|css|javascript|js|page|site|web|appli|application|interface|"
r"bouton|formulaire|canvas|animation|jeu|game|échiquier|echiquier|"
r"dashboard|landing|maquette|ui|front)\b",
re.I,
)
def is_web_task(message: str) -> bool:
"""La demande porte-t-elle sur une page / appli web ?"""
return bool(_WEB_RE.search(message or ""))
# Contraintes « appli web » : le sandbox de prévisualisation est HORS-LIGNE et
# isolé (pas de CDN, pas de WASM téléchargé). Sans ces règles, les modèles
# promettent des libs (Stockfish, React…) qui ne se chargent jamais, éclatent
# le code en plusieurs fichiers incohérents, puis déclarent « ça marche ».
WEBAPP_GUIDANCE = (
"Contraintes STRICTES pour une page/appli web :\n"
"1. UN SEUL fichier index.html AUTONOME : tout le CSS dans <style>, tout le "
"JS dans <script> à la fin du <body>. Pas de fichiers .css/.js séparés.\n"
"2. AUCUNE ressource externe : pas de CDN, pas de <script src=\"https://…\">, "
"pas de bibliothèque à télécharger (jQuery, React, chess.js, Stockfish, "
".wasm…). Le sandbox est hors-ligne : ça ne se charge JAMAIS. Si une "
"fonctionnalité a besoin d'une lib, écris une version simple toi-même en "
"JavaScript natif.\n"
"3. Le rendu doit s'afficher AU CHARGEMENT : construis réellement le DOM "
"dans un script qui s'exécute (ex. génère les 64 cases de l'échiquier en "
"boucle). Pas de fonction jamais appelée, pas de conteneur laissé vide.\n"
"4. Ne prétends JAMAIS qu'une fonctionnalité marche sans l'avoir codée, et "
"ne « simule » pas un moteur en le faisant passer pour réel : dis "
"clairement ce qui est réel et ce qui ne l'est pas.\n"
"5. Design sobre, lisible et moderne — mais la priorité est que ça "
"FONCTIONNE vraiment."
)
ALL_SKILLS: dict[str, dict] = _load()
+12 -3
View File
@@ -123,9 +123,18 @@ def check_html(target: str) -> list[str]:
base_dir = os.path.dirname(target)
# Références locales cassées (href/src vers un fichier absent).
for _, ref in re.findall(r"""(href|src)=["']([^"'#]+)["']""", content, re.I):
if re.match(r"^(https?:|data:|mailto:|tel:|//|javascript:)", ref, re.I):
# Références href/src : cassées (fichier local absent) ou externes
# (CDN/WASM qui ne se chargeront pas dans un sandbox hors-ligne).
for attr, ref in re.findall(r"""(href|src)=["']([^"'#]+)["']""", content, re.I):
low = ref.lower()
if low.startswith(("data:", "mailto:", "tel:", "javascript:")):
continue
if re.match(r"^(https?:)?//", ref, re.I):
base = low.split("?")[0]
if attr.lower() == "src" and base.endswith((".js", ".mjs", ".wasm")):
issues.append(f"script externe (hors-ligne : ne se charge pas) : {ref}")
elif attr.lower() == "href" and base.endswith(".css"):
issues.append(f"style externe (hors-ligne : ne se charge pas) : {ref}")
continue
ref_path = os.path.normpath(os.path.join(base_dir, ref.split("?")[0]))
if not os.path.exists(ref_path):