[{"data":1,"prerenderedAt":1107},["ShallowReactive",2],{"i-simple-icons:github":3,"i-simple-icons:linkedin":8,"blog-\u002Fblog\u002Fnuxt-capacitor-un-seul-code":11,"blog-others-\u002Fblog\u002Fnuxt-capacitor-un-seul-code":772},{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":7},0,24,false,"\u003Cpath fill=\"currentColor\" d=\"M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":9,"hidden":10},"\u003Cpath fill=\"currentColor\" d=\"M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037c-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85c3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.06 2.06 0 0 1-2.063-2.065a2.064 2.064 0 1 1 2.063 2.065m1.782 13.019H3.555V9h3.564zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0z\"\u002F>",true,{"id":12,"title":13,"body":14,"date":757,"description":758,"extension":759,"meta":760,"navigation":10,"path":761,"readingTime":167,"seo":762,"seoTitle":763,"stem":764,"tags":765,"tone":769,"updated":770,"__hash__":771},"blog\u002Fblog\u002Fnuxt-capacitor-un-seul-code.md","Nuxt et Capacitor, un seul code pour le web et les stores",{"type":15,"value":16,"toc":750},"minimark",[17,21,24,29,32,35,39,46,103,106,208,227,233,409,416,422,551,555,569,621,628,638,696,702,712,718,726,730,733,737,740,743,746],[18,19,20],"p",{},"Une application mobile n’est pas toujours un nouveau projet. Quand le site existe déjà en Vue et Nuxt, Capacitor l’emballe dans une coque native et lui ouvre le téléphone : caméra, notifications, partage, stockage hors ligne. Le code reste le mien, les composants restent les mêmes, et l’application se publie sur l’App Store et Google Play.",[18,22,23],{},"Ce n’est pas magique pour autant. Voici ce que ça demande, étape par étape, et ce que j’ai appris en le faisant plusieurs fois.",[25,26,28],"h2",{"id":27},"comment-ça-marche","Comment ça marche",[18,30,31],{},"Capacitor crée un projet iOS et un projet Android qui contiennent chacun une vue web plein écran. Cette vue charge les fichiers de votre site, embarqués dans l’application. Quand le code appelle une fonction native, Capacitor fait le pont avec Swift ou Kotlin, et renvoie le résultat en JavaScript.",[18,33,34],{},"Autrement dit, l’application est votre site, servi depuis le téléphone plutôt que depuis un serveur, avec une porte vers le matériel. Pas de serveur dans la poche de l’utilisateur, pas de réécriture.",[25,36,38],{"id":37},"ce-qui-change-dans-le-projet","Ce qui change dans le projet",[18,40,41,45],{},[42,43,44],"strong",{},"Le rendu."," L’application embarque une version statique du site. Dans Nuxt, je désactive le rendu côté serveur et je génère les fichiers :",[47,48,54],"pre",{"className":49,"code":50,"filename":51,"language":52,"meta":53,"style":53},"language-ts shiki shiki-themes github-light github-dark","export default defineNuxtConfig({\n  \u002F\u002F L’app tourne sans serveur : tout est rendu dans la vue web\n  ssr: false,\n})\n","nuxt.config.ts","ts","",[55,56,57,77,84,97],"code",{"__ignoreMap":53},[58,59,62,66,69,73],"span",{"class":60,"line":61},"line",1,[58,63,65],{"class":64},"szBVR","export",[58,67,68],{"class":64}," default",[58,70,72],{"class":71},"sScJk"," defineNuxtConfig",[58,74,76],{"class":75},"sVt8B","({\n",[58,78,80],{"class":60,"line":79},2,[58,81,83],{"class":82},"sJ8bj","  \u002F\u002F L’app tourne sans serveur : tout est rendu dans la vue web\n",[58,85,87,90,94],{"class":60,"line":86},3,[58,88,89],{"class":75},"  ssr: ",[58,91,93],{"class":92},"sj4cs","false",[58,95,96],{"class":75},",\n",[58,98,100],{"class":60,"line":99},4,[58,101,102],{"class":75},"})\n",[18,104,105],{},"Puis Capacitor pointe vers le dossier produit :",[47,107,110],{"className":49,"code":108,"filename":109,"language":52,"meta":53,"style":53},"import type { CapacitorConfig } from '@capacitor\u002Fcli'\n\nconst config: CapacitorConfig = {\n  appId: 'fr.melaniedasilva.trajets',\n  appName: 'Trajets',\n  webDir: '.output\u002Fpublic',\n}\n\nexport default config\n","capacitor.config.ts",[55,111,112,130,135,155,165,176,187,193,198],{"__ignoreMap":53},[58,113,114,117,120,123,126],{"class":60,"line":61},[58,115,116],{"class":64},"import",[58,118,119],{"class":64}," type",[58,121,122],{"class":75}," { CapacitorConfig } ",[58,124,125],{"class":64},"from",[58,127,129],{"class":128},"sZZnC"," '@capacitor\u002Fcli'\n",[58,131,132],{"class":60,"line":79},[58,133,134],{"emptyLinePlaceholder":10},"\n",[58,136,137,140,143,146,149,152],{"class":60,"line":86},[58,138,139],{"class":64},"const",[58,141,142],{"class":92}," config",[58,144,145],{"class":64},":",[58,147,148],{"class":71}," CapacitorConfig",[58,150,151],{"class":64}," =",[58,153,154],{"class":75}," {\n",[58,156,157,160,163],{"class":60,"line":99},[58,158,159],{"class":75},"  appId: ",[58,161,162],{"class":128},"'fr.melaniedasilva.trajets'",[58,164,96],{"class":75},[58,166,168,171,174],{"class":60,"line":167},5,[58,169,170],{"class":75},"  appName: ",[58,172,173],{"class":128},"'Trajets'",[58,175,96],{"class":75},[58,177,179,182,185],{"class":60,"line":178},6,[58,180,181],{"class":75},"  webDir: ",[58,183,184],{"class":128},"'.output\u002Fpublic'",[58,186,96],{"class":75},[58,188,190],{"class":60,"line":189},7,[58,191,192],{"class":75},"}\n",[58,194,196],{"class":60,"line":195},8,[58,197,134],{"emptyLinePlaceholder":10},[58,199,201,203,205],{"class":60,"line":200},9,[58,202,65],{"class":64},[58,204,68],{"class":64},[58,206,207],{"class":75}," config\n",[18,209,210,211,214,215,218,219,222,223,226],{},"Le cycle de travail tient en trois commandes : ",[55,212,213],{},"nuxt generate"," pour produire le site, ",[55,216,217],{},"npx cap sync"," pour le copier dans les projets natifs, ",[55,220,221],{},"npx cap open ios"," ou ",[55,224,225],{},"npx cap open android"," pour lancer Xcode ou Android Studio.",[18,228,229,232],{},[42,230,231],{},"Les accès natifs."," Chaque fonctionnalité du téléphone passe par un plugin, avec une version de repli sur le web. Le partage, par exemple :",[47,234,237],{"className":49,"code":235,"filename":236,"language":52,"meta":53,"style":53},"import { Capacitor } from '@capacitor\u002Fcore'\nimport { Share } from '@capacitor\u002Fshare'\n\nexport function useShare() {\n  return async (title: string, url: string) => {\n    if (Capacitor.isNativePlatform()) {\n      await Share.share({ title, url })\n      return\n    }\n    \u002F\u002F Sur le web : la boîte de partage du navigateur, ou le presse-papiers\n    if (navigator.share) await navigator.share({ title, url })\n    else await navigator.clipboard.writeText(url)\n  }\n}\n","composables\u002FuseShare.ts",[55,238,239,251,263,267,280,318,332,346,351,356,362,380,398,404],{"__ignoreMap":53},[58,240,241,243,246,248],{"class":60,"line":61},[58,242,116],{"class":64},[58,244,245],{"class":75}," { Capacitor } ",[58,247,125],{"class":64},[58,249,250],{"class":128}," '@capacitor\u002Fcore'\n",[58,252,253,255,258,260],{"class":60,"line":79},[58,254,116],{"class":64},[58,256,257],{"class":75}," { Share } ",[58,259,125],{"class":64},[58,261,262],{"class":128}," '@capacitor\u002Fshare'\n",[58,264,265],{"class":60,"line":86},[58,266,134],{"emptyLinePlaceholder":10},[58,268,269,271,274,277],{"class":60,"line":99},[58,270,65],{"class":64},[58,272,273],{"class":64}," function",[58,275,276],{"class":71}," useShare",[58,278,279],{"class":75},"() {\n",[58,281,282,285,288,291,295,297,300,303,306,308,310,313,316],{"class":60,"line":167},[58,283,284],{"class":64},"  return",[58,286,287],{"class":64}," async",[58,289,290],{"class":75}," (",[58,292,294],{"class":293},"s4XuR","title",[58,296,145],{"class":64},[58,298,299],{"class":92}," string",[58,301,302],{"class":75},", ",[58,304,305],{"class":293},"url",[58,307,145],{"class":64},[58,309,299],{"class":92},[58,311,312],{"class":75},") ",[58,314,315],{"class":64},"=>",[58,317,154],{"class":75},[58,319,320,323,326,329],{"class":60,"line":178},[58,321,322],{"class":64},"    if",[58,324,325],{"class":75}," (Capacitor.",[58,327,328],{"class":71},"isNativePlatform",[58,330,331],{"class":75},"()) {\n",[58,333,334,337,340,343],{"class":60,"line":189},[58,335,336],{"class":64},"      await",[58,338,339],{"class":75}," Share.",[58,341,342],{"class":71},"share",[58,344,345],{"class":75},"({ title, url })\n",[58,347,348],{"class":60,"line":195},[58,349,350],{"class":64},"      return\n",[58,352,353],{"class":60,"line":200},[58,354,355],{"class":75},"    }\n",[58,357,359],{"class":60,"line":358},10,[58,360,361],{"class":82},"    \u002F\u002F Sur le web : la boîte de partage du navigateur, ou le presse-papiers\n",[58,363,365,367,370,373,376,378],{"class":60,"line":364},11,[58,366,322],{"class":64},[58,368,369],{"class":75}," (navigator.share) ",[58,371,372],{"class":64},"await",[58,374,375],{"class":75}," navigator.",[58,377,342],{"class":71},[58,379,345],{"class":75},[58,381,383,386,389,392,395],{"class":60,"line":382},12,[58,384,385],{"class":64},"    else",[58,387,388],{"class":64}," await",[58,390,391],{"class":75}," navigator.clipboard.",[58,393,394],{"class":71},"writeText",[58,396,397],{"class":75},"(url)\n",[58,399,401],{"class":60,"line":400},13,[58,402,403],{"class":75},"  }\n",[58,405,407],{"class":60,"line":406},14,[58,408,192],{"class":75},[18,410,411,412,415],{},"Le composant qui appelle ",[55,413,414],{},"useShare"," ne sait pas où il tourne. C’est le but : un seul code, deux comportements.",[18,417,418,421],{},[42,419,420],{},"La navigation."," Les liens restent des liens, mais les gestes doivent répondre comme dans une application. Sur Android, le bouton retour du système doit revenir à la page précédente, pas fermer l’application :",[47,423,426],{"className":49,"code":424,"filename":425,"language":52,"meta":53,"style":53},"import { App } from '@capacitor\u002Fapp'\n\nexport default defineNuxtPlugin(() => {\n  const router = useRouter()\n  App.addListener('backButton', ({ canGoBack }) => {\n    if (canGoBack && router.currentRoute.value.path !== '\u002F') router.back()\n    else App.exitApp()\n  })\n})\n","plugins\u002Fback-button.client.ts",[55,427,428,440,444,460,476,503,530,542,547],{"__ignoreMap":53},[58,429,430,432,435,437],{"class":60,"line":61},[58,431,116],{"class":64},[58,433,434],{"class":75}," { App } ",[58,436,125],{"class":64},[58,438,439],{"class":128}," '@capacitor\u002Fapp'\n",[58,441,442],{"class":60,"line":79},[58,443,134],{"emptyLinePlaceholder":10},[58,445,446,448,450,453,456,458],{"class":60,"line":86},[58,447,65],{"class":64},[58,449,68],{"class":64},[58,451,452],{"class":71}," defineNuxtPlugin",[58,454,455],{"class":75},"(() ",[58,457,315],{"class":64},[58,459,154],{"class":75},[58,461,462,465,468,470,473],{"class":60,"line":99},[58,463,464],{"class":64},"  const",[58,466,467],{"class":92}," router",[58,469,151],{"class":64},[58,471,472],{"class":71}," useRouter",[58,474,475],{"class":75},"()\n",[58,477,478,481,484,487,490,493,496,499,501],{"class":60,"line":167},[58,479,480],{"class":75},"  App.",[58,482,483],{"class":71},"addListener",[58,485,486],{"class":75},"(",[58,488,489],{"class":128},"'backButton'",[58,491,492],{"class":75},", ({ ",[58,494,495],{"class":293},"canGoBack",[58,497,498],{"class":75}," }) ",[58,500,315],{"class":64},[58,502,154],{"class":75},[58,504,505,507,510,513,516,519,522,525,528],{"class":60,"line":178},[58,506,322],{"class":64},[58,508,509],{"class":75}," (canGoBack ",[58,511,512],{"class":64},"&&",[58,514,515],{"class":75}," router.currentRoute.value.path ",[58,517,518],{"class":64},"!==",[58,520,521],{"class":128}," '\u002F'",[58,523,524],{"class":75},") router.",[58,526,527],{"class":71},"back",[58,529,475],{"class":75},[58,531,532,534,537,540],{"class":60,"line":189},[58,533,385],{"class":64},[58,535,536],{"class":75}," App.",[58,538,539],{"class":71},"exitApp",[58,541,475],{"class":75},[58,543,544],{"class":60,"line":195},[58,545,546],{"class":75},"  })\n",[58,548,549],{"class":60,"line":200},[58,550,102],{"class":75},[25,552,554],{"id":553},"les-pièges-que-jévite-maintenant","Les pièges que j’évite maintenant",[18,556,557,564,565,568],{},[42,558,559,560,563],{},"Le ",[55,561,562],{},"100vh"," qui ment."," Sur iOS, la barre d’adresse change la hauteur de la fenêtre, et l’encoche mange le haut de l’écran. J’utilise ",[55,566,567],{},"100dvh"," et les marges de sécurité :",[47,570,575],{"className":571,"code":572,"filename":573,"language":574,"meta":53,"style":53},"language-css shiki shiki-themes github-light github-dark",".screen {\n  min-height: 100dvh;\n  padding-top: env(safe-area-inset-top);\n  padding-bottom: env(safe-area-inset-bottom);\n}\n","main.css","css",[55,576,577,584,601,609,617],{"__ignoreMap":53},[58,578,579,582],{"class":60,"line":61},[58,580,581],{"class":71},".screen",[58,583,154],{"class":75},[58,585,586,589,592,595,598],{"class":60,"line":79},[58,587,588],{"class":92},"  min-height",[58,590,591],{"class":75},": ",[58,593,594],{"class":92},"100",[58,596,597],{"class":64},"dvh",[58,599,600],{"class":75},";\n",[58,602,603,606],{"class":60,"line":86},[58,604,605],{"class":92},"  padding-top",[58,607,608],{"class":75},": env(safe-area-inset-top);\n",[58,610,611,614],{"class":60,"line":99},[58,612,613],{"class":92},"  padding-bottom",[58,615,616],{"class":75},": env(safe-area-inset-bottom);\n",[58,618,619],{"class":60,"line":167},[58,620,192],{"class":75},[18,622,623,624,627],{},"Sans ",[55,625,626],{},"viewport-fit=cover"," dans la balise viewport, ces marges valent zéro. C’est la première chose que je vérifie quand « rien ne passe sous l’encoche » ne marche pas.",[18,629,630,633,634,637],{},[42,631,632],{},"Le survol qui colle."," Sur un écran tactile, ",[55,635,636],{},":hover"," reste actif après le toucher. Un bouton garde sa couleur de survol jusqu’au prochain appui, et l’interface paraît cassée. Les états de survol sont réservés aux pointeurs fins :",[47,639,641],{"className":571,"code":640,"filename":573,"language":574,"meta":53,"style":53},"@media (hover: hover) and (pointer: fine) {\n  .card:hover { transform: translateY(-2px); }\n}\n",[55,642,643,665,692],{"__ignoreMap":53},[58,644,645,648,650,653,655,657,659,662],{"class":60,"line":61},[58,646,647],{"class":64},"@media",[58,649,290],{"class":75},[58,651,652],{"class":92},"hover",[58,654,591],{"class":75},[58,656,652],{"class":92},[58,658,312],{"class":75},[58,660,661],{"class":64},"and",[58,663,664],{"class":75}," (pointer: fine) {\n",[58,666,667,670,673,676,678,681,683,686,689],{"class":60,"line":79},[58,668,669],{"class":71},"  .card:hover",[58,671,672],{"class":75}," { ",[58,674,675],{"class":92},"transform",[58,677,591],{"class":75},[58,679,680],{"class":92},"translateY",[58,682,486],{"class":75},[58,684,685],{"class":92},"-2",[58,687,688],{"class":64},"px",[58,690,691],{"class":75},"); }\n",[58,693,694],{"class":60,"line":86},[58,695,192],{"class":75},[18,697,698,701],{},[42,699,700],{},"Le zoom au focus."," Un champ dont le texte fait moins de 16 px fait zoomer Safari quand on le touche. Taille minimale, toujours, même pour un petit champ de recherche.",[18,703,704,707,708,711],{},[42,705,706],{},"La barre d’état invisible."," Texte blanc sur fond blanc en haut de l’écran, parce que la barre d’état garde son style par défaut. Le plugin ",[55,709,710],{},"@capacitor\u002Fstatus-bar"," règle sa couleur selon le thème de l’application, et je le fais au démarrage, pas page par page.",[18,713,714,717],{},[42,715,716],{},"Le réseau qui n’existe pas."," Sur le web, une requête qui échoue affiche une erreur et on recharge. Dans une application, l’utilisateur est dans le métro. Chaque écran a un état hors ligne, et les données déjà chargées restent affichées. C’est le même principe que pour les états d’un écran, en plus strict.",[719,720,723],"callout",{"title":721,"type":722},"Tester sur un vrai téléphone","tip",[18,724,725],{},"Le simulateur suffit pour la mise en page. Il ne dit rien du toucher, de la latence, de la caméra ni du clavier qui recouvre la moitié de l’écran. Avant chaque version, l’application tourne sur un iPhone et un Android que je garde pour ça.",[25,727,729],{"id":728},"mettre-à-jour","Mettre à jour",[18,731,732],{},"Le code web se met à jour comme un site : je génère, je synchronise, je publie une nouvelle version dans les stores. La relecture d’Apple prend de quelques heures à quelques jours, celle de Google est en général plus rapide. Un bug visible le lundi est corrigé en ligne le lundi, et dans les stores un peu plus tard. Je le dis au client dès le début, pour que personne ne l’apprenne le jour du bug.",[25,734,736],{"id":735},"quand-ça-vaut-le-coup","Quand ça vaut le coup",[18,738,739],{},"Quand l’application est l’usage principal, pas une vitrine. Un tableau de bord qu’on ouvre tous les jours, un outil de terrain qui prend des photos, un espace client avec des notifications. Là, la présence sur les stores et l’accès au matériel changent vraiment l’usage.",[18,741,742],{},"Pour un site vitrine, une page web rapide, installable et bien référencée suffit souvent. Je le dis quand c’est le cas, parce qu’une application à maintenir sans raison coûte plus qu’elle ne rapporte.",[18,744,745],{},"Dans tous les cas, c’est le même code que je teste, que je publie et que je fais évoluer. Une seule base, deux magasins.",[747,748,749],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":53,"searchDepth":79,"depth":79,"links":751},[752,753,754,755,756],{"id":27,"depth":79,"text":28},{"id":37,"depth":79,"text":38},{"id":553,"depth":79,"text":554},{"id":728,"depth":79,"text":729},{"id":735,"depth":79,"text":736},"2026-06-10","Comment je transforme un site Nuxt en application iPhone et Android sans tout réécrire, ce qui change vraiment dans le projet, et les pièges que j’évite maintenant.","md",{},"\u002Fblog\u002Fnuxt-capacitor-un-seul-code",{"title":13,"description":758},"Nuxt et Capacitor : une app iOS\u002FAndroid depuis votre site","blog\u002Fnuxt-capacitor-un-seul-code",[766,767,768],"Nuxt","Capacitor","Mobile","slate",null,"3WkfZ14AW-J1au84JRhAv9O0Cc4DuD35mrNP4bYts9s",[773,933],{"id":774,"title":775,"body":776,"date":920,"description":921,"extension":759,"meta":922,"navigation":10,"path":923,"readingTime":99,"seo":924,"seoTitle":925,"stem":926,"tags":927,"tone":931,"updated":770,"__hash__":932},"blog\u002Fblog\u002Fcoder-avec-une-ia-sans-perdre-la-main.md","Coder avec une IA sans perdre la main",{"type":15,"value":777,"toc":913},[778,781,784,788,791,794,797,801,807,813,819,825,829,835,841,847,859,863,897,903,907,910],[18,779,780],{},"Il y a deux façons de rater le virage des assistants de code. La première est de les ignorer et de taper à la main ce qu’une machine écrit mieux et plus vite. La seconde est de tout leur laisser et de ne plus savoir ce qui tourne en production. Entre les deux, il y a une pratique, et elle se construit.",[18,782,783],{},"Voici la mienne, telle qu’elle est aujourd’hui, après un an de projets livrés avec Claude Code dans l’éditeur.",[25,785,787],{"id":786},"une-boucle-pas-une-baguette","Une boucle, pas une baguette",[18,789,790],{},"L’erreur la plus courante est de voir l’IA comme un générateur : on décrit, on reçoit, on colle. Ce n’est pas comme ça que ça marche bien. Ce qui marche, c’est une boucle courte où chaque étape a un responsable.",[792,793],"demo-loop",{},[18,795,796],{},"L’IA n’intervient qu’à une étape. Tout le reste, c’est du travail d’ingénierie classique, et c’est lui qui fait la qualité du résultat. Si le brief est flou, la génération est floue. Si la relecture est bâclée, les bugs passent. Si les tests n’existent pas, personne ne sait si la troisième itération a cassé la première.",[25,798,800],{"id":799},"ce-que-je-lui-confie","Ce que je lui confie",[18,802,803,806],{},[42,804,805],{},"Le code qui a déjà été écrit mille fois."," Un formulaire avec validation, un composant de liste avec ses états, une fonction qui formate des dates en français, la configuration d’un module Nuxt. L’IA connaît ces motifs par cœur et les écrit sans faute de frappe. Je gagne vingt minutes par occurrence, et il y en a beaucoup dans une journée.",[18,808,809,812],{},[42,810,811],{},"Les tâches larges et peu risquées."," Renommer un composant dans tout le projet, extraire un composable, ajouter un type manquant, écrire les premiers tests d’un fichier. Ce sont des tâches où l’erreur se voit tout de suite.",[18,814,815,818],{},[42,816,817],{},"La première version de quelque chose."," Quand je ne sais pas encore à quoi va ressembler un écran, je lui demande trois propositions. Je n’en garde jamais une telle quelle, mais je choisis plus vite.",[18,820,821,824],{},[42,822,823],{},"Les explications."," Un code hérité que personne ne commente, une erreur de build incompréhensible, une API que je n’ai jamais utilisée. Demander « explique-moi ce fichier » avant de le modifier m’a évité pas mal de casse.",[25,826,828],{"id":827},"ce-que-je-garde","Ce que je garde",[18,830,831,834],{},[42,832,833],{},"Les décisions."," L’architecture, le découpage en composants, le choix d’une bibliothèque, ce qui va dans le state global et ce qui n’y va pas. L’IA peut argumenter, elle ne tranche pas. C’est moi qui porterai la décision dans six mois.",[18,836,837,840],{},[42,838,839],{},"La relecture, ligne par ligne."," Je ne fusionne rien que je n’ai pas lu. Pas survolé : lu. Si une ligne me paraît magique, je demande pourquoi elle est là, et si la réponse ne me convainc pas, elle saute. Un code que je ne comprends pas est un code que je ne pourrai pas corriger à deux heures du matin.",[18,842,843,846],{},[42,844,845],{},"Tout ce qui touche à la sécurité et à l’argent."," Authentification, paiement, droits d’accès, données personnelles. L’IA écrit ce code aussi bien qu’un autre, mais là, je vérifie deux fois plus, et j’écris les tests moi-même.",[18,848,849,852,853,858],{},[42,850,851],{},"L’interface finale."," Les détails qu’un humain remarque sans savoir les nommer, j’en parle dans ",[854,855,857],"a",{"href":856},"\u002Fblog\u002Fce-que-l-ia-ne-voit-pas-dans-une-interface","un autre article",". Ils ne viennent pas de la génération, ils viennent de la relecture avec les yeux de l’utilisateur.",[25,860,862],{"id":861},"les-règles-que-je-me-suis-fixées","Les règles que je me suis fixées",[864,865,866,873,879,885,891],"ol",{},[867,868,869,872],"li",{},[42,870,871],{},"Un fichier de conventions à la racine du projet."," Nom des composants, structure des dossiers, langue des commentaires, format des commits. L’assistant le lit à chaque session, et le code qu’il produit ressemble au mien. Sans ce fichier, chaque génération part dans une direction différente.",[867,874,875,878],{},[42,876,877],{},"Une tâche à la fois."," « Ajoute le champ téléphone au formulaire de contact, avec la validation et le test » est une tâche. « Améliore le formulaire » n’en est pas une. Plus la demande est bornée, plus la réponse est juste, et plus la relecture est rapide.",[867,880,881,884],{},[42,882,883],{},"Les tests tournent avant que je regarde."," Si la suite Cypress est rouge, je ne lis même pas le diff. Ça m’oblige à garder des tests fiables, et ça m’évite de perdre du temps sur un code qui ne marche pas.",[867,886,887,890],{},[42,888,889],{},"Je relis dans l’éditeur, pas dans la conversation."," Le diff dans Git, avec la coloration, le contexte autour, et la possibilité de lancer le projet. La conversation sert à discuter, pas à valider.",[867,892,893,896],{},[42,894,895],{},"Je garde une trace de ce qui a été généré."," Pas pour m’en excuser, mais parce que si un bug apparaît dans trois semaines, savoir qu’un bloc est sorti d’une génération rapide oriente la recherche.",[719,898,900],{"title":899,"type":722},"Le test du lendemain",[18,901,902],{},"Avant de fusionner un gros morceau généré, je me pose une question simple : si cette fonction casse demain et que je n’ai plus accès à l’assistant, est-ce que je sais la réparer ? Si la réponse est non, ce n’est pas prêt.",[25,904,906],{"id":905},"ce-que-ça-change-concrètement","Ce que ça change, concrètement",[18,908,909],{},"Je livre plus vite, c’est vrai, mais ce n’est pas le plus intéressant. Ce qui a changé, c’est où va mon attention. Moins de temps sur la syntaxe et la plomberie, plus de temps sur ce que l’utilisateur voit et ressent. Plus de temps aussi sur les tests, parce qu’ils sont devenus la vraie garantie, celle qui ne dépend pas de qui a écrit le code.",[18,911,912],{},"Et je comprends toujours ce que je livre. C’était la condition, et elle tient.",{"title":53,"searchDepth":79,"depth":79,"links":914},[915,916,917,918,919],{"id":786,"depth":79,"text":787},{"id":799,"depth":79,"text":800},{"id":827,"depth":79,"text":828},{"id":861,"depth":79,"text":862},{"id":905,"depth":79,"text":906},"2026-10-21","J’utilise Claude Code tous les jours. Voici ce que je lui confie, ce que je garde, et les règles qui m’évitent de livrer du code que je ne comprends pas.",{},"\u002Fblog\u002Fcoder-avec-une-ia-sans-perdre-la-main",{"title":775,"description":921},"Coder avec Claude Code sans perdre la main : ma méthode","blog\u002Fcoder-avec-une-ia-sans-perdre-la-main",[928,929,930],"IA","Méthode","Claude Code","lavender","v2_5veRBSuqiUYQJU5moplrHaQM26kKEITUIO4IdjOU",{"id":934,"title":935,"body":936,"date":1097,"description":1098,"extension":759,"meta":1099,"navigation":10,"path":856,"readingTime":99,"seo":1100,"seoTitle":1101,"stem":1102,"tags":1103,"tone":769,"updated":770,"__hash__":1106},"blog\u002Fblog\u002Fce-que-l-ia-ne-voit-pas-dans-une-interface.md","Ce qu’une IA ne voit pas dans une interface",{"type":15,"value":937,"toc":1088},[938,941,944,948,951,954,957,984,988,991,999,1003,1014,1025,1029,1032,1035,1039,1042,1045,1049,1052,1058,1062,1065,1085],[18,939,940],{},"Demandez à un assistant de code « une page de liste de projets avec un bouton pour en créer un ». Vous aurez une page propre, des cartes alignées, un bouton bleu. Elle marchera. Et elle sera incomplète d’une façon que personne ne remarque dans la conversation, mais que tout le monde remarque en l’utilisant.",[18,942,943],{},"Ce n’est pas une critique des outils. C’est la nature du problème : une interface se juge dans la main, pas sur une capture d’écran. Voici ce que je regarde systématiquement sur un écran généré, et que je trouve presque toujours à corriger.",[25,945,947],{"id":946},"les-états-que-personne-na-demandés","Les états que personne n’a demandés",[18,949,950],{},"Un écran n’existe pas dans un seul état. Il se charge, il peut être vide, il peut échouer, et seulement ensuite il montre quelque chose. Une génération produit l’état « succès » parce que c’est celui du brief. Les trois autres restent à écrire.",[952,953],"demo-states",{},[18,955,956],{},"Pour chacun, la question est la même : qu’est-ce que la personne comprend, et qu’est-ce qu’elle peut faire ?",[958,959,960,966,972,978],"ul",{},[867,961,962,965],{},[42,963,964],{},"Chargement"," : montrer la forme de ce qui arrive, pas un spinner au milieu du vide. Un squelette garde la page stable et dit « ça vient ».",[867,967,968,971],{},[42,969,970],{},"Vide"," : ce n’est pas une erreur. Dire qu’il n’y a rien encore, et proposer la première action.",[867,973,974,977],{},[42,975,976],{},"Erreur"," : dire ce qui s’est passé sans jargon, rassurer sur ce qui n’a pas été perdu, et offrir une sortie. « Une erreur est survenue » n’aide personne.",[867,979,980,983],{},[42,981,982],{},"Succès"," : c’est le seul qu’on obtient gratuitement.",[25,985,987],{"id":986},"le-retour-à-laction","Le retour à l’action",[18,989,990],{},"Un bouton généré change de couleur au survol, et c’est tout. Il ne réagit pas à l’appui, il ne confirme rien, il ne dit pas si l’action est en cours. Sur mobile, où il n’y a pas de survol, il ne donne aucun signe de vie.",[18,992,993,994,998],{},"J’ai écrit ",[854,995,997],{"href":996},"\u002Fblog\u002Fun-bouton-qui-repond","un article entier"," sur ce seul point, parce qu’il change la sensation de toute l’interface. Le résumé : le retour visuel part à l’appui, l’action part au relâchement, et la confirmation arrive après.",[25,1000,1002],{"id":1001},"le-clavier","Le clavier",[18,1004,1005,1006,1009,1010,1013],{},"Essayez de parcourir l’écran avec la touche Tab. Souvent, l’ordre de parcours ne suit pas l’ordre visuel, le focus n’est pas visible, et la carte cliquable n’est pas atteignable parce que c’est un ",[55,1007,1008],{},"div"," avec un ",[55,1011,1012],{},"@click",". Rien de tout ça ne se voit sur une capture.",[18,1015,1016,1017,1020,1021,1024],{},"Les corrections sont petites : un vrai ",[55,1018,1019],{},"button"," ou un vrai lien, un ",[55,1022,1023],{},":focus-visible"," dessiné, l’ordre du DOM aligné sur l’ordre de lecture. Mais il faut les faire, et il faut y penser.",[25,1026,1028],{"id":1027},"le-texte","Le texte",[18,1030,1031],{},"Les textes générés sont corrects et interchangeables. « Gérer vos projets », « Commencer maintenant », « Une erreur est survenue ». Ils remplissent la case. Un texte qui aide est spécifique : il nomme ce qui va se passer, dans les mots de la personne qui lit.",[18,1033,1034],{},"Je réécris presque tous les libellés d’un écran généré. C’est le poste qui prend le plus de temps après les états, et c’est celui qui se voit le plus.",[25,1036,1038],{"id":1037},"les-écrans-intermédiaires","Les écrans intermédiaires",[18,1040,1041],{},"Entre 375 et 1440 pixels, il y a des largeurs où tout casse : une grille à trois colonnes qui devient illisible à 800, un titre qui déborde à 390, un tableau qui force le défilement horizontal. Les générations sont testées sur un écran, le vôtre, et c’est souvent le seul qui marche.",[18,1043,1044],{},"Je redimensionne la fenêtre lentement, de la plus étroite à la plus large, et je note chaque endroit où quelque chose saute. Ça prend deux minutes et ça évite les retours clients.",[25,1046,1048],{"id":1047},"le-mode-sombre","Le mode sombre",[18,1050,1051],{},"Si le projet a un thème sombre, le code généré oublie une couleur sur trois. Un fond blanc codé en dur dans une carte, une ombre invisible, un texte gris sur gris. La solution est en amont : ne jamais écrire une couleur dans un composant, toujours passer par les variables du thème. Une fois la règle dans le fichier de conventions, l’assistant la suit.",[719,1053,1055],{"type":1054},"note",[18,1056,1057],{},"Tout ce qui précède tient en une idée : l’IA produit ce qu’on lui décrit, et personne ne décrit les états vides, le focus clavier ou le texte d’erreur dans un brief. Ce sont les angles morts de la description, pas de l’outil.",[25,1059,1061],{"id":1060},"ma-liste","Ma liste",[18,1063,1064],{},"Avant qu’un écran généré n’aille plus loin, je passe ces points dans l’ordre :",[864,1066,1067,1070,1073,1076,1079,1082],{},[867,1068,1069],{},"Les quatre états existent et disent quoi faire.",[867,1071,1072],{},"Chaque action a un retour à l’appui et une confirmation.",[867,1074,1075],{},"Tout est atteignable au clavier, dans le bon ordre, avec un focus visible.",[867,1077,1078],{},"Chaque libellé nomme ce qui va se passer.",[867,1080,1081],{},"L’écran tient à 390, 768, 1024 et 1440 pixels.",[867,1083,1084],{},"Aucune couleur n’est écrite en dur.",[18,1086,1087],{},"Six points, dix minutes. C’est la différence entre un écran qui fonctionne et un écran qu’on a envie d’utiliser.",{"title":53,"searchDepth":79,"depth":79,"links":1089},[1090,1091,1092,1093,1094,1095,1096],{"id":946,"depth":79,"text":947},{"id":986,"depth":79,"text":987},{"id":1001,"depth":79,"text":1002},{"id":1027,"depth":79,"text":1028},{"id":1037,"depth":79,"text":1038},{"id":1047,"depth":79,"text":1048},{"id":1060,"depth":79,"text":1061},"2026-10-07","Une IA génère un écran correct en quelques secondes. Les détails qui le rendent agréable, ou simplement utilisable, viennent après. Liste de ce que je vérifie à la main.",{},{"title":935,"description":1098},"Interface générée par IA : 6 points à vérifier à la main","blog\u002Fce-que-l-ia-ne-voit-pas-dans-une-interface",[928,1104,1105],"Interface","Accessibilité","h6PnK0QzuO576IsiV6s7VYyIjKFmKVXSJ2Lu4TYMc0M",1793214702927]