[{"data":1,"prerenderedAt":731},["ShallowReactive",2],{"navigation_docs_en":3,"-en-reference-performance":191,"-en-reference-performance-surround":726},[4,61,127,171],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":60},"Guide","i-lucide-book-open","\u002Fen\u002Fguide","en\u002F1.guide",[10,15,20,25,30,35,40,45,50,55],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fen\u002Fguide\u002Fintroduction","en\u002F1.guide\u002F01.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Install the App","\u002Fen\u002Fguide\u002Finstallation","en\u002F1.guide\u002F02.installation","i-lucide-smartphone",{"title":21,"path":22,"stem":23,"icon":24},"Authentication","\u002Fen\u002Fguide\u002Fauth","en\u002F1.guide\u002F03.auth","i-lucide-lock",{"title":26,"path":27,"stem":28,"icon":29},"Wallets","\u002Fen\u002Fguide\u002Fwallets","en\u002F1.guide\u002F04.wallets","i-lucide-wallet",{"title":31,"path":32,"stem":33,"icon":34},"Categories","\u002Fen\u002Fguide\u002Fcategories","en\u002F1.guide\u002F05.categories","i-lucide-tags",{"title":36,"path":37,"stem":38,"icon":39},"Transactions","\u002Fen\u002Fguide\u002Ftransactions","en\u002F1.guide\u002F06.transactions","i-lucide-receipt",{"title":41,"path":42,"stem":43,"icon":44},"Transfers","\u002Fen\u002Fguide\u002Ftransfers","en\u002F1.guide\u002F07.transfers","i-lucide-arrow-left-right",{"title":46,"path":47,"stem":48,"icon":49},"Statistics","\u002Fen\u002Fguide\u002Fstatistics","en\u002F1.guide\u002F08.statistics","i-lucide-bar-chart-3",{"title":51,"path":52,"stem":53,"icon":54},"Theme","\u002Fen\u002Fguide\u002Ftheme","en\u002F1.guide\u002F09.theme","i-lucide-palette",{"title":56,"path":57,"stem":58,"icon":59},"Settings","\u002Fen\u002Fguide\u002Fsettings","en\u002F1.guide\u002F10.settings","i-lucide-settings",false,{"title":62,"icon":63,"path":64,"stem":65,"children":66,"page":60},"Development","i-lucide-code","\u002Fen\u002Fdevelopment","en\u002F2.development",[67,72,77,82,87,92,97,102,122],{"title":68,"path":69,"stem":70,"icon":71},"Installation","\u002Fen\u002Fdevelopment\u002Finstallation","en\u002F2.development\u002F01.installation","i-lucide-download",{"title":73,"path":74,"stem":75,"icon":76},"Codebase Graph","\u002Fen\u002Fdevelopment\u002Funderstand-anything","en\u002F2.development\u002F02.understand-anything","i-lucide-network",{"title":78,"path":79,"stem":80,"icon":81},"Offline & PWA","\u002Fen\u002Fdevelopment\u002Foffline","en\u002F2.development\u002F03.offline","i-lucide-wifi-off",{"title":83,"path":84,"stem":85,"icon":86},"Data Migration History","\u002Fen\u002Fdevelopment\u002Fmigration","en\u002F2.development\u002F04.migration","i-lucide-database",{"title":88,"path":89,"stem":90,"icon":91},"Deployment","\u002Fen\u002Fdevelopment\u002Fdeployment","en\u002F2.development\u002F05.deployment","i-lucide-rocket",{"title":93,"path":94,"stem":95,"icon":96},"Testing","\u002Fen\u002Fdevelopment\u002Ftesting","en\u002F2.development\u002F06.testing","i-lucide-flask-conical",{"title":98,"path":99,"stem":100,"icon":101},"Date Utilities","\u002Fen\u002Fdevelopment\u002Fdate-utilities","en\u002F2.development\u002F07.date-utilities","i-lucide-calendar",{"title":103,"path":104,"stem":105,"children":106,"page":60},"Ai Workflow","\u002Fen\u002Fdevelopment\u002Fai-workflow","en\u002F2.development\u002F08.ai-workflow",[107,112,117],{"title":108,"path":109,"stem":110,"icon":111},"Overview","\u002Fen\u002Fdevelopment\u002Fai-workflow\u002Foverview","en\u002F2.development\u002F08.ai-workflow\u002F01.overview","i-lucide-bot",{"title":113,"path":114,"stem":115,"icon":116},"Agents","\u002Fen\u002Fdevelopment\u002Fai-workflow\u002Fagents","en\u002F2.development\u002F08.ai-workflow\u002F02.agents","i-lucide-users",{"title":118,"path":119,"stem":120,"icon":121},"Skills","\u002Fen\u002Fdevelopment\u002Fai-workflow\u002Fskills","en\u002F2.development\u002F08.ai-workflow\u002F03.skills","i-lucide-lightbulb",{"title":123,"path":124,"stem":125,"icon":126},"Troubleshooting","\u002Fen\u002Fdevelopment\u002Ftroubleshooting","en\u002F2.development\u002F09.troubleshooting","i-lucide-life-buoy",{"title":128,"icon":129,"path":130,"stem":131,"children":132,"page":60},"Reference","i-lucide-file-code","\u002Fen\u002Freference","en\u002F3.reference",[133,138,142,147,152,156,161,166],{"title":134,"path":135,"stem":136,"icon":137},"Architecture","\u002Fen\u002Freference\u002Farchitecture","en\u002F3.reference\u002F01.architecture","i-lucide-boxes",{"title":139,"path":140,"stem":141,"icon":44},"Transaction Types","\u002Fen\u002Freference\u002Ftransaction-types","en\u002F3.reference\u002F02.transaction-types",{"title":143,"path":144,"stem":145,"icon":146},"Sync","\u002Fen\u002Freference\u002Fsync","en\u002F3.reference\u002F03.sync","i-lucide-refresh-cw",{"title":148,"path":149,"stem":150,"icon":151},"Offline-first","\u002Fen\u002Freference\u002Foffline-first","en\u002F3.reference\u002F04.offline-first","i-lucide-list-ordered",{"title":153,"path":154,"stem":155,"icon":121},"Technical Decisions","\u002Fen\u002Freference\u002Ftech-decisions","en\u002F3.reference\u002F05.tech-decisions",{"title":157,"path":158,"stem":159,"icon":160},"Validation Strategy","\u002Fen\u002Freference\u002Fvalidation-strategy","en\u002F3.reference\u002F06.validation-strategy","i-lucide-shield-check",{"title":162,"path":163,"stem":164,"icon":165},"What Changed Since Firebase","\u002Fen\u002Freference\u002Ffirebase-migration","en\u002F3.reference\u002F07.firebase-migration","i-lucide-hamburger",{"title":167,"path":168,"stem":169,"icon":170},"Performance","\u002Fen\u002Freference\u002Fperformance","en\u002F3.reference\u002F08.performance","i-lucide-gauge",{"title":172,"icon":173,"path":174,"stem":175,"children":176,"page":60},"Premium","i-lucide-star","\u002Fen\u002Fpremium","en\u002F4.premium",[177,181,186],{"title":108,"path":178,"stem":179,"icon":180},"\u002Fen\u002Fpremium\u002Foverview","en\u002F4.premium\u002F01.overview","i-lucide-layers",{"title":182,"path":183,"stem":184,"icon":185},"Telegram Bot","\u002Fen\u002Fpremium\u002Ftelegram-bot","en\u002F4.premium\u002F02.telegram-bot","i-lucide-send",{"title":187,"path":188,"stem":189,"icon":190},"AI Chat","\u002Fen\u002Fpremium\u002Fai-chat","en\u002F4.premium\u002F03.ai-chat","i-lucide-sparkles",{"id":192,"title":167,"body":193,"description":718,"extension":719,"links":720,"meta":721,"navigation":722,"path":168,"seo":723,"stem":169,"__hash__":725},"docs_en\u002Fen\u002F3.reference\u002F08.performance.md",{"type":194,"value":195,"toc":712},"minimark",[196,205,210,213,257,266,299,303,409,413,416,593,639,643,653,708],[197,198,199,200,204],"p",{},"What keeps the cold start fast, and how to measure it. Reference numbers below were taken on a prod build (",[201,202,203],"code",{},"pnpm generate",") with a real account of ~8 000 transactions, desktop hardware, assets served from the service worker - treat them as relative, not absolute.",[206,207,209],"h2",{"id":208},"cold-start-budget","Cold-Start Budget",[197,211,212],{},"The boot is dominated by JS execution, not network (everything is SW-cached). Two paths fill the stores:",[214,215,216,229],"table",{},[217,218,219],"thead",{},[220,221,222,226],"tr",{},[223,224,225],"th",{},"Path",[223,227,228],{},"Data on screen",[230,231,232,245],"tbody",{},[220,233,234,242],{},[235,236,237,238,241],"td",{},"Blob cache prime (",[201,239,240],{},"cache:prime",", ~70 ms read)",[235,243,244],{},"~280 ms",[220,246,247,254],{},[235,248,249,250,253],{},"First ",[201,251,252],{},"trns"," watch emission from SQLite",[235,255,256],{},"~730 ms",[197,258,259,260,265],{},"That ~450 ms gap is why the ",[261,262,264],"a",{"href":263},"\u002Fen\u002Freference\u002Farchitecture#cache-first-cold-start","blob cache"," exists: its value is the early paint, not skipping the row transform (transforming 8k rows takes ~3 ms - the cost is the SQLite scan + worker transfer, not JS).",[197,267,268,269,273,274,277,278,277,281,277,284,277,287,289,290,292,293,295,296,298],{},"The five boot watches are ",[270,271,272],"strong",{},"serialized"," on the single PowerSync worker connection - their first emissions land stair-stepped (",[201,275,276],{},"user_settings"," → ",[201,279,280],{},"rates",[201,282,283],{},"categories",[201,285,286],{},"wallets",[201,288,252],{},"), with the full-table ",[201,291,252],{}," scan dominating. Merging the four small queries would save little; windowing ",[201,294,252],{}," by date was rejected because wallet totals need full history (see ",[261,297,153],{"href":154},").",[206,300,302],{"id":301},"bundle-what-loads-when","Bundle: What Loads When",[304,305,306,336,350,364,373,395],"ul",{},[307,308,309,315,316,319,320,323,324,327,328,331,332,335],"li",{},[270,310,311,314],{},[201,312,313],{},"@powersync\u002Fweb"," is lazy."," ",[201,317,318],{},"getPowerSyncDb()"," (",[201,321,322],{},"app\u002Fservices\u002Fpowersync\u002Fdb.ts",") dynamically imports the library, schema, and connector on first use, keeping ~230 KB out of the entry chunk - the login page and demo mode never parse it. Two details guard this: ",[201,325,326],{},"uploadErrorHandler.ts"," exists so the plugin's static import doesn't reach connector.ts, and ",[201,329,330],{},"uploadReconcile.ts"," compares op strings instead of importing the ",[201,333,334],{},"UpdateType"," enum.",[307,337,338,341,342,345,346,349],{},[270,339,340],{},"swiper is lazy."," The transaction form (",[201,343,344],{},"LazyTrnFormBottom"," \u002F ",[201,347,348],{},"LazyTrnFormSidebar"," in the default layout) mounts after its first open; an idle prefetch keeps that first open instant.",[307,351,352,355,356,359,360,363],{},[270,353,354],{},"echarts mounts on idle."," Charts are gated on ",[201,357,358],{},"useIdleMount()"," behind an equal-height placeholder (",[201,361,362],{},"stat\u002Fchart\u002FWrap.vue","), so the 450 KB echarts chunk never competes with the first paint.",[307,365,366,315,369,372],{},[270,367,368],{},"Dashboard skeleton.",[201,370,371],{},"StatPageSkeleton"," reserves the page shape while stores hydrate, so arriving data doesn't shift the layout.",[307,374,375,378,379,382,383,386,387,390,391,394],{},[270,376,377],{},"Icons are bundled."," All statically referenced icon names ship in the client bundle (",[201,380,381],{},"icon.clientBundle.icons"," in ",[201,384,385],{},"nuxt.config.ts",", including the ",[201,388,389],{},"hugeicons"," collection) - icons fetched from ",[201,392,393],{},"api.iconify.design"," at runtime pop in after first paint and shift layout (CLS). The icon scanner misses names inside dynamic ternaries, hence the explicit list. Only user-data icons (category\u002Fwallet icons outside the bundle) still fetch at runtime, cached by the SW (CacheFirst).",[307,396,397,400,401,404,405,408],{},[270,398,399],{},"SW precache is narrowed."," Of the eight emitted wa-sqlite WASM variants, only ",[201,402,403],{},"wa-sqlite-async.\u003Chash>.wasm"," (the one the worker actually loads - cipher builds need an ",[201,406,407],{},"encryptionKey"," we never pass) is precached, saving ~12 MB per service-worker install.",[206,410,412],{"id":411},"instrumentation","Instrumentation",[197,414,415],{},"Cold-start marks are always on (they cost nothing). In the browser console after a reload:",[417,418,423],"pre",{"className":419,"code":420,"language":421,"meta":422,"style":422},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","performance.getEntriesByType('measure')\n  .filter(m => \u002F^(ps:watch|cache:|trns:)\u002F.test(m.name))\n  .map(m => `${m.name}: ${Math.round(m.startTime)}ms +${Math.round(m.duration)}ms`)\n","js","",[201,424,425,457,515],{"__ignoreMap":422},[426,427,430,434,438,442,445,448,452,454],"span",{"class":428,"line":429},"line",1,[426,431,433],{"class":432},"sTEyZ","performance",[426,435,437],{"class":436},"sMK4o",".",[426,439,441],{"class":440},"s2Zo4","getEntriesByType",[426,443,444],{"class":432},"(",[426,446,447],{"class":436},"'",[426,449,451],{"class":450},"sfazB","measure",[426,453,447],{"class":436},[426,455,456],{"class":432},")\n",[426,458,460,463,466,468,472,476,479,483,485,488,491,494,496,499,502,504,507,510,512],{"class":428,"line":459},2,[426,461,462],{"class":436},"  .",[426,464,465],{"class":440},"filter",[426,467,444],{"class":432},[426,469,471],{"class":470},"sHdIc","m",[426,473,475],{"class":474},"spNyl"," =>",[426,477,478],{"class":436}," \u002F",[426,480,482],{"class":481},"s7zQu","^",[426,484,444],{"class":436},[426,486,487],{"class":450},"ps:watch",[426,489,490],{"class":436},"|",[426,492,493],{"class":450},"cache:",[426,495,490],{"class":436},[426,497,498],{"class":450},"trns:",[426,500,501],{"class":436},")\u002F",[426,503,437],{"class":436},[426,505,506],{"class":440},"test",[426,508,509],{"class":432},"(m",[426,511,437],{"class":436},[426,513,514],{"class":432},"name))\n",[426,516,518,520,523,525,527,529,532,534,536,539,542,545,548,551,553,556,558,560,563,565,568,570,572,574,576,578,580,583,585,588,591],{"class":428,"line":517},3,[426,519,462],{"class":436},[426,521,522],{"class":440},"map",[426,524,444],{"class":432},[426,526,471],{"class":470},[426,528,475],{"class":474},[426,530,531],{"class":436}," `${",[426,533,471],{"class":432},[426,535,437],{"class":436},[426,537,538],{"class":432},"name",[426,540,541],{"class":436},"}",[426,543,544],{"class":450},": ",[426,546,547],{"class":436},"${",[426,549,550],{"class":432},"Math",[426,552,437],{"class":436},[426,554,555],{"class":440},"round",[426,557,509],{"class":432},[426,559,437],{"class":436},[426,561,562],{"class":432},"startTime)",[426,564,541],{"class":436},[426,566,567],{"class":450},"ms +",[426,569,547],{"class":436},[426,571,550],{"class":432},[426,573,437],{"class":436},[426,575,555],{"class":440},[426,577,509],{"class":432},[426,579,437],{"class":436},[426,581,582],{"class":432},"duration)",[426,584,541],{"class":436},[426,586,587],{"class":450},"ms",[426,589,590],{"class":436},"`",[426,592,456],{"class":432},[214,594,595,605],{},[217,596,597],{},[220,598,599,602],{},[223,600,601],{},"Mark",[223,603,604],{},"Meaning",[230,606,607,616,626],{},[220,608,609,613],{},[235,610,611],{},[201,612,240],{},[235,614,615],{},"Blob cache read + store prime (cold-start paint path)",[220,617,618,623],{},[235,619,620],{},[201,621,622],{},"ps:watch:\u003Ctable>",[235,624,625],{},"Subscribe → first SQLite emission per boot watch",[220,627,628,633],{},[235,629,630],{},[201,631,632],{},"trns:first-transform",[235,634,635,636,638],{},"Row → item transform cost of the first ",[201,637,252],{}," emission",[206,640,642],{"id":641},"rejected-approaches","Rejected Approaches",[197,644,645,646,648,649,652],{},"Documented so they aren't re-attempted without new data (details in ",[261,647,153],{"href":154}," and ",[201,650,651],{},"todo.md","):",[304,654,655,669,678,695],{},[307,656,657,660,661,664,665,668],{},[270,658,659],{},"OPFS VFS"," instead of ",[201,662,663],{},"IDBBatchAtomicVFS"," - cold start is bound by ",[201,666,667],{},"db.init"," + query serialization, not page I\u002FO.",[307,670,671,677],{},[270,672,673,674,676],{},"Date-windowed ",[201,675,252],{}," watch"," - breaks wallet totals, which need full history.",[307,679,680,686,687,690,691,694],{},[270,681,682,683],{},"Splitting vendor chunks via ",[201,684,685],{},"manualChunks"," - fragmenting reka-ui breaks ",[201,688,689],{},"inject","\u002F",[201,692,693],{},"createContext"," in production builds.",[307,696,697,703,704,707],{},[270,698,699,702],{},[201,700,701],{},"Map","-based store items"," - saves single-digit ms per write at 8k rows, but breaks every reader of the plain-object ",[201,705,706],{},"items"," shape.",[709,710,711],"style",{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html .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":422,"searchDepth":459,"depth":459,"links":713},[714,715,716,717],{"id":208,"depth":459,"text":209},{"id":301,"depth":459,"text":302},{"id":411,"depth":459,"text":412},{"id":641,"depth":459,"text":642},"Cold-start budget, lazy chunks, icon bundling, and the built-in instrumentation marks.","md",null,{},{"icon":170},{"title":167,"description":724},"How Finapp keeps cold start fast - cache-first paint, lazy PowerSync\u002Fswiper\u002Fecharts chunks, icon bundling, narrowed SW precache, and performance marks.","YMCHm6o0209tyxCMQeN6vVtsVY_QVrAgc_WXGZFVd50",[727,729],{"title":162,"path":163,"stem":164,"description":728,"icon":165,"children":-1},"What changed between v6 (Firebase) and the current Supabase + PowerSync app - backend, performance, security, tests, and architecture.",{"title":108,"path":178,"stem":179,"description":730,"icon":180,"children":-1},"Add a Telegram bot and AI-powered transaction parsing to Finapp via the Premium Nuxt Layer.",1782114344908]