[{"data":1,"prerenderedAt":283},["ShallowReactive",2],{"blog-slugs":3,"project-vuestrata-en":8},{"en":4,"ar":7},[5,6],"dynamic-generic-slots","from-renderless-component-to-composable-function",[5,6],{"id":9,"title":10,"body":11,"description":275,"extension":276,"meta":277,"navigation":278,"path":279,"seo":280,"stem":281,"__hash__":282},"projects/projects/en/vuestrata.md","Vuestrata",{"type":12,"value":13,"toc":267},"minimark",[14,19,23,31,35,38,67,71,78,151,155,158,162,165,198,201,205,208,263],[15,16,18],"h2",{"id":17},"the-problem","The problem",[20,21,22],"p",{},"Starting a Vue project is easy. Keeping routing, state ownership, module boundaries, API access, auth, themes, permissions, mocks, tests and deployment consistent as the app grows is the expensive part.",[20,24,25,26,30],{},"The conventions that matter most also fail silently. A module never added to ",[27,28,29],"code",{},"setup.ts"," does not throw; the feature just never loads. A theme wired into three of its four files does not throw; it renders the wrong surfaces. Demo mocks left in a production build do not throw; they ship.",[15,32,34],{"id":33},"conventions-enforced-by-tooling","Conventions enforced by tooling",[20,36,37],{},"Vuestrata documents its conventions and also checks the important ones automatically:",[39,40,41,49,55,61],"ul",{},[42,43,44,48],"li",{},[45,46,47],"strong",{},"Six custom lint rules",": module-scope state, raw palette utilities, inline template handlers, i18n key parity across English, French and Arabic, icon-name parity, and the four files a theme must appear in.",[42,50,51,54],{},[45,52,53],{},"Four architecture tests",": module registry drift, the module contract, docs registration and demo-only access.",[42,56,57,60],{},[45,58,59],{},"Bundle verification",": the build fails if a mock service worker chunk reaches a production artifact, or if a size budget is exceeded.",[42,62,63,66],{},[45,64,65],{},"Drift checks",": toolchain pins and security headers are generated from one definition each, and CI fails when a copy disagrees.",[15,68,70],{"id":69},"generators","Generators",[20,72,73,74,77],{},"Each common extension has a generator that writes the files and the registry entries that are easy to forget. ",[27,75,76],{},"--dry-run"," prints the plan without writing anything.",[79,80,85],"pre",{"className":81,"code":82,"language":83,"meta":84,"style":84},"language-bash shiki shiki-themes site-light site-light site-dark","vpr gen:module payments        # CRUD domain: schema, queries, mocks, pages, i18n, registered\nvpr gen:page payments refunds  # a page, wired into the module's routes\nvpr gen:theme midnight         # theme, imported and registered in all four places\nvpr gen:component Tag          # Ui* wrapper (+ --field for a forms composable)\n","bash","",[27,86,87,107,123,137],{"__ignoreMap":84},[88,89,92,96,100,103],"span",{"class":90,"line":91},"line",1,[88,93,95],{"class":94},"sUqqj","vpr",[88,97,99],{"class":98},"sgVH1"," gen:module",[88,101,102],{"class":98}," payments",[88,104,106],{"class":105},"s5c__","        # CRUD domain: schema, queries, mocks, pages, i18n, registered\n",[88,108,110,112,115,117,120],{"class":90,"line":109},2,[88,111,95],{"class":94},[88,113,114],{"class":98}," gen:page",[88,116,102],{"class":98},[88,118,119],{"class":98}," refunds",[88,121,122],{"class":105},"  # a page, wired into the module's routes\n",[88,124,126,128,131,134],{"class":90,"line":125},3,[88,127,95],{"class":94},[88,129,130],{"class":98}," gen:theme",[88,132,133],{"class":98}," midnight",[88,135,136],{"class":105},"         # theme, imported and registered in all four places\n",[88,138,140,142,145,148],{"class":90,"line":139},4,[88,141,95],{"class":94},[88,143,144],{"class":98}," gen:component",[88,146,147],{"class":98}," Tag",[88,149,150],{"class":105},"          # Ui* wrapper (+ --field for a forms composable)\n",[15,152,154],{"id":153},"twelve-themes-one-set-of-components","Twelve themes, one set of components",[20,156,157],{},"Each built-in theme redefines colour ramps, radii and elevation in both colour modes over the same components, and eight of them change the typeface too. The icon set, the spacing scale and motion stay constant, which is what makes the themes interchangeable.",[15,159,161],{"id":160},"updating-a-copy-of-the-template","Updating a copy of the template",[20,163,164],{},"Vuestrata is a template you copy, not a dependency you install. Its CLI brings upstream fixes into a project that has already diverged:",[79,166,168],{"className":81,"code":167,"language":83,"meta":84,"style":84},"vp exec vuestrata diff      # what would change; writes nothing\nvp exec vuestrata upgrade\n",[27,169,170,187],{"__ignoreMap":84},[88,171,172,175,178,181,184],{"class":90,"line":91},[88,173,174],{"class":94},"vp",[88,176,177],{"class":98}," exec",[88,179,180],{"class":98}," vuestrata",[88,182,183],{"class":98}," diff",[88,185,186],{"class":105},"      # what would change; writes nothing\n",[88,188,189,191,193,195],{"class":90,"line":109},[88,190,174],{"class":94},[88,192,177],{"class":98},[88,194,180],{"class":98},[88,196,197],{"class":98}," upgrade\n",[20,199,200],{},"Files you never touched are replaced. Files you edited are left alone, with the new version placed beside them to compare.",[15,202,204],{"id":203},"authentication-adapters","Authentication adapters",[20,206,207],{},"Authentication goes through pluggable adapters: a mock one for demos, JWT, and OAuth with PKCE. Each adapter declares its transport and its capabilities, so the interface hides controls the configured adapter cannot support.",[79,209,213],{"className":210,"code":211,"language":212,"meta":84,"style":84},"language-ts shiki shiki-themes site-light site-light site-dark","const { login, register, logout, isLoading, error } = useAuth()\n","ts",[27,214,215],{"__ignoreMap":84},[88,216,217,221,225,229,232,235,237,240,242,245,247,250,253,257,260],{"class":90,"line":91},[88,218,220],{"class":219},"sZ9Zp","const",[88,222,224],{"class":223},"sXnUm"," {",[88,226,228],{"class":227},"sJ-0L"," login",[88,230,231],{"class":223},",",[88,233,234],{"class":227}," register",[88,236,231],{"class":223},[88,238,239],{"class":227}," logout",[88,241,231],{"class":223},[88,243,244],{"class":227}," isLoading",[88,246,231],{"class":223},[88,248,249],{"class":227}," error",[88,251,252],{"class":223}," }",[88,254,256],{"class":255},"sZ7qJ"," =",[88,258,259],{"class":94}," useAuth",[88,261,262],{"class":223},"()\n",[264,265,266],"style",{},"html pre.shiki code .sUqqj, html code.shiki .sUqqj{--shiki-light:#4A3170;--shiki-default:#4A3170;--shiki-dark:#D9BDF0}html pre.shiki code .sgVH1, html code.shiki .sgVH1{--shiki-light:#3A6830;--shiki-default:#3A6830;--shiki-dark:#A5CF93}html pre.shiki code .s5c__, html code.shiki .s5c__{--shiki-light:#736D67;--shiki-light-font-style:italic;--shiki-default:#736D67;--shiki-default-font-style:italic;--shiki-dark:#979189;--shiki-dark-font-style:italic}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 .sZ9Zp, html code.shiki .sZ9Zp{--shiki-light:#994920;--shiki-default:#994920;--shiki-dark:#ECA457}html pre.shiki code .sXnUm, html code.shiki .sXnUm{--shiki-light:#6D6863;--shiki-default:#6D6863;--shiki-dark:#96918C}html pre.shiki code .sJ-0L, html code.shiki .sJ-0L{--shiki-light:#2D54A0;--shiki-default:#2D54A0;--shiki-dark:#93BDF5}html pre.shiki code .sZ7qJ, html code.shiki .sZ7qJ{--shiki-light:#6D6863;--shiki-default:#6D6863;--shiki-dark:#A39E98}",{"title":84,"searchDepth":109,"depth":109,"links":268},[269,270,271,272,273,274],{"id":17,"depth":109,"text":18},{"id":33,"depth":109,"text":34},{"id":69,"depth":109,"text":70},{"id":153,"depth":109,"text":154},{"id":160,"depth":109,"text":161},{"id":203,"depth":109,"text":204},"A Vue 3 application starter that checks its own conventions.","md",{},true,"/projects/en/vuestrata",{"title":10,"description":275},"projects/en/vuestrata","HgwpLn4f0xSxt29FC9H_ra2kxwa1SImoEbxccunvu_k",1791794146535]