@wealthfolio/addon-sdk 3.7.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -42,6 +42,8 @@ integrations, and visualizations.
42
42
  - **ESM Support**: Modern ECMAScript modules with tree-shaking support
43
43
  - **Comprehensive Logging**: Built-in logging system with multiple levels
44
44
  - **Event System**: Subscribe to application events and state changes
45
+ - **Spend Categorization**: Manage reusable expense, income, and savings rules
46
+ - **Localization**: Follow the host locale with scoped addon translations
45
47
  - **Performance Optimized**: Lightweight bundle with minimal overhead
46
48
  - **Developer Tools**: Built-in debugging and development utilities
47
49
  - **Backwards Compatible**: Stable API with semantic versioning
@@ -125,12 +127,12 @@ pnpm add @wealthfolio/addon-sdk @tanstack/react-query
125
127
  - **Node.js**: >= 20.0.0
126
128
  - **React**: ^19.2.4 (peer dependency and host-provided version)
127
129
  - **TypeScript**: ^5.0.0 (recommended for development)
128
- - **React Query**: ^4.0.0 or ^5.0.0 (for data fetching)
130
+ - **React Query**: ^5.90.0 (for data fetching)
129
131
 
130
132
  ### Package Information
131
133
 
132
134
  - **Package Name**: `@wealthfolio/addon-sdk`
133
- - **Current Version**: 3.7.0
135
+ - **Current Version**: 3.8.0
134
136
  - **Bundle Format**: ESM (ECMAScript Modules)
135
137
  - **Type Definitions**: Included (TypeScript ready)
136
138
  - **License**: MIT
@@ -140,23 +142,28 @@ pnpm add @wealthfolio/addon-sdk @tanstack/react-query
140
142
 
141
143
  ### Import Methods
142
144
 
143
- The SDK supports multiple import patterns:
145
+ The SDK supports a public entry point plus focused subpath imports:
144
146
 
145
147
  ```typescript
146
- // Default import (recommended)
147
- import { getAddonContext } from '@wealthfolio/addon-sdk';
148
-
149
- // Named imports
150
- import { AddonContext, PermissionLevel } from '@wealthfolio/addon-sdk';
151
-
152
- // Type-only imports
153
- import type { AddonManifest, Permission } from '@wealthfolio/addon-sdk';
154
-
155
- // Subpath imports
156
- import type { PortfolioHolding } from '@wealthfolio/addon-sdk/types';
157
- import { PERMISSION_CATEGORIES } from '@wealthfolio/addon-sdk/permissions';
148
+ // Public entry point (recommended)
149
+ import type {
150
+ AddonContext,
151
+ AddonManifest,
152
+ Holding,
153
+ Permission,
154
+ RiskLevel,
155
+ } from '@wealthfolio/addon-sdk';
156
+ import { PERMISSION_CATEGORIES } from '@wealthfolio/addon-sdk';
157
+
158
+ // Optional module-specific subpath imports
159
+ import type { AddonManifest as Manifest } from '@wealthfolio/addon-sdk/manifest';
160
+ import type { Permission as AddonPermission } from '@wealthfolio/addon-sdk/permissions';
158
161
  ```
159
162
 
163
+ The `/types` subpath contains core context, routing, sidebar, and event types.
164
+ Financial data types such as `Account`, `Activity`, and `Holding` are exported
165
+ from the package root.
166
+
160
167
  ## 🏗️ Project Structure
161
168
 
162
169
  Create your addon with the following recommended structure:
@@ -233,11 +240,16 @@ Create a `manifest.json` file in your addon root:
233
240
  "homepage": "https://github.com/yourname/investment-fees-tracker",
234
241
  "license": "MIT",
235
242
  "main": "dist/addon.js",
236
- "sdkVersion": "3.7.0",
237
- "minWealthfolioVersion": "3.7.0",
243
+ "sdkVersion": "3.8.0",
244
+ "minWealthfolioVersion": "3.8.0",
238
245
  "keywords": ["portfolio", "fees", "tracking", "analytics"],
239
246
  "icon": "data:image/svg+xml;base64,...",
240
247
  "permissions": [
248
+ {
249
+ "category": "accounts",
250
+ "functions": ["getAll"],
251
+ "purpose": "List accounts whose holdings will be analyzed"
252
+ },
241
253
  {
242
254
  "category": "portfolio",
243
255
  "functions": ["getHoldings"],
@@ -247,6 +259,11 @@ Create a `manifest.json` file in your addon root:
247
259
  "category": "activities",
248
260
  "functions": ["getAll"],
249
261
  "purpose": "Analyze transaction history for fee calculations"
262
+ },
263
+ {
264
+ "category": "performance",
265
+ "functions": ["calculateSummary"],
266
+ "purpose": "Calculate account performance alongside fee totals"
250
267
  }
251
268
  ]
252
269
  }
@@ -285,7 +302,11 @@ example:
285
302
  ```typescript
286
303
  // src/addon.tsx
287
304
  import { QueryClientProvider } from '@tanstack/react-query';
288
- import type { AddonContext, AddonEnableFunction } from '@wealthfolio/addon-sdk';
305
+ import type {
306
+ AddonContext,
307
+ AddonEnableFunction,
308
+ QueryClient,
309
+ } from '@wealthfolio/addon-sdk';
289
310
  import FeesPage from './pages/fees-page';
290
311
 
291
312
  // Main addon component
@@ -319,7 +340,7 @@ const enable: AddonEnableFunction = (context) => {
319
340
 
320
341
  // Create wrapper component with this addon's QueryClient
321
342
  const InvestmentFeesTrackerWrapper = () => {
322
- const addonQueryClient = context.api.query.getClient();
343
+ const addonQueryClient = context.api.query.getClient() as QueryClient;
323
344
  return (
324
345
  <QueryClientProvider client={addonQueryClient}>
325
346
  <InvestmentFeesTrackerAddon ctx={context} />
@@ -367,7 +388,7 @@ export default enable;
367
388
 
368
389
  1. **Addon Query Client**: Uses `context.api.query.getClient()` for local data
369
390
  fetching with host invalidation bridging
370
- 2. **UI Icons**: Leverages `@wealthfolio/ui` for consistent iconography
391
+ 2. **UI Icons**: Uses a host-supported icon token for consistent navigation
371
392
  3. **Error Handling**: Comprehensive error handling with logging
372
393
  4. **Resource Management**: Proper cleanup of sidebar items and event listeners
373
394
  5. **TypeScript**: Full type safety with proper imports
@@ -377,10 +398,9 @@ export default enable;
377
398
 
378
399
  ```typescript
379
400
  // components/FeesPage.tsx
380
- import React, { useEffect, useState } from 'react';
401
+ import React, { useEffect } from 'react';
381
402
  import { useQuery } from '@tanstack/react-query';
382
403
  import type { AddonContext } from '@wealthfolio/addon-sdk';
383
- import type { Holding, Account, Activity } from '@wealthfolio/addon-sdk/types';
384
404
 
385
405
  interface FeesPageProps {
386
406
  ctx: AddonContext;
@@ -415,25 +435,20 @@ export function FeesPage({ ctx }: FeesPageProps) {
415
435
 
416
436
  // Calculate total fees from activities
417
437
  const totalFees = React.useMemo(() => {
418
- if (!activities?.data) return 0;
438
+ if (!activities) return 0;
419
439
 
420
- return activities.data.reduce((total, activity) => {
440
+ return activities.reduce((total, activity) => {
421
441
  // Look for fee-related activities or transaction costs
422
- const fee = activity.fee || 0;
442
+ const fee = Number(activity.fee ?? 0);
423
443
  return total + fee;
424
444
  }, 0);
425
445
  }, [activities]);
426
446
 
427
447
  useEffect(() => {
428
448
  if (!isLoading) {
429
- ctx.api.logger.info('Fees data loaded successfully', {
430
- accountsCount: accounts?.length,
431
- holdingsCount: holdings?.length,
432
- activitiesCount: activities?.data?.length,
433
- totalFees
434
- });
449
+ ctx.api.logger.info("Fees data loaded successfully");
435
450
  }
436
- }, [isLoading, accounts, holdings, activities, totalFees, ctx.api.logger]);
451
+ }, [isLoading, ctx.api.logger]);
437
452
 
438
453
  if (isLoading) {
439
454
  return (
@@ -476,14 +491,16 @@ export function FeesPage({ ctx }: FeesPageProps) {
476
491
  <div className="bg-white p-6 rounded-lg shadow border">
477
492
  <h2 className="text-xl font-semibold mb-4">Recent Fee Activities</h2>
478
493
  <div className="space-y-3">
479
- {activities?.data?.slice(0, 5).map((activity) => (
494
+ {activities?.slice(0, 5).map((activity) => (
480
495
  <div key={activity.id} className="flex justify-between items-center py-2 border-b">
481
496
  <div>
482
497
  <p className="font-medium">{activity.activityType}</p>
483
- <p className="text-sm text-gray-600">{activity.date}</p>
498
+ <p className="text-sm text-gray-600">
499
+ {activity.date.toLocaleDateString()}
500
+ </p>
484
501
  </div>
485
502
  <span className="text-red-600 font-medium">
486
- ${(activity.fee || 0).toFixed(2)}
503
+ ${Number(activity.fee ?? 0).toFixed(2)}
487
504
  </span>
488
505
  </div>
489
506
  ))}
@@ -535,10 +552,13 @@ export default AnalyticsDashboard;
535
552
  ```typescript
536
553
  // hooks/usePortfolioData.ts
537
554
  import { useState, useEffect } from 'react';
538
- import { getAddonContext } from '@wealthfolio/addon-sdk';
539
- import type { Holding, PerformanceResult } from '@wealthfolio/addon-sdk/types';
555
+ import type {
556
+ AddonContext,
557
+ Holding,
558
+ PerformanceResult,
559
+ } from '@wealthfolio/addon-sdk';
540
560
 
541
- export function usePortfolioData(accountId?: string) {
561
+ export function usePortfolioData(ctx: AddonContext, accountId?: string) {
542
562
  const [holdings, setHoldings] = useState<Holding[]>([]);
543
563
  const [performance, setPerformance] = useState<PerformanceResult | null>(
544
564
  null,
@@ -552,26 +572,25 @@ export function usePortfolioData(accountId?: string) {
552
572
  setLoading(true);
553
573
  setError(null);
554
574
 
555
- const ctx = getAddonContext();
575
+ if (!accountId) {
576
+ setHoldings([]);
577
+ setPerformance(null);
578
+ return;
579
+ }
556
580
 
557
- const holdingsData = await ctx.api.portfolio.getHoldings(
558
- accountId || '',
559
- );
581
+ const holdingsData = await ctx.api.portfolio.getHoldings(accountId);
560
582
  setHoldings(holdingsData);
561
583
 
562
- if (accountId) {
563
- const performanceData =
564
- await ctx.api.portfolio.calculatePerformanceSummary({
565
- itemType: 'account',
566
- itemId: accountId,
567
- });
568
- console.log(
569
- performanceData.returns.twr,
570
- performanceData.returns.irr,
571
- performanceData.risk.maxDrawdown,
572
- );
573
- setPerformance(performanceData);
574
- }
584
+ const performanceData = await ctx.api.performance.calculateSummary({
585
+ itemType: 'account',
586
+ itemId: accountId,
587
+ });
588
+ console.log(
589
+ performanceData.returns.twr,
590
+ performanceData.returns.irr,
591
+ performanceData.risk.maxDrawdown,
592
+ );
593
+ setPerformance(performanceData);
575
594
  } catch (err) {
576
595
  setError(err instanceof Error ? err.message : 'Unknown error');
577
596
  } finally {
@@ -580,7 +599,7 @@ export function usePortfolioData(accountId?: string) {
580
599
  }
581
600
 
582
601
  fetchData();
583
- }, [accountId]);
602
+ }, [accountId, ctx]);
584
603
 
585
604
  return { holdings, performance, loading, error };
586
605
  }
@@ -594,18 +613,28 @@ cash flows.
594
613
 
595
614
  ### Permission Categories
596
615
 
597
- | Category | Risk Level | Description |
598
- | -------------------- | ---------- | ------------------------------- |
599
- | `ui` | Low | Add navigation items and routes |
600
- | `market-data` | Low | Access market prices and quotes |
601
- | `events` | Low | Listen to application events |
602
- | `currency` | Low | Access exchange rates |
603
- | `portfolio` | Medium | Access holdings and valuations |
604
- | `files` | Medium | File dialog operations |
605
- | `financial-planning` | Medium | Goals and contribution limits |
606
- | `activities` | High | Transaction history access |
607
- | `accounts` | High | Account management |
608
- | `settings` | High | Application configuration |
616
+ | Category | Risk Level | Description |
617
+ | --------------------- | ---------- | -------------------------------------------- |
618
+ | `market-data` | Low | Search and synchronize market data |
619
+ | `quotes` | Low | Read and update quotes |
620
+ | `events` | Low | Listen to application events |
621
+ | `currency` | Low | Access exchange rates |
622
+ | `assets` | Medium | Read and update financial asset profiles |
623
+ | `performance` | Medium | Calculate portfolio performance |
624
+ | `spending` | Medium | View categories and manage spending rules |
625
+ | `financial-planning` | Medium | Manage goals and allocations |
626
+ | `contribution-limits` | Medium | Manage contribution limits |
627
+ | `files` | Medium | Open host file dialogs |
628
+ | `settings` | Medium | Access application configuration |
629
+ | `portfolio` | High | Access holdings and valuations |
630
+ | `activities` | High | Access and modify transaction history |
631
+ | `accounts` | High | Access and create accounts |
632
+ | `snapshots` | High | Access and modify holdings snapshots |
633
+ | `network` | High | Request declared external HTTPS hosts |
634
+ | `secrets` | High | Store and use secrets through the OS keyring |
635
+
636
+ `ui`, `navigation`, `query`, `toast`, `logger`, and `storage` are baseline
637
+ capabilities and must not be declared as permissions.
609
638
 
610
639
  ### Declaring Permissions
611
640
 
@@ -614,7 +643,7 @@ cash flows.
614
643
  "permissions": [
615
644
  {
616
645
  "category": "portfolio",
617
- "functions": ["getHoldings", "getHolding", "calculatePerformanceSummary"],
646
+ "functions": ["getHoldings", "getHolding"],
618
647
  "purpose": "Display detailed portfolio analytics and performance metrics"
619
648
  },
620
649
  {
@@ -624,8 +653,13 @@ cash flows.
624
653
  },
625
654
  {
626
655
  "category": "market-data",
627
- "functions": ["searchTicker", "getQuoteHistory"],
656
+ "functions": ["searchTicker"],
628
657
  "purpose": "Show price charts and enable ticker search functionality"
658
+ },
659
+ {
660
+ "category": "spending",
661
+ "functions": ["getCategories", "saveRule"],
662
+ "purpose": "Save categorization rules selected by the user"
629
663
  }
630
664
  ]
631
665
  }
@@ -794,7 +828,6 @@ Add an item to the application sidebar.
794
828
  `chart-bar`, or `calendar-dots`
795
829
  - `config.route` (string): Navigation route
796
830
  - `config.order` (number): Display order (optional)
797
- - `config.onClick` (function): Click handler (optional)
798
831
 
799
832
  **Returns:** `SidebarItemHandle` with `remove()` method
800
833
 
@@ -805,8 +838,10 @@ Register a new route in the application.
805
838
  **Parameters:**
806
839
 
807
840
  - `route.path` (string): Route path pattern
808
- - `route.render` (function): `({ root, location }) => void` — mount your React
809
- root into the provided `root` element
841
+ - `route.component` (component): Preferred; the host mounts the React component
842
+ and passes the current `location`
843
+ - `route.render` (function): Legacy imperative alternative receiving
844
+ `{ root, location }`
810
845
 
811
846
  #### `onDisable(callback)`
812
847
 
@@ -821,49 +856,72 @@ Register cleanup callback for addon disable.
821
856
  All data access is performed through the context's `api` property:
822
857
 
823
858
  ```typescript
824
- const ctx = getAddonContext();
859
+ // Use the ctx parameter supplied to enable(ctx), or pass it to this helper.
825
860
 
826
861
  // Portfolio data
827
862
  const holdings = await ctx.api.portfolio.getHoldings(accountId);
828
863
  const accounts = await ctx.api.accounts.getAll();
829
864
 
830
865
  // Market data
831
- const quotes = await ctx.api.marketData.getQuoteHistory(symbol);
832
- const profile = await ctx.api.marketData.getAssetProfile(assetId);
866
+ const symbols = await ctx.api.market.searchTicker('AAPL');
867
+ const quotes = await ctx.api.quotes.getHistory(assetId);
868
+ const profile = await ctx.api.assets.getProfile(assetId);
833
869
 
834
870
  // Financial planning
835
871
  const goals = await ctx.api.goals.getAll();
836
- const limits = await ctx.api.financialPlanning.getContributionLimit();
872
+ const limits = await ctx.api.contributionLimits.getAll();
873
+
874
+ // Historical exchange rates and spend categorization (Wealthfolio 3.8+)
875
+ const rates = await ctx.api.exchangeRates.getRatesForDates([
876
+ { fromCurrency: 'USD', toCurrency: 'EUR', date: '2026-09-04' },
877
+ ]);
878
+ const spendCategories = await ctx.api.spending.getCategories('expense');
837
879
 
838
880
  // Settings
839
- const settings = await ctx.api.getSettings();
881
+ const settings = await ctx.api.settings.get();
840
882
 
841
883
  // Logging and debugging
842
884
  ctx.api.logger.info('Operation completed successfully');
843
- ctx.api.logger.error('Error occurred:', error);
844
- ctx.api.logger.debug('Debug info:', debugData);
885
+ ctx.api.logger.error(`Error occurred: ${String(error)}`);
886
+ ctx.api.logger.debug(`Debug info: ${JSON.stringify(debugData)}`);
845
887
  ```
846
888
 
847
889
  ### Available API Methods
848
890
 
849
- | Method | Description | Permission Required |
850
- | ----------------------------------------------- | --------------------------------------------------------- | -------------------- |
851
- | `portfolio.getHoldings(accountId)` | Get portfolio holdings for account | `portfolio` |
852
- | `portfolio.getHolding(accountId, assetId)` | Get specific holding | `portfolio` |
853
- | `portfolio.calculatePerformanceSummary(params)` | Calculate performance metrics | `portfolio` |
854
- | `portfolio.getIncomeSummary()` | Get income summary data | `portfolio` |
855
- | `accounts.getAll()` | Get all account information | `accounts` |
856
- | `accounts.create(account)` | Create new account | `accounts` |
857
- | `activities.getAll(accountId?)` | Get activity history (optionally filtered to one account) | `activities` |
858
- | `activities.create(activity)` | Create new activity | `activities` |
859
- | `marketData.getQuoteHistory(symbol)` | Get historical quotes | `market-data` |
860
- | `marketData.getAssetProfile(assetId)` | Get asset profile | `market-data` |
861
- | `marketData.searchTicker(query)` | Search for tickers | `market-data` |
862
- | `goals.getAll()` | Get financial goals | `financial-planning` |
863
- | `goals.getFunding(goalId)` | Get funding rules for a goal | `financial-planning` |
864
- | `goals.saveFunding(goalId, rules)` | Save funding rules for a goal | `financial-planning` |
865
- | `settings.get()` | Get app settings | `settings` |
866
- | `query.getClient()` | Get this addon's QueryClient | None |
891
+ | Method | Description | Permission Required |
892
+ | ------------------------------------------ | ------------------------------------------- | --------------------- |
893
+ | `portfolio.getHoldings(accountId)` | Get portfolio holdings for an account | `portfolio` |
894
+ | `portfolio.getHolding(accountId, assetId)` | Get a specific holding | `portfolio` |
895
+ | `performance.calculateSummary(params)` | Calculate performance metrics | `performance` |
896
+ | `accounts.getAll()` | Get all account information | `accounts` |
897
+ | `accounts.create(account)` | Create an account | `accounts` |
898
+ | `activities.getAll(accountId?)` | Get activity history | `activities` |
899
+ | `activities.create(activity)` | Create an activity | `activities` |
900
+ | `market.searchTicker(query)` | Search for tickers | `market-data` |
901
+ | `assets.getProfile(assetId)` | Get a financial asset profile | `assets` |
902
+ | `quotes.getHistory(assetId)` | Get historical quotes | `quotes` |
903
+ | `exchangeRates.getRatesForDates(pairs)` | Resolve dated exchange rates | `currency` |
904
+ | `spending.isEnabled()` | Check whether Spending is enabled | `spending` |
905
+ | `spending.getCategories(kind?)` | List expense, income, or savings categories | `spending` |
906
+ | `spending.getRules()` | List this addon's categorization rules | `spending` |
907
+ | `spending.saveRule(rule)` | Create or update an addon-owned rule | `spending` |
908
+ | `spending.deleteRule(ruleKey)` | Delete an addon-owned rule | `spending` |
909
+ | `spending.rerunRules(onlyUncategorized?)` | Re-run categorization rules | `spending` |
910
+ | `goals.getAll()` | Get financial goals | `financial-planning` |
911
+ | `contributionLimits.getAll()` | Get contribution limits | `contribution-limits` |
912
+ | `settings.get()` | Get application settings | `settings` |
913
+ | `query.getClient()` | Get this addon's QueryClient | None |
914
+
915
+ The Spending and dated exchange-rate APIs require Wealthfolio 3.8 or newer. See
916
+ the [complete API reference](../../docs/addons/addon-api-reference.md).
917
+
918
+ ### Localization
919
+
920
+ Wealthfolio 3.8 addons can register private translation bundles with
921
+ `registerTranslations()` and read them from React components with
922
+ `useAddonTranslation()`. Addons using these exports must set
923
+ `minWealthfolioVersion` to `3.8.0` or newer. See the
924
+ [localization guide](../../docs/addons/addon-localization.md).
867
925
 
868
926
  > Tip: `activities.getAll` accepts an optional account ID string to scope
869
927
  > results to a single account. The SDK normalizes this for both desktop (Tauri)
@@ -900,21 +958,14 @@ const response = await ctx.api.activities.search(
900
958
  The SDK provides a comprehensive logging system:
901
959
 
902
960
  ```typescript
903
- const ctx = getAddonContext();
961
+ // Use the ctx parameter supplied to enable(ctx), or pass it to this helper.
904
962
 
905
- // Log levels: 'error', 'warn', 'info', 'debug'
906
- ctx.api.logger.error('Critical error occurred', { error, context });
907
- ctx.api.logger.warn('Warning message', additionalData);
963
+ // Each method accepts one string message.
964
+ ctx.api.logger.error(`Critical error occurred: ${String(error)}`);
965
+ ctx.api.logger.warn(`Warning message: ${String(additionalData)}`);
908
966
  ctx.api.logger.info('Information message');
909
- ctx.api.logger.debug('Debug information', debugObject);
910
-
911
- // Set log level (for development)
912
- ctx.api.logger.setLevel('debug');
913
-
914
- // Check if logging level is enabled
915
- if (ctx.api.logger.isLevelEnabled('debug')) {
916
- ctx.api.logger.debug('Expensive debug operation', expensiveData);
917
- }
967
+ ctx.api.logger.debug(`Debug information: ${JSON.stringify(debugObject)}`);
968
+ ctx.api.logger.trace('Detailed trace message');
918
969
  ```
919
970
 
920
971
  ### Addon QueryClient Integration
@@ -924,28 +975,34 @@ across that addon's route renders, not shared with the host or other addons.
924
975
  Invalidate/refetch operations are mirrored to the host:
925
976
 
926
977
  ```typescript
927
- // Access this addon's QueryClient instance
928
- const addonQueryClient = context.api.query.getClient();
978
+ import { QueryClientProvider, useQuery } from '@tanstack/react-query';
979
+ import type { AddonContext, QueryClient } from '@wealthfolio/addon-sdk';
929
980
 
930
981
  // Wrap your components with QueryClientProvider
931
- const MyAddonWrapper = () => {
982
+ const MyAddonWrapper = ({ ctx }: { ctx: AddonContext }) => {
983
+ const addonQueryClient = ctx.api.query.getClient() as QueryClient;
984
+
932
985
  return (
933
986
  <QueryClientProvider client={addonQueryClient}>
934
- <MyAddonComponent />
987
+ <MyAddonComponent ctx={ctx} />
935
988
  </QueryClientProvider>
936
989
  );
937
990
  };
938
991
 
939
992
  // Use React Query hooks in your components
940
- function MyAddonComponent() {
993
+ function MyAddonComponent({ ctx }: { ctx: AddonContext }) {
941
994
  const { data: accounts, isLoading } = useQuery({
942
995
  queryKey: ['accounts'],
943
996
  queryFn: () => ctx.api.accounts.getAll()
944
997
  });
945
998
 
999
+ const selectedAccountId = accounts?.[0]?.id;
946
1000
  const { data: holdings } = useQuery({
947
1001
  queryKey: ['holdings', selectedAccountId],
948
- queryFn: () => ctx.api.portfolio.getHoldings(selectedAccountId),
1002
+ queryFn: () =>
1003
+ selectedAccountId
1004
+ ? ctx.api.portfolio.getHoldings(selectedAccountId)
1005
+ : Promise.resolve([]),
949
1006
  enabled: !!selectedAccountId
950
1007
  });
951
1008
 
@@ -979,9 +1036,14 @@ the required development-tools upgrade.
979
1036
  // Before
980
1037
  import ctx from '@wealthfolio/addon-sdk';
981
1038
 
982
- // After (recommended)
983
- import { getAddonContext } from '@wealthfolio/addon-sdk';
984
- const ctx = getAddonContext();
1039
+ // Current SDK
1040
+ import type { AddonEnableFunction } from '@wealthfolio/addon-sdk';
1041
+
1042
+ const enable: AddonEnableFunction = (ctx) => {
1043
+ // Pass ctx to components, hooks, and helper functions that need host APIs.
1044
+ };
1045
+
1046
+ export default enable;
985
1047
  ```
986
1048
 
987
1049
  #### Type Imports
@@ -1208,9 +1270,9 @@ npm publish --tag beta
1208
1270
 
1209
1271
  ```typescript
1210
1272
  // In your addon
1211
- const ctx = getAddonContext();
1212
- ctx.api.logger.setLevel('debug');
1213
- ctx.api.logger.debug('Debug information:', data);
1273
+ function logDebug(ctx: AddonContext, data: unknown) {
1274
+ ctx.api.logger.debug(`Debug information: ${JSON.stringify(data)}`);
1275
+ }
1214
1276
  ```
1215
1277
 
1216
1278
  #### 2. Development Console
@@ -1241,11 +1303,9 @@ if (process.env.NODE_ENV === 'development') {
1241
1303
  #### 1. Error Handling
1242
1304
 
1243
1305
  ```typescript
1244
- import { getAddonContext } from '@wealthfolio/addon-sdk';
1245
-
1246
- async function fetchPortfolioData() {
1247
- const ctx = getAddonContext();
1306
+ import type { AddonContext } from '@wealthfolio/addon-sdk';
1248
1307
 
1308
+ async function fetchPortfolioData(ctx: AddonContext) {
1249
1309
  try {
1250
1310
  // Get all accounts first, then holdings for each
1251
1311
  const accounts = await ctx.api.accounts.getAll();
@@ -1254,14 +1314,8 @@ async function fetchPortfolioData() {
1254
1314
  ).then((results) => results.flat());
1255
1315
  return holdings;
1256
1316
  } catch (error) {
1257
- ctx.api.logger.error('Failed to fetch holdings:', error);
1258
-
1259
- // Handle different error types
1260
- if (error.code === 'PERMISSION_DENIED') {
1261
- // Show permission error to user
1262
- } else if (error.code === 'NETWORK_ERROR') {
1263
- // Handle network issues
1264
- }
1317
+ const message = error instanceof Error ? error.message : String(error);
1318
+ ctx.api.logger.error(`Failed to fetch holdings: ${message}`);
1265
1319
 
1266
1320
  throw error;
1267
1321
  }
@@ -1325,17 +1379,15 @@ const HeavyChart = lazy(() => import('./components/HeavyChart'));
1325
1379
 
1326
1380
  ```typescript
1327
1381
  // Use React Query or SWR for caching
1328
- import { useQuery } from 'react-query';
1382
+ import { useQuery } from '@tanstack/react-query';
1329
1383
 
1330
1384
  function usePortfolioData(accountId: string) {
1331
- return useQuery(
1332
- ['portfolio', accountId],
1333
- () => ctx.api.portfolio.getHoldings(accountId),
1334
- {
1335
- staleTime: 5 * 60 * 1000, // 5 minutes
1336
- cacheTime: 10 * 60 * 1000, // 10 minutes
1337
- },
1338
- );
1385
+ return useQuery({
1386
+ queryKey: ['portfolio', accountId],
1387
+ queryFn: () => ctx.api.portfolio.getHoldings(accountId),
1388
+ staleTime: 5 * 60 * 1000, // 5 minutes
1389
+ gcTime: 10 * 60 * 1000, // 10 minutes
1390
+ });
1339
1391
  }
1340
1392
  ```
1341
1393
 
@@ -1438,6 +1490,7 @@ We follow [Semantic Versioning](https://semver.org/) (SemVer):
1438
1490
 
1439
1491
  | SDK Version | Wealthfolio Version | Node.js | React |
1440
1492
  | ----------- | ------------------- | --------- | ------- |
1493
+ | 3.8.x | >= 3.8.0 | >= 20.0.0 | ^19.2.4 |
1441
1494
  | 3.7.x | >= 3.7.0 | >= 20.0.0 | ^19.2.4 |
1442
1495
  | 0.9.x | >= 0.9.0 | >= 16.0.0 | ^17.0.0 |
1443
1496
 
@@ -1450,10 +1503,10 @@ We follow [Semantic Versioning](https://semver.org/) (SemVer):
1450
1503
  npm install @wealthfolio/addon-sdk
1451
1504
 
1452
1505
  # Specific version
1453
- npm install @wealthfolio/addon-sdk@3.7.0
1506
+ npm install @wealthfolio/addon-sdk@3.8.0
1454
1507
 
1455
1508
  # Version range
1456
- npm install @wealthfolio/addon-sdk@^3.7.0
1509
+ npm install @wealthfolio/addon-sdk@^3.8.0
1457
1510
  ```
1458
1511
 
1459
1512
  #### Beta/Preview Releases
@@ -1709,31 +1762,29 @@ export default defineConfig({
1709
1762
  "permissions": [
1710
1763
  {
1711
1764
  "category": "portfolio",
1712
- "functions": ["holdings"],
1765
+ "functions": ["getHoldings"],
1713
1766
  "purpose": "Access portfolio data for analytics"
1714
1767
  }
1715
1768
  ]
1716
1769
  }
1717
1770
  ```
1718
1771
 
1719
- #### 6. Context Not Available
1772
+ #### 6. Context Not Available in a Component or Helper
1720
1773
 
1721
- **Error**: `getAddonContext() returns undefined`
1774
+ **Error**: A component or helper cannot access the addon context.
1722
1775
 
1723
1776
  **Solutions**:
1724
1777
 
1725
1778
  ```typescript
1726
- // Ensure you're calling it within addon context
1727
- function MyComponent() {
1728
- useEffect(() => {
1729
- // Call context inside useEffect or event handlers
1730
- const ctx = getAddonContext();
1731
- // ... use context
1732
- }, []);
1779
+ import type { AddonContext, AddonEnableFunction } from '@wealthfolio/addon-sdk';
1780
+
1781
+ function MyComponent({ ctx }: { ctx: AddonContext }) {
1782
+ return <button onClick={() => ctx.api.toast.success('Ready')}>Test API</button>;
1733
1783
  }
1734
1784
 
1735
- // Don't call at module level
1736
- // const ctx = getAddonContext(); // Wrong
1785
+ const enable: AddonEnableFunction = (ctx) => {
1786
+ // Capture ctx for a route wrapper, or pass it directly to helpers/components.
1787
+ };
1737
1788
  ```
1738
1789
 
1739
1790
  ### Development Environment Issues
@@ -1779,13 +1830,10 @@ ls -la dist/ # Should update when you save files
1779
1830
  try {
1780
1831
  const accounts = await ctx.api.accounts.getAll();
1781
1832
  const data = await ctx.api.portfolio.getHoldings(accounts[0]?.id);
1782
- ctx.api.logger.info('Data loaded successfully', { count: data.length });
1833
+ ctx.api.logger.info(`Data loaded successfully (${data.length} holdings)`);
1783
1834
  } catch (error) {
1784
- ctx.api.logger.error('API call failed', {
1785
- error: error.message,
1786
- stack: error.stack,
1787
- timestamp: new Date().toISOString(),
1788
- });
1835
+ const message = error instanceof Error ? error.message : String(error);
1836
+ ctx.api.logger.error(`API call failed: ${message}`);
1789
1837
  }
1790
1838
  ```
1791
1839