[{"data":1,"prerenderedAt":2226},["ShallowReactive",2],{"navigation":3,"\u002Fexamples\u002Fkanban-todo":189,"\u002Fexamples\u002Fkanban-todo-surround":2221},[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":86,"body":191,"description":2214,"extension":2215,"links":2216,"meta":2217,"navigation":2218,"path":87,"seo":2219,"stem":88,"__hash__":2220},"docs\u002F5.examples\u002F3.kanban-todo.md",{"type":192,"value":193,"toc":2210},"minimark",[194,210,213,216,219,253,2135,2139,2142,2175,2182,2186,2206],[195,196,197],"note",{},[198,199,200,204,205,209],"p",{},[201,202,203],"strong",{},"Prerequisite:"," Complete the ",[206,207,5],"a",{"href":208},"\u002Fget-started\u002F"," guide first.",[198,211,212],{},"To build this feature, copy the first prompt, paste it into Claude Code (or Codex, or any AI coding tool), let it finish, then run the next prompt.",[198,214,215],{},"A kanban board is how teams organize work visually — moving cards through stages from \"to do\" to \"done\". Think project management, sprint planning, content pipelines, personal productivity, or any workflow where tasks move through distinct phases.",[198,217,218],{},"By the end of this example you will have:",[220,221,222,229,235,241,247],"ul",{},[223,224,225,228],"li",{},[201,226,227],{},"Boards"," — multiple boards for different projects or workflows",[223,230,231,234],{},[201,232,233],{},"Columns"," — customizable stages with drag-and-drop reordering",[223,236,237,240],{},[201,238,239],{},"Cards"," — tasks with descriptions, assignees, labels, due dates, and checklists",[223,242,243,246],{},[201,244,245],{},"Board dashboard"," — stats showing card counts, overdue items, and team workload",[223,248,249,252],{},[201,250,251],{},"Realtime"," — all changes sync instantly across connected browsers",[254,255,257,262,851,863,867,1233,1237,1723,1727,1732,1947,1950],"steps",{"level":256},"2",[258,259,261],"h2",{"id":260},"database-schema-and-permissions","Database Schema and Permissions",[263,264,269],"pre",{"className":265,"code":266,"language":267,"meta":268,"style":268},"language-txt shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","We are building a kanban board app on top of this template. Create the\ndatabase schema and add the permissions we need.\n\nDatabase (via Supabase MCP):\n\nCreate five tables:\n\n1. `boards` table — id (uuid, default gen_random_uuid(), primary key),\n   team_id (uuid, references teams(id) on delete cascade, not null),\n   name (text, not null),\n   description (text, nullable),\n   color (text, default '#3b82f6', not null),\n   is_archived (boolean, default false, not null),\n   created_by (uuid, references profiles(id), not null),\n   created_at (timestamptz, default now()),\n   updated_at (timestamptz, default now()).\n\n2. `columns` table — id (uuid, default gen_random_uuid(), primary key),\n   board_id (uuid, references boards(id) on delete cascade, not null),\n   name (text, not null),\n   sort_order (integer, default 0, not null),\n   card_limit (integer, nullable),\n   created_at (timestamptz, default now()),\n   updated_at (timestamptz, default now()).\n\n3. `cards` table — id (uuid, default gen_random_uuid(), primary key),\n   column_id (uuid, references columns(id) on delete cascade, not null),\n   board_id (uuid, references boards(id) on delete cascade, not null),\n   title (text, not null),\n   description (text, nullable),\n   sort_order (integer, default 0, not null),\n   assigned_to (uuid, references profiles(id) on delete set null, nullable),\n   priority (text, check priority in ('low', 'normal', 'high', 'urgent'),\n   default 'normal', not null),\n   due_date (date, nullable),\n   labels (text[], default '{}', not null),\n   is_archived (boolean, default false, not null),\n   created_by (uuid, references profiles(id), not null),\n   created_at (timestamptz, default now()),\n   updated_at (timestamptz, default now()).\n\n4. `card_comments` table — id (uuid, default gen_random_uuid(), primary key),\n   card_id (uuid, references cards(id) on delete cascade, not null),\n   user_id (uuid, references profiles(id), not null),\n   content (text, not null),\n   created_at (timestamptz, default now()).\n\n5. `card_checklist_items` table — id (uuid, default gen_random_uuid(),\n   primary key),\n   card_id (uuid, references cards(id) on delete cascade, not null),\n   title (text, not null),\n   is_completed (boolean, default false, not null),\n   sort_order (integer, default 0, not null),\n   created_at (timestamptz, default now()),\n   updated_at (timestamptz, default now()).\n\nEnable RLS on all five tables. Create policies that use the existing\n`is_team_member()` function to scope access. For `boards`, check team_id.\nFor `columns` and `cards`, join through `boards` to check team membership.\nFor `card_comments` and `card_checklist_items`, join through `cards` and\n`boards`.\n\nOnly create a SELECT policy for team members — follow the\n`announcements_team_member_read` pattern in\n`supabase\u002Fmigrations\u002F00001_initial_schema.sql`. Writes go through\nservice-role server routes, so no insert\u002Fupdate\u002Fdelete RLS policies are\nneeded. Permission checks live in server routes via `authUser(event, \"key\")`.\n\nAfter creating the tables, add these permissions to `shared\u002Fpermissions.ts`:\n\n```\n\"boards.view\": [\"owner\", \"admin\", \"member\"],\n\"boards.create\": [\"owner\", \"admin\", \"member\"],\n\"boards.update\": [\"owner\", \"admin\"],\n\"boards.delete\": [\"owner\", \"admin\"],\n\"cards.view\": [\"owner\", \"admin\", \"member\"],\n\"cards.create\": [\"owner\", \"admin\", \"member\"],\n\"cards.update\": [\"owner\", \"admin\", \"member\"],\n\"cards.delete\": [\"owner\", \"admin\"],\n```\n\nAlso wire the new tables into the baked-in AI chat and activity log per\nCLAUDE.md conventions:\n\n- Add `COMMENT ON COLUMN` on every non-obvious column — one short technical\n  sentence. Mention format or business rules the DB does not enforce.\n- `select enable_activity_log('\u003Ctable>');` for each mutation-bearing table.\n- Grant chat read access on each team-scoped table:\n  ```\n  grant select on \u003Ctable> to chat_reader;\n  create policy \"\u003Ctable>_select_chat\" on \u003Ctable>\n    for select to chat_reader using (team_id = current_chat_team());\n  ```\n  Skip tables without a `team_id` column (scope them through a parent).\n- Register each table in `tablePermissions` in `shared\u002Fpermissions.ts`\n  using the permission keys above.\n- Add filter entries in `app\u002Fcomponents\u002Factivity\u002FList.vue` (`tableItems`)\n  for each new table.\n\nRegenerate the TypeScript types via Supabase MCP and save them to\n`shared\u002Ftypes\u002Fdatabase.types.ts`.\n","txt","",[270,271,272,280,286,293,299,304,310,315,321,327,333,339,345,351,357,363,369,374,380,386,391,397,403,408,413,418,424,430,435,441,446,451,457,463,469,475,481,486,491,496,501,506,512,518,524,530,536,541,547,553,558,563,569,574,579,584,589,595,601,607,613,619,624,630,636,642,648,654,659,665,670,676,682,688,694,700,706,712,718,724,729,734,740,746,751,757,763,769,775,781,787,793,799,804,810,816,822,828,834,839,845],"code",{"__ignoreMap":268},[273,274,277],"span",{"class":275,"line":276},"line",1,[273,278,279],{},"We are building a kanban board app on top of this template. Create the\n",[273,281,283],{"class":275,"line":282},2,[273,284,285],{},"database schema and add the permissions we need.\n",[273,287,289],{"class":275,"line":288},3,[273,290,292],{"emptyLinePlaceholder":291},true,"\n",[273,294,296],{"class":275,"line":295},4,[273,297,298],{},"Database (via Supabase MCP):\n",[273,300,302],{"class":275,"line":301},5,[273,303,292],{"emptyLinePlaceholder":291},[273,305,307],{"class":275,"line":306},6,[273,308,309],{},"Create five tables:\n",[273,311,313],{"class":275,"line":312},7,[273,314,292],{"emptyLinePlaceholder":291},[273,316,318],{"class":275,"line":317},8,[273,319,320],{},"1. `boards` table — id (uuid, default gen_random_uuid(), primary key),\n",[273,322,324],{"class":275,"line":323},9,[273,325,326],{},"   team_id (uuid, references teams(id) on delete cascade, not null),\n",[273,328,330],{"class":275,"line":329},10,[273,331,332],{},"   name (text, not null),\n",[273,334,336],{"class":275,"line":335},11,[273,337,338],{},"   description (text, nullable),\n",[273,340,342],{"class":275,"line":341},12,[273,343,344],{},"   color (text, default '#3b82f6', not null),\n",[273,346,348],{"class":275,"line":347},13,[273,349,350],{},"   is_archived (boolean, default false, not null),\n",[273,352,354],{"class":275,"line":353},14,[273,355,356],{},"   created_by (uuid, references profiles(id), not null),\n",[273,358,360],{"class":275,"line":359},15,[273,361,362],{},"   created_at (timestamptz, default now()),\n",[273,364,366],{"class":275,"line":365},16,[273,367,368],{},"   updated_at (timestamptz, default now()).\n",[273,370,372],{"class":275,"line":371},17,[273,373,292],{"emptyLinePlaceholder":291},[273,375,377],{"class":275,"line":376},18,[273,378,379],{},"2. `columns` table — id (uuid, default gen_random_uuid(), primary key),\n",[273,381,383],{"class":275,"line":382},19,[273,384,385],{},"   board_id (uuid, references boards(id) on delete cascade, not null),\n",[273,387,389],{"class":275,"line":388},20,[273,390,332],{},[273,392,394],{"class":275,"line":393},21,[273,395,396],{},"   sort_order (integer, default 0, not null),\n",[273,398,400],{"class":275,"line":399},22,[273,401,402],{},"   card_limit (integer, nullable),\n",[273,404,406],{"class":275,"line":405},23,[273,407,362],{},[273,409,411],{"class":275,"line":410},24,[273,412,368],{},[273,414,416],{"class":275,"line":415},25,[273,417,292],{"emptyLinePlaceholder":291},[273,419,421],{"class":275,"line":420},26,[273,422,423],{},"3. `cards` table — id (uuid, default gen_random_uuid(), primary key),\n",[273,425,427],{"class":275,"line":426},27,[273,428,429],{},"   column_id (uuid, references columns(id) on delete cascade, not null),\n",[273,431,433],{"class":275,"line":432},28,[273,434,385],{},[273,436,438],{"class":275,"line":437},29,[273,439,440],{},"   title (text, not null),\n",[273,442,444],{"class":275,"line":443},30,[273,445,338],{},[273,447,449],{"class":275,"line":448},31,[273,450,396],{},[273,452,454],{"class":275,"line":453},32,[273,455,456],{},"   assigned_to (uuid, references profiles(id) on delete set null, nullable),\n",[273,458,460],{"class":275,"line":459},33,[273,461,462],{},"   priority (text, check priority in ('low', 'normal', 'high', 'urgent'),\n",[273,464,466],{"class":275,"line":465},34,[273,467,468],{},"   default 'normal', not null),\n",[273,470,472],{"class":275,"line":471},35,[273,473,474],{},"   due_date (date, nullable),\n",[273,476,478],{"class":275,"line":477},36,[273,479,480],{},"   labels (text[], default '{}', not null),\n",[273,482,484],{"class":275,"line":483},37,[273,485,350],{},[273,487,489],{"class":275,"line":488},38,[273,490,356],{},[273,492,494],{"class":275,"line":493},39,[273,495,362],{},[273,497,499],{"class":275,"line":498},40,[273,500,368],{},[273,502,504],{"class":275,"line":503},41,[273,505,292],{"emptyLinePlaceholder":291},[273,507,509],{"class":275,"line":508},42,[273,510,511],{},"4. `card_comments` table — id (uuid, default gen_random_uuid(), primary key),\n",[273,513,515],{"class":275,"line":514},43,[273,516,517],{},"   card_id (uuid, references cards(id) on delete cascade, not null),\n",[273,519,521],{"class":275,"line":520},44,[273,522,523],{},"   user_id (uuid, references profiles(id), not null),\n",[273,525,527],{"class":275,"line":526},45,[273,528,529],{},"   content (text, not null),\n",[273,531,533],{"class":275,"line":532},46,[273,534,535],{},"   created_at (timestamptz, default now()).\n",[273,537,539],{"class":275,"line":538},47,[273,540,292],{"emptyLinePlaceholder":291},[273,542,544],{"class":275,"line":543},48,[273,545,546],{},"5. `card_checklist_items` table — id (uuid, default gen_random_uuid(),\n",[273,548,550],{"class":275,"line":549},49,[273,551,552],{},"   primary key),\n",[273,554,556],{"class":275,"line":555},50,[273,557,517],{},[273,559,561],{"class":275,"line":560},51,[273,562,440],{},[273,564,566],{"class":275,"line":565},52,[273,567,568],{},"   is_completed (boolean, default false, not null),\n",[273,570,572],{"class":275,"line":571},53,[273,573,396],{},[273,575,577],{"class":275,"line":576},54,[273,578,362],{},[273,580,582],{"class":275,"line":581},55,[273,583,368],{},[273,585,587],{"class":275,"line":586},56,[273,588,292],{"emptyLinePlaceholder":291},[273,590,592],{"class":275,"line":591},57,[273,593,594],{},"Enable RLS on all five tables. Create policies that use the existing\n",[273,596,598],{"class":275,"line":597},58,[273,599,600],{},"`is_team_member()` function to scope access. For `boards`, check team_id.\n",[273,602,604],{"class":275,"line":603},59,[273,605,606],{},"For `columns` and `cards`, join through `boards` to check team membership.\n",[273,608,610],{"class":275,"line":609},60,[273,611,612],{},"For `card_comments` and `card_checklist_items`, join through `cards` and\n",[273,614,616],{"class":275,"line":615},61,[273,617,618],{},"`boards`.\n",[273,620,622],{"class":275,"line":621},62,[273,623,292],{"emptyLinePlaceholder":291},[273,625,627],{"class":275,"line":626},63,[273,628,629],{},"Only create a SELECT policy for team members — follow the\n",[273,631,633],{"class":275,"line":632},64,[273,634,635],{},"`announcements_team_member_read` pattern in\n",[273,637,639],{"class":275,"line":638},65,[273,640,641],{},"`supabase\u002Fmigrations\u002F00001_initial_schema.sql`. Writes go through\n",[273,643,645],{"class":275,"line":644},66,[273,646,647],{},"service-role server routes, so no insert\u002Fupdate\u002Fdelete RLS policies are\n",[273,649,651],{"class":275,"line":650},67,[273,652,653],{},"needed. Permission checks live in server routes via `authUser(event, \"key\")`.\n",[273,655,657],{"class":275,"line":656},68,[273,658,292],{"emptyLinePlaceholder":291},[273,660,662],{"class":275,"line":661},69,[273,663,664],{},"After creating the tables, add these permissions to `shared\u002Fpermissions.ts`:\n",[273,666,668],{"class":275,"line":667},70,[273,669,292],{"emptyLinePlaceholder":291},[273,671,673],{"class":275,"line":672},71,[273,674,675],{},"```\n",[273,677,679],{"class":275,"line":678},72,[273,680,681],{},"\"boards.view\": [\"owner\", \"admin\", \"member\"],\n",[273,683,685],{"class":275,"line":684},73,[273,686,687],{},"\"boards.create\": [\"owner\", \"admin\", \"member\"],\n",[273,689,691],{"class":275,"line":690},74,[273,692,693],{},"\"boards.update\": [\"owner\", \"admin\"],\n",[273,695,697],{"class":275,"line":696},75,[273,698,699],{},"\"boards.delete\": [\"owner\", \"admin\"],\n",[273,701,703],{"class":275,"line":702},76,[273,704,705],{},"\"cards.view\": [\"owner\", \"admin\", \"member\"],\n",[273,707,709],{"class":275,"line":708},77,[273,710,711],{},"\"cards.create\": [\"owner\", \"admin\", \"member\"],\n",[273,713,715],{"class":275,"line":714},78,[273,716,717],{},"\"cards.update\": [\"owner\", \"admin\", \"member\"],\n",[273,719,721],{"class":275,"line":720},79,[273,722,723],{},"\"cards.delete\": [\"owner\", \"admin\"],\n",[273,725,727],{"class":275,"line":726},80,[273,728,675],{},[273,730,732],{"class":275,"line":731},81,[273,733,292],{"emptyLinePlaceholder":291},[273,735,737],{"class":275,"line":736},82,[273,738,739],{},"Also wire the new tables into the baked-in AI chat and activity log per\n",[273,741,743],{"class":275,"line":742},83,[273,744,745],{},"CLAUDE.md conventions:\n",[273,747,749],{"class":275,"line":748},84,[273,750,292],{"emptyLinePlaceholder":291},[273,752,754],{"class":275,"line":753},85,[273,755,756],{},"- Add `COMMENT ON COLUMN` on every non-obvious column — one short technical\n",[273,758,760],{"class":275,"line":759},86,[273,761,762],{},"  sentence. Mention format or business rules the DB does not enforce.\n",[273,764,766],{"class":275,"line":765},87,[273,767,768],{},"- `select enable_activity_log('\u003Ctable>');` for each mutation-bearing table.\n",[273,770,772],{"class":275,"line":771},88,[273,773,774],{},"- Grant chat read access on each team-scoped table:\n",[273,776,778],{"class":275,"line":777},89,[273,779,780],{},"  ```\n",[273,782,784],{"class":275,"line":783},90,[273,785,786],{},"  grant select on \u003Ctable> to chat_reader;\n",[273,788,790],{"class":275,"line":789},91,[273,791,792],{},"  create policy \"\u003Ctable>_select_chat\" on \u003Ctable>\n",[273,794,796],{"class":275,"line":795},92,[273,797,798],{},"    for select to chat_reader using (team_id = current_chat_team());\n",[273,800,802],{"class":275,"line":801},93,[273,803,780],{},[273,805,807],{"class":275,"line":806},94,[273,808,809],{},"  Skip tables without a `team_id` column (scope them through a parent).\n",[273,811,813],{"class":275,"line":812},95,[273,814,815],{},"- Register each table in `tablePermissions` in `shared\u002Fpermissions.ts`\n",[273,817,819],{"class":275,"line":818},96,[273,820,821],{},"  using the permission keys above.\n",[273,823,825],{"class":275,"line":824},97,[273,826,827],{},"- Add filter entries in `app\u002Fcomponents\u002Factivity\u002FList.vue` (`tableItems`)\n",[273,829,831],{"class":275,"line":830},98,[273,832,833],{},"  for each new table.\n",[273,835,837],{"class":275,"line":836},99,[273,838,292],{"emptyLinePlaceholder":291},[273,840,842],{"class":275,"line":841},100,[273,843,844],{},"Regenerate the TypeScript types via Supabase MCP and save them to\n",[273,846,848],{"class":275,"line":847},101,[273,849,850],{},"`shared\u002Ftypes\u002Fdatabase.types.ts`.\n",[852,853,854],"tip",{},[198,855,856,859,860,862],{},[201,857,858],{},"Adding the public API later?"," If you plan to add the ",[206,861,59],{"href":60}," plugin later, its page will guide you through adding the required permissions. You don't need to add them now.",[258,864,866],{"id":865},"boards-columns","Boards & Columns",[263,868,870],{"className":265,"code":869,"language":267,"meta":268,"style":268},"Build the board and column management — the structural layer of the kanban.\n\nServer routes (all use `authUser` with the appropriate permission):\n\n- `GET \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards` — uses `authUser(event, \"boards.view\")`.\n  Returns all non-archived boards for the team, ordered by name asc. Include\n  a count of columns and total card count per board by joining. Support\n  optional query param `?include_archived=true` to also return archived boards.\n\n- `POST \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards` — uses\n  `authUser(event, \"boards.create\")`. Reads { name, description, color }\n  from the body. Validates that name is required. Sets team_id from auth\n  context and created_by from the authenticated user (user.sub). After\n  creating the board, create three default columns: \"To Do\" (sort_order 0),\n  \"In Progress\" (sort_order 1), \"Done\" (sort_order 2).\n\n- `PATCH \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]` — uses\n  `authUser(event, \"boards.update\")`. Updates whichever fields are provided.\n  Validates that the board belongs to the team.\n\n- `DELETE \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]` — uses\n  `authUser(event, \"boards.delete\")`. Validates that the board belongs to\n  the team before deleting.\n\n- `GET \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcolumns` — uses\n  `authUser(event, \"boards.view\")`. Returns all columns for the board,\n  ordered by sort_order asc. Include a count of non-archived cards per column.\n\n- `POST \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcolumns` — uses\n  `authUser(event, \"boards.update\")`. Reads { name, card_limit } from the\n  body. Validates that name is required. Sets sort_order to max existing\n  sort_order + 1 for this board.\n\n- `PATCH \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcolumns\u002F[columnId]` — uses\n  `authUser(event, \"boards.update\")`. Updates name, card_limit, or\n  sort_order. Validates that the column belongs to the board.\n\n- `DELETE \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcolumns\u002F[columnId]` — uses\n  `authUser(event, \"boards.update\")`. Return 400 if the column still has\n  cards. Validates board membership.\n\n- `POST \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcolumns\u002Freorder` — uses\n  `authUser(event, \"boards.update\")`. Reads { column_ids } from the body\n  (an ordered array of column UUIDs). Updates the sort_order of each column\n  to match its array index. Validates that all columns belong to the board.\n\nUI:\n\nCreate `app\u002Fpages\u002Fapp\u002Fboards\u002Findex.vue` — a board list page as a top-level\nroute, wrapped in a `UDashboardPanel`:\n\n- `UDashboardNavbar` in the header: title \"Boards\",\n  `UDashboardSidebarCollapse` on the left. On the right, a \"New Board\"\n  button wrapped in `CanAccess permission=\"boards.create\"`.\n\n- The body shows a grid of board cards (not a table). Each card shows the\n  board name, description (truncated), color accent bar on the left or top,\n  column count, and card count. Use `USkeleton` for loading. Show empty state.\n\n- \"New Board\" button opens a `UModal` with a form: name (required),\n  description, color (preset color picker with 6-8 color options). Use Zod\n  for validation. Show loading on submit.\n\n- Clicking a board card navigates to `app\u002Fpages\u002Fapp\u002Fboards\u002F[boardId].vue` —\n  the kanban board view (built in the next prompt).\n\n- Board settings: a gear icon on each board card (wrapped in\n  `CanAccess permission=\"boards.update\"`) that opens a `UModal` for\n  editing name, description, color, and archiving. Include delete wrapped\n  in `CanAccess permission=\"boards.delete\"` with confirmation.\n\nSidebar navigation:\n\nAdd a \"Boards\" link to the top navigation group in\n`app\u002Fcomponents\u002Flayout\u002Fsidebar\u002FLinks.vue`, between \"Dashboard\" and\n\"Settings\". Use the icon `i-solar-widget-2-bold-duotone`.\n",[270,871,872,877,881,886,890,895,900,905,910,914,919,924,929,934,939,944,948,953,958,963,967,972,977,982,986,991,996,1001,1005,1010,1015,1020,1025,1029,1034,1039,1044,1048,1053,1058,1063,1067,1072,1077,1082,1087,1091,1096,1100,1105,1110,1114,1119,1124,1129,1133,1138,1143,1148,1152,1157,1162,1167,1171,1176,1181,1185,1190,1195,1200,1205,1209,1214,1218,1223,1228],{"__ignoreMap":268},[273,873,874],{"class":275,"line":276},[273,875,876],{},"Build the board and column management — the structural layer of the kanban.\n",[273,878,879],{"class":275,"line":282},[273,880,292],{"emptyLinePlaceholder":291},[273,882,883],{"class":275,"line":288},[273,884,885],{},"Server routes (all use `authUser` with the appropriate permission):\n",[273,887,888],{"class":275,"line":295},[273,889,292],{"emptyLinePlaceholder":291},[273,891,892],{"class":275,"line":301},[273,893,894],{},"- `GET \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards` — uses `authUser(event, \"boards.view\")`.\n",[273,896,897],{"class":275,"line":306},[273,898,899],{},"  Returns all non-archived boards for the team, ordered by name asc. Include\n",[273,901,902],{"class":275,"line":312},[273,903,904],{},"  a count of columns and total card count per board by joining. Support\n",[273,906,907],{"class":275,"line":317},[273,908,909],{},"  optional query param `?include_archived=true` to also return archived boards.\n",[273,911,912],{"class":275,"line":323},[273,913,292],{"emptyLinePlaceholder":291},[273,915,916],{"class":275,"line":329},[273,917,918],{},"- `POST \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards` — uses\n",[273,920,921],{"class":275,"line":335},[273,922,923],{},"  `authUser(event, \"boards.create\")`. Reads { name, description, color }\n",[273,925,926],{"class":275,"line":341},[273,927,928],{},"  from the body. Validates that name is required. Sets team_id from auth\n",[273,930,931],{"class":275,"line":347},[273,932,933],{},"  context and created_by from the authenticated user (user.sub). After\n",[273,935,936],{"class":275,"line":353},[273,937,938],{},"  creating the board, create three default columns: \"To Do\" (sort_order 0),\n",[273,940,941],{"class":275,"line":359},[273,942,943],{},"  \"In Progress\" (sort_order 1), \"Done\" (sort_order 2).\n",[273,945,946],{"class":275,"line":365},[273,947,292],{"emptyLinePlaceholder":291},[273,949,950],{"class":275,"line":371},[273,951,952],{},"- `PATCH \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]` — uses\n",[273,954,955],{"class":275,"line":376},[273,956,957],{},"  `authUser(event, \"boards.update\")`. Updates whichever fields are provided.\n",[273,959,960],{"class":275,"line":382},[273,961,962],{},"  Validates that the board belongs to the team.\n",[273,964,965],{"class":275,"line":388},[273,966,292],{"emptyLinePlaceholder":291},[273,968,969],{"class":275,"line":393},[273,970,971],{},"- `DELETE \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]` — uses\n",[273,973,974],{"class":275,"line":399},[273,975,976],{},"  `authUser(event, \"boards.delete\")`. Validates that the board belongs to\n",[273,978,979],{"class":275,"line":405},[273,980,981],{},"  the team before deleting.\n",[273,983,984],{"class":275,"line":410},[273,985,292],{"emptyLinePlaceholder":291},[273,987,988],{"class":275,"line":415},[273,989,990],{},"- `GET \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcolumns` — uses\n",[273,992,993],{"class":275,"line":420},[273,994,995],{},"  `authUser(event, \"boards.view\")`. Returns all columns for the board,\n",[273,997,998],{"class":275,"line":426},[273,999,1000],{},"  ordered by sort_order asc. Include a count of non-archived cards per column.\n",[273,1002,1003],{"class":275,"line":432},[273,1004,292],{"emptyLinePlaceholder":291},[273,1006,1007],{"class":275,"line":437},[273,1008,1009],{},"- `POST \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcolumns` — uses\n",[273,1011,1012],{"class":275,"line":443},[273,1013,1014],{},"  `authUser(event, \"boards.update\")`. Reads { name, card_limit } from the\n",[273,1016,1017],{"class":275,"line":448},[273,1018,1019],{},"  body. Validates that name is required. Sets sort_order to max existing\n",[273,1021,1022],{"class":275,"line":453},[273,1023,1024],{},"  sort_order + 1 for this board.\n",[273,1026,1027],{"class":275,"line":459},[273,1028,292],{"emptyLinePlaceholder":291},[273,1030,1031],{"class":275,"line":465},[273,1032,1033],{},"- `PATCH \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcolumns\u002F[columnId]` — uses\n",[273,1035,1036],{"class":275,"line":471},[273,1037,1038],{},"  `authUser(event, \"boards.update\")`. Updates name, card_limit, or\n",[273,1040,1041],{"class":275,"line":477},[273,1042,1043],{},"  sort_order. Validates that the column belongs to the board.\n",[273,1045,1046],{"class":275,"line":483},[273,1047,292],{"emptyLinePlaceholder":291},[273,1049,1050],{"class":275,"line":488},[273,1051,1052],{},"- `DELETE \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcolumns\u002F[columnId]` — uses\n",[273,1054,1055],{"class":275,"line":493},[273,1056,1057],{},"  `authUser(event, \"boards.update\")`. Return 400 if the column still has\n",[273,1059,1060],{"class":275,"line":498},[273,1061,1062],{},"  cards. Validates board membership.\n",[273,1064,1065],{"class":275,"line":503},[273,1066,292],{"emptyLinePlaceholder":291},[273,1068,1069],{"class":275,"line":508},[273,1070,1071],{},"- `POST \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcolumns\u002Freorder` — uses\n",[273,1073,1074],{"class":275,"line":514},[273,1075,1076],{},"  `authUser(event, \"boards.update\")`. Reads { column_ids } from the body\n",[273,1078,1079],{"class":275,"line":520},[273,1080,1081],{},"  (an ordered array of column UUIDs). Updates the sort_order of each column\n",[273,1083,1084],{"class":275,"line":526},[273,1085,1086],{},"  to match its array index. Validates that all columns belong to the board.\n",[273,1088,1089],{"class":275,"line":532},[273,1090,292],{"emptyLinePlaceholder":291},[273,1092,1093],{"class":275,"line":538},[273,1094,1095],{},"UI:\n",[273,1097,1098],{"class":275,"line":543},[273,1099,292],{"emptyLinePlaceholder":291},[273,1101,1102],{"class":275,"line":549},[273,1103,1104],{},"Create `app\u002Fpages\u002Fapp\u002Fboards\u002Findex.vue` — a board list page as a top-level\n",[273,1106,1107],{"class":275,"line":555},[273,1108,1109],{},"route, wrapped in a `UDashboardPanel`:\n",[273,1111,1112],{"class":275,"line":560},[273,1113,292],{"emptyLinePlaceholder":291},[273,1115,1116],{"class":275,"line":565},[273,1117,1118],{},"- `UDashboardNavbar` in the header: title \"Boards\",\n",[273,1120,1121],{"class":275,"line":571},[273,1122,1123],{},"  `UDashboardSidebarCollapse` on the left. On the right, a \"New Board\"\n",[273,1125,1126],{"class":275,"line":576},[273,1127,1128],{},"  button wrapped in `CanAccess permission=\"boards.create\"`.\n",[273,1130,1131],{"class":275,"line":581},[273,1132,292],{"emptyLinePlaceholder":291},[273,1134,1135],{"class":275,"line":586},[273,1136,1137],{},"- The body shows a grid of board cards (not a table). Each card shows the\n",[273,1139,1140],{"class":275,"line":591},[273,1141,1142],{},"  board name, description (truncated), color accent bar on the left or top,\n",[273,1144,1145],{"class":275,"line":597},[273,1146,1147],{},"  column count, and card count. Use `USkeleton` for loading. Show empty state.\n",[273,1149,1150],{"class":275,"line":603},[273,1151,292],{"emptyLinePlaceholder":291},[273,1153,1154],{"class":275,"line":609},[273,1155,1156],{},"- \"New Board\" button opens a `UModal` with a form: name (required),\n",[273,1158,1159],{"class":275,"line":615},[273,1160,1161],{},"  description, color (preset color picker with 6-8 color options). Use Zod\n",[273,1163,1164],{"class":275,"line":621},[273,1165,1166],{},"  for validation. Show loading on submit.\n",[273,1168,1169],{"class":275,"line":626},[273,1170,292],{"emptyLinePlaceholder":291},[273,1172,1173],{"class":275,"line":632},[273,1174,1175],{},"- Clicking a board card navigates to `app\u002Fpages\u002Fapp\u002Fboards\u002F[boardId].vue` —\n",[273,1177,1178],{"class":275,"line":638},[273,1179,1180],{},"  the kanban board view (built in the next prompt).\n",[273,1182,1183],{"class":275,"line":644},[273,1184,292],{"emptyLinePlaceholder":291},[273,1186,1187],{"class":275,"line":650},[273,1188,1189],{},"- Board settings: a gear icon on each board card (wrapped in\n",[273,1191,1192],{"class":275,"line":656},[273,1193,1194],{},"  `CanAccess permission=\"boards.update\"`) that opens a `UModal` for\n",[273,1196,1197],{"class":275,"line":661},[273,1198,1199],{},"  editing name, description, color, and archiving. Include delete wrapped\n",[273,1201,1202],{"class":275,"line":667},[273,1203,1204],{},"  in `CanAccess permission=\"boards.delete\"` with confirmation.\n",[273,1206,1207],{"class":275,"line":672},[273,1208,292],{"emptyLinePlaceholder":291},[273,1210,1211],{"class":275,"line":678},[273,1212,1213],{},"Sidebar navigation:\n",[273,1215,1216],{"class":275,"line":684},[273,1217,292],{"emptyLinePlaceholder":291},[273,1219,1220],{"class":275,"line":690},[273,1221,1222],{},"Add a \"Boards\" link to the top navigation group in\n",[273,1224,1225],{"class":275,"line":696},[273,1226,1227],{},"`app\u002Fcomponents\u002Flayout\u002Fsidebar\u002FLinks.vue`, between \"Dashboard\" and\n",[273,1229,1230],{"class":275,"line":702},[273,1231,1232],{},"\"Settings\". Use the icon `i-solar-widget-2-bold-duotone`.\n",[258,1234,1236],{"id":1235},"cards-kanban-board-view","Cards (Kanban Board View)",[263,1238,1240],{"className":265,"code":1239,"language":267,"meta":268,"style":268},"Build the kanban board view and card management — the main interactive feature.\n\nServer routes (all use `authUser` with the appropriate permission):\n\n- `GET \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards` — uses\n  `authUser(event, \"cards.view\")`. Returns all non-archived cards for the\n  board with assignee name joined in, grouped by column. Order by sort_order\n  asc within each column. Support optional query params: `?assigned_to=`\n  to filter by assignee, `?priority=` to filter by priority,\n  `?include_archived=true`.\n\n- `POST \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards` — uses\n  `authUser(event, \"cards.create\")`. Reads { column_id, title, description,\n  assigned_to, priority, due_date, labels } from the body. Validates that\n  column_id and title are required. Sets board_id from the route, sort_order\n  to max existing sort_order + 1 for that column, and created_by from the\n  authenticated user.\n\n- `PATCH \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards\u002F[cardId]` — uses\n  `authUser(event, \"cards.update\")`. Updates whichever fields are provided.\n  Validates that the card belongs to the board.\n\n- `DELETE \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards\u002F[cardId]` — uses\n  `authUser(event, \"cards.delete\")`. Validates board membership.\n\n- `POST \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards\u002Fmove` — uses\n  `authUser(event, \"cards.update\")`. Reads { card_id, target_column_id,\n  target_sort_order } from the body. Moves the card to the target column\n  at the specified sort position. Reorders other cards in both the source\n  and target columns to fill gaps and make room.\n\n- `GET \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards\u002F[cardId]\u002Fcomments` — uses\n  `authUser(event, \"cards.view\")`. Returns all comments with author name,\n  ordered by created_at asc.\n\n- `POST \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards\u002F[cardId]\u002Fcomments` — uses\n  `authUser(event, \"cards.view\")`. Any team member can comment. Reads\n  { content } from body. Sets user_id from authenticated user.\n\n- `GET \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards\u002F[cardId]\u002Fchecklist` — uses\n  `authUser(event, \"cards.view\")`. Returns all checklist items ordered by\n  sort_order asc.\n\n- `POST \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards\u002F[cardId]\u002Fchecklist` — uses\n  `authUser(event, \"cards.update\")`. Reads { title } from body.\n\n- `PATCH \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards\u002F[cardId]\u002Fchecklist\u002F[itemId]`\n  — uses `authUser(event, \"cards.update\")`. Updates title or is_completed.\n\n- `DELETE \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards\u002F[cardId]\u002Fchecklist\u002F[itemId]`\n  — uses `authUser(event, \"cards.update\")`.\n\nUI:\n\nCreate `app\u002Fpages\u002Fapp\u002Fboards\u002F[boardId].vue` — the kanban board view, wrapped\nin a `UDashboardPanel`:\n\n- `UDashboardNavbar` in the header: board name as title (fetched from the\n  board endpoint), `UDashboardSidebarCollapse` on the left. On the right,\n  a \"New Card\" button wrapped in `CanAccess permission=\"cards.create\"`,\n  and a board settings gear icon.\n\n- The board body is a horizontal scrollable container with columns\n  side-by-side. Each column is a vertical container with:\n  - A header showing the column name, card count (and card limit if set,\n    e.g. \"3 \u002F 5\"), and an \"Add Card\" button.\n  - A scrollable list of card components below the header.\n  - If the column is at its card_limit, the header shows a warning color.\n\n- Each card component shows: title, priority badge (small colored dot),\n  assignee avatar or initials, due date (with red text if overdue), label\n  badges, and a checklist progress indicator (e.g. \"2\u002F5\") if the card has\n  checklist items.\n\n- **Drag and drop**: cards can be dragged between columns and reordered\n  within a column. Install and use `vuedraggable` (or `@vueuse\u002Fintegrations`\n  with sortable). On drop, call the `\u002Fcards\u002Fmove` endpoint with the new\n  column and position. Update the UI optimistically — show the card in its\n  new position immediately, then roll back if the API call fails.\n\n- Clicking a card opens a `USlideover` for the card detail. Show all fields\n  in an editable form at the top: title, description (textarea), column\n  (select to move between columns), assignee (select from team members,\n  use placeholder \"Unassigned\"), priority (select), due date (`UPopover`\n  with `UCalendar`), labels (comma-separated input).\n\n  Below the form, show three sections:\n  1. \"Checklist\" — a list of checklist items with checkboxes. Completed\n     items are struck through. An \"Add Item\" input at the bottom. Delete\n     button (x) on each item.\n  2. \"Comments\" — a scrollable list of comments (author, content, timestamp)\n     with a text input at the bottom to add a new comment.\n  3. Actions — archive card toggle, delete button wrapped in\n     `CanAccess permission=\"cards.delete\"` with confirmation.\n\n- \"New Card\" (either the top button or column-level button) opens a `UModal`\n  with a quick-create form: title (required), column (pre-selected if added\n  from a column button), assignee, priority, due date. Use Zod for\n  validation.\n\n- After any card change, refresh the board.\n",[270,1241,1242,1247,1251,1255,1259,1264,1269,1274,1279,1284,1289,1293,1298,1303,1308,1313,1318,1323,1327,1332,1337,1342,1346,1351,1356,1360,1365,1370,1375,1380,1385,1389,1394,1399,1404,1408,1413,1418,1423,1427,1432,1437,1442,1446,1451,1456,1460,1465,1470,1474,1479,1484,1488,1492,1496,1501,1506,1510,1515,1520,1525,1530,1534,1539,1544,1549,1554,1559,1564,1568,1573,1578,1583,1588,1592,1597,1602,1607,1612,1617,1621,1626,1631,1636,1641,1646,1650,1655,1660,1665,1670,1675,1680,1685,1690,1694,1699,1704,1709,1714,1718],{"__ignoreMap":268},[273,1243,1244],{"class":275,"line":276},[273,1245,1246],{},"Build the kanban board view and card management — the main interactive feature.\n",[273,1248,1249],{"class":275,"line":282},[273,1250,292],{"emptyLinePlaceholder":291},[273,1252,1253],{"class":275,"line":288},[273,1254,885],{},[273,1256,1257],{"class":275,"line":295},[273,1258,292],{"emptyLinePlaceholder":291},[273,1260,1261],{"class":275,"line":301},[273,1262,1263],{},"- `GET \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards` — uses\n",[273,1265,1266],{"class":275,"line":306},[273,1267,1268],{},"  `authUser(event, \"cards.view\")`. Returns all non-archived cards for the\n",[273,1270,1271],{"class":275,"line":312},[273,1272,1273],{},"  board with assignee name joined in, grouped by column. Order by sort_order\n",[273,1275,1276],{"class":275,"line":317},[273,1277,1278],{},"  asc within each column. Support optional query params: `?assigned_to=`\n",[273,1280,1281],{"class":275,"line":323},[273,1282,1283],{},"  to filter by assignee, `?priority=` to filter by priority,\n",[273,1285,1286],{"class":275,"line":329},[273,1287,1288],{},"  `?include_archived=true`.\n",[273,1290,1291],{"class":275,"line":335},[273,1292,292],{"emptyLinePlaceholder":291},[273,1294,1295],{"class":275,"line":341},[273,1296,1297],{},"- `POST \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards` — uses\n",[273,1299,1300],{"class":275,"line":347},[273,1301,1302],{},"  `authUser(event, \"cards.create\")`. Reads { column_id, title, description,\n",[273,1304,1305],{"class":275,"line":353},[273,1306,1307],{},"  assigned_to, priority, due_date, labels } from the body. Validates that\n",[273,1309,1310],{"class":275,"line":359},[273,1311,1312],{},"  column_id and title are required. Sets board_id from the route, sort_order\n",[273,1314,1315],{"class":275,"line":365},[273,1316,1317],{},"  to max existing sort_order + 1 for that column, and created_by from the\n",[273,1319,1320],{"class":275,"line":371},[273,1321,1322],{},"  authenticated user.\n",[273,1324,1325],{"class":275,"line":376},[273,1326,292],{"emptyLinePlaceholder":291},[273,1328,1329],{"class":275,"line":382},[273,1330,1331],{},"- `PATCH \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards\u002F[cardId]` — uses\n",[273,1333,1334],{"class":275,"line":388},[273,1335,1336],{},"  `authUser(event, \"cards.update\")`. Updates whichever fields are provided.\n",[273,1338,1339],{"class":275,"line":393},[273,1340,1341],{},"  Validates that the card belongs to the board.\n",[273,1343,1344],{"class":275,"line":399},[273,1345,292],{"emptyLinePlaceholder":291},[273,1347,1348],{"class":275,"line":405},[273,1349,1350],{},"- `DELETE \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards\u002F[cardId]` — uses\n",[273,1352,1353],{"class":275,"line":410},[273,1354,1355],{},"  `authUser(event, \"cards.delete\")`. Validates board membership.\n",[273,1357,1358],{"class":275,"line":415},[273,1359,292],{"emptyLinePlaceholder":291},[273,1361,1362],{"class":275,"line":420},[273,1363,1364],{},"- `POST \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards\u002Fmove` — uses\n",[273,1366,1367],{"class":275,"line":426},[273,1368,1369],{},"  `authUser(event, \"cards.update\")`. Reads { card_id, target_column_id,\n",[273,1371,1372],{"class":275,"line":432},[273,1373,1374],{},"  target_sort_order } from the body. Moves the card to the target column\n",[273,1376,1377],{"class":275,"line":437},[273,1378,1379],{},"  at the specified sort position. Reorders other cards in both the source\n",[273,1381,1382],{"class":275,"line":443},[273,1383,1384],{},"  and target columns to fill gaps and make room.\n",[273,1386,1387],{"class":275,"line":448},[273,1388,292],{"emptyLinePlaceholder":291},[273,1390,1391],{"class":275,"line":453},[273,1392,1393],{},"- `GET \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards\u002F[cardId]\u002Fcomments` — uses\n",[273,1395,1396],{"class":275,"line":459},[273,1397,1398],{},"  `authUser(event, \"cards.view\")`. Returns all comments with author name,\n",[273,1400,1401],{"class":275,"line":465},[273,1402,1403],{},"  ordered by created_at asc.\n",[273,1405,1406],{"class":275,"line":471},[273,1407,292],{"emptyLinePlaceholder":291},[273,1409,1410],{"class":275,"line":477},[273,1411,1412],{},"- `POST \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards\u002F[cardId]\u002Fcomments` — uses\n",[273,1414,1415],{"class":275,"line":483},[273,1416,1417],{},"  `authUser(event, \"cards.view\")`. Any team member can comment. Reads\n",[273,1419,1420],{"class":275,"line":488},[273,1421,1422],{},"  { content } from body. Sets user_id from authenticated user.\n",[273,1424,1425],{"class":275,"line":493},[273,1426,292],{"emptyLinePlaceholder":291},[273,1428,1429],{"class":275,"line":498},[273,1430,1431],{},"- `GET \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards\u002F[cardId]\u002Fchecklist` — uses\n",[273,1433,1434],{"class":275,"line":503},[273,1435,1436],{},"  `authUser(event, \"cards.view\")`. Returns all checklist items ordered by\n",[273,1438,1439],{"class":275,"line":508},[273,1440,1441],{},"  sort_order asc.\n",[273,1443,1444],{"class":275,"line":514},[273,1445,292],{"emptyLinePlaceholder":291},[273,1447,1448],{"class":275,"line":520},[273,1449,1450],{},"- `POST \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards\u002F[cardId]\u002Fchecklist` — uses\n",[273,1452,1453],{"class":275,"line":526},[273,1454,1455],{},"  `authUser(event, \"cards.update\")`. Reads { title } from body.\n",[273,1457,1458],{"class":275,"line":532},[273,1459,292],{"emptyLinePlaceholder":291},[273,1461,1462],{"class":275,"line":538},[273,1463,1464],{},"- `PATCH \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards\u002F[cardId]\u002Fchecklist\u002F[itemId]`\n",[273,1466,1467],{"class":275,"line":543},[273,1468,1469],{},"  — uses `authUser(event, \"cards.update\")`. Updates title or is_completed.\n",[273,1471,1472],{"class":275,"line":549},[273,1473,292],{"emptyLinePlaceholder":291},[273,1475,1476],{"class":275,"line":555},[273,1477,1478],{},"- `DELETE \u002Fapi\u002Fteams\u002F[teamId]\u002Fboards\u002F[boardId]\u002Fcards\u002F[cardId]\u002Fchecklist\u002F[itemId]`\n",[273,1480,1481],{"class":275,"line":560},[273,1482,1483],{},"  — uses `authUser(event, \"cards.update\")`.\n",[273,1485,1486],{"class":275,"line":565},[273,1487,292],{"emptyLinePlaceholder":291},[273,1489,1490],{"class":275,"line":571},[273,1491,1095],{},[273,1493,1494],{"class":275,"line":576},[273,1495,292],{"emptyLinePlaceholder":291},[273,1497,1498],{"class":275,"line":581},[273,1499,1500],{},"Create `app\u002Fpages\u002Fapp\u002Fboards\u002F[boardId].vue` — the kanban board view, wrapped\n",[273,1502,1503],{"class":275,"line":586},[273,1504,1505],{},"in a `UDashboardPanel`:\n",[273,1507,1508],{"class":275,"line":591},[273,1509,292],{"emptyLinePlaceholder":291},[273,1511,1512],{"class":275,"line":597},[273,1513,1514],{},"- `UDashboardNavbar` in the header: board name as title (fetched from the\n",[273,1516,1517],{"class":275,"line":603},[273,1518,1519],{},"  board endpoint), `UDashboardSidebarCollapse` on the left. On the right,\n",[273,1521,1522],{"class":275,"line":609},[273,1523,1524],{},"  a \"New Card\" button wrapped in `CanAccess permission=\"cards.create\"`,\n",[273,1526,1527],{"class":275,"line":615},[273,1528,1529],{},"  and a board settings gear icon.\n",[273,1531,1532],{"class":275,"line":621},[273,1533,292],{"emptyLinePlaceholder":291},[273,1535,1536],{"class":275,"line":626},[273,1537,1538],{},"- The board body is a horizontal scrollable container with columns\n",[273,1540,1541],{"class":275,"line":632},[273,1542,1543],{},"  side-by-side. Each column is a vertical container with:\n",[273,1545,1546],{"class":275,"line":638},[273,1547,1548],{},"  - A header showing the column name, card count (and card limit if set,\n",[273,1550,1551],{"class":275,"line":644},[273,1552,1553],{},"    e.g. \"3 \u002F 5\"), and an \"Add Card\" button.\n",[273,1555,1556],{"class":275,"line":650},[273,1557,1558],{},"  - A scrollable list of card components below the header.\n",[273,1560,1561],{"class":275,"line":656},[273,1562,1563],{},"  - If the column is at its card_limit, the header shows a warning color.\n",[273,1565,1566],{"class":275,"line":661},[273,1567,292],{"emptyLinePlaceholder":291},[273,1569,1570],{"class":275,"line":667},[273,1571,1572],{},"- Each card component shows: title, priority badge (small colored dot),\n",[273,1574,1575],{"class":275,"line":672},[273,1576,1577],{},"  assignee avatar or initials, due date (with red text if overdue), label\n",[273,1579,1580],{"class":275,"line":678},[273,1581,1582],{},"  badges, and a checklist progress indicator (e.g. \"2\u002F5\") if the card has\n",[273,1584,1585],{"class":275,"line":684},[273,1586,1587],{},"  checklist items.\n",[273,1589,1590],{"class":275,"line":690},[273,1591,292],{"emptyLinePlaceholder":291},[273,1593,1594],{"class":275,"line":696},[273,1595,1596],{},"- **Drag and drop**: cards can be dragged between columns and reordered\n",[273,1598,1599],{"class":275,"line":702},[273,1600,1601],{},"  within a column. Install and use `vuedraggable` (or `@vueuse\u002Fintegrations`\n",[273,1603,1604],{"class":275,"line":708},[273,1605,1606],{},"  with sortable). On drop, call the `\u002Fcards\u002Fmove` endpoint with the new\n",[273,1608,1609],{"class":275,"line":714},[273,1610,1611],{},"  column and position. Update the UI optimistically — show the card in its\n",[273,1613,1614],{"class":275,"line":720},[273,1615,1616],{},"  new position immediately, then roll back if the API call fails.\n",[273,1618,1619],{"class":275,"line":726},[273,1620,292],{"emptyLinePlaceholder":291},[273,1622,1623],{"class":275,"line":731},[273,1624,1625],{},"- Clicking a card opens a `USlideover` for the card detail. Show all fields\n",[273,1627,1628],{"class":275,"line":736},[273,1629,1630],{},"  in an editable form at the top: title, description (textarea), column\n",[273,1632,1633],{"class":275,"line":742},[273,1634,1635],{},"  (select to move between columns), assignee (select from team members,\n",[273,1637,1638],{"class":275,"line":748},[273,1639,1640],{},"  use placeholder \"Unassigned\"), priority (select), due date (`UPopover`\n",[273,1642,1643],{"class":275,"line":753},[273,1644,1645],{},"  with `UCalendar`), labels (comma-separated input).\n",[273,1647,1648],{"class":275,"line":759},[273,1649,292],{"emptyLinePlaceholder":291},[273,1651,1652],{"class":275,"line":765},[273,1653,1654],{},"  Below the form, show three sections:\n",[273,1656,1657],{"class":275,"line":771},[273,1658,1659],{},"  1. \"Checklist\" — a list of checklist items with checkboxes. Completed\n",[273,1661,1662],{"class":275,"line":777},[273,1663,1664],{},"     items are struck through. An \"Add Item\" input at the bottom. Delete\n",[273,1666,1667],{"class":275,"line":783},[273,1668,1669],{},"     button (x) on each item.\n",[273,1671,1672],{"class":275,"line":789},[273,1673,1674],{},"  2. \"Comments\" — a scrollable list of comments (author, content, timestamp)\n",[273,1676,1677],{"class":275,"line":795},[273,1678,1679],{},"     with a text input at the bottom to add a new comment.\n",[273,1681,1682],{"class":275,"line":801},[273,1683,1684],{},"  3. Actions — archive card toggle, delete button wrapped in\n",[273,1686,1687],{"class":275,"line":806},[273,1688,1689],{},"     `CanAccess permission=\"cards.delete\"` with confirmation.\n",[273,1691,1692],{"class":275,"line":812},[273,1693,292],{"emptyLinePlaceholder":291},[273,1695,1696],{"class":275,"line":818},[273,1697,1698],{},"- \"New Card\" (either the top button or column-level button) opens a `UModal`\n",[273,1700,1701],{"class":275,"line":824},[273,1702,1703],{},"  with a quick-create form: title (required), column (pre-selected if added\n",[273,1705,1706],{"class":275,"line":830},[273,1707,1708],{},"  from a column button), assignee, priority, due date. Use Zod for\n",[273,1710,1711],{"class":275,"line":836},[273,1712,1713],{},"  validation.\n",[273,1715,1716],{"class":275,"line":841},[273,1717,292],{"emptyLinePlaceholder":291},[273,1719,1720],{"class":275,"line":847},[273,1721,1722],{},"- After any card change, refresh the board.\n",[258,1724,1726],{"id":1725},"dashboard-and-realtime","Dashboard and Realtime",[1728,1729,1731],"h4",{"id":1730},"dashboard","Dashboard",[263,1733,1735],{"className":265,"code":1734,"language":267,"meta":268,"style":268},"Replace the placeholder dashboard with real board and task stats.\n\nServer route:\n\nCreate `GET \u002Fapi\u002Fteams\u002F[teamId]\u002Fstats` — uses `authUser(event, \"team.view\")`.\nReturns a JSON object with:\n\n- `board_count` — total number of non-archived boards for the team\n- `total_cards` — total number of non-archived cards across all boards\n- `overdue_cards` — count of non-archived cards with due_date \u003C today\n- `completed_this_week` — count of cards that were moved to a column named\n  \"Done\" (case-insensitive) in the current calendar week, based on\n  updated_at\n- `cards_by_priority` — array of { priority, count } for non-archived cards\n- `cards_by_board` — array of { board_name, board_color, card_count } for\n  each non-archived board, ordered by card_count desc\n- `my_cards` — up to 10 non-archived cards assigned to the requesting user,\n  ordered by due_date asc (nulls last), with board name and column name\n- `overdue_list` — up to 10 overdue non-archived cards with board name,\n  column name, assignee name, title, and due_date\n\nAll queries are scoped to the team's team_id.\n\nUI:\n\nUpdate `app\u002Fpages\u002Fapp\u002Findex.vue` to show:\n\n- A row of four stat cards at the top using a grid layout. Each card shows\n  an icon, a label, and the value. Cards: \"Active Boards\" (board_count),\n  \"Total Cards\" (total_cards), \"Overdue\" (overdue_cards, red text if > 0),\n  \"Completed This Week\" (completed_this_week).\n  Use `USkeleton` placeholders while loading.\n\n- Below the stats, a two-column layout:\n  - Left column: \"My Cards\" — a list of cards assigned to the current user.\n    Each item shows card title, board name (with color dot), column name,\n    priority badge, and due date (red if overdue). Clicking a card navigates\n    to the board. Show an empty state if no assigned cards.\n  - Right column: \"Overdue\" — a list of overdue cards. Each item shows\n    title, board name, assignee name, and how many days overdue. Show an\n    empty state if nothing is overdue.\n\nRemove any placeholder\u002Fscaffolding content that was in the dashboard before.\nKeep the `UDashboardPanel` wrapper with navbar and sidebar collapse.\n",[270,1736,1737,1742,1746,1751,1755,1760,1765,1769,1774,1779,1784,1789,1794,1799,1804,1809,1814,1819,1824,1829,1834,1838,1843,1847,1851,1855,1860,1864,1869,1874,1879,1884,1889,1893,1898,1903,1908,1913,1918,1923,1928,1933,1937,1942],{"__ignoreMap":268},[273,1738,1739],{"class":275,"line":276},[273,1740,1741],{},"Replace the placeholder dashboard with real board and task stats.\n",[273,1743,1744],{"class":275,"line":282},[273,1745,292],{"emptyLinePlaceholder":291},[273,1747,1748],{"class":275,"line":288},[273,1749,1750],{},"Server route:\n",[273,1752,1753],{"class":275,"line":295},[273,1754,292],{"emptyLinePlaceholder":291},[273,1756,1757],{"class":275,"line":301},[273,1758,1759],{},"Create `GET \u002Fapi\u002Fteams\u002F[teamId]\u002Fstats` — uses `authUser(event, \"team.view\")`.\n",[273,1761,1762],{"class":275,"line":306},[273,1763,1764],{},"Returns a JSON object with:\n",[273,1766,1767],{"class":275,"line":312},[273,1768,292],{"emptyLinePlaceholder":291},[273,1770,1771],{"class":275,"line":317},[273,1772,1773],{},"- `board_count` — total number of non-archived boards for the team\n",[273,1775,1776],{"class":275,"line":323},[273,1777,1778],{},"- `total_cards` — total number of non-archived cards across all boards\n",[273,1780,1781],{"class":275,"line":329},[273,1782,1783],{},"- `overdue_cards` — count of non-archived cards with due_date \u003C today\n",[273,1785,1786],{"class":275,"line":335},[273,1787,1788],{},"- `completed_this_week` — count of cards that were moved to a column named\n",[273,1790,1791],{"class":275,"line":341},[273,1792,1793],{},"  \"Done\" (case-insensitive) in the current calendar week, based on\n",[273,1795,1796],{"class":275,"line":347},[273,1797,1798],{},"  updated_at\n",[273,1800,1801],{"class":275,"line":353},[273,1802,1803],{},"- `cards_by_priority` — array of { priority, count } for non-archived cards\n",[273,1805,1806],{"class":275,"line":359},[273,1807,1808],{},"- `cards_by_board` — array of { board_name, board_color, card_count } for\n",[273,1810,1811],{"class":275,"line":365},[273,1812,1813],{},"  each non-archived board, ordered by card_count desc\n",[273,1815,1816],{"class":275,"line":371},[273,1817,1818],{},"- `my_cards` — up to 10 non-archived cards assigned to the requesting user,\n",[273,1820,1821],{"class":275,"line":376},[273,1822,1823],{},"  ordered by due_date asc (nulls last), with board name and column name\n",[273,1825,1826],{"class":275,"line":382},[273,1827,1828],{},"- `overdue_list` — up to 10 overdue non-archived cards with board name,\n",[273,1830,1831],{"class":275,"line":388},[273,1832,1833],{},"  column name, assignee name, title, and due_date\n",[273,1835,1836],{"class":275,"line":393},[273,1837,292],{"emptyLinePlaceholder":291},[273,1839,1840],{"class":275,"line":399},[273,1841,1842],{},"All queries are scoped to the team's team_id.\n",[273,1844,1845],{"class":275,"line":405},[273,1846,292],{"emptyLinePlaceholder":291},[273,1848,1849],{"class":275,"line":410},[273,1850,1095],{},[273,1852,1853],{"class":275,"line":415},[273,1854,292],{"emptyLinePlaceholder":291},[273,1856,1857],{"class":275,"line":420},[273,1858,1859],{},"Update `app\u002Fpages\u002Fapp\u002Findex.vue` to show:\n",[273,1861,1862],{"class":275,"line":426},[273,1863,292],{"emptyLinePlaceholder":291},[273,1865,1866],{"class":275,"line":432},[273,1867,1868],{},"- A row of four stat cards at the top using a grid layout. Each card shows\n",[273,1870,1871],{"class":275,"line":437},[273,1872,1873],{},"  an icon, a label, and the value. Cards: \"Active Boards\" (board_count),\n",[273,1875,1876],{"class":275,"line":443},[273,1877,1878],{},"  \"Total Cards\" (total_cards), \"Overdue\" (overdue_cards, red text if > 0),\n",[273,1880,1881],{"class":275,"line":448},[273,1882,1883],{},"  \"Completed This Week\" (completed_this_week).\n",[273,1885,1886],{"class":275,"line":453},[273,1887,1888],{},"  Use `USkeleton` placeholders while loading.\n",[273,1890,1891],{"class":275,"line":459},[273,1892,292],{"emptyLinePlaceholder":291},[273,1894,1895],{"class":275,"line":465},[273,1896,1897],{},"- Below the stats, a two-column layout:\n",[273,1899,1900],{"class":275,"line":471},[273,1901,1902],{},"  - Left column: \"My Cards\" — a list of cards assigned to the current user.\n",[273,1904,1905],{"class":275,"line":477},[273,1906,1907],{},"    Each item shows card title, board name (with color dot), column name,\n",[273,1909,1910],{"class":275,"line":483},[273,1911,1912],{},"    priority badge, and due date (red if overdue). Clicking a card navigates\n",[273,1914,1915],{"class":275,"line":488},[273,1916,1917],{},"    to the board. Show an empty state if no assigned cards.\n",[273,1919,1920],{"class":275,"line":493},[273,1921,1922],{},"  - Right column: \"Overdue\" — a list of overdue cards. Each item shows\n",[273,1924,1925],{"class":275,"line":498},[273,1926,1927],{},"    title, board name, assignee name, and how many days overdue. Show an\n",[273,1929,1930],{"class":275,"line":503},[273,1931,1932],{},"    empty state if nothing is overdue.\n",[273,1934,1935],{"class":275,"line":508},[273,1936,292],{"emptyLinePlaceholder":291},[273,1938,1939],{"class":275,"line":514},[273,1940,1941],{},"Remove any placeholder\u002Fscaffolding content that was in the dashboard before.\n",[273,1943,1944],{"class":275,"line":520},[273,1945,1946],{},"Keep the `UDashboardPanel` wrapper with navbar and sidebar collapse.\n",[1728,1948,251],{"id":1949},"realtime",[263,1951,1953],{"className":265,"code":1952,"language":267,"meta":268,"style":268},"Add Supabase Realtime sync for the new tables so changes appear instantly\nacross browser sessions.\n\nDatabase migration (via Supabase MCP):\n\nEnable realtime publication and full replica identity for the new tables:\n\n```sql\nALTER PUBLICATION supabase_realtime ADD TABLE boards, columns, cards, card_comments, card_checklist_items;\nALTER TABLE boards REPLICA IDENTITY FULL;\nALTER TABLE columns REPLICA IDENTITY FULL;\nALTER TABLE cards REPLICA IDENTITY FULL;\nALTER TABLE card_comments REPLICA IDENTITY FULL;\nALTER TABLE card_checklist_items REPLICA IDENTITY FULL;\n```\n\nUpdate `app\u002Fcomposables\u002FuseRealtime.ts`:\n\n1. Add `\"boards\"`, `\"columns\"`, `\"cards\"`, `\"card_comments\"`, and\n   `\"card_checklist_items\"` to the `RealtimeTable` union type.\n2. In the `setup()` function, add `.on(\"postgres_changes\", ...)` handlers\n   for each new table, filtered by `team_id=eq.${teamId}` where the table\n   has a team_id column. For `card_comments` and `card_checklist_items`,\n   subscribe unfiltered since they lack a direct team_id — RLS already\n   scopes the events.\n\nIntegration — use `onTableDebounced` from `useRealtime()` inline in each\npage. Do NOT create separate `useRealtimeX` composable files:\n\n- In `app\u002Fpages\u002Fapp\u002Fboards\u002F[boardId].vue`:\n  `const { onTableDebounced } = useRealtime()`\n  `onTableDebounced([\"cards\", \"columns\", \"card_comments\", \"card_checklist_items\"], () => refreshBoard())`\n- In `app\u002Fpages\u002Fapp\u002Fboards\u002Findex.vue`:\n  `const { onTableDebounced } = useRealtime()`\n  `onTableDebounced(\"boards\", () => refreshBoards())`\n- In the dashboard stats page:\n  `const { onTableDebounced } = useRealtime()`\n  `onTableDebounced([\"cards\", \"boards\"], () => refreshStats())`\n",[270,1954,1955,1960,1965,1969,1974,1978,1983,1987,1992,1997,2002,2007,2012,2017,2022,2026,2030,2035,2039,2044,2049,2054,2059,2064,2069,2074,2078,2083,2088,2092,2097,2102,2107,2112,2116,2121,2126,2130],{"__ignoreMap":268},[273,1956,1957],{"class":275,"line":276},[273,1958,1959],{},"Add Supabase Realtime sync for the new tables so changes appear instantly\n",[273,1961,1962],{"class":275,"line":282},[273,1963,1964],{},"across browser sessions.\n",[273,1966,1967],{"class":275,"line":288},[273,1968,292],{"emptyLinePlaceholder":291},[273,1970,1971],{"class":275,"line":295},[273,1972,1973],{},"Database migration (via Supabase MCP):\n",[273,1975,1976],{"class":275,"line":301},[273,1977,292],{"emptyLinePlaceholder":291},[273,1979,1980],{"class":275,"line":306},[273,1981,1982],{},"Enable realtime publication and full replica identity for the new tables:\n",[273,1984,1985],{"class":275,"line":312},[273,1986,292],{"emptyLinePlaceholder":291},[273,1988,1989],{"class":275,"line":317},[273,1990,1991],{},"```sql\n",[273,1993,1994],{"class":275,"line":323},[273,1995,1996],{},"ALTER PUBLICATION supabase_realtime ADD TABLE boards, columns, cards, card_comments, card_checklist_items;\n",[273,1998,1999],{"class":275,"line":329},[273,2000,2001],{},"ALTER TABLE boards REPLICA IDENTITY FULL;\n",[273,2003,2004],{"class":275,"line":335},[273,2005,2006],{},"ALTER TABLE columns REPLICA IDENTITY FULL;\n",[273,2008,2009],{"class":275,"line":341},[273,2010,2011],{},"ALTER TABLE cards REPLICA IDENTITY FULL;\n",[273,2013,2014],{"class":275,"line":347},[273,2015,2016],{},"ALTER TABLE card_comments REPLICA IDENTITY FULL;\n",[273,2018,2019],{"class":275,"line":353},[273,2020,2021],{},"ALTER TABLE card_checklist_items REPLICA IDENTITY FULL;\n",[273,2023,2024],{"class":275,"line":359},[273,2025,675],{},[273,2027,2028],{"class":275,"line":365},[273,2029,292],{"emptyLinePlaceholder":291},[273,2031,2032],{"class":275,"line":371},[273,2033,2034],{},"Update `app\u002Fcomposables\u002FuseRealtime.ts`:\n",[273,2036,2037],{"class":275,"line":376},[273,2038,292],{"emptyLinePlaceholder":291},[273,2040,2041],{"class":275,"line":382},[273,2042,2043],{},"1. Add `\"boards\"`, `\"columns\"`, `\"cards\"`, `\"card_comments\"`, and\n",[273,2045,2046],{"class":275,"line":388},[273,2047,2048],{},"   `\"card_checklist_items\"` to the `RealtimeTable` union type.\n",[273,2050,2051],{"class":275,"line":393},[273,2052,2053],{},"2. In the `setup()` function, add `.on(\"postgres_changes\", ...)` handlers\n",[273,2055,2056],{"class":275,"line":399},[273,2057,2058],{},"   for each new table, filtered by `team_id=eq.${teamId}` where the table\n",[273,2060,2061],{"class":275,"line":405},[273,2062,2063],{},"   has a team_id column. For `card_comments` and `card_checklist_items`,\n",[273,2065,2066],{"class":275,"line":410},[273,2067,2068],{},"   subscribe unfiltered since they lack a direct team_id — RLS already\n",[273,2070,2071],{"class":275,"line":415},[273,2072,2073],{},"   scopes the events.\n",[273,2075,2076],{"class":275,"line":420},[273,2077,292],{"emptyLinePlaceholder":291},[273,2079,2080],{"class":275,"line":426},[273,2081,2082],{},"Integration — use `onTableDebounced` from `useRealtime()` inline in each\n",[273,2084,2085],{"class":275,"line":432},[273,2086,2087],{},"page. Do NOT create separate `useRealtimeX` composable files:\n",[273,2089,2090],{"class":275,"line":437},[273,2091,292],{"emptyLinePlaceholder":291},[273,2093,2094],{"class":275,"line":443},[273,2095,2096],{},"- In `app\u002Fpages\u002Fapp\u002Fboards\u002F[boardId].vue`:\n",[273,2098,2099],{"class":275,"line":448},[273,2100,2101],{},"  `const { onTableDebounced } = useRealtime()`\n",[273,2103,2104],{"class":275,"line":453},[273,2105,2106],{},"  `onTableDebounced([\"cards\", \"columns\", \"card_comments\", \"card_checklist_items\"], () => refreshBoard())`\n",[273,2108,2109],{"class":275,"line":459},[273,2110,2111],{},"- In `app\u002Fpages\u002Fapp\u002Fboards\u002Findex.vue`:\n",[273,2113,2114],{"class":275,"line":465},[273,2115,2101],{},[273,2117,2118],{"class":275,"line":471},[273,2119,2120],{},"  `onTableDebounced(\"boards\", () => refreshBoards())`\n",[273,2122,2123],{"class":275,"line":477},[273,2124,2125],{},"- In the dashboard stats page:\n",[273,2127,2128],{"class":275,"line":483},[273,2129,2101],{},[273,2131,2132],{"class":275,"line":488},[273,2133,2134],{},"  `onTableDebounced([\"cards\", \"boards\"], () => refreshStats())`\n",[258,2136,2138],{"id":2137},"what-you-built","What You Built",[198,2140,2141],{},"Starting from a template that handled auth, teams, roles, and permissions, you added:",[2143,2144,2145,2150,2155,2160,2166,2171],"ol",{},[223,2146,2147,2149],{},[201,2148,227],{}," — multiple project boards with color-coding and archiving",[223,2151,2152,2154],{},[201,2153,233],{}," — customizable pipeline stages with card limits and drag-and-drop reordering",[223,2156,2157,2159],{},[201,2158,239],{}," — full task cards with assignees, priorities, due dates, labels, checklists, and comments",[223,2161,2162,2165],{},[201,2163,2164],{},"Drag and drop"," — cards move between columns with optimistic updates and API persistence",[223,2167,2168,2170],{},[201,2169,1731],{}," — personal task list, overdue tracking, and team workload",[223,2172,2173,252],{},[201,2174,251],{},[198,2176,2177,2178,2181],{},"Every feature follows the same patterns: permission-gated server routes, team-scoped data, Nuxt UI components, and the conventions defined in ",[270,2179,2180],{},"CLAUDE.md",".",[258,2183,2185],{"id":2184},"whats-next","What's Next",[220,2187,2188,2195],{},[223,2189,2190,2194],{},[201,2191,2192],{},[206,2193,59],{"href":60}," — let external tools create cards or update statuses programmatically",[223,2196,2197,2201,2202,2205],{},[201,2198,2199],{},[206,2200,143],{"href":144}," — the baked-in assistant is already wired to your new tables via ",[270,2203,2204],{},"tablePermissions",". Try it with \"What cards are overdue?\" or \"How many tasks did we complete this week?\"",[2207,2208,2209],"style",{},"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);}",{"title":268,"searchDepth":276,"depth":282,"links":2211},[2212,2213],{"id":2137,"depth":282,"text":2138},{"id":2184,"depth":282,"text":2185},"Build a project board with boards, columns, cards, drag-and-drop, and checklists","md",null,{},{"icon":89},{"title":86,"description":2214},"Ba6_UoFnNLyKvCvpmdU5aZ-VopUU9F5d8Ywva8f42oE",[2222,2224],{"title":81,"path":82,"stem":83,"description":2223,"icon":84,"children":-1},"Build a complete job management system — clients, jobs, dashboard, and realtime sync",{"title":91,"path":92,"stem":93,"description":2225,"icon":94,"children":-1},"Track products, warehouses, and stock movements with full audit history",1777092169440]