From c64d61b8fde8769aeb822442dfecbbdca494b935 Mon Sep 17 00:00:00 2001 From: Aziz FADIL Date: Thu, 20 Aug 2026 20:57:38 -0400 Subject: [PATCH 1/3] docs(addons): say how a merged listing reaches the site Merging a listing does not publish it. The website reads a committed snapshot of this repository, so a page only changes when someone runs the sync, reads the diff, and commits it. That step was undocumented, which is a good way to merge a listing and wonder for a week why it is not live. --- CONTRIBUTING.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ea75977..b25c41c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -166,6 +166,32 @@ pnpm bundle:official If you are moving an addon from the old app repository layout, read [docs/repository-migration.md](docs/repository-migration.md) first. +## Publishing a merged listing (maintainers) + +Merging a listing does not put it on the site. The website reads a committed +snapshot of this repository, not this repository live — so a page only changes +when someone runs the sync, reads the diff, and commits it. + +In the website repo: + +```bash +pnpm sync:addons +``` + +That archives this repository at a commit, reads every `active` community +listing plus `community/derived.json`, and rewrites +`src/data/community-addons.ts`. It refuses to run against a dirty checkout, +because the listings you are looking at would not be the listings it publishes. + +It applies the publishing gate a second time, independently of CI here. A +listing is dropped, with the reason printed, when it has no derived record, has +derivation problems, is not built against a sandbox-era SDK, or does not state +`commercialModel`. Nothing gets onto the site because a check was skipped +somewhere else. + +Commit the regenerated file and deploy. `pnpm sync:addons:check` fails when the +committed snapshot has fallen behind this repository. + ## Security expectations - Do not include secrets in source, docs, manifests, or screenshots. From 860322aa3d04ecd70122f9ac2aed2440df92d132 Mon Sep 17 00:00:00 2001 From: Aziz FADIL Date: Fri, 21 Aug 2026 10:01:27 -0400 Subject: [PATCH 2/3] fix(addons): say where data goes in words a user would use MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The derived `basis` line is the most-read sentence on a community listing, and it was written for the person who built the sandbox: "The addon does not request the network permission, so the Wealthfolio runtime blocks all outbound requests." Every noun in that is ours, not the reader's. Same facts, plainer: "It never asked for internet access, so Wealthfolio will not let it connect to anything." Nothing is softened — the point of the line is that it is a hard statement about what the app does, and it survives being said in short words. Regenerated derived.json; only the basis strings move. --- community/derived.json | 209 ++-------------------------------------- scripts/lib/derive.mjs | 8 +- scripts/test-derive.mjs | 2 +- 3 files changed, 12 insertions(+), 207 deletions(-) diff --git a/community/derived.json b/community/derived.json index dba1bf6..97508a4 100644 --- a/community/derived.json +++ b/community/derived.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-08-21T17:36:00.942Z", + "generatedAt": "2026-08-21T13:59:26.588Z", "addons": { "asset-and-cash-timeline": { "id": "asset-and-cash-timeline", @@ -44,7 +44,7 @@ "dataHandling": { "userDataLeavesDevice": null, "externalServices": [], - "basis": "Built before the 3.6 sandbox, when addons could reach the network without declaring it, so the manifest cannot show where data goes." + "basis": "Built before Wealthfolio 3.6, back when add-ons could go online without asking. There is no way to tell from the outside where its data goes." }, "compatibility": { "state": "predates-sandbox", @@ -85,7 +85,7 @@ "dataHandling": { "userDataLeavesDevice": null, "externalServices": [], - "basis": "Built before the 3.6 sandbox, when addons could reach the network without declaring it, so the manifest cannot show where data goes." + "basis": "Built before Wealthfolio 3.6, back when add-ons could go online without asking. There is no way to tell from the outside where its data goes." }, "compatibility": { "state": "predates-sandbox", @@ -148,7 +148,7 @@ "dataHandling": { "userDataLeavesDevice": false, "externalServices": [], - "basis": "The addon does not request the network permission, so the Wealthfolio runtime blocks all outbound requests." + "basis": "It never asked for internet access, so Wealthfolio will not let it connect to anything." }, "compatibility": { "state": "current", @@ -207,7 +207,7 @@ "dataHandling": { "userDataLeavesDevice": null, "externalServices": [], - "basis": "Built before the 3.6 sandbox, when addons could reach the network without declaring it, so the manifest cannot show where data goes." + "basis": "Built before Wealthfolio 3.6, back when add-ons could go online without asking. There is no way to tell from the outside where its data goes." }, "compatibility": { "state": "predates-sandbox", @@ -217,70 +217,6 @@ "problems": [], "warnings": [] }, - "myinvestor-importer-addon": { - "id": "myinvestor-importer-addon", - "runtimeId": "myinvestor-importer", - "repository": "https://github.com/blastik/myinvestor-importer-addon", - "defaultBranch": "main", - "commit": "0cfd17fe4e356458a90f2cad52344e56444cca38", - "license": "MIT", - "lastPushedAt": "2026-08-21", - "manifest": { - "name": "MyInvestor Importer", - "version": "1.0.1", - "sdkVersion": "3.6.1", - "minWealthfolioVersion": "3.6.0", - "permissions": [ - { - "category": "accounts", - "purpose": "List accounts so the user can select their MyInvestor account" - }, - { - "category": "activities", - "purpose": "Validate and import activities parsed from the MyInvestor exports; read existing account activities to avoid double-recording money already imported as a cross-account transfer by another addon" - }, - { - "category": "assets", - "purpose": "Create new asset entries for unrecognised funds during import" - }, - { - "category": "secrets", - "purpose": "Persist addon configuration (account selection, security mappings) across sessions" - }, - { - "category": "ui", - "purpose": "Add importer navigation item to sidebar and register addon routes" - }, - { - "category": "query", - "purpose": "Refresh host application data after import" - }, - { - "category": "portfolio", - "purpose": "Trigger portfolio recalculation after import" - }, - { - "category": "market-data", - "purpose": "Search for securities when mapping unrecognised funds" - } - ] - }, - "dataHandling": { - "userDataLeavesDevice": false, - "externalServices": [], - "basis": "The addon does not request the network permission, so the Wealthfolio runtime blocks all outbound requests." - }, - "compatibility": { - "state": "current", - "detail": "Built against SDK 3.6.1." - }, - "notices": [], - "problems": [], - "warnings": [ - "declares the secrets permission but not network, so any service it stores credentials for is unreachable", - "manifest id \"myinvestor-importer\" differs from the listing id \"myinvestor-importer-addon\"" - ] - }, "rebalancer": { "id": "rebalancer", "runtimeId": "rebalancer", @@ -320,7 +256,7 @@ "dataHandling": { "userDataLeavesDevice": false, "externalServices": [], - "basis": "The addon does not request the network permission, so the Wealthfolio runtime blocks all outbound requests." + "basis": "It never asked for internet access, so Wealthfolio will not let it connect to anything." }, "compatibility": { "state": "current", @@ -334,137 +270,6 @@ ], "warnings": [] }, - "simplefin-sync": { - "id": "simplefin-sync", - "runtimeId": "simplefin-sync", - "repository": "https://github.com/Bubbles840/wealthfolio-simplefin-addon", - "defaultBranch": "main", - "commit": "9d4277a283df6be6d057511e6c231baf74c5f54d", - "license": "MIT", - "lastPushedAt": "2026-08-21", - "manifest": { - "name": "SimpleFin Sync", - "version": "1.17.1", - "sdkVersion": "3.6.2", - "minWealthfolioVersion": "3.6.2", - "permissions": [ - { - "category": "accounts", - "purpose": "Show existing accounts, and optionally create new Wealthfolio accounts, for mapping to SimpleFin accounts" - }, - { - "category": "activities", - "purpose": "Import bank transactions, reconcile pending transactions (create/update/delete), and link transfer pairs" - }, - { - "category": "navigation", - "purpose": "Open a mapped account's page in Wealthfolio when its row is clicked" - }, - { - "category": "portfolio", - "purpose": "Read current account balances to size one-time starting-balance corrections" - }, - { - "category": "secrets", - "purpose": "Store SimpleFin credentials and sync configuration securely" - }, - { - "category": "network", - "purpose": "Fetch transaction data from SimpleFin Bridge" - }, - { - "category": "ui", - "purpose": "Register sidebar navigation item and route for SimpleFin Sync" - } - ] - }, - "dataHandling": { - "userDataLeavesDevice": true, - "externalServices": [ - { - "host": "bridge.simplefin.org" - }, - { - "host": "beta-bridge.simplefin.org" - }, - { - "host": "api.telegram.org" - } - ], - "basis": "The addon requests the network permission and may reach the hosts listed above, subject to your approval at install time." - }, - "compatibility": { - "state": "current", - "detail": "Built against SDK 3.6.2." - }, - "notices": [], - "problems": [], - "warnings": [] - }, - "trade-republic-importer-addon": { - "id": "trade-republic-importer-addon", - "runtimeId": "trade-republic-importer", - "repository": "https://github.com/blastik/trade-republic-importer-addon", - "defaultBranch": "main", - "commit": "ccce1da26fa6c01a55c9494cf358774934b514de", - "license": "MIT", - "lastPushedAt": "2026-08-21", - "manifest": { - "name": "Trade Republic Importer", - "version": "1.3.1", - "sdkVersion": "3.6.1", - "minWealthfolioVersion": "3.6.0", - "permissions": [ - { - "category": "accounts", - "purpose": "List accounts so the user can select their cash and portfolio accounts" - }, - { - "category": "activities", - "purpose": "Validate and import activities parsed from the Trade Republic CSV" - }, - { - "category": "assets", - "purpose": "Create new asset entries for unrecognised securities during import" - }, - { - "category": "secrets", - "purpose": "Persist addon configuration (account selection, transfer patterns) across sessions" - }, - { - "category": "ui", - "purpose": "Add importer navigation item to sidebar and register addon routes" - }, - { - "category": "query", - "purpose": "Refresh host application data after import" - }, - { - "category": "portfolio", - "purpose": "Trigger portfolio recalculation after import" - }, - { - "category": "market-data", - "purpose": "Search for securities when mapping unrecognised tickers" - } - ] - }, - "dataHandling": { - "userDataLeavesDevice": false, - "externalServices": [], - "basis": "The addon does not request the network permission, so the Wealthfolio runtime blocks all outbound requests." - }, - "compatibility": { - "state": "current", - "detail": "Built against SDK 3.6.1." - }, - "notices": [], - "problems": [], - "warnings": [ - "declares the secrets permission but not network, so any service it stores credentials for is unreachable", - "manifest id \"trade-republic-importer\" differs from the listing id \"trade-republic-importer-addon\"" - ] - }, "value-averaging-addon": { "id": "value-averaging-addon", "runtimeId": "value-averaging-addon", @@ -504,7 +309,7 @@ "dataHandling": { "userDataLeavesDevice": null, "externalServices": [], - "basis": "Built before the 3.6 sandbox, when addons could reach the network without declaring it, so the manifest cannot show where data goes." + "basis": "Built before Wealthfolio 3.6, back when add-ons could go online without asking. There is no way to tell from the outside where its data goes." }, "compatibility": { "state": "predates-sandbox", diff --git a/scripts/lib/derive.mjs b/scripts/lib/derive.mjs index 691b92a..3cb7121 100644 --- a/scripts/lib/derive.mjs +++ b/scripts/lib/derive.mjs @@ -65,7 +65,7 @@ export function deriveDataHandling(manifest, compatibility) { return { userDataLeavesDevice: null, externalServices: [], - basis: "Built before the 3.6 sandbox, when addons could reach the network without declaring it, so the manifest cannot show where data goes.", + basis: "Built before Wealthfolio 3.6, back when add-ons could go online without asking. There is no way to tell from the outside where its data goes.", }; } @@ -73,7 +73,7 @@ export function deriveDataHandling(manifest, compatibility) { return { userDataLeavesDevice: null, externalServices: [], - basis: "The manifest declares no usable SDK version, so it is not known whether the sandbox constrains this addon's network access.", + basis: "Its manifest does not say which SDK it was built with, so we cannot tell whether Wealthfolio would stop it going online.", }; } @@ -81,7 +81,7 @@ export function deriveDataHandling(manifest, compatibility) { return { userDataLeavesDevice: false, externalServices: [], - basis: "The addon does not request the network permission, so the Wealthfolio runtime blocks all outbound requests.", + basis: "It never asked for internet access, so Wealthfolio will not let it connect to anything.", }; } @@ -91,7 +91,7 @@ export function deriveDataHandling(manifest, compatibility) { return { userDataLeavesDevice: true, externalServices: hosts.map((host) => ({ host })), - basis: "The addon requests the network permission and may reach the hosts listed above, subject to your approval at install time.", + basis: "It asked for internet access and can reach the sites listed here, if you allow that when you install it.", }; } diff --git a/scripts/test-derive.mjs b/scripts/test-derive.mjs index b6694d9..18ce1ef 100644 --- a/scripts/test-derive.mjs +++ b/scripts/test-derive.mjs @@ -56,7 +56,7 @@ for (const sdkVersion of ["3.6evil", "3.x", undefined]) { ); expect( `basis for ${JSON.stringify(sdkVersion)} does not claim a sandbox build`, - !/Built before the 3\.6 sandbox/.test(handling.basis), + !/Built before Wealthfolio 3\.6/.test(handling.basis), handling.basis, ); } From d8fcb26442dbf414db33a1bcb70503318282e45f Mon Sep 17 00:00:00 2001 From: Aziz FADIL Date: Fri, 21 Aug 2026 13:44:51 -0400 Subject: [PATCH 3/3] chore(addons): re-derive so the new listings carry the plainer wording --- community/derived.json | 197 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 196 insertions(+), 1 deletion(-) diff --git a/community/derived.json b/community/derived.json index 97508a4..01d241b 100644 --- a/community/derived.json +++ b/community/derived.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-08-21T13:59:26.588Z", + "generatedAt": "2026-08-21T17:44:29.606Z", "addons": { "asset-and-cash-timeline": { "id": "asset-and-cash-timeline", @@ -217,6 +217,70 @@ "problems": [], "warnings": [] }, + "myinvestor-importer-addon": { + "id": "myinvestor-importer-addon", + "runtimeId": "myinvestor-importer", + "repository": "https://github.com/blastik/myinvestor-importer-addon", + "defaultBranch": "main", + "commit": "0cfd17fe4e356458a90f2cad52344e56444cca38", + "license": "MIT", + "lastPushedAt": "2026-08-21", + "manifest": { + "name": "MyInvestor Importer", + "version": "1.0.1", + "sdkVersion": "3.6.1", + "minWealthfolioVersion": "3.6.0", + "permissions": [ + { + "category": "accounts", + "purpose": "List accounts so the user can select their MyInvestor account" + }, + { + "category": "activities", + "purpose": "Validate and import activities parsed from the MyInvestor exports; read existing account activities to avoid double-recording money already imported as a cross-account transfer by another addon" + }, + { + "category": "assets", + "purpose": "Create new asset entries for unrecognised funds during import" + }, + { + "category": "secrets", + "purpose": "Persist addon configuration (account selection, security mappings) across sessions" + }, + { + "category": "ui", + "purpose": "Add importer navigation item to sidebar and register addon routes" + }, + { + "category": "query", + "purpose": "Refresh host application data after import" + }, + { + "category": "portfolio", + "purpose": "Trigger portfolio recalculation after import" + }, + { + "category": "market-data", + "purpose": "Search for securities when mapping unrecognised funds" + } + ] + }, + "dataHandling": { + "userDataLeavesDevice": false, + "externalServices": [], + "basis": "It never asked for internet access, so Wealthfolio will not let it connect to anything." + }, + "compatibility": { + "state": "current", + "detail": "Built against SDK 3.6.1." + }, + "notices": [], + "problems": [], + "warnings": [ + "declares the secrets permission but not network, so any service it stores credentials for is unreachable", + "manifest id \"myinvestor-importer\" differs from the listing id \"myinvestor-importer-addon\"" + ] + }, "rebalancer": { "id": "rebalancer", "runtimeId": "rebalancer", @@ -270,6 +334,137 @@ ], "warnings": [] }, + "simplefin-sync": { + "id": "simplefin-sync", + "runtimeId": "simplefin-sync", + "repository": "https://github.com/Bubbles840/wealthfolio-simplefin-addon", + "defaultBranch": "main", + "commit": "9d4277a283df6be6d057511e6c231baf74c5f54d", + "license": "MIT", + "lastPushedAt": "2026-08-21", + "manifest": { + "name": "SimpleFin Sync", + "version": "1.17.1", + "sdkVersion": "3.6.2", + "minWealthfolioVersion": "3.6.2", + "permissions": [ + { + "category": "accounts", + "purpose": "Show existing accounts, and optionally create new Wealthfolio accounts, for mapping to SimpleFin accounts" + }, + { + "category": "activities", + "purpose": "Import bank transactions, reconcile pending transactions (create/update/delete), and link transfer pairs" + }, + { + "category": "navigation", + "purpose": "Open a mapped account's page in Wealthfolio when its row is clicked" + }, + { + "category": "portfolio", + "purpose": "Read current account balances to size one-time starting-balance corrections" + }, + { + "category": "secrets", + "purpose": "Store SimpleFin credentials and sync configuration securely" + }, + { + "category": "network", + "purpose": "Fetch transaction data from SimpleFin Bridge" + }, + { + "category": "ui", + "purpose": "Register sidebar navigation item and route for SimpleFin Sync" + } + ] + }, + "dataHandling": { + "userDataLeavesDevice": true, + "externalServices": [ + { + "host": "bridge.simplefin.org" + }, + { + "host": "beta-bridge.simplefin.org" + }, + { + "host": "api.telegram.org" + } + ], + "basis": "It asked for internet access and can reach the sites listed here, if you allow that when you install it." + }, + "compatibility": { + "state": "current", + "detail": "Built against SDK 3.6.2." + }, + "notices": [], + "problems": [], + "warnings": [] + }, + "trade-republic-importer-addon": { + "id": "trade-republic-importer-addon", + "runtimeId": "trade-republic-importer", + "repository": "https://github.com/blastik/trade-republic-importer-addon", + "defaultBranch": "main", + "commit": "ccce1da26fa6c01a55c9494cf358774934b514de", + "license": "MIT", + "lastPushedAt": "2026-08-21", + "manifest": { + "name": "Trade Republic Importer", + "version": "1.3.1", + "sdkVersion": "3.6.1", + "minWealthfolioVersion": "3.6.0", + "permissions": [ + { + "category": "accounts", + "purpose": "List accounts so the user can select their cash and portfolio accounts" + }, + { + "category": "activities", + "purpose": "Validate and import activities parsed from the Trade Republic CSV" + }, + { + "category": "assets", + "purpose": "Create new asset entries for unrecognised securities during import" + }, + { + "category": "secrets", + "purpose": "Persist addon configuration (account selection, transfer patterns) across sessions" + }, + { + "category": "ui", + "purpose": "Add importer navigation item to sidebar and register addon routes" + }, + { + "category": "query", + "purpose": "Refresh host application data after import" + }, + { + "category": "portfolio", + "purpose": "Trigger portfolio recalculation after import" + }, + { + "category": "market-data", + "purpose": "Search for securities when mapping unrecognised tickers" + } + ] + }, + "dataHandling": { + "userDataLeavesDevice": false, + "externalServices": [], + "basis": "It never asked for internet access, so Wealthfolio will not let it connect to anything." + }, + "compatibility": { + "state": "current", + "detail": "Built against SDK 3.6.1." + }, + "notices": [], + "problems": [], + "warnings": [ + "declares the secrets permission but not network, so any service it stores credentials for is unreachable", + "manifest id \"trade-republic-importer\" differs from the listing id \"trade-republic-importer-addon\"" + ] + }, "value-averaging-addon": { "id": "value-averaging-addon", "runtimeId": "value-averaging-addon",