[{"data":1,"prerenderedAt":1769},["ShallowReactive",2],{"docs-nav:fr":3,"docs:\u002Ffr\u002Fdocs\u002Finfra\u002Ftesting":189},[4],{"title":5,"path":6,"stem":7,"children":8,"page":52},"Fr","\u002Ffr","fr",[9],{"title":10,"path":11,"stem":12,"children":13},"Qu'est-ce que Fougere","\u002Ffr\u002Fdocs","fr\u002Fdocs\u002Findex",[14,15,19,23,27,31,53,78,123,148],{"title":10,"path":11,"stem":12},{"title":16,"path":17,"stem":18},"Démarrer","\u002Ffr\u002Fdocs\u002Fgetting-started","fr\u002Fdocs\u002F01.getting-started",{"title":20,"path":21,"stem":22},"Une app que vous avez déjà","\u002Ffr\u002Fdocs\u002Fexisting-app","fr\u002Fdocs\u002F02.existing-app",{"title":24,"path":25,"stem":26},"Apportez votre schéma","\u002Ffr\u002Fdocs\u002Fbring-your-schema","fr\u002Fdocs\u002F03.bring-your-schema",{"title":28,"path":29,"stem":30},"La CLI","\u002Ffr\u002Fdocs\u002Fcli","fr\u002Fdocs\u002F04.cli",{"title":32,"path":33,"stem":34,"children":35,"page":52},"Concepts","\u002Ffr\u002Fdocs\u002Fconcepts","fr\u002Fdocs\u002F2.concepts",[36,40,44,48],{"title":37,"path":38,"stem":39},"Philosophie","\u002Ffr\u002Fdocs\u002Fconcepts\u002Fphilosophy","fr\u002Fdocs\u002F2.concepts\u002F1.philosophy",{"title":41,"path":42,"stem":43},"La Frond","\u002Ffr\u002Fdocs\u002Fconcepts\u002Ffrond","fr\u002Fdocs\u002F2.concepts\u002F2.frond",{"title":45,"path":46,"stem":47},"Le chemin le plus court","\u002Ffr\u002Fdocs\u002Fconcepts\u002Fshortest-path","fr\u002Fdocs\u002F2.concepts\u002F3.shortest-path",{"title":49,"path":50,"stem":51},"Le socle","\u002Ffr\u002Fdocs\u002Fconcepts\u002Fthe-base","fr\u002Fdocs\u002F2.concepts\u002F4.the-base",false,{"title":54,"path":55,"stem":56,"children":57,"page":52},"Schéma","\u002Ffr\u002Fdocs\u002Fschema","fr\u002Fdocs\u002F3.schema",[58,62,66,70,74],{"title":59,"path":60,"stem":61},"Entités","\u002Ffr\u002Fdocs\u002Fschema\u002Fentities","fr\u002Fdocs\u002F3.schema\u002F1.entities",{"title":63,"path":64,"stem":65},"Vues","\u002Ffr\u002Fdocs\u002Fschema\u002Fviews","fr\u002Fdocs\u002F3.schema\u002F2.views",{"title":67,"path":68,"stem":69},"La carte d'identité","\u002Ffr\u002Fdocs\u002Fschema\u002Fcard","fr\u002Fdocs\u002F3.schema\u002F3.card",{"title":71,"path":72,"stem":73},"Standard Schema","\u002Ffr\u002Fdocs\u002Fschema\u002Fstandard-schema","fr\u002Fdocs\u002F3.schema\u002F4.standard-schema",{"title":75,"path":76,"stem":77},"Comment un schéma bouge","\u002Ffr\u002Fdocs\u002Fschema\u002Fevolution","fr\u002Fdocs\u002F3.schema\u002F5.evolution",{"title":79,"path":80,"stem":81,"children":82,"page":52},"Métier","\u002Ffr\u002Fdocs\u002Fbusiness","fr\u002Fdocs\u002F4.business",[83,87,91,95,99,103,107,111,115,119],{"title":84,"path":85,"stem":86},"Handlers","\u002Ffr\u002Fdocs\u002Fbusiness\u002Fhandlers","fr\u002Fdocs\u002F4.business\u002F1.handlers",{"title":88,"path":89,"stem":90},"Les ports","\u002Ffr\u002Fdocs\u002Fbusiness\u002Fports","fr\u002Fdocs\u002F4.business\u002F10.ports",{"title":92,"path":93,"stem":94},"Presenters","\u002Ffr\u002Fdocs\u002Fbusiness\u002Fpresenters","fr\u002Fdocs\u002F4.business\u002F2.presenters",{"title":96,"path":97,"stem":98},"Collectors","\u002Ffr\u002Fdocs\u002Fbusiness\u002Fcollectors","fr\u002Fdocs\u002F4.business\u002F3.collectors",{"title":100,"path":101,"stem":102},"Erreurs","\u002Ffr\u002Fdocs\u002Fbusiness\u002Ferrors","fr\u002Fdocs\u002F4.business\u002F4.errors",{"title":104,"path":105,"stem":106},"Seeds","\u002Ffr\u002Fdocs\u002Fbusiness\u002Fseeds","fr\u002Fdocs\u002F4.business\u002F5.seeds",{"title":108,"path":109,"stem":110},"Les faits","\u002Ffr\u002Fdocs\u002Fbusiness\u002Ffacts","fr\u002Fdocs\u002F4.business\u002F6.facts",{"title":112,"path":113,"stem":114},"Le port Storage","\u002Ffr\u002Fdocs\u002Fbusiness\u002Fstorage","fr\u002Fdocs\u002F4.business\u002F7.storage",{"title":116,"path":117,"stem":118},"Des écritures qui tiennent ou tombent ensemble","\u002Ffr\u002Fdocs\u002Fbusiness\u002Ftogether","fr\u002Fdocs\u002F4.business\u002F8.together",{"title":120,"path":121,"stem":122},"Les repositories","\u002Ffr\u002Fdocs\u002Fbusiness\u002Frepositories","fr\u002Fdocs\u002F4.business\u002F9.repositories",{"title":124,"path":125,"stem":126,"children":127,"page":52},"Client","\u002Ffr\u002Fdocs\u002Fclient","fr\u002Fdocs\u002F5.client",[128,132,136,140,144],{"title":129,"path":130,"stem":131},"Queries & commands","\u002Ffr\u002Fdocs\u002Fclient\u002Fqueries-commands","fr\u002Fdocs\u002F5.client\u002F1.queries-commands",{"title":133,"path":134,"stem":135},"Formulaires","\u002Ffr\u002Fdocs\u002Fclient\u002Fforms","fr\u002Fdocs\u002F5.client\u002F2.forms",{"title":137,"path":138,"stem":139},"Session","\u002Ffr\u002Fdocs\u002Fclient\u002Fsession","fr\u002Fdocs\u002F5.client\u002F3.session",{"title":141,"path":142,"stem":143},"invoke","\u002Ffr\u002Fdocs\u002Fclient\u002Finvoke","fr\u002Fdocs\u002F5.client\u002F4.invoke",{"title":145,"path":146,"stem":147},"Back-office","\u002Ffr\u002Fdocs\u002Fclient\u002Fadmin","fr\u002Fdocs\u002F5.client\u002F5.admin",{"title":149,"path":150,"stem":151,"children":152,"page":52},"Infrastructure","\u002Ffr\u002Fdocs\u002Finfra","fr\u002Fdocs\u002F6.infra",[153,157,161,165,169,173,177,181,185],{"title":154,"path":155,"stem":156},"Le gradient","\u002Ffr\u002Fdocs\u002Finfra\u002Fgradient","fr\u002Fdocs\u002F6.infra\u002F1.gradient",{"title":158,"path":159,"stem":160},"Surfaces — REST & GraphQL","\u002Ffr\u002Fdocs\u002Finfra\u002Fsurfaces","fr\u002Fdocs\u002F6.infra\u002F2.surfaces",{"title":162,"path":163,"stem":164},"Déploiement","\u002Ffr\u002Fdocs\u002Finfra\u002Fdeployment","fr\u002Fdocs\u002F6.infra\u002F3.deployment",{"title":166,"path":167,"stem":168},"Hôtes","\u002Ffr\u002Fdocs\u002Finfra\u002Fhosts","fr\u002Fdocs\u002F6.infra\u002F4.hosts",{"title":170,"path":171,"stem":172},"Les sources","\u002Ffr\u002Fdocs\u002Finfra\u002Fsources","fr\u002Fdocs\u002F6.infra\u002F5.sources",{"title":174,"path":175,"stem":176},"Le cycle de vie","\u002Ffr\u002Fdocs\u002Finfra\u002Flifecycle","fr\u002Fdocs\u002F6.infra\u002F6.lifecycle",{"title":178,"path":179,"stem":180},"L'identité entre Fronds","\u002Ffr\u002Fdocs\u002Finfra\u002Fidentity","fr\u002Fdocs\u002F6.infra\u002F7.identity",{"title":182,"path":183,"stem":184},"Observabilité","\u002Ffr\u002Fdocs\u002Finfra\u002Fobservability","fr\u002Fdocs\u002F6.infra\u002F8.observability",{"title":186,"path":187,"stem":188},"Tests","\u002Ffr\u002Fdocs\u002Finfra\u002Ftesting","fr\u002Fdocs\u002F6.infra\u002F9.testing",{"id":190,"title":186,"body":191,"description":1764,"extension":1765,"meta":1766,"navigation":317,"path":187,"seo":1767,"stem":188,"__hash__":1768},"docs_fr\u002Ffr\u002Fdocs\u002F6.infra\u002F9.testing.md",{"type":192,"value":193,"toc":1750},"minimark",[194,198,206,221,236,265,336,341,349,364,459,477,495,499,556,562,568,591,598,608,650,655,680,684,761,806,817,820,824,831,985,996,1003,1010,1014,1037,1044,1051,1065,1092,1095,1181,1196,1203,1272,1276,1283,1351,1366,1369,1373,1376,1399,1481,1493,1510,1514,1534,1564,1570,1574,1710,1725,1736,1743,1746],[195,196,186],"h1",{"id":197},"tests",[199,200,201,205],"p",{},[202,203,204],"code",{},"@fougere\u002Ftesting"," repose sur deux idées, et aucune n'est une technique de test.",[199,207,208,212,213,216,217,220],{},[209,210,211],"strong",{},"Une partie d'une suite n'a pas de raison d'être écrite."," ",[202,214,215],{},"Judge.row"," a une liste\n",[209,218,219],{},"fermée"," de refus — champ inconnu, champ manquant, lecture seule, immuable, la forme, un\ncodec nommé — et les quatre axes disent qui écrit quoi et quand. Les cas se calculent donc\ndepuis l'entité. Un test tapé à la main devient faux le jour où l'entité bouge et personne\nne l'apprend ; un test fabriqué bouge avec elle.",[199,222,223,226,227,230,231,235],{},[209,224,225],{},"Le niveau de réalité d'un test ne lui appartient pas."," C'est le geste que Fougere\napplique déjà à la topologie : ",[202,228,229],{},"remotes:"," est extérieur au frond, et le même code tourne\nen process ou derrière JSON-RPC. Ici, ce qui est réel et l'endroit où ça tourne sont\nénoncés ",[232,233,234],"em",{},"hors"," du fichier de test, qui ne dit que ce qu'il attend.",[237,238,243],"pre",{"className":239,"code":240,"language":241,"meta":242,"style":242},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","pnpm add -D @fougere\u002Ftesting\n","bash","",[202,244,245],{"__ignoreMap":242},[246,247,250,254,258,262],"span",{"class":248,"line":249},"line",1,[246,251,253],{"class":252},"sbgvK","pnpm",[246,255,257],{"class":256},"s_sjI"," add",[246,259,261],{"class":260},"stzsN"," -D",[246,263,264],{"class":256}," @fougere\u002Ftesting\n",[237,266,270],{"className":267,"code":268,"language":269,"meta":242,"style":242},"language-ts shiki shiki-themes material-theme-lighter github-light github-dark","\u002F\u002F vitest.config.ts — le fichier entier\nimport { fougereTest } from '@fougere\u002Ftesting\u002Fvitest';\n\nexport default fougereTest();\n","ts",[202,271,272,278,312,319],{"__ignoreMap":242},[246,273,274],{"class":248,"line":249},[246,275,277],{"class":276},"sutJx","\u002F\u002F vitest.config.ts — le fichier entier\n",[246,279,281,285,289,293,296,299,303,306,309],{"class":248,"line":280},2,[246,282,284],{"class":283},"sVHd0","import",[246,286,288],{"class":287},"sP7_E"," {",[246,290,292],{"class":291},"su5hD"," fougereTest",[246,294,295],{"class":287}," }",[246,297,298],{"class":283}," from",[246,300,302],{"class":301},"sjJ54"," '",[246,304,305],{"class":256},"@fougere\u002Ftesting\u002Fvitest",[246,307,308],{"class":301},"'",[246,310,311],{"class":287},";\n",[246,313,315],{"class":248,"line":314},3,[246,316,318],{"emptyLinePlaceholder":317},true,"\n",[246,320,322,325,328,331,334],{"class":248,"line":321},4,[246,323,324],{"class":283},"export",[246,326,327],{"class":283}," default",[246,329,292],{"class":330},"sGLFI",[246,332,333],{"class":291},"()",[246,335,311],{"class":287},[337,338,340],"h2",{"id":339},"la-position-du-fichier-dit-son-sujet","La position du fichier dit son sujet",[237,342,347],{"className":343,"code":345,"language":346},[344],"language-text","fronds\u002Fcatalog\u002Ftests\u002Fpricing.test.ts    catalog est réel, ses voisins non\ntests\u002Fcontracts.test.ts                 tous les fronds sont réels, ensemble\n","text",[202,348,345],{"__ignoreMap":242},[199,350,351,352,355,356,359,360,363],{},"Un dossier est une déclaration — la lecture que le scan fait déjà sur ",[202,353,354],{},"entities\u002F"," et\n",[202,357,358],{},"handlers\u002F",". Donc ",[202,361,362],{},"testApp()"," ne prend aucun argument :",[237,365,367],{"className":267,"code":366,"language":269,"meta":242,"style":242},"await using app = await testApp();\n\nexpect(app.fronds.map((frond) => frond.name)).toEqual(['catalog']);\n",[202,368,369,393,397],{"__ignoreMap":242},[246,370,371,375,379,383,386,389,391],{"class":248,"line":249},[246,372,374],{"class":373},"sbsja","await using",[246,376,378],{"class":377},"s_hVV"," app",[246,380,382],{"class":381},"smGrS"," =",[246,384,385],{"class":283}," await",[246,387,388],{"class":330}," testApp",[246,390,333],{"class":291},[246,392,311],{"class":287},[246,394,395],{"class":248,"line":280},[246,396,318],{"emptyLinePlaceholder":317},[246,398,399,402,405,408,411,413,416,419,421,425,428,431,434,436,439,441,444,447,449,452,454,457],{"class":248,"line":314},[246,400,401],{"class":330},"expect",[246,403,404],{"class":291},"(app",[246,406,407],{"class":287},".",[246,409,410],{"class":291},"fronds",[246,412,407],{"class":287},[246,414,415],{"class":330},"map",[246,417,418],{"class":291},"(",[246,420,418],{"class":287},[246,422,424],{"class":423},"s99_P","frond",[246,426,427],{"class":287},")",[246,429,430],{"class":373}," =>",[246,432,433],{"class":291}," frond",[246,435,407],{"class":287},[246,437,438],{"class":291},"name))",[246,440,407],{"class":287},[246,442,443],{"class":330},"toEqual",[246,445,446],{"class":291},"([",[246,448,308],{"class":301},[246,450,451],{"class":256},"catalog",[246,453,308],{"class":301},[246,455,456],{"class":291},"])",[246,458,311],{"class":287},[199,460,461,462,465,466,469,470,472,473,476],{},"Le projet commence au premier ancêtre portant un ",[202,463,464],{},"fougere.config.ts"," ou un ",[202,467,468],{},"fronds\u002F",", et\nle sujet est le segment qui suit ",[202,471,468],{},". Un sous-dossier sous ",[202,474,475],{},"tests\u002F"," ne porte rien, et\nle titre d'un test non plus : un nom est de la prose, et de la prose qui décide du câblage\nd'une application est le runtime caché que la doctrine refuse.",[199,478,479,480,483,484,487,488,491,492,494],{},"Les lignes vivent dans SQLite ",[202,481,482],{},":memory:"," — un vrai moteur, donc le DDL tourne, les\ncontraintes ",[202,485,486],{},"CHECK"," existent et les transactions sont réelles, et chaque appel a sa propre\nbase. Énoncer ",[202,489,490],{},"root"," ou ",[202,493,410],{}," soi-même gagne sur la position, la même précédence qu'une\nclé de config a sur une convention partout ailleurs.",[337,496,498],{"id":497},"les-cas-viennent-de-lentité","Les cas viennent de l'entité",[237,500,502],{"className":267,"code":501,"language":269,"meta":242,"style":242},"\u002F\u002F tests\u002Fcontracts.test.ts — le fichier entier\nimport { testApp, checkAll } from '@fougere\u002Ftesting';\n\ncheckAll(await testApp());\n",[202,503,504,509,535,539],{"__ignoreMap":242},[246,505,506],{"class":248,"line":249},[246,507,508],{"class":276},"\u002F\u002F tests\u002Fcontracts.test.ts — le fichier entier\n",[246,510,511,513,515,517,520,523,525,527,529,531,533],{"class":248,"line":280},[246,512,284],{"class":283},[246,514,288],{"class":287},[246,516,388],{"class":291},[246,518,519],{"class":287},",",[246,521,522],{"class":291}," checkAll",[246,524,295],{"class":287},[246,526,298],{"class":283},[246,528,302],{"class":301},[246,530,204],{"class":256},[246,532,308],{"class":301},[246,534,311],{"class":287},[246,536,537],{"class":248,"line":314},[246,538,318],{"emptyLinePlaceholder":317},[246,540,541,544,546,549,551,554],{"class":248,"line":321},[246,542,543],{"class":330},"checkAll",[246,545,418],{"class":291},[246,547,548],{"class":283},"await",[246,550,388],{"class":330},[246,552,553],{"class":291},"())",[246,555,311],{"class":287},[199,557,558,559,407],{},"Il ne nomme aucune entité : la liste vient du scan. Une liste d'imports est une seconde\ncopie de ce que le projet déclare déjà, et elle périme le jour où une entité est ajoutée —\nen silence, parce qu'un test absent ne peut pas échouer. Une entité à la fois s'écrit\n",[202,560,561],{},"checkContract(app, Product)",[237,563,566],{"className":564,"code":565,"language":346},[344],"✓ Product — le contrat qu'elle déclare\n  ✓ un corps valide             ✓ sku plus court que le min   ✓ cents sous le minimum\n  ✓ une clé hors contrat        ✓ sku plus long que le max    ✓ cents au-dessus du maximum\n  ✓ pas un objet du tout        ✓ name absent                 ✓ status hors de l'ensemble\n  ✓ sku absent                  ✓ name du mauvais type        ✓ createdAt fourni sur un update\n",[202,567,565],{"__ignoreMap":242},[199,569,570,571,574,575,578,579,582,583,586,587,590],{},"Aucun cas n'est écrit. Ils sont énumérés depuis cinq lectures qui existent déjà —\n",[202,572,573],{},"Visibility.input"," pour ce qui a le droit d'entrer, ",[202,576,577],{},"Judge.onAbsent"," pour ce qui est requis,\n",[202,580,581],{},"Lifecycle.of(field).immutable"," pour le mode patch, la ",[202,584,585],{},"shape"," pour les bornes, et la clé\nhors contrat. Change un ",[202,588,589],{},"min:"," dans l'entité, les cas suivent.",[199,592,593,594,597],{},"Un cas porte le ",[209,595,596],{},"chemin"," du champ fautif, jamais le message : on sait quel champ doit\nêtre refusé parce que c'est nous qui l'avons cassé, et nommer le message ferait de la liste\nun second juge à tenir à la main.",[199,599,600,603,604,607],{},[202,601,602],{},"checkOutput"," ne coûte rien à énoncer — ",[202,605,606],{},"Visibility.output"," y répond déjà, donc un champ que la\nfrontière ferme n'a rien à faire dans une réponse :",[237,609,611],{"className":267,"code":610,"language":269,"meta":242,"style":242},"password: text({ boundary: 'writeOnly' })   \u002F\u002F entre, ne ressort jamais\n",[202,612,613],{"__ignoreMap":242},[246,614,615,618,621,624,626,629,633,635,637,640,642,644,647],{"class":248,"line":249},[246,616,617],{"class":252},"password",[246,619,620],{"class":287},":",[246,622,623],{"class":330}," text",[246,625,418],{"class":291},[246,627,628],{"class":287},"{",[246,630,632],{"class":631},"skxfh"," boundary",[246,634,620],{"class":287},[246,636,302],{"class":301},[246,638,639],{"class":256},"writeOnly",[246,641,308],{"class":301},[246,643,295],{"class":287},[246,645,646],{"class":291},")   ",[246,648,649],{"class":276},"\u002F\u002F entre, ne ressort jamais\n",[651,652,654],"h3",{"id":653},"ce-que-ça-ne-prouve-pas","Ce que ça ne prouve pas",[199,656,657,658,661,662,664,665,668,669,675,676,679],{},"Un test fabriqué depuis ",[202,659,660],{},"Post"," ne peut jamais dire que ",[202,663,660],{}," a tort. Il prouve que les\n",[209,666,667],{},"réalisations sont d'accord",". Ce qui doit casser quand la déclaration bouge est ailleurs\net déjà construit : ",[670,671,672],"a",{"href":76},[202,673,674],{},"fougere migrate",". Et l'intention métier —\n",[232,677,678],{},"publier met le statut à published"," — s'écrit toujours à la main.",[337,681,683],{"id":682},"fabriquer-un-corps-valide","Fabriquer un corps valide",[237,685,687],{"className":267,"code":686,"language":269,"meta":242,"style":242},"sampleInput(Product)                          \u002F\u002F → { sku: 'gDXv8gBIU', name: '…', cents: 750868 }\nsampleInput(Product, { sku: 'ABC-01' })       \u002F\u002F ce que tu imposes, tel quel\nsampleInput(Product, {}, { seed: 7 })         \u002F\u002F figé\n",[202,688,689,700,731],{"__ignoreMap":242},[246,690,691,694,697],{"class":248,"line":249},[246,692,693],{"class":330},"sampleInput",[246,695,696],{"class":291},"(Product)                          ",[246,698,699],{"class":276},"\u002F\u002F → { sku: 'gDXv8gBIU', name: '…', cents: 750868 }\n",[246,701,702,704,707,709,711,714,716,718,721,723,725,728],{"class":248,"line":280},[246,703,693],{"class":330},[246,705,706],{"class":291},"(Product",[246,708,519],{"class":287},[246,710,288],{"class":287},[246,712,713],{"class":631}," sku",[246,715,620],{"class":287},[246,717,302],{"class":301},[246,719,720],{"class":256},"ABC-01",[246,722,308],{"class":301},[246,724,295],{"class":287},[246,726,727],{"class":291},")       ",[246,729,730],{"class":276},"\u002F\u002F ce que tu imposes, tel quel\n",[246,732,733,735,737,739,742,744,747,749,753,755,758],{"class":248,"line":314},[246,734,693],{"class":330},[246,736,706],{"class":291},[246,738,519],{"class":287},[246,740,741],{"class":287}," {},",[246,743,288],{"class":287},[246,745,746],{"class":631}," seed",[246,748,620],{"class":287},[246,750,752],{"class":751},"srdBf"," 7",[246,754,295],{"class":287},[246,756,757],{"class":291},")         ",[246,759,760],{"class":276},"\u002F\u002F figé\n",[199,762,763,764,212,766,769,770,773,774,777,778,777,781,777,784,787,788,791,792,795,796,798,799,787,802,805],{},"La valeur elle-même n'est pas à nous d'inventer : une ",[202,765,585],{},[209,767,768],{},"est"," du JSON Schema, donc\n",[202,771,772],{},"json-schema-faker"," respecte ",[202,775,776],{},"minLength",", ",[202,779,780],{},"enum",[202,782,783],{},"format",[202,785,786],{},"pattern"," et ",[202,789,790],{},"items",". Ce qui\nest à nous, c'est ",[232,793,794],{},"quels"," champs appartiennent à un corps — ",[202,797,573],{},", le seul lecteur\ndes axes ",[202,800,801],{},"boundary",[202,803,804],{},"lifecycle"," sur lequel la façade et le formulaire se tiennent déjà.",[199,807,808,809,812,813,816],{},"La graine vaut par défaut une valeur dérivée du nom de l'entité, donc deux exécutions sont\nd'accord et un échec se rejoue. Une ",[202,810,811],{},"ref()"," est ",[209,814,815],{},"refusée par son nom"," plutôt que\nfabriquée : elle désigne une ligne qui doit exister, et un id inventé ne pointe sur rien.",[199,818,819],{},"La même fonction sème une base de dev et pose le décor d'un scénario navigateur.",[337,821,823],{"id":822},"les-bouchons","Les bouchons",[199,825,826,827,830],{},"On ne peut boucher que ce qui est déjà déclaré remplaçable — un\n",[670,828,829],{"href":89},"port",". « J'ai besoin d'un mock ici » devient un signal de\nconception plutôt qu'un réflexe.",[237,832,834],{"className":267,"code":833,"language":269,"meta":242,"style":242},"await using app = await testApp({ stub: [Pricing] });\napp.stub(Pricing).total.mockReturnValue(4242);\n\nawait run({ entity: 'product', op: 'quote' }, invocation);\n\nexpect(app.stub(Pricing).total).toHaveBeenCalledWith(1000);\n",[202,835,836,867,899,903,948,953],{"__ignoreMap":242},[246,837,838,840,842,844,846,848,850,852,855,857,860,863,865],{"class":248,"line":249},[246,839,374],{"class":373},[246,841,378],{"class":377},[246,843,382],{"class":381},[246,845,385],{"class":283},[246,847,388],{"class":330},[246,849,418],{"class":291},[246,851,628],{"class":287},[246,853,854],{"class":631}," stub",[246,856,620],{"class":287},[246,858,859],{"class":291}," [Pricing] ",[246,861,862],{"class":287},"}",[246,864,427],{"class":291},[246,866,311],{"class":287},[246,868,869,872,874,877,880,882,885,887,890,892,895,897],{"class":248,"line":280},[246,870,871],{"class":291},"app",[246,873,407],{"class":287},[246,875,876],{"class":330},"stub",[246,878,879],{"class":291},"(Pricing)",[246,881,407],{"class":287},[246,883,884],{"class":291},"total",[246,886,407],{"class":287},[246,888,889],{"class":330},"mockReturnValue",[246,891,418],{"class":291},[246,893,894],{"class":751},"4242",[246,896,427],{"class":291},[246,898,311],{"class":287},[246,900,901],{"class":248,"line":314},[246,902,318],{"emptyLinePlaceholder":317},[246,904,905,907,910,912,914,917,919,921,924,926,928,931,933,935,938,940,943,946],{"class":248,"line":321},[246,906,548],{"class":283},[246,908,909],{"class":330}," run",[246,911,418],{"class":291},[246,913,628],{"class":287},[246,915,916],{"class":631}," entity",[246,918,620],{"class":287},[246,920,302],{"class":301},[246,922,923],{"class":256},"product",[246,925,308],{"class":301},[246,927,519],{"class":287},[246,929,930],{"class":631}," op",[246,932,620],{"class":287},[246,934,302],{"class":301},[246,936,937],{"class":256},"quote",[246,939,308],{"class":301},[246,941,942],{"class":287}," },",[246,944,945],{"class":291}," invocation)",[246,947,311],{"class":287},[246,949,951],{"class":248,"line":950},5,[246,952,318],{"emptyLinePlaceholder":317},[246,954,956,958,960,962,964,966,968,971,973,976,978,981,983],{"class":248,"line":955},6,[246,957,401],{"class":330},[246,959,404],{"class":291},[246,961,407],{"class":287},[246,963,876],{"class":330},[246,965,879],{"class":291},[246,967,407],{"class":287},[246,969,970],{"class":291},"total)",[246,972,407],{"class":287},[246,974,975],{"class":330},"toHaveBeenCalledWith",[246,977,418],{"class":291},[246,979,980],{"class":751},"1000",[246,982,427],{"class":291},[246,984,311],{"class":287},[199,986,987,988,991,992,995],{},"Le bouchon est ",[209,989,990],{},"proposé, pas écrit"," : ses méthodes sont lues sur le prototype du port,\ndonc il porte exactement ce que le port porte et gagne une méthode le jour où le port en\ngagne une. Ce qu'il ",[232,993,994],{},"renvoie"," est à toi : le type de retour d'un service est un type\nTypeScript nu, effacé à l'exécution, sans champs déclarés d'où partir. C'est la ligne sur\nlaquelle tout le paquet repose : ce que le vocabulaire de Fougere déclare se dérive, du\ncode arbitraire non.",[199,997,998,999,1002],{},"Un port sous lequel personne ne répond est refusé par son nom, pour la raison qui fait que\n",[202,1000,1001],{},"ports:"," refuse une clé qui n'a rien trouvé.",[199,1004,1005,1006,1009],{},"Rien ne peut se substituer à ce qu'un handler va chercher lui-même — un ",[202,1007,1008],{},"fetch",", une lib\nimportée en haut du fichier, l'horloge. Fougere résout par la signature, donc il ne\nremplace que ce que la signature déclare, et la réponse du modèle pour un appel extérieur\nest déjà d'en faire un port.",[337,1011,1013],{"id":1012},"les-portes-sont-daccord","Les portes sont d'accord",[237,1015,1017],{"className":267,"code":1016,"language":269,"meta":242,"style":242},"checkDoors(app, Product);   \u002F\u002F locale · REST · GraphQL · RPC\n",[202,1018,1019],{"__ignoreMap":242},[246,1020,1021,1024,1026,1028,1031,1034],{"class":248,"line":249},[246,1022,1023],{"class":330},"checkDoors",[246,1025,404],{"class":291},[246,1027,519],{"class":287},[246,1029,1030],{"class":291}," Product)",[246,1032,1033],{"class":287},";",[246,1035,1036],{"class":276},"   \u002F\u002F locale · REST · GraphQL · RPC\n",[199,1038,1039,1040,1043],{},"Les cinq opérations CRUD, lectures et écritures. Une écriture crée une ligne différente à\nchaque porte, donc ce qui est comparé est ce que l'appelant a ",[209,1041,1042],{},"envoyé"," — comparer les\nvaleurs comparerait les horloges.",[199,1045,1046,1047,1050],{},"Le dépôt affirme partout que REST, GraphQL et RPC sont trois projections d'un seul\ncontrat. C'est ce qui le vérifie, sur les lignes ",[209,1048,1049],{},"et"," sur les refus — un refus est\nl'endroit où les portes divergent le plus, et où chacune est le plus tentée de répondre\ndans ses propres mots.",[199,1052,1053,1054,1057,1058,1061,1062,407],{},"L'enveloppe de chaque porte est retirée avant de comparer : elles emballent différemment\npar construction, et comparer les emballages comparerait les protocoles. Le champ GraphQL\nn'est pas recalculé mais ",[209,1055,1056],{},"demandé au schéma"," — une liste est le champ dont le type est le\ntype-liste de l'entité, un ",[202,1059,1060],{},"findById"," celui du type de l'entité qui prend un ",[202,1063,1064],{},"id",[199,1066,1067,1068,1071,1072,1075,1076,1079,1080,1083,1084,1087,1088,1091],{},"Pour un invariant propre à un handler, ",[202,1069,1070],{},"checkDoorContract(app, Entity, cases)"," exécute les\nmêmes cas écrits à la main sur les quatre portes. Fougere l'utilise pour verrouiller la\nsémantique des entrées optionnelles : ",[202,1073,1074],{},"foo?: T"," omis arrive en ",[202,1077,1078],{},"undefined",",\n",[202,1081,1082],{},"foo: T | null"," conserve un ",[202,1085,1086],{},"null"," explicite, et ",[202,1089,1090],{},"foo?: T | null"," distingue les deux.",[337,1093,108],{"id":1094},"les-faits",[237,1096,1098],{"className":267,"code":1097,"language":269,"meta":242,"style":242},"await run({ entity: 'post', op: 'publish' }, invocation);\n\nexpect(app.announced(PostPublished)).toMatchObject([{ id, title }]);\n",[202,1099,1100,1140,1144],{"__ignoreMap":242},[246,1101,1102,1104,1106,1108,1110,1112,1114,1116,1119,1121,1123,1125,1127,1129,1132,1134,1136,1138],{"class":248,"line":249},[246,1103,548],{"class":283},[246,1105,909],{"class":330},[246,1107,418],{"class":291},[246,1109,628],{"class":287},[246,1111,916],{"class":631},[246,1113,620],{"class":287},[246,1115,302],{"class":301},[246,1117,1118],{"class":256},"post",[246,1120,308],{"class":301},[246,1122,519],{"class":287},[246,1124,930],{"class":631},[246,1126,620],{"class":287},[246,1128,302],{"class":301},[246,1130,1131],{"class":256},"publish",[246,1133,308],{"class":301},[246,1135,942],{"class":287},[246,1137,945],{"class":291},[246,1139,311],{"class":287},[246,1141,1142],{"class":248,"line":280},[246,1143,318],{"emptyLinePlaceholder":317},[246,1145,1146,1148,1150,1152,1155,1158,1160,1163,1165,1167,1170,1172,1175,1177,1179],{"class":248,"line":314},[246,1147,401],{"class":330},[246,1149,404],{"class":291},[246,1151,407],{"class":287},[246,1153,1154],{"class":330},"announced",[246,1156,1157],{"class":291},"(PostPublished))",[246,1159,407],{"class":287},[246,1161,1162],{"class":330},"toMatchObject",[246,1164,446],{"class":291},[246,1166,628],{"class":287},[246,1168,1169],{"class":291}," id",[246,1171,519],{"class":287},[246,1173,1174],{"class":291}," title ",[246,1176,862],{"class":287},[246,1178,456],{"class":291},[246,1180,311],{"class":287},[199,1182,1183,1184,1187,1188,1191,1192,1195],{},"Observer ne coûte rien : ",[202,1185,1186],{},"Emissions.announce"," remet chaque fait au transporteur, donc un\ntest s'assied à sa place et n'ajoute aucun second dispatcheur — la seule chose que\n",[202,1189,1190],{},"Emissions"," refuse d'être. Un fait voyage sous sa clé d'enregistrement (",[202,1193,1194],{},"postPublished","),\net les deux orthographes sont acceptées.",[199,1197,1198,1199,1202],{},"Son dual n'a rien demandé — ",[202,1200,1201],{},"app.deliver"," est déjà la porte du transporteur, elle attend\ntous les abonnés et rejette si l'un refuse :",[237,1204,1206],{"className":267,"code":1205,"language":269,"meta":242,"style":242},"await app.deliver('postPublished', { id: 'p1', title: 'Delivered', at: … });\n",[202,1207,1208],{"__ignoreMap":242},[246,1209,1210,1212,1214,1216,1219,1221,1223,1225,1227,1229,1231,1233,1235,1237,1240,1242,1244,1247,1249,1251,1254,1256,1258,1261,1263,1266,1268,1270],{"class":248,"line":249},[246,1211,548],{"class":283},[246,1213,378],{"class":291},[246,1215,407],{"class":287},[246,1217,1218],{"class":330},"deliver",[246,1220,418],{"class":291},[246,1222,308],{"class":301},[246,1224,1194],{"class":256},[246,1226,308],{"class":301},[246,1228,519],{"class":287},[246,1230,288],{"class":287},[246,1232,1169],{"class":631},[246,1234,620],{"class":287},[246,1236,302],{"class":301},[246,1238,1239],{"class":256},"p1",[246,1241,308],{"class":301},[246,1243,519],{"class":287},[246,1245,1246],{"class":631}," title",[246,1248,620],{"class":287},[246,1250,302],{"class":301},[246,1252,1253],{"class":256},"Delivered",[246,1255,308],{"class":301},[246,1257,519],{"class":287},[246,1259,1260],{"class":631}," at",[246,1262,620],{"class":287},[246,1264,1265],{"class":291}," … ",[246,1267,862],{"class":287},[246,1269,427],{"class":291},[246,1271,311],{"class":287},[337,1273,1275],{"id":1274},"la-dérive-entre-fronds","La dérive entre fronds",[199,1277,1278,1279,1282],{},"Le seul endroit où le gradient ment vraiment : le code est identique en process et coupé\nen deux, mais un côté peut avoir vieilli. ",[202,1280,1281],{},"fougere sync"," a écrit la copie du consommateur\nil y a trois semaines, le producteur a avancé, et ça compile toujours.",[237,1284,1286],{"className":267,"code":1285,"language":269,"meta":242,"style":242},"const drift = driftOf(mine, theirs, 'blog');\n\nexplain(drift);\n\u002F\u002F blog.post — gone: publish\n\u002F\u002F blog.post — title: its bounds moved\n\u002F\u002F postPublished — + authorId (required) → re-sync and deploy the readers, THEN the sender\n",[202,1287,1288,1322,1326,1336,1341,1346],{"__ignoreMap":242},[246,1289,1290,1293,1296,1298,1301,1304,1306,1309,1311,1313,1316,1318,1320],{"class":248,"line":249},[246,1291,1292],{"class":373},"const",[246,1294,1295],{"class":377}," drift",[246,1297,382],{"class":381},[246,1299,1300],{"class":330}," driftOf",[246,1302,1303],{"class":291},"(mine",[246,1305,519],{"class":287},[246,1307,1308],{"class":291}," theirs",[246,1310,519],{"class":287},[246,1312,302],{"class":301},[246,1314,1315],{"class":256},"blog",[246,1317,308],{"class":301},[246,1319,427],{"class":291},[246,1321,311],{"class":287},[246,1323,1324],{"class":248,"line":280},[246,1325,318],{"emptyLinePlaceholder":317},[246,1327,1328,1331,1334],{"class":248,"line":314},[246,1329,1330],{"class":330},"explain",[246,1332,1333],{"class":291},"(drift)",[246,1335,311],{"class":287},[246,1337,1338],{"class":248,"line":321},[246,1339,1340],{"class":276},"\u002F\u002F blog.post — gone: publish\n",[246,1342,1343],{"class":248,"line":950},[246,1344,1345],{"class":276},"\u002F\u002F blog.post — title: its bounds moved\n",[246,1347,1348],{"class":248,"line":955},[246,1349,1350],{"class":276},"\u002F\u002F postPublished — + authorId (required) → re-sync and deploy the readers, THEN the sender\n",[199,1352,1353,1354,1357,1358,1361,1362,1365],{},"Les deux cartes viennent de ",[202,1355,1356],{},"rpc.discover",", et ",[202,1359,1360],{},"diff"," nomme ce qui les sépare. Lu dans un\nseul sens, exprès : un producteur qui sert ",[232,1363,1364],{},"plus"," que ce que le consommateur connaît n'est\npas une dérive, c'est un producteur qui a avancé sans casser personne.",[199,1367,1368],{},"Sur les faits, la ligne dit l'ordre à voix haute, parce qu'un fait est jugé strictement :\nun lecteur qui n'a pas été re-synchronisé refuse ce que l'émetteur annonce maintenant.",[337,1370,1372],{"id":1371},"le-navigateur","Le navigateur",[199,1374,1375],{},"Le cran que les autres ne peuvent pas atteindre : un formulaire qui n'énonce aucune règle\nde son côté.",[237,1377,1379],{"className":267,"code":1378,"language":269,"meta":242,"style":242},"formFieldsOf(Product, 'product')\n",[202,1380,1381],{"__ignoreMap":242},[246,1382,1383,1386,1388,1390,1392,1394,1396],{"class":248,"line":249},[246,1384,1385],{"class":330},"formFieldsOf",[246,1387,706],{"class":291},[246,1389,519],{"class":287},[246,1391,302],{"class":301},[246,1393,923],{"class":256},[246,1395,308],{"class":301},[246,1397,1398],{"class":291},")\n",[237,1400,1404],{"className":1401,"code":1402,"language":1403,"meta":242,"style":242},"language-html shiki shiki-themes material-theme-lighter github-light github-dark","\u003Cinput id=\"sku\" name=\"sku\" type=\"text\" required minlength=\"3\" maxlength=\"12\">\n","html",[202,1405,1406],{"__ignoreMap":242},[246,1407,1408,1411,1415,1418,1421,1424,1427,1429,1432,1434,1436,1438,1440,1443,1445,1447,1449,1451,1454,1457,1459,1461,1464,1466,1469,1471,1473,1476,1478],{"class":248,"line":249},[246,1409,1410],{"class":287},"\u003C",[246,1412,1414],{"class":1413},"sQzsp","input",[246,1416,1169],{"class":1417},"s9AJx",[246,1419,1420],{"class":287},"=",[246,1422,1423],{"class":301},"\"",[246,1425,1426],{"class":256},"sku",[246,1428,1423],{"class":301},[246,1430,1431],{"class":1417}," name",[246,1433,1420],{"class":287},[246,1435,1423],{"class":301},[246,1437,1426],{"class":256},[246,1439,1423],{"class":301},[246,1441,1442],{"class":1417}," type",[246,1444,1420],{"class":287},[246,1446,1423],{"class":301},[246,1448,346],{"class":256},[246,1450,1423],{"class":301},[246,1452,1453],{"class":1417}," required",[246,1455,1456],{"class":1417}," minlength",[246,1458,1420],{"class":287},[246,1460,1423],{"class":301},[246,1462,1463],{"class":256},"3",[246,1465,1423],{"class":301},[246,1467,1468],{"class":1417}," maxlength",[246,1470,1420],{"class":287},[246,1472,1423],{"class":301},[246,1474,1475],{"class":256},"12",[246,1477,1423],{"class":301},[246,1479,1480],{"class":287},">\n",[199,1482,1483,1484,1486,1487,1489,1490,1492],{},"L'appartenance vient de ",[202,1485,573],{},", l'obligation de l'axe ",[202,1488,804],{},", et les bornes de\nla forme — sous les noms que le navigateur applique déjà. La page ne contient donc aucun\ncode de validation : le navigateur refuse un ",[202,1491,1426],{}," de deux caractères, et le même corps\nenvoyé directement à la porte est refusé par le juge. « Juge local = juge distant » se\nregarde ici au lieu de se plaider ; tous les autres crans ne peuvent que le simuler dans\nun seul process.",[199,1494,1495,1496,1499,1500,1503,1504,777,1507,407],{},"Playwright pilote et démarre le serveur lui-même (",[202,1497,1498],{},"webServer"," dans\n",[202,1501,1502],{},"playwright.config.ts","). Un exemple complet : ",[202,1505,1506],{},"demos\u002Ftest-gradient",[202,1508,1509],{},"pnpm e2e",[337,1511,1513],{"id":1512},"une-horloge-figée","Une horloge figée",[199,1515,1516,777,1519,787,1522,1525,1526,1529,1530,1533],{},[202,1517,1518],{},"created()",[202,1520,1521],{},"updated()",[202,1523,1524],{},"create: 'now'"," sont réalisés en un seul endroit —\n",[202,1527,1528],{},"applyCreate","\u002F",[202,1531,1532],{},"applyUpdate"," — et c'est ce qui rend l'instant substituable :",[237,1535,1537],{"className":267,"code":1536,"language":269,"meta":242,"style":242},"const release = freezeClock('2026-01-02T03:04:05Z');\n",[202,1538,1539],{"__ignoreMap":242},[246,1540,1541,1543,1546,1548,1551,1553,1555,1558,1560,1562],{"class":248,"line":249},[246,1542,1292],{"class":373},[246,1544,1545],{"class":377}," release",[246,1547,382],{"class":381},[246,1549,1550],{"class":330}," freezeClock",[246,1552,418],{"class":291},[246,1554,308],{"class":301},[246,1556,1557],{"class":256},"2026-01-02T03:04:05Z",[246,1559,308],{"class":301},[246,1561,427],{"class":291},[246,1563,311],{"class":287},[199,1565,1566,1567,407],{},"Une valeur, pas une interception : rien là-dedans n'est un canal, et un instant stable est\nun fait sur l'exécution plutôt qu'un crochet sur ",[202,1568,1569],{},"Date",[337,1571,1573],{"id":1572},"la-charge","La charge",[237,1575,1577],{"className":267,"code":1576,"language":269,"meta":242,"style":242},"\u002F\u002F scripts\u002Fload.ts\nimport { writeFileSync } from 'node:fs';\nimport { testApp, loadScript } from '@fougere\u002Ftesting';\n\nconst app = await testApp();\nwriteFileSync('load.js', loadScript(app, { door: 'http:\u002F\u002F127.0.0.1:3000\u002F_fougere\u002Fcall' }));\nawait app.dispose();\n",[202,1578,1579,1584,1606,1631,1635,1651,1694],{"__ignoreMap":242},[246,1580,1581],{"class":248,"line":249},[246,1582,1583],{"class":276},"\u002F\u002F scripts\u002Fload.ts\n",[246,1585,1586,1588,1590,1593,1595,1597,1599,1602,1604],{"class":248,"line":280},[246,1587,284],{"class":283},[246,1589,288],{"class":287},[246,1591,1592],{"class":291}," writeFileSync",[246,1594,295],{"class":287},[246,1596,298],{"class":283},[246,1598,302],{"class":301},[246,1600,1601],{"class":256},"node:fs",[246,1603,308],{"class":301},[246,1605,311],{"class":287},[246,1607,1608,1610,1612,1614,1616,1619,1621,1623,1625,1627,1629],{"class":248,"line":314},[246,1609,284],{"class":283},[246,1611,288],{"class":287},[246,1613,388],{"class":291},[246,1615,519],{"class":287},[246,1617,1618],{"class":291}," loadScript",[246,1620,295],{"class":287},[246,1622,298],{"class":283},[246,1624,302],{"class":301},[246,1626,204],{"class":256},[246,1628,308],{"class":301},[246,1630,311],{"class":287},[246,1632,1633],{"class":248,"line":321},[246,1634,318],{"emptyLinePlaceholder":317},[246,1636,1637,1639,1641,1643,1645,1647,1649],{"class":248,"line":950},[246,1638,1292],{"class":373},[246,1640,378],{"class":377},[246,1642,382],{"class":381},[246,1644,385],{"class":283},[246,1646,388],{"class":330},[246,1648,333],{"class":291},[246,1650,311],{"class":287},[246,1652,1653,1656,1658,1660,1663,1665,1667,1669,1671,1673,1675,1678,1680,1682,1685,1687,1689,1692],{"class":248,"line":955},[246,1654,1655],{"class":330},"writeFileSync",[246,1657,418],{"class":291},[246,1659,308],{"class":301},[246,1661,1662],{"class":256},"load.js",[246,1664,308],{"class":301},[246,1666,519],{"class":287},[246,1668,1618],{"class":330},[246,1670,404],{"class":291},[246,1672,519],{"class":287},[246,1674,288],{"class":287},[246,1676,1677],{"class":631}," door",[246,1679,620],{"class":287},[246,1681,302],{"class":301},[246,1683,1684],{"class":256},"http:\u002F\u002F127.0.0.1:3000\u002F_fougere\u002Fcall",[246,1686,308],{"class":301},[246,1688,295],{"class":287},[246,1690,1691],{"class":291},"))",[246,1693,311],{"class":287},[246,1695,1697,1699,1701,1703,1706,1708],{"class":248,"line":1696},7,[246,1698,548],{"class":283},[246,1700,378],{"class":291},[246,1702,407],{"class":287},[246,1704,1705],{"class":330},"dispose",[246,1707,333],{"class":291},[246,1709,311],{"class":287},[237,1711,1713],{"className":239,"code":1712,"language":241,"meta":242,"style":242},"k6 run load.js\n",[202,1714,1715],{"__ignoreMap":242},[246,1716,1717,1720,1722],{"class":248,"line":249},[246,1718,1719],{"class":252},"k6",[246,1721,909],{"class":256},[246,1723,1724],{"class":256}," load.js\n",[199,1726,1727,1728,1731,1732,1735],{},"Le scénario généré couvre ",[209,1729,1730],{},"toutes"," les opérations que l'app sert, avec un corps valide\npour chacune, et son enveloppe JSON-RPC vient de ",[202,1733,1734],{},"frameCall"," plutôt que d'une copie qui\ncontinuera de se prétendre du JSON-RPC le jour où le format bouge.",[199,1737,1738,1739,1742],{},"Ce qui reste à toi : les poids, les paliers et les seuils. Le scan sait quelles opérations\nexistent ; il ne sait rien du trafic qu'elles reçoivent. Une opération mise à ",[202,1740,1741],{},"weight: 0","\ndevient une décision visible dans le fichier au lieu d'un oubli que personne ne voit.",[199,1744,1745],{},"Régénérer plutôt que rapiécer.",[1747,1748,1749],"style",{},"html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .stzsN, html code.shiki .stzsN{--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sQzsp, html code.shiki .sQzsp{--shiki-light:#E53935;--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .s9AJx, html code.shiki .s9AJx{--shiki-light:#9C3EDA;--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":242,"searchDepth":280,"depth":280,"links":1751},[1752,1753,1756,1757,1758,1759,1760,1761,1762,1763],{"id":339,"depth":280,"text":340},{"id":497,"depth":280,"text":498,"children":1754},[1755],{"id":653,"depth":314,"text":654},{"id":682,"depth":280,"text":683},{"id":822,"depth":280,"text":823},{"id":1012,"depth":280,"text":1013},{"id":1094,"depth":280,"text":108},{"id":1274,"depth":280,"text":1275},{"id":1371,"depth":280,"text":1372},{"id":1512,"depth":280,"text":1513},{"id":1572,"depth":280,"text":1573},"Des cas fabriqués depuis la déclaration, des bouchons fabriqués depuis le port, et un gradient que le fichier de test n'énonce jamais — sa position dit ce qu'il teste.","md",{},{"title":186,"description":1764},"MHTBxdDtLwWUCNHL6SrsJ7SI6_DzQNYGLB_jSJBlYco",1788598419966]