What Changed Since Firebase
Two eras. Finapp's backend went from Firebase (v6 - Firebase Realtime Database + Firebase Auth) to the current stack: Supabase Postgres + PowerSync (offline-first) + Supabase Auth.
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 Architecture for a full description of the current Supabase + PowerSync architecture.
Side-by-side comparison of what the app looks like on the firebase branch (v6, Firebase) vs the current Supabase + PowerSync app.
Backend
| firebase (Firebase) | current | |
|---|---|---|
| Database | Firebase Realtime Database | Supabase Postgres (replicated by PowerSync) |
| Auth | Firebase Auth via nuxt-vuefire | Supabase Auth (email/password) |
| Data access | Real-time listeners (getDataAndWatch) | Local SQLite via db.watch(...), writes via upsertRow / deleteRow |
| Validation | Firebase security rules (JSON) | Postgres RLS (auth.uid()) + client-side Zod schemas |
| Server code | None (client-only SDK) | Supabase migrations + self-hosted PowerSync sync rules (per-user WHERE "userId" = auth.user_id()) |
| Exchange rates | Client-side fetch | rates table synced through PowerSync like any other table |
| User settings | localStorage only | user_settings table (baseCurrency, locale), auto-created on signup by a trigger |
| Schema indexes | N/A | Client SQLite indexes in AppSchema.ts (userDate: ['userId','date'], plus userId per table) |
| Migration | N/A | Firebase JSON export -> transform -> PostgREST import via scripts/import-firebase.mjs (see Migration History) |
| Account deletion | N/A | RLS-scoped delete of the user's rows + local wipe (removeAllUserData) |
What the current backend gives
- Offline-first - all reads/writes hit local SQLite; PowerSync syncs in the background, so the app works fully offline
- Per-user isolation - Postgres RLS (
auth.uid()) + PowerSync sync rules guarantee each user only ever syncs their own rows - Typed client schema -
AppSchema.tsdefines the SQLite tables; row<->item transforms are centralized intransforms.ts - Automatic upload queue - local mutations are queued and uploaded by the connector (
uploadData), with auto-reconcile on rejected ops - Incremental sync - PowerSync streams only changed rows after the initial sync
Store Pattern
| firebase | current | |
|---|---|---|
| Data source | Firebase listener -> setTrns() | db.watch('SELECT * FROM ...') -> reconcile -> setX() |
| Persist | deepUnref() + immediate localforage.setItem() | PowerSync SQLite is the source of truth; a blob read-through cache (useStoreCache.ts) primes the first frame |
| Offline ops | Manual helpers in trns/helpers.ts, ad-hoc localStorage keys | None needed - PowerSync's built-in upload queue handles offline writes |
| ID system | Firebase push IDs | Client-generated UUIDs, stable across sync (no remap) |
| Optimistic UI | Partial (fire-and-forget) | Full: upsertRow/deleteRow write local SQLite -> watch re-emits -> connector uploads in the background |
| Sync verification | None | PowerSync checksums per bucket; rejected uploads trigger auto-reconcile (uploadReconcile.ts) |
The vue-deepunref dependency is removed. Store data uses shallowRef, so deep cloning on every persist was unnecessary.
Performance
Algorithm improvements
| What | firebase | current | Impact |
|---|---|---|---|
| Wallet totals | getWalletTotal(id) called per wallet in reduce - each call filters all trns: O(W×N) | getWalletsTotals() single pass, distributes amounts via Map: O(N), then O(1) lookup per wallet | 5 wallets × 10K trns: ~50K → ~10K ops |
| Interval bucketing | Inline in Item.vue: intervalsInRange.reduce() filtering all trns per interval: O(N×I) | Extracted to bucketTrnsByIntervals(): single pass + binary search: O(N log I) | Year/365 days/5K trns: 1.8M → ~43K comparisons |
| Transaction filtering | 6 sequential .filter() chained via reduce + walletsIds.some(w => includes(w)) O(W²) + always sorts O(N log N) | Single .filter() with early returns + Set.has() O(1) + sorting optional | 5 intermediate arrays + O(W²) → single pass + O(1); sort O(N log N) skipped when not needed |
| Wallet lookup in getTotal | walletsIds.includes(): O(W) per transfer | new Set(walletsIds).has(): O(1) per transfer | Linear → constant per lookup |
| Demo data generation | { ...acc } spread in .reduce() + getTransactibleIds() called twice per iteration | Direct acc[i] = ... + lookups cached before loop | O(N²) → O(N) |
| Category statistics | categoriesStat eagerly computes both grouped and ungrouped on every change; filter().at(0) for biggest | Lazy computed() per variant, only accessed one executes; .find() stops at first match | Up to 2× fewer computeCategoriesWithData calls |
| Vertical categories filter | verticalCategories.filter(c => c.value !== 0) called twice in template (title + v-for): re-evaluated on every render | Cached in visibleVerticalCategories computed: filtered once, reused | 2 filter passes → 1 cached |
| Number formatting | currency.js library + currencies.find() O(N) called twice per format | Native Intl.NumberFormat cached in Map + currencyMap.get() O(1) | No library overhead; formatter created once per precision |
| Last created trn | Object.keys().sort().find() - sorts all trns O(N log N) then searches | Single O(N) pass tracking running max date | 10K trns: ~130K ops → ~10K ops |
| Category parent check | categoriesForBeParent calls Object.values(trns).some() O(N) per root category: O(M×N) | Pre-built usedCategoryIds Set via for...in O(N) + Set.has() O(1) per check; no intermediate arrays | 200 categories × 10K trns: ~2M → ~10K ops |
| Recent categories | Object.keys(trns).filter().sort() O(N log N) + acc.some(c => c.id) O(K²) dedup + includes() O(W) per category | Single O(N) pass via Map tracking latest date + entries().toSorted() O(K log K) + Set.has() O(1) | 10K trns: ~130K + K² → ~10K + K log K ops |
| Transactible categories | .reduce() with acc.includes() O(M) + childIds.filter(!acc.includes) O(C×M): O(M²) | Map<parentId, childIds[]> built in O(M) + Set dedup O(1) per check | 200 categories: ~40K → ~200 ops |
| Transfer detection | transferCategoriesIds.includes(categoryId) O(W) array scan per transaction | trn.type === TrnType.Transfer O(1) enum comparison | Linear → constant per check |
| Type filter counts | 3 separate .filter() passes over trnsIds (expense, income, transfer): O(3N) | Single loop with counters object: O(N) | 3 full passes → 1 pass |
| Trn item cache in list | computeTrnItem(id) called twice per item in template (item + date): O(2P) calls | Pre-computed trnItemsMap via Map: O(P) compute + O(1) lookups | 30 items: ~60 → ~30 computations per render |
| Group totals cache | getTotalOfTrnsIds() called twice per group in template (income + expense): O(2G) calls | Pre-computed groupTotals Map + paginatedTotal computed: O(G) compute + O(1) lookups | 15 groups: ~30 → ~15 total computations per render |
| Category grouping sort | categories.sort() called inside reduce on every child push: O(K² log K) per parent | Sort once after grouping loop, only if length > 1: O(K log K) per parent | 5 children: ~50 → ~12 comparisons |
| Category grouping totals | getTotalOfTrnsIds(cat.trnsIds).sum called twice per child (for push value + parent sum) | Computed once into catTotal, reused: single call per child | 2× fewer getTotal calls per child category |
| Data sync | Firebase full replacement on every update: O(N) | Delta sync: only changed records fetched - O(D) where D « N | Avoids reprocessing unchanged transactions |
| Interval generation | list.unshift(current) in while loop: O(n) per call, O(n²) total | list.push(current) + list.reverse(): O(1) per call, O(n) total | Year/365 days: ~66K shifts → ~365 pushes + 1 reverse |
| Date duration keys | Dynamic { [\${period}s`]: value } - TS seesRecord<string, number>, no type check against Duration` | toDuration(period, value) returns typed Duration via exhaustive switch | Compile-time safety; new period variants cause errors, not silent fallback |
| Date formatting | Each format function called twice (type: 'start' + type: 'end'), concatenated; 3 IIFE switch blocks for period comparison | Each format function returns full string; isSamePeriod(a, b, period) replaces all 3 switches | ~40% fewer lines in useGetDateRange; no double calls |
| Average computation | sum !== 0 checked 3 times + Object.keys(items).length > 0 guard | Early sum === 0 exit; day average always present (range ≥ 2 guaranteed) | Single check instead of 4 |
| Favorite categories sort | Inline sort duplicating parent-name + name logic (8 lines) | Reuses compareCategoriesByParentAndName utility (1 line) | DRY; single sort implementation to maintain |
| Recent categories loop | .reduce() iterates all sorted entries even after reaching maxCategories limit | for loop with break stops at limit | 200 categories, limit 16: ~200 → ~16 iterations |
| Recent categories iteration | for...of Object.keys(trnsItems) creates intermediate array | for...in trnsItems iterates directly on object | No intermediate array allocation for 10K+ trns |
| Category IDs reuse | Object.keys(items.value) called separately in categoriesRootIds, favoriteCategoriesIds | Both reuse categoriesIds computed | Keys computed once, shared across dependents |
| Transactible IDs reuse | categoriesIdsForTrnValues re-filters all categories with !hasChildren check | Delegates to already-computed transactibleIds + .filter(id !== 'transfer') | Avoids redundant O(M) parent scan |
| Children IDs dedup | getChildrenIdsOrParent duplicates .filter() logic from getChildrenIds | Delegates to getChildrenIds(), checks .length | Single filter implementation |
Bundle and rendering
| What | firebase | current |
|---|---|---|
| ECharts | 9 components in main bundle (incl. PieChart, DataZoom, MarkPoint) | 7 components in lazy-loaded async chunk (unused removed) |
| Theme plugin | 81 lines with SSR inline scripts and regex replacements | 32 lines, SPA-only appConfig updates from localStorage |
| Persist | deepUnref() on every localforage.setItem() - full recursive clone | Direct write, debounced 300ms, no cloning |
| Responsive layout | useWindowSize() JS listener for sidebar | Pure CSS sm: / md: Tailwind breakpoints |
| Container queries | Media queries only | CSS container queries (@xl/page:, @md/page:) for layout-aware sizing |
| Dependencies | currency.js for number formatting | Native Intl.NumberFormat (no dependency) |
| Fonts | 4 families (Roboto, Roboto Condensed, Nunito, Unica One), multiple weights, cyrillic only - Google Fonts <link> in head | Same 4 families via @nuxt/fonts, cyrillic + latin |
| Dark mode | import colors from 'tailwindcss/colors' + as any cast | usePreferredDark() + hardcoded neutral, handles system mode |
Security
| What | firebase | current |
|---|---|---|
| Calculator parser | new Function(expression) - code injection risk, requires unsafe-eval CSP | Recursive descent parser - only +,-,*,/ on numeric literals |
| Input validation | Firebase rules (client can't verify) | Client-side Zod schemas + Postgres column types / RLS |
| Auth tokens | Firebase-managed | Supabase-managed JWT + refresh token (validated by PowerSync via JWKS) |
| Session lifetime | Firebase default | Supabase Auth session with automatic token refresh |
| Cascade deletes | N/A (Firebase) | RLS-scoped delete of the user's rows (no FK constraints; PowerSync clears local data) |
| Data integrity | None enforced in code | Transfer ↔ categoryId='transfer', type change normalization, name uniqueness |
| Redirect safety | No validation | getSafeRedirectPath() - only relative paths starting with / (not //) |
Auth
| firebase | current | |
|---|---|---|
| Provider | Firebase Auth | Supabase Auth (email/password) |
| Plugin | nuxt-vuefire (auto-handles auth) | plugins/powersync.client.ts - connects PowerSync when a session resolves |
| Token flow | Firebase SDK internal | SupabaseConnector.fetchCredentials() hands the Supabase JWT to PowerSync; validated via JWKS |
| Offline | Firebase SDK handles reconnect | Persisted Supabase session in localStorage; the route guard reads it synchronously (useAuthSession) |
| Auth state hint | None | Persisted session read synchronously at cold start (getPersistedSession()), no network |
| Callback | Firebase redirect | Email/password sign-in, no OAuth popups |
| CORS | N/A | Supabase + PowerSync service URLs from runtime config |
| Login | Firebase auto-redirect | Stores the intended redirect, signs in via useSupabaseAuth() |
Sync and offline
| firebase | current | |
|---|---|---|
| Mechanism | Ad-hoc helpers in trns/helpers.ts | PowerSync's built-in upload queue - no hand-written offline queue |
| Writes | Scattered localStorage keys | upsertRow / deleteRow write local SQLite (INSERT/UPDATE, never ON CONFLICT) |
| Upload | saveTrnToAddLaterLocal(), removeTrnToDeleteLaterLocal() | SupabaseConnector.uploadData() drains the queue to Supabase |
| Reads | Manual on next load | db.watch(...) - one subscription covers initial load + realtime, local + synced |
| Conflict handling | None | Auto-reconcile: rejected uploads compute divergedOps (uploadReconcile.ts) and trigger forceResync |
| Validation | None | Client-side Zod schemas on form input; Postgres RLS on the server |
| First frame | None | Blob read-through cache (useStoreCache.ts) primes stores before PowerSync init |
PWA
| firebase | current | |
|---|---|---|
| Strategy | injectManifest (custom service worker via SW env var) | generateSW (auto-generated by Workbox) |
| Service worker | Hand-written sw.ts | None - Workbox generates it |
| Precaching | Manual configuration | Automatic for all build assets |
| Icons | Fetched from Iconify API at runtime | Bundled at build time via clientBundle |
TypeScript
| firebase | current | |
|---|---|---|
| Strict mode | Enabled, but unresolved errors | Zero TypeScript errors |
| Type casts | as any used across codebase | Zero as any - replaced with type guards, @vue-ignore, narrow casts |
| Transfer detection | transferCategoriesIds computed array | TrnType.Transfer enum + discriminated union types |
| Runtime validation | None | Zod schemas for user settings, rates, stat config, date params |
| Date period functions | getStartOf(date, string), getEndOf(date, string) - default fallback to day | Typed as Period, exhaustive switch with case 'day' - new variants cause compile errors |
| Duration construction | Dynamic { [\${period}s`]: value }- TS seesRecord<string, number>` | toDuration(period, value): Duration - typed return via exhaustive switch |
| Stat type mapping | Record<string, TrnType[]> with ?? fallback | Record<SeriesSlugSelected | StatTabSlug, TrnType[]> - all keys covered, no fallback needed |
| Average key resolution | key as keyof TotalReturns unsafe cast + as number | Typed key: keyof TotalReturns via explicit 'netIncome' -> 'sum' mapping, no casts |
| Currency rate fallback | rates[code] || 1, wallet?.currency || 'USD' - falsy 0/'' triggers fallback | rates[code] ?? 1, wallet?.currency ?? 'USD' - only null/undefined triggers fallback |
| Query param parsing | if (data.rangeOffset) - falsy 0 skipped | if (data.x !== undefined) - zero values applied correctly |
| Total props | baseCurrencyCode?: string loose type | baseCurrencyCode?: CurrencyCode - consistent with getAmountInRate |
| Category stat params | trnsItems: Record<TrnId, { categoryId?: string }> inline type | trnsItems: Record<TrnId, Pick<TrnItem, 'categoryId'>> - linked to source type |
| Sort null guard | sortCategoriesByAmount checks if (!a || !b) at runtime | Guard removed - TS already guarantees CategoryWithData parameters; explicit : number return type |
Tests
| firebase | current | |
|---|---|---|
| Config | No vitest config | 2 vitest projects: unit (node), store (happy-dom) |
| Sync tests | None | Row<->item transforms, upload reconcile / divergence planning (uploadReconcile.test.ts) |
| Calculator tests | Basic | 46 cases (precedence, decimals, division by zero, injection safety) |
| Extracted logic tests | None | Wallet filter/grouping/counts (archived exclusion, available visibility), category grouping, auth gate, stat barUtils, useStatItem |
| Test infrastructure | None | setup-store.ts (shared mocks for PowerSync/Supabase) |
Refactoring
Pure function extraction
Business logic moved from Vue components into testable pure functions:
- Statistics:
bucketTrnsByIntervals,computeAverageTotal,sortCategoriesByAmount,getSelectedType,computeBarStyle,formatCompactAmount,computeSeriesAverage,getTrnTypeByAmount - Wallets:
filterWalletsByCurrency,filterWalletsByViewType,groupWalletsByProperty,computeWalletCounts,sumWalletAmounts,getCreditAvailable - Categories:
collectCategoriesByTrns,flattenCategoriesWithValues,groupCategoriesWithValues - Sync:
reconcile(store reconciliation against fresh watch emissions),planDivergence(upload reconcile),transforms(row<->item) - Demo: simplified to read-only - removed per-entity CRUD methods (
addDemoCategory,deleteDemoWallet, etc.), onlygenerateDemoData()+isDemo
Component changes
Added: ActionButton, ChipButton, TabsScroll, TextMuted, SettingsCard, TitleSection, ItemBody, BottomSheetModal, WalletsPageListItem, WalletsPageGroupHeader, Onboarding, CurrenciesPageList, CurrenciesItem, ConfigSwitch, GroupingToggle, SelectorItem
Removed: Item1–4, Tabs2, TabsItem1/3/4, TextSm1/2, Title3/6/7/8, TitleOption, ToggleAction, SwitcherTabs, Welcome, Round (stat chart), SectionWithCollapse, CurrenciesToggleDep, getStyles.ts
Component patterns
- Class merging:
getStyles()utility removed - replaced withcn()(clsx + tailwind-merge) - Form inputs:
value/updateValueprops ->v-model/defineModel
Composable decomposition
- Wallets:
useWalletContextMenu,useWalletDelete,useWalletsFilter,useWalletsGrouping,useWalletsCounts - Categories:
useCategoryContextMenu - Statistics: Symbol-based injection keys (
filterKey,statDateKey,statConfigKey) for type-safeprovide/inject - Stat pages:
useStatPagecomposable - 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 viagetTrnsFiltercallback. Script section reduced by 24–53% across the three pages useCategoryLongPresscomposable: extracted 59 lines of duplicated long-press logic fromRound2lines.vue,Line.vue, andVertical.vueinto a shared composable. Handles long-press to create transactions from categories with proper date selection from chart intervalsuseStatDateencapsulation: navigation computed properties (shouldShowNav,isAtEnd,isAtStart,isDayToday,shouldShowNavHome) and directparams.valuemutations moved fromNavigation.vueandchart/Wrap.vueintouseStatDatemethods (selectInterval(),resetInterval(),setIntervalsBy(),navigate()). Navigation.vue reduced from ~54 lines of script to 5 lines- Duplicate
stat/date/Nav.vueremoved: identical copy ofdate/Nav.vuewas deleted - only the canonicalDateNavcomponent remains UiNumberSteppercomponent: extracted duplicated ±/input counter pattern fromConfigModal.vueinto a reusable UI primitive withv-model,min,maxprops- Stat component self-containment:
StatAverageconsolidated three conditionalAmountblocks into one via computed props;StatSumWrapandStatChartWrapremoved redundant prop-drilling in favor of inject;StatItemmerged two modal blocks into one via state-machine pattern - Config caching:
Section2.vueandLine.vuecachestatConfig.config.value.catsList.*reads into computed properties (isItemsBg,isLines,isRoundIcon,isListGrouped, etc.) for template readability Range.vuesimplified: replaced manualintervalsInRangeindexing withstatDate.selectedInterval; extractedisIntervalSelectedcomputeduseStatItemcleanup: extractedbaseTrnsIdsForSelectioncomputed to eliminate duplicated interval-selection logic; fixed unsafetype.value as keyof TotalReturnscast withtype.value ?? 'sum'fallback; fixed chart average denominator using wrong data source (intervalsData->intervalsDataWithFilteredCategories)useCategoriesExpandedcomposable: extracted 52-line expand/collapse logic fromDetailedSection.vue(286->234 lines)- Stat component rename:
Section/Section2->RoundSection/DetailedSectionfor clarity barUtilsextraction:computeBarStyle()andformatCompactAmount()extracted from inline template logic into testable utilities- Wallet statistics consistency: archived wallets fully excluded from all counts (total, type sums, withdrawal, available) via early
continue;available.isShowrequires both withdrawal and credit wallets; auto-reset filter to Total when currency switch produces empty results - Store sync consolidation:
showErrorToast/showSuccessToast/showWarningToastmerged into genericshowToast(type, key, params?) - Wallet constants:
WALLET_STORAGE_KEYSextracted from inline strings toconstants.ts
Semantic renaming
| firebase | current |
|---|---|
getTrnsIds | filterTrnsIds |
getCategoriesWithData | computeCategoriesWithData |
getTotalOfTrnsIds | computeTotalForTrnsIds |
getChildsIds | getChildrenIds |
trnFormCreate / Edit / Duplicate | openFormForCreate / Edit / Duplicate |
getIsShowSum | shouldShowSum |
isItTransactible | isTransactible |
editedAt | updatedAt |
TransferType | TransferSide |
Selector2 (categories) | SelectorGrid |
Selector (categories) | SelectorTree |
Checkbox (ui) | SwitchItem |
Tabs1 (ui) | TabsBar |
Toggle3 (ui) | ToggleControlled |
Section (stat) | RoundSection |
Section2 (stat) | DetailedSection |
isNotTransferCategory | shouldShowAmounts |
biggestCatNumber | maxCategoryValues |
addMarkArea | withMarkArea |
toggleOpened | toggleAllCategoriesExpanded |
Data model
| firebase | current | |
|---|---|---|
| Category children | Denormalized childIds array | Computed dynamically from parentId |
| Transfer detection | transferCategoriesIds (computed from category data) | TrnType.Transfer enum value |
| Adjustment | Not distinguished | categoryId === 'adjustment', excluded from income/expense stats |
UX Improvements
- Haptic feedback on transaction submit (mobile vibration)
- Accessibility:
<div>-><button>in interactive components, i18naria-labelon icon-only buttons - Calculator: fixed infinite loop on zero result, proper
0.decimal display, rounded division results - Form: amount clears immediately after submit, popover closes before edit navigation
- Onboarding:
Welcomecomponent replaced withOnboarding- improved layout, cookie-persisted state (finapp.isOnboarded) - Currencies page: two sections (Used / All) with global search, O(1) lookup via
currencyMap - Wallet/category detail: edit/delete actions moved to three-dot menu
- 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
- Wallet edit guard: redirect to
/walletswhen wallet doesn't exist - Toast dismiss: click anywhere on toast notification to close it
- Layout:
keepaliveprop from layout slot for page transition persistence - Settings page: reorganized with
SettingsCard, language dropdown, currency picker modal, danger zone with "Delete all data" - Login page: version display at bottom
i18n
| firebase | current | |
|---|---|---|
| Error messages | Generic or none | Per-entity error keys (categories.errors.*, wallets.errors.*, trns.errors.*) |
| Form labels | Verbose (Category name, Category color) | Concise (Name, Color, Icon) |
| Onboarding | welcome.* keys | onboarding.* with step-by-step guidance |
| Theme | neutral, radius | Background color, Rounding, Appearance |
| Typos | errorChilds | errorChildren |
Code Quality
Removed from firebase
vue-deepunrefdependency- Old page-specific wallet composables
- Dead code: unused props, emits, functions, stale TODOs
- Deprecated types:
WalletsDirty,TrnItemDirty,TransferDeprecated,TrnTypeSlug - CSS utilities:
flex-center-col,absolute-center,layoutBase nuxt-vuefireandfirebasedependencies- Firebase config files (
firebase.json,.firebaserc,pnpm-workspace.yaml)
Fixed
- Memory leaks:
addEventListenerwithout cleanup,ResizeObserverwithout disconnect - Null spreading in 3 stores (missing
?? {}guards) - Emit during render in
SelectionCategoriesFast - Magic numbers replaced with
TrnTypeenum (8 places) ref<any>replaced with proper types (4 files)- Error layout localized (hardcoded English -> i18n keys)
- Tailwind v4 CSS syntax:
[var(--name)]->(--name)functional notation - Radius uses
??(not||) so0is valid:radius ?? 0.375 - CSS utility typo:
bottomSheetDrugClassesCustom->bottomSheetDragClassesCustom - ESLint: layout
vue/no-multiple-template-rootexception createLogger(prefix)- dev-onlylog, always-onwarnanderror, with[prefix]formatting across all modules- Hardcoded
'ru'locale in chart formatters -> dynamic locale from settings Statistics.vueempty group sections hidden withv-showSelector.vueonSelectRange(value: any)-> typed{ end: unknown, start: unknown }setWalletViewTypetype fixed fromWalletType | 'total'toWalletViewTypes | 'total'- Version bumped from v6.6.4 (the
firebasebranch baseline) useAmount.getAmountInBaseRate: deadnoFormatparam removed, unnecessary+unary on number removeduseFilter: redundant[...array.filter()]and[...string.split()]spreads removed - both methods already return new arraysgetUCalendarTimedDate:new Date(string)parsed as UTC ->date.toDate(getLocalTimeZone())for correct local timezoneuseGetDateRange:type: 'start' | 'end'pattern removed - format functions return full strings directly, 3 IIFE switches replaced withisSamePeriodhelpergetIntervalsInRange:rangeOffsetmade optional inIntervalsInRangeProps- not used bygetIntervalsInRange, only bycalculateIntervalInRangecompareCategoriesByParentAndName:||->??for empty string fallback (consistent nullish semantics)useCategoriesStore: removed 5 unnecessary?? {}defensive checks -itemsis always initialized viashallowRefuseCategoryLongPress: removed redundant!after optional chaining (value?.startalready narrowed)useCategoriesExpanded:forEach->for...of;reducewithascast ->Object.fromEntriessortCategoriesByAmount: renamedisP/isN->bothPositive/bothNegativefor readability; removed redundantcategories ??= [](already initialized in grouping)
Dependencies
(What changed from the firebase branch to the current app. For the full list see the project package.json.)
| Removed (firebase era) | Added (current) |
|---|---|
firebase | @powersync/web |
nuxt-vuefire | @supabase/supabase-js |
vue-deepunref | @vueuse/nuxt |
currency.js | zod |
es-toolkit |