[{"data":1,"prerenderedAt":2545},["ShallowReactive",2],{"navigation_docs":3,"-examples-starters-mobile-standalone":587,"-examples-starters-mobile-standalone-surround":2540},[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":462,"body":589,"description":2533,"extension":2534,"links":2535,"meta":2536,"navigation":2537,"path":463,"seo":2538,"stem":464,"__hash__":2539},"docs\u002F4.examples\u002F1.starters\u002F3.mobile-standalone.md",{"type":590,"value":591,"toc":2517},"minimark",[592,599,629,645,690,701,706,714,750,754,790,902,914,1015,1019,1031,1048,1052,1065,1156,1177,1193,1213,1225,1238,1242,1252,1264,1288,1304,1317,1321,1328,1354,1367,1370,1395,1402,1406,1418,1421,1443,1447,1463,1544,1549,1563,1892,1968,1988,1998,2038,2051,2060,2065,2069,2080,2111,2115,2118,2339,2343,2357,2369,2372,2412,2415,2472,2482,2486,2513],[593,594,595],"p",{},[596,597,598],"strong",{},"What you'll have afterwards:",[600,601,602,615,622],"ul",{},[603,604,605,606,610,611,614],"li",{},"A web app on ",[607,608,609],"code",{},"http:\u002F\u002Flocalhost:3000"," with sign-up, sign-in, account pages and an admin area, signed in with the\n",[607,612,613],{},"admin"," role.",[603,616,617,618,621],{},"A mobile app on ",[607,619,620],{},"http:\u002F\u002Flocalhost:3001",", and on iOS and Android, that signs in at that web app.",[603,623,624,625,628],{},"An API route, ",[607,626,627],{},"\u002Fapi\u002Fme",", that the mobile app calls with its access token.",[593,630,631,632,636,637,640,641,644],{},"Use this ",[633,634,635],"a",{"href":87},"starter"," when your app needs its own users and API, with nothing else to run. It is\nthe ",[633,638,639],{"href":454},"workspace starter"," plus the app of the ",[633,642,643],{"href":458},"mobile starter",",\nwired together:",[600,646,647,666],{},[603,648,649,654,655,658,659,661,662,665],{},[596,650,651],{},[607,652,653],{},"apps\u002Fweb"," has sign-up and sign-in, ",[607,656,657],{},"\u002Faccount"," pages and an admin area with user management. With\n",[633,660,225],{"href":226}," it is also the ",[633,663,664],{"href":87},"issuer"," and the API of the\nmobile app.",[603,667,668,673,674,676,677,679,680,683,684,686,687,689],{},[596,669,670],{},[607,671,672],{},"apps\u002Fmobile"," is a Capacitor app on ",[633,675,362],{"href":363},". It signs in at ",[607,678,653],{}," with\n",[633,681,682],{"href":184},"Auth in client mode",", and picks its guards with\n",[633,685,199],{"href":200},". It calls ",[607,688,627],{}," with its access token.",[691,692,693,694,696,697,700],"tip",{},"There is nothing to register by hand. ",[607,695,653],{}," reads the app's client id and redirect URIs from\n",[607,698,699],{},"apps\u002Fmobile\u002Fidentity.ts",".",[702,703,705],"h2",{"id":704},"prerequisites","Prerequisites",[593,707,708,709,713],{},"The tools in ",[633,710,712],{"href":711},"\u002Fget-started\u002Finstallation#requirements","Requirements",", plus:",[715,716,717,730],"table",{},[718,719,720],"thead",{},[721,722,723,727],"tr",{},[724,725,726],"th",{},"Tool",[724,728,729],{},"For",[731,732,733,742],"tbody",{},[721,734,735,739],{},[736,737,738],"td",{},"Xcode",[736,740,741],{},"iOS: a recent Xcode with an iOS simulator. The native project uses Swift Package Manager, no CocoaPods.",[721,743,744,747],{},[736,745,746],{},"Android Studio",[736,748,749],{},"Android: a Java Development Kit (JDK) and the Android SDK.",[702,751,753],{"id":752},"_1-create-the-project","1. Create the project",[593,755,756,757,765,766,769,770,772,773,778,779,781,782,785,786,789],{},"Copy ",[633,758,762],{"href":759,"rel":760},"https:\u002F\u002Fgithub.com\u002FFrigear-nu\u002Ffrigear-nu\u002Ftree\u002Fmain\u002F.starters\u002Fmobile-standalone",[761],"nofollow",[607,763,764],{},".starters\u002Fmobile-standalone","\nwith ",[607,767,768],{},"pnpm create frigear-app"," (",[633,771,562],{"href":563},") or with ",[633,774,777],{"href":775,"rel":776},"https:\u002F\u002Fgithub.com\u002Funjs\u002Fgiget",[761],"giget",".\n",[607,780,768],{}," also runs ",[607,783,784],{},"pnpm install"," and ",[607,787,788],{},"pnpm prepare:env",":",[791,792,793,847],"code-group",{},[794,795,801],"pre",{"className":796,"code":797,"filename":798,"language":799,"meta":800,"style":800},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","pnpm create frigear-app my-app --platform web,mobile\ncd my-app\ngit init\n","pnpm create","bash","",[607,802,803,828,838],{"__ignoreMap":800},[804,805,808,812,816,819,822,825],"span",{"class":806,"line":807},"line",1,[804,809,811],{"class":810},"sBMFI","pnpm",[804,813,815],{"class":814},"sfazB"," create",[804,817,818],{"class":814}," frigear-app",[804,820,821],{"class":814}," my-app",[804,823,824],{"class":814}," --platform",[804,826,827],{"class":814}," web,mobile\n",[804,829,831,835],{"class":806,"line":830},2,[804,832,834],{"class":833},"s2Zo4","cd",[804,836,837],{"class":814}," my-app\n",[804,839,841,844],{"class":806,"line":840},3,[804,842,843],{"class":810},"git",[804,845,846],{"class":814}," init\n",[794,848,850],{"className":796,"code":849,"filename":777,"language":799,"meta":800,"style":800},"pnpx giget@latest gh:Frigear-nu\u002Ffrigear-nu\u002F.starters\u002Fmobile-standalone my-app\ncd my-app\ngit init\ncorepack enable\npnpm install\npnpm prepare:env\n",[607,851,852,865,871,877,886,894],{"__ignoreMap":800},[804,853,854,857,860,863],{"class":806,"line":807},[804,855,856],{"class":810},"pnpx",[804,858,859],{"class":814}," giget@latest",[804,861,862],{"class":814}," gh:Frigear-nu\u002Ffrigear-nu\u002F.starters\u002Fmobile-standalone",[804,864,837],{"class":814},[804,866,867,869],{"class":806,"line":830},[804,868,834],{"class":833},[804,870,837],{"class":814},[804,872,873,875],{"class":806,"line":840},[804,874,843],{"class":810},[804,876,846],{"class":814},[804,878,880,883],{"class":806,"line":879},4,[804,881,882],{"class":810},"corepack",[804,884,885],{"class":814}," enable\n",[804,887,889,891],{"class":806,"line":888},5,[804,890,811],{"class":810},[804,892,893],{"class":814}," install\n",[804,895,897,899],{"class":806,"line":896},6,[804,898,811],{"class":810},[804,900,901],{"class":814}," prepare:env\n",[593,903,904,906,907,785,910,913],{},[607,905,788],{}," creates ",[607,908,909],{},"apps\u002Fweb\u002F.env",[607,911,912],{},"apps\u002Fmobile\u002F.env"," from their examples, and generates the secrets. It\nnever overwrites a value, so it is safe to run again.",[715,915,916,929],{},[718,917,918],{},[721,919,920,923,926],{},[724,921,922],{},"Variable",[724,924,925],{},"Where",[724,927,928],{},"What it is",[731,930,931,949,963,983,999],{},[721,932,933,938,942],{},[736,934,935],{},[607,936,937],{},"NUXT_APP_SECRET",[736,939,940],{},[607,941,909],{},[736,943,944,945,948],{},"Seals the session cookie (Nuxt's ",[607,946,947],{},"appSecret","). Generated; 32+ characters in production.",[721,950,951,956,960],{},[736,952,953],{},[607,954,955],{},"NUXT_OAUTH_SERVER_PRIVATE_KEY",[736,957,958],{},[607,959,909],{},[736,961,962],{},"Signs the OAuth tokens. A PKCS#8 RSA key, generated.",[721,964,965,970,974],{},[736,966,967],{},[607,968,969],{},"NUXT_ACL_ADMINS",[736,971,972],{},[607,973,909],{},[736,975,976,977,979,980],{},"Comma separated emails that always get the ",[607,978,613],{}," role once verified. ",[596,981,982],{},"Put your own email here.",[721,984,985,990,994],{},[736,986,987],{},[607,988,989],{},"NUXT_PUBLIC_AUTHENTICATION_CLIENT_ISSUER",[736,991,992],{},[607,993,912],{},[736,995,996,998],{},[607,997,609],{},", the web app.",[721,1000,1001,1006,1010],{},[736,1002,1003],{},[607,1004,1005],{},"NUXT_PUBLIC_AUTHENTICATION_CLIENT_API_BASE",[736,1007,1008],{},[607,1009,912],{},[736,1011,1012,1014],{},[607,1013,609],{},", the web app's API.",[702,1016,1018],{"id":1017},"_2-run-both-apps","2. Run both apps",[794,1020,1022],{"className":796,"code":1021,"language":799,"meta":800,"style":800},"pnpm dev\n",[607,1023,1024],{"__ignoreMap":800},[804,1025,1026,1028],{"class":806,"line":807},[804,1027,811],{"class":810},[804,1029,1030],{"class":814}," dev\n",[593,1032,1033,1034,1036,1037,785,1039,1036,1041,1043,1044,1047],{},"This starts ",[607,1035,653],{}," on ",[607,1038,609],{},[607,1040,672],{},[607,1042,620],{},". On the first start,\nNuxtHub creates a local SQLite database in ",[607,1045,1046],{},"apps\u002Fweb\u002F.data\u002F",". It applies the committed migrations, including the tables\nof OAuth Server.",[702,1049,1051],{"id":1050},"_3-sign-in","3. Sign in",[1053,1054,1055,1056,1058,1059,1061,1062,700],"warning",{},"Put your email in ",[607,1057,969],{}," in ",[607,1060,909],{}," before you start the app. An email added later counts after a\nrestart of ",[607,1063,1064],{},"pnpm dev",[1066,1067,1068,1092,1117,1130,1142],"ol",{},[603,1069,1070,1071,1073,1074,1077,1078,1080,1081,1084,1085,1087,1088,1091],{},"Open ",[607,1072,609],{},", choose ",[596,1075,1076],{},"Create account"," and sign up with the email you put in ",[607,1079,969],{},".\nSigning up sends a verification email (",[607,1082,1083],{},"verifyEmail"," is on), printed in the terminal that runs ",[607,1086,1064],{},": open its\nlink and press ",[596,1089,1090],{},"Continue"," to sign in. The list only counts for a verified address.",[603,1093,1094,1097,1098,1101,1102,1105,1106,1109,1110,769,1113,1116],{},[607,1095,1096],{},"\u002Fadmin"," is the admin dashboard. ",[607,1099,1100],{},"\u002Fadmin\u002Foauth"," lists the OAuth applications. The mobile app's ",[607,1103,1104],{},"mobile"," application\nis marked ",[596,1107,1108],{},"Managed in nuxt.config",". On ",[596,1111,1112],{},"Security",[607,1114,1115],{},"\u002Faccount\u002Fsecurity",") you can add a passkey, which the\nmobile app's sign-in then offers too.",[603,1118,1070,1119,1121,1122,1125,1126,1129],{},[607,1120,620],{},", the mobile app in a browser. Its ",[607,1123,1124],{},"'\u002F**': { authenticated: true }"," route rule sends you\nto its ",[607,1127,1128],{},"\u002Flogin"," page.",[603,1131,1132,1135,1136,1138,1139,700],{},[596,1133,1134],{},"Sign in"," sends you to ",[607,1137,653],{},", which asks you to approve the app. It then returns to\n",[607,1140,1141],{},"http:\u002F\u002Flocalhost:3001\u002Fauth\u002Fcallback",[603,1143,1144,1145,1147,1148,1151,1152,1155],{},"On ",[596,1146,195],{},", ",[596,1149,1150],{},"Sign out"," revokes the refresh token and returns to ",[607,1153,1154],{},"\u002F",". The route rule sends you on to the\nsign-in page.",[1157,1158,1161,1164,1165,1168,1169,1172,1173,1176],"callout",{"color":1159,"icon":1160},"success","i-lucide-check",[596,1162,1163],{},"What you should see:"," ",[596,1166,1167],{},"Home"," shows your sign-in (",[607,1170,1171],{},"useUserSession().user",", from the ID token) and the result of\n",[607,1174,1175],{},"GET \u002Fapi\u002Fme",", called cross-origin with the access token. A reload keeps you signed in.",[593,1178,1179,1180,1147,1183,785,1186,1189,1190,700],{},"Both apps use the same app API: ",[607,1181,1182],{},"useUserSession()",[607,1184,1185],{},"useAuth()",[607,1187,1188],{},"$api"," \u002F ",[607,1191,1192],{},"useApi()",[600,1194,1195,1204],{},[603,1196,1197,1199,1200,1203],{},[607,1198,653],{}," runs Auth in ",[633,1201,1202],{"href":87},"server mode",": a cookie session.",[603,1205,1206,1208,1209,1212],{},[607,1207,672],{}," runs it in ",[633,1210,1211],{"href":87},"client mode",": tokens on the device.",[1214,1215,1216,1217,1221,1222,1224],"note",{},"The app's ",[633,1218,1220],{"href":1219},"\u002Fexamples\u002Fstarters\u002Fmobile#how-the-guards-work","guards"," only decide what it shows. ",[607,1223,653],{}," checks every\nAPI request.",[593,1226,1227,1228,1230,1231,1234,1235,1237],{},"Emails such as password resets are printed in the terminal that runs ",[607,1229,1064],{},", by the ",[607,1232,1233],{},"log"," driver of\n",[633,1236,403],{"href":404},". They stay there until you configure a real driver.",[702,1239,1241],{"id":1240},"_4-run-it-on-the-ios-simulator","4. Run it on the iOS simulator",[593,1243,1244,1245,1247,1248,1251],{},"Keep the web app running (",[607,1246,1064],{},", or ",[607,1249,1250],{},"pnpm dev:web","). Then, in a second terminal:",[794,1253,1255],{"className":796,"code":1254,"language":799,"meta":800,"style":800},"pnpm mobile:ios\n",[607,1256,1257],{"__ignoreMap":800},[804,1258,1259,1261],{"class":806,"line":807},[804,1260,811],{"class":810},[804,1262,1263],{"class":814}," mobile:ios\n",[593,1265,1266,1267,785,1270,1273,1274,1058,1277,785,1280,1283,1284,1287],{},"The script creates the native projects the first time: ",[607,1268,1269],{},"cap add ios",[607,1271,1272],{},"cap add android",", plus the URL scheme\n",[607,1275,1276],{},"com.example.mobile",[607,1278,1279],{},"Info.plist",[607,1281,1282],{},"AndroidManifest.xml",". It builds the SPA, syncs it into ",[607,1285,1286],{},"ios\u002F"," and asks which\nsimulator to run.",[593,1289,1290,1291,1293,1294,1296,1297,1299,1300,1303],{},"The simulator shares your Mac's network, so the app reaches ",[607,1292,609],{},". ",[596,1295,1134],{}," opens the in-app\nbrowser at ",[607,1298,653],{},". It has its own cookies, so you sign in there once. After you approve, the redirect to\n",[607,1301,1302],{},"com.example.mobile:\u002Fauth\u002Fcallback"," returns to the app, signed in.",[1053,1305,1306,1307,1310,1311,1314,1315,700],{},"Run ",[607,1308,1309],{},"pnpm mobile:sync"," after every change to the app or its ",[607,1312,1313],{},".env",", and after every ",[607,1316,784],{},[702,1318,1320],{"id":1319},"_5-run-it-on-an-iphone","5. Run it on an iPhone",[593,1322,1323,1324,1327],{},"A phone cannot reach ",[607,1325,1326],{},"localhost"," on your Mac. Start the web app on every interface, and build the app against your\nMac's local network (LAN) address:",[794,1329,1331],{"className":796,"code":1330,"language":799,"meta":800,"style":800},"pnpm dev:lan             # apps\u002Fweb on 0.0.0.0:3000\npnpm mobile:ios:device   # MOBILE_LAN=1 build, then opens Xcode\n",[607,1332,1333,1344],{"__ignoreMap":800},[804,1334,1335,1337,1340],{"class":806,"line":807},[804,1336,811],{"class":810},[804,1338,1339],{"class":814}," dev:lan",[804,1341,1343],{"class":1342},"sHwdD","             # apps\u002Fweb on 0.0.0.0:3000\n",[804,1345,1346,1348,1351],{"class":806,"line":830},[804,1347,811],{"class":810},[804,1349,1350],{"class":814}," mobile:ios:device",[804,1352,1353],{"class":1342},"   # MOBILE_LAN=1 build, then opens Xcode\n",[593,1355,1356,1359,1360,1362,1363,1366],{},[607,1357,1358],{},"MOBILE_LAN=1"," rewrites ",[607,1361,1326],{}," in the issuer and API base to this Mac's LAN address. ",[607,1364,1365],{},"MOBILE_LAN=192.168.1.20","\npicks the address. OAuth Server takes its issuer from the request, so it matches.",[593,1368,1369],{},"In Xcode:",[1066,1371,1372,1382,1392],{},[603,1373,1374,1375,1377,1378,1381],{},"Select the ",[596,1376,267],{}," target, then ",[596,1379,1380],{},"Signing & Capabilities",", and pick your team. A free Apple ID works, but the\ninstall then expires after 7 days. Do not commit the team id.",[603,1383,1384,1385,1388,1389,700],{},"Connect the phone and turn on ",[596,1386,1387],{},"Developer Mode"," on it (Settings, Privacy & Security). Choose it as the run\ndestination and press ",[596,1390,1391],{},"Run",[603,1393,1394],{},"The first time, trust the developer profile on the phone (Settings, General, VPN & Device Management). Allow local\nnetwork access when asked.",[1214,1396,1397,1398,1401],{},"The phone and the Mac must be on the same network. A LAN build never ships: the build fails when ",[607,1399,1400],{},"MOBILE_RELEASE=1"," is\nalso set.",[702,1403,1405],{"id":1404},"_6-run-it-on-android","6. Run it on Android",[794,1407,1409],{"className":796,"code":1408,"language":799,"meta":800,"style":800},"pnpm mobile:android\n",[607,1410,1411],{"__ignoreMap":800},[804,1412,1413,1415],{"class":806,"line":807},[804,1414,811],{"class":810},[804,1416,1417],{"class":814}," mobile:android\n",[593,1419,1420],{},"The steps are the same as for iOS.",[1053,1422,1423,1424,1427,1428,1431,1432,1435,1436,1438,1439,1442],{},"The Android WebView runs at ",[607,1425,1426],{},"https:\u002F\u002Flocalhost",", so calls to the plain ",[607,1429,1430],{},"http"," dev server are mixed content. Test Android\nagainst an ",[607,1433,1434],{},"https"," deployment of ",[607,1437,653],{},", or use live reload (see ",[633,1440,1441],{"href":363},"Device development",").",[702,1444,1446],{"id":1445},"what-is-in-the-starter","What is in the starter",[593,1448,1449,1451,1452,1454,1455,1458,1459,1462],{},[607,1450,672],{}," is the mobile starter's app, file for file. ",[607,1453,653],{}," is the workspace starter's app, except for the\nfiles below. The\n",[633,1456,639],{"href":1457},"\u002Fexamples\u002Fstarters\u002Fworkspace#what-is-in-the-starter"," and the\n",[633,1460,643],{"href":1461},"\u002Fexamples\u002Fstarters\u002Fmobile#what-is-in-the-starter"," list those files. This starter adds:",[715,1464,1465,1475],{},[718,1466,1467],{},[721,1468,1469,1472],{},[724,1470,1471],{},"Path",[724,1473,1474],{},"What it does",[731,1476,1477,1487,1497,1509,1524,1534],{},[721,1478,1479,1484],{},[736,1480,1481],{},[607,1482,1483],{},"apps\u002Fweb\u002Fnuxt.config.ts",[736,1485,1486],{},"Adds OAuth Server and registers the mobile app as a configured client.",[721,1488,1489,1494],{},[736,1490,1491],{},[607,1492,1493],{},"apps\u002Fweb\u002Fserver\u002Fapi\u002Fme.get.ts",[736,1495,1496],{},"The API route the mobile app calls.",[721,1498,1499,1504],{},[736,1500,1501],{},[607,1502,1503],{},"apps\u002Fweb\u002F.env.example",[736,1505,1506,1507,700],{},"Adds ",[607,1508,955],{},[721,1510,1511,1516],{},[736,1512,1513],{},[607,1514,1515],{},"apps\u002Fweb\u002Fpackage.json",[736,1517,1506,1518,785,1521,700],{},[607,1519,1520],{},"@frigear-nu\u002Foauth-server",[607,1522,1523],{},"@workspace\u002Fmobile",[721,1525,1526,1531],{},[736,1527,1528],{},[607,1529,1530],{},"apps\u002Fweb\u002Fserver\u002Fdb\u002Fmigrations\u002F",[736,1532,1533],{},"Also creates the tables of OAuth Server.",[721,1535,1536,1541],{},[736,1537,1538],{},[607,1539,1540],{},"apps\u002Fweb\u002FAGENTS.md",[736,1542,1543],{},"Conventions that include OAuth Server.",[1545,1546,1548],"h3",{"id":1547},"how-the-two-apps-fit-together","How the two apps fit together",[593,1550,1551,1553,1554,1293,1557,1559,1560,1562],{},[607,1552,699],{}," is exported as ",[607,1555,1556],{},"@workspace\u002Fmobile\u002Fidentity",[607,1558,653],{}," depends on ",[607,1561,1523],{}," to read\nit:",[794,1564,1568],{"className":1565,"code":1566,"filename":1483,"language":1567,"meta":800,"style":800},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { appName, clientId, devRedirectUri, nativeRedirectUri } from '@workspace\u002Fmobile\u002Fidentity'\n\nexport default defineNuxtConfig({\n  modules: [\n    \u002F\u002F ... the workspace starter's modules\n    '@frigear-nu\u002Foauth-server',\n  ],\n  oauthServer: {\n    cors: {\n      \u002F\u002F The webviews of the native app: iOS (capacitor:\u002F\u002Flocalhost) and Android (https:\u002F\u002Flocalhost).\n      origins: ['capacitor:\u002F\u002Flocalhost', 'https:\u002F\u002Flocalhost'],\n      \u002F\u002F Your API: the same origins may call it with `Authorization: Bearer`.\n      paths: ['\u002Fapi\u002F'],\n    },\n    clients: [{\n      id: clientId, \u002F\u002F 'mobile'\n      name: appName,\n      redirectUris: [nativeRedirectUri], \u002F\u002F 'com.example.mobile:\u002Fauth\u002Fcallback'\n      devRedirectUris: [devRedirectUri], \u002F\u002F 'http:\u002F\u002Flocalhost:3001\u002Fauth\u002Fcallback', under `nuxt dev` only\n    }],\n    settings: { pages: { admin: true } },\n  },\n})\n","ts",[607,1569,1570,1614,1620,1637,1648,1653,1666,1674,1685,1695,1701,1732,1738,1759,1765,1777,1792,1804,1820,1836,1846,1877,1883],{"__ignoreMap":800},[804,1571,1572,1576,1580,1584,1587,1590,1592,1595,1597,1600,1603,1606,1609,1611],{"class":806,"line":807},[804,1573,1575],{"class":1574},"s7zQu","import",[804,1577,1579],{"class":1578},"sMK4o"," {",[804,1581,1583],{"class":1582},"sTEyZ"," appName",[804,1585,1586],{"class":1578},",",[804,1588,1589],{"class":1582}," clientId",[804,1591,1586],{"class":1578},[804,1593,1594],{"class":1582}," devRedirectUri",[804,1596,1586],{"class":1578},[804,1598,1599],{"class":1582}," nativeRedirectUri",[804,1601,1602],{"class":1578}," }",[804,1604,1605],{"class":1574}," from",[804,1607,1608],{"class":1578}," '",[804,1610,1556],{"class":814},[804,1612,1613],{"class":1578},"'\n",[804,1615,1616],{"class":806,"line":830},[804,1617,1619],{"emptyLinePlaceholder":1618},true,"\n",[804,1621,1622,1625,1628,1631,1634],{"class":806,"line":840},[804,1623,1624],{"class":1574},"export",[804,1626,1627],{"class":1574}," default",[804,1629,1630],{"class":833}," defineNuxtConfig",[804,1632,1633],{"class":1582},"(",[804,1635,1636],{"class":1578},"{\n",[804,1638,1639,1643,1645],{"class":806,"line":879},[804,1640,1642],{"class":1641},"swJcz","  modules",[804,1644,789],{"class":1578},[804,1646,1647],{"class":1582}," [\n",[804,1649,1650],{"class":806,"line":888},[804,1651,1652],{"class":1342},"    \u002F\u002F ... the workspace starter's modules\n",[804,1654,1655,1658,1660,1663],{"class":806,"line":896},[804,1656,1657],{"class":1578},"    '",[804,1659,1520],{"class":814},[804,1661,1662],{"class":1578},"'",[804,1664,1665],{"class":1578},",\n",[804,1667,1669,1672],{"class":806,"line":1668},7,[804,1670,1671],{"class":1582},"  ]",[804,1673,1665],{"class":1578},[804,1675,1677,1680,1682],{"class":806,"line":1676},8,[804,1678,1679],{"class":1641},"  oauthServer",[804,1681,789],{"class":1578},[804,1683,1684],{"class":1578}," {\n",[804,1686,1688,1691,1693],{"class":806,"line":1687},9,[804,1689,1690],{"class":1641},"    cors",[804,1692,789],{"class":1578},[804,1694,1684],{"class":1578},[804,1696,1698],{"class":806,"line":1697},10,[804,1699,1700],{"class":1342},"      \u002F\u002F The webviews of the native app: iOS (capacitor:\u002F\u002Flocalhost) and Android (https:\u002F\u002Flocalhost).\n",[804,1702,1704,1707,1709,1712,1714,1717,1719,1721,1723,1725,1727,1730],{"class":806,"line":1703},11,[804,1705,1706],{"class":1641},"      origins",[804,1708,789],{"class":1578},[804,1710,1711],{"class":1582}," [",[804,1713,1662],{"class":1578},[804,1715,1716],{"class":814},"capacitor:\u002F\u002Flocalhost",[804,1718,1662],{"class":1578},[804,1720,1586],{"class":1578},[804,1722,1608],{"class":1578},[804,1724,1426],{"class":814},[804,1726,1662],{"class":1578},[804,1728,1729],{"class":1582},"]",[804,1731,1665],{"class":1578},[804,1733,1735],{"class":806,"line":1734},12,[804,1736,1737],{"class":1342},"      \u002F\u002F Your API: the same origins may call it with `Authorization: Bearer`.\n",[804,1739,1741,1744,1746,1748,1750,1753,1755,1757],{"class":806,"line":1740},13,[804,1742,1743],{"class":1641},"      paths",[804,1745,789],{"class":1578},[804,1747,1711],{"class":1582},[804,1749,1662],{"class":1578},[804,1751,1752],{"class":814},"\u002Fapi\u002F",[804,1754,1662],{"class":1578},[804,1756,1729],{"class":1582},[804,1758,1665],{"class":1578},[804,1760,1762],{"class":806,"line":1761},14,[804,1763,1764],{"class":1578},"    },\n",[804,1766,1768,1771,1773,1775],{"class":806,"line":1767},15,[804,1769,1770],{"class":1641},"    clients",[804,1772,789],{"class":1578},[804,1774,1711],{"class":1582},[804,1776,1636],{"class":1578},[804,1778,1780,1783,1785,1787,1789],{"class":806,"line":1779},16,[804,1781,1782],{"class":1641},"      id",[804,1784,789],{"class":1578},[804,1786,1589],{"class":1582},[804,1788,1586],{"class":1578},[804,1790,1791],{"class":1342}," \u002F\u002F 'mobile'\n",[804,1793,1795,1798,1800,1802],{"class":806,"line":1794},17,[804,1796,1797],{"class":1641},"      name",[804,1799,789],{"class":1578},[804,1801,1583],{"class":1582},[804,1803,1665],{"class":1578},[804,1805,1807,1810,1812,1815,1817],{"class":806,"line":1806},18,[804,1808,1809],{"class":1641},"      redirectUris",[804,1811,789],{"class":1578},[804,1813,1814],{"class":1582}," [nativeRedirectUri]",[804,1816,1586],{"class":1578},[804,1818,1819],{"class":1342}," \u002F\u002F 'com.example.mobile:\u002Fauth\u002Fcallback'\n",[804,1821,1823,1826,1828,1831,1833],{"class":806,"line":1822},19,[804,1824,1825],{"class":1641},"      devRedirectUris",[804,1827,789],{"class":1578},[804,1829,1830],{"class":1582}," [devRedirectUri]",[804,1832,1586],{"class":1578},[804,1834,1835],{"class":1342}," \u002F\u002F 'http:\u002F\u002Flocalhost:3001\u002Fauth\u002Fcallback', under `nuxt dev` only\n",[804,1837,1839,1842,1844],{"class":806,"line":1838},20,[804,1840,1841],{"class":1578},"    }",[804,1843,1729],{"class":1582},[804,1845,1665],{"class":1578},[804,1847,1849,1852,1854,1856,1859,1861,1863,1866,1868,1872,1874],{"class":806,"line":1848},21,[804,1850,1851],{"class":1641},"    settings",[804,1853,789],{"class":1578},[804,1855,1579],{"class":1578},[804,1857,1858],{"class":1641}," pages",[804,1860,789],{"class":1578},[804,1862,1579],{"class":1578},[804,1864,1865],{"class":1641}," admin",[804,1867,789],{"class":1578},[804,1869,1871],{"class":1870},"sfNiH"," true",[804,1873,1602],{"class":1578},[804,1875,1876],{"class":1578}," },\n",[804,1878,1880],{"class":806,"line":1879},22,[804,1881,1882],{"class":1578},"  },\n",[804,1884,1886,1889],{"class":806,"line":1885},23,[804,1887,1888],{"class":1578},"}",[804,1890,1891],{"class":1582},")\n",[715,1893,1894,1903],{},[718,1895,1896],{},[721,1897,1898,1901],{},[724,1899,1900],{},"Piece",[724,1902,1474],{},[731,1904,1905,1921,1931,1945,1959],{},[721,1906,1907,1912],{},[736,1908,1909],{},[607,1910,1911],{},"oauthServer.clients",[736,1913,1914,1915,1918,1919,700],{},"A ",[633,1916,1917],{"href":87},"configured client",": a public OAuth application with the fixed id ",[607,1920,1104],{},[721,1922,1923,1928],{},[736,1924,1925],{},[607,1926,1927],{},"cors.origins",[736,1929,1930],{},"The webview origins may call the token, revoke, userinfo and discovery endpoints.",[721,1932,1933,1938],{},[736,1934,1935],{},[607,1936,1937],{},"cors.paths",[736,1939,1940,1941,1944],{},"The same origins may call ",[607,1942,1943],{},"\u002Fapi\u002F**"," with a bearer token. Cookies never travel cross-origin.",[721,1946,1947,1952],{},[736,1948,1949],{},[607,1950,1951],{},"server\u002Fapi\u002Fme.get.ts",[736,1953,1954,1955,1958],{},"Returns an allowlisted ",[607,1956,1957],{},"{ id, name, email }"," for the signed-in user.",[721,1960,1961,1965],{},[736,1962,1963],{},[607,1964,955],{},[736,1966,1967],{},"The key that signs tokens.",[593,1969,1970,1974,1975,1978,1979,1982,1983,1987],{},[596,1971,1972,700],{},[607,1973,1911],{}," The configured client uses PKCE (Proof Key for Code Exchange) and has no secret. It is\ncreated in the database on the first OAuth request. The dev redirect URI, and its origin for CORS (cross-origin resource\nsharing), only exist under ",[607,1976,1977],{},"nuxt dev",", so production never accepts them. ",[607,1980,1981],{},"NUXT_OAUTH_SERVER_CLIENTS"," (JSON) replaces\nthe list per environment. ",[633,1984,1986],{"href":1985},"\u002Fguide\u002Fgoing-further\u002Foauth-application#create-it-in-nuxtconfig","Create it in nuxt.config","\nhas the details.",[593,1989,1990,1164,1994,1997],{},[596,1991,1992,700],{},[607,1993,1951],{},[607,1995,1996],{},"requireUser(event)"," accepts the session cookie and the access token. Write your API routes\nthe same way.",[593,1999,2000,2003,2004,1147,2007,2010,2011,2013,2014,2017,2018,2020,2021,2024,2025,2028,2029,2031,2032,2034,2035,2037],{},[596,2001,2002],{},"Who may do what."," OAuth Server registers its permissions (",[607,2005,2006],{},"oauth:read",[607,2008,2009],{},"oauth:manage","), and the starter's ",[607,2012,613],{},"\nrole holds them through ",[607,2015,2016],{},"grant: ['*']",", so admins manage the applications at ",[607,2019,1100],{},". An application's\n",[607,2022,2023],{},"allowedRoles"," is checked against the user's roles from ",[607,2026,2027],{},"authorization.roles",", the ",[607,2030,969],{}," admins included,\nso no plugin is needed. The ",[607,2033,1104],{}," application has no ",[607,2036,2023],{},", so every signed-in user may use it. An access\ntoken holds the permissions of its scopes that the user has, frozen when it is issued, and every request is still\nchecked against the user's current permissions.",[593,2039,2040,2044,2045,2047,2048,700],{},[596,2041,2042,700],{},[607,2043,955],{}," Without it, ",[607,2046,1977],{}," keeps a dev key in\n",[607,2049,2050],{},"apps\u002Fweb\u002F.data\u002Foauth-server\u002Fdev-key.pem",[1053,2052,2053,2054,2056,2057,700],{},"A production server without ",[607,2055,955],{}," answers ",[607,2058,2059],{},"500",[593,2061,2062,2063,700],{},"To register more OAuth applications, such as another app or a confidential web app, or to change this one, see\n",[633,2064,136],{"href":137},[1545,2066,2068],{"id":2067},"rename-the-app","Rename the app",[593,2070,2071,2072,2074,2075,2079],{},"Change ",[607,2073,699],{}," (app id, name, client id, scopes). The\n",[633,2076,2078],{"href":2077},"\u002Fexamples\u002Fstarters\u002Fmobile#rename-the-app","mobile starter guide"," lists the fields. Then:",[1066,2081,2082,2093],{},[603,2083,2084,2085,1293,2087,2089,2090,2092],{},"Restart ",[607,2086,1064],{},[607,2088,653],{}," reads the new client id and redirect URIs from the same file. The old OAuth\napplication stays in the database as an ordinary application, so its users stay signed in. Delete it in\n",[607,2091,1100],{}," when you no longer need it.",[603,2094,2095,2096,785,2099,2102,2103,2106,2107,2110],{},"Delete ",[607,2097,2098],{},"apps\u002Fmobile\u002Fios",[607,2100,2101],{},"apps\u002Fmobile\u002Fandroid",". Then run ",[607,2104,2105],{},"pnpm mobile:native"," (or ",[607,2108,2109],{},"pnpm mobile:ios",") to create\nthem with the new id and URL scheme.",[1545,2112,2114],{"id":2113},"commands","Commands",[593,2116,2117],{},"Run everything from the project root:",[715,2119,2120,2129],{},[718,2121,2122],{},[721,2123,2124,2127],{},[724,2125,2126],{},"Command",[724,2128,1474],{},[731,2130,2131,2147,2163,2175,2187,2197,2210,2229,2239,2249,2264,2274,2290,2299,2309,2319],{},[721,2132,2133,2137],{},[736,2134,2135],{},[607,2136,788],{},[736,2138,2139,2140,2142,2143,2146],{},"Creates each app's ",[607,2141,1313],{}," from ",[607,2144,2145],{},".env.example"," and fills in empty secrets.",[721,2148,2149,2153],{},[736,2150,2151],{},[607,2152,1064],{},[736,2154,2155,1036,2157,785,2159,1036,2161,700],{},[607,2156,653],{},[607,2158,609],{},[607,2160,672],{},[607,2162,620],{},[721,2164,2165,2172],{},[736,2166,2167,1189,2169],{},[607,2168,1250],{},[607,2170,2171],{},"pnpm dev:mobile",[736,2173,2174],{},"One of them.",[721,2176,2177,2182],{},[736,2178,2179],{},[607,2180,2181],{},"pnpm dev:lan",[736,2183,2184,2186],{},[607,2185,653],{}," on every interface, for a phone on the same network.",[721,2188,2189,2194],{},[736,2190,2191],{},[607,2192,2193],{},"pnpm build",[736,2195,2196],{},"Builds every app and package.",[721,2198,2199,2204],{},[736,2200,2201],{},[607,2202,2203],{},"pnpm preview",[736,2205,2206,2207,2209],{},"Previews the ",[607,2208,653],{}," build.",[721,2211,2212,2226],{},[736,2213,2214,1189,2217,1189,2220,1189,2223],{},[607,2215,2216],{},"pnpm lint",[607,2218,2219],{},"pnpm lint:fix",[607,2221,2222],{},"pnpm typecheck",[607,2224,2225],{},"pnpm test",[736,2227,2228],{},"Checks across the workspace.",[721,2230,2231,2236],{},[736,2232,2233],{},[607,2234,2235],{},"pnpm db:generate",[736,2237,2238],{},"Generates a migration after adding or upgrading a module. Commit it.",[721,2240,2241,2246],{},[736,2242,2243],{},[607,2244,2245],{},"pnpm db:migrate",[736,2247,2248],{},"Applies migrations without starting the dev server.",[721,2250,2251,2255],{},[736,2252,2253],{},[607,2254,2105],{},[736,2256,2257,2258,785,2260,2263],{},"Creates ",[607,2259,1286],{},[607,2261,2262],{},"android\u002F"," and registers the URL scheme. Safe to rerun.",[721,2265,2266,2271],{},[736,2267,2268],{},[607,2269,2270],{},"pnpm mobile:doctor",[736,2272,2273],{},"Lists missing Capacitor plugins and URL schemes.",[721,2275,2276,2280],{},[736,2277,2278],{},[607,2279,1309],{},[736,2281,2282,2283,785,2286,2289],{},"Runs ",[607,2284,2285],{},"nuxt generate",[607,2287,2288],{},"cap sync",". Run it after every app change.",[721,2291,2292,2296],{},[736,2293,2294],{},[607,2295,2109],{},[736,2297,2298],{},"Builds and runs on an iOS simulator.",[721,2300,2301,2306],{},[736,2302,2303],{},[607,2304,2305],{},"pnpm mobile:ios:device",[736,2307,2308],{},"Builds against this Mac's LAN address and opens Xcode, to run on a phone.",[721,2310,2311,2316],{},[736,2312,2313],{},[607,2314,2315],{},"pnpm mobile:android",[736,2317,2318],{},"Builds and runs on an Android emulator or device.",[721,2320,2321,2326],{},[736,2322,2323],{},[607,2324,2325],{},"pnpm clean",[736,2327,2328,2329,2332,2333,785,2336,700],{},"Runs each package's ",[607,2330,2331],{},"clean"," script, then removes the root ",[607,2334,2335],{},"node_modules",[607,2337,2338],{},".data",[702,2340,2342],{"id":2341},"production-checklist","Production checklist",[593,2344,2345,2346,2348,2349,2351,2352,1147,2354,2356],{},"Do everything in ",[633,2347,46],{"href":47}," for ",[607,2350,653],{},": the database and its migrations,\n",[607,2353,937],{},[607,2355,969],{}," and a real Notify driver. Then add the items below.",[2358,2359,2360,2361,2363,2364,2366,2367,1442],"caution",{},"Use a new ",[607,2362,955],{}," per environment. Never reuse the one in your local ",[607,2365,1313],{},"\n(",[633,2368,141],{"href":142},[593,2370,2371],{},"The server:",[600,2373,2376,2392,2400],{"className":2374},[2375],"contains-task-list",[603,2377,2380,2384,2385,2388,2389,2391],{"className":2378},[2379],"task-list-item",[2381,2382],"input",{"disabled":1618,"type":2383},"checkbox"," Set ",[607,2386,2387],{},"oauthServer.issuer"," to the public ",[607,2390,1434],{}," origin, so tokens do not depend on the request host behind a\nproxy.",[603,2393,2395,2384,2397,2399],{"className":2394},[2379],[2381,2396],{"disabled":1618,"type":2383},[607,2398,955],{}," in your hosting platform's environment, as above.",[603,2401,2403,2405,2406,2408,2409,2411],{"className":2402},[2379],[2381,2404],{"disabled":1618,"type":2383}," Serve ",[607,2407,653],{}," on its final ",[607,2410,1434],{}," domain. A passkey belongs to the domain it was created on, so moving means\nusers add theirs again.",[593,2413,2414],{},"The app:",[600,2416,2418,2441,2454,2466],{"className":2417},[2375],[603,2419,2421,2423,2424,1147,2427,785,2430,1058,2433,2436,2437,785,2439,700],{"className":2420},[2379],[2381,2422],{"disabled":1618,"type":2383}," Set your own ",[607,2425,2426],{},"appId",[607,2428,2429],{},"appName",[607,2431,2432],{},"clientId",[607,2434,2435],{},"identity.ts",", and recreate ",[607,2438,1286],{},[607,2440,2262],{},[603,2442,2444,2446,2447,785,2449,2451,2452,700],{"className":2443},[2379],[2381,2445],{"disabled":1618,"type":2383}," Point ",[607,2448,989],{},[607,2450,1005],{}," at the\nproduction server in the environment of the release build. Build with ",[607,2453,1400],{},[603,2455,2457,2459,2460,2463,2464,700],{"className":2456},[2379],[2381,2458],{"disabled":1618,"type":2383}," Keep ",[607,2461,2462],{},"@aparajita\u002Fcapacitor-secure-storage"," installed, and run ",[607,2465,2270],{},[603,2467,2469,2471],{"className":2468},[2379],[2381,2470],{"disabled":1618,"type":2383}," Replace the icon and splash screen, set the version and build number, and set up signing for the App Store and\nGoogle Play.",[593,2473,2474,2475,2478,2479,2481],{},"The ",[633,2476,2477],{"href":363},"Mobile release checklist"," and the ",[633,2480,225],{"href":226}," page\nhave the details.",[702,2483,2485],{"id":2484},"next-steps","Next steps",[600,2487,2488,2498,2503,2508],{},[603,2489,2490,2494,2495,2497],{},[633,2491,2493],{"href":2492},"\u002Fget-started\u002Fadding-modules#add-a-module","Add a module"," – add a feature to ",[607,2496,653],{},", with the same steps as any\napp.",[603,2499,2500,2502],{},[633,2501,225],{"href":226}," – scopes, claims, roles and the admin pages for OAuth applications.",[603,2504,2505,2507],{},[633,2506,682],{"href":184}," – the app's sign-in, tokens and options.",[603,2509,2510,2512],{},[633,2511,91],{"href":97}," – follow the shared patterns when you add screens.",[2514,2515,2516],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}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 .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 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 .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":800,"searchDepth":830,"depth":830,"links":2518},[2519,2520,2521,2522,2523,2524,2525,2526,2531,2532],{"id":704,"depth":830,"text":705},{"id":752,"depth":830,"text":753},{"id":1017,"depth":830,"text":1018},{"id":1050,"depth":830,"text":1051},{"id":1240,"depth":830,"text":1241},{"id":1319,"depth":830,"text":1320},{"id":1404,"depth":830,"text":1405},{"id":1445,"depth":830,"text":1446,"children":2527},[2528,2529,2530],{"id":1547,"depth":840,"text":1548},{"id":2067,"depth":840,"text":2068},{"id":2113,"depth":840,"text":2114},{"id":2341,"depth":830,"text":2342},{"id":2484,"depth":830,"text":2485},"Start a mobile app with its own backend - a Nuxt 4 web app that is the OAuth server and API, and a Capacitor app that signs in at it - from the standalone mobile starter.","md",null,{},{"icon":465},{"title":462,"description":2533},"f4Dkd_D8KgzykkzvV4JR77s_f8Mj31pGp3VJpjpEHIc",[2541,2543],{"title":457,"path":458,"stem":459,"description":2542,"icon":460,"children":-1},"Start an iOS and Android app on the Frigear mobile shell that signs in at frigear.nu or any OpenID Connect issuer, from the mobile starter.",{"title":472,"path":473,"stem":474,"description":2544,"icon":475,"children":-1},"The reference host app behind frigear.nu, with its modules, content, environment, database and deployment.",1794503140359]