From 09a45bf9b8303acbfa15cda2c2ceba820f6e71cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 29 Mar 2026 04:23:53 +0000 Subject: [PATCH 1/6] Initial plan From a6c04a3a17de841933171e80c2673767e850fd8e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 29 Mar 2026 04:31:37 +0000 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20Accounts=20page=20=E2=80=94=20creat?= =?UTF-8?q?e,=20edit,=20delete=20accounts=20with=20PluresDB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Logs-Url: https://github.com/plures/pares-modulus/sessions/6ba5a4d3-18b7-4a80-8fc1-2e24cace7d72 Co-authored-by: kayodebristol <3579196+kayodebristol@users.noreply.github.com> --- plugins/financial-advisor/src/index.ts | 8 +- plugins/financial-advisor/src/lib/accounts.ts | 35 + plugins/financial-advisor/src/lib/context.ts | 20 + .../src/pages/Accounts.svelte | 787 ++++++++++++++++++ types/pares-radix.d.ts | 52 +- 5 files changed, 898 insertions(+), 4 deletions(-) create mode 100644 plugins/financial-advisor/src/lib/accounts.ts create mode 100644 plugins/financial-advisor/src/lib/context.ts create mode 100644 plugins/financial-advisor/src/pages/Accounts.svelte diff --git a/plugins/financial-advisor/src/index.ts b/plugins/financial-advisor/src/index.ts index ed01c3e..0d36d29 100644 --- a/plugins/financial-advisor/src/index.ts +++ b/plugins/financial-advisor/src/index.ts @@ -5,11 +5,12 @@ * inference rules, budgets, goals, reports, and contextual advice. */ -import type { RadixPlugin } from '@plures/pares-radix'; +import type { RadixPlugin, PluginContext } from '@plures/pares-radix'; import { recurringAmountPattern } from './rules/recurring-amount.js'; import { vendorClustering } from './rules/vendor-clustering.js'; import { refundDetection } from './rules/refund-detection.js'; import { taxVarianceDetection } from './rules/tax-variance.js'; +import { setPluginContext } from './lib/context.js'; const financialAdvisor: RadixPlugin = { id: 'financial-advisor', @@ -219,9 +220,10 @@ const financialAdvisor: RadixPlugin = { constraints: [], - async onActivate() { + async onActivate(ctx: PluginContext) { console.log('[financial-advisor] Plugin activated'); - // TODO: Initialize PluresDB collections, load inference rules + setPluginContext(ctx); + // TODO: load inference rules into inference engine }, async onDeactivate() { diff --git a/plugins/financial-advisor/src/lib/accounts.ts b/plugins/financial-advisor/src/lib/accounts.ts new file mode 100644 index 0000000..ffa779b --- /dev/null +++ b/plugins/financial-advisor/src/lib/accounts.ts @@ -0,0 +1,35 @@ +/** + * Account types and helpers for the fa-accounts PluresDB collection. + */ + +export type AccountType = 'checking' | 'savings' | 'credit' | 'investment'; + +export interface Account { + id: string; + name: string; + institution: string; + type: AccountType; + balance: number; + createdAt: string; + updatedAt: string; +} + +export const FA_ACCOUNTS_COLLECTION = 'fa-accounts'; + +export function generateAccountId(): string { + return `acct-${crypto.randomUUID()}`; +} + +export const ACCOUNT_TYPE_LABELS: Record = { + checking: 'Checking', + savings: 'Savings', + credit: 'Credit Card', + investment: 'Investment', +}; + +export const ACCOUNT_TYPE_ICONS: Record = { + checking: '🏦', + savings: 'πŸ’°', + credit: 'πŸ’³', + investment: 'πŸ“ˆ', +}; diff --git a/plugins/financial-advisor/src/lib/context.ts b/plugins/financial-advisor/src/lib/context.ts new file mode 100644 index 0000000..06fc415 --- /dev/null +++ b/plugins/financial-advisor/src/lib/context.ts @@ -0,0 +1,20 @@ +/** + * Plugin context singleton for financial-advisor. + * + * `setPluginContext` is called from `onActivate` so that Svelte page + * components can retrieve the PluresDB data API, notify API, etc. + * without needing Svelte's own `setContext` / `getContext` mechanism + * (which is scoped to component trees and unavailable in lifecycle hooks). + */ + +import type { PluginContext } from '@plures/pares-radix'; + +let _context: PluginContext | null = null; + +export function setPluginContext(ctx: PluginContext): void { + _context = ctx; +} + +export function getPluginContext(): PluginContext | null { + return _context; +} diff --git a/plugins/financial-advisor/src/pages/Accounts.svelte b/plugins/financial-advisor/src/pages/Accounts.svelte new file mode 100644 index 0000000..c60c28d --- /dev/null +++ b/plugins/financial-advisor/src/pages/Accounts.svelte @@ -0,0 +1,787 @@ + + + +
+ + + + + {#if loading} +
+ {#each [1, 2, 3] as _} +
+ {/each} +
+ + + {:else if accounts.length === 0} +
+ +

No accounts yet

+

+ Add your first bank account, credit card, or investment account to + start tracking your finances. +

+ +
+ + + {:else} + + {/if} +
+ + +{#if showForm} + + + +
+

+ {isEditMode ? 'Edit Account' : 'Add Account'} +

+ +
+ +
{ + e.preventDefault(); + saveAccount().catch(() => { + formError = 'Failed to save account. Please try again.'; + }); + }} + novalidate + > + {#if formError} + + {/if} + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + +
+
+ +
+ + +
+
+
+{/if} + + +{#if confirmDeleteId !== null} + + + +
+

Delete Account?

+
+
+

+ Are you sure you want to delete + {confirmDeleteTarget?.name ?? 'this account'}? + This action cannot be undone. +

+
+
+ + +
+
+{/if} + + diff --git a/types/pares-radix.d.ts b/types/pares-radix.d.ts index 187fe71..1d7e8a2 100644 --- a/types/pares-radix.d.ts +++ b/types/pares-radix.d.ts @@ -103,6 +103,56 @@ export interface InferenceRule { evaluate(input: InferenceInput): InferenceResult | null; } +export interface DataCollection> { + put(id: string, data: T): Promise; + get(id: string): Promise; + query(filter?: Record): Promise; + count(): Promise; + delete(id: string): Promise; +} + +export interface NotifyAPI { + success(message: string): void; + error(message: string): void; + info(message: string): void; + warning(message: string): void; +} + +export interface NavigationAPI { + goto(path: string): void; + setBreadcrumbs(crumbs: Array<{ label: string; href?: string }>): void; +} + +export interface SettingsAPI { + get(key: string): T; + set(key: string, value: unknown): void; + subscribe(key: string, callback: (value: unknown) => void): () => void; +} + +export interface LLMAPI { + available(): boolean; + remainingBudget(): number; + complete(prompt: string, context: Record): Promise; +} + +export interface InferenceAPI { + infer(type: string, record: Record): Promise; + getInferences(sourceId: string): Promise; + confirm(inferenceId: string, accepted: boolean): Promise; + getDecisionChain(inferenceId: string): Promise; +} + +export interface PluginContext { + data: { + collection>(name: string): DataCollection; + }; + notify: NotifyAPI; + navigation: NavigationAPI; + settings: SettingsAPI; + llm: LLMAPI; + inference: InferenceAPI; +} + export interface RadixPlugin { id: string; name: string; @@ -118,7 +168,7 @@ export interface RadixPlugin { rules?: InferenceRule[]; expectations?: Expectation[]; constraints?: unknown[]; - onActivate?: (ctx: unknown) => Promise; + onActivate?: (ctx: PluginContext) => Promise; onDeactivate?: () => Promise; onDataExport?: () => Promise>; onDataImport?: (data: unknown) => Promise; From 7695726b19d3b0825b8b84ae0b89bf93584c232b Mon Sep 17 00:00:00 2001 From: Kayode Bristol <3579196+kayodebristol@users.noreply.github.com> Date: Mon, 30 Mar 2026 19:01:58 -0700 Subject: [PATCH 3/6] Update plugins/financial-advisor/src/pages/Accounts.svelte Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- plugins/financial-advisor/src/pages/Accounts.svelte | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/financial-advisor/src/pages/Accounts.svelte b/plugins/financial-advisor/src/pages/Accounts.svelte index c60c28d..c9f8e58 100644 --- a/plugins/financial-advisor/src/pages/Accounts.svelte +++ b/plugins/financial-advisor/src/pages/Accounts.svelte @@ -10,9 +10,13 @@ type AccountType, } from '../lib/accounts.js'; - const ctx = getPluginContext(); - const collection = ctx?.data.collection(FA_ACCOUNTS_COLLECTION); + let ctx: any; + let collection: any; + onMount(() => { + ctx = getPluginContext(); + collection = ctx?.data.collection(FA_ACCOUNTS_COLLECTION); + }); // ── Page state ──────────────────────────────────────────────────────────── let accounts = $state([]); let loading = $state(true); From 9bdd8c29c282502b02db9af10e75347bd25bdc64 Mon Sep 17 00:00:00 2001 From: Kayode Bristol <3579196+kayodebristol@users.noreply.github.com> Date: Mon, 30 Mar 2026 19:02:09 -0700 Subject: [PATCH 4/6] Update plugins/financial-advisor/src/pages/Accounts.svelte Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- plugins/financial-advisor/src/pages/Accounts.svelte | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/financial-advisor/src/pages/Accounts.svelte b/plugins/financial-advisor/src/pages/Accounts.svelte index c9f8e58..e11404d 100644 --- a/plugins/financial-advisor/src/pages/Accounts.svelte +++ b/plugins/financial-advisor/src/pages/Accounts.svelte @@ -264,13 +264,13 @@ {#if showForm} - - + > Date: Mon, 30 Mar 2026 19:02:25 -0700 Subject: [PATCH 5/6] Update plugins/financial-advisor/src/lib/context.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- plugins/financial-advisor/src/lib/context.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/financial-advisor/src/lib/context.ts b/plugins/financial-advisor/src/lib/context.ts index 06fc415..a6fc7c3 100644 --- a/plugins/financial-advisor/src/lib/context.ts +++ b/plugins/financial-advisor/src/lib/context.ts @@ -15,6 +15,9 @@ export function setPluginContext(ctx: PluginContext): void { _context = ctx; } +export function clearPluginContext(): void { + _context = null; +} export function getPluginContext(): PluginContext | null { return _context; } From f19f26569531ddf00a2c63b3a6186af1bf437a5c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 31 Mar 2026 02:08:30 +0000 Subject: [PATCH 6/6] fix: use showModal() for dialogs, clear context on deactivate Agent-Logs-Url: https://github.com/plures/pares-modulus/sessions/c04226b5-9504-4a3d-b247-6e381220588b Co-authored-by: kayodebristol <3579196+kayodebristol@users.noreply.github.com> --- plugins/financial-advisor/src/index.ts | 3 +- plugins/financial-advisor/src/lib/context.ts | 1 + .../src/pages/Accounts.svelte | 68 +++++++++---------- 3 files changed, 35 insertions(+), 37 deletions(-) diff --git a/plugins/financial-advisor/src/index.ts b/plugins/financial-advisor/src/index.ts index 0d36d29..4e681aa 100644 --- a/plugins/financial-advisor/src/index.ts +++ b/plugins/financial-advisor/src/index.ts @@ -10,7 +10,7 @@ import { recurringAmountPattern } from './rules/recurring-amount.js'; import { vendorClustering } from './rules/vendor-clustering.js'; import { refundDetection } from './rules/refund-detection.js'; import { taxVarianceDetection } from './rules/tax-variance.js'; -import { setPluginContext } from './lib/context.js'; +import { setPluginContext, clearPluginContext } from './lib/context.js'; const financialAdvisor: RadixPlugin = { id: 'financial-advisor', @@ -228,6 +228,7 @@ const financialAdvisor: RadixPlugin = { async onDeactivate() { console.log('[financial-advisor] Plugin deactivated'); + clearPluginContext(); }, async onDataExport() { diff --git a/plugins/financial-advisor/src/lib/context.ts b/plugins/financial-advisor/src/lib/context.ts index a6fc7c3..35a8895 100644 --- a/plugins/financial-advisor/src/lib/context.ts +++ b/plugins/financial-advisor/src/lib/context.ts @@ -18,6 +18,7 @@ export function setPluginContext(ctx: PluginContext): void { export function clearPluginContext(): void { _context = null; } + export function getPluginContext(): PluginContext | null { return _context; } diff --git a/plugins/financial-advisor/src/pages/Accounts.svelte b/plugins/financial-advisor/src/pages/Accounts.svelte index e11404d..b7e71f0 100644 --- a/plugins/financial-advisor/src/pages/Accounts.svelte +++ b/plugins/financial-advisor/src/pages/Accounts.svelte @@ -13,10 +13,6 @@ let ctx: any; let collection: any; - onMount(() => { - ctx = getPluginContext(); - collection = ctx?.data.collection(FA_ACCOUNTS_COLLECTION); - }); // ── Page state ──────────────────────────────────────────────────────────── let accounts = $state([]); let loading = $state(true); @@ -47,6 +43,8 @@ // ── Load accounts on mount ──────────────────────────────────────────────── onMount(() => { + ctx = getPluginContext(); + collection = ctx?.data.collection(FA_ACCOUNTS_COLLECTION); loadAccounts().catch(() => { ctx?.notify.error('Failed to load accounts.'); loading = false; @@ -65,6 +63,32 @@ } } + // ── Modal action β€” wires to showModal() for native focus trapping, + // backdrop, and Escape handling. Click on ::backdrop closes the dialog. + function useModal(node: HTMLDialogElement, params: { onClose: () => void }) { + function handleCancel(e: Event) { + // Prevent the browser from closing the dialog on its own so that Svelte + // state (the {#if} block) remains the single source of truth for visibility. + e.preventDefault(); + params.onClose(); + } + function handleBackdropClick(e: MouseEvent) { + // A click whose direct target is the element itself means the + // user clicked on the ::backdrop (outside the dialog box). + if (e.target === node) params.onClose(); + } + node.showModal(); + node.addEventListener('cancel', handleCancel); + node.addEventListener('click', handleBackdropClick); + return { + destroy() { + node.removeEventListener('cancel', handleCancel); + node.removeEventListener('click', handleBackdropClick); + if (node.open) node.close(); + }, + }; + } + // ── Form helpers ────────────────────────────────────────────────────────── function openCreate(): void { editingAccount = null; @@ -167,14 +191,6 @@ currency: 'USD', }).format(amount); } - - function handleFormKeydown(event: KeyboardEvent): void { - if (event.key === 'Escape') closeForm(); - } - - function handleDeleteKeydown(event: KeyboardEvent): void { - if (event.key === 'Escape') confirmDeleteId = null; - } @@ -264,19 +280,11 @@ {#if showForm} -

@@ -384,19 +392,11 @@ {#if confirmDeleteId !== null} - - (confirmDeleteId = null) }} class="dialog dialog--sm" - open aria-modal="true" aria-labelledby="confirm-title" - onkeydown={handleDeleteKeydown} >

Delete Account?

@@ -587,12 +587,9 @@ flex-shrink: 0; } - /* ── Overlay ─────────────────────────────────────────────────────────────── */ - .overlay { - position: fixed; - inset: 0; + /* ── Dialog backdrop (native modal via showModal()) ─────────────────────── */ + .dialog::backdrop { background: rgba(0, 0, 0, 0.4); - z-index: 40; } /* ── Dialog ──────────────────────────────────────────────────────────────── */ @@ -606,7 +603,6 @@ border: 1px solid var(--color-border, #e5e7eb); border-radius: var(--radius-xl, 1rem); box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2); - z-index: 50; padding: 0; }