[{"data":1,"prerenderedAt":606},["ShallowReactive",2],{"docs-nav:en":3,"docs:\u002Fdocs\u002Fconcepts\u002Fshortest-path":186},[4],{"title":5,"path":6,"stem":7,"children":8},"Docs","\u002Fdocs","docs",[9,12,16,20,24,28,50,75,120,145],{"title":10,"path":6,"stem":11},"What is Fougere","docs\u002Findex",{"title":13,"path":14,"stem":15},"Getting started","\u002Fdocs\u002Fgetting-started","docs\u002F01.getting-started",{"title":17,"path":18,"stem":19},"An app you already have","\u002Fdocs\u002Fexisting-app","docs\u002F02.existing-app",{"title":21,"path":22,"stem":23},"Bring your schema","\u002Fdocs\u002Fbring-your-schema","docs\u002F03.bring-your-schema",{"title":25,"path":26,"stem":27},"The CLI","\u002Fdocs\u002Fcli","docs\u002F04.cli",{"title":29,"path":30,"stem":31,"children":32,"page":49},"Concepts","\u002Fdocs\u002Fconcepts","docs\u002F2.concepts",[33,37,41,45],{"title":34,"path":35,"stem":36},"Philosophy","\u002Fdocs\u002Fconcepts\u002Fphilosophy","docs\u002F2.concepts\u002F1.philosophy",{"title":38,"path":39,"stem":40},"The Frond","\u002Fdocs\u002Fconcepts\u002Ffrond","docs\u002F2.concepts\u002F2.frond",{"title":42,"path":43,"stem":44},"The shortest path","\u002Fdocs\u002Fconcepts\u002Fshortest-path","docs\u002F2.concepts\u002F3.shortest-path",{"title":46,"path":47,"stem":48},"The base","\u002Fdocs\u002Fconcepts\u002Fthe-base","docs\u002F2.concepts\u002F4.the-base",false,{"title":51,"path":52,"stem":53,"children":54,"page":49},"Schema","\u002Fdocs\u002Fschema","docs\u002F3.schema",[55,59,63,67,71],{"title":56,"path":57,"stem":58},"Entities","\u002Fdocs\u002Fschema\u002Fentities","docs\u002F3.schema\u002F1.entities",{"title":60,"path":61,"stem":62},"Views","\u002Fdocs\u002Fschema\u002Fviews","docs\u002F3.schema\u002F2.views",{"title":64,"path":65,"stem":66},"The identity card","\u002Fdocs\u002Fschema\u002Fcard","docs\u002F3.schema\u002F3.card",{"title":68,"path":69,"stem":70},"Standard Schema","\u002Fdocs\u002Fschema\u002Fstandard-schema","docs\u002F3.schema\u002F4.standard-schema",{"title":72,"path":73,"stem":74},"How a schema moves","\u002Fdocs\u002Fschema\u002Fevolution","docs\u002F3.schema\u002F5.evolution",{"title":76,"path":77,"stem":78,"children":79,"page":49},"Business","\u002Fdocs\u002Fbusiness","docs\u002F4.business",[80,84,88,92,96,100,104,108,112,116],{"title":81,"path":82,"stem":83},"Handlers","\u002Fdocs\u002Fbusiness\u002Fhandlers","docs\u002F4.business\u002F1.handlers",{"title":85,"path":86,"stem":87},"Ports","\u002Fdocs\u002Fbusiness\u002Fports","docs\u002F4.business\u002F10.ports",{"title":89,"path":90,"stem":91},"Presenters","\u002Fdocs\u002Fbusiness\u002Fpresenters","docs\u002F4.business\u002F2.presenters",{"title":93,"path":94,"stem":95},"Collectors","\u002Fdocs\u002Fbusiness\u002Fcollectors","docs\u002F4.business\u002F3.collectors",{"title":97,"path":98,"stem":99},"Errors","\u002Fdocs\u002Fbusiness\u002Ferrors","docs\u002F4.business\u002F4.errors",{"title":101,"path":102,"stem":103},"Seeds","\u002Fdocs\u002Fbusiness\u002Fseeds","docs\u002F4.business\u002F5.seeds",{"title":105,"path":106,"stem":107},"Facts","\u002Fdocs\u002Fbusiness\u002Ffacts","docs\u002F4.business\u002F6.facts",{"title":109,"path":110,"stem":111},"Storage","\u002Fdocs\u002Fbusiness\u002Fstorage","docs\u002F4.business\u002F7.storage",{"title":113,"path":114,"stem":115},"Writes that stand or fall as one","\u002Fdocs\u002Fbusiness\u002Ftogether","docs\u002F4.business\u002F8.together",{"title":117,"path":118,"stem":119},"Repositories","\u002Fdocs\u002Fbusiness\u002Frepositories","docs\u002F4.business\u002F9.repositories",{"title":121,"path":122,"stem":123,"children":124,"page":49},"Client","\u002Fdocs\u002Fclient","docs\u002F5.client",[125,129,133,137,141],{"title":126,"path":127,"stem":128},"Queries & commands","\u002Fdocs\u002Fclient\u002Fqueries-commands","docs\u002F5.client\u002F1.queries-commands",{"title":130,"path":131,"stem":132},"Forms","\u002Fdocs\u002Fclient\u002Fforms","docs\u002F5.client\u002F2.forms",{"title":134,"path":135,"stem":136},"Session","\u002Fdocs\u002Fclient\u002Fsession","docs\u002F5.client\u002F3.session",{"title":138,"path":139,"stem":140},"invoke","\u002Fdocs\u002Fclient\u002Finvoke","docs\u002F5.client\u002F4.invoke",{"title":142,"path":143,"stem":144},"Back-office","\u002Fdocs\u002Fclient\u002Fadmin","docs\u002F5.client\u002F5.admin",{"title":146,"path":147,"stem":148,"children":149,"page":49},"Infrastructure","\u002Fdocs\u002Finfra","docs\u002F6.infra",[150,154,158,162,166,170,174,178,182],{"title":151,"path":152,"stem":153},"The gradient","\u002Fdocs\u002Finfra\u002Fgradient","docs\u002F6.infra\u002F1.gradient",{"title":155,"path":156,"stem":157},"Surfaces — REST & GraphQL","\u002Fdocs\u002Finfra\u002Fsurfaces","docs\u002F6.infra\u002F2.surfaces",{"title":159,"path":160,"stem":161},"Deployment","\u002Fdocs\u002Finfra\u002Fdeployment","docs\u002F6.infra\u002F3.deployment",{"title":163,"path":164,"stem":165},"Hosts","\u002Fdocs\u002Finfra\u002Fhosts","docs\u002F6.infra\u002F4.hosts",{"title":167,"path":168,"stem":169},"Sources","\u002Fdocs\u002Finfra\u002Fsources","docs\u002F6.infra\u002F5.sources",{"title":171,"path":172,"stem":173},"Lifecycle","\u002Fdocs\u002Finfra\u002Flifecycle","docs\u002F6.infra\u002F6.lifecycle",{"title":175,"path":176,"stem":177},"Identity across Fronds","\u002Fdocs\u002Finfra\u002Fidentity","docs\u002F6.infra\u002F7.identity",{"title":179,"path":180,"stem":181},"Observability","\u002Fdocs\u002Finfra\u002Fobservability","docs\u002F6.infra\u002F8.observability",{"title":183,"path":184,"stem":185},"Testing","\u002Fdocs\u002Finfra\u002Ftesting","docs\u002F6.infra\u002F9.testing",{"id":187,"title":42,"body":188,"description":601,"extension":602,"meta":603,"navigation":514,"path":43,"seo":604,"stem":44,"__hash__":605},"docs_en\u002Fdocs\u002F2.concepts\u002F3.shortest-path.md",{"type":189,"value":190,"toc":594},"minimark",[191,195,199,207,212,215,295,298,302,305,308,312,315,337,345,348,371,374,378,385,571,578,582,585,590],[192,193,42],"h1",{"id":194},"the-shortest-path",[196,197,198],"p",{},"When a task is urgent, the simplest available path will usually be used. Fougere\ntherefore aims to make the conventional path shorter than bypassing it.",[196,200,201,202,206],{},"This page covers a design principle and its related tooling. ",[203,204,205],"code",{},"fougere graph"," is available\ntoday. CI graph checks and the refusal catalog described below are still under study.",[208,209,211],"h2",{"id":210},"one-api-for-each-common-need","One API for each common need",[196,213,214],{},"Common operations have a recommended API:",[216,217,218,231],"table",{},[219,220,221],"thead",{},[222,223,224,228],"tr",{},[225,226,227],"th",{},"Need",[225,229,230],{},"API",[232,233,234,248,260,272,283],"tbody",{},[222,235,236,240],{},[237,238,239],"td",{},"read from a page",[237,241,242],{},[243,244,245],"a",{"href":127},[203,246,247],{},"useQuery",[222,249,250,253],{},[237,251,252],{},"change state from a page",[237,254,255],{},[243,256,257],{"href":127},[203,258,259],{},"useCommand",[222,261,262,265],{},[237,263,264],{},"manage a form",[237,266,267],{},[243,268,269],{"href":131},[203,270,271],{},"useFormFor",[222,273,274,277],{},[237,275,276],{},"call from the server",[237,278,279],{},[243,280,281],{"href":139},[203,282,138],{},[222,284,285,288],{},[237,286,287],{},"validate input",[237,289,290],{},[243,291,292],{"href":57},[203,293,294],{},"Entity.validate",[196,296,297],{},"These APIs share the same entity-and-operation designation. They also pass the context\nexpected by collectors and normalize errors. A manually assembled call must handle those\nresponsibilities explicitly.",[208,299,301],{"id":300},"how-a-codebase-drifts","How a codebase drifts",[196,303,304],{},"Architecture drift often comes from reasonable local fixes: a rule added to a form, then\nto a route, then to an adapter. Each change solves its immediate problem, but the rule no\nlonger has one owner.",[196,306,307],{},"Fougere conventions place field rules in entities and business rules in operations. They\ndo not guarantee that the model is correct, but they give elements that must change\ntogether a shared location.",[208,309,311],{"id":310},"read-the-graph","Read the graph",[196,313,314],{},"The scanner knows about Fronds, their entities, and their references. The following\ncommand prints those relationships:",[316,317,322],"pre",{"className":318,"code":319,"language":320,"meta":321,"style":321},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","fougere graph\n","bash","",[203,323,324],{"__ignoreMap":321},[325,326,329,333],"span",{"class":327,"line":328},"line",1,[325,330,332],{"class":331},"sbgvK","fougere",[325,334,336],{"class":335},"s_sjI"," graph\n",[316,338,343],{"className":339,"code":341,"language":342,"meta":321},[340],"language-text","Post -> Author, Category (2 incoming)\nAuthor\n","text",[203,344,341],{"__ignoreMap":321},[196,346,347],{},"This graph can be read along three dimensions:",[349,350,351,359,365],"ul",{},[352,353,354,358],"li",{},[355,356,357],"strong",{},"coupling",": the number of relationships attached to an entity;",[352,360,361,364],{},[355,362,363],{},"duplication",": locations where the same rule is declared;",[352,366,367,370],{},[355,368,369],{},"ownership",": the domain responsible for a rule or piece of data.",[196,372,373],{},"The current command exposes relationships and suggests clusters beyond six entities. It\ndoes not yet measure duplication or ownership automatically. Showing graph changes in\npull requests remains a possible CI feature.",[208,375,377],{"id":376},"errors-that-suggest-a-solution","Errors that suggest a solution",[196,379,380,381,384],{},"A useful safeguard should identify the detected usage, its risk, and the recommended API.\nThe following shape illustrates the planned catalog; ",[203,382,383],{},"defineRefusal"," is not a shipped API:",[316,386,390],{"className":387,"code":388,"language":389,"meta":321,"style":321},"language-ts shiki shiki-themes material-theme-lighter github-light github-dark","defineRefusal({\n  id: 'F-042',\n  detect: (site) => isHandRolledCallToFrond(site),\n  message: (site) => dedent`\n    Hand-assembled call to the ${site.frond} Frond.\n    This call does not automatically pass identity and locale.\n\n      -> invoke('${site.suggestion}')\n  `,\n  origin: 'incident: context omitted from several routes',\n});\n","ts",[203,391,392,405,428,458,480,503,509,516,536,544,561],{"__ignoreMap":321},[325,393,394,397,401],{"class":327,"line":328},[325,395,383],{"class":396},"sGLFI",[325,398,400],{"class":399},"su5hD","(",[325,402,404],{"class":403},"sP7_E","{\n",[325,406,408,412,415,419,422,425],{"class":327,"line":407},2,[325,409,411],{"class":410},"skxfh","  id",[325,413,414],{"class":403},":",[325,416,418],{"class":417},"sjJ54"," '",[325,420,421],{"class":335},"F-042",[325,423,424],{"class":417},"'",[325,426,427],{"class":403},",\n",[325,429,431,434,436,439,443,446,450,453,456],{"class":327,"line":430},3,[325,432,433],{"class":396},"  detect",[325,435,414],{"class":403},[325,437,438],{"class":403}," (",[325,440,442],{"class":441},"s99_P","site",[325,444,445],{"class":403},")",[325,447,449],{"class":448},"sbsja"," =>",[325,451,452],{"class":396}," isHandRolledCallToFrond",[325,454,455],{"class":399},"(site)",[325,457,427],{"class":403},[325,459,461,464,466,468,470,472,474,477],{"class":327,"line":460},4,[325,462,463],{"class":396},"  message",[325,465,414],{"class":403},[325,467,438],{"class":403},[325,469,442],{"class":441},[325,471,445],{"class":403},[325,473,449],{"class":448},[325,475,476],{"class":396}," dedent",[325,478,479],{"class":417},"`\n",[325,481,483,486,489,491,494,497,500],{"class":327,"line":482},5,[325,484,485],{"class":335},"    Hand-assembled call to the ",[325,487,488],{"class":417},"${",[325,490,442],{"class":399},[325,492,493],{"class":417},".",[325,495,496],{"class":399},"frond",[325,498,499],{"class":417},"}",[325,501,502],{"class":335}," Frond.\n",[325,504,506],{"class":327,"line":505},6,[325,507,508],{"class":335},"    This call does not automatically pass identity and locale.\n",[325,510,512],{"class":327,"line":511},7,[325,513,515],{"emptyLinePlaceholder":514},true,"\n",[325,517,519,522,524,526,528,531,533],{"class":327,"line":518},8,[325,520,521],{"class":335},"      -> invoke('",[325,523,488],{"class":417},[325,525,442],{"class":399},[325,527,493],{"class":417},[325,529,530],{"class":399},"suggestion",[325,532,499],{"class":417},[325,534,535],{"class":335},"')\n",[325,537,539,542],{"class":327,"line":538},9,[325,540,541],{"class":417},"  `",[325,543,427],{"class":403},[325,545,547,550,552,554,557,559],{"class":327,"line":546},10,[325,548,549],{"class":410},"  origin",[325,551,414],{"class":403},[325,553,418],{"class":417},[325,555,556],{"class":335},"incident: context omitted from several routes",[325,558,424],{"class":417},[325,560,427],{"class":403},[325,562,564,566,568],{"class":327,"line":563},11,[325,565,499],{"class":403},[325,567,445],{"class":399},[325,569,570],{"class":403},";\n",[196,572,573,574,577],{},"These build-time errors would remain separate from\n",[243,575,576],{"href":98},"business errors"," returned at runtime.",[208,579,581],{"id":580},"limit","Limit",[196,583,584],{},"Fougere can check the structure of a declaration, not whether it is correct for the\nbusiness. An incorrect rule remains incorrect when declared only once. Choosing concepts\nand their boundaries remains a design decision.",[196,586,587,588,493],{},"Next: ",[243,589,46],{"href":47},[591,592,593],"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 .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 .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--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 .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 .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":321,"searchDepth":407,"depth":407,"links":595},[596,597,598,599,600],{"id":210,"depth":407,"text":211},{"id":300,"depth":407,"text":301},{"id":310,"depth":407,"text":311},{"id":376,"depth":407,"text":377},{"id":580,"depth":407,"text":581},"Fougere conventions, the available graph, and safeguards still under study.","md",{},{"title":42,"description":601},"EjNnoEtgarwQoPg9FvFdwIcoVR2ASLigc7KF4Z4P-wM",1788560230919]