[{"data":1,"prerenderedAt":1095},["ShallowReactive",2],{"navigation:en":3,"\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fnotifications:en":311},[4],{"title":5,"path":6,"stem":7,"children":8,"icon":10},"Nuxfire Docs","\u002Fdocs","docs\u002F0.index",[9,11,152],{"title":5,"path":6,"stem":7,"icon":10},"i-lucide-layout-grid",{"title":12,"path":13,"stem":14,"children":15,"icon":151},"SaaS Starter Kit","\u002Fdocs\u002Fsaas-starter-kit","docs\u002F1.saas-starter-kit\u002F0.index",[16,19,63,129],{"title":17,"path":13,"stem":14,"icon":18},"What is the SaaS Starter Kit?","i-lucide-info",{"title":20,"icon":21,"hide":22,"path":23,"stem":24,"children":25,"page":62},"Getting Started","i-lucide-play",true,"\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started","docs\u002F1.saas-starter-kit\u002F1.getting-started",[26,30,34,38,42,46,50,54,58],{"title":27,"path":28,"stem":29},"Quick Start","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fquick-start","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F0.quick-start",{"title":31,"path":32,"stem":33},"Installation","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Finstallation","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F1.installation",{"title":35,"path":36,"stem":37},"Cloudflare Access","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fcloudflare-access","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F2.cloudflare-access",{"title":39,"path":40,"stem":41},"Configuration","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fconfiguration","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F3.configuration",{"title":43,"path":44,"stem":45},"Secrets","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fsecrets","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F4.secrets",{"title":47,"path":48,"stem":49},"Environment Variables","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fenvironment-variables","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F5.environment-variables",{"title":51,"path":52,"stem":53},"Deploy","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fdeploy","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F6.deploy",{"title":55,"path":56,"stem":57},"Local Development","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Flocal-development","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F7.local-development",{"title":59,"path":60,"stem":61},"Project Structure","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fproject-structure","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F8.project-structure",false,{"title":64,"icon":65,"hide":22,"path":66,"stem":67,"children":68,"page":62},"Features","i-lucide-box","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures","docs\u002F1.saas-starter-kit\u002F2.features",[69,73,77,81,85,89,93,97,101,105,109,113,117,121,125],{"title":70,"path":71,"stem":72},"Authentication","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fauth","docs\u002F1.saas-starter-kit\u002F2.features\u002F1.auth",{"title":74,"path":75,"stem":76},"Rate Limiting","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Frate-limiting","docs\u002F1.saas-starter-kit\u002F2.features\u002F10.rate-limiting",{"title":78,"path":79,"stem":80},"WebSockets","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fwebsockets","docs\u002F1.saas-starter-kit\u002F2.features\u002F11.websockets",{"title":82,"path":83,"stem":84},"Blog","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fblog","docs\u002F1.saas-starter-kit\u002F2.features\u002F14.blog",{"title":86,"path":87,"stem":88},"Documentation","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fdocumentation","docs\u002F1.saas-starter-kit\u002F2.features\u002F15.documentation",{"title":90,"path":91,"stem":92},"Analytics","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fanalytics","docs\u002F1.saas-starter-kit\u002F2.features\u002F16.analytics",{"title":94,"path":95,"stem":96},"Platform Admin Console","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fplatform-admin","docs\u002F1.saas-starter-kit\u002F2.features\u002F17.platform-admin",{"title":98,"path":99,"stem":100},"Database","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fdatabase","docs\u002F1.saas-starter-kit\u002F2.features\u002F2.database",{"title":102,"path":103,"stem":104},"Payments & Billing","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fpayments","docs\u002F1.saas-starter-kit\u002F2.features\u002F3.payments",{"title":106,"path":107,"stem":108},"Emails","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Femails","docs\u002F1.saas-starter-kit\u002F2.features\u002F4.emails",{"title":110,"path":111,"stem":112},"Notifications","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fnotifications","docs\u002F1.saas-starter-kit\u002F2.features\u002F5.notifications",{"title":114,"path":115,"stem":116},"Role-Based Access Control","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Frbac","docs\u002F1.saas-starter-kit\u002F2.features\u002F6.rbac",{"title":118,"path":119,"stem":120},"Async Jobs","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fasync-jobs","docs\u002F1.saas-starter-kit\u002F2.features\u002F7.async-jobs",{"title":122,"path":123,"stem":124},"Storage","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fstorage","docs\u002F1.saas-starter-kit\u002F2.features\u002F8.storage",{"title":126,"path":127,"stem":128},"Internationalization","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Finternationalization","docs\u002F1.saas-starter-kit\u002F2.features\u002F9.internationalization",{"title":130,"icon":131,"path":132,"stem":133,"children":134,"page":62},"Concepts","i-lucide-graduation-cap","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts","docs\u002F1.saas-starter-kit\u002F3.concepts",[135,139,143,147],{"title":136,"path":137,"stem":138},"Technology Stack","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts\u002Ftech-stack","docs\u002F1.saas-starter-kit\u002F3.concepts\u002F0.tech-stack",{"title":140,"path":141,"stem":142},"SST.dev","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts\u002Fsst","docs\u002F1.saas-starter-kit\u002F3.concepts\u002F1.sst",{"title":144,"path":145,"stem":146},"NuxtHub","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts\u002Fnuxt-hub","docs\u002F1.saas-starter-kit\u002F3.concepts\u002F2.nuxt-hub",{"title":148,"path":149,"stem":150},"Design System & Branding","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts\u002Fdesign-system","docs\u002F1.saas-starter-kit\u002F3.concepts\u002F3.design-system","i-lucide-zap",{"title":153,"path":154,"stem":155,"children":156,"icon":159},"Fireskills","\u002Fdocs\u002Ffireskills","docs\u002F2.fireskills\u002F0.index",[157,160,219,249,275,293],{"title":158,"path":154,"stem":155,"icon":159},"What is Fireskills?","i-lucide-flame",{"title":20,"icon":21,"hide":22,"path":161,"stem":162,"children":163,"page":62},"\u002Fdocs\u002Ffireskills\u002Fgetting-started","docs\u002F2.fireskills\u002F1.getting-started",[164,167,171,175,179,183,187,191,195,199,203,207,211,215],{"title":27,"path":165,"stem":166},"\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fquick-start","docs\u002F2.fireskills\u002F1.getting-started\u002F00.quick-start",{"title":168,"path":169,"stem":170},"Workflow & Governance","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fworkflow","docs\u002F2.fireskills\u002F1.getting-started\u002F01.workflow",{"title":172,"path":173,"stem":174},"Project Context","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fproject-context","docs\u002F2.fireskills\u002F1.getting-started\u002F02.project-context",{"title":176,"path":177,"stem":178},"Step 0: Setup","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-setup","docs\u002F2.fireskills\u002F1.getting-started\u002F03.step-setup",{"title":180,"path":181,"stem":182},"Step 1: Inbox","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-inbox","docs\u002F2.fireskills\u002F1.getting-started\u002F04.step-inbox",{"title":184,"path":185,"stem":186},"Step 2: Backlog","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-backlog","docs\u002F2.fireskills\u002F1.getting-started\u002F05.step-backlog",{"title":188,"path":189,"stem":190},"Step 3: Specify","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-specify","docs\u002F2.fireskills\u002F1.getting-started\u002F06.step-specify",{"title":192,"path":193,"stem":194},"Step 4: Validate","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-validate","docs\u002F2.fireskills\u002F1.getting-started\u002F07.step-validate",{"title":196,"path":197,"stem":198},"Step 5: Tasks","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-tasks","docs\u002F2.fireskills\u002F1.getting-started\u002F08.step-tasks",{"title":200,"path":201,"stem":202},"Step 6: TDD\u002FBDD","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-tdd-bdd","docs\u002F2.fireskills\u002F1.getting-started\u002F09.step-tdd-bdd",{"title":204,"path":205,"stem":206},"Step 7: Implement","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-implement","docs\u002F2.fireskills\u002F1.getting-started\u002F10.step-implement",{"title":208,"path":209,"stem":210},"Step 8: Documentator","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-documentator","docs\u002F2.fireskills\u002F1.getting-started\u002F11.step-documentator",{"title":212,"path":213,"stem":214},"Late Changes","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Flate-changes","docs\u002F2.fireskills\u002F1.getting-started\u002F12.late-changes",{"title":216,"path":217,"stem":218},"Other Entry Points","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fother-entry-points","docs\u002F2.fireskills\u002F1.getting-started\u002F13.other-entry-points",{"title":220,"icon":221,"hide":22,"path":222,"stem":223,"children":224,"page":62},"Methodology","i-lucide-git-branch","\u002Fdocs\u002Ffireskills\u002Fmethodology","docs\u002F2.fireskills\u002F2.methodology",[225,229,233,237,241,245],{"title":226,"path":227,"stem":228},"The Three Acts","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Fthree-acts","docs\u002F2.fireskills\u002F2.methodology\u002F0.three-acts",{"title":230,"path":231,"stem":232},"The Three Gates","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Fthree-gates","docs\u002F2.fireskills\u002F2.methodology\u002F1.three-gates",{"title":234,"path":235,"stem":236},"The Canonical Spec","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Fcanonical-spec","docs\u002F2.fireskills\u002F2.methodology\u002F2.canonical-spec",{"title":238,"path":239,"stem":240},"Lifecycle States","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Flifecycle-states","docs\u002F2.fireskills\u002F2.methodology\u002F3.lifecycle-states",{"title":242,"path":243,"stem":244},"Effort Scale","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Feffort-scale","docs\u002F2.fireskills\u002F2.methodology\u002F4.effort-scale",{"title":246,"path":247,"stem":248},"Milestones & Governance","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Fgovernance-and-milestones","docs\u002F2.fireskills\u002F2.methodology\u002F5.governance-and-milestones",{"title":250,"icon":251,"hide":22,"path":252,"stem":253,"children":254,"page":62},"Skills","i-lucide-layers","\u002Fdocs\u002Ffireskills\u002Fskills","docs\u002F2.fireskills\u002F3.skills",[255,259,263,267,271],{"title":256,"path":257,"stem":258},"Catalog","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fcatalog","docs\u002F2.fireskills\u002F3.skills\u002F0.catalog",{"title":260,"path":261,"stem":262},"Act I: Define","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fact-1-define","docs\u002F2.fireskills\u002F3.skills\u002F1.act-1-define",{"title":264,"path":265,"stem":266},"Act II: Design & Prove","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fact-2-design-prove","docs\u002F2.fireskills\u002F3.skills\u002F2.act-2-design-prove",{"title":268,"path":269,"stem":270},"Act III: Deliver","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fact-3-deliver","docs\u002F2.fireskills\u002F3.skills\u002F3.act-3-deliver",{"title":272,"path":273,"stem":274},"Governance & Decisions","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fgovernance-and-decisions","docs\u002F2.fireskills\u002F3.skills\u002F4.governance-and-decisions",{"title":276,"icon":277,"hide":22,"path":278,"stem":279,"children":280,"page":62},"Specialists","i-lucide-sparkles","\u002Fdocs\u002Ffireskills\u002Fspecialists","docs\u002F2.fireskills\u002F4.specialists",[281,285,289],{"title":282,"path":283,"stem":284},"Overview","\u002Fdocs\u002Ffireskills\u002Fspecialists\u002Foverview","docs\u002F2.fireskills\u002F4.specialists\u002F0.overview",{"title":286,"path":287,"stem":288},"Nuxfire Stack","\u002Fdocs\u002Ffireskills\u002Fspecialists\u002Fnuxfire-stack","docs\u002F2.fireskills\u002F4.specialists\u002F1.nuxfire-stack",{"title":290,"path":291,"stem":292},"Engineering Specialists","\u002Fdocs\u002Ffireskills\u002Fspecialists\u002Fengineering-specialists","docs\u002F2.fireskills\u002F4.specialists\u002F2.engineering-specialists",{"title":294,"icon":295,"hide":22,"path":296,"stem":297,"children":298,"page":62},"CLI & Tooling","i-lucide-terminal","\u002Fdocs\u002Ffireskills\u002Fcli","docs\u002F2.fireskills\u002F5.cli",[299,303,307],{"title":300,"path":301,"stem":302},"CLI Overview","\u002Fdocs\u002Ffireskills\u002Fcli\u002Foverview","docs\u002F2.fireskills\u002F5.cli\u002F0.overview",{"title":304,"path":305,"stem":306},"Commands","\u002Fdocs\u002Ffireskills\u002Fcli\u002Fcommands","docs\u002F2.fireskills\u002F5.cli\u002F1.commands",{"title":308,"path":309,"stem":310},"Safety Scripts","\u002Fdocs\u002Ffireskills\u002Fcli\u002Fgovernance-scripts","docs\u002F2.fireskills\u002F5.cli\u002F2.governance-scripts",{"id":312,"title":110,"body":313,"description":1090,"extension":1091,"meta":1092,"navigation":22,"path":111,"seo":1093,"stem":112,"__hash__":1094},"docs\u002Fdocs\u002F1.saas-starter-kit\u002F2.features\u002F5.notifications.md",{"type":314,"value":315,"toc":1079},"minimark",[316,321,341,345,350,358,378,676,687,691,701,731,735,741,745,753,757,779,1036,1054,1058,1061,1075],[317,318,320],"h2",{"id":319},"whats-included","What's Included",[322,323,324,328,335,338],"ul",{},[325,326,327],"li",{},"Real-time notifications delivered over WebSockets",[325,329,330,331],{},"Per-user enable\u002Fdisable preference, persisted in ",[332,333,334],"code",{},"localStorage",[325,336,337],{},"Unread count tracking",[325,339,340],{},"A notification slideover UI",[317,342,344],{"id":343},"architecture","Architecture",[346,347,349],"h3",{"id":348},"backend","Backend",[351,352,353,354,357],"p",{},"The notifications API is the tRPC router at ",[332,355,356],{},"layers\u002Fnotifications\u002Fserver\u002Ftrpc\u002Fnotifications.ts",":",[322,359,360,366,372],{},[325,361,362,365],{},[332,363,364],{},"list"," — this user's notifications, newest first.",[325,367,368,371],{},[332,369,370],{},"create"," — inserts a notification, then immediately publishes it over WebSockets in the same call.",[325,373,374,377],{},[332,375,376],{},"updateReadStatus"," — marks a batch of notification ids read.",[379,380,385],"pre",{"className":381,"code":382,"filename":356,"language":383,"meta":384,"style":384},"language-typescript shiki shiki-themes material-theme-lighter github-light github-dark monokai","create: protectedProcedure\n  .input(z.object({ title: z.string(), message: z.string() }))\n  .mutation(async ({ ctx: { user, sendWebsocketsEvent }, input }) => {\n    const [notification] = await useDB().insert(notifications).values({ ...input, userId: user.id }).returning();\n    await sendWebsocketsEvent(user.id, { type: \"notification\", payload: notification });\n    return notification;\n  }),\n","typescript","",[332,386,387,402,467,518,603,655,665],{"__ignoreMap":384},[388,389,392,395,398],"span",{"class":390,"line":391},"line",1,[388,393,370],{"class":394},"sQqfL",[388,396,357],{"class":397},"swvn1",[388,399,401],{"class":400},"ss--_"," protectedProcedure\n",[388,403,405,408,412,415,418,421,424,427,431,433,436,438,441,444,447,450,452,454,456,458,461,464],{"class":390,"line":404},2,[388,406,407],{"class":397},"  .",[388,409,411],{"class":410},"sD0ED","input",[388,413,414],{"class":400},"(z",[388,416,417],{"class":397},".",[388,419,420],{"class":410},"object",[388,422,423],{"class":400},"(",[388,425,426],{"class":397},"{",[388,428,430],{"class":429},"squCx"," title",[388,432,357],{"class":397},[388,434,435],{"class":400}," z",[388,437,417],{"class":397},[388,439,440],{"class":410},"string",[388,442,443],{"class":400},"()",[388,445,446],{"class":397},",",[388,448,449],{"class":429}," message",[388,451,357],{"class":397},[388,453,435],{"class":400},[388,455,417],{"class":397},[388,457,440],{"class":410},[388,459,460],{"class":400},"() ",[388,462,463],{"class":397},"}",[388,465,466],{"class":400},"))\n",[388,468,470,472,475,477,481,484,488,490,493,497,499,502,505,508,511,515],{"class":390,"line":469},3,[388,471,407],{"class":397},[388,473,474],{"class":410},"mutation",[388,476,423],{"class":400},[388,478,480],{"class":479},"sTNss","async",[388,482,483],{"class":397}," ({",[388,485,487],{"class":486},"sIDdj"," ctx",[388,489,357],{"class":397},[388,491,492],{"class":397}," {",[388,494,496],{"class":495},"sQgqH"," user",[388,498,446],{"class":397},[388,500,501],{"class":495}," sendWebsocketsEvent",[388,503,504],{"class":397}," },",[388,506,507],{"class":495}," input",[388,509,510],{"class":397}," })",[388,512,514],{"class":513},"srJo8"," =>",[388,516,517],{"class":397}," {\n",[388,519,521,524,527,531,534,538,542,545,547,549,552,554,557,560,562,565,567,569,572,574,576,579,581,583,585,588,591,593,595,598,600],{"class":390,"line":520},4,[388,522,523],{"class":513},"    const",[388,525,526],{"class":397}," [",[388,528,530],{"class":529},"s91G_","notification",[388,532,533],{"class":397},"]",[388,535,537],{"class":536},"sGXK2"," =",[388,539,541],{"class":540},"sRxSC"," await",[388,543,544],{"class":410}," useDB",[388,546,443],{"class":429},[388,548,417],{"class":397},[388,550,551],{"class":410},"insert",[388,553,423],{"class":429},[388,555,556],{"class":400},"notifications",[388,558,559],{"class":429},")",[388,561,417],{"class":397},[388,563,564],{"class":410},"values",[388,566,423],{"class":429},[388,568,426],{"class":397},[388,570,571],{"class":536}," ...",[388,573,411],{"class":400},[388,575,446],{"class":397},[388,577,578],{"class":429}," userId",[388,580,357],{"class":397},[388,582,496],{"class":400},[388,584,417],{"class":397},[388,586,587],{"class":400},"id",[388,589,590],{"class":397}," }",[388,592,559],{"class":429},[388,594,417],{"class":397},[388,596,597],{"class":410},"returning",[388,599,443],{"class":429},[388,601,602],{"class":397},";\n",[388,604,606,609,611,613,616,618,620,622,624,627,629,633,636,639,641,644,646,649,651,653],{"class":390,"line":605},5,[388,607,608],{"class":540},"    await",[388,610,501],{"class":410},[388,612,423],{"class":429},[388,614,615],{"class":400},"user",[388,617,417],{"class":397},[388,619,587],{"class":400},[388,621,446],{"class":397},[388,623,492],{"class":397},[388,625,626],{"class":429}," type",[388,628,357],{"class":397},[388,630,632],{"class":631},"siCPE"," \"",[388,634,530],{"class":635},"sLACW",[388,637,638],{"class":631},"\"",[388,640,446],{"class":397},[388,642,643],{"class":429}," payload",[388,645,357],{"class":397},[388,647,648],{"class":400}," notification",[388,650,590],{"class":397},[388,652,559],{"class":429},[388,654,602],{"class":397},[388,656,658,661,663],{"class":390,"line":657},6,[388,659,660],{"class":540},"    return",[388,662,648],{"class":400},[388,664,602],{"class":397},[388,666,668,671,673],{"class":390,"line":667},7,[388,669,670],{"class":397},"  }",[388,672,559],{"class":400},[388,674,675],{"class":397},",\n",[351,677,678,679,682,683,686],{},"See ",[680,681,78],"a",{"href":79}," for how ",[332,684,685],{},"sendWebsocketsEvent"," reaches the browser.",[346,688,690],{"id":689},"frontend-state","Frontend state",[351,692,693,696,697,700],{},[332,694,695],{},"useNotificationsStore"," (",[332,698,699],{},"layers\u002Fnotifications\u002Fapp\u002Fcomposables\u002Fuse-notifications-store.ts",") is a Pinia store:",[322,702,703,706,718],{},[325,704,705],{},"Holds notifications keyed by id, plus a running unread count.",[325,707,708,711,712,715,716,417],{},[332,709,710],{},"initializeNotificationSettings()"," \u002F ",[332,713,714],{},"setNotificationsEnabled()"," persist the on\u002Foff preference to ",[332,717,334],{},[325,719,720,723,724,723,727,730],{},[332,721,722],{},"fetchAllNotifications()",", ",[332,725,726],{},"markAsRead()",[332,728,729],{},"createNotification()"," wrap the tRPC calls above.",[346,732,734],{"id":733},"real-time-updates","Real-time updates",[351,736,737,740],{},[332,738,739],{},"layers\u002Fnotifications\u002Fapp\u002Fplugins\u002Fws-notifications.client.ts"," subscribes to the current user's own topic through the WebSocket plugin; incoming events are pushed into the store, and toasts surface them when the preference is enabled.",[317,742,744],{"id":743},"components","Components",[322,746,747],{},[325,748,749,752],{},[332,750,751],{},"NotificationsSlideover.vue"," — the notification list UI, mounted in the team dashboard header's bell icon.",[317,754,756],{"id":755},"sending-to-another-user-the-admin-demo","Sending to another user: the admin demo",[351,758,759,762,763,767,768,771,772,775,776,778],{},[332,760,761],{},"notifications.create"," above only ever notifies the ",[764,765,766],"em",{},"calling"," user — there's no ",[332,769,770],{},"targetUserId",", because\nnothing decides who's allowed to notify whom yet (that needs a real cross-tenant role, which this starter\nkit doesn't model). What it does ship, as a demonstration of the same real-time pipeline aimed at someone\nelse: ",[332,773,774],{},"\u002Fadmin\u002Fnotifications"," (see ",[680,777,94],{"href":95},"),\na platform-admin-only page that picks a team, then a member of that team, and sends them a live\nnotification via a dedicated procedure:",[379,780,783],{"className":381,"code":781,"filename":782,"language":383,"meta":384,"style":384},"send: platformAdminProcedure\n  .input(z.object({ targetUserId: z.string(), title: z.string(), message: z.string() }))\n  .mutation(async ({ ctx: { sendWebsocketsEvent }, input }) => {\n    const [notification] = await useDB().insert(notifications)\n      .values({ title: input.title, message: input.message, userId: input.targetUserId })\n      .returning();\n    await sendWebsocketsEvent(input.targetUserId, { type: \"notification\", payload: notification });\n    return notification;\n  }),\n","layers\u002Fnotifications\u002Fserver\u002Ftrpc\u002Fadmin-notifications.ts",[332,784,785,795,856,886,915,966,976,1018,1027],{"__ignoreMap":384},[388,786,787,790,792],{"class":390,"line":391},[388,788,789],{"class":394},"send",[388,791,357],{"class":397},[388,793,794],{"class":400}," platformAdminProcedure\n",[388,796,797,799,801,803,805,807,809,811,814,816,818,820,822,824,826,828,830,832,834,836,838,840,842,844,846,848,850,852,854],{"class":390,"line":404},[388,798,407],{"class":397},[388,800,411],{"class":410},[388,802,414],{"class":400},[388,804,417],{"class":397},[388,806,420],{"class":410},[388,808,423],{"class":400},[388,810,426],{"class":397},[388,812,813],{"class":429}," targetUserId",[388,815,357],{"class":397},[388,817,435],{"class":400},[388,819,417],{"class":397},[388,821,440],{"class":410},[388,823,443],{"class":400},[388,825,446],{"class":397},[388,827,430],{"class":429},[388,829,357],{"class":397},[388,831,435],{"class":400},[388,833,417],{"class":397},[388,835,440],{"class":410},[388,837,443],{"class":400},[388,839,446],{"class":397},[388,841,449],{"class":429},[388,843,357],{"class":397},[388,845,435],{"class":400},[388,847,417],{"class":397},[388,849,440],{"class":410},[388,851,460],{"class":400},[388,853,463],{"class":397},[388,855,466],{"class":400},[388,857,858,860,862,864,866,868,870,872,874,876,878,880,882,884],{"class":390,"line":469},[388,859,407],{"class":397},[388,861,474],{"class":410},[388,863,423],{"class":400},[388,865,480],{"class":479},[388,867,483],{"class":397},[388,869,487],{"class":486},[388,871,357],{"class":397},[388,873,492],{"class":397},[388,875,501],{"class":495},[388,877,504],{"class":397},[388,879,507],{"class":495},[388,881,510],{"class":397},[388,883,514],{"class":513},[388,885,517],{"class":397},[388,887,888,890,892,894,896,898,900,902,904,906,908,910,912],{"class":390,"line":520},[388,889,523],{"class":513},[388,891,526],{"class":397},[388,893,530],{"class":529},[388,895,533],{"class":397},[388,897,537],{"class":536},[388,899,541],{"class":540},[388,901,544],{"class":410},[388,903,443],{"class":429},[388,905,417],{"class":397},[388,907,551],{"class":410},[388,909,423],{"class":429},[388,911,556],{"class":400},[388,913,914],{"class":429},")\n",[388,916,917,920,922,924,926,928,930,932,934,937,939,941,943,945,947,950,952,954,956,958,960,962,964],{"class":390,"line":605},[388,918,919],{"class":397},"      .",[388,921,564],{"class":410},[388,923,423],{"class":429},[388,925,426],{"class":397},[388,927,430],{"class":429},[388,929,357],{"class":397},[388,931,507],{"class":400},[388,933,417],{"class":397},[388,935,936],{"class":400},"title",[388,938,446],{"class":397},[388,940,449],{"class":429},[388,942,357],{"class":397},[388,944,507],{"class":400},[388,946,417],{"class":397},[388,948,949],{"class":400},"message",[388,951,446],{"class":397},[388,953,578],{"class":429},[388,955,357],{"class":397},[388,957,507],{"class":400},[388,959,417],{"class":397},[388,961,770],{"class":400},[388,963,590],{"class":397},[388,965,914],{"class":429},[388,967,968,970,972,974],{"class":390,"line":657},[388,969,919],{"class":397},[388,971,597],{"class":410},[388,973,443],{"class":429},[388,975,602],{"class":397},[388,977,978,980,982,984,986,988,990,992,994,996,998,1000,1002,1004,1006,1008,1010,1012,1014,1016],{"class":390,"line":667},[388,979,608],{"class":540},[388,981,501],{"class":410},[388,983,423],{"class":429},[388,985,411],{"class":400},[388,987,417],{"class":397},[388,989,770],{"class":400},[388,991,446],{"class":397},[388,993,492],{"class":397},[388,995,626],{"class":429},[388,997,357],{"class":397},[388,999,632],{"class":631},[388,1001,530],{"class":635},[388,1003,638],{"class":631},[388,1005,446],{"class":397},[388,1007,643],{"class":429},[388,1009,357],{"class":397},[388,1011,648],{"class":400},[388,1013,590],{"class":397},[388,1015,559],{"class":429},[388,1017,602],{"class":397},[388,1019,1021,1023,1025],{"class":390,"line":1020},8,[388,1022,660],{"class":540},[388,1024,648],{"class":400},[388,1026,602],{"class":397},[388,1028,1030,1032,1034],{"class":390,"line":1029},9,[388,1031,670],{"class":397},[388,1033,559],{"class":400},[388,1035,675],{"class":397},[351,1037,1038,1039,1041,1042,1045,1046,1049,1050,1053],{},"Same table, same ",[332,1040,685],{}," call as the tenant router — the only difference is ",[332,1043,1044],{},"userId"," comes\nfrom the input instead of the caller's own session, and the procedure sits behind ",[332,1047,1048],{},"platformAdminProcedure","\ninstead of ",[332,1051,1052],{},"protectedProcedure",". A real \"team owner notifies their members\" feature would reuse this exact\nshape once the app has a role that's allowed to call it.",[317,1055,1057],{"id":1056},"settings","Settings",[351,1059,1060],{},"The preference is exposed in two places, both reading and writing the same store state:",[322,1062,1063,1069],{},[325,1064,1065,1068],{},[332,1066,1067],{},"layers\u002Fnotifications\u002Fapp\u002Fpages\u002Faccount-settings\u002Fnotifications.vue"," — account-level.",[325,1070,1071,1074],{},[332,1072,1073],{},"layers\u002Fnotifications\u002Fapp\u002Fpages\u002F[[team]]\u002Fsettings\u002Fnotifications.vue"," — team-level.",[1076,1077,1078],"style",{},"html pre.shiki code .sQqfL, html code.shiki .sQqfL{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#F8F8F2}html pre.shiki code .swvn1, html code.shiki .swvn1{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .ss--_, html code.shiki .ss--_{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sD0ED, html code.shiki .sD0ED{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .squCx, html code.shiki .squCx{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sTNss, html code.shiki .sTNss{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sIDdj, html code.shiki .sIDdj{--shiki-light:#E53935;--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#F8F8F2}html pre.shiki code .sQgqH, html code.shiki .sQgqH{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit;--shiki-sepia:#FD971F;--shiki-sepia-font-style:italic}html pre.shiki code .srJo8, html code.shiki .srJo8{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .s91G_, html code.shiki .s91G_{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .sGXK2, html code.shiki .sGXK2{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sRxSC, html code.shiki .sRxSC{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#F92672;--shiki-sepia-font-style:inherit}html pre.shiki code .siCPE, html code.shiki .siCPE{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sLACW, html code.shiki .sLACW{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}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 .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":384,"searchDepth":404,"depth":404,"links":1080},[1081,1082,1087,1088,1089],{"id":319,"depth":404,"text":320},{"id":343,"depth":404,"text":344,"children":1083},[1084,1085,1086],{"id":348,"depth":469,"text":349},{"id":689,"depth":469,"text":690},{"id":733,"depth":469,"text":734},{"id":743,"depth":404,"text":744},{"id":755,"depth":404,"text":756},{"id":1056,"depth":404,"text":1057},"Real-time in-app notifications, delivered over WebSockets and backed by a Pinia store on the client.","md",{},{"title":110,"description":1090},"wZKWKYUSCRVN0NpjAr4J5b-5qrvP3TxaE6uPx0MrFYY",1790707485288]