diff --git a/frontend-v2/src/lib/components/layout/AppShell.svelte b/frontend-v2/src/lib/components/layout/AppShell.svelte index 4aeeb74..fe10f27 100644 --- a/frontend-v2/src/lib/components/layout/AppShell.svelte +++ b/frontend-v2/src/lib/components/layout/AppShell.svelte @@ -2,6 +2,7 @@ import { page } from '$app/state'; import { BookOpen, + Brain, CalendarDays, CircleDot, Compass, @@ -39,6 +40,7 @@ { id: 'inventory', href: '/inventory', label: 'Inventory', icon: Package2 }, { id: 'reader', href: '/reader', label: 'Reader', icon: BookOpen }, { id: 'media', href: '/media', label: 'Media', icon: LibraryBig }, + { id: 'brain', href: '/brain', label: 'Brain', icon: Brain }, { id: 'settings', href: '/settings', label: 'Settings', icon: Settings2 } ]; diff --git a/frontend-v2/src/lib/components/layout/Navbar.svelte b/frontend-v2/src/lib/components/layout/Navbar.svelte index f623210..9fb3d0d 100644 --- a/frontend-v2/src/lib/components/layout/Navbar.svelte +++ b/frontend-v2/src/lib/components/layout/Navbar.svelte @@ -63,6 +63,9 @@ {#if showApp('media')} Media {/if} + {#if showApp('brain')} + Brain + {/if} + {/if} + + + + +
+ + {#each folders.slice(0, 5) as folder} + + {/each} +
+ + +
+
+ +
+
+
+
Search
+
Find saved items
+
+
{items.length} item{items.length !== 1 ? 's' : ''}
+
+
+ + + {#if searchQuery} + + {/if} +
+
+ +
+
+
Feed
+
{activeFolder || 'All items'}
+
+ {#if activeFolder} + Items the AI classified under {activeFolder.toLowerCase()}. + {:else} + Your complete saved collection, newest first. + {/if} +
+
+
+ + {#if loading} +
+ {#each [1, 2, 3, 4] as _} +
+ {/each} +
+ {:else if items.length === 0} +
+
No items yet. Paste a URL or note above to get started.
+
+ {:else} +
+ {#each items as item (item.id)} + + {/each} +
+ {/if} +
+
+ + + + + +{#if selectedItem} + +
{ if (e.target === e.currentTarget) selectedItem = null; }} onkeydown={(e) => { if (e.key === 'Escape') selectedItem = null; }}> +
+
+
+
{selectedItem.type}
+

{selectedItem.title || 'Untitled'}

+
+ +
+ + {#if selectedItem.url} + {selectedItem.url} + {/if} + + {#if selectedItem.summary} +
{selectedItem.summary}
+ {/if} + +
+
+
Folder
+
{selectedItem.folder || 'โ€”'}
+
+
+
Confidence
+
{selectedItem.confidence ? (selectedItem.confidence * 100).toFixed(0) + '%' : 'โ€”'}
+
+
+
Status
+
{selectedItem.processing_status}
+
+
+
Saved
+
{new Date(selectedItem.created_at).toLocaleDateString()}
+
+
+ + {#if selectedItem.tags && selectedItem.tags.length > 0} +
+ {#each selectedItem.tags as tag} + {tag} + {/each} +
+ {/if} + +
+ + + {#if selectedItem.url} + Open original + {/if} +
+
+
+{/if} + + diff --git a/frontend-v2/src/lib/pages/budget/AtelierBudgetPage.svelte b/frontend-v2/src/lib/pages/budget/AtelierBudgetPage.svelte index 794fb56..9f87729 100644 --- a/frontend-v2/src/lib/pages/budget/AtelierBudgetPage.svelte +++ b/frontend-v2/src/lib/pages/budget/AtelierBudgetPage.svelte @@ -50,11 +50,11 @@ if (activeTab === 'categorized') return transactions.filter((t) => t.categoryType !== 'uncat'); return transactions; }); - const spendMagnitude = $derived(() => { + const spendMagnitude = $derived.by(() => { const value = Number(headerSpending.replace(/[$,]/g, '')) || 0; return '$' + value.toLocaleString('en-US'); }); - const incomeMagnitude = $derived(() => { + const incomeMagnitude = $derived.by(() => { const value = Number(headerIncome.replace(/[$,]/g, '')) || 0; return '$' + value.toLocaleString('en-US'); }); @@ -184,9 +184,9 @@ .filter((c: any) => c.name !== 'Starting Balances') .map((c: any) => ({ name: c.name, - budgeted: Math.round(c.budgeted / 100), - spent: Math.round(Math.abs(c.spent) / 100), - available: Math.round(c.balance / 100) + budgeted: Math.round(Number(c.budgeted ?? 0) / 100), + spent: Math.round(Math.abs(Number(c.spent ?? 0)) / 100), + available: Math.round(Number(c.balance ?? 0) / 100) })) })) .filter((g: any) => g.categories.length > 0); @@ -313,18 +313,6 @@ selected = next; } - function handleRowKeydown(e: KeyboardEvent) { - if (e.key === 'c' || e.key === 'C') { - e.preventDefault(); - const row = (e.target as HTMLElement).closest('.txn-row'); - const select = row?.querySelector('.cat-select') as HTMLSelectElement | null; - if (select) { - select.focus(); - select.click(); - } - } - } - function formatDateShort(dateStr: string) { const d = new Date(dateStr + 'T00:00:00'); return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); @@ -372,6 +360,9 @@
{currentMonthLabel || 'Budget'}

Budget

+

+ {uncatCount} waiting for category ยท {formatBalance(onBudgetTotal)} across on-budget accounts +

@@ -379,13 +370,13 @@
- -
+
Spent
{spendMagnitude}
@@ -405,24 +396,42 @@
- - {#each accounts as acct} - - {/each} - {#if offBudgetAccounts.length > 0} - {#each offBudgetAccounts as acct} - {/each} - {/if} + {#if offBudgetAccounts.length > 0} + {#each offBudgetAccounts as acct} + + {/each} + {/if} +
{#if activeView === 'transactions'} @@ -430,11 +439,12 @@

Suggested transfers

- {suggestedTransfers.length} + {suggestedTransfers.length} open
-
+
{#each suggestedTransfers as s}
+
{s.from.account} to {s.to.account}
{s.from.payee} matched with {s.to.payee}
@@ -452,99 +462,108 @@
{/if} -
-
- - - -
- - {#if selected.size > 0} -
-
{selected.size} selected
-
- {#if canTransfer} - - {/if} - {#if bulkCategoryOpen} - - {:else} - - {/if} - -
+
+
+
+

Ledger

+ {filteredTransactions.length} visible +
+
+ + +
- {/if} -
-
-
- {#each filteredTransactions() as txn (txn.id)} -
- -
{txn.date}
-
-
-
{txn.payee}
-
= 0} class:neg={txn.amount < 0}>{formatAmount(txn.amount)}
-
-
- {txn.account} - {#if txn.note}{txn.note}{/if} -
-
-
- {#if txn.categoryType === 'transfer'} - Transfer - {:else if txn.categoryType === 'uncat'} - bulkCategorize((e.target as HTMLSelectElement).value)}> + + {#each sortedCategories as cat} {/each} {:else} - {txn.category} + {/if} + +
+
+ {/if} +
+ +
+
+ {#each filteredTransactions as txn (txn.id)} +
+ +
+
+
+ {txn.date} + +
+
{txn.payee}
+
+ {#if txn.note}{txn.note}{/if} +
+
+
+
= 0} class:neg={txn.amount < 0}>{formatAmount(txn.amount)}
+
+ {#if txn.categoryType === 'transfer'} + Transfer + {:else if txn.categoryType === 'uncat'} + + {:else} + {txn.category} + {/if} +
{/each} - {#if filteredTransactions().length === 0} + {#if filteredTransactions.length === 0}
{#if activeTab === 'uncategorized'} No uncategorized transactions right now. {:else} - No transactions to show. - {/if} + No transactions to show. + {/if}
{/if} -
+
- {#if hasMore} - - {/if} + {#if hasMore} + + {/if} +
{:else} -
+
+
{#each budgetGroups as group}
@@ -554,19 +573,32 @@
{#each group.categories as cat}
-
{cat.name}
+
+
{cat.name}
+
+ {#if cat.budgeted > 0} + Budgeted {formatBudgetAmount(cat.budgeted)} + {/if} + {#if cat.spent > 0} + Spent {formatBudgetAmount(cat.spent)} + {/if} + {#if cat.budgeted === 0 && cat.spent === 0} + Nothing planned yet + {/if} +
+
- Budgeted {formatBudgetAmount(cat.budgeted)} - Spent {formatBudgetAmount(cat.spent)} 0}> {cat.available < 0 ? '-' : ''}{formatBudgetAmount(Math.abs(cat.available))} + {cat.available < 0 ? 'Over' : 'Left'}
{/each}
{/each} +
{/if} @@ -574,37 +606,21 @@ diff --git a/frontend-v2/src/routes/(app)/+layout.server.ts b/frontend-v2/src/routes/(app)/+layout.server.ts index 222d619..1a7a5b0 100644 --- a/frontend-v2/src/routes/(app)/+layout.server.ts +++ b/frontend-v2/src/routes/(app)/+layout.server.ts @@ -24,7 +24,7 @@ export const load: LayoutServerLoad = async ({ cookies, url }) => { // Hides nav items but does NOT block direct URL access. // This is intentional: all shared services are accessible to all authenticated users. // Hiding reduces clutter for users who don't need certain apps day-to-day. - const allApps = ['tasks', 'trips', 'fitness', 'inventory', 'budget', 'reader', 'media']; + const allApps = ['tasks', 'trips', 'fitness', 'inventory', 'budget', 'reader', 'media', 'brain']; const hiddenByUser: Record = { 'madiha': ['inventory', 'reader'], }; diff --git a/frontend-v2/src/routes/(app)/brain/+page.svelte b/frontend-v2/src/routes/(app)/brain/+page.svelte new file mode 100644 index 0000000..4451465 --- /dev/null +++ b/frontend-v2/src/routes/(app)/brain/+page.svelte @@ -0,0 +1,11 @@ + + +{#if useAtelierShell} + +{:else} + +{/if}