Back to Wealthfolio

Migration Guide: Wealthfolio Addons v2 to v3

docs/addons/addon-migration-guide-v2-to-v3.md

3.3.012.0 KB
Original Source

Migration Guide: Wealthfolio Addons v2 to v3

This guide covers all changes needed to migrate your Wealthfolio addon from v2 to v3.

Overview

Aspectv2v3
SDK Version2.0.03.0.0
React Version19.1.119.2.4
Route Prefix/addon//addons/

1. Update manifest.json

Change the sdkVersion in your manifest:

json
// v2
{
  "sdkVersion": "2.0.0"
}

// v3
{
  "sdkVersion": "3.0.0"
}

Also update your addon version:

json
{
  "version": "2.0.0"
}

2. Update package.json

Update the @wealthfolio/addon-sdk dependency and devDependencies:

2.1 Peer Dependencies

json
// v2
{
  "peerDependencies": {
    "react": "^19.1.1",
    "react-dom": "^19.1.1"
  }
}

// v3
{
  "peerDependencies": {
    "react": "^19.2.4",
    "react-dom": "^19.2.4"
  }
}

2.2 Dev Dependencies

json
// v2
{
  "devDependencies": {
    "@tailwindcss/vite": "^4.1.13",
    "@types/node": "^20.0.0",
    "@types/react": "^19.1.1",
    "@types/react-dom": "^19.1.1",
    "rollup-plugin-external-globals": "^0.13.0",
    "tailwindcss": "^4.1.13",
    "typescript": "^5.9.2",
    "vite": "^7.1.5"
  }
}

// v3
{
  "devDependencies": {
    "@tailwindcss/vite": "^4.1.18",
    "@types/node": "^20.19.33",
    "@types/react": "^19.2.13",
    "@types/react-dom": "^19.2.3",
    "@vitejs/plugin-react": "^4.3.4",
    "rollup-plugin-external-globals": "^0.13.0",
    "tailwindcss": "^4.1.18",
    "typescript": "^5.9.3",
    "vite": "^7.3.1",
    "clsx": "^2.1.1",
    "tailwind-merge": "^3.4.0"
  }
}

Key changes:

  • Updated Tailwind CSS from ^4.1.13 to ^4.1.18
  • Added @vitejs/plugin-react (required for React support)
  • Added clsx and tailwind-merge (commonly used utilities)
  • Updated all @types/react* versions
  • Updated Vite to ^7.3.1

3. Tailwind CSS v4 Changes

Wealthfolio v3 uses Tailwind CSS v4. While the addon system uses the host app's Tailwind configuration, there are some important considerations:

3.1 CSS Import Syntax

v4 uses the new @import syntax:

css
/* v2/v3 - Use this syntax */
@import "tailwindcss";

3.2 No Configuration File Needed

In Tailwind v4, you don't need a tailwind.config.js file. The configuration is done via CSS:

css
/* globals.css in the host app */
@theme {
  --color-primary: #your-color;
  --font-sans: "Inter", sans-serif;
}

3.3 Custom Variants for Dark Mode

v4 uses a new dark mode variant syntax:

css
/* v4 syntax */
@custom-variant dark (&:where(.dark, .dark *));

3.4 Custom Utilities

In v4, custom utilities are defined using @utility:

css
@utility scrollbar-hide {
  scrollbar-width: none;
  -ms-overflow-style: none;
  &::-webkit-scrollbar {
    display: none;
  }
}

3.5 Using @wealthfolio/ui

Addons should use the @wealthfolio/ui package which provides pre-built components styled with the host app's theme. The UI package exports components that automatically inherit the host app's:

  • Color palette (semantic colors like primary, secondary, etc.)
  • Typography
  • Spacing
  • Border radius
typescript
// Your addon can use UI components directly
import { Button, Card, Page } from "@wealthfolio/ui";

3.6 Using Tailwind Classes

Addons can use all standard Tailwind utility classes. The host app provides a custom theme via CSS variables that your addon classes will automatically inherit:

typescript
// These classes will automatically use the host app's theme
function MyComponent() {
  return (
    <div className="bg-background text-foreground p-4 rounded-lg border-border">
      <h1 className="text-primary font-semibold">Title</h1>
    </div>
  );
}

4. API Changes

3.1 Market API

The sync function now uses assetIds instead of symbols:

typescript
// v2
await ctx.api.market.sync(["AAPL", "MSFT"], true);

// v3
await ctx.api.market.sync(["asset-id-1", "asset-id-2"], true);
// or with recent days refetch
await ctx.api.market.sync(["asset-id-1"], true, 7);

The return type changed from QuoteSummary[] to SymbolSearchResult[]:

typescript
// v2
const results = await ctx.api.market.searchTicker("AAPL");
// QuoteSummary[]

// v3
const results = await ctx.api.market.searchTicker("AAPL");
// SymbolSearchResult[] - has additional fields: exchangeMic, currency, assetKind, isExisting, existingAssetId

3.2 Assets API

updateDataSource replaced with updateQuoteMode:

typescript
// v2
await ctx.api.assets.updateDataSource("AAPL", "MANUAL");

// v3
await ctx.api.assets.updateQuoteMode("asset-id", "MANUAL");

3.3 Quotes API

Methods now use assetId instead of symbol:

typescript
// v2
await ctx.api.quotes.update('AAPL', { close: 150.00, ... });
const history = await ctx.api.quotes.getHistory('AAPL');

// v3
await ctx.api.quotes.update('asset-id', { close: 150.00, ... });
const history = await ctx.api.quotes.getHistory('asset-id');

4. Data Type Changes

4.1 ActivityType

The activity types now include additional types and use string values for quantities:

typescript
// v2 - 13 types
const ActivityType = {
  BUY: "BUY",
  SELL: "SELL",
  DIVIDEND: "DIVIDEND",
  INTEREST: "INTEREST",
  DEPOSIT: "DEPOSIT",
  WITHDRAWAL: "WITHDRAWAL",
  ADD_HOLDING: "ADD_HOLDING",
  REMOVE_HOLDING: "REMOVE_HOLDING",
  TRANSFER_IN: "TRANSFER_IN",
  TRANSFER_OUT: "TRANSFER_OUT",
  FEE: "FEE",
  TAX: "TAX",
  SPLIT: "SPLIT",
};

// v3 - 15 types (added CREDIT, ADJUSTMENT, UNKNOWN)
const ActivityType = {
  BUY: "BUY",
  SELL: "SELL",
  SPLIT: "SPLIT",
  DIVIDEND: "DIVIDEND",
  INTEREST: "INTEREST",
  DEPOSIT: "DEPOSIT",
  WITHDRAWAL: "WITHDRAWAL",
  TRANSFER_IN: "TRANSFER_IN",
  TRANSFER_OUT: "TRANSFER_OUT",
  FEE: "FEE",
  TAX: "TAX",
  CREDIT: "CREDIT",
  ADJUSTMENT: "ADJUSTMENT",
  UNKNOWN: "UNKNOWN",
};

4.2 Activity Interface

Key changes to the Activity interface:

typescript
// v2
interface Activity {
  id: string;
  type: ActivityType;
  date: Date | string;
  quantity: number;
  unitPrice: number;
  currency: string;
  fee: number;
  isDraft: boolean;
  comment?: string;
  accountId?: string | null;
  symbolProfileId: string;
}

// v3 - uses strings for precision, has activityType + activityTypeOverride
interface Activity {
  id: string;
  accountId: string;
  assetId?: string; // NOW OPTIONAL for pure cash events

  activityType: string; // Canonical type (closed set of 15)
  activityTypeOverride?: string; // User override
  sourceType?: string; // Raw provider label
  subtype?: string; // Semantic variation (DRIP, STAKING_REWARD, etc.)
  status: ActivityStatus;

  activityDate: string; // ISO timestamp (UTC)

  quantity?: string; // STRING to preserve precision
  unitPrice?: string;
  amount?: string;
  fee?: string;
  currency: string;
  fxRate?: string;

  isUserModified: boolean;
  needsReview: boolean;
  // ... more fields
}

4.3 Asset Interface

The Asset interface has been redesigned around identity:

typescript
// v2 - uses symbol as primary identity
interface Asset {
  id: string;
  symbol: string;
  name?: string;
  assetType?: string;
  // ...
}

// v3 - identity is opaque (UUID), classification via kind and instrumentType
interface Asset {
  id: string;

  // Classification
  kind: AssetKind; // INVESTMENT, PROPERTY, VEHICLE, COLLECTIBLE, etc.
  name?: string;
  displayCode?: string; // User-visible ticker/label

  // Valuation
  quoteMode: QuoteMode; // MARKET or MANUAL
  quoteCcy: string;

  // Instrument identity (null for non-market assets)
  instrumentType?: string; // EQUITY, CRYPTO, FX, OPTION, METAL
  instrumentSymbol?: string; // Canonical symbol (AAPL, BTC, EUR)
  instrumentExchangeMic?: string;
  // ...
}

4.4 Holding Interface

typescript
// v3 - adds assetKind
interface Holding {
  id: string;
  holdingType: HoldingType;
  accountId: string;
  instrument?: Instrument | null;
  assetKind?: AssetKind | null; // NEW in v3
  // ... rest same
}

4.5 New Types in v3

v3 introduces several new types:

typescript
// AssetKind - how the asset behaves
const AssetKind = {
  INVESTMENT: "INVESTMENT",
  PROPERTY: "PROPERTY",
  VEHICLE: "VEHICLE",
  COLLECTIBLE: "COLLECTIBLE",
  PRECIOUS_METAL: "PRECIOUS_METAL",
  PRIVATE_EQUITY: "PRIVATE_EQUITY",
  LIABILITY: "LIABILITY",
  OTHER: "OTHER",
  FX: "FX",
};

// QuoteMode - how price is determined
const QuoteMode = {
  MARKET: "MARKET",
  MANUAL: "MANUAL",
};

// ActivityStatus
const ActivityStatus = {
  POSTED: "POSTED",
  PENDING: "PENDING",
  DRAFT: "DRAFT",
  VOID: "VOID",
};

// ActivitySubtypes
const ACTIVITY_SUBTYPES = {
  DRIP: "DRIP",
  QUALIFIED: "QUALIFIED",
  ORDINARY: "ORDINARY",
  STAKING_REWARD: "STAKING_REWARD",
  LENDING_INTEREST: "LENDING_INTEREST",
  COUPON: "COUPON",
  // ... more subtypes
};

6. Route Changes

Routes now use /addons/ prefix instead of /addon/:

typescript
// v2
ctx.router.add({
  path: "/addon/my-addon",
  component: React.lazy(() => import("./MyPage")),
});

// v3
ctx.router.add({
  path: "/addons/my-addon",
  component: React.lazy(() => import("./MyPage")),
});

7. Permission Changes

The permission categories may have changed. Review your addon permissions and ensure they still match the v3 categories. The general structure remains the same:

json
{
  "permissions": [
    {
      "category": "activities",
      "functions": ["search", "create", "update"],
      "purpose": "Access and manage trading activities"
    }
  ]
}

8. Example Migration

Here's a complete example of migrating an addon from v2 to v3:

Before (v2):

typescript
// src/addon.tsx
import { type AddonContext } from "@wealthfolio/addon-sdk";

export default function enable(ctx: AddonContext) {
  ctx.api.logger.info("My addon enabled!");

  // Add sidebar
  const sidebarItem = ctx.sidebar.addItem({
    id: "my-addon",
    label: "My Addon",
    route: "/addon/my-addon", // old route prefix
    order: 150,
  });

  // Register route
  ctx.router.add({
    path: "/addon/my-addon",
    component: React.lazy(() => import("./pages/MainPage")),
  });

  // Fetch holdings (v2 style)
  const holdings = await ctx.api.portfolio.getHoldings(accountId);

  // Update quote (v2 style)
  await ctx.api.quotes.update(symbol, { close: 150.0 });

  ctx.onDisable(() => {
    sidebarItem.remove();
  });
}

After (v3):

typescript
// src/addon.tsx
import { type AddonContext } from "@wealthfolio/addon-sdk";

export default function enable(ctx: AddonContext) {
  ctx.api.logger.info("My addon enabled!");

  // Add sidebar
  const sidebarItem = ctx.sidebar.addItem({
    id: "my-addon",
    label: "My Addon",
    route: "/addons/my-addon", // new route prefix
    order: 150,
  });

  // Register route
  ctx.router.add({
    path: "/addons/my-addon",
    component: React.lazy(() => import("./pages/MainPage")),
  });

  // Fetch holdings - same API
  const holdings = await ctx.api.portfolio.getHoldings(accountId);

  // Update quote (v3 style - uses assetId)
  await ctx.api.quotes.update(assetId, { close: 150.0 });

  ctx.onDisable(() => {
    sidebarItem.remove();
  });
}

9. Testing Your Migration

  1. Build the addon: Ensure the TypeScript compiles without errors
  2. Test in development mode: Run with wealthfolio with VITE_ENABLE_ADDON_DEV_MODE=true pnpm tauri dev
  3. cd to the addon dir and run your addons with pnpm dev:server
  4. Verify routes: Check that navigation works with the new /addons/ prefix
  5. Test data fetching: Ensure all API calls work with the new assetId-based APIs

10. Common Issues

Issue: Type errors with Activity fields

Solution: Use string types for quantity fields (quantity?: string) and check for activityTypeOverride

Issue: Routes not showing

Solution: Verify route uses /addons/ prefix instead of /addon/

Issue: Asset lookup failing

Solution: Use assetId instead of symbol for quotes and market data operations


11. Need Help