[{"data":1,"prerenderedAt":3065},["ShallowReactive",2],{"navigation":3,"\u002Freference\u002Fpermissions":189,"\u002Freference\u002Fpermissions-surround":3060},[4,23,40,51,74,116,157,177],{"title":5,"path":6,"stem":7,"children":8,"icon":22},"Get Started","\u002Fget-started","1.get-started\u002F1.index",[9,12,17],{"title":10,"path":6,"stem":7,"icon":11},"Introduction","i-lucide-house",{"title":13,"path":14,"stem":15,"icon":16},"Prerequisites","\u002Fget-started\u002Fprerequisites","1.get-started\u002F2.prerequisites","i-lucide-list-checks",{"title":18,"path":19,"stem":20,"icon":21},"Installation","\u002Fget-started\u002Finstallation","1.get-started\u002F3.installation","i-lucide-settings","i-lucide-rocket",{"title":24,"icon":25,"path":26,"stem":27,"children":28,"page":39},"Develop","i-lucide-code","\u002Fdevelop","2.develop",[29,34],{"title":30,"path":31,"stem":32,"icon":33},"Version Control","\u002Fdevelop\u002Fversion-control","2.develop\u002F1.version-control","i-lucide-git-branch",{"title":35,"path":36,"stem":37,"icon":38},"Claude Code","\u002Fdevelop\u002Fclaude-code","2.develop\u002F2.claude-code","i-lucide-sparkles",false,{"title":41,"icon":42,"path":43,"stem":44,"children":45,"page":39},"Launch","i-lucide-globe","\u002Flaunch","3.launch",[46],{"title":47,"path":48,"stem":49,"icon":50},"Cloudflare","\u002Flaunch\u002Fcloudflare","3.launch\u002F1.cloudflare","i-lucide-cloud-upload",{"title":52,"path":53,"stem":54,"children":55,"icon":73},"Plugins","\u002Fplugins","4.plugins\u002F1.index",[56,58,63,68],{"title":52,"path":53,"stem":54,"icon":57},"i-lucide-list",{"title":59,"path":60,"stem":61,"icon":62},"Public API","\u002Fplugins\u002Fapi-keys","4.plugins\u002F2.api-keys","i-lucide-key",{"title":64,"path":65,"stem":66,"icon":67},"Cron Jobs","\u002Fplugins\u002Fcron-jobs","4.plugins\u002F4.cron-jobs","i-lucide-clock",{"title":69,"path":70,"stem":71,"icon":72},"Rate Limiting","\u002Fplugins\u002Frate-limiting","4.plugins\u002F5.rate-limiting","i-lucide-gauge","i-lucide-puzzle",{"title":75,"path":76,"stem":77,"children":78,"icon":115},"Examples","\u002Fexamples","5.examples\u002F1.index",[79,80,85,90,95,100,105,110],{"title":75,"path":76,"stem":77,"icon":57},{"title":81,"path":82,"stem":83,"icon":84},"Job Management","\u002Fexamples\u002Fjob-management","5.examples\u002F2.job-management","i-lucide-briefcase",{"title":86,"path":87,"stem":88,"icon":89},"Kanban \u002F To-Do List","\u002Fexamples\u002Fkanban-todo","5.examples\u002F3.kanban-todo","i-lucide-kanban",{"title":91,"path":92,"stem":93,"icon":94},"Inventory Management","\u002Fexamples\u002Finventory-management","5.examples\u002F4.inventory-management","i-lucide-package",{"title":96,"path":97,"stem":98,"icon":99},"Mini CRM","\u002Fexamples\u002Fmini-crm","5.examples\u002F5.mini-crm","i-lucide-users",{"title":101,"path":102,"stem":103,"icon":104},"Sales Orders & Invoices","\u002Fexamples\u002Fsales-invoices","5.examples\u002F6.sales-invoices","i-lucide-receipt",{"title":106,"path":107,"stem":108,"icon":109},"Calendar & Booking","\u002Fexamples\u002Fcalendar-booking","5.examples\u002F7.calendar-booking","i-lucide-calendar",{"title":111,"path":112,"stem":113,"icon":114},"Support Tickets","\u002Fexamples\u002Fsupport-tickets","5.examples\u002F8.support-tickets","i-lucide-life-buoy","i-lucide-book-open",{"title":117,"icon":118,"path":119,"stem":120,"children":121,"page":39},"Reference","i-lucide-file-text","\u002Freference","6.reference",[122,127,132,137,142,147,152],{"title":123,"path":124,"stem":125,"icon":126},"Architecture","\u002Freference\u002Farchitecture","6.reference\u002F1.architecture","i-lucide-layers",{"title":128,"path":129,"stem":130,"icon":131},"Permissions","\u002Freference\u002Fpermissions","6.reference\u002F2.permissions","i-lucide-shield",{"title":133,"path":134,"stem":135,"icon":136},"Invitations","\u002Freference\u002Finvitations","6.reference\u002F3.invitations","i-lucide-mail",{"title":138,"path":139,"stem":140,"icon":141},"Webhooks","\u002Freference\u002Fwebhooks","6.reference\u002F4.webhooks","i-lucide-webhook",{"title":143,"path":144,"stem":145,"icon":146},"AI Chat","\u002Freference\u002Fai-chat","6.reference\u002F5.ai-chat","i-lucide-message-square",{"title":148,"path":149,"stem":150,"icon":151},"Activity Log","\u002Freference\u002Factivity-log","6.reference\u002F6.activity-log","i-lucide-scroll",{"title":153,"path":154,"stem":155,"icon":156},"Manual Setup","\u002Freference\u002Fmanual-setup","6.reference\u002F7.manual-setup","i-lucide-wrench",{"title":158,"icon":159,"path":160,"stem":161,"children":162,"page":39},"Legal","i-lucide-scale","\u002Flegal","7.legal",[163,168,172],{"title":164,"path":165,"stem":166,"icon":167},"License","\u002Flegal\u002Flicense","7.legal\u002F1.license","i-lucide-file-check",{"title":169,"path":170,"stem":171,"icon":118},"Terms and Conditions","\u002Flegal\u002Fterms","7.legal\u002F2.terms",{"title":173,"path":174,"stem":175,"icon":176},"Privacy Policy","\u002Flegal\u002Fprivacy","7.legal\u002F3.privacy","i-lucide-lock",{"title":178,"path":179,"stem":180,"children":181,"icon":183},"Upgrades","\u002Fupgrades","8.upgrades\u002F1.index",[182,184],{"title":178,"path":179,"stem":180,"icon":183},"i-lucide-arrow-up-circle",{"title":185,"path":186,"stem":187,"icon":188},"\u002Fapp\u002F* gated subtree routing","\u002Fupgrades\u002Fapp-subtree-routing","8.upgrades\u002F2.app-subtree-routing","i-lucide-route",{"id":190,"title":128,"body":191,"description":3053,"extension":3054,"links":3055,"meta":3056,"navigation":3057,"path":129,"seo":3058,"stem":130,"__hash__":3059},"docs\u002F6.reference\u002F2.permissions.md",{"type":192,"value":193,"toc":3036},"minimark",[194,223,228,1205,1261,1265,1268,1310,1321,1325,1328,1693,1697,1704,1726,1959,1962,1969,1975,2073,2080,2087,2097,2173,2180,2230,2233,2240,2249,2254,2290,2295,2346,2355,2416,2423,2439,2699,2709,2720,2798,2801,2810,3018,3032],[195,196,197,198,202,203,206,207,210,211,214,215,218,219,222],"p",{},"Roles and permissions are defined in a single file: ",[199,200,201],"code",{},"shared\u002Fpermissions.ts",".\nServer (",[199,204,205],{},"authUser","), client (",[199,208,209],{},"useUserRole",", ",[199,212,213],{},"\u003CCanAccess>","), route middleware\n(",[199,216,217],{},"requirePermission","), and the AI chat tool surface (",[199,220,221],{},"tablePermissions",") all\nread from this map. Adding a new role or changing what a role can do means\nediting this one file.",[224,225,227],"h2",{"id":226},"the-permissions-file","The permissions file",[229,230,235],"pre",{"className":231,"code":232,"language":233,"meta":234,"style":234},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F shared\u002Fpermissions.ts\n\nexport type TeamRole = \"owner\" | \"admin\" | \"member\";\n\nexport const assignableRoles = [\"admin\", \"member\"] as const;\n\nexport const permissions = {\n  \u002F\u002F UI-only visibility gates — no corresponding server action\n  \"visible.member\":      [\"owner\", \"admin\", \"member\"],\n  \"visible.admin\":       [\"owner\", \"admin\"],\n  \"visible.owner\":       [\"owner\"],\n\n  \u002F\u002F Action permissions — enforced server-side via authUser()\n  \"team.view\":           [\"owner\", \"admin\", \"member\"],\n  \"team.update\":         [\"owner\"],\n  \"team.delete\":         [\"owner\"],\n  \"members.view\":        [\"owner\", \"admin\", \"member\"],\n  \"members.invite\":      [\"owner\", \"admin\"],\n  \"members.remove\":      [\"owner\", \"admin\"],\n  \"members.role.change\": [\"owner\", \"admin\"],\n  \"invitations.view\":    [\"owner\", \"admin\"],\n  \"invitations.revoke\":  [\"owner\", \"admin\"],\n  \"settings.team\":       [\"owner\"],\n  \"ai.chat\":             [\"owner\", \"admin\", \"member\"],\n  \"activity.view\":       [\"owner\"],\n\n  \"announcements.view\":   [\"owner\", \"admin\", \"member\"],\n  \"announcements.create\": [\"owner\", \"admin\"],\n  \"announcements.update\": [\"owner\", \"admin\"],\n  \"announcements.delete\": [\"owner\", \"admin\"],\n\n  \u002F\u002F Directory data readable by chat users.\n  \"profiles.view\":      [\"owner\", \"admin\", \"member\"],\n  \"team_members.view\":  [\"owner\", \"admin\", \"member\"],\n\n  \u002F\u002F Shared empty role set — used as the create\u002Fupdate\u002Fdelete entry in\n  \u002F\u002F tablePermissions for any table the LLM may only read.\n  \"readonly.write\":     [],\n} as const satisfies Record\u003Cstring, readonly TeamRole[]>;\n\nexport type Permission = keyof typeof permissions;\n","ts","",[199,236,237,246,253,304,309,352,357,372,378,424,457,481,486,492,533,558,582,623,655,687,719,752,785,809,850,874,879,920,952,984,1016,1021,1027,1067,1107,1112,1118,1124,1141,1177,1182],{"__ignoreMap":234},[238,239,242],"span",{"class":240,"line":241},"line",1,[238,243,245],{"class":244},"sHwdD","\u002F\u002F shared\u002Fpermissions.ts\n",[238,247,249],{"class":240,"line":248},2,[238,250,252],{"emptyLinePlaceholder":251},true,"\n",[238,254,256,260,264,268,272,275,279,282,285,287,290,292,294,296,299,301],{"class":240,"line":255},3,[238,257,259],{"class":258},"s7zQu","export",[238,261,263],{"class":262},"spNyl"," type",[238,265,267],{"class":266},"sBMFI"," TeamRole",[238,269,271],{"class":270},"sMK4o"," =",[238,273,274],{"class":270}," \"",[238,276,278],{"class":277},"sfazB","owner",[238,280,281],{"class":270},"\"",[238,283,284],{"class":270}," |",[238,286,274],{"class":270},[238,288,289],{"class":277},"admin",[238,291,281],{"class":270},[238,293,284],{"class":270},[238,295,274],{"class":270},[238,297,298],{"class":277},"member",[238,300,281],{"class":270},[238,302,303],{"class":270},";\n",[238,305,307],{"class":240,"line":306},4,[238,308,252],{"emptyLinePlaceholder":251},[238,310,312,314,317,321,324,327,329,331,333,336,338,340,342,345,348,350],{"class":240,"line":311},5,[238,313,259],{"class":258},[238,315,316],{"class":262}," const",[238,318,320],{"class":319},"sTEyZ"," assignableRoles ",[238,322,323],{"class":270},"=",[238,325,326],{"class":319}," [",[238,328,281],{"class":270},[238,330,289],{"class":277},[238,332,281],{"class":270},[238,334,335],{"class":270},",",[238,337,274],{"class":270},[238,339,298],{"class":277},[238,341,281],{"class":270},[238,343,344],{"class":319},"] ",[238,346,347],{"class":258},"as",[238,349,316],{"class":262},[238,351,303],{"class":270},[238,353,355],{"class":240,"line":354},6,[238,356,252],{"emptyLinePlaceholder":251},[238,358,360,362,364,367,369],{"class":240,"line":359},7,[238,361,259],{"class":258},[238,363,316],{"class":262},[238,365,366],{"class":319}," permissions ",[238,368,323],{"class":270},[238,370,371],{"class":270}," {\n",[238,373,375],{"class":240,"line":374},8,[238,376,377],{"class":244},"  \u002F\u002F UI-only visibility gates — no corresponding server action\n",[238,379,381,384,388,390,393,396,398,400,402,404,406,408,410,412,414,416,418,421],{"class":240,"line":380},9,[238,382,383],{"class":270},"  \"",[238,385,387],{"class":386},"swJcz","visible.member",[238,389,281],{"class":270},[238,391,392],{"class":270},":",[238,394,395],{"class":319},"      [",[238,397,281],{"class":270},[238,399,278],{"class":277},[238,401,281],{"class":270},[238,403,335],{"class":270},[238,405,274],{"class":270},[238,407,289],{"class":277},[238,409,281],{"class":270},[238,411,335],{"class":270},[238,413,274],{"class":270},[238,415,298],{"class":277},[238,417,281],{"class":270},[238,419,420],{"class":319},"]",[238,422,423],{"class":270},",\n",[238,425,427,429,432,434,436,439,441,443,445,447,449,451,453,455],{"class":240,"line":426},10,[238,428,383],{"class":270},[238,430,431],{"class":386},"visible.admin",[238,433,281],{"class":270},[238,435,392],{"class":270},[238,437,438],{"class":319},"       [",[238,440,281],{"class":270},[238,442,278],{"class":277},[238,444,281],{"class":270},[238,446,335],{"class":270},[238,448,274],{"class":270},[238,450,289],{"class":277},[238,452,281],{"class":270},[238,454,420],{"class":319},[238,456,423],{"class":270},[238,458,460,462,465,467,469,471,473,475,477,479],{"class":240,"line":459},11,[238,461,383],{"class":270},[238,463,464],{"class":386},"visible.owner",[238,466,281],{"class":270},[238,468,392],{"class":270},[238,470,438],{"class":319},[238,472,281],{"class":270},[238,474,278],{"class":277},[238,476,281],{"class":270},[238,478,420],{"class":319},[238,480,423],{"class":270},[238,482,484],{"class":240,"line":483},12,[238,485,252],{"emptyLinePlaceholder":251},[238,487,489],{"class":240,"line":488},13,[238,490,491],{"class":244},"  \u002F\u002F Action permissions — enforced server-side via authUser()\n",[238,493,495,497,500,502,504,507,509,511,513,515,517,519,521,523,525,527,529,531],{"class":240,"line":494},14,[238,496,383],{"class":270},[238,498,499],{"class":386},"team.view",[238,501,281],{"class":270},[238,503,392],{"class":270},[238,505,506],{"class":319},"           [",[238,508,281],{"class":270},[238,510,278],{"class":277},[238,512,281],{"class":270},[238,514,335],{"class":270},[238,516,274],{"class":270},[238,518,289],{"class":277},[238,520,281],{"class":270},[238,522,335],{"class":270},[238,524,274],{"class":270},[238,526,298],{"class":277},[238,528,281],{"class":270},[238,530,420],{"class":319},[238,532,423],{"class":270},[238,534,536,538,541,543,545,548,550,552,554,556],{"class":240,"line":535},15,[238,537,383],{"class":270},[238,539,540],{"class":386},"team.update",[238,542,281],{"class":270},[238,544,392],{"class":270},[238,546,547],{"class":319},"         [",[238,549,281],{"class":270},[238,551,278],{"class":277},[238,553,281],{"class":270},[238,555,420],{"class":319},[238,557,423],{"class":270},[238,559,561,563,566,568,570,572,574,576,578,580],{"class":240,"line":560},16,[238,562,383],{"class":270},[238,564,565],{"class":386},"team.delete",[238,567,281],{"class":270},[238,569,392],{"class":270},[238,571,547],{"class":319},[238,573,281],{"class":270},[238,575,278],{"class":277},[238,577,281],{"class":270},[238,579,420],{"class":319},[238,581,423],{"class":270},[238,583,585,587,590,592,594,597,599,601,603,605,607,609,611,613,615,617,619,621],{"class":240,"line":584},17,[238,586,383],{"class":270},[238,588,589],{"class":386},"members.view",[238,591,281],{"class":270},[238,593,392],{"class":270},[238,595,596],{"class":319},"        [",[238,598,281],{"class":270},[238,600,278],{"class":277},[238,602,281],{"class":270},[238,604,335],{"class":270},[238,606,274],{"class":270},[238,608,289],{"class":277},[238,610,281],{"class":270},[238,612,335],{"class":270},[238,614,274],{"class":270},[238,616,298],{"class":277},[238,618,281],{"class":270},[238,620,420],{"class":319},[238,622,423],{"class":270},[238,624,626,628,631,633,635,637,639,641,643,645,647,649,651,653],{"class":240,"line":625},18,[238,627,383],{"class":270},[238,629,630],{"class":386},"members.invite",[238,632,281],{"class":270},[238,634,392],{"class":270},[238,636,395],{"class":319},[238,638,281],{"class":270},[238,640,278],{"class":277},[238,642,281],{"class":270},[238,644,335],{"class":270},[238,646,274],{"class":270},[238,648,289],{"class":277},[238,650,281],{"class":270},[238,652,420],{"class":319},[238,654,423],{"class":270},[238,656,658,660,663,665,667,669,671,673,675,677,679,681,683,685],{"class":240,"line":657},19,[238,659,383],{"class":270},[238,661,662],{"class":386},"members.remove",[238,664,281],{"class":270},[238,666,392],{"class":270},[238,668,395],{"class":319},[238,670,281],{"class":270},[238,672,278],{"class":277},[238,674,281],{"class":270},[238,676,335],{"class":270},[238,678,274],{"class":270},[238,680,289],{"class":277},[238,682,281],{"class":270},[238,684,420],{"class":319},[238,686,423],{"class":270},[238,688,690,692,695,697,699,701,703,705,707,709,711,713,715,717],{"class":240,"line":689},20,[238,691,383],{"class":270},[238,693,694],{"class":386},"members.role.change",[238,696,281],{"class":270},[238,698,392],{"class":270},[238,700,326],{"class":319},[238,702,281],{"class":270},[238,704,278],{"class":277},[238,706,281],{"class":270},[238,708,335],{"class":270},[238,710,274],{"class":270},[238,712,289],{"class":277},[238,714,281],{"class":270},[238,716,420],{"class":319},[238,718,423],{"class":270},[238,720,722,724,727,729,731,734,736,738,740,742,744,746,748,750],{"class":240,"line":721},21,[238,723,383],{"class":270},[238,725,726],{"class":386},"invitations.view",[238,728,281],{"class":270},[238,730,392],{"class":270},[238,732,733],{"class":319},"    [",[238,735,281],{"class":270},[238,737,278],{"class":277},[238,739,281],{"class":270},[238,741,335],{"class":270},[238,743,274],{"class":270},[238,745,289],{"class":277},[238,747,281],{"class":270},[238,749,420],{"class":319},[238,751,423],{"class":270},[238,753,755,757,760,762,764,767,769,771,773,775,777,779,781,783],{"class":240,"line":754},22,[238,756,383],{"class":270},[238,758,759],{"class":386},"invitations.revoke",[238,761,281],{"class":270},[238,763,392],{"class":270},[238,765,766],{"class":319},"  [",[238,768,281],{"class":270},[238,770,278],{"class":277},[238,772,281],{"class":270},[238,774,335],{"class":270},[238,776,274],{"class":270},[238,778,289],{"class":277},[238,780,281],{"class":270},[238,782,420],{"class":319},[238,784,423],{"class":270},[238,786,788,790,793,795,797,799,801,803,805,807],{"class":240,"line":787},23,[238,789,383],{"class":270},[238,791,792],{"class":386},"settings.team",[238,794,281],{"class":270},[238,796,392],{"class":270},[238,798,438],{"class":319},[238,800,281],{"class":270},[238,802,278],{"class":277},[238,804,281],{"class":270},[238,806,420],{"class":319},[238,808,423],{"class":270},[238,810,812,814,817,819,821,824,826,828,830,832,834,836,838,840,842,844,846,848],{"class":240,"line":811},24,[238,813,383],{"class":270},[238,815,816],{"class":386},"ai.chat",[238,818,281],{"class":270},[238,820,392],{"class":270},[238,822,823],{"class":319},"             [",[238,825,281],{"class":270},[238,827,278],{"class":277},[238,829,281],{"class":270},[238,831,335],{"class":270},[238,833,274],{"class":270},[238,835,289],{"class":277},[238,837,281],{"class":270},[238,839,335],{"class":270},[238,841,274],{"class":270},[238,843,298],{"class":277},[238,845,281],{"class":270},[238,847,420],{"class":319},[238,849,423],{"class":270},[238,851,853,855,858,860,862,864,866,868,870,872],{"class":240,"line":852},25,[238,854,383],{"class":270},[238,856,857],{"class":386},"activity.view",[238,859,281],{"class":270},[238,861,392],{"class":270},[238,863,438],{"class":319},[238,865,281],{"class":270},[238,867,278],{"class":277},[238,869,281],{"class":270},[238,871,420],{"class":319},[238,873,423],{"class":270},[238,875,877],{"class":240,"line":876},26,[238,878,252],{"emptyLinePlaceholder":251},[238,880,882,884,887,889,891,894,896,898,900,902,904,906,908,910,912,914,916,918],{"class":240,"line":881},27,[238,883,383],{"class":270},[238,885,886],{"class":386},"announcements.view",[238,888,281],{"class":270},[238,890,392],{"class":270},[238,892,893],{"class":319},"   [",[238,895,281],{"class":270},[238,897,278],{"class":277},[238,899,281],{"class":270},[238,901,335],{"class":270},[238,903,274],{"class":270},[238,905,289],{"class":277},[238,907,281],{"class":270},[238,909,335],{"class":270},[238,911,274],{"class":270},[238,913,298],{"class":277},[238,915,281],{"class":270},[238,917,420],{"class":319},[238,919,423],{"class":270},[238,921,923,925,928,930,932,934,936,938,940,942,944,946,948,950],{"class":240,"line":922},28,[238,924,383],{"class":270},[238,926,927],{"class":386},"announcements.create",[238,929,281],{"class":270},[238,931,392],{"class":270},[238,933,326],{"class":319},[238,935,281],{"class":270},[238,937,278],{"class":277},[238,939,281],{"class":270},[238,941,335],{"class":270},[238,943,274],{"class":270},[238,945,289],{"class":277},[238,947,281],{"class":270},[238,949,420],{"class":319},[238,951,423],{"class":270},[238,953,955,957,960,962,964,966,968,970,972,974,976,978,980,982],{"class":240,"line":954},29,[238,956,383],{"class":270},[238,958,959],{"class":386},"announcements.update",[238,961,281],{"class":270},[238,963,392],{"class":270},[238,965,326],{"class":319},[238,967,281],{"class":270},[238,969,278],{"class":277},[238,971,281],{"class":270},[238,973,335],{"class":270},[238,975,274],{"class":270},[238,977,289],{"class":277},[238,979,281],{"class":270},[238,981,420],{"class":319},[238,983,423],{"class":270},[238,985,987,989,992,994,996,998,1000,1002,1004,1006,1008,1010,1012,1014],{"class":240,"line":986},30,[238,988,383],{"class":270},[238,990,991],{"class":386},"announcements.delete",[238,993,281],{"class":270},[238,995,392],{"class":270},[238,997,326],{"class":319},[238,999,281],{"class":270},[238,1001,278],{"class":277},[238,1003,281],{"class":270},[238,1005,335],{"class":270},[238,1007,274],{"class":270},[238,1009,289],{"class":277},[238,1011,281],{"class":270},[238,1013,420],{"class":319},[238,1015,423],{"class":270},[238,1017,1019],{"class":240,"line":1018},31,[238,1020,252],{"emptyLinePlaceholder":251},[238,1022,1024],{"class":240,"line":1023},32,[238,1025,1026],{"class":244},"  \u002F\u002F Directory data readable by chat users.\n",[238,1028,1030,1032,1035,1037,1039,1041,1043,1045,1047,1049,1051,1053,1055,1057,1059,1061,1063,1065],{"class":240,"line":1029},33,[238,1031,383],{"class":270},[238,1033,1034],{"class":386},"profiles.view",[238,1036,281],{"class":270},[238,1038,392],{"class":270},[238,1040,395],{"class":319},[238,1042,281],{"class":270},[238,1044,278],{"class":277},[238,1046,281],{"class":270},[238,1048,335],{"class":270},[238,1050,274],{"class":270},[238,1052,289],{"class":277},[238,1054,281],{"class":270},[238,1056,335],{"class":270},[238,1058,274],{"class":270},[238,1060,298],{"class":277},[238,1062,281],{"class":270},[238,1064,420],{"class":319},[238,1066,423],{"class":270},[238,1068,1070,1072,1075,1077,1079,1081,1083,1085,1087,1089,1091,1093,1095,1097,1099,1101,1103,1105],{"class":240,"line":1069},34,[238,1071,383],{"class":270},[238,1073,1074],{"class":386},"team_members.view",[238,1076,281],{"class":270},[238,1078,392],{"class":270},[238,1080,766],{"class":319},[238,1082,281],{"class":270},[238,1084,278],{"class":277},[238,1086,281],{"class":270},[238,1088,335],{"class":270},[238,1090,274],{"class":270},[238,1092,289],{"class":277},[238,1094,281],{"class":270},[238,1096,335],{"class":270},[238,1098,274],{"class":270},[238,1100,298],{"class":277},[238,1102,281],{"class":270},[238,1104,420],{"class":319},[238,1106,423],{"class":270},[238,1108,1110],{"class":240,"line":1109},35,[238,1111,252],{"emptyLinePlaceholder":251},[238,1113,1115],{"class":240,"line":1114},36,[238,1116,1117],{"class":244},"  \u002F\u002F Shared empty role set — used as the create\u002Fupdate\u002Fdelete entry in\n",[238,1119,1121],{"class":240,"line":1120},37,[238,1122,1123],{"class":244},"  \u002F\u002F tablePermissions for any table the LLM may only read.\n",[238,1125,1127,1129,1132,1134,1136,1139],{"class":240,"line":1126},38,[238,1128,383],{"class":270},[238,1130,1131],{"class":386},"readonly.write",[238,1133,281],{"class":270},[238,1135,392],{"class":270},[238,1137,1138],{"class":319},"     []",[238,1140,423],{"class":270},[238,1142,1144,1147,1150,1152,1155,1158,1161,1164,1166,1169,1171,1174],{"class":240,"line":1143},39,[238,1145,1146],{"class":270},"}",[238,1148,1149],{"class":258}," as",[238,1151,316],{"class":266},[238,1153,1154],{"class":258}," satisfies",[238,1156,1157],{"class":266}," Record",[238,1159,1160],{"class":270},"\u003C",[238,1162,1163],{"class":266},"string",[238,1165,335],{"class":270},[238,1167,1168],{"class":262}," readonly",[238,1170,267],{"class":266},[238,1172,1173],{"class":319},"[]",[238,1175,1176],{"class":270},">;\n",[238,1178,1180],{"class":240,"line":1179},40,[238,1181,252],{"emptyLinePlaceholder":251},[238,1183,1185,1187,1189,1192,1194,1197,1200,1203],{"class":240,"line":1184},41,[238,1186,259],{"class":258},[238,1188,263],{"class":262},[238,1190,1191],{"class":266}," Permission",[238,1193,271],{"class":270},[238,1195,1196],{"class":270}," keyof",[238,1198,1199],{"class":270}," typeof",[238,1201,1202],{"class":319}," permissions",[238,1204,303],{"class":270},[1206,1207,1208,1218,1226],"ul",{},[1209,1210,1211,1217],"li",{},[1212,1213,1214],"strong",{},[199,1215,1216],{},"TeamRole"," — the union of all roles. Add new roles here.",[1209,1219,1220,1225],{},[1212,1221,1222],{},[199,1223,1224],{},"assignableRoles"," — roles that can be given via invitation or role\nchange. Drives the Zod schemas in API routes and the role selector in\nthe invite modal. \"owner\" is excluded because ownership is set at team\ncreation, not assigned.",[1209,1227,1228,1233,1234],{},[1212,1229,1230],{},[199,1231,1232],{},"permissions"," — the map has two kinds of entries:\n",[1206,1235,1236,1247],{},[1209,1237,1238,1243,1244,1246],{},[1212,1239,1240],{},[199,1241,1242],{},"visible.*"," — UI-only visibility gates. Use these with ",[199,1245,213],{},"\nwhen you want to show or hide a template element based on role without\ntying it to a specific action. They have no server-side enforcement.",[1209,1248,1249,1252,1253,1256,1257,1260],{},[1212,1250,1251],{},"Action keys"," (everything else) — enforced server-side via ",[199,1254,1255],{},"authUser()",".\nThe dot-separated names are a convention (",[199,1258,1259],{},"resource.action","), not a\nframework feature. Each key is manually referenced in the API route that\nperforms that action.",[224,1262,1264],{"id":1263},"how-permissions-are-enforced","How permissions are enforced",[195,1266,1267],{},"Permission keys are just strings in a lookup table. Nothing automatically maps\nthem to database tables or operations. A permission only does something when\ncode explicitly references it:",[1206,1269,1270,1283,1293],{},[1209,1271,1272,1275,1276,1279,1280,1282],{},[1212,1273,1274],{},"Server-side",": A developer writes ",[199,1277,1278],{},"await authUser(event, \"tasks.delete\")","\nat the top of an API route. ",[199,1281,205],{}," looks up the key in the permissions\nmap, checks if the user's role is in the allowed list, and throws 403 if not.",[1209,1284,1285,1288,1289,1292],{},[1212,1286,1287],{},"Route-level",": ",[199,1290,1291],{},"definePageMeta({ middleware: requirePermission(\"tasks.view\") })","\nredirects before navigation, so users without access never see a flash of\npage content.",[1209,1294,1295,1288,1298,1301,1302,1305,1306,1309],{},[1212,1296,1297],{},"Client-side",[199,1299,1300],{},"\u003CCanAccess permission=\"tasks.delete\">"," hides UI elements\nthe user cannot act on, and ",[199,1303,1304],{},"can(\"tasks.delete\")"," from ",[199,1307,1308],{},"useUserRole()"," gates\ncomputed values and conditional logic. This is a UX convenience — even if\nbypassed, the server rejects the request.",[195,1311,1312,1313,1315,1316,423,1318,1320],{},"The ",[199,1314,1259],{}," naming convention (e.g. ",[199,1317,662],{},[199,1319,565],{},") is just for readability. There is no framework parsing these\nstrings.",[224,1322,1324],{"id":1323},"adding-permissions-for-a-new-resource","Adding permissions for a new resource",[195,1326,1327],{},"When you add a new table with CRUD endpoints, add one permission key per\nendpoint and reference it in the corresponding route:",[1329,1330,1331,1468,1636,1681],"ol",{},[1209,1332,1333,1334,1336,1337],{},"Add keys to ",[199,1335,201],{},":\n",[229,1338,1340],{"className":231,"code":1339,"language":233,"meta":234,"style":234},"\"tasks.view\":   [\"owner\", \"admin\", \"member\"],\n\"tasks.create\": [\"owner\", \"admin\", \"member\"],\n\"tasks.update\": [\"owner\", \"admin\"],\n\"tasks.delete\": [\"owner\"],\n",[199,1341,1342,1380,1418,1447],{"__ignoreMap":234},[238,1343,1344,1346,1349,1351,1354,1356,1358,1360,1362,1364,1366,1368,1370,1372,1374,1376,1378],{"class":240,"line":241},[238,1345,281],{"class":270},[238,1347,1348],{"class":277},"tasks.view",[238,1350,281],{"class":270},[238,1352,1353],{"class":319},":   [",[238,1355,281],{"class":270},[238,1357,278],{"class":277},[238,1359,281],{"class":270},[238,1361,335],{"class":270},[238,1363,274],{"class":270},[238,1365,289],{"class":277},[238,1367,281],{"class":270},[238,1369,335],{"class":270},[238,1371,274],{"class":270},[238,1373,298],{"class":277},[238,1375,281],{"class":270},[238,1377,420],{"class":319},[238,1379,423],{"class":270},[238,1381,1382,1384,1387,1389,1392,1394,1396,1398,1400,1402,1404,1406,1408,1410,1412,1414,1416],{"class":240,"line":248},[238,1383,281],{"class":270},[238,1385,1386],{"class":277},"tasks.create",[238,1388,281],{"class":270},[238,1390,1391],{"class":319},": [",[238,1393,281],{"class":270},[238,1395,278],{"class":277},[238,1397,281],{"class":270},[238,1399,335],{"class":270},[238,1401,274],{"class":270},[238,1403,289],{"class":277},[238,1405,281],{"class":270},[238,1407,335],{"class":270},[238,1409,274],{"class":270},[238,1411,298],{"class":277},[238,1413,281],{"class":270},[238,1415,420],{"class":319},[238,1417,423],{"class":270},[238,1419,1420,1422,1425,1427,1429,1431,1433,1435,1437,1439,1441,1443,1445],{"class":240,"line":255},[238,1421,281],{"class":270},[238,1423,1424],{"class":277},"tasks.update",[238,1426,281],{"class":270},[238,1428,1391],{"class":319},[238,1430,281],{"class":270},[238,1432,278],{"class":277},[238,1434,281],{"class":270},[238,1436,335],{"class":270},[238,1438,274],{"class":270},[238,1440,289],{"class":277},[238,1442,281],{"class":270},[238,1444,420],{"class":319},[238,1446,423],{"class":270},[238,1448,1449,1451,1454,1456,1458,1460,1462,1464,1466],{"class":240,"line":306},[238,1450,281],{"class":270},[238,1452,1453],{"class":277},"tasks.delete",[238,1455,281],{"class":270},[238,1457,1391],{"class":319},[238,1459,281],{"class":270},[238,1461,278],{"class":277},[238,1463,281],{"class":270},[238,1465,420],{"class":319},[238,1467,423],{"class":270},[1209,1469,1470,1471],{},"Gate each API route:\n",[229,1472,1474],{"className":231,"code":1473,"language":233,"meta":234,"style":234},"\u002F\u002F server\u002Fapi\u002Fteams\u002F[teamId]\u002Ftasks\u002Findex.get.ts\nconst { client } = await authUser(event, \"tasks.view\");\n\n\u002F\u002F server\u002Fapi\u002Fteams\u002F[teamId]\u002Ftasks\u002Findex.post.ts\nconst { client } = await authUser(event, \"tasks.create\");\n\n\u002F\u002F server\u002Fapi\u002Fteams\u002F[teamId]\u002Ftasks\u002F[taskId].patch.ts\nconst { client } = await authUser(event, \"tasks.update\");\n\n\u002F\u002F server\u002Fapi\u002Fteams\u002F[teamId]\u002Ftasks\u002F[taskId].delete.ts\nconst { client } = await authUser(event, \"tasks.delete\");\n",[199,1475,1476,1481,1519,1523,1528,1558,1562,1567,1597,1601,1606],{"__ignoreMap":234},[238,1477,1478],{"class":240,"line":241},[238,1479,1480],{"class":244},"\u002F\u002F server\u002Fapi\u002Fteams\u002F[teamId]\u002Ftasks\u002Findex.get.ts\n",[238,1482,1483,1486,1489,1492,1494,1496,1499,1503,1506,1508,1510,1512,1514,1517],{"class":240,"line":248},[238,1484,1485],{"class":262},"const",[238,1487,1488],{"class":270}," {",[238,1490,1491],{"class":319}," client ",[238,1493,1146],{"class":270},[238,1495,271],{"class":270},[238,1497,1498],{"class":258}," await",[238,1500,1502],{"class":1501},"s2Zo4"," authUser",[238,1504,1505],{"class":319},"(event",[238,1507,335],{"class":270},[238,1509,274],{"class":270},[238,1511,1348],{"class":277},[238,1513,281],{"class":270},[238,1515,1516],{"class":319},")",[238,1518,303],{"class":270},[238,1520,1521],{"class":240,"line":255},[238,1522,252],{"emptyLinePlaceholder":251},[238,1524,1525],{"class":240,"line":306},[238,1526,1527],{"class":244},"\u002F\u002F server\u002Fapi\u002Fteams\u002F[teamId]\u002Ftasks\u002Findex.post.ts\n",[238,1529,1530,1532,1534,1536,1538,1540,1542,1544,1546,1548,1550,1552,1554,1556],{"class":240,"line":311},[238,1531,1485],{"class":262},[238,1533,1488],{"class":270},[238,1535,1491],{"class":319},[238,1537,1146],{"class":270},[238,1539,271],{"class":270},[238,1541,1498],{"class":258},[238,1543,1502],{"class":1501},[238,1545,1505],{"class":319},[238,1547,335],{"class":270},[238,1549,274],{"class":270},[238,1551,1386],{"class":277},[238,1553,281],{"class":270},[238,1555,1516],{"class":319},[238,1557,303],{"class":270},[238,1559,1560],{"class":240,"line":354},[238,1561,252],{"emptyLinePlaceholder":251},[238,1563,1564],{"class":240,"line":359},[238,1565,1566],{"class":244},"\u002F\u002F server\u002Fapi\u002Fteams\u002F[teamId]\u002Ftasks\u002F[taskId].patch.ts\n",[238,1568,1569,1571,1573,1575,1577,1579,1581,1583,1585,1587,1589,1591,1593,1595],{"class":240,"line":374},[238,1570,1485],{"class":262},[238,1572,1488],{"class":270},[238,1574,1491],{"class":319},[238,1576,1146],{"class":270},[238,1578,271],{"class":270},[238,1580,1498],{"class":258},[238,1582,1502],{"class":1501},[238,1584,1505],{"class":319},[238,1586,335],{"class":270},[238,1588,274],{"class":270},[238,1590,1424],{"class":277},[238,1592,281],{"class":270},[238,1594,1516],{"class":319},[238,1596,303],{"class":270},[238,1598,1599],{"class":240,"line":380},[238,1600,252],{"emptyLinePlaceholder":251},[238,1602,1603],{"class":240,"line":426},[238,1604,1605],{"class":244},"\u002F\u002F server\u002Fapi\u002Fteams\u002F[teamId]\u002Ftasks\u002F[taskId].delete.ts\n",[238,1607,1608,1610,1612,1614,1616,1618,1620,1622,1624,1626,1628,1630,1632,1634],{"class":240,"line":459},[238,1609,1485],{"class":262},[238,1611,1488],{"class":270},[238,1613,1491],{"class":319},[238,1615,1146],{"class":270},[238,1617,271],{"class":270},[238,1619,1498],{"class":258},[238,1621,1502],{"class":1501},[238,1623,1505],{"class":319},[238,1625,335],{"class":270},[238,1627,274],{"class":270},[238,1629,1453],{"class":277},[238,1631,281],{"class":270},[238,1633,1516],{"class":319},[238,1635,303],{"class":270},[1209,1637,1638,1639],{},"Gate UI elements:\n",[229,1640,1644],{"className":1641,"code":1642,"language":1643,"meta":234,"style":234},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003CCanAccess permission=\"tasks.delete\">\n  \u003CUButton label=\"Delete\" @click=\"confirmDelete(task)\" \u002F>\n\u003C\u002FCanAccess>\n","vue",[199,1645,1646,1667,1672],{"__ignoreMap":234},[238,1647,1648,1650,1653,1656,1658,1660,1662,1664],{"class":240,"line":241},[238,1649,1160],{"class":270},[238,1651,1652],{"class":386},"CanAccess",[238,1654,1655],{"class":262}," permission",[238,1657,323],{"class":270},[238,1659,281],{"class":270},[238,1661,1453],{"class":277},[238,1663,281],{"class":270},[238,1665,1666],{"class":270},">\n",[238,1668,1669],{"class":240,"line":248},[238,1670,1671],{"class":319},"  \u003CUButton label=\"Delete\" @click=\"confirmDelete(task)\" \u002F>\n",[238,1673,1674,1677,1679],{"class":240,"line":255},[238,1675,1676],{"class":270},"\u003C\u002F",[238,1678,1652],{"class":386},[238,1680,1666],{"class":270},[1209,1682,1683,1684,1686,1687,1692],{},"(Optional) Register the table in ",[199,1685,221],{}," so the AI chat can\nread and write it — see ",[1688,1689,1691],"a",{"href":1690},"#exposing-tables-to-ai-chat","Exposing tables to AI chat","\nbelow.",[224,1694,1696],{"id":1695},"adding-a-new-role","Adding a new role",[195,1698,1699,1700,1703],{},"To add a ",[199,1701,1702],{},"\"viewer\""," role that can only view teams and members:",[1329,1705,1706,1715,1720],{},[1209,1707,1708,1709,1711,1712,1714],{},"Add ",[199,1710,1702],{}," to the ",[199,1713,1216],{}," union.",[1209,1716,1708,1717,1719],{},[199,1718,1702],{}," to the permission entries it should have access to.",[1209,1721,1722,1723,1725],{},"If the role can be assigned via invitation, add it to ",[199,1724,1224],{},".",[229,1727,1729],{"className":231,"code":1728,"language":233,"meta":234,"style":234},"export type TeamRole = \"owner\" | \"admin\" | \"member\" | \"viewer\";\n\nexport const assignableRoles = [\"admin\", \"member\", \"viewer\"] as const;\n\nexport const permissions = {\n  \"team.view\":    [\"owner\", \"admin\", \"member\", \"viewer\"],\n  \"members.view\": [\"owner\", \"admin\", \"member\", \"viewer\"],\n  \u002F\u002F ... everything else stays the same\n} as const satisfies Record\u003Cstring, readonly TeamRole[]>;\n",[199,1730,1731,1774,1778,1820,1824,1836,1882,1928,1933],{"__ignoreMap":234},[238,1732,1733,1735,1737,1739,1741,1743,1745,1747,1749,1751,1753,1755,1757,1759,1761,1763,1765,1767,1770,1772],{"class":240,"line":241},[238,1734,259],{"class":258},[238,1736,263],{"class":262},[238,1738,267],{"class":266},[238,1740,271],{"class":270},[238,1742,274],{"class":270},[238,1744,278],{"class":277},[238,1746,281],{"class":270},[238,1748,284],{"class":270},[238,1750,274],{"class":270},[238,1752,289],{"class":277},[238,1754,281],{"class":270},[238,1756,284],{"class":270},[238,1758,274],{"class":270},[238,1760,298],{"class":277},[238,1762,281],{"class":270},[238,1764,284],{"class":270},[238,1766,274],{"class":270},[238,1768,1769],{"class":277},"viewer",[238,1771,281],{"class":270},[238,1773,303],{"class":270},[238,1775,1776],{"class":240,"line":248},[238,1777,252],{"emptyLinePlaceholder":251},[238,1779,1780,1782,1784,1786,1788,1790,1792,1794,1796,1798,1800,1802,1804,1806,1808,1810,1812,1814,1816,1818],{"class":240,"line":255},[238,1781,259],{"class":258},[238,1783,316],{"class":262},[238,1785,320],{"class":319},[238,1787,323],{"class":270},[238,1789,326],{"class":319},[238,1791,281],{"class":270},[238,1793,289],{"class":277},[238,1795,281],{"class":270},[238,1797,335],{"class":270},[238,1799,274],{"class":270},[238,1801,298],{"class":277},[238,1803,281],{"class":270},[238,1805,335],{"class":270},[238,1807,274],{"class":270},[238,1809,1769],{"class":277},[238,1811,281],{"class":270},[238,1813,344],{"class":319},[238,1815,347],{"class":258},[238,1817,316],{"class":262},[238,1819,303],{"class":270},[238,1821,1822],{"class":240,"line":306},[238,1823,252],{"emptyLinePlaceholder":251},[238,1825,1826,1828,1830,1832,1834],{"class":240,"line":311},[238,1827,259],{"class":258},[238,1829,316],{"class":262},[238,1831,366],{"class":319},[238,1833,323],{"class":270},[238,1835,371],{"class":270},[238,1837,1838,1840,1842,1844,1846,1848,1850,1852,1854,1856,1858,1860,1862,1864,1866,1868,1870,1872,1874,1876,1878,1880],{"class":240,"line":354},[238,1839,383],{"class":270},[238,1841,499],{"class":386},[238,1843,281],{"class":270},[238,1845,392],{"class":270},[238,1847,733],{"class":319},[238,1849,281],{"class":270},[238,1851,278],{"class":277},[238,1853,281],{"class":270},[238,1855,335],{"class":270},[238,1857,274],{"class":270},[238,1859,289],{"class":277},[238,1861,281],{"class":270},[238,1863,335],{"class":270},[238,1865,274],{"class":270},[238,1867,298],{"class":277},[238,1869,281],{"class":270},[238,1871,335],{"class":270},[238,1873,274],{"class":270},[238,1875,1769],{"class":277},[238,1877,281],{"class":270},[238,1879,420],{"class":319},[238,1881,423],{"class":270},[238,1883,1884,1886,1888,1890,1892,1894,1896,1898,1900,1902,1904,1906,1908,1910,1912,1914,1916,1918,1920,1922,1924,1926],{"class":240,"line":359},[238,1885,383],{"class":270},[238,1887,589],{"class":386},[238,1889,281],{"class":270},[238,1891,392],{"class":270},[238,1893,326],{"class":319},[238,1895,281],{"class":270},[238,1897,278],{"class":277},[238,1899,281],{"class":270},[238,1901,335],{"class":270},[238,1903,274],{"class":270},[238,1905,289],{"class":277},[238,1907,281],{"class":270},[238,1909,335],{"class":270},[238,1911,274],{"class":270},[238,1913,298],{"class":277},[238,1915,281],{"class":270},[238,1917,335],{"class":270},[238,1919,274],{"class":270},[238,1921,1769],{"class":277},[238,1923,281],{"class":270},[238,1925,420],{"class":319},[238,1927,423],{"class":270},[238,1929,1930],{"class":240,"line":374},[238,1931,1932],{"class":244},"  \u002F\u002F ... everything else stays the same\n",[238,1934,1935,1937,1939,1941,1943,1945,1947,1949,1951,1953,1955,1957],{"class":240,"line":380},[238,1936,1146],{"class":270},[238,1938,1149],{"class":258},[238,1940,316],{"class":266},[238,1942,1154],{"class":258},[238,1944,1157],{"class":266},[238,1946,1160],{"class":270},[238,1948,1163],{"class":266},[238,1950,335],{"class":270},[238,1952,1168],{"class":262},[238,1954,267],{"class":266},[238,1956,1173],{"class":319},[238,1958,1176],{"class":270},[195,1960,1961],{},"No other files need to change. The server will enforce the new role on API\nrequests, the UI will show\u002Fhide elements accordingly, and the invite modal\nwill offer the new role as an option.",[224,1963,1965,1966],{"id":1964},"server-side-authuserevent-permission","Server-side: ",[199,1967,1968],{},"authUser(event, permission)",[195,1970,1971,1972,1974],{},"Every protected API route calls ",[199,1973,205],{}," with a permission key. The\nfunction verifies the JWT, confirms team membership, and checks that the\nuser's role is in the permission's allowed list. If not, it throws a 403.\nIt returns an audited Supabase client — every write goes through the\nactivity log automatically.",[229,1976,1978],{"className":231,"code":1977,"language":233,"meta":234,"style":234},"\u002F\u002F server\u002Fapi\u002Fteams\u002F[teamId]\u002Findex.patch.ts\nexport default defineEventHandler(async (event) => {\n  const { client, profile, teamMember } = await authUser(event, \"team.update\");\n  \u002F\u002F ... only owners reach this point\n});\n",[199,1979,1980,1985,2015,2060,2065],{"__ignoreMap":234},[238,1981,1982],{"class":240,"line":241},[238,1983,1984],{"class":244},"\u002F\u002F server\u002Fapi\u002Fteams\u002F[teamId]\u002Findex.patch.ts\n",[238,1986,1987,1989,1992,1995,1998,2001,2004,2008,2010,2013],{"class":240,"line":248},[238,1988,259],{"class":258},[238,1990,1991],{"class":258}," default",[238,1993,1994],{"class":1501}," defineEventHandler",[238,1996,1997],{"class":319},"(",[238,1999,2000],{"class":262},"async",[238,2002,2003],{"class":270}," (",[238,2005,2007],{"class":2006},"sHdIc","event",[238,2009,1516],{"class":270},[238,2011,2012],{"class":262}," =>",[238,2014,371],{"class":270},[238,2016,2017,2020,2022,2025,2027,2030,2032,2035,2038,2040,2042,2044,2046,2048,2050,2052,2054,2056,2058],{"class":240,"line":255},[238,2018,2019],{"class":262},"  const",[238,2021,1488],{"class":270},[238,2023,2024],{"class":319}," client",[238,2026,335],{"class":270},[238,2028,2029],{"class":319}," profile",[238,2031,335],{"class":270},[238,2033,2034],{"class":319}," teamMember",[238,2036,2037],{"class":270}," }",[238,2039,271],{"class":270},[238,2041,1498],{"class":258},[238,2043,1502],{"class":1501},[238,2045,1997],{"class":386},[238,2047,2007],{"class":319},[238,2049,335],{"class":270},[238,2051,274],{"class":270},[238,2053,540],{"class":277},[238,2055,281],{"class":270},[238,2057,1516],{"class":386},[238,2059,303],{"class":270},[238,2061,2062],{"class":240,"line":306},[238,2063,2064],{"class":244},"  \u002F\u002F ... only owners reach this point\n",[238,2066,2067,2069,2071],{"class":240,"line":311},[238,2068,1146],{"class":270},[238,2070,1516],{"class":319},[238,2072,303],{"class":270},[195,2074,2075,2076,2079],{},"For routes that do not need team context (e.g., profile updates, team\ncreation), use ",[199,2077,2078],{},"authUserOnly(event)"," which only verifies the JWT.",[224,2081,2083,2084],{"id":2082},"route-middleware-requirepermissionkey-fallback","Route middleware: ",[199,2085,2086],{},"requirePermission(key, fallback?)",[195,2088,2089,2090,2092,2093,2096],{},"For pages whose entire content requires a permission, use the\n",[199,2091,217],{}," middleware in ",[199,2094,2095],{},"app\u002Futils\u002FrequirePermission.ts",". It runs\nbefore navigation and redirects users without access, so they never see a\nflash of page content.",[229,2098,2100],{"className":231,"code":2099,"language":233,"meta":234,"style":234},"\u002F\u002F app\u002Fpages\u002Fapp\u002Fsettings\u002Fteam.vue\n\u003Cscript setup lang=\"ts\">\ndefinePageMeta({\n  middleware: requirePermission(\"settings.team\"),\n});\n\u003C\u002Fscript>\n",[199,2101,2102,2107,2124,2134,2156,2164],{"__ignoreMap":234},[238,2103,2104],{"class":240,"line":241},[238,2105,2106],{"class":244},"\u002F\u002F app\u002Fpages\u002Fapp\u002Fsettings\u002Fteam.vue\n",[238,2108,2109,2111,2114,2116,2118,2120,2122],{"class":240,"line":248},[238,2110,1160],{"class":270},[238,2112,2113],{"class":319},"script setup lang",[238,2115,323],{"class":270},[238,2117,281],{"class":270},[238,2119,233],{"class":277},[238,2121,281],{"class":270},[238,2123,1666],{"class":270},[238,2125,2126,2129,2131],{"class":240,"line":255},[238,2127,2128],{"class":1501},"definePageMeta",[238,2130,1997],{"class":319},[238,2132,2133],{"class":270},"{\n",[238,2135,2136,2139,2141,2144,2146,2148,2150,2152,2154],{"class":240,"line":306},[238,2137,2138],{"class":386},"  middleware",[238,2140,392],{"class":270},[238,2142,2143],{"class":1501}," requirePermission",[238,2145,1997],{"class":319},[238,2147,281],{"class":270},[238,2149,792],{"class":277},[238,2151,281],{"class":270},[238,2153,1516],{"class":319},[238,2155,423],{"class":270},[238,2157,2158,2160,2162],{"class":240,"line":311},[238,2159,1146],{"class":270},[238,2161,1516],{"class":319},[238,2163,303],{"class":270},[238,2165,2166,2168,2171],{"class":240,"line":354},[238,2167,1676],{"class":270},[238,2169,2170],{"class":319},"script",[238,2172,1666],{"class":270},[195,2174,2175,2176,2179],{},"Fallback defaults to ",[199,2177,2178],{},"\u002Fapp",". Pass a second argument to override:",[229,2181,2183],{"className":231,"code":2182,"language":233,"meta":234,"style":234},"definePageMeta({\n  middleware: requirePermission(\"activity.view\", \"\u002Fapp\u002Fsettings\"),\n});\n",[199,2184,2185,2193,2222],{"__ignoreMap":234},[238,2186,2187,2189,2191],{"class":240,"line":241},[238,2188,2128],{"class":1501},[238,2190,1997],{"class":319},[238,2192,2133],{"class":270},[238,2194,2195,2197,2199,2201,2203,2205,2207,2209,2211,2213,2216,2218,2220],{"class":240,"line":248},[238,2196,2138],{"class":386},[238,2198,392],{"class":270},[238,2200,2143],{"class":1501},[238,2202,1997],{"class":319},[238,2204,281],{"class":270},[238,2206,857],{"class":277},[238,2208,281],{"class":270},[238,2210,335],{"class":270},[238,2212,274],{"class":270},[238,2214,2215],{"class":277},"\u002Fapp\u002Fsettings",[238,2217,281],{"class":270},[238,2219,1516],{"class":319},[238,2221,423],{"class":270},[238,2223,2224,2226,2228],{"class":240,"line":255},[238,2225,1146],{"class":270},[238,2227,1516],{"class":319},[238,2229,303],{"class":270},[195,2231,2232],{},"Prefer this over a client-side watcher that redirects after mount — the\nmiddleware runs before the page component loads.",[224,2234,2236,2237,2239],{"id":2235},"client-side-canaccess-component","Client-side: ",[199,2238,213],{}," component",[195,2241,2242,2244,2245,2248],{},[199,2243,213],{}," is a renderless component that shows its default slot when the\nuser has the given permission, and an optional ",[199,2246,2247],{},"fallback"," slot when they\ndo not.",[195,2250,2251],{},[1212,2252,2253],{},"Hide an element entirely:",[229,2255,2257],{"className":1641,"code":2256,"language":1643,"meta":234,"style":234},"\u003CCanAccess permission=\"members.invite\">\n  \u003CUButton label=\"Invite people\" @click=\"inviteOpen = true\" \u002F>\n\u003C\u002FCanAccess>\n",[199,2258,2259,2277,2282],{"__ignoreMap":234},[238,2260,2261,2263,2265,2267,2269,2271,2273,2275],{"class":240,"line":241},[238,2262,1160],{"class":270},[238,2264,1652],{"class":386},[238,2266,1655],{"class":262},[238,2268,323],{"class":270},[238,2270,281],{"class":270},[238,2272,630],{"class":277},[238,2274,281],{"class":270},[238,2276,1666],{"class":270},[238,2278,2279],{"class":240,"line":248},[238,2280,2281],{"class":319},"  \u003CUButton label=\"Invite people\" @click=\"inviteOpen = true\" \u002F>\n",[238,2283,2284,2286,2288],{"class":240,"line":255},[238,2285,1676],{"class":270},[238,2287,1652],{"class":386},[238,2289,1666],{"class":270},[195,2291,2292],{},[1212,2293,2294],{},"Show a read-only fallback instead:",[229,2296,2298],{"className":1641,"code":2297,"language":1643,"meta":234,"style":234},"\u003CCanAccess permission=\"members.role.change\">\n  \u003CUSelect :model-value=\"member.role\" :items=\"[...assignableRoles]\" @update:model-value=\"changeRole(member, $event)\" \u002F>\n  \u003Ctemplate #fallback>\n    \u003CUBadge :label=\"member.role\" color=\"neutral\" variant=\"subtle\" \u002F>\n  \u003C\u002Ftemplate>\n\u003C\u002FCanAccess>\n",[199,2299,2300,2318,2323,2328,2333,2338],{"__ignoreMap":234},[238,2301,2302,2304,2306,2308,2310,2312,2314,2316],{"class":240,"line":241},[238,2303,1160],{"class":270},[238,2305,1652],{"class":386},[238,2307,1655],{"class":262},[238,2309,323],{"class":270},[238,2311,281],{"class":270},[238,2313,694],{"class":277},[238,2315,281],{"class":270},[238,2317,1666],{"class":270},[238,2319,2320],{"class":240,"line":248},[238,2321,2322],{"class":319},"  \u003CUSelect :model-value=\"member.role\" :items=\"[...assignableRoles]\" @update:model-value=\"changeRole(member, $event)\" \u002F>\n",[238,2324,2325],{"class":240,"line":255},[238,2326,2327],{"class":319},"  \u003Ctemplate #fallback>\n",[238,2329,2330],{"class":240,"line":306},[238,2331,2332],{"class":319},"    \u003CUBadge :label=\"member.role\" color=\"neutral\" variant=\"subtle\" \u002F>\n",[238,2334,2335],{"class":240,"line":311},[238,2336,2337],{"class":319},"  \u003C\u002Ftemplate>\n",[238,2339,2340,2342,2344],{"class":240,"line":354},[238,2341,1676],{"class":270},[238,2343,1652],{"class":386},[238,2345,1666],{"class":270},[195,2347,2348],{},[1212,2349,2350,2351,2354],{},"With ",[199,2352,2353],{},"v-if"," on the component (for owner-specific rendering):",[229,2356,2358],{"className":1641,"code":2357,"language":1643,"meta":234,"style":234},"\u003CCanAccess v-if=\"member.role !== 'owner'\" permission=\"members.remove\">\n  \u003CUButton label=\"Remove\" @click=\"confirmRemove(member)\" \u002F>\n\u003C\u002FCanAccess>\n",[199,2359,2360,2403,2408],{"__ignoreMap":234},[238,2361,2362,2364,2366,2369,2371,2373,2375,2377,2380,2383,2386,2388,2391,2393,2395,2397,2399,2401],{"class":240,"line":241},[238,2363,1160],{"class":270},[238,2365,1652],{"class":386},[238,2367,2368],{"class":258}," v-if",[238,2370,323],{"class":270},[238,2372,281],{"class":270},[238,2374,298],{"class":319},[238,2376,1725],{"class":270},[238,2378,2379],{"class":319},"role ",[238,2381,2382],{"class":270},"!==",[238,2384,2385],{"class":270}," '",[238,2387,278],{"class":277},[238,2389,2390],{"class":270},"'\"",[238,2392,1655],{"class":262},[238,2394,323],{"class":270},[238,2396,281],{"class":270},[238,2398,662],{"class":277},[238,2400,281],{"class":270},[238,2402,1666],{"class":270},[238,2404,2405],{"class":240,"line":248},[238,2406,2407],{"class":319},"  \u003CUButton label=\"Remove\" @click=\"confirmRemove(member)\" \u002F>\n",[238,2409,2410,2412,2414],{"class":240,"line":255},[238,2411,1676],{"class":270},[238,2413,1652],{"class":386},[238,2415,1666],{"class":270},[224,2417,2236,2419,2422],{"id":2418},"client-side-can-in-scripts",[199,2420,2421],{},"can()"," in scripts",[195,2424,2425,2426,2429,2430,2433,2434,2436,2437,1725],{},"For logic that runs in ",[199,2427,2428],{},"\u003Cscript setup>"," — computed properties, conditional\nnav items, predicate gating for ",[199,2431,2432],{},"useFetch"," — use the ",[199,2435,2421],{}," function from\n",[199,2438,1308],{},[229,2440,2442],{"className":231,"code":2441,"language":233,"meta":234,"style":234},"const { role, can } = useUserRole();\n\n\u002F\u002F Gate a useFetch at setup using a synchronous predicate (SSR-safe, because\n\u002F\u002F useTeam\u002FuseUserRole are SSR-hydrated).\nconst { data: invites, status, refresh } = await useFetch(\"\u002Fapi\u002Finvitations\", {\n  key: \"invitations\",\n  default: () => [],\n  immediate: can(\"invitations.view\"),\n  getCachedData,\n});\nrevalidateOnMount(status, refresh);\n\n\u002F\u002F Conditionally include a nav item\nif (can(\"settings.team\")) {\n  items.push({ label: \"Team\", to: \"\u002Fapp\u002Fsettings\u002Fteam\" });\n}\n",[199,2443,2444,2470,2474,2479,2484,2530,2546,2563,2585,2592,2600,2615,2619,2624,2647,2694],{"__ignoreMap":234},[238,2445,2446,2448,2450,2453,2455,2458,2460,2462,2465,2468],{"class":240,"line":241},[238,2447,1485],{"class":262},[238,2449,1488],{"class":270},[238,2451,2452],{"class":319}," role",[238,2454,335],{"class":270},[238,2456,2457],{"class":319}," can ",[238,2459,1146],{"class":270},[238,2461,271],{"class":270},[238,2463,2464],{"class":1501}," useUserRole",[238,2466,2467],{"class":319},"()",[238,2469,303],{"class":270},[238,2471,2472],{"class":240,"line":248},[238,2473,252],{"emptyLinePlaceholder":251},[238,2475,2476],{"class":240,"line":255},[238,2477,2478],{"class":244},"\u002F\u002F Gate a useFetch at setup using a synchronous predicate (SSR-safe, because\n",[238,2480,2481],{"class":240,"line":306},[238,2482,2483],{"class":244},"\u002F\u002F useTeam\u002FuseUserRole are SSR-hydrated).\n",[238,2485,2486,2488,2490,2493,2495,2498,2500,2503,2505,2508,2510,2512,2514,2517,2519,2521,2524,2526,2528],{"class":240,"line":311},[238,2487,1485],{"class":262},[238,2489,1488],{"class":270},[238,2491,2492],{"class":386}," data",[238,2494,392],{"class":270},[238,2496,2497],{"class":319}," invites",[238,2499,335],{"class":270},[238,2501,2502],{"class":319}," status",[238,2504,335],{"class":270},[238,2506,2507],{"class":319}," refresh ",[238,2509,1146],{"class":270},[238,2511,271],{"class":270},[238,2513,1498],{"class":258},[238,2515,2516],{"class":1501}," useFetch",[238,2518,1997],{"class":319},[238,2520,281],{"class":270},[238,2522,2523],{"class":277},"\u002Fapi\u002Finvitations",[238,2525,281],{"class":270},[238,2527,335],{"class":270},[238,2529,371],{"class":270},[238,2531,2532,2535,2537,2539,2542,2544],{"class":240,"line":354},[238,2533,2534],{"class":386},"  key",[238,2536,392],{"class":270},[238,2538,274],{"class":270},[238,2540,2541],{"class":277},"invitations",[238,2543,281],{"class":270},[238,2545,423],{"class":270},[238,2547,2548,2551,2553,2556,2558,2561],{"class":240,"line":359},[238,2549,2550],{"class":1501},"  default",[238,2552,392],{"class":270},[238,2554,2555],{"class":270}," ()",[238,2557,2012],{"class":262},[238,2559,2560],{"class":319}," []",[238,2562,423],{"class":270},[238,2564,2565,2568,2570,2573,2575,2577,2579,2581,2583],{"class":240,"line":374},[238,2566,2567],{"class":386},"  immediate",[238,2569,392],{"class":270},[238,2571,2572],{"class":1501}," can",[238,2574,1997],{"class":319},[238,2576,281],{"class":270},[238,2578,726],{"class":277},[238,2580,281],{"class":270},[238,2582,1516],{"class":319},[238,2584,423],{"class":270},[238,2586,2587,2590],{"class":240,"line":380},[238,2588,2589],{"class":319},"  getCachedData",[238,2591,423],{"class":270},[238,2593,2594,2596,2598],{"class":240,"line":426},[238,2595,1146],{"class":270},[238,2597,1516],{"class":319},[238,2599,303],{"class":270},[238,2601,2602,2605,2608,2610,2613],{"class":240,"line":459},[238,2603,2604],{"class":1501},"revalidateOnMount",[238,2606,2607],{"class":319},"(status",[238,2609,335],{"class":270},[238,2611,2612],{"class":319}," refresh)",[238,2614,303],{"class":270},[238,2616,2617],{"class":240,"line":483},[238,2618,252],{"emptyLinePlaceholder":251},[238,2620,2621],{"class":240,"line":488},[238,2622,2623],{"class":244},"\u002F\u002F Conditionally include a nav item\n",[238,2625,2626,2629,2631,2634,2636,2638,2640,2642,2645],{"class":240,"line":494},[238,2627,2628],{"class":258},"if",[238,2630,2003],{"class":319},[238,2632,2633],{"class":1501},"can",[238,2635,1997],{"class":319},[238,2637,281],{"class":270},[238,2639,792],{"class":277},[238,2641,281],{"class":270},[238,2643,2644],{"class":319},")) ",[238,2646,2133],{"class":270},[238,2648,2649,2652,2654,2657,2659,2662,2665,2667,2669,2672,2674,2676,2679,2681,2683,2686,2688,2690,2692],{"class":240,"line":535},[238,2650,2651],{"class":319},"  items",[238,2653,1725],{"class":270},[238,2655,2656],{"class":1501},"push",[238,2658,1997],{"class":386},[238,2660,2661],{"class":270},"{",[238,2663,2664],{"class":386}," label",[238,2666,392],{"class":270},[238,2668,274],{"class":270},[238,2670,2671],{"class":277},"Team",[238,2673,281],{"class":270},[238,2675,335],{"class":270},[238,2677,2678],{"class":386}," to",[238,2680,392],{"class":270},[238,2682,274],{"class":270},[238,2684,2685],{"class":277},"\u002Fapp\u002Fsettings\u002Fteam",[238,2687,281],{"class":270},[238,2689,2037],{"class":270},[238,2691,1516],{"class":386},[238,2693,303],{"class":270},[238,2695,2696],{"class":240,"line":560},[238,2697,2698],{"class":270},"}\n",[195,2700,2701,2702,2708],{},"For redirecting away from a page the user can't access, prefer the\n",[1688,2703,2705,2707],{"href":2704},"#route-middleware-requirepermissionkey-fallback",[199,2706,217],{}," middleware","\nover a watcher — it runs before navigation.",[224,2710,2712,2713,2715,2716,2715,2718],{"id":2711},"when-to-use-canaccess-vs-can-vs-requirepermission","When to use ",[199,2714,213],{}," vs ",[199,2717,2421],{},[199,2719,217],{},[2721,2722,2723,2736],"table",{},[2724,2725,2726],"thead",{},[2727,2728,2729,2733],"tr",{},[2730,2731,2732],"th",{},"Scenario",[2730,2734,2735],{},"Use",[2737,2738,2739,2751,2761,2775,2785],"tbody",{},[2727,2740,2741,2745],{},[2742,2743,2744],"td",{},"Whole page requires a permission",[2742,2746,2747,2750],{},[199,2748,2749],{},"requirePermission(\"...\")"," as page middleware",[2727,2752,2753,2756],{},[2742,2754,2755],{},"Show\u002Fhide a template element based on permission",[2742,2757,2758],{},[199,2759,2760],{},"\u003CCanAccess permission=\"...\">",[2727,2762,2763,2766],{},[2742,2764,2765],{},"Show a different element when permission is denied",[2742,2767,2768,2770,2771,2774],{},[199,2769,213],{}," with ",[199,2772,2773],{},"#fallback"," slot",[2727,2776,2777,2780],{},[2742,2778,2779],{},"Build menu items or nav links in script",[2742,2781,2782],{},[199,2783,2784],{},"can(\"...\")",[2727,2786,2787,2793],{},[2742,2788,2789,2790,2792],{},"Gate a ",[199,2791,2432],{}," at setup",[2742,2794,2795],{},[199,2796,2797],{},"immediate: can(\"...\")",[224,2799,1691],{"id":2800},"exposing-tables-to-ai-chat",[195,2802,2803,2804,2806,2807,2809],{},"The AI chat can read and write any table registered in the ",[199,2805,221],{},"\nmap in ",[199,2808,201],{},". Tables not listed are invisible to the chat\ntools, so adding a new resource to chat is an explicit, per-table decision.",[229,2811,2813],{"className":231,"code":2812,"language":233,"meta":234,"style":234},"\u002F\u002F shared\u002Fpermissions.ts\nexport const tablePermissions = {\n  tasks: {\n    view:   \"tasks.view\",\n    create: \"tasks.create\",\n    update: \"tasks.update\",\n    delete: \"tasks.delete\",\n  },\n  \u002F\u002F read-only for chat — writes blocked at the tool layer (readonly.write\n  \u002F\u002F has an empty role set) and at the DB (chat_reader is SELECT only).\n  activity_log: {\n    view:   \"activity.view\",\n    create: \"readonly.write\",\n    update: \"readonly.write\",\n    delete: \"readonly.write\",\n  },\n} as const satisfies Record\u003Cstring, Record\u003CCrudAction, Permission>>;\n",[199,2814,2815,2819,2832,2841,2857,2872,2887,2902,2907,2912,2917,2926,2940,2954,2968,2982,2986],{"__ignoreMap":234},[238,2816,2817],{"class":240,"line":241},[238,2818,245],{"class":244},[238,2820,2821,2823,2825,2828,2830],{"class":240,"line":248},[238,2822,259],{"class":258},[238,2824,316],{"class":262},[238,2826,2827],{"class":319}," tablePermissions ",[238,2829,323],{"class":270},[238,2831,371],{"class":270},[238,2833,2834,2837,2839],{"class":240,"line":255},[238,2835,2836],{"class":386},"  tasks",[238,2838,392],{"class":270},[238,2840,371],{"class":270},[238,2842,2843,2846,2848,2851,2853,2855],{"class":240,"line":306},[238,2844,2845],{"class":386},"    view",[238,2847,392],{"class":270},[238,2849,2850],{"class":270},"   \"",[238,2852,1348],{"class":277},[238,2854,281],{"class":270},[238,2856,423],{"class":270},[238,2858,2859,2862,2864,2866,2868,2870],{"class":240,"line":311},[238,2860,2861],{"class":386},"    create",[238,2863,392],{"class":270},[238,2865,274],{"class":270},[238,2867,1386],{"class":277},[238,2869,281],{"class":270},[238,2871,423],{"class":270},[238,2873,2874,2877,2879,2881,2883,2885],{"class":240,"line":354},[238,2875,2876],{"class":386},"    update",[238,2878,392],{"class":270},[238,2880,274],{"class":270},[238,2882,1424],{"class":277},[238,2884,281],{"class":270},[238,2886,423],{"class":270},[238,2888,2889,2892,2894,2896,2898,2900],{"class":240,"line":359},[238,2890,2891],{"class":386},"    delete",[238,2893,392],{"class":270},[238,2895,274],{"class":270},[238,2897,1453],{"class":277},[238,2899,281],{"class":270},[238,2901,423],{"class":270},[238,2903,2904],{"class":240,"line":374},[238,2905,2906],{"class":270},"  },\n",[238,2908,2909],{"class":240,"line":380},[238,2910,2911],{"class":244},"  \u002F\u002F read-only for chat — writes blocked at the tool layer (readonly.write\n",[238,2913,2914],{"class":240,"line":426},[238,2915,2916],{"class":244},"  \u002F\u002F has an empty role set) and at the DB (chat_reader is SELECT only).\n",[238,2918,2919,2922,2924],{"class":240,"line":459},[238,2920,2921],{"class":386},"  activity_log",[238,2923,392],{"class":270},[238,2925,371],{"class":270},[238,2927,2928,2930,2932,2934,2936,2938],{"class":240,"line":483},[238,2929,2845],{"class":386},[238,2931,392],{"class":270},[238,2933,2850],{"class":270},[238,2935,857],{"class":277},[238,2937,281],{"class":270},[238,2939,423],{"class":270},[238,2941,2942,2944,2946,2948,2950,2952],{"class":240,"line":488},[238,2943,2861],{"class":386},[238,2945,392],{"class":270},[238,2947,274],{"class":270},[238,2949,1131],{"class":277},[238,2951,281],{"class":270},[238,2953,423],{"class":270},[238,2955,2956,2958,2960,2962,2964,2966],{"class":240,"line":494},[238,2957,2876],{"class":386},[238,2959,392],{"class":270},[238,2961,274],{"class":270},[238,2963,1131],{"class":277},[238,2965,281],{"class":270},[238,2967,423],{"class":270},[238,2969,2970,2972,2974,2976,2978,2980],{"class":240,"line":535},[238,2971,2891],{"class":386},[238,2973,392],{"class":270},[238,2975,274],{"class":270},[238,2977,1131],{"class":277},[238,2979,281],{"class":270},[238,2981,423],{"class":270},[238,2983,2984],{"class":240,"line":560},[238,2985,2906],{"class":270},[238,2987,2988,2990,2992,2994,2996,2998,3000,3002,3004,3006,3008,3011,3013,3015],{"class":240,"line":584},[238,2989,1146],{"class":270},[238,2991,1149],{"class":258},[238,2993,316],{"class":266},[238,2995,1154],{"class":258},[238,2997,1157],{"class":266},[238,2999,1160],{"class":270},[238,3001,1163],{"class":266},[238,3003,335],{"class":270},[238,3005,1157],{"class":266},[238,3007,1160],{"class":270},[238,3009,3010],{"class":266},"CrudAction",[238,3012,335],{"class":270},[238,3014,1191],{"class":266},[238,3016,3017],{"class":270},">>;\n",[195,3019,3020,3021,3023,3024,3027,3028,3031],{},"Each action points at a permission key the chat user's role is checked\nagainst before the tool runs. See ",[1688,3022,143],{"href":144}," for the\nfull table-onboarding checklist (RLS policy for ",[199,3025,3026],{},"chat_reader",", CHECK\nconstraints, ",[199,3029,3030],{},"COMMENT ON COLUMN",", activity log).",[3033,3034,3035],"style",{},"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 .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}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}",{"title":234,"searchDepth":241,"depth":248,"links":3037},[3038,3039,3040,3041,3042,3044,3046,3048,3050,3052],{"id":226,"depth":248,"text":227},{"id":1263,"depth":248,"text":1264},{"id":1323,"depth":248,"text":1324},{"id":1695,"depth":248,"text":1696},{"id":1964,"depth":248,"text":3043},"Server-side: authUser(event, permission)",{"id":2082,"depth":248,"text":3045},"Route middleware: requirePermission(key, fallback?)",{"id":2235,"depth":248,"text":3047},"Client-side: \u003CCanAccess> component",{"id":2418,"depth":248,"text":3049},"Client-side: can() in scripts",{"id":2711,"depth":248,"text":3051},"When to use \u003CCanAccess> vs can() vs requirePermission",{"id":2800,"depth":248,"text":1691},"How roles and permissions work, and how to add your own.","md",null,{},{"icon":131},{"title":128,"description":3053},"ATWRjohpeoWwvdYk4nT_08JphC6SjgD-c3ttfP4_xas",[3061,3063],{"title":123,"path":124,"stem":125,"description":3062,"icon":126,"children":-1},"What ships in the template and how the pieces fit together.",{"title":133,"path":134,"stem":135,"description":3064,"icon":136,"children":-1},"How team invites work — webhook-only delivery, token flow, and how to add email later.",1777092171100]