[{"data":1,"prerenderedAt":3000},["ShallowReactive",2],{"navigation_docs":3,"-examples-tasks-add-an-admin-page":587,"-examples-tasks-add-an-admin-page-surround":2995},[4,50,155,441,504],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":49},"Get Started","i-lucide-rocket","\u002Fget-started","1.get-started",[10,15,20,25,30,35,40,45],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fget-started\u002Fintroduction","1.get-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Installation","\u002Fget-started\u002Finstallation","1.get-started\u002F2.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":24},"Project structure","\u002Fget-started\u002Fproject-structure","1.get-started\u002F3.project-structure","i-lucide-folder-tree",{"title":26,"path":27,"stem":28,"icon":29},"Configuration","\u002Fget-started\u002Fconfiguration","1.get-started\u002F4.configuration","i-lucide-settings",{"title":31,"path":32,"stem":33,"icon":34},"Authentication","\u002Fget-started\u002Fauthentication","1.get-started\u002F5.authentication","i-lucide-lock",{"title":36,"path":37,"stem":38,"icon":39},"Adding modules","\u002Fget-started\u002Fadding-modules","1.get-started\u002F6.adding-modules","i-lucide-puzzle",{"title":41,"path":42,"stem":43,"icon":44},"Database","\u002Fget-started\u002Fdatabase","1.get-started\u002F7.database","i-lucide-database",{"title":46,"path":47,"stem":48,"icon":6},"Deployment","\u002Fget-started\u002Fdeployment","1.get-started\u002F8.deployment",false,{"title":51,"icon":52,"path":53,"stem":54,"children":55,"page":49},"Guide","i-lucide-book-open","\u002Fguide","2.guide",[56,90,115,125],{"title":57,"icon":49,"path":58,"stem":59,"children":60,"page":49},"Concepts","\u002Fguide\u002Fconcepts","2.guide\u002F1.concepts",[61,66,71,76,80,85],{"title":62,"path":63,"stem":64,"icon":65},"Architecture","\u002Fguide\u002Fconcepts\u002Farchitecture","2.guide\u002F1.concepts\u002F1.architecture","i-lucide-blocks",{"title":67,"path":68,"stem":69,"icon":70},"Access control","\u002Fguide\u002Fconcepts\u002Fauth-and-scopes","2.guide\u002F1.concepts\u002F2.auth-and-scopes","i-lucide-shield-check",{"title":72,"path":73,"stem":74,"icon":75},"Extension points","\u002Fguide\u002Fconcepts\u002Fextension-points","2.guide\u002F1.concepts\u002F3.extension-points","i-lucide-plug",{"title":77,"path":78,"stem":79,"icon":44},"Database extensions","\u002Fguide\u002Fconcepts\u002Fdatabase-extensions","2.guide\u002F1.concepts\u002F4.database-extensions",{"title":81,"path":82,"stem":83,"icon":84},"Identity","\u002Fguide\u002Fconcepts\u002Fidentity","2.guide\u002F1.concepts\u002F5.identity","i-lucide-fingerprint",{"title":86,"path":87,"stem":88,"icon":89},"Glossary","\u002Fguide\u002Fconcepts\u002Fglossary","2.guide\u002F1.concepts\u002F6.glossary","i-lucide-book-a",{"title":91,"icon":49,"path":92,"stem":93,"children":94,"page":49},"UX patterns","\u002Fguide\u002Fux-patterns","2.guide\u002F2.ux-patterns",[95,100,105,110],{"title":96,"path":97,"stem":98,"icon":99},"Layouts and navigation","\u002Fguide\u002Fux-patterns\u002Flayouts-and-navigation","2.guide\u002F2.ux-patterns\u002F1.layouts-and-navigation","i-lucide-layout-template",{"title":101,"path":102,"stem":103,"icon":104},"Pages","\u002Fguide\u002Fux-patterns\u002Fpages","2.guide\u002F2.ux-patterns\u002F2.pages","i-lucide-table",{"title":106,"path":107,"stem":108,"icon":109},"Forms and actions","\u002Fguide\u002Fux-patterns\u002Fforms-and-actions","2.guide\u002F2.ux-patterns\u002F3.forms-and-actions","i-lucide-square-pen",{"title":111,"path":112,"stem":113,"icon":114},"Text and formatting","\u002Fguide\u002Fux-patterns\u002Ftext-and-formatting","2.guide\u002F2.ux-patterns\u002F4.text-and-formatting","i-lucide-type",{"title":116,"icon":49,"path":117,"stem":118,"children":119,"page":49},"Best practices","\u002Fguide\u002Fbest-practices","2.guide\u002F3.best-practices",[120],{"title":121,"path":122,"stem":123,"icon":124},"HTTP routes","\u002Fguide\u002Fbest-practices\u002Fhttp-routes","2.guide\u002F3.best-practices\u002F1.http-routes","i-lucide-route",{"title":126,"icon":49,"path":127,"stem":128,"children":129,"page":49},"Going further","\u002Fguide\u002Fgoing-further","2.guide\u002F4.going-further",[130,135,140,145,150],{"title":131,"path":132,"stem":133,"icon":134},"Use your own OAuth server","\u002Fguide\u002Fgoing-further\u002Fown-oauth-server","2.guide\u002F4.going-further\u002F1.own-oauth-server","i-lucide-key-round",{"title":136,"path":137,"stem":138,"icon":139},"Create an OAuth application","\u002Fguide\u002Fgoing-further\u002Foauth-application","2.guide\u002F4.going-further\u002F2.oauth-application","i-lucide-app-window",{"title":141,"path":142,"stem":143,"icon":144},"Signing keys","\u002Fguide\u002Fgoing-further\u002Fsigning-keys","2.guide\u002F4.going-further\u002F3.signing-keys","i-lucide-key-square",{"title":146,"path":147,"stem":148,"icon":149},"Stripe setup","\u002Fguide\u002Fgoing-further\u002Fstripe-setup","2.guide\u002F4.going-further\u002F4.stripe-setup","i-lucide-credit-card",{"title":151,"path":152,"stem":153,"icon":154},"Migrate to the permission model","\u002Fguide\u002Fgoing-further\u002Fmigrate-access-control","2.guide\u002F4.going-further\u002F5.migrate-access-control","i-lucide-arrow-right-left",{"title":156,"icon":157,"path":158,"stem":159,"children":160},"Modules","i-lucide-boxes","\u002Fmodules","3.modules",[161,163,244,273,320,375],{"title":156,"path":158,"stem":162},"3.modules\u002Findex",{"title":81,"icon":49,"path":164,"stem":165,"children":166,"page":49},"\u002Fmodules\u002Fidentity","3.modules\u002F1.identity",[167,190,194,198,220,224,240],{"title":168,"path":169,"stem":170,"children":171},"Auth","\u002Fmodules\u002Fidentity\u002Fauth","3.modules\u002F1.identity\u002F1.auth\u002Findex",[172,174,178,182,186],{"title":173,"path":169,"stem":170},"Overview",{"title":175,"path":176,"stem":177},"Server API","\u002Fmodules\u002Fidentity\u002Fauth\u002Fserver","3.modules\u002F1.identity\u002F1.auth\u002F2.server",{"title":179,"path":180,"stem":181},"Reference","\u002Fmodules\u002Fidentity\u002Fauth\u002Freference","3.modules\u002F1.identity\u002F1.auth\u002F3.reference",{"title":183,"path":184,"stem":185},"Client mode","\u002Fmodules\u002Fidentity\u002Fauth\u002Fclient-mode","3.modules\u002F1.identity\u002F1.auth\u002F4.client-mode",{"title":187,"path":188,"stem":189},"OAuth providers","\u002Fmodules\u002Fidentity\u002Fauth\u002Foauth-providers","3.modules\u002F1.identity\u002F1.auth\u002F5.oauth-providers",{"title":191,"path":192,"stem":193},"Users","\u002Fmodules\u002Fidentity\u002Fusers","3.modules\u002F1.identity\u002F2.users",{"title":195,"path":196,"stem":197},"Account","\u002Fmodules\u002Fidentity\u002Faccount","3.modules\u002F1.identity\u002F3.account",{"title":199,"path":200,"stem":201,"children":202},"Authorization","\u002Fmodules\u002Fidentity\u002Fauthorization","3.modules\u002F1.identity\u002F4.authorization\u002Findex",[203,204,208,212,216],{"title":173,"path":200,"stem":201},{"title":205,"path":206,"stem":207},"Roles and requirements","\u002Fmodules\u002Fidentity\u002Fauthorization\u002Froles-and-requirements","3.modules\u002F1.identity\u002F4.authorization\u002F2.roles-and-requirements",{"title":209,"path":210,"stem":211},"Policies","\u002Fmodules\u002Fidentity\u002Fauthorization\u002Fpolicies","3.modules\u002F1.identity\u002F4.authorization\u002F3.policies",{"title":213,"path":214,"stem":215},"Client","\u002Fmodules\u002Fidentity\u002Fauthorization\u002Fclient","3.modules\u002F1.identity\u002F4.authorization\u002F4.client",{"title":217,"path":218,"stem":219},"Admin","\u002Fmodules\u002Fidentity\u002Fauthorization\u002Fadmin","3.modules\u002F1.identity\u002F4.authorization\u002F5.admin",{"title":221,"path":222,"stem":223},"Preferences","\u002Fmodules\u002Fidentity\u002Fpreferences","3.modules\u002F1.identity\u002F5.preferences",{"title":225,"path":226,"stem":227,"children":228},"OAuth Server","\u002Fmodules\u002Fidentity\u002Foauth-server","3.modules\u002F1.identity\u002F6.oauth-server\u002Findex",[229,230,233,237],{"title":173,"path":226,"stem":227},{"title":175,"path":231,"stem":232},"\u002Fmodules\u002Fidentity\u002Foauth-server\u002Fserver","3.modules\u002F1.identity\u002F6.oauth-server\u002F2.server",{"title":234,"path":235,"stem":236},"Hooks","\u002Fmodules\u002Fidentity\u002Foauth-server\u002Fhooks","3.modules\u002F1.identity\u002F6.oauth-server\u002F3.hooks",{"title":179,"path":238,"stem":239},"\u002Fmodules\u002Fidentity\u002Foauth-server\u002Froutes-and-clients","3.modules\u002F1.identity\u002F6.oauth-server\u002F4.routes-and-clients",{"title":241,"path":242,"stem":243},"OAuth Resource","\u002Fmodules\u002Fidentity\u002Foauth-resource","3.modules\u002F1.identity\u002F8.oauth-resource",{"title":245,"icon":49,"path":246,"stem":247,"children":248,"page":49},"Commerce","\u002Fmodules\u002Fcommerce","3.modules\u002F2.commerce",[249,253,257],{"title":250,"path":251,"stem":252},"Stripe","\u002Fmodules\u002Fcommerce\u002Fstripe","3.modules\u002F2.commerce\u002F1.stripe",{"title":254,"path":255,"stem":256},"Payments","\u002Fmodules\u002Fcommerce\u002Fpayments","3.modules\u002F2.commerce\u002F2.payments",{"title":258,"path":259,"stem":260,"children":261},"Subscriptions","\u002Fmodules\u002Fcommerce\u002Fsubscriptions","3.modules\u002F2.commerce\u002F3.subscriptions\u002Findex",[262,263,266,270],{"title":173,"path":259,"stem":260},{"title":175,"path":264,"stem":265},"\u002Fmodules\u002Fcommerce\u002Fsubscriptions\u002Fserver","3.modules\u002F2.commerce\u002F3.subscriptions\u002F2.server",{"title":267,"path":268,"stem":269},"App","\u002Fmodules\u002Fcommerce\u002Fsubscriptions\u002Fapp","3.modules\u002F2.commerce\u002F3.subscriptions\u002F3.app",{"title":179,"path":271,"stem":272},"\u002Fmodules\u002Fcommerce\u002Fsubscriptions\u002Freference","3.modules\u002F2.commerce\u002F3.subscriptions\u002F4.reference",{"title":274,"icon":49,"path":275,"stem":276,"children":277,"page":49},"Features","\u002Fmodules\u002Ffeatures","3.modules\u002F3.features",[278,293,312,316],{"title":279,"path":280,"stem":281,"children":282},"Events","\u002Fmodules\u002Ffeatures\u002Fevents","3.modules\u002F3.features\u002F1.events\u002Findex",[283,284,287,290],{"title":173,"path":280,"stem":281},{"title":175,"path":285,"stem":286},"\u002Fmodules\u002Ffeatures\u002Fevents\u002Fserver","3.modules\u002F3.features\u002F1.events\u002F2.server",{"title":267,"path":288,"stem":289},"\u002Fmodules\u002Ffeatures\u002Fevents\u002Fapp","3.modules\u002F3.features\u002F1.events\u002F3.app",{"title":179,"path":291,"stem":292},"\u002Fmodules\u002Ffeatures\u002Fevents\u002Freference","3.modules\u002F3.features\u002F1.events\u002F4.reference",{"title":294,"path":295,"stem":296,"children":297},"Forms","\u002Fmodules\u002Ffeatures\u002Fforms","3.modules\u002F3.features\u002F2.forms\u002Findex",[298,299,303,306,309],{"title":173,"path":295,"stem":296},{"title":300,"path":301,"stem":302},"Definition","\u002Fmodules\u002Ffeatures\u002Fforms\u002Fdefinition","3.modules\u002F3.features\u002F2.forms\u002F2.definition",{"title":175,"path":304,"stem":305},"\u002Fmodules\u002Ffeatures\u002Fforms\u002Fserver","3.modules\u002F3.features\u002F2.forms\u002F3.server",{"title":267,"path":307,"stem":308},"\u002Fmodules\u002Ffeatures\u002Fforms\u002Fapp","3.modules\u002F3.features\u002F2.forms\u002F4.app",{"title":179,"path":310,"stem":311},"\u002Fmodules\u002Ffeatures\u002Fforms\u002Freference","3.modules\u002F3.features\u002F2.forms\u002F5.reference",{"title":313,"path":314,"stem":315},"Cookie Consent","\u002Fmodules\u002Ffeatures\u002Fcookie-consent","3.modules\u002F3.features\u002F3.cookie-consent",{"title":317,"path":318,"stem":319},"MCP","\u002Fmodules\u002Ffeatures\u002Fmcp","3.modules\u002F3.features\u002F4.mcp",{"title":321,"icon":49,"path":322,"stem":323,"children":324,"page":49},"Interface","\u002Fmodules\u002Finterface","3.modules\u002F4.interface",[325,350,353,357,361],{"title":326,"path":327,"stem":328,"children":329},"UI","\u002Fmodules\u002Finterface\u002Fui","3.modules\u002F4.interface\u002F1.ui\u002Findex",[330,331,335,339,343,347],{"title":173,"path":327,"stem":328},{"title":332,"path":333,"stem":334},"Page components","\u002Fmodules\u002Finterface\u002Fui\u002Fpage-components","3.modules\u002F4.interface\u002F1.ui\u002F2.page-components",{"title":336,"path":337,"stem":338},"App shell","\u002Fmodules\u002Finterface\u002Fui\u002Fapp-shell","3.modules\u002F4.interface\u002F1.ui\u002F3.app-shell",{"title":340,"path":341,"stem":342},"Form inputs","\u002Fmodules\u002Finterface\u002Fui\u002Fform-inputs","3.modules\u002F4.interface\u002F1.ui\u002F4.form-inputs",{"title":344,"path":345,"stem":346},"Composables and helpers","\u002Fmodules\u002Finterface\u002Fui\u002Fcomposables","3.modules\u002F4.interface\u002F1.ui\u002F5.composables",{"title":179,"path":348,"stem":349},"\u002Fmodules\u002Finterface\u002Fui\u002Freference","3.modules\u002F4.interface\u002F1.ui\u002F6.reference",{"title":217,"path":351,"stem":352},"\u002Fmodules\u002Finterface\u002Fadmin","3.modules\u002F4.interface\u002F2.admin",{"title":354,"path":355,"stem":356},"Menu","\u002Fmodules\u002Finterface\u002Fmenu","3.modules\u002F4.interface\u002F3.menu",{"title":358,"path":359,"stem":360},"Widget","\u002Fmodules\u002Finterface\u002Fwidget","3.modules\u002F4.interface\u002F4.widget",{"title":362,"path":363,"stem":364,"children":365},"Mobile","\u002Fmodules\u002Finterface\u002Fmobile","3.modules\u002F4.interface\u002F5.mobile\u002Findex",[366,367,371],{"title":173,"path":363,"stem":364},{"title":368,"path":369,"stem":370},"CLI and devices","\u002Fmodules\u002Finterface\u002Fmobile\u002Fcli-and-devices","3.modules\u002F4.interface\u002F5.mobile\u002F2.cli-and-devices",{"title":372,"path":373,"stem":374},"Splash screen","\u002Fmodules\u002Finterface\u002Fmobile\u002Fsplash-screen","3.modules\u002F4.interface\u002F5.mobile\u002F3.splash-screen",{"title":376,"icon":49,"path":377,"stem":378,"children":379,"page":49},"Platform","\u002Fmodules\u002Fplatform","3.modules\u002F5.platform",[380,402,406,423],{"title":381,"path":382,"stem":383,"children":384},"Module Kit","\u002Fmodules\u002Fplatform\u002Fmodule-kit","3.modules\u002F5.platform\u002F1.module-kit\u002Findex",[385,386,390,394,398],{"title":173,"path":382,"stem":383},{"title":387,"path":388,"stem":389},"Server toolkit","\u002Fmodules\u002Fplatform\u002Fmodule-kit\u002Fserver-toolkit","3.modules\u002F5.platform\u002F1.module-kit\u002F2.server-toolkit",{"title":391,"path":392,"stem":393},"Registries and menus","\u002Fmodules\u002Fplatform\u002Fmodule-kit\u002Fregistries-and-menus","3.modules\u002F5.platform\u002F1.module-kit\u002F3.registries-and-menus",{"title":395,"path":396,"stem":397},"Translations","\u002Fmodules\u002Fplatform\u002Fmodule-kit\u002Fi18n","3.modules\u002F5.platform\u002F1.module-kit\u002F4.i18n",{"title":399,"path":400,"stem":401},"Database helpers","\u002Fmodules\u002Fplatform\u002Fmodule-kit\u002Fdatabase","3.modules\u002F5.platform\u002F1.module-kit\u002F5.database",{"title":403,"path":404,"stem":405},"Notify","\u002Fmodules\u002Fplatform\u002Fnotify","3.modules\u002F5.platform\u002F2.notify",{"title":407,"path":408,"stem":409,"children":410},"Email Templates","\u002Fmodules\u002Fplatform\u002Femail-templates","3.modules\u002F5.platform\u002F3.email-templates\u002Findex",[411,412,416,419],{"title":173,"path":408,"stem":409},{"title":413,"path":414,"stem":415},"Email components","\u002Fmodules\u002Fplatform\u002Femail-templates\u002Fcomponents","3.modules\u002F5.platform\u002F3.email-templates\u002F2.components",{"title":175,"path":417,"stem":418},"\u002Fmodules\u002Fplatform\u002Femail-templates\u002Fserver","3.modules\u002F5.platform\u002F3.email-templates\u002F3.server",{"title":420,"path":421,"stem":422},"Module emails","\u002Fmodules\u002Fplatform\u002Femail-templates\u002Fmodule-emails","3.modules\u002F5.platform\u002F3.email-templates\u002F4.module-emails",{"title":424,"path":425,"stem":426,"children":427},"Base Layer","\u002Fmodules\u002Fplatform\u002Fbase-layer","3.modules\u002F5.platform\u002F4.base-layer\u002Findex",[428,429,433,437],{"title":173,"path":425,"stem":426},{"title":430,"path":431,"stem":432},"Content collections","\u002Fmodules\u002Fplatform\u002Fbase-layer\u002Fcontent-collections","3.modules\u002F5.platform\u002F4.base-layer\u002F2.content-collections",{"title":434,"path":435,"stem":436},"Layouts and routing","\u002Fmodules\u002Fplatform\u002Fbase-layer\u002Flayouts-and-routing","3.modules\u002F5.platform\u002F4.base-layer\u002F3.layouts-and-routing",{"title":438,"path":439,"stem":440},"Composables and utils","\u002Fmodules\u002Fplatform\u002Fbase-layer\u002Fcomposables-and-utils","3.modules\u002F5.platform\u002F4.base-layer\u002F4.composables-and-utils",{"title":442,"icon":443,"path":444,"stem":445,"children":446,"page":49},"Examples","i-lucide-code-xml","\u002Fexamples","4.examples",[447,466,476],{"title":448,"icon":49,"path":449,"stem":450,"children":451,"page":49},"Starters","\u002Fexamples\u002Fstarters","4.examples\u002F1.starters",[452,456,461],{"title":453,"path":454,"stem":455,"icon":6},"Workspace starter","\u002Fexamples\u002Fstarters\u002Fworkspace","4.examples\u002F1.starters\u002F1.workspace",{"title":457,"path":458,"stem":459,"icon":460},"Mobile starter","\u002Fexamples\u002Fstarters\u002Fmobile","4.examples\u002F1.starters\u002F2.mobile","i-lucide-smartphone",{"title":462,"path":463,"stem":464,"icon":465},"Standalone mobile starter","\u002Fexamples\u002Fstarters\u002Fmobile-standalone","4.examples\u002F1.starters\u002F3.mobile-standalone","i-lucide-tablet-smartphone",{"title":467,"icon":49,"path":468,"stem":469,"children":470,"page":49},"Apps","\u002Fexamples\u002Fapps","4.examples\u002F2.apps",[471],{"title":472,"path":473,"stem":474,"icon":475},"Frigear website app","\u002Fexamples\u002Fapps\u002Ffrigear-nu-website","4.examples\u002F2.apps\u002F1.frigear-nu-website","i-lucide-layout-dashboard",{"title":477,"icon":49,"path":478,"stem":479,"children":480,"page":49},"Tasks","\u002Fexamples\u002Ftasks","4.examples\u002F3.tasks",[481,485,489,494,499],{"title":482,"path":483,"stem":484,"icon":70},"Protect a route","\u002Fexamples\u002Ftasks\u002Fprotect-a-route","4.examples\u002F3.tasks\u002F1.protect-a-route",{"title":486,"path":487,"stem":488,"icon":475},"Add an admin page","\u002Fexamples\u002Ftasks\u002Fadd-an-admin-page","4.examples\u002F3.tasks\u002F2.add-an-admin-page",{"title":490,"path":491,"stem":492,"icon":493},"Sell event tickets","\u002Fexamples\u002Ftasks\u002Fsell-event-tickets","4.examples\u002F3.tasks\u002F3.sell-event-tickets","i-lucide-ticket",{"title":495,"path":496,"stem":497,"icon":498},"Build a form","\u002Fexamples\u002Ftasks\u002Fbuild-a-form","4.examples\u002F3.tasks\u002F4.build-a-form","i-lucide-clipboard-list",{"title":500,"path":501,"stem":502,"icon":503},"Send an email","\u002Fexamples\u002Ftasks\u002Fsend-an-email","4.examples\u002F3.tasks\u002F5.send-an-email","i-lucide-mail",{"title":505,"icon":506,"path":507,"stem":508,"children":509,"page":49},"Contribute","i-lucide-git-pull-request","\u002Fcontribute","5.contribute",[510,513,518,544,549,552,556,571],{"title":173,"path":511,"stem":512,"icon":506},"\u002Fcontribute\u002Foverview","5.contribute\u002F1.overview",{"title":514,"path":515,"stem":516,"icon":517},"Local development","\u002Fcontribute\u002Flocal-development","5.contribute\u002F2.local-development","i-lucide-terminal",{"title":519,"icon":49,"path":520,"stem":521,"children":522,"page":49},"Writing a module","\u002Fcontribute\u002Fwriting-a-module","5.contribute\u002F3.writing-a-module",[523,527,532,536,541],{"title":524,"path":525,"stem":526,"icon":39},"Scaffold a module","\u002Fcontribute\u002Fwriting-a-module\u002Fscaffold","5.contribute\u002F3.writing-a-module\u002F1.scaffold",{"title":528,"path":529,"stem":530,"icon":531},"Setup checklist","\u002Fcontribute\u002Fwriting-a-module\u002Fsetup-checklist","5.contribute\u002F3.writing-a-module\u002F2.setup-checklist","i-lucide-list-checks",{"title":533,"path":534,"stem":535,"icon":44},"Database tables","\u002Fcontribute\u002Fwriting-a-module\u002Fdatabase-tables","5.contribute\u002F3.writing-a-module\u002F3.database-tables",{"title":537,"path":538,"stem":539,"icon":540},"Testing","\u002Fcontribute\u002Fwriting-a-module\u002Ftesting","5.contribute\u002F3.writing-a-module\u002F4.testing","i-lucide-flask-conical",{"title":67,"path":542,"stem":543,"icon":70},"\u002Fcontribute\u002Fwriting-a-module\u002Faccess-control","5.contribute\u002F3.writing-a-module\u002F5.access-control",{"title":545,"path":546,"stem":547,"icon":548},"Publishing","\u002Fcontribute\u002Fpublishing","5.contribute\u002F4.publishing","i-lucide-package-check",{"title":448,"path":550,"stem":551,"icon":6},"\u002Fcontribute\u002Fstarters","5.contribute\u002F5.starters",{"title":553,"path":554,"stem":555,"icon":52},"Documentation","\u002Fcontribute\u002Fdocumentation","5.contribute\u002F6.documentation",{"title":557,"icon":49,"path":558,"stem":559,"children":560,"page":49},"Tooling","\u002Fcontribute\u002Ftooling","5.contribute\u002F7.tooling",[561,566],{"title":562,"path":563,"stem":564,"icon":565},"CLI","\u002Fcontribute\u002Ftooling\u002Fcli","5.contribute\u002F7.tooling\u002F1.cli","i-lucide-square-terminal",{"title":567,"path":568,"stem":569,"icon":570},"ESLint config","\u002Fcontribute\u002Ftooling\u002Feslint-config","5.contribute\u002F7.tooling\u002F2.eslint-config","i-lucide-spell-check",{"title":572,"icon":49,"path":573,"stem":574,"children":575},"Decisions","\u002Fcontribute\u002Fdecisions","5.contribute\u002F8.decisions\u002Findex",[576,578,583],{"title":572,"path":573,"stem":574,"icon":577},"i-lucide-scale",{"title":579,"path":580,"stem":581,"icon":582},"Session lifetimes per OAuth client","\u002Fcontribute\u002Fdecisions\u002Foauth-token-lifetimes","5.contribute\u002F8.decisions\u002F1.oauth-token-lifetimes","i-lucide-timer",{"title":584,"path":585,"stem":586,"icon":70},"One permission model","\u002Fcontribute\u002Fdecisions\u002Fpermission-model","5.contribute\u002F8.decisions\u002F2.permission-model",{"id":588,"title":486,"body":589,"description":2988,"extension":2989,"links":2990,"meta":2991,"navigation":2992,"path":487,"seo":2993,"stem":488,"__hash__":2994},"docs\u002F4.examples\u002F3.tasks\u002F2.add-an-admin-page.md",{"type":590,"value":591,"toc":2981},"minimark",[592,609,614,665,676,702,706,717,920,934,1090,1120,1131,1149,1296,1349,1353,1378,2331,2338,2439,2443,2446,2513,2526,2869,2905,2941,2945,2977],[593,594,595,599,600,604,605,608],"p",{},[596,597,598],"strong",{},"What you'll build:"," a Units list page at ",[601,602,603],"code",{},"\u002Fadmin\u002Funits"," in your app, and the staff-only API route it reads. The\npage has a sidebar entry under ",[596,606,607],{},"Manage",", a heading with an action, a table in a card and an empty state.",[610,611,613],"h2",{"id":612},"prerequisites","Prerequisites",[615,616,617,636,651,656],"ul",{},[618,619,620,621,625,626,629,630,632,633,635],"li",{},"The ",[622,623,624],"a",{"href":454},"workspace starter",", or another ",[622,627,628],{"href":87},"host app"," with\n",[622,631,217],{"href":351}," and ",[622,634,199],{"href":200},".",[618,637,638,639,642,643,646,647,635],{},"A signed-in user whose role holds ",[601,640,641],{},"admin:access"," (the ",[601,644,645],{},"admin"," role does):\n",[622,648,650],{"href":649},"\u002Fget-started\u002Fauthentication#_4-roles-and-the-first-admin","Roles and the first admin",[618,652,653,655],{},[622,654,101],{"href":102},", for the patterns this page follows.",[618,657,658,660,661,664],{},[622,659,482],{"href":483},", for ",[601,662,663],{},"authorize"," and permissions.",[593,666,667,668,671,672,675],{},"The paths below are for the starter's ",[601,669,670],{},"apps\u002Fweb",". In another host app, leave out the ",[601,673,674],{},"apps\u002Fweb\u002F"," prefix.",[677,678,679,680,682,683,632,685,687,688,691,692,695,696,698,699,701],"note",{},"Admin brings ",[622,681,326],{"href":327},", ",[622,684,354],{"href":355},[622,686,358],{"href":359},".\nOnly users who hold ",[601,689,690],{},"admin.settings.permission"," get into the ",[622,693,694],{"href":87},"admin area",". It defaults to\n",[601,697,641],{},", which Admin registers and grants to the ",[601,700,645],{}," role. A role that should see the admin area at all\nneeds it too.",[610,703,705],{"id":704},"_1-register-the-permission-and-add-the-api-route","1. Register the permission and add the API route",[593,707,708,709,712,713,716],{},"The page reads its rows from ",[601,710,711],{},"\u002Fapi\u002Fadmin\u002Funits",", so that route comes first. It needs a permission of its own. Declare\nit with ",[601,714,715],{},"defineAccess",", in a file the server and the pages can import too:",[718,719,725],"pre",{"className":720,"code":721,"filename":722,"language":723,"meta":724,"style":724},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { defineAccess } from '@frigear-nu\u002Fauth\u002Faccess'\n\nexport const unitsAccess = defineAccess('units', {\n  permissions: {\n    read: { label: 'View units', grantTo: ['admin'] }, \u002F\u002F units:read\n    manage: { label: 'Manage units', grantTo: ['admin'] }, \u002F\u002F units:manage\n  },\n})\n","apps\u002Fweb\u002Fshared\u002Faccess\u002Funits.ts","ts","",[601,726,727,760,767,803,815,863,905,911],{"__ignoreMap":724},[728,729,732,736,740,744,747,750,753,757],"span",{"class":730,"line":731},"line",1,[728,733,735],{"class":734},"s7zQu","import",[728,737,739],{"class":738},"sMK4o"," {",[728,741,743],{"class":742},"sTEyZ"," defineAccess",[728,745,746],{"class":738}," }",[728,748,749],{"class":734}," from",[728,751,752],{"class":738}," '",[728,754,756],{"class":755},"sfazB","@frigear-nu\u002Fauth\u002Faccess",[728,758,759],{"class":738},"'\n",[728,761,763],{"class":730,"line":762},2,[728,764,766],{"emptyLinePlaceholder":765},true,"\n",[728,768,770,773,777,780,783,786,789,792,795,797,800],{"class":730,"line":769},3,[728,771,772],{"class":734},"export",[728,774,776],{"class":775},"spNyl"," const",[728,778,779],{"class":742}," unitsAccess ",[728,781,782],{"class":738},"=",[728,784,743],{"class":785},"s2Zo4",[728,787,788],{"class":742},"(",[728,790,791],{"class":738},"'",[728,793,794],{"class":755},"units",[728,796,791],{"class":738},[728,798,799],{"class":738},",",[728,801,802],{"class":738}," {\n",[728,804,806,810,813],{"class":730,"line":805},4,[728,807,809],{"class":808},"swJcz","  permissions",[728,811,812],{"class":738},":",[728,814,802],{"class":738},[728,816,818,821,823,825,828,830,832,835,837,839,842,844,847,849,851,853,856,859],{"class":730,"line":817},5,[728,819,820],{"class":808},"    read",[728,822,812],{"class":738},[728,824,739],{"class":738},[728,826,827],{"class":808}," label",[728,829,812],{"class":738},[728,831,752],{"class":738},[728,833,834],{"class":755},"View units",[728,836,791],{"class":738},[728,838,799],{"class":738},[728,840,841],{"class":808}," grantTo",[728,843,812],{"class":738},[728,845,846],{"class":742}," [",[728,848,791],{"class":738},[728,850,645],{"class":755},[728,852,791],{"class":738},[728,854,855],{"class":742},"] ",[728,857,858],{"class":738},"},",[728,860,862],{"class":861},"sHwdD"," \u002F\u002F units:read\n",[728,864,866,869,871,873,875,877,879,882,884,886,888,890,892,894,896,898,900,902],{"class":730,"line":865},6,[728,867,868],{"class":808},"    manage",[728,870,812],{"class":738},[728,872,739],{"class":738},[728,874,827],{"class":808},[728,876,812],{"class":738},[728,878,752],{"class":738},[728,880,881],{"class":755},"Manage units",[728,883,791],{"class":738},[728,885,799],{"class":738},[728,887,841],{"class":808},[728,889,812],{"class":738},[728,891,846],{"class":742},[728,893,791],{"class":738},[728,895,645],{"class":755},[728,897,791],{"class":738},[728,899,855],{"class":742},[728,901,858],{"class":738},[728,903,904],{"class":861}," \u002F\u002F units:manage\n",[728,906,908],{"class":730,"line":907},7,[728,909,910],{"class":738},"  },\n",[728,912,914,917],{"class":730,"line":913},8,[728,915,916],{"class":738},"}",[728,918,919],{"class":742},")\n",[593,921,922,923,926,927,930,931,812],{},"A host app registers it in a local module (Nuxt loads every file in ",[601,924,925],{},"apps\u002Fweb\u002Fmodules\u002F","). A module does the same in its\n",[601,928,929],{},"setup()",", with the declaration in ",[601,932,933],{},"src\u002Fruntime\u002Faccess.ts",[718,935,938],{"className":720,"code":936,"filename":937,"language":723,"meta":724,"style":724},"import { defineNuxtModule } from 'nuxt\u002Fkit'\nimport { registerAccess } from '@frigear-nu\u002Fauth\u002Futils'\nimport { unitsAccess } from '..\u002Fshared\u002Faccess\u002Funits'\n\nexport default defineNuxtModule({\n  meta: { name: 'units' },\n  setup(_options, nuxt) {\n    registerAccess(nuxt, unitsAccess)\n  },\n})\n","apps\u002Fweb\u002Fmodules\u002Funits.ts",[601,939,940,960,980,1000,1004,1018,1041,1062,1078,1083],{"__ignoreMap":724},[728,941,942,944,946,949,951,953,955,958],{"class":730,"line":731},[728,943,735],{"class":734},[728,945,739],{"class":738},[728,947,948],{"class":742}," defineNuxtModule",[728,950,746],{"class":738},[728,952,749],{"class":734},[728,954,752],{"class":738},[728,956,957],{"class":755},"nuxt\u002Fkit",[728,959,759],{"class":738},[728,961,962,964,966,969,971,973,975,978],{"class":730,"line":762},[728,963,735],{"class":734},[728,965,739],{"class":738},[728,967,968],{"class":742}," registerAccess",[728,970,746],{"class":738},[728,972,749],{"class":734},[728,974,752],{"class":738},[728,976,977],{"class":755},"@frigear-nu\u002Fauth\u002Futils",[728,979,759],{"class":738},[728,981,982,984,986,989,991,993,995,998],{"class":730,"line":769},[728,983,735],{"class":734},[728,985,739],{"class":738},[728,987,988],{"class":742}," unitsAccess",[728,990,746],{"class":738},[728,992,749],{"class":734},[728,994,752],{"class":738},[728,996,997],{"class":755},"..\u002Fshared\u002Faccess\u002Funits",[728,999,759],{"class":738},[728,1001,1002],{"class":730,"line":805},[728,1003,766],{"emptyLinePlaceholder":765},[728,1005,1006,1008,1011,1013,1015],{"class":730,"line":817},[728,1007,772],{"class":734},[728,1009,1010],{"class":734}," default",[728,1012,948],{"class":785},[728,1014,788],{"class":742},[728,1016,1017],{"class":738},"{\n",[728,1019,1020,1023,1025,1027,1030,1032,1034,1036,1038],{"class":730,"line":865},[728,1021,1022],{"class":808},"  meta",[728,1024,812],{"class":738},[728,1026,739],{"class":738},[728,1028,1029],{"class":808}," name",[728,1031,812],{"class":738},[728,1033,752],{"class":738},[728,1035,794],{"class":755},[728,1037,791],{"class":738},[728,1039,1040],{"class":738}," },\n",[728,1042,1043,1046,1048,1052,1054,1057,1060],{"class":730,"line":907},[728,1044,1045],{"class":808},"  setup",[728,1047,788],{"class":738},[728,1049,1051],{"class":1050},"sHdIc","_options",[728,1053,799],{"class":738},[728,1055,1056],{"class":1050}," nuxt",[728,1058,1059],{"class":738},")",[728,1061,802],{"class":738},[728,1063,1064,1067,1069,1072,1074,1076],{"class":730,"line":913},[728,1065,1066],{"class":785},"    registerAccess",[728,1068,788],{"class":808},[728,1070,1071],{"class":742},"nuxt",[728,1073,799],{"class":738},[728,1075,988],{"class":742},[728,1077,919],{"class":808},[728,1079,1081],{"class":730,"line":1080},9,[728,1082,910],{"class":738},[728,1084,1086,1088],{"class":730,"line":1085},10,[728,1087,916],{"class":738},[728,1089,919],{"class":742},[593,1091,1092,1093,682,1096,632,1099,1102,1103,1106,1107,682,1109,682,1112,1115,1116,1119],{},"After the next ",[601,1094,1095],{},"nuxi prepare",[601,1097,1098],{},"units:read",[601,1100,1101],{},"units:manage"," are part of the generated ",[601,1104,1105],{},"Permission"," type, so a typo\nin ",[601,1108,663],{},[601,1110,1111],{},"\u003CCan>",[601,1113,1114],{},"definePageMeta({ authorize })"," or a nav item's ",[601,1117,1118],{},"permission"," fails typecheck.",[1121,1122,1123,1124,1127,1128,1130],"caution",{},"Sign-in alone is not authorization, and neither is the route rule on ",[601,1125,1126],{},"\u002Fadmin\u002F**",": it guards the page, not the API\nroutes behind it. A route that never calls ",[601,1129,663],{}," answers every caller.",[593,1132,1133,1134,1136,1137,1140,1141,1144,1145,1148],{},"Check the permission with ",[601,1135,663],{},", before anything else. It answers ",[601,1138,1139],{},"401"," without a credential and ",[601,1142,1143],{},"403"," without\nthe permission. ",[601,1146,1147],{},"forbidden()"," and friends are for errors your own code raises after that.",[718,1150,1153],{"className":720,"code":1151,"filename":1152,"language":723,"meta":724,"style":724},"export default defineEventHandler(async (event) => {\n  await authorize(event, 'units:read') \u002F\u002F 401 \u002F 403 first: a user who may not list units learns nothing else\n\n  const items: { id: number, name: string, code: string }[] = [] \u002F\u002F load your units here\n  return { items, total: items.length }\n})\n","apps\u002Fweb\u002Fserver\u002Fapi\u002Fadmin\u002Funits.get.ts",[601,1154,1155,1182,1208,1212,1264,1290],{"__ignoreMap":724},[728,1156,1157,1159,1161,1164,1166,1169,1172,1175,1177,1180],{"class":730,"line":731},[728,1158,772],{"class":734},[728,1160,1010],{"class":734},[728,1162,1163],{"class":785}," defineEventHandler",[728,1165,788],{"class":742},[728,1167,1168],{"class":775},"async",[728,1170,1171],{"class":738}," (",[728,1173,1174],{"class":1050},"event",[728,1176,1059],{"class":738},[728,1178,1179],{"class":775}," =>",[728,1181,802],{"class":738},[728,1183,1184,1187,1190,1192,1194,1196,1198,1200,1202,1205],{"class":730,"line":762},[728,1185,1186],{"class":734},"  await",[728,1188,1189],{"class":785}," authorize",[728,1191,788],{"class":808},[728,1193,1174],{"class":742},[728,1195,799],{"class":738},[728,1197,752],{"class":738},[728,1199,1098],{"class":755},[728,1201,791],{"class":738},[728,1203,1204],{"class":808},") ",[728,1206,1207],{"class":861},"\u002F\u002F 401 \u002F 403 first: a user who may not list units learns nothing else\n",[728,1209,1210],{"class":730,"line":769},[728,1211,766],{"emptyLinePlaceholder":765},[728,1213,1214,1217,1220,1222,1224,1227,1229,1233,1235,1237,1239,1242,1244,1247,1249,1251,1253,1256,1258,1261],{"class":730,"line":805},[728,1215,1216],{"class":775},"  const",[728,1218,1219],{"class":742}," items",[728,1221,812],{"class":738},[728,1223,739],{"class":738},[728,1225,1226],{"class":808}," id",[728,1228,812],{"class":738},[728,1230,1232],{"class":1231},"sBMFI"," number",[728,1234,799],{"class":738},[728,1236,1029],{"class":808},[728,1238,812],{"class":738},[728,1240,1241],{"class":1231}," string",[728,1243,799],{"class":738},[728,1245,1246],{"class":808}," code",[728,1248,812],{"class":738},[728,1250,1241],{"class":1231},[728,1252,746],{"class":738},[728,1254,1255],{"class":808},"[] ",[728,1257,782],{"class":738},[728,1259,1260],{"class":808}," [] ",[728,1262,1263],{"class":861},"\u002F\u002F load your units here\n",[728,1265,1266,1269,1271,1273,1275,1278,1280,1282,1284,1287],{"class":730,"line":817},[728,1267,1268],{"class":734},"  return",[728,1270,739],{"class":738},[728,1272,1219],{"class":742},[728,1274,799],{"class":738},[728,1276,1277],{"class":808}," total",[728,1279,812],{"class":738},[728,1281,1219],{"class":742},[728,1283,635],{"class":738},[728,1285,1286],{"class":742},"length",[728,1288,1289],{"class":738}," }\n",[728,1291,1292,1294],{"class":730,"line":865},[728,1293,916],{"class":738},[728,1295,919],{"class":742},[615,1297,1298,1303,1318,1331,1342],{},[618,1299,1300,1302],{},[601,1301,663],{}," reads the user's roles from the database on every request, so a demoted admin is refused at once.",[618,1304,1305,1306,1309,1310,1313,1314,1317],{},"A route rule is ",[596,1307,1308],{},"not"," a substitute for the check. A rule ",[601,1311,1312],{},"'\u002Fapi\u002Fadmin\u002Funits': { permission: 'units:read' }"," in\n",[601,1315,1316],{},"nuxt.config.ts"," is a coarse first gate that Auth enforces on the path. It guards one pattern, not the handler, so\nkeep the check in the handler.",[618,1319,1320,1321,1324,1325,1328,1329,635],{},"Staff routes live under ",[601,1322,1323],{},"\u002Fapi\u002Fadmin\u002F...",". An action that changes who a user is, or moves money, gets its own\n",[601,1326,1327],{},"sessionOnly"," permission. More on ",[622,1330,482],{"href":483},[618,1332,1333,1334,682,1336,1338,1339,635],{},"One permission per kind of action (",[601,1335,1098],{},[601,1337,1101],{},"), not one per route. Name the staff permission\nafter the resource, and mark ones that expose personal data ",[601,1340,1341],{},":pii",[618,1343,1344,1345,1348],{},"The response is ",[601,1346,1347],{},"{ items, total }",", the shape the page in step 2 reads.",[610,1350,1352],{"id":1351},"_2-add-the-page","2. Add the page",[593,1354,1355,1356,1359,1360,1362,1363,1366,1367,1370,1371,1374,1375,635],{},"Pages under ",[601,1357,1358],{},"\u002Fadmin"," get the ",[601,1361,645],{}," layout automatically, so your page renders only its own body. Create\n",[601,1364,1365],{},"apps\u002Fweb\u002Fapp\u002Fpages\u002Fadmin\u002Funits.vue",". Its root is a plain ",[601,1368,1369],{},"\u003Cdiv class=\"flex flex-col gap-4\">",". It renders its own\nheading (",[601,1372,1373],{},"PageHeading","), which is the page's only ",[601,1376,1377],{},"\u003Ch1>",[718,1379,1383],{"className":1380,"code":1381,"filename":1365,"language":1382,"meta":724,"style":724},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003Cscript setup lang=\"ts\">\nimport type { TableColumn } from '@nuxt\u002Fui'\n\ninterface Unit { id: number, name: string, code: string }\n\nconst t = useModuleT('units')\nconst { data, loading } = await useCachedFetch\u003C{ items: Unit[], total: number }>('\u002Fapi\u002Fadmin\u002Funits', { key: 'admin:units' })\nconst units = computed(() => data.value?.items ?? [])\n\nconst TableIdentity = resolveComponent('TableIdentity')\nconst columns: TableColumn\u003CUnit>[] = [\n  {\n    accessorKey: 'name',\n    header: t('admin.name', 'Name'),\n    cell: ({ row }) => h(TableIdentity, { title: row.original.name, description: row.original.code }),\n  },\n]\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"flex flex-col gap-4\">\n    \u003CPageHeading :title=\"t('admin.title', 'Units')\">\n      \u003Ctemplate #actions>\n        \u003CCan permission=\"units:manage\">\n          \u003CUButton icon=\"i-lucide-plus\" :label=\"t('admin.create', 'Add unit')\" \u002F>\n        \u003C\u002FCan>\n      \u003C\u002Ftemplate>\n    \u003C\u002FPageHeading>\n\n    \u003CTableCard :loading=\"loading\">\n      \u003CUTable :data=\"units\" :columns=\"columns\" :ui=\"tableUi()\">\n        \u003Ctemplate #empty>\n          \u003CUEmpty variant=\"naked\" icon=\"i-lucide-building\" :title=\"t('admin.empty', 'No units yet')\">\n            \u003Ctemplate #actions>\n              \u003CCan permission=\"units:manage\">\n                \u003CUButton :label=\"t('admin.create', 'Add unit')\" \u002F>\n              \u003C\u002FCan>\n            \u003C\u002Ftemplate>\n          \u003C\u002FUEmpty>\n        \u003C\u002Ftemplate>\n      \u003C\u002FUTable>\n    \u003C\u002FTableCard>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n","vue",[601,1384,1385,1411,1434,1438,1472,1476,1499,1575,1613,1617,1640,1667,1673,1691,1724,1793,1798,1804,1814,1819,1829,1852,1897,1913,1935,1992,2002,2012,2022,2027,2050,2102,2116,2181,2195,2215,2255,2265,2275,2285,2294,2303,2312,2322],{"__ignoreMap":724},[728,1386,1387,1390,1393,1396,1399,1401,1404,1406,1408],{"class":730,"line":731},[728,1388,1389],{"class":738},"\u003C",[728,1391,1392],{"class":808},"script",[728,1394,1395],{"class":775}," setup",[728,1397,1398],{"class":775}," lang",[728,1400,782],{"class":738},[728,1402,1403],{"class":738},"\"",[728,1405,723],{"class":755},[728,1407,1403],{"class":738},[728,1409,1410],{"class":738},">\n",[728,1412,1413,1415,1418,1420,1423,1425,1427,1429,1432],{"class":730,"line":762},[728,1414,735],{"class":734},[728,1416,1417],{"class":734}," type",[728,1419,739],{"class":738},[728,1421,1422],{"class":742}," TableColumn",[728,1424,746],{"class":738},[728,1426,749],{"class":734},[728,1428,752],{"class":738},[728,1430,1431],{"class":755},"@nuxt\u002Fui",[728,1433,759],{"class":738},[728,1435,1436],{"class":730,"line":769},[728,1437,766],{"emptyLinePlaceholder":765},[728,1439,1440,1443,1446,1448,1450,1452,1454,1456,1458,1460,1462,1464,1466,1468,1470],{"class":730,"line":805},[728,1441,1442],{"class":775},"interface",[728,1444,1445],{"class":1231}," Unit",[728,1447,739],{"class":738},[728,1449,1226],{"class":808},[728,1451,812],{"class":738},[728,1453,1232],{"class":1231},[728,1455,799],{"class":738},[728,1457,1029],{"class":808},[728,1459,812],{"class":738},[728,1461,1241],{"class":1231},[728,1463,799],{"class":738},[728,1465,1246],{"class":808},[728,1467,812],{"class":738},[728,1469,1241],{"class":1231},[728,1471,1289],{"class":738},[728,1473,1474],{"class":730,"line":817},[728,1475,766],{"emptyLinePlaceholder":765},[728,1477,1478,1481,1484,1486,1489,1491,1493,1495,1497],{"class":730,"line":865},[728,1479,1480],{"class":775},"const",[728,1482,1483],{"class":742}," t ",[728,1485,782],{"class":738},[728,1487,1488],{"class":785}," useModuleT",[728,1490,788],{"class":742},[728,1492,791],{"class":738},[728,1494,794],{"class":755},[728,1496,791],{"class":738},[728,1498,919],{"class":742},[728,1500,1501,1503,1505,1508,1510,1513,1515,1518,1521,1524,1527,1529,1531,1533,1536,1538,1540,1542,1544,1547,1549,1551,1553,1555,1557,1559,1562,1564,1566,1569,1571,1573],{"class":730,"line":907},[728,1502,1480],{"class":775},[728,1504,739],{"class":738},[728,1506,1507],{"class":742}," data",[728,1509,799],{"class":738},[728,1511,1512],{"class":742}," loading ",[728,1514,916],{"class":738},[728,1516,1517],{"class":738}," =",[728,1519,1520],{"class":734}," await",[728,1522,1523],{"class":785}," useCachedFetch",[728,1525,1526],{"class":738},"\u003C{",[728,1528,1219],{"class":808},[728,1530,812],{"class":738},[728,1532,1445],{"class":1231},[728,1534,1535],{"class":742},"[]",[728,1537,799],{"class":738},[728,1539,1277],{"class":808},[728,1541,812],{"class":738},[728,1543,1232],{"class":1231},[728,1545,1546],{"class":738}," }>",[728,1548,788],{"class":742},[728,1550,791],{"class":738},[728,1552,711],{"class":755},[728,1554,791],{"class":738},[728,1556,799],{"class":738},[728,1558,739],{"class":738},[728,1560,1561],{"class":808}," key",[728,1563,812],{"class":738},[728,1565,752],{"class":738},[728,1567,1568],{"class":755},"admin:units",[728,1570,791],{"class":738},[728,1572,746],{"class":738},[728,1574,919],{"class":742},[728,1576,1577,1579,1582,1584,1587,1589,1592,1594,1596,1598,1601,1604,1607,1610],{"class":730,"line":913},[728,1578,1480],{"class":775},[728,1580,1581],{"class":742}," units ",[728,1583,782],{"class":738},[728,1585,1586],{"class":785}," computed",[728,1588,788],{"class":742},[728,1590,1591],{"class":738},"()",[728,1593,1179],{"class":775},[728,1595,1507],{"class":742},[728,1597,635],{"class":738},[728,1599,1600],{"class":742},"value",[728,1602,1603],{"class":738},"?.",[728,1605,1606],{"class":742},"items ",[728,1608,1609],{"class":738},"??",[728,1611,1612],{"class":742}," [])\n",[728,1614,1615],{"class":730,"line":1080},[728,1616,766],{"emptyLinePlaceholder":765},[728,1618,1619,1621,1624,1626,1629,1631,1633,1636,1638],{"class":730,"line":1085},[728,1620,1480],{"class":775},[728,1622,1623],{"class":742}," TableIdentity ",[728,1625,782],{"class":738},[728,1627,1628],{"class":785}," resolveComponent",[728,1630,788],{"class":742},[728,1632,791],{"class":738},[728,1634,1635],{"class":755},"TableIdentity",[728,1637,791],{"class":738},[728,1639,919],{"class":742},[728,1641,1643,1645,1648,1650,1652,1654,1657,1660,1662,1664],{"class":730,"line":1642},11,[728,1644,1480],{"class":775},[728,1646,1647],{"class":742}," columns",[728,1649,812],{"class":738},[728,1651,1422],{"class":1231},[728,1653,1389],{"class":738},[728,1655,1656],{"class":1231},"Unit",[728,1658,1659],{"class":738},">",[728,1661,1255],{"class":742},[728,1663,782],{"class":738},[728,1665,1666],{"class":742}," [\n",[728,1668,1670],{"class":730,"line":1669},12,[728,1671,1672],{"class":738},"  {\n",[728,1674,1676,1679,1681,1683,1686,1688],{"class":730,"line":1675},13,[728,1677,1678],{"class":808},"    accessorKey",[728,1680,812],{"class":738},[728,1682,752],{"class":738},[728,1684,1685],{"class":755},"name",[728,1687,791],{"class":738},[728,1689,1690],{"class":738},",\n",[728,1692,1694,1697,1699,1702,1704,1706,1709,1711,1713,1715,1718,1720,1722],{"class":730,"line":1693},14,[728,1695,1696],{"class":808},"    header",[728,1698,812],{"class":738},[728,1700,1701],{"class":785}," t",[728,1703,788],{"class":742},[728,1705,791],{"class":738},[728,1707,1708],{"class":755},"admin.name",[728,1710,791],{"class":738},[728,1712,799],{"class":738},[728,1714,752],{"class":738},[728,1716,1717],{"class":755},"Name",[728,1719,791],{"class":738},[728,1721,1059],{"class":742},[728,1723,1690],{"class":738},[728,1725,1727,1730,1732,1735,1738,1741,1743,1746,1749,1751,1753,1756,1758,1760,1762,1765,1767,1769,1771,1774,1776,1778,1780,1782,1784,1787,1789,1791],{"class":730,"line":1726},15,[728,1728,1729],{"class":785},"    cell",[728,1731,812],{"class":738},[728,1733,1734],{"class":738}," ({",[728,1736,1737],{"class":1050}," row",[728,1739,1740],{"class":738}," })",[728,1742,1179],{"class":775},[728,1744,1745],{"class":785}," h",[728,1747,1748],{"class":742},"(TableIdentity",[728,1750,799],{"class":738},[728,1752,739],{"class":738},[728,1754,1755],{"class":808}," title",[728,1757,812],{"class":738},[728,1759,1737],{"class":742},[728,1761,635],{"class":738},[728,1763,1764],{"class":742},"original",[728,1766,635],{"class":738},[728,1768,1685],{"class":742},[728,1770,799],{"class":738},[728,1772,1773],{"class":808}," description",[728,1775,812],{"class":738},[728,1777,1737],{"class":742},[728,1779,635],{"class":738},[728,1781,1764],{"class":742},[728,1783,635],{"class":738},[728,1785,1786],{"class":742},"code ",[728,1788,916],{"class":738},[728,1790,1059],{"class":742},[728,1792,1690],{"class":738},[728,1794,1796],{"class":730,"line":1795},16,[728,1797,910],{"class":738},[728,1799,1801],{"class":730,"line":1800},17,[728,1802,1803],{"class":742},"]\n",[728,1805,1807,1810,1812],{"class":730,"line":1806},18,[728,1808,1809],{"class":738},"\u003C\u002F",[728,1811,1392],{"class":808},[728,1813,1410],{"class":738},[728,1815,1817],{"class":730,"line":1816},19,[728,1818,766],{"emptyLinePlaceholder":765},[728,1820,1822,1824,1827],{"class":730,"line":1821},20,[728,1823,1389],{"class":738},[728,1825,1826],{"class":808},"template",[728,1828,1410],{"class":738},[728,1830,1832,1835,1838,1841,1843,1845,1848,1850],{"class":730,"line":1831},21,[728,1833,1834],{"class":738},"  \u003C",[728,1836,1837],{"class":808},"div",[728,1839,1840],{"class":775}," class",[728,1842,782],{"class":738},[728,1844,1403],{"class":738},[728,1846,1847],{"class":755},"flex flex-col gap-4",[728,1849,1403],{"class":738},[728,1851,1410],{"class":738},[728,1853,1855,1858,1860,1863,1866,1868,1870,1873,1875,1877,1880,1882,1884,1886,1889,1891,1893,1895],{"class":730,"line":1854},22,[728,1856,1857],{"class":738},"    \u003C",[728,1859,1373],{"class":808},[728,1861,1862],{"class":738}," :",[728,1864,1865],{"class":775},"title",[728,1867,782],{"class":738},[728,1869,1403],{"class":738},[728,1871,1872],{"class":785},"t",[728,1874,788],{"class":738},[728,1876,791],{"class":738},[728,1878,1879],{"class":755},"admin.title",[728,1881,791],{"class":738},[728,1883,682],{"class":738},[728,1885,791],{"class":738},[728,1887,1888],{"class":755},"Units",[728,1890,791],{"class":738},[728,1892,1059],{"class":738},[728,1894,1403],{"class":738},[728,1896,1410],{"class":738},[728,1898,1900,1903,1905,1908,1911],{"class":730,"line":1899},23,[728,1901,1902],{"class":738},"      \u003C",[728,1904,1826],{"class":808},[728,1906,1907],{"class":738}," #",[728,1909,1910],{"class":775},"actions",[728,1912,1410],{"class":738},[728,1914,1916,1919,1922,1925,1927,1929,1931,1933],{"class":730,"line":1915},24,[728,1917,1918],{"class":738},"        \u003C",[728,1920,1921],{"class":808},"Can",[728,1923,1924],{"class":775}," permission",[728,1926,782],{"class":738},[728,1928,1403],{"class":738},[728,1930,1101],{"class":755},[728,1932,1403],{"class":738},[728,1934,1410],{"class":738},[728,1936,1938,1941,1944,1947,1949,1951,1954,1956,1958,1961,1963,1965,1967,1969,1971,1974,1976,1978,1980,1983,1985,1987,1989],{"class":730,"line":1937},25,[728,1939,1940],{"class":738},"          \u003C",[728,1942,1943],{"class":808},"UButton",[728,1945,1946],{"class":775}," icon",[728,1948,782],{"class":738},[728,1950,1403],{"class":738},[728,1952,1953],{"class":755},"i-lucide-plus",[728,1955,1403],{"class":738},[728,1957,1862],{"class":738},[728,1959,1960],{"class":775},"label",[728,1962,782],{"class":738},[728,1964,1403],{"class":738},[728,1966,1872],{"class":785},[728,1968,788],{"class":738},[728,1970,791],{"class":738},[728,1972,1973],{"class":755},"admin.create",[728,1975,791],{"class":738},[728,1977,682],{"class":738},[728,1979,791],{"class":738},[728,1981,1982],{"class":755},"Add unit",[728,1984,791],{"class":738},[728,1986,1059],{"class":738},[728,1988,1403],{"class":738},[728,1990,1991],{"class":738}," \u002F>\n",[728,1993,1995,1998,2000],{"class":730,"line":1994},26,[728,1996,1997],{"class":738},"        \u003C\u002F",[728,1999,1921],{"class":808},[728,2001,1410],{"class":738},[728,2003,2005,2008,2010],{"class":730,"line":2004},27,[728,2006,2007],{"class":738},"      \u003C\u002F",[728,2009,1826],{"class":808},[728,2011,1410],{"class":738},[728,2013,2015,2018,2020],{"class":730,"line":2014},28,[728,2016,2017],{"class":738},"    \u003C\u002F",[728,2019,1373],{"class":808},[728,2021,1410],{"class":738},[728,2023,2025],{"class":730,"line":2024},29,[728,2026,766],{"emptyLinePlaceholder":765},[728,2028,2030,2032,2035,2037,2040,2042,2044,2046,2048],{"class":730,"line":2029},30,[728,2031,1857],{"class":738},[728,2033,2034],{"class":808},"TableCard",[728,2036,1862],{"class":738},[728,2038,2039],{"class":775},"loading",[728,2041,782],{"class":738},[728,2043,1403],{"class":738},[728,2045,2039],{"class":742},[728,2047,1403],{"class":738},[728,2049,1410],{"class":738},[728,2051,2053,2055,2058,2060,2063,2065,2067,2069,2071,2073,2076,2078,2080,2082,2084,2086,2089,2091,2093,2096,2098,2100],{"class":730,"line":2052},31,[728,2054,1902],{"class":738},[728,2056,2057],{"class":808},"UTable",[728,2059,1862],{"class":738},[728,2061,2062],{"class":775},"data",[728,2064,782],{"class":738},[728,2066,1403],{"class":738},[728,2068,794],{"class":742},[728,2070,1403],{"class":738},[728,2072,1862],{"class":738},[728,2074,2075],{"class":775},"columns",[728,2077,782],{"class":738},[728,2079,1403],{"class":738},[728,2081,2075],{"class":742},[728,2083,1403],{"class":738},[728,2085,1862],{"class":738},[728,2087,2088],{"class":775},"ui",[728,2090,782],{"class":738},[728,2092,1403],{"class":738},[728,2094,2095],{"class":785},"tableUi",[728,2097,1591],{"class":738},[728,2099,1403],{"class":738},[728,2101,1410],{"class":738},[728,2103,2105,2107,2109,2111,2114],{"class":730,"line":2104},32,[728,2106,1918],{"class":738},[728,2108,1826],{"class":808},[728,2110,1907],{"class":738},[728,2112,2113],{"class":775},"empty",[728,2115,1410],{"class":738},[728,2117,2119,2121,2124,2127,2129,2131,2134,2136,2138,2140,2142,2145,2147,2149,2151,2153,2155,2157,2159,2161,2164,2166,2168,2170,2173,2175,2177,2179],{"class":730,"line":2118},33,[728,2120,1940],{"class":738},[728,2122,2123],{"class":808},"UEmpty",[728,2125,2126],{"class":775}," variant",[728,2128,782],{"class":738},[728,2130,1403],{"class":738},[728,2132,2133],{"class":755},"naked",[728,2135,1403],{"class":738},[728,2137,1946],{"class":775},[728,2139,782],{"class":738},[728,2141,1403],{"class":738},[728,2143,2144],{"class":755},"i-lucide-building",[728,2146,1403],{"class":738},[728,2148,1862],{"class":738},[728,2150,1865],{"class":775},[728,2152,782],{"class":738},[728,2154,1403],{"class":738},[728,2156,1872],{"class":785},[728,2158,788],{"class":738},[728,2160,791],{"class":738},[728,2162,2163],{"class":755},"admin.empty",[728,2165,791],{"class":738},[728,2167,682],{"class":738},[728,2169,791],{"class":738},[728,2171,2172],{"class":755},"No units yet",[728,2174,791],{"class":738},[728,2176,1059],{"class":738},[728,2178,1403],{"class":738},[728,2180,1410],{"class":738},[728,2182,2184,2187,2189,2191,2193],{"class":730,"line":2183},34,[728,2185,2186],{"class":738},"            \u003C",[728,2188,1826],{"class":808},[728,2190,1907],{"class":738},[728,2192,1910],{"class":775},[728,2194,1410],{"class":738},[728,2196,2198,2201,2203,2205,2207,2209,2211,2213],{"class":730,"line":2197},35,[728,2199,2200],{"class":738},"              \u003C",[728,2202,1921],{"class":808},[728,2204,1924],{"class":775},[728,2206,782],{"class":738},[728,2208,1403],{"class":738},[728,2210,1101],{"class":755},[728,2212,1403],{"class":738},[728,2214,1410],{"class":738},[728,2216,2218,2221,2223,2225,2227,2229,2231,2233,2235,2237,2239,2241,2243,2245,2247,2249,2251,2253],{"class":730,"line":2217},36,[728,2219,2220],{"class":738},"                \u003C",[728,2222,1943],{"class":808},[728,2224,1862],{"class":738},[728,2226,1960],{"class":775},[728,2228,782],{"class":738},[728,2230,1403],{"class":738},[728,2232,1872],{"class":785},[728,2234,788],{"class":738},[728,2236,791],{"class":738},[728,2238,1973],{"class":755},[728,2240,791],{"class":738},[728,2242,682],{"class":738},[728,2244,791],{"class":738},[728,2246,1982],{"class":755},[728,2248,791],{"class":738},[728,2250,1059],{"class":738},[728,2252,1403],{"class":738},[728,2254,1991],{"class":738},[728,2256,2258,2261,2263],{"class":730,"line":2257},37,[728,2259,2260],{"class":738},"              \u003C\u002F",[728,2262,1921],{"class":808},[728,2264,1410],{"class":738},[728,2266,2268,2271,2273],{"class":730,"line":2267},38,[728,2269,2270],{"class":738},"            \u003C\u002F",[728,2272,1826],{"class":808},[728,2274,1410],{"class":738},[728,2276,2278,2281,2283],{"class":730,"line":2277},39,[728,2279,2280],{"class":738},"          \u003C\u002F",[728,2282,2123],{"class":808},[728,2284,1410],{"class":738},[728,2286,2288,2290,2292],{"class":730,"line":2287},40,[728,2289,1997],{"class":738},[728,2291,1826],{"class":808},[728,2293,1410],{"class":738},[728,2295,2297,2299,2301],{"class":730,"line":2296},41,[728,2298,2007],{"class":738},[728,2300,2057],{"class":808},[728,2302,1410],{"class":738},[728,2304,2306,2308,2310],{"class":730,"line":2305},42,[728,2307,2017],{"class":738},[728,2309,2034],{"class":808},[728,2311,1410],{"class":738},[728,2313,2315,2318,2320],{"class":730,"line":2314},43,[728,2316,2317],{"class":738},"  \u003C\u002F",[728,2319,1837],{"class":808},[728,2321,1410],{"class":738},[728,2323,2325,2327,2329],{"class":730,"line":2324},44,[728,2326,1809],{"class":738},[728,2328,1826],{"class":808},[728,2330,1410],{"class":738},[593,2332,2333,2334,812],{},"The page follows the ",[622,2335,2337],{"href":2336},"\u002Fguide\u002Fux-patterns\u002Fpages#lists","list pattern",[615,2339,2340,2363,2387,2396,2412,2422],{},[618,2341,2342,2345,2346,2348,2349,2351,2352,2355,2356,2358,2359,2362],{},[596,2343,2344],{},"Heading and action."," A list is a ",[601,2347,1373],{}," with the primary action, and a ",[601,2350,2057],{}," styled with ",[601,2353,2354],{},"tableUi()","\ninside a ",[601,2357,2034],{},". Past about eight rows, add a ",[601,2360,2361],{},"PageToolbar"," for search.",[618,2364,2365,2368,2369,2372,2373,2375,2376,2378,2379,2382,2383,2386],{},[596,2366,2367],{},"Loading."," ",[601,2370,2371],{},"useCachedFetch"," (from ",[622,2374,326],{"href":327},") returns ",[601,2377,2039],{},", which is ",[601,2380,2381],{},"true"," only while\nnothing is on screen yet. ",[601,2384,2385],{},"TableCard :loading"," shows skeleton rows for it, and a later refresh keeps the rows that\nare already there. It also keeps the list when you navigate away and back.",[618,2388,2389,2392,2393,2395],{},[596,2390,2391],{},"Empty state."," An empty list is a ",[601,2394,2123],{}," with exactly one action. It is never an empty table, and it never shows\nwhile the list is still loading.",[618,2397,2398,2368,2401,2404,2405,2408,2409,2411],{},[596,2399,2400],{},"Permissions.",[601,2402,2403],{},"\u003CCan permission=\"units:manage\">"," renders its content only for a visitor who holds the permission\n(",[601,2406,2407],{},"\u003CCannot>"," is the opposite). It decides what is shown, not what is allowed: the create route checks\n",[601,2410,1101],{}," itself.",[618,2413,2414,2417,2418,2421],{},[596,2415,2416],{},"Strings."," Every string goes through ",[601,2419,2420],{},"useModuleT"," with an English default. Buttons name the object: \"Add unit\",\nnot \"Add\".",[618,2423,2424,2427,2428,2431,2432,2435,2436,635],{},[596,2425,2426],{},"Row menu."," A list with actions adds a row menu (",[601,2429,2430],{},"RowMenu",") in the last column. It comes from a\n",[601,2433,2434],{},"use\u003CEntity>Actions()"," composable that owns create, edit and remove. See\n",[622,2437,106],{"href":2438},"\u002Fguide\u002Fux-patterns\u002Fforms-and-actions#one-actions-composable-per-entity",[610,2440,2442],{"id":2441},"_3-add-the-sidebar-entry","3. Add the sidebar entry",[593,2444,2445],{},"The page needs an entry in the admin sidebar, or nobody finds it. The sidebar has three groups:",[2447,2448,2449,2465],"table",{},[2450,2451,2452],"thead",{},[2453,2454,2455,2459,2462],"tr",{},[2456,2457,2458],"th",{},"Group",[2456,2460,2461],{},"Menu path",[2456,2463,2464],{},"Argument",[2466,2467,2468,2483,2498],"tbody",{},[2453,2469,2470,2473,2478],{},[2471,2472,173],"td",{},[2471,2474,2475],{},[601,2476,2477],{},"admin.nav.overview",[2471,2479,2480],{},[601,2481,2482],{},"'overview'",[2453,2484,2485,2487,2492],{},[2471,2486,607],{},[2471,2488,2489],{},[601,2490,2491],{},"admin.nav",[2471,2493,2494,2497],{},[601,2495,2496],{},"'manage'",", the default",[2453,2499,2500,2503,2508],{},[2471,2501,2502],{},"Settings",[2471,2504,2505],{},[601,2506,2507],{},"admin.nav.settings",[2471,2509,2510],{},[601,2511,2512],{},"'settings'",[593,2514,2515,2516,2519,2520,2523,2524,812],{},"Put a new feature in an existing group. Add the entry at build time with ",[601,2517,2518],{},"extendAdminNav"," from\n",[601,2521,2522],{},"@frigear-nu\u002Fadmin\u002Futils",". It runs in a Nuxt module's ",[601,2525,929],{},[2527,2528,2529,2742],"tabs",{},[2530,2531,2533,2546],"tabs-item",{"label":2532},"Host app",[593,2534,2535,2536,2538,2539,2541,2542,2545],{},"Add the call to the ",[601,2537,794],{}," module from step 1, or to a module of its own in ",[601,2540,925],{},", as the starter's\n",[601,2543,2544],{},"libsql-binding.ts"," shows.",[718,2547,2550],{"className":720,"code":2548,"filename":2549,"language":723,"meta":724,"style":724},"import { defineNuxtModule } from 'nuxt\u002Fkit'\nimport { adminRoute, extendAdminNav } from '@frigear-nu\u002Fadmin\u002Futils'\n\nexport default defineNuxtModule({\n  meta: { name: 'units-nav' },\n  setup(_options, nuxt) {\n    extendAdminNav(nuxt, { label: 'Units', to: adminRoute(nuxt, 'units'), icon: 'i-lucide-building', order: 20, permission: 'units:read' })\n  },\n})\n","apps\u002Fweb\u002Fmodules\u002Funits-nav.ts",[601,2551,2552,2570,2594,2598,2610,2631,2647,2732,2736],{"__ignoreMap":724},[728,2553,2554,2556,2558,2560,2562,2564,2566,2568],{"class":730,"line":731},[728,2555,735],{"class":734},[728,2557,739],{"class":738},[728,2559,948],{"class":742},[728,2561,746],{"class":738},[728,2563,749],{"class":734},[728,2565,752],{"class":738},[728,2567,957],{"class":755},[728,2569,759],{"class":738},[728,2571,2572,2574,2576,2579,2581,2584,2586,2588,2590,2592],{"class":730,"line":762},[728,2573,735],{"class":734},[728,2575,739],{"class":738},[728,2577,2578],{"class":742}," adminRoute",[728,2580,799],{"class":738},[728,2582,2583],{"class":742}," extendAdminNav",[728,2585,746],{"class":738},[728,2587,749],{"class":734},[728,2589,752],{"class":738},[728,2591,2522],{"class":755},[728,2593,759],{"class":738},[728,2595,2596],{"class":730,"line":769},[728,2597,766],{"emptyLinePlaceholder":765},[728,2599,2600,2602,2604,2606,2608],{"class":730,"line":805},[728,2601,772],{"class":734},[728,2603,1010],{"class":734},[728,2605,948],{"class":785},[728,2607,788],{"class":742},[728,2609,1017],{"class":738},[728,2611,2612,2614,2616,2618,2620,2622,2624,2627,2629],{"class":730,"line":817},[728,2613,1022],{"class":808},[728,2615,812],{"class":738},[728,2617,739],{"class":738},[728,2619,1029],{"class":808},[728,2621,812],{"class":738},[728,2623,752],{"class":738},[728,2625,2626],{"class":755},"units-nav",[728,2628,791],{"class":738},[728,2630,1040],{"class":738},[728,2632,2633,2635,2637,2639,2641,2643,2645],{"class":730,"line":865},[728,2634,1045],{"class":808},[728,2636,788],{"class":738},[728,2638,1051],{"class":1050},[728,2640,799],{"class":738},[728,2642,1056],{"class":1050},[728,2644,1059],{"class":738},[728,2646,802],{"class":738},[728,2648,2649,2652,2654,2656,2658,2660,2662,2664,2666,2668,2670,2672,2675,2677,2679,2681,2683,2685,2687,2689,2691,2693,2695,2697,2699,2701,2703,2705,2707,2710,2712,2716,2718,2720,2722,2724,2726,2728,2730],{"class":730,"line":907},[728,2650,2651],{"class":785},"    extendAdminNav",[728,2653,788],{"class":808},[728,2655,1071],{"class":742},[728,2657,799],{"class":738},[728,2659,739],{"class":738},[728,2661,827],{"class":808},[728,2663,812],{"class":738},[728,2665,752],{"class":738},[728,2667,1888],{"class":755},[728,2669,791],{"class":738},[728,2671,799],{"class":738},[728,2673,2674],{"class":808}," to",[728,2676,812],{"class":738},[728,2678,2578],{"class":785},[728,2680,788],{"class":808},[728,2682,1071],{"class":742},[728,2684,799],{"class":738},[728,2686,752],{"class":738},[728,2688,794],{"class":755},[728,2690,791],{"class":738},[728,2692,1059],{"class":808},[728,2694,799],{"class":738},[728,2696,1946],{"class":808},[728,2698,812],{"class":738},[728,2700,752],{"class":738},[728,2702,2144],{"class":755},[728,2704,791],{"class":738},[728,2706,799],{"class":738},[728,2708,2709],{"class":808}," order",[728,2711,812],{"class":738},[728,2713,2715],{"class":2714},"sbssI"," 20",[728,2717,799],{"class":738},[728,2719,1924],{"class":808},[728,2721,812],{"class":738},[728,2723,752],{"class":738},[728,2725,1098],{"class":755},[728,2727,791],{"class":738},[728,2729,746],{"class":738},[728,2731,919],{"class":808},[728,2733,2734],{"class":730,"line":913},[728,2735,910],{"class":738},[728,2737,2738,2740],{"class":730,"line":1080},[728,2739,916],{"class":738},[728,2741,919],{"class":742},[2530,2743,2745,2750],{"label":2744},"Module",[593,2746,2747,2748,812],{},"A module adds the entry in its own ",[601,2749,929],{},[718,2751,2754],{"className":720,"code":2752,"filename":2753,"language":723,"meta":724,"style":724},"import { adminRoute, extendAdminNav } from '@frigear-nu\u002Fadmin\u002Futils'\n\nextendAdminNav(nuxt, { label: 'Units', to: adminRoute(nuxt, 'units'), icon: 'i-lucide-building', order: 20, permission: 'units:read' })\n\u002F\u002F A configuration page goes into the Settings group:\n\u002F\u002F extendAdminNav(nuxt, { label: 'Integrations', to: adminRoute(nuxt, 'integrations') }, 'settings')\n","src\u002Fmodule.ts",[601,2755,2756,2778,2782,2859,2864],{"__ignoreMap":724},[728,2757,2758,2760,2762,2764,2766,2768,2770,2772,2774,2776],{"class":730,"line":731},[728,2759,735],{"class":734},[728,2761,739],{"class":738},[728,2763,2578],{"class":742},[728,2765,799],{"class":738},[728,2767,2583],{"class":742},[728,2769,746],{"class":738},[728,2771,749],{"class":734},[728,2773,752],{"class":738},[728,2775,2522],{"class":755},[728,2777,759],{"class":738},[728,2779,2780],{"class":730,"line":762},[728,2781,766],{"emptyLinePlaceholder":765},[728,2783,2784,2786,2789,2791,2793,2795,2797,2799,2801,2803,2805,2807,2809,2811,2813,2815,2817,2819,2821,2823,2825,2827,2829,2831,2833,2835,2837,2839,2841,2843,2845,2847,2849,2851,2853,2855,2857],{"class":730,"line":769},[728,2785,2518],{"class":785},[728,2787,2788],{"class":742},"(nuxt",[728,2790,799],{"class":738},[728,2792,739],{"class":738},[728,2794,827],{"class":808},[728,2796,812],{"class":738},[728,2798,752],{"class":738},[728,2800,1888],{"class":755},[728,2802,791],{"class":738},[728,2804,799],{"class":738},[728,2806,2674],{"class":808},[728,2808,812],{"class":738},[728,2810,2578],{"class":785},[728,2812,2788],{"class":742},[728,2814,799],{"class":738},[728,2816,752],{"class":738},[728,2818,794],{"class":755},[728,2820,791],{"class":738},[728,2822,1059],{"class":742},[728,2824,799],{"class":738},[728,2826,1946],{"class":808},[728,2828,812],{"class":738},[728,2830,752],{"class":738},[728,2832,2144],{"class":755},[728,2834,791],{"class":738},[728,2836,799],{"class":738},[728,2838,2709],{"class":808},[728,2840,812],{"class":738},[728,2842,2715],{"class":2714},[728,2844,799],{"class":738},[728,2846,1924],{"class":808},[728,2848,812],{"class":738},[728,2850,752],{"class":738},[728,2852,1098],{"class":755},[728,2854,791],{"class":738},[728,2856,746],{"class":738},[728,2858,919],{"class":742},[728,2860,2861],{"class":730,"line":805},[728,2862,2863],{"class":861},"\u002F\u002F A configuration page goes into the Settings group:\n",[728,2865,2866],{"class":730,"line":817},[728,2867,2868],{"class":861},"\u002F\u002F extendAdminNav(nuxt, { label: 'Integrations', to: adminRoute(nuxt, 'integrations') }, 'settings')\n",[615,2870,2871,2883,2892,2898],{},[618,2872,2873,2876,2877,2879,2880,635],{},[601,2874,2875],{},"adminRoute(nuxt, 'units')"," is ",[601,2878,603],{},", below the configured ",[601,2881,2882],{},"base",[618,2884,2885,2887,2888,2891],{},[601,2886,1118],{}," hides the entry from visitors who do not hold it. It only hides: the route behind it still checks the\npermission, and a page rule or ",[601,2889,2890],{},"definePageMeta({ authorize: 'units:read' })"," keeps others out of the page itself.",[618,2893,2894,2895,2897],{},"A configuration page goes into the Settings group: pass ",[601,2896,2512],{}," as the third argument.",[618,2899,2900,2901,2904],{},"A module can instead register a runtime menu source with ",[601,2902,2903],{},"extendMenuItem('admin.nav', ...)",". That works whether or\nnot Admin is installed.",[2906,2907,2910,2913,2914,682,2917,2919,2920,2922,2923,2925,2926,2928,2929,2931,2932,2934,2935,2938,2939,635],"callout",{"color":2908,"icon":2909},"success","i-lucide-check",[596,2911,2912],{},"What you should see:"," after a restart of ",[601,2915,2916],{},"pnpm dev",[596,2918,1888],{}," in the ",[596,2921,607],{}," group of the admin sidebar. It\nopens ",[601,2924,603],{},", with the ",[596,2927,1888],{}," heading and the ",[596,2930,1982],{}," button. A signed-in user whose roles lack\n",[601,2933,1098],{}," sees no entry, and ",[601,2936,2937],{},"GET \u002Fapi\u002Fadmin\u002Funits"," answers ",[601,2940,1143],{},[610,2942,2944],{"id":2943},"next-steps","Next steps",[615,2946,2947,2955,2960],{},[618,2948,2949,2951,2952,2954],{},[622,2950,106],{"href":107}," – make ",[596,2953,1982],{}," open a create form, and add the row\nmenu.",[618,2956,2957,2959],{},[622,2958,217],{"href":351}," – add a widget to the admin dashboard.",[618,2961,2962,2964,2965,682,2967,682,2969,682,2971,632,2974,635],{},[622,2963,326],{"href":327}," – look up ",[601,2966,1373],{},[601,2968,2034],{},[601,2970,2430],{},[601,2972,2973],{},"SectionCard",[601,2975,2976],{},"FormSlideover",[2978,2979,2980],"style",{},"html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--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 .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":724,"searchDepth":762,"depth":762,"links":2982},[2983,2984,2985,2986,2987],{"id":612,"depth":762,"text":613},{"id":704,"depth":762,"text":705},{"id":1351,"depth":762,"text":1352},{"id":2441,"depth":762,"text":2442},{"id":2943,"depth":762,"text":2944},"Add a list page with a sidebar entry to the admin area, built from the shared page components.","md",null,{},{"icon":475},{"title":486,"description":2988},"HdioqU59mRM8YmN3Cvf_o7fw41_JcE4VlETvzymk8v0",[2996,2998],{"title":482,"path":483,"stem":484,"description":2997,"icon":70,"children":-1},"Declare a permission with defineAccess, check it on a server route with authorize, keep money and credential actions to cookie sessions, and guard pages with route rules.",{"title":490,"path":491,"stem":492,"description":2999,"icon":493,"children":-1},"Sell paid and free event tickets with Events, Payments and Stripe, and run code when a ticket is paid.",1794503140360]