[{"data":1,"prerenderedAt":4285},["ShallowReactive",2],{"navigation_docs_en":3,"-en-reference-firebase-migration":191,"-en-reference-firebase-migration-surround":4280},[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":162,"body":193,"description":4272,"extension":4273,"links":4274,"meta":4275,"navigation":4276,"path":163,"seo":4277,"stem":164,"__hash__":4279},"docs_en\u002Fen\u002F3.reference\u002F07.firebase-migration.md",{"type":194,"value":195,"toc":4238},"minimark",[196,230,243,248,450,455,501,505,632,643,646,650,1479,1483,1651,1655,1793,1796,1933,1937,2069,2073,2155,2159,2472,2476,2566,2570,2574,2577,2680,2684,2737,2803,2807,2840,2844,3206,3210,3469,3473,3539,3543,3658,3661,3785,3789,3793,3852,3856,4155,4158,4171],[197,198,199,223],"blockquote",{},[200,201,202,206,207,210,211,214,215,218,219,222],"p",{},[203,204,205],"strong",{},"Two eras."," Finapp's backend went from ",[203,208,209],{},"Firebase"," (v6 - Firebase Realtime Database + Firebase Auth) to the ",[203,212,213],{},"current"," stack: ",[203,216,217],{},"Supabase Postgres + PowerSync"," (offline-first) + ",[203,220,221],{},"Supabase Auth",".",[200,224,225,226,229],{},"This document is a side-by-side comparison of what changed. The app-level improvements (performance, refactoring, UX, TypeScript, i18n) and the backend, auth, sync, and dependency sections all describe the current codebase. See ",[227,228,134],"a",{"href":135}," for a full description of the current Supabase + PowerSync architecture.",[200,231,232,233,242],{},"Side-by-side comparison of what the app looks like on the ",[227,234,238],{"href":235,"rel":236},"https:\u002F\u002Fgithub.com\u002Filkome\u002Ffinapp\u002Ftree\u002Ffirebase",[237],"nofollow",[239,240,241],"code",{},"firebase"," branch (v6, Firebase) vs the current Supabase + PowerSync app.",[244,245,247],"h2",{"id":246},"backend","Backend",[249,250,251,265],"table",{},[252,253,254],"thead",{},[255,256,257,260,263],"tr",{},[258,259],"th",{},[258,261,262],{},"firebase (Firebase)",[258,264,213],{},[266,267,268,282,298,326,343,359,375,391,416,435],"tbody",{},[255,269,270,276,279],{},[271,272,273],"td",{},[203,274,275],{},"Database",[271,277,278],{},"Firebase Realtime Database",[271,280,281],{},"Supabase Postgres (replicated by PowerSync)",[255,283,284,289,295],{},[271,285,286],{},[203,287,288],{},"Auth",[271,290,291,292],{},"Firebase Auth via ",[239,293,294],{},"nuxt-vuefire",[271,296,297],{},"Supabase Auth (email\u002Fpassword)",[255,299,300,305,312],{},[271,301,302],{},[203,303,304],{},"Data access",[271,306,307,308,311],{},"Real-time listeners (",[239,309,310],{},"getDataAndWatch",")",[271,313,314,315,318,319,322,323],{},"Local SQLite via ",[239,316,317],{},"db.watch(...)",", writes via ",[239,320,321],{},"upsertRow"," \u002F ",[239,324,325],{},"deleteRow",[255,327,328,333,336],{},[271,329,330],{},[203,331,332],{},"Validation",[271,334,335],{},"Firebase security rules (JSON)",[271,337,338,339,342],{},"Postgres RLS (",[239,340,341],{},"auth.uid()",") + client-side Zod schemas",[255,344,345,350,353],{},[271,346,347],{},[203,348,349],{},"Server code",[271,351,352],{},"None (client-only SDK)",[271,354,355,356,311],{},"Supabase migrations + self-hosted PowerSync sync rules (per-user ",[239,357,358],{},"WHERE \"userId\" = auth.user_id()",[255,360,361,366,369],{},[271,362,363],{},[203,364,365],{},"Exchange rates",[271,367,368],{},"Client-side fetch",[271,370,371,374],{},[239,372,373],{},"rates"," table synced through PowerSync like any other table",[255,376,377,382,385],{},[271,378,379],{},[203,380,381],{},"User settings",[271,383,384],{},"localStorage only",[271,386,387,390],{},[239,388,389],{},"user_settings"," table (baseCurrency, locale), auto-created on signup by a trigger",[255,392,393,398,401],{},[271,394,395],{},[203,396,397],{},"Schema indexes",[271,399,400],{},"N\u002FA",[271,402,403,404,407,408,411,412,415],{},"Client SQLite indexes in ",[239,405,406],{},"AppSchema.ts"," (",[239,409,410],{},"userDate: ['userId','date']",", plus ",[239,413,414],{},"userId"," per table)",[255,417,418,423,425],{},[271,419,420],{},[203,421,422],{},"Migration",[271,424,400],{},[271,426,427,428,431,432,311],{},"Firebase JSON export -> transform -> PostgREST import via ",[239,429,430],{},"scripts\u002Fimport-firebase.mjs"," (see ",[227,433,434],{"href":84},"Migration History",[255,436,437,442,444],{},[271,438,439],{},[203,440,441],{},"Account deletion",[271,443,400],{},[271,445,446,447,311],{},"RLS-scoped delete of the user's rows + local wipe (",[239,448,449],{},"removeAllUserData",[451,452,454],"h3",{"id":453},"what-the-current-backend-gives","What the current backend gives",[456,457,458,464,473,485,495],"ul",{},[459,460,461,463],"li",{},[203,462,148],{}," - all reads\u002Fwrites hit local SQLite; PowerSync syncs in the background, so the app works fully offline",[459,465,466,469,470,472],{},[203,467,468],{},"Per-user isolation"," - Postgres RLS (",[239,471,341],{},") + PowerSync sync rules guarantee each user only ever syncs their own rows",[459,474,475,478,479,481,482],{},[203,476,477],{},"Typed client schema"," - ",[239,480,406],{}," defines the SQLite tables; row\u003C->item transforms are centralized in ",[239,483,484],{},"transforms.ts",[459,486,487,490,491,494],{},[203,488,489],{},"Automatic upload queue"," - local mutations are queued and uploaded by the connector (",[239,492,493],{},"uploadData","), with auto-reconcile on rejected ops",[459,496,497,500],{},[203,498,499],{},"Incremental sync"," - PowerSync streams only changed rows after the initial sync",[244,502,504],{"id":503},"store-pattern","Store Pattern",[249,506,507,517],{},[252,508,509],{},[255,510,511,513,515],{},[258,512],{},[258,514,241],{},[258,516,213],{},[266,518,519,544,567,584,597,616],{},[255,520,521,526,532],{},[271,522,523],{},[203,524,525],{},"Data source",[271,527,528,529],{},"Firebase listener -> ",[239,530,531],{},"setTrns()",[271,533,534,537,538,537,541],{},[239,535,536],{},"db.watch('SELECT * FROM ...')"," -> ",[239,539,540],{},"reconcile",[239,542,543],{},"setX()",[255,545,546,551,560],{},[271,547,548],{},[203,549,550],{},"Persist",[271,552,553,556,557],{},[239,554,555],{},"deepUnref()"," + immediate ",[239,558,559],{},"localforage.setItem()",[271,561,562,563,566],{},"PowerSync SQLite is the source of truth; a blob read-through cache (",[239,564,565],{},"useStoreCache.ts",") primes the first frame",[255,568,569,574,581],{},[271,570,571],{},[203,572,573],{},"Offline ops",[271,575,576,577,580],{},"Manual helpers in ",[239,578,579],{},"trns\u002Fhelpers.ts",", ad-hoc localStorage keys",[271,582,583],{},"None needed - PowerSync's built-in upload queue handles offline writes",[255,585,586,591,594],{},[271,587,588],{},[203,589,590],{},"ID system",[271,592,593],{},"Firebase push IDs",[271,595,596],{},"Client-generated UUIDs, stable across sync (no remap)",[255,598,599,604,607],{},[271,600,601],{},[203,602,603],{},"Optimistic UI",[271,605,606],{},"Partial (fire-and-forget)",[271,608,609,610,612,613,615],{},"Full: ",[239,611,321],{},"\u002F",[239,614,325],{}," write local SQLite -> watch re-emits -> connector uploads in the background",[255,617,618,623,626],{},[271,619,620],{},[203,621,622],{},"Sync verification",[271,624,625],{},"None",[271,627,628,629,311],{},"PowerSync checksums per bucket; rejected uploads trigger auto-reconcile (",[239,630,631],{},"uploadReconcile.ts",[200,633,634,635,638,639,642],{},"The ",[239,636,637],{},"vue-deepunref"," dependency is removed. Store data uses ",[239,640,641],{},"shallowRef",", so deep cloning on every persist was unnecessary.",[244,644,167],{"id":645},"performance",[451,647,649],{"id":648},"algorithm-improvements","Algorithm improvements",[249,651,652,666],{},[252,653,654],{},[255,655,656,659,661,663],{},[258,657,658],{},"What",[258,660,241],{},[258,662,213],{},[258,664,665],{},"Impact",[266,667,668,698,726,760,782,812,855,878,911,930,964,1000,1033,1055,1074,1100,1126,1152,1179,1195,1220,1255,1286,1317,1337,1366,1388,1418,1447],{},[255,669,670,675,685,695],{},[271,671,672],{},[203,673,674],{},"Wallet totals",[271,676,677,680,681,684],{},[239,678,679],{},"getWalletTotal(id)"," called per wallet in ",[239,682,683],{},"reduce","  - each call filters all trns: O(W×N)",[271,686,687,690,691,694],{},[239,688,689],{},"getWalletsTotals()"," single pass, distributes amounts via ",[239,692,693],{},"Map",": O(N), then O(1) lookup per wallet",[271,696,697],{},"5 wallets × 10K trns: ~50K → ~10K ops",[255,699,700,705,716,723],{},[271,701,702],{},[203,703,704],{},"Interval bucketing",[271,706,707,708,711,712,715],{},"Inline in ",[239,709,710],{},"Item.vue",": ",[239,713,714],{},"intervalsInRange.reduce()"," filtering all trns per interval: O(N×I)",[271,717,718,719,722],{},"Extracted to ",[239,720,721],{},"bucketTrnsByIntervals()",": single pass + binary search: O(N log I)",[271,724,725],{},"Year\u002F365 days\u002F5K trns: 1.8M → ~43K comparisons",[255,727,728,733,747,757],{},[271,729,730],{},[203,731,732],{},"Transaction filtering",[271,734,735,736,739,740,742,743,746],{},"6 sequential ",[239,737,738],{},".filter()"," chained via ",[239,741,683],{}," + ",[239,744,745],{},"walletsIds.some(w => includes(w))"," O(W²) + always sorts O(N log N)",[271,748,749,750,752,753,756],{},"Single ",[239,751,738],{}," with early returns + ",[239,754,755],{},"Set.has()"," O(1) + sorting optional",[271,758,759],{},"5 intermediate arrays + O(W²) → single pass + O(1); sort O(N log N) skipped when not needed",[255,761,762,767,773,779],{},[271,763,764],{},[203,765,766],{},"Wallet lookup in getTotal",[271,768,769,772],{},[239,770,771],{},"walletsIds.includes()",": O(W) per transfer",[271,774,775,778],{},[239,776,777],{},"new Set(walletsIds).has()",": O(1) per transfer",[271,780,781],{},"Linear → constant per lookup",[255,783,784,789,802,809],{},[271,785,786],{},[203,787,788],{},"Demo data generation",[271,790,791,794,795,742,798,801],{},[239,792,793],{},"{ ...acc }"," spread in ",[239,796,797],{},".reduce()",[239,799,800],{},"getTransactibleIds()"," called twice per iteration",[271,803,804,805,808],{},"Direct ",[239,806,807],{},"acc[i] = ..."," + lookups cached before loop",[271,810,811],{},"O(N²) → O(N)",[255,813,814,819,837,848],{},[271,815,816],{},[203,817,818],{},"Category statistics",[271,820,821,824,825,828,829,832,833,836],{},[239,822,823],{},"categoriesStat"," eagerly computes both ",[239,826,827],{},"grouped"," and ",[239,830,831],{},"ungrouped"," on every change; ",[239,834,835],{},"filter().at(0)"," for biggest",[271,838,839,840,843,844,847],{},"Lazy ",[239,841,842],{},"computed()"," per variant, only accessed one executes; ",[239,845,846],{},".find()"," stops at first match",[271,849,850,851,854],{},"Up to 2× fewer ",[239,852,853],{},"computeCategoriesWithData"," calls",[255,856,857,862,868,875],{},[271,858,859],{},[203,860,861],{},"Vertical categories filter",[271,863,864,867],{},[239,865,866],{},"verticalCategories.filter(c => c.value !== 0)"," called twice in template (title + v-for): re-evaluated on every render",[271,869,870,871,874],{},"Cached in ",[239,872,873],{},"visibleVerticalCategories"," computed: filtered once, reused",[271,876,877],{},"2 filter passes → 1 cached",[255,879,880,885,895,908],{},[271,881,882],{},[203,883,884],{},"Number formatting",[271,886,887,890,891,894],{},[239,888,889],{},"currency.js"," library + ",[239,892,893],{},"currencies.find()"," O(N) called twice per format",[271,896,897,898,901,902,742,904,907],{},"Native ",[239,899,900],{},"Intl.NumberFormat"," cached in ",[239,903,693],{},[239,905,906],{},"currencyMap.get()"," O(1)",[271,909,910],{},"No library overhead; formatter created once per precision",[255,912,913,918,924,927],{},[271,914,915],{},[203,916,917],{},"Last created trn",[271,919,920,923],{},[239,921,922],{},"Object.keys().sort().find()","  - sorts all trns O(N log N) then searches",[271,925,926],{},"Single O(N) pass tracking running max date",[271,928,929],{},"10K trns: ~130K ops → ~10K ops",[255,931,932,937,947,961],{},[271,933,934],{},[203,935,936],{},"Category parent check",[271,938,939,942,943,946],{},[239,940,941],{},"categoriesForBeParent"," calls ",[239,944,945],{},"Object.values(trns).some()"," O(N) per root category: O(M×N)",[271,948,949,950,953,954,957,958,960],{},"Pre-built ",[239,951,952],{},"usedCategoryIds"," Set via ",[239,955,956],{},"for...in"," O(N) + ",[239,959,755],{}," O(1) per check; no intermediate arrays",[271,962,963],{},"200 categories × 10K trns: ~2M → ~10K ops",[255,965,966,971,985,997],{},[271,967,968],{},[203,969,970],{},"Recent categories",[271,972,973,976,977,980,981,984],{},[239,974,975],{},"Object.keys(trns).filter().sort()"," O(N log N) + ",[239,978,979],{},"acc.some(c => c.id)"," O(K²) dedup + ",[239,982,983],{},"includes()"," O(W) per category",[271,986,987,988,990,991,994,995,907],{},"Single O(N) pass via ",[239,989,693],{}," tracking latest date + ",[239,992,993],{},"entries().toSorted()"," O(K log K) + ",[239,996,755],{},[271,998,999],{},"10K trns: ~130K + K² → ~10K + K log K ops",[255,1001,1002,1007,1020,1030],{},[271,1003,1004],{},[203,1005,1006],{},"Transactible categories",[271,1008,1009,1011,1012,1015,1016,1019],{},[239,1010,797],{}," with ",[239,1013,1014],{},"acc.includes()"," O(M) + ",[239,1017,1018],{},"childIds.filter(!acc.includes)"," O(C×M): O(M²)",[271,1021,1022,1025,1026,1029],{},[239,1023,1024],{},"Map\u003CparentId, childIds[]>"," built in O(M) + ",[239,1027,1028],{},"Set"," dedup O(1) per check",[271,1031,1032],{},"200 categories: ~40K → ~200 ops",[255,1034,1035,1040,1046,1052],{},[271,1036,1037],{},[203,1038,1039],{},"Transfer detection",[271,1041,1042,1045],{},[239,1043,1044],{},"transferCategoriesIds.includes(categoryId)"," O(W) array scan per transaction",[271,1047,1048,1051],{},[239,1049,1050],{},"trn.type === TrnType.Transfer"," O(1) enum comparison",[271,1053,1054],{},"Linear → constant per check",[255,1056,1057,1062,1068,1071],{},[271,1058,1059],{},[203,1060,1061],{},"Type filter counts",[271,1063,1064,1065,1067],{},"3 separate ",[239,1066,738],{}," passes over trnsIds (expense, income, transfer): O(3N)",[271,1069,1070],{},"Single loop with counters object: O(N)",[271,1072,1073],{},"3 full passes → 1 pass",[255,1075,1076,1081,1087,1097],{},[271,1077,1078],{},[203,1079,1080],{},"Trn item cache in list",[271,1082,1083,1086],{},[239,1084,1085],{},"computeTrnItem(id)"," called twice per item in template (item + date): O(2P) calls",[271,1088,1089,1090,1093,1094,1096],{},"Pre-computed ",[239,1091,1092],{},"trnItemsMap"," via ",[239,1095,693],{},": O(P) compute + O(1) lookups",[271,1098,1099],{},"30 items: ~60 → ~30 computations per render",[255,1101,1102,1107,1113,1123],{},[271,1103,1104],{},[203,1105,1106],{},"Group totals cache",[271,1108,1109,1112],{},[239,1110,1111],{},"getTotalOfTrnsIds()"," called twice per group in template (income + expense): O(2G) calls",[271,1114,1089,1115,1118,1119,1122],{},[239,1116,1117],{},"groupTotals"," Map + ",[239,1120,1121],{},"paginatedTotal"," computed: O(G) compute + O(1) lookups",[271,1124,1125],{},"15 groups: ~30 → ~15 total computations per render",[255,1127,1128,1133,1142,1149],{},[271,1129,1130],{},[203,1131,1132],{},"Category grouping sort",[271,1134,1135,1138,1139,1141],{},[239,1136,1137],{},"categories.sort()"," called inside ",[239,1140,683],{}," on every child push: O(K² log K) per parent",[271,1143,1144,1145,1148],{},"Sort once after grouping loop, only if ",[239,1146,1147],{},"length > 1",": O(K log K) per parent",[271,1150,1151],{},"5 children: ~50 → ~12 comparisons",[255,1153,1154,1159,1165,1172],{},[271,1155,1156],{},[203,1157,1158],{},"Category grouping totals",[271,1160,1161,1164],{},[239,1162,1163],{},"getTotalOfTrnsIds(cat.trnsIds).sum"," called twice per child (for push value + parent sum)",[271,1166,1167,1168,1171],{},"Computed once into ",[239,1169,1170],{},"catTotal",", reused: single call per child",[271,1173,1174,1175,1178],{},"2× fewer ",[239,1176,1177],{},"getTotal"," calls per child category",[255,1180,1181,1186,1189,1192],{},[271,1182,1183],{},[203,1184,1185],{},"Data sync",[271,1187,1188],{},"Firebase full replacement on every update: O(N)",[271,1190,1191],{},"Delta sync: only changed records fetched  - O(D) where D « N",[271,1193,1194],{},"Avoids reprocessing unchanged transactions",[255,1196,1197,1202,1208,1217],{},[271,1198,1199],{},[203,1200,1201],{},"Interval generation",[271,1203,1204,1207],{},[239,1205,1206],{},"list.unshift(current)"," in while loop: O(n) per call, O(n²) total",[271,1209,1210,742,1213,1216],{},[239,1211,1212],{},"list.push(current)",[239,1214,1215],{},"list.reverse()",": O(1) per call, O(n) total",[271,1218,1219],{},"Year\u002F365 days: ~66K shifts → ~365 pushes + 1 reverse",[255,1221,1222,1227,1242,1252],{},[271,1223,1224],{},[203,1225,1226],{},"Date duration keys",[271,1228,1229,1230,1233,1234,1237,1238,1241],{},"Dynamic ",[239,1231,1232],{},"{ [\\","${period}s`]: value }",[239,1235,1236],{}," - TS sees","Record\u003Cstring, number>",[239,1239,1240],{},", no type check against ","Duration`",[271,1243,1244,1247,1248,1251],{},[239,1245,1246],{},"toDuration(period, value)"," returns typed ",[239,1249,1250],{},"Duration"," via exhaustive switch",[271,1253,1254],{},"Compile-time safety; new period variants cause errors, not silent fallback",[255,1256,1257,1262,1272,1279],{},[271,1258,1259],{},[203,1260,1261],{},"Date formatting",[271,1263,1264,1265,742,1268,1271],{},"Each format function called twice (",[239,1266,1267],{},"type: 'start'",[239,1269,1270],{},"type: 'end'","), concatenated; 3 IIFE switch blocks for period comparison",[271,1273,1274,1275,1278],{},"Each format function returns full string; ",[239,1276,1277],{},"isSamePeriod(a, b, period)"," replaces all 3 switches",[271,1280,1281,1282,1285],{},"~40% fewer lines in ",[239,1283,1284],{},"useGetDateRange","; no double calls",[255,1287,1288,1293,1303,1314],{},[271,1289,1290],{},[203,1291,1292],{},"Average computation",[271,1294,1295,1298,1299,1302],{},[239,1296,1297],{},"sum !== 0"," checked 3 times + ",[239,1300,1301],{},"Object.keys(items).length > 0"," guard",[271,1304,1305,1306,1309,1310,1313],{},"Early ",[239,1307,1308],{},"sum === 0"," exit; ",[239,1311,1312],{},"day"," average always present (range ≥ 2 guaranteed)",[271,1315,1316],{},"Single check instead of 4",[255,1318,1319,1324,1327,1334],{},[271,1320,1321],{},[203,1322,1323],{},"Favorite categories sort",[271,1325,1326],{},"Inline sort duplicating parent-name + name logic (8 lines)",[271,1328,1329,1330,1333],{},"Reuses ",[239,1331,1332],{},"compareCategoriesByParentAndName"," utility (1 line)",[271,1335,1336],{},"DRY; single sort implementation to maintain",[255,1338,1339,1344,1353,1363],{},[271,1340,1341],{},[203,1342,1343],{},"Recent categories loop",[271,1345,1346,1348,1349,1352],{},[239,1347,797],{}," iterates all sorted entries even after reaching ",[239,1350,1351],{},"maxCategories"," limit",[271,1354,1355,1358,1359,1362],{},[239,1356,1357],{},"for"," loop with ",[239,1360,1361],{},"break"," stops at limit",[271,1364,1365],{},"200 categories, limit 16: ~200 → ~16 iterations",[255,1367,1368,1373,1379,1385],{},[271,1369,1370],{},[203,1371,1372],{},"Recent categories iteration",[271,1374,1375,1378],{},[239,1376,1377],{},"for...of Object.keys(trnsItems)"," creates intermediate array",[271,1380,1381,1384],{},[239,1382,1383],{},"for...in trnsItems"," iterates directly on object",[271,1386,1387],{},"No intermediate array allocation for 10K+ trns",[255,1389,1390,1395,1408,1415],{},[271,1391,1392],{},[203,1393,1394],{},"Category IDs reuse",[271,1396,1397,1400,1401,1404,1405],{},[239,1398,1399],{},"Object.keys(items.value)"," called separately in ",[239,1402,1403],{},"categoriesRootIds",", ",[239,1406,1407],{},"favoriteCategoriesIds",[271,1409,1410,1411,1414],{},"Both reuse ",[239,1412,1413],{},"categoriesIds"," computed",[271,1416,1417],{},"Keys computed once, shared across dependents",[255,1419,1420,1425,1435,1444],{},[271,1421,1422],{},[203,1423,1424],{},"Transactible IDs reuse",[271,1426,1427,1430,1431,1434],{},[239,1428,1429],{},"categoriesIdsForTrnValues"," re-filters all categories with ",[239,1432,1433],{},"!hasChildren"," check",[271,1436,1437,1438,742,1441],{},"Delegates to already-computed ",[239,1439,1440],{},"transactibleIds",[239,1442,1443],{},".filter(id !== 'transfer')",[271,1445,1446],{},"Avoids redundant O(M) parent scan",[255,1448,1449,1454,1466,1476],{},[271,1450,1451],{},[203,1452,1453],{},"Children IDs dedup",[271,1455,1456,1459,1460,1462,1463],{},[239,1457,1458],{},"getChildrenIdsOrParent"," duplicates ",[239,1461,738],{}," logic from ",[239,1464,1465],{},"getChildrenIds",[271,1467,1468,1469,1472,1473],{},"Delegates to ",[239,1470,1471],{},"getChildrenIds()",", checks ",[239,1474,1475],{},".length",[271,1477,1478],{},"Single filter implementation",[451,1480,1482],{"id":1481},"bundle-and-rendering","Bundle and rendering",[249,1484,1485,1495],{},[252,1486,1487],{},[255,1488,1489,1491,1493],{},[258,1490,658],{},[258,1492,241],{},[258,1494,213],{},[266,1496,1497,1510,1527,1544,1567,1587,1604,1625],{},[255,1498,1499,1504,1507],{},[271,1500,1501],{},[203,1502,1503],{},"ECharts",[271,1505,1506],{},"9 components in main bundle (incl. PieChart, DataZoom, MarkPoint)",[271,1508,1509],{},"7 components in lazy-loaded async chunk (unused removed)",[255,1511,1512,1517,1520],{},[271,1513,1514],{},[203,1515,1516],{},"Theme plugin",[271,1518,1519],{},"81 lines with SSR inline scripts and regex replacements",[271,1521,1522,1523,1526],{},"32 lines, SPA-only ",[239,1524,1525],{},"appConfig"," updates from localStorage",[255,1528,1529,1533,1541],{},[271,1530,1531],{},[203,1532,550],{},[271,1534,1535,1537,1538,1540],{},[239,1536,555],{}," on every ",[239,1539,559],{},"  - full recursive clone",[271,1542,1543],{},"Direct write, debounced 300ms, no cloning",[255,1545,1546,1551,1557],{},[271,1547,1548],{},[203,1549,1550],{},"Responsive layout",[271,1552,1553,1556],{},[239,1554,1555],{},"useWindowSize()"," JS listener for sidebar",[271,1558,1559,1560,322,1563,1566],{},"Pure CSS ",[239,1561,1562],{},"sm:",[239,1564,1565],{},"md:"," Tailwind breakpoints",[255,1568,1569,1574,1577],{},[271,1570,1571],{},[203,1572,1573],{},"Container queries",[271,1575,1576],{},"Media queries only",[271,1578,1579,1580,1404,1583,1586],{},"CSS container queries (",[239,1581,1582],{},"@xl\u002Fpage:",[239,1584,1585],{},"@md\u002Fpage:",") for layout-aware sizing",[255,1588,1589,1594,1599],{},[271,1590,1591],{},[203,1592,1593],{},"Dependencies",[271,1595,1596,1598],{},[239,1597,889],{}," for number formatting",[271,1600,897,1601,1603],{},[239,1602,900],{}," (no dependency)",[255,1605,1606,1611,1618],{},[271,1607,1608],{},[203,1609,1610],{},"Fonts",[271,1612,1613,1614,1617],{},"4 families (Roboto, Roboto Condensed, Nunito, Unica One), multiple weights, cyrillic only - Google Fonts ",[239,1615,1616],{},"\u003Clink>"," in head",[271,1619,1620,1621,1624],{},"Same 4 families via ",[239,1622,1623],{},"@nuxt\u002Ffonts",", cyrillic + latin",[255,1626,1627,1632,1641],{},[271,1628,1629],{},[203,1630,1631],{},"Dark mode",[271,1633,1634,742,1637,1640],{},[239,1635,1636],{},"import colors from 'tailwindcss\u002Fcolors'",[239,1638,1639],{},"as any"," cast",[271,1642,1643,1646,1647,1650],{},[239,1644,1645],{},"usePreferredDark()"," + hardcoded neutral, handles ",[239,1648,1649],{},"system"," mode",[244,1652,1654],{"id":1653},"security","Security",[249,1656,1657,1667],{},[252,1658,1659],{},[255,1660,1661,1663,1665],{},[258,1662,658],{},[258,1664,241],{},[258,1666,213],{},[266,1668,1669,1702,1715,1728,1741,1754,1771],{},[255,1670,1671,1676,1686],{},[271,1672,1673],{},[203,1674,1675],{},"Calculator parser",[271,1677,1678,1681,1682,1685],{},[239,1679,1680],{},"new Function(expression)","  - code injection risk, requires ",[239,1683,1684],{},"unsafe-eval"," CSP",[271,1687,1688,1689,1692,1693,1692,1696,1692,1699,1701],{},"Recursive descent parser  - only ",[239,1690,1691],{},"+",",",[239,1694,1695],{},"-",[239,1697,1698],{},"*",[239,1700,612],{}," on numeric literals",[255,1703,1704,1709,1712],{},[271,1705,1706],{},[203,1707,1708],{},"Input validation",[271,1710,1711],{},"Firebase rules (client can't verify)",[271,1713,1714],{},"Client-side Zod schemas + Postgres column types \u002F RLS",[255,1716,1717,1722,1725],{},[271,1718,1719],{},[203,1720,1721],{},"Auth tokens",[271,1723,1724],{},"Firebase-managed",[271,1726,1727],{},"Supabase-managed JWT + refresh token (validated by PowerSync via JWKS)",[255,1729,1730,1735,1738],{},[271,1731,1732],{},[203,1733,1734],{},"Session lifetime",[271,1736,1737],{},"Firebase default",[271,1739,1740],{},"Supabase Auth session with automatic token refresh",[255,1742,1743,1748,1751],{},[271,1744,1745],{},[203,1746,1747],{},"Cascade deletes",[271,1749,1750],{},"N\u002FA (Firebase)",[271,1752,1753],{},"RLS-scoped delete of the user's rows (no FK constraints; PowerSync clears local data)",[255,1755,1756,1761,1764],{},[271,1757,1758],{},[203,1759,1760],{},"Data integrity",[271,1762,1763],{},"None enforced in code",[271,1765,1766,1767,1770],{},"Transfer ↔ ",[239,1768,1769],{},"categoryId='transfer'",", type change normalization, name uniqueness",[255,1772,1773,1778,1781],{},[271,1774,1775],{},[203,1776,1777],{},"Redirect safety",[271,1779,1780],{},"No validation",[271,1782,1783,1786,1787,1789,1790,311],{},[239,1784,1785],{},"getSafeRedirectPath()","  - only relative paths starting with ",[239,1788,612],{}," (not ",[239,1791,1792],{},"\u002F\u002F",[244,1794,288],{"id":1795},"auth",[249,1797,1798,1808],{},[252,1799,1800],{},[255,1801,1802,1804,1806],{},[258,1803],{},[258,1805,241],{},[258,1807,213],{},[266,1809,1810,1822,1840,1856,1876,1892,1905,1917],{},[255,1811,1812,1817,1820],{},[271,1813,1814],{},[203,1815,1816],{},"Provider",[271,1818,1819],{},"Firebase Auth",[271,1821,297],{},[255,1823,1824,1829,1834],{},[271,1825,1826],{},[203,1827,1828],{},"Plugin",[271,1830,1831,1833],{},[239,1832,294],{}," (auto-handles auth)",[271,1835,1836,1839],{},[239,1837,1838],{},"plugins\u002Fpowersync.client.ts"," - connects PowerSync when a session resolves",[255,1841,1842,1847,1850],{},[271,1843,1844],{},[203,1845,1846],{},"Token flow",[271,1848,1849],{},"Firebase SDK internal",[271,1851,1852,1855],{},[239,1853,1854],{},"SupabaseConnector.fetchCredentials()"," hands the Supabase JWT to PowerSync; validated via JWKS",[255,1857,1858,1863,1866],{},[271,1859,1860],{},[203,1861,1862],{},"Offline",[271,1864,1865],{},"Firebase SDK handles reconnect",[271,1867,1868,1869,1872,1873,311],{},"Persisted Supabase session in ",[239,1870,1871],{},"localStorage","; the route guard reads it synchronously (",[239,1874,1875],{},"useAuthSession",[255,1877,1878,1883,1885],{},[271,1879,1880],{},[203,1881,1882],{},"Auth state hint",[271,1884,625],{},[271,1886,1887,1888,1891],{},"Persisted session read synchronously at cold start (",[239,1889,1890],{},"getPersistedSession()","), no network",[255,1893,1894,1899,1902],{},[271,1895,1896],{},[203,1897,1898],{},"Callback",[271,1900,1901],{},"Firebase redirect",[271,1903,1904],{},"Email\u002Fpassword sign-in, no OAuth popups",[255,1906,1907,1912,1914],{},[271,1908,1909],{},[203,1910,1911],{},"CORS",[271,1913,400],{},[271,1915,1916],{},"Supabase + PowerSync service URLs from runtime config",[255,1918,1919,1924,1927],{},[271,1920,1921],{},[203,1922,1923],{},"Login",[271,1925,1926],{},"Firebase auto-redirect",[271,1928,1929,1930],{},"Stores the intended redirect, signs in via ",[239,1931,1932],{},"useSupabaseAuth()",[244,1934,1936],{"id":1935},"sync-and-offline","Sync and offline",[249,1938,1939,1949],{},[252,1940,1941],{},[255,1942,1943,1945,1947],{},[258,1944],{},[258,1946,241],{},[258,1948,213],{},[266,1950,1951,1966,1986,2007,2022,2043,2054],{},[255,1952,1953,1958,1963],{},[271,1954,1955],{},[203,1956,1957],{},"Mechanism",[271,1959,1960,1961],{},"Ad-hoc helpers in ",[239,1962,579],{},[271,1964,1965],{},"PowerSync's built-in upload queue - no hand-written offline queue",[255,1967,1968,1973,1976],{},[271,1969,1970],{},[203,1971,1972],{},"Writes",[271,1974,1975],{},"Scattered localStorage keys",[271,1977,1978,322,1980,1982,1983,311],{},[239,1979,321],{},[239,1981,325],{}," write local SQLite (INSERT\u002FUPDATE, never ",[239,1984,1985],{},"ON CONFLICT",[255,1987,1988,1993,2001],{},[271,1989,1990],{},[203,1991,1992],{},"Upload",[271,1994,1995,1404,1998],{},[239,1996,1997],{},"saveTrnToAddLaterLocal()",[239,1999,2000],{},"removeTrnToDeleteLaterLocal()",[271,2002,2003,2006],{},[239,2004,2005],{},"SupabaseConnector.uploadData()"," drains the queue to Supabase",[255,2008,2009,2014,2017],{},[271,2010,2011],{},[203,2012,2013],{},"Reads",[271,2015,2016],{},"Manual on next load",[271,2018,2019,2021],{},[239,2020,317],{}," - one subscription covers initial load + realtime, local + synced",[255,2023,2024,2029,2031],{},[271,2025,2026],{},[203,2027,2028],{},"Conflict handling",[271,2030,625],{},[271,2032,2033,2034,407,2037,2039,2040],{},"Auto-reconcile: rejected uploads compute ",[239,2035,2036],{},"divergedOps",[239,2038,631],{},") and trigger ",[239,2041,2042],{},"forceResync",[255,2044,2045,2049,2051],{},[271,2046,2047],{},[203,2048,332],{},[271,2050,625],{},[271,2052,2053],{},"Client-side Zod schemas on form input; Postgres RLS on the server",[255,2055,2056,2061,2063],{},[271,2057,2058],{},[203,2059,2060],{},"First frame",[271,2062,625],{},[271,2064,2065,2066,2068],{},"Blob read-through cache (",[239,2067,565],{},") primes stores before PowerSync init",[244,2070,2072],{"id":2071},"pwa","PWA",[249,2074,2075,2085],{},[252,2076,2077],{},[255,2078,2079,2081,2083],{},[258,2080],{},[258,2082,241],{},[258,2084,213],{},[266,2086,2087,2110,2126,2139],{},[255,2088,2089,2094,2104],{},[271,2090,2091],{},[203,2092,2093],{},"Strategy",[271,2095,2096,2099,2100,2103],{},[239,2097,2098],{},"injectManifest"," (custom service worker via ",[239,2101,2102],{},"SW"," env var)",[271,2105,2106,2109],{},[239,2107,2108],{},"generateSW"," (auto-generated by Workbox)",[255,2111,2112,2117,2123],{},[271,2113,2114],{},[203,2115,2116],{},"Service worker",[271,2118,2119,2120],{},"Hand-written ",[239,2121,2122],{},"sw.ts",[271,2124,2125],{},"None - Workbox generates it",[255,2127,2128,2133,2136],{},[271,2129,2130],{},[203,2131,2132],{},"Precaching",[271,2134,2135],{},"Manual configuration",[271,2137,2138],{},"Automatic for all build assets",[255,2140,2141,2146,2149],{},[271,2142,2143],{},[203,2144,2145],{},"Icons",[271,2147,2148],{},"Fetched from Iconify API at runtime",[271,2150,2151,2152],{},"Bundled at build time via ",[239,2153,2154],{},"clientBundle",[244,2156,2158],{"id":2157},"typescript","TypeScript",[249,2160,2161,2171],{},[252,2162,2163],{},[255,2164,2165,2167,2169],{},[258,2166],{},[258,2168,241],{},[258,2170,213],{},[266,2172,2173,2186,2208,2226,2238,2271,2292,2314,2344,2382,2403,2425,2444],{},[255,2174,2175,2180,2183],{},[271,2176,2177],{},[203,2178,2179],{},"Strict mode",[271,2181,2182],{},"Enabled, but unresolved errors",[271,2184,2185],{},"Zero TypeScript errors",[255,2187,2188,2193,2198],{},[271,2189,2190],{},[203,2191,2192],{},"Type casts",[271,2194,2195,2197],{},[239,2196,1639],{}," used across codebase",[271,2199,2200,2201,2203,2204,2207],{},"Zero ",[239,2202,1639],{}," - replaced with type guards, ",[239,2205,2206],{},"@vue-ignore",", narrow casts",[255,2209,2210,2214,2220],{},[271,2211,2212],{},[203,2213,1039],{},[271,2215,2216,2219],{},[239,2217,2218],{},"transferCategoriesIds"," computed array",[271,2221,2222,2225],{},[239,2223,2224],{},"TrnType.Transfer"," enum + discriminated union types",[255,2227,2228,2233,2235],{},[271,2229,2230],{},[203,2231,2232],{},"Runtime validation",[271,2234,625],{},[271,2236,2237],{},"Zod schemas for user settings, rates, stat config, date params",[255,2239,2240,2245,2257],{},[271,2241,2242],{},[203,2243,2244],{},"Date period functions",[271,2246,2247,1404,2250,478,2253,2256],{},[239,2248,2249],{},"getStartOf(date, string)",[239,2251,2252],{},"getEndOf(date, string)",[239,2254,2255],{},"default"," fallback to day",[271,2258,2259,2260,2263,2264,1011,2267,2270],{},"Typed as ",[239,2261,2262],{},"Period",", exhaustive ",[239,2265,2266],{},"switch",[239,2268,2269],{},"case 'day'"," - new variants cause compile errors",[255,2272,2273,2278,2286],{},[271,2274,2275],{},[203,2276,2277],{},"Duration construction",[271,2279,1229,2280,1233,2282,2285],{},[239,2281,1232],{},[239,2283,2284],{},"- TS sees","Record\u003Cstring, number>`",[271,2287,2288,2291],{},[239,2289,2290],{},"toDuration(period, value): Duration"," - typed return via exhaustive switch",[255,2293,2294,2299,2308],{},[271,2295,2296],{},[203,2297,2298],{},"Stat type mapping",[271,2300,2301,1011,2304,2307],{},[239,2302,2303],{},"Record\u003Cstring, TrnType[]>",[239,2305,2306],{},"??"," fallback",[271,2309,2310,2313],{},[239,2311,2312],{},"Record\u003CSeriesSlugSelected | StatTabSlug, TrnType[]>"," - all keys covered, no fallback needed",[255,2315,2316,2321,2330],{},[271,2317,2318],{},[203,2319,2320],{},"Average key resolution",[271,2322,2323,2326,2327],{},[239,2324,2325],{},"key as keyof TotalReturns"," unsafe cast + ",[239,2328,2329],{},"as number",[271,2331,2332,2333,2336,2337,537,2340,2343],{},"Typed ",[239,2334,2335],{},"key: keyof TotalReturns"," via explicit ",[239,2338,2339],{},"'netIncome'",[239,2341,2342],{},"'sum'"," mapping, no casts",[255,2345,2346,2351,2367],{},[271,2347,2348],{},[203,2349,2350],{},"Currency rate fallback",[271,2352,2353,1404,2356,2359,2360,612,2363,2366],{},[239,2354,2355],{},"rates[code] || 1",[239,2357,2358],{},"wallet?.currency || 'USD'"," - falsy ",[239,2361,2362],{},"0",[239,2364,2365],{},"''"," triggers fallback",[271,2368,2369,1404,2372,2375,2376,612,2379,2366],{},[239,2370,2371],{},"rates[code] ?? 1",[239,2373,2374],{},"wallet?.currency ?? 'USD'"," - only ",[239,2377,2378],{},"null",[239,2380,2381],{},"undefined",[255,2383,2384,2389,2397],{},[271,2385,2386],{},[203,2387,2388],{},"Query param parsing",[271,2390,2391,2359,2394,2396],{},[239,2392,2393],{},"if (data.rangeOffset)",[239,2395,2362],{}," skipped",[271,2398,2399,2402],{},[239,2400,2401],{},"if (data.x !== undefined)"," - zero values applied correctly",[255,2404,2405,2410,2416],{},[271,2406,2407],{},[203,2408,2409],{},"Total props",[271,2411,2412,2415],{},[239,2413,2414],{},"baseCurrencyCode?: string"," loose type",[271,2417,2418,2421,2422],{},[239,2419,2420],{},"baseCurrencyCode?: CurrencyCode"," - consistent with ",[239,2423,2424],{},"getAmountInRate",[255,2426,2427,2432,2438],{},[271,2428,2429],{},[203,2430,2431],{},"Category stat params",[271,2433,2434,2437],{},[239,2435,2436],{},"trnsItems: Record\u003CTrnId, { categoryId?: string }>"," inline type",[271,2439,2440,2443],{},[239,2441,2442],{},"trnsItems: Record\u003CTrnId, Pick\u003CTrnItem, 'categoryId'>>"," - linked to source type",[255,2445,2446,2451,2461],{},[271,2447,2448],{},[203,2449,2450],{},"Sort null guard",[271,2452,2453,2456,2457,2460],{},[239,2454,2455],{},"sortCategoriesByAmount"," checks ",[239,2458,2459],{},"if (!a || !b)"," at runtime",[271,2462,2463,2464,2467,2468,2471],{},"Guard removed - TS already guarantees ",[239,2465,2466],{},"CategoryWithData"," parameters; explicit ",[239,2469,2470],{},": number"," return type",[244,2473,2475],{"id":2474},"tests","Tests",[249,2477,2478,2488],{},[252,2479,2480],{},[255,2481,2482,2484,2486],{},[258,2483],{},[258,2485,241],{},[258,2487,213],{},[266,2489,2490,2511,2526,2539,2551],{},[255,2491,2492,2497,2500],{},[271,2493,2494],{},[203,2495,2496],{},"Config",[271,2498,2499],{},"No vitest config",[271,2501,2502,2503,2506,2507,2510],{},"2 vitest projects: ",[239,2504,2505],{},"unit"," (node), ",[239,2508,2509],{},"store"," (happy-dom)",[255,2512,2513,2518,2520],{},[271,2514,2515],{},[203,2516,2517],{},"Sync tests",[271,2519,625],{},[271,2521,2522,2523,311],{},"Row\u003C->item transforms, upload reconcile \u002F divergence planning (",[239,2524,2525],{},"uploadReconcile.test.ts",[255,2527,2528,2533,2536],{},[271,2529,2530],{},[203,2531,2532],{},"Calculator tests",[271,2534,2535],{},"Basic",[271,2537,2538],{},"46 cases (precedence, decimals, division by zero, injection safety)",[255,2540,2541,2546,2548],{},[271,2542,2543],{},[203,2544,2545],{},"Extracted logic tests",[271,2547,625],{},[271,2549,2550],{},"Wallet filter\u002Fgrouping\u002Fcounts (archived exclusion, available visibility), category grouping, auth gate, stat barUtils, useStatItem",[255,2552,2553,2558,2560],{},[271,2554,2555],{},[203,2556,2557],{},"Test infrastructure",[271,2559,625],{},[271,2561,2562,2565],{},[239,2563,2564],{},"setup-store.ts"," (shared mocks for PowerSync\u002FSupabase)",[244,2567,2569],{"id":2568},"refactoring","Refactoring",[451,2571,2573],{"id":2572},"pure-function-extraction","Pure function extraction",[200,2575,2576],{},"Business logic moved from Vue components into testable pure functions:",[456,2578,2579,2608,2631,2645,2661],{},[459,2580,2581,2584,2585,1404,2588,1404,2591,1404,2593,1404,2596,1404,2599,1404,2602,1404,2605],{},[203,2582,2583],{},"Statistics:"," ",[239,2586,2587],{},"bucketTrnsByIntervals",[239,2589,2590],{},"computeAverageTotal",[239,2592,2455],{},[239,2594,2595],{},"getSelectedType",[239,2597,2598],{},"computeBarStyle",[239,2600,2601],{},"formatCompactAmount",[239,2603,2604],{},"computeSeriesAverage",[239,2606,2607],{},"getTrnTypeByAmount",[459,2609,2610,2584,2613,1404,2616,1404,2619,1404,2622,1404,2625,1404,2628],{},[203,2611,2612],{},"Wallets:",[239,2614,2615],{},"filterWalletsByCurrency",[239,2617,2618],{},"filterWalletsByViewType",[239,2620,2621],{},"groupWalletsByProperty",[239,2623,2624],{},"computeWalletCounts",[239,2626,2627],{},"sumWalletAmounts",[239,2629,2630],{},"getCreditAvailable",[459,2632,2633,2584,2636,1404,2639,1404,2642],{},[203,2634,2635],{},"Categories:",[239,2637,2638],{},"collectCategoriesByTrns",[239,2640,2641],{},"flattenCategoriesWithValues",[239,2643,2644],{},"groupCategoriesWithValues",[459,2646,2647,2584,2650,2652,2653,2656,2657,2660],{},[203,2648,2649],{},"Sync:",[239,2651,540],{}," (store reconciliation against fresh watch emissions), ",[239,2654,2655],{},"planDivergence"," (upload reconcile), ",[239,2658,2659],{},"transforms"," (row\u003C->item)",[459,2662,2663,2666,2667,1404,2670,2673,2674,742,2677],{},[203,2664,2665],{},"Demo:"," simplified to read-only - removed per-entity CRUD methods (",[239,2668,2669],{},"addDemoCategory",[239,2671,2672],{},"deleteDemoWallet",", etc.), only ",[239,2675,2676],{},"generateDemoData()",[239,2678,2679],{},"isDemo",[451,2681,2683],{"id":2682},"component-changes","Component changes",[200,2685,2686,2584,2689,1404,2692,1404,2695,1404,2698,1404,2701,1404,2704,1404,2707,1404,2710,1404,2713,1404,2716,1404,2719,1404,2722,1404,2725,1404,2728,1404,2731,1404,2734],{},[203,2687,2688],{},"Added:",[239,2690,2691],{},"ActionButton",[239,2693,2694],{},"ChipButton",[239,2696,2697],{},"TabsScroll",[239,2699,2700],{},"TextMuted",[239,2702,2703],{},"SettingsCard",[239,2705,2706],{},"TitleSection",[239,2708,2709],{},"ItemBody",[239,2711,2712],{},"BottomSheetModal",[239,2714,2715],{},"WalletsPageListItem",[239,2717,2718],{},"WalletsPageGroupHeader",[239,2720,2721],{},"Onboarding",[239,2723,2724],{},"CurrenciesPageList",[239,2726,2727],{},"CurrenciesItem",[239,2729,2730],{},"ConfigSwitch",[239,2732,2733],{},"GroupingToggle",[239,2735,2736],{},"SelectorItem",[200,2738,2739,2584,2742,2745,2746,1404,2749,1404,2752,612,2755,612,2758,1404,2760,612,2763,1404,2766,612,2769,612,2772,612,2775,1404,2778,1404,2781,1404,2784,1404,2787,1404,2790,2793,2794,1404,2797,1404,2800],{},[203,2740,2741],{},"Removed:",[239,2743,2744],{},"Item1","–",[239,2747,2748],{},"4",[239,2750,2751],{},"Tabs2",[239,2753,2754],{},"TabsItem1",[239,2756,2757],{},"3",[239,2759,2748],{},[239,2761,2762],{},"TextSm1",[239,2764,2765],{},"2",[239,2767,2768],{},"Title3",[239,2770,2771],{},"6",[239,2773,2774],{},"7",[239,2776,2777],{},"8",[239,2779,2780],{},"TitleOption",[239,2782,2783],{},"ToggleAction",[239,2785,2786],{},"SwitcherTabs",[239,2788,2789],{},"Welcome",[239,2791,2792],{},"Round"," (stat chart), ",[239,2795,2796],{},"SectionWithCollapse",[239,2798,2799],{},"CurrenciesToggleDep",[239,2801,2802],{},"getStyles.ts",[451,2804,2806],{"id":2805},"component-patterns","Component patterns",[456,2808,2809,2822],{},[459,2810,2811,2584,2814,2817,2818,2821],{},[203,2812,2813],{},"Class merging:",[239,2815,2816],{},"getStyles()"," utility removed - replaced with ",[239,2819,2820],{},"cn()"," (clsx + tailwind-merge)",[459,2823,2824,2584,2827,612,2830,2833,2834,322,2837],{},[203,2825,2826],{},"Form inputs:",[239,2828,2829],{},"value",[239,2831,2832],{},"updateValue"," props -> ",[239,2835,2836],{},"v-model",[239,2838,2839],{},"defineModel",[451,2841,2843],{"id":2842},"composable-decomposition","Composable decomposition",[456,2845,2846,2865,2872,2893,2906,2926,2978,2996,3018,3042,3070,3090,3117,3129,3147,3162,3176,3194],{},[459,2847,2848,2584,2850,1404,2853,1404,2856,1404,2859,1404,2862],{},[203,2849,2612],{},[239,2851,2852],{},"useWalletContextMenu",[239,2854,2855],{},"useWalletDelete",[239,2857,2858],{},"useWalletsFilter",[239,2860,2861],{},"useWalletsGrouping",[239,2863,2864],{},"useWalletsCounts",[459,2866,2867,2584,2869],{},[203,2868,2635],{},[239,2870,2871],{},"useCategoryContextMenu",[459,2873,2874,2876,2877,1404,2880,1404,2883,2886,2887,612,2890],{},[203,2875,2583],{}," Symbol-based injection keys (",[239,2878,2879],{},"filterKey",[239,2881,2882],{},"statDateKey",[239,2884,2885],{},"statConfigKey",") for type-safe ",[239,2888,2889],{},"provide",[239,2891,2892],{},"inject",[459,2894,2895,2584,2898,2901,2902,2905],{},[203,2896,2897],{},"Stat pages:",[239,2899,2900],{},"useStatPage"," composable - extracts duplicated stat infrastructure (filter, statConfig, statDate creation + provide, activeTab, storageKey, trnsIds, maxRange) from three page components (Dashboard, Category, Wallet) into a single reusable composable. Each page passes only its entity-specific filter logic via ",[239,2903,2904],{},"getTrnsFilter"," callback. Script section reduced by 24–53% across the three pages",[459,2907,2908,2914,2915,1404,2918,2921,2922,2925],{},[203,2909,2910,2913],{},[239,2911,2912],{},"useCategoryLongPress"," composable:"," extracted 59 lines of duplicated long-press logic from ",[239,2916,2917],{},"Round2lines.vue",[239,2919,2920],{},"Line.vue",", and ",[239,2923,2924],{},"Vertical.vue"," into a shared composable. Handles long-press to create transactions from categories with proper date selection from chart intervals",[459,2927,2928,2934,2935,1404,2938,1404,2941,1404,2944,1404,2947,2950,2951,2954,2955,828,2958,2961,2962,2964,2965,1404,2968,1404,2971,1404,2974,2977],{},[203,2929,2930,2933],{},[239,2931,2932],{},"useStatDate"," encapsulation:"," navigation computed properties (",[239,2936,2937],{},"shouldShowNav",[239,2939,2940],{},"isAtEnd",[239,2942,2943],{},"isAtStart",[239,2945,2946],{},"isDayToday",[239,2948,2949],{},"shouldShowNavHome",") and direct ",[239,2952,2953],{},"params.value"," mutations moved from ",[239,2956,2957],{},"Navigation.vue",[239,2959,2960],{},"chart\u002FWrap.vue"," into ",[239,2963,2932],{}," methods (",[239,2966,2967],{},"selectInterval()",[239,2969,2970],{},"resetInterval()",[239,2972,2973],{},"setIntervalsBy()",[239,2975,2976],{},"navigate()","). Navigation.vue reduced from ~54 lines of script to 5 lines",[459,2979,2980,2987,2988,2991,2992,2995],{},[203,2981,2982,2983,2986],{},"Duplicate ",[239,2984,2985],{},"stat\u002Fdate\u002FNav.vue"," removed:"," identical copy of ",[239,2989,2990],{},"date\u002FNav.vue"," was deleted - only the canonical ",[239,2993,2994],{},"DateNav"," component remains",[459,2997,2998,3004,3005,3008,3009,1404,3011,1404,3014,3017],{},[203,2999,3000,3003],{},[239,3001,3002],{},"UiNumberStepper"," component:"," extracted duplicated ±\u002Finput counter pattern from ",[239,3006,3007],{},"ConfigModal.vue"," into a reusable UI primitive with ",[239,3010,2836],{},[239,3012,3013],{},"min",[239,3015,3016],{},"max"," props",[459,3019,3020,2584,3023,3026,3027,3030,3031,828,3034,3037,3038,3041],{},[203,3021,3022],{},"Stat component self-containment:",[239,3024,3025],{},"StatAverage"," consolidated three conditional ",[239,3028,3029],{},"Amount"," blocks into one via computed props; ",[239,3032,3033],{},"StatSumWrap",[239,3035,3036],{},"StatChartWrap"," removed redundant prop-drilling in favor of inject; ",[239,3039,3040],{},"StatItem"," merged two modal blocks into one via state-machine pattern",[459,3043,3044,2584,3047,828,3050,3052,3053,3056,3057,1404,3060,1404,3063,1404,3066,3069],{},[203,3045,3046],{},"Config caching:",[239,3048,3049],{},"Section2.vue",[239,3051,2920],{}," cache ",[239,3054,3055],{},"statConfig.config.value.catsList.*"," reads into computed properties (",[239,3058,3059],{},"isItemsBg",[239,3061,3062],{},"isLines",[239,3064,3065],{},"isRoundIcon",[239,3067,3068],{},"isListGrouped",", etc.) for template readability",[459,3071,3072,3078,3079,3082,3083,3086,3087,1414],{},[203,3073,3074,3077],{},[239,3075,3076],{},"Range.vue"," simplified:"," replaced manual ",[239,3080,3081],{},"intervalsInRange"," indexing with ",[239,3084,3085],{},"statDate.selectedInterval","; extracted ",[239,3088,3089],{},"isIntervalSelected",[459,3091,3092,3098,3099,3102,3103,3106,3107,3110,3111,537,3114,311],{},[203,3093,3094,3097],{},[239,3095,3096],{},"useStatItem"," cleanup:"," extracted ",[239,3100,3101],{},"baseTrnsIdsForSelection"," computed to eliminate duplicated interval-selection logic; fixed unsafe ",[239,3104,3105],{},"type.value as keyof TotalReturns"," cast with ",[239,3108,3109],{},"type.value ?? 'sum'"," fallback; fixed chart average denominator using wrong data source (",[239,3112,3113],{},"intervalsData",[239,3115,3116],{},"intervalsDataWithFilteredCategories",[459,3118,3119,3124,3125,3128],{},[203,3120,3121,2913],{},[239,3122,3123],{},"useCategoriesExpanded"," extracted 52-line expand\u002Fcollapse logic from ",[239,3126,3127],{},"DetailedSection.vue"," (286->234 lines)",[459,3130,3131,2584,3134,612,3137,537,3140,612,3143,3146],{},[203,3132,3133],{},"Stat component rename:",[239,3135,3136],{},"Section",[239,3138,3139],{},"Section2",[239,3141,3142],{},"RoundSection",[239,3144,3145],{},"DetailedSection"," for clarity",[459,3148,3149,2584,3155,828,3158,3161],{},[203,3150,3151,3154],{},[239,3152,3153],{},"barUtils"," extraction:",[239,3156,3157],{},"computeBarStyle()",[239,3159,3160],{},"formatCompactAmount()"," extracted from inline template logic into testable utilities",[459,3163,3164,3167,3168,3171,3172,3175],{},[203,3165,3166],{},"Wallet statistics consistency:"," archived wallets fully excluded from all counts (total, type sums, withdrawal, available) via early ",[239,3169,3170],{},"continue","; ",[239,3173,3174],{},"available.isShow"," requires both withdrawal and credit wallets; auto-reset filter to Total when currency switch produces empty results",[459,3177,3178,2584,3181,612,3184,612,3187,3190,3191],{},[203,3179,3180],{},"Store sync consolidation:",[239,3182,3183],{},"showErrorToast",[239,3185,3186],{},"showSuccessToast",[239,3188,3189],{},"showWarningToast"," merged into generic ",[239,3192,3193],{},"showToast(type, key, params?)",[459,3195,3196,2584,3199,3202,3203],{},[203,3197,3198],{},"Wallet constants:",[239,3200,3201],{},"WALLET_STORAGE_KEYS"," extracted from inline strings to ",[239,3204,3205],{},"constants.ts",[451,3207,3209],{"id":3208},"semantic-renaming","Semantic renaming",[249,3211,3212,3220],{},[252,3213,3214],{},[255,3215,3216,3218],{},[258,3217,241],{},[258,3219,213],{},[266,3221,3222,3234,3245,3257,3268,3290,3302,3314,3326,3338,3351,3363,3376,3388,3400,3411,3421,3433,3445,3457],{},[255,3223,3224,3229],{},[271,3225,3226],{},[239,3227,3228],{},"getTrnsIds",[271,3230,3231],{},[239,3232,3233],{},"filterTrnsIds",[255,3235,3236,3241],{},[271,3237,3238],{},[239,3239,3240],{},"getCategoriesWithData",[271,3242,3243],{},[239,3244,853],{},[255,3246,3247,3252],{},[271,3248,3249],{},[239,3250,3251],{},"getTotalOfTrnsIds",[271,3253,3254],{},[239,3255,3256],{},"computeTotalForTrnsIds",[255,3258,3259,3264],{},[271,3260,3261],{},[239,3262,3263],{},"getChildsIds",[271,3265,3266],{},[239,3267,1465],{},[255,3269,3270,3281],{},[271,3271,3272,322,3275,322,3278],{},[239,3273,3274],{},"trnFormCreate",[239,3276,3277],{},"Edit",[239,3279,3280],{},"Duplicate",[271,3282,3283,322,3286,322,3288],{},[239,3284,3285],{},"openFormForCreate",[239,3287,3277],{},[239,3289,3280],{},[255,3291,3292,3297],{},[271,3293,3294],{},[239,3295,3296],{},"getIsShowSum",[271,3298,3299],{},[239,3300,3301],{},"shouldShowSum",[255,3303,3304,3309],{},[271,3305,3306],{},[239,3307,3308],{},"isItTransactible",[271,3310,3311],{},[239,3312,3313],{},"isTransactible",[255,3315,3316,3321],{},[271,3317,3318],{},[239,3319,3320],{},"editedAt",[271,3322,3323],{},[239,3324,3325],{},"updatedAt",[255,3327,3328,3333],{},[271,3329,3330],{},[239,3331,3332],{},"TransferType",[271,3334,3335],{},[239,3336,3337],{},"TransferSide",[255,3339,3340,3346],{},[271,3341,3342,3345],{},[239,3343,3344],{},"Selector2"," (categories)",[271,3347,3348],{},[239,3349,3350],{},"SelectorGrid",[255,3352,3353,3358],{},[271,3354,3355,3345],{},[239,3356,3357],{},"Selector",[271,3359,3360],{},[239,3361,3362],{},"SelectorTree",[255,3364,3365,3371],{},[271,3366,3367,3370],{},[239,3368,3369],{},"Checkbox"," (ui)",[271,3372,3373],{},[239,3374,3375],{},"SwitchItem",[255,3377,3378,3383],{},[271,3379,3380,3370],{},[239,3381,3382],{},"Tabs1",[271,3384,3385],{},[239,3386,3387],{},"TabsBar",[255,3389,3390,3395],{},[271,3391,3392,3370],{},[239,3393,3394],{},"Toggle3",[271,3396,3397],{},[239,3398,3399],{},"ToggleControlled",[255,3401,3402,3407],{},[271,3403,3404,3406],{},[239,3405,3136],{}," (stat)",[271,3408,3409],{},[239,3410,3142],{},[255,3412,3413,3417],{},[271,3414,3415,3406],{},[239,3416,3139],{},[271,3418,3419],{},[239,3420,3145],{},[255,3422,3423,3428],{},[271,3424,3425],{},[239,3426,3427],{},"isNotTransferCategory",[271,3429,3430],{},[239,3431,3432],{},"shouldShowAmounts",[255,3434,3435,3440],{},[271,3436,3437],{},[239,3438,3439],{},"biggestCatNumber",[271,3441,3442],{},[239,3443,3444],{},"maxCategoryValues",[255,3446,3447,3452],{},[271,3448,3449],{},[239,3450,3451],{},"addMarkArea",[271,3453,3454],{},[239,3455,3456],{},"withMarkArea",[255,3458,3459,3464],{},[271,3460,3461],{},[239,3462,3463],{},"toggleOpened",[271,3465,3466],{},[239,3467,3468],{},"toggleAllCategoriesExpanded",[451,3470,3472],{"id":3471},"data-model","Data model",[249,3474,3475,3485],{},[252,3476,3477],{},[255,3478,3479,3481,3483],{},[258,3480],{},[258,3482,241],{},[258,3484,213],{},[266,3486,3487,3507,3523],{},[255,3488,3489,3494,3501],{},[271,3490,3491],{},[203,3492,3493],{},"Category children",[271,3495,3496,3497,3500],{},"Denormalized ",[239,3498,3499],{},"childIds"," array",[271,3502,3503,3504],{},"Computed dynamically from ",[239,3505,3506],{},"parentId",[255,3508,3509,3513,3518],{},[271,3510,3511],{},[203,3512,1039],{},[271,3514,3515,3517],{},[239,3516,2218],{}," (computed from category data)",[271,3519,3520,3522],{},[239,3521,2224],{}," enum value",[255,3524,3525,3530,3533],{},[271,3526,3527],{},[203,3528,3529],{},"Adjustment",[271,3531,3532],{},"Not distinguished",[271,3534,3535,3538],{},[239,3536,3537],{},"categoryId === 'adjustment'",", excluded from income\u002Fexpense stats",[244,3540,3542],{"id":3541},"ux-improvements","UX Improvements",[456,3544,3545,3551,3567,3577,3583,3597,3606,3612,3618,3628,3634,3643,3652],{},[459,3546,3547,3550],{},[203,3548,3549],{},"Haptic feedback"," on transaction submit (mobile vibration)",[459,3552,3553,2584,3556,537,3559,3562,3563,3566],{},[203,3554,3555],{},"Accessibility:",[239,3557,3558],{},"\u003Cdiv>",[239,3560,3561],{},"\u003Cbutton>"," in interactive components, i18n ",[239,3564,3565],{},"aria-label"," on icon-only buttons",[459,3568,3569,3572,3573,3576],{},[203,3570,3571],{},"Calculator:"," fixed infinite loop on zero result, proper ",[239,3574,3575],{},"0."," decimal display, rounded division results",[459,3578,3579,3582],{},[203,3580,3581],{},"Form:"," amount clears immediately after submit, popover closes before edit navigation",[459,3584,3585,2584,3588,3590,3591,3593,3594,311],{},[203,3586,3587],{},"Onboarding:",[239,3589,2789],{}," component replaced with ",[239,3592,2721],{}," - improved layout, cookie-persisted state (",[239,3595,3596],{},"finapp.isOnboarded",[459,3598,3599,3602,3603],{},[203,3600,3601],{},"Currencies page:"," two sections (Used \u002F All) with global search, O(1) lookup via ",[239,3604,3605],{},"currencyMap",[459,3607,3608,3611],{},[203,3609,3610],{},"Wallet\u002Fcategory detail:"," edit\u002Fdelete actions moved to three-dot menu",[459,3613,3614,3617],{},[203,3615,3616],{},"Wallet statistics:"," archived wallets fully excluded from all filter tabs; Available tab only shown when both withdrawal and credit wallets exist; filter auto-resets to Total on currency switch if current filter is empty",[459,3619,3620,3623,3624,3627],{},[203,3621,3622],{},"Wallet edit guard:"," redirect to ",[239,3625,3626],{},"\u002Fwallets"," when wallet doesn't exist",[459,3629,3630,3633],{},[203,3631,3632],{},"Toast dismiss:"," click anywhere on toast notification to close it",[459,3635,3636,2584,3639,3642],{},[203,3637,3638],{},"Layout:",[239,3640,3641],{},"keepalive"," prop from layout slot for page transition persistence",[459,3644,3645,3648,3649,3651],{},[203,3646,3647],{},"Settings page:"," reorganized with ",[239,3650,2703],{},", language dropdown, currency picker modal, danger zone with \"Delete all data\"",[459,3653,3654,3657],{},[203,3655,3656],{},"Login page:"," version display at bottom",[244,3659,3660],{"id":3660},"i18n",[249,3662,3663,3673],{},[252,3664,3665],{},[255,3666,3667,3669,3671],{},[258,3668],{},[258,3670,241],{},[258,3672,213],{},[266,3674,3675,3697,3725,3743,3768],{},[255,3676,3677,3682,3685],{},[271,3678,3679],{},[203,3680,3681],{},"Error messages",[271,3683,3684],{},"Generic or none",[271,3686,3687,3688,1404,3691,1404,3694,311],{},"Per-entity error keys (",[239,3689,3690],{},"categories.errors.*",[239,3692,3693],{},"wallets.errors.*",[239,3695,3696],{},"trns.errors.*",[255,3698,3699,3704,3713],{},[271,3700,3701],{},[203,3702,3703],{},"Form labels",[271,3705,3706,3707,1404,3710,311],{},"Verbose (",[239,3708,3709],{},"Category name",[239,3711,3712],{},"Category color",[271,3714,3715,3716,1404,3719,1404,3722,311],{},"Concise (",[239,3717,3718],{},"Name",[239,3720,3721],{},"Color",[239,3723,3724],{},"Icon",[255,3726,3727,3731,3737],{},[271,3728,3729],{},[203,3730,2721],{},[271,3732,3733,3736],{},[239,3734,3735],{},"welcome.*"," keys",[271,3738,3739,3742],{},[239,3740,3741],{},"onboarding.*"," with step-by-step guidance",[255,3744,3745,3749,3757],{},[271,3746,3747],{},[203,3748,51],{},[271,3750,3751,1404,3754],{},[239,3752,3753],{},"neutral",[239,3755,3756],{},"radius",[271,3758,3759,1404,3762,1404,3765],{},[239,3760,3761],{},"Background color",[239,3763,3764],{},"Rounding",[239,3766,3767],{},"Appearance",[255,3769,3770,3775,3780],{},[271,3771,3772],{},[203,3773,3774],{},"Typos",[271,3776,3777],{},[239,3778,3779],{},"errorChilds",[271,3781,3782],{},[239,3783,3784],{},"errorChildren",[244,3786,3788],{"id":3787},"code-quality","Code Quality",[451,3790,3792],{"id":3791},"removed-from-firebase","Removed from firebase",[456,3794,3795,3800,3803,3806,3821,3833,3840],{},[459,3796,3797,3799],{},[239,3798,637],{}," dependency",[459,3801,3802],{},"Old page-specific wallet composables",[459,3804,3805],{},"Dead code: unused props, emits, functions, stale TODOs",[459,3807,3808,3809,1404,3812,1404,3815,1404,3818],{},"Deprecated types: ",[239,3810,3811],{},"WalletsDirty",[239,3813,3814],{},"TrnItemDirty",[239,3816,3817],{},"TransferDeprecated",[239,3819,3820],{},"TrnTypeSlug",[459,3822,3823,3824,1404,3827,1404,3830],{},"CSS utilities: ",[239,3825,3826],{},"flex-center-col",[239,3828,3829],{},"absolute-center",[239,3831,3832],{},"layoutBase",[459,3834,3835,828,3837,3839],{},[239,3836,294],{},[239,3838,241],{}," dependencies",[459,3841,3842,3843,1404,3846,1404,3849,311],{},"Firebase config files (",[239,3844,3845],{},"firebase.json",[239,3847,3848],{},".firebaserc",[239,3850,3851],{},"pnpm-workspace.yaml",[451,3853,3855],{"id":3854},"fixed","Fixed",[456,3857,3858,3869,3876,3882,3889,3895,3898,3908,3923,3932,3939,3960,3967,3976,3988,4001,4007,4020,4033,4046,4058,4077,4086,4101,4114,4133],{},[459,3859,3860,3861,3864,3865,3868],{},"Memory leaks: ",[239,3862,3863],{},"addEventListener"," without cleanup, ",[239,3866,3867],{},"ResizeObserver"," without disconnect",[459,3870,3871,3872,3875],{},"Null spreading in 3 stores (missing ",[239,3873,3874],{},"?? {}"," guards)",[459,3877,3878,3879],{},"Emit during render in ",[239,3880,3881],{},"SelectionCategoriesFast",[459,3883,3884,3885,3888],{},"Magic numbers replaced with ",[239,3886,3887],{},"TrnType"," enum (8 places)",[459,3890,3891,3894],{},[239,3892,3893],{},"ref\u003Cany>"," replaced with proper types (4 files)",[459,3896,3897],{},"Error layout localized (hardcoded English -> i18n keys)",[459,3899,3900,3901,537,3904,3907],{},"Tailwind v4 CSS syntax: ",[239,3902,3903],{},"[var(--name)]",[239,3905,3906],{},"(--name)"," functional notation",[459,3909,3910,3911,1789,3913,3916,3917,3919,3920],{},"Radius uses ",[239,3912,2306],{},[239,3914,3915],{},"||",") so ",[239,3918,2362],{}," is valid: ",[239,3921,3922],{},"radius ?? 0.375",[459,3924,3925,3926,537,3929],{},"CSS utility typo: ",[239,3927,3928],{},"bottomSheetDrugClassesCustom",[239,3930,3931],{},"bottomSheetDragClassesCustom",[459,3933,3934,3935,3938],{},"ESLint: layout ",[239,3936,3937],{},"vue\u002Fno-multiple-template-root"," exception",[459,3940,3941,3944,3945,3948,3949,828,3952,3955,3956,3959],{},[239,3942,3943],{},"createLogger(prefix)"," - dev-only ",[239,3946,3947],{},"log",", always-on ",[239,3950,3951],{},"warn",[239,3953,3954],{},"error",", with ",[239,3957,3958],{},"[prefix]"," formatting across all modules",[459,3961,3962,3963,3966],{},"Hardcoded ",[239,3964,3965],{},"'ru'"," locale in chart formatters -> dynamic locale from settings",[459,3968,3969,3972,3973],{},[239,3970,3971],{},"Statistics.vue"," empty group sections hidden with ",[239,3974,3975],{},"v-show",[459,3977,3978,2584,3981,3984,3985],{},[239,3979,3980],{},"Selector.vue",[239,3982,3983],{},"onSelectRange(value: any)"," -> typed ",[239,3986,3987],{},"{ end: unknown, start: unknown }",[459,3989,3990,3993,3994,3997,3998],{},[239,3991,3992],{},"setWalletViewType"," type fixed from ",[239,3995,3996],{},"WalletType | 'total'"," to ",[239,3999,4000],{},"WalletViewTypes | 'total'",[459,4002,4003,4004,4006],{},"Version bumped from v6.6.4 (the ",[239,4005,241],{}," branch baseline)",[459,4008,4009,4012,4013,4016,4017,4019],{},[239,4010,4011],{},"useAmount.getAmountInBaseRate",": dead ",[239,4014,4015],{},"noFormat"," param removed, unnecessary ",[239,4018,1691],{}," unary on number removed",[459,4021,4022,4025,4026,828,4029,4032],{},[239,4023,4024],{},"useFilter",": redundant ",[239,4027,4028],{},"[...array.filter()]",[239,4030,4031],{},"[...string.split()]"," spreads removed - both methods already return new arrays",[459,4034,4035,711,4038,4041,4042,4045],{},[239,4036,4037],{},"getUCalendarTimedDate",[239,4039,4040],{},"new Date(string)"," parsed as UTC -> ",[239,4043,4044],{},"date.toDate(getLocalTimeZone())"," for correct local timezone",[459,4047,4048,711,4050,4053,4054,4057],{},[239,4049,1284],{},[239,4051,4052],{},"type: 'start' | 'end'"," pattern removed - format functions return full strings directly, 3 IIFE switches replaced with ",[239,4055,4056],{},"isSamePeriod"," helper",[459,4059,4060,711,4063,4066,4067,4070,4071,4073,4074],{},[239,4061,4062],{},"getIntervalsInRange",[239,4064,4065],{},"rangeOffset"," made optional in ",[239,4068,4069],{},"IntervalsInRangeProps"," - not used by ",[239,4072,4062],{},", only by ",[239,4075,4076],{},"calculateIntervalInRange",[459,4078,4079,711,4081,537,4083,4085],{},[239,4080,1332],{},[239,4082,3915],{},[239,4084,2306],{}," for empty string fallback (consistent nullish semantics)",[459,4087,4088,4091,4092,4094,4095,4098,4099],{},[239,4089,4090],{},"useCategoriesStore",": removed 5 unnecessary ",[239,4093,3874],{}," defensive checks - ",[239,4096,4097],{},"items"," is always initialized via ",[239,4100,641],{},[459,4102,4103,4105,4106,4109,4110,4113],{},[239,4104,2912],{},": removed redundant ",[239,4107,4108],{},"!"," after optional chaining (",[239,4111,4112],{},"value?.start"," already narrowed)",[459,4115,4116,711,4118,537,4121,3171,4124,1011,4126,4129,4130],{},[239,4117,3123],{},[239,4119,4120],{},"forEach",[239,4122,4123],{},"for...of",[239,4125,683],{},[239,4127,4128],{},"as"," cast -> ",[239,4131,4132],{},"Object.fromEntries",[459,4134,4135,4137,4138,612,4141,537,4144,612,4147,4150,4151,4154],{},[239,4136,2455],{},": renamed ",[239,4139,4140],{},"isP",[239,4142,4143],{},"isN",[239,4145,4146],{},"bothPositive",[239,4148,4149],{},"bothNegative"," for readability; removed redundant ",[239,4152,4153],{},"categories ??= []"," (already initialized in grouping)",[451,4156,1593],{"id":4157},"dependencies",[200,4159,4160],{},[4161,4162,4163,4164,4166,4167,4170],"em",{},"(What changed from the ",[239,4165,241],{}," branch to the current app. For the full list see the project ",[239,4168,4169],{},"package.json",".)",[249,4172,4173,4183],{},[252,4174,4175],{},[255,4176,4177,4180],{},[258,4178,4179],{},"Removed (firebase era)",[258,4181,4182],{},"Added (current)",[266,4184,4185,4196,4207,4218,4229],{},[255,4186,4187,4191],{},[271,4188,4189],{},[239,4190,241],{},[271,4192,4193],{},[239,4194,4195],{},"@powersync\u002Fweb",[255,4197,4198,4202],{},[271,4199,4200],{},[239,4201,294],{},[271,4203,4204],{},[239,4205,4206],{},"@supabase\u002Fsupabase-js",[255,4208,4209,4213],{},[271,4210,4211],{},[239,4212,637],{},[271,4214,4215],{},[239,4216,4217],{},"@vueuse\u002Fnuxt",[255,4219,4220,4224],{},[271,4221,4222],{},[239,4223,889],{},[271,4225,4226],{},[239,4227,4228],{},"zod",[255,4230,4231,4233],{},[271,4232],{},[271,4234,4235],{},[239,4236,4237],{},"es-toolkit",{"title":4239,"searchDepth":4240,"depth":4240,"links":4241},"",2,[4242,4246,4247,4251,4252,4253,4254,4255,4256,4257,4265,4266,4267],{"id":246,"depth":4240,"text":247,"children":4243},[4244],{"id":453,"depth":4245,"text":454},3,{"id":503,"depth":4240,"text":504},{"id":645,"depth":4240,"text":167,"children":4248},[4249,4250],{"id":648,"depth":4245,"text":649},{"id":1481,"depth":4245,"text":1482},{"id":1653,"depth":4240,"text":1654},{"id":1795,"depth":4240,"text":288},{"id":1935,"depth":4240,"text":1936},{"id":2071,"depth":4240,"text":2072},{"id":2157,"depth":4240,"text":2158},{"id":2474,"depth":4240,"text":2475},{"id":2568,"depth":4240,"text":2569,"children":4258},[4259,4260,4261,4262,4263,4264],{"id":2572,"depth":4245,"text":2573},{"id":2682,"depth":4245,"text":2683},{"id":2805,"depth":4245,"text":2806},{"id":2842,"depth":4245,"text":2843},{"id":3208,"depth":4245,"text":3209},{"id":3471,"depth":4245,"text":3472},{"id":3541,"depth":4240,"text":3542},{"id":3660,"depth":4240,"text":3660},{"id":3787,"depth":4240,"text":3788,"children":4268},[4269,4270,4271],{"id":3791,"depth":4245,"text":3792},{"id":3854,"depth":4245,"text":3855},{"id":4157,"depth":4245,"text":1593},"What changed between v6 (Firebase) and the current Supabase + PowerSync app - backend, performance, security, tests, and architecture.","md",null,{},{"icon":165},{"title":162,"description":4278},"Side-by-side comparison of Finapp v6 (Firebase) and the current Supabase Postgres + PowerSync app - backend, performance, security, tests, and architecture.","bi4Nu051NCaHKj3-KmjIVQodWiv1FQdDCtUu6GoqqkQ",[4281,4283],{"title":157,"path":158,"stem":159,"description":4282,"icon":160,"children":-1},"Client-only validation approach and trade-offs.",{"title":167,"path":168,"stem":169,"description":4284,"icon":170,"children":-1},"Cold-start budget, lazy chunks, icon bundling, and the built-in instrumentation marks.",1782114344895]