diff --git a/.env.example b/.env.example index f4b299a..e10ac81 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,10 @@ NUXT_PUBLIC_API_BASE_URL=http://localhost:8000/api/v1 +# Real-time chat streaming (Laravel Reverb). Leave REVERB_KEY empty to run on +# polling only. Host/port are where the browser reaches the Reverb server (the +# published docker port, default 6001). +NUXT_PUBLIC_REVERB_KEY=your-reverb-app-key +NUXT_PUBLIC_REVERB_HOST=localhost +NUXT_PUBLIC_REVERB_PORT=6001 +NUXT_PUBLIC_REVERB_SCHEME=http + diff --git a/CHANGELOG.md b/CHANGELOG.md index 76a02f1..90249a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.0.0-beta.1] - 2026-07-11 + +### Added + +- Unified AI chat experience with a side canvas; chat can be the landing surface via a landing-mode setting, backed by a dashboard assistant hub +- Live assistant progress streamed over websockets with a polling fallback, and an enriched chat landing with live insights and suggested prompts +- Settings panel to connect AI clients over MCP +- Financial position, holdings, and intent-tagging UI +- Admin console UI with outreach and onboarding +- Plugin-described UI rendered through extension slots +- Recent-imports list with delete and a live link into chat +- Section-by-section progressive dashboard stats and a streamed transactions spreadsheet with faster reports +- Navigation and top-bar icons unified; the assistant moved to `/assistant` + +### Changed + +- Renamed the country preference configuration key to `country` +- Removed duplicate dashboard stats requests and cache bypass +- Added assistant, import, and canvas translation strings + +### Fixed + +- Canvas report now prints with correct styling and margins +- Pie and donut charts render as intended instead of downgrading to bar + ## [1.1.2] - 2026-05-18 ### Changed diff --git a/assets/scss/_onboarding.scss b/assets/scss/_onboarding.scss index 9553a08..c386dc1 100644 --- a/assets/scss/_onboarding.scss +++ b/assets/scss/_onboarding.scss @@ -96,18 +96,25 @@ } .step-icon { - width: 60px; - height: 60px; - background: $primary-light; - border-radius: 50%; + width: 88px; + height: 88px; + border-radius: 26px; + background: + radial-gradient(120% 120% at 30% 18%, $primary-light, transparent 72%), + linear-gradient( + 135deg, + rgba(var(--color-primary-rgb), 0.16), + rgba(var(--color-primary-rgb), 0.04) + ); + box-shadow: $elevation-1; display: flex; align-items: center; justify-content: center; margin: 0 auto; svg { - width: 28px; - height: 28px; + width: 46px; + height: 46px; color: $primary; } } @@ -178,10 +185,12 @@ align-items: center; justify-content: center; gap: 0.5rem; + box-shadow: 0 6px 16px -6px rgba(var(--color-primary-rgb), 0.55); &:hover:not(:disabled) { background: $primary-hover; transform: translateY(-1px); + box-shadow: 0 10px 22px -8px rgba(var(--color-primary-rgb), 0.6); } &:disabled { @@ -218,18 +227,21 @@ } .completion-icon { - width: 80px; - height: 80px; - background: linear-gradient(135deg, $success 0%, $primary-dark 100%); - border-radius: 50%; + width: 96px; + height: 96px; + background: + radial-gradient(120% 120% at 30% 18%, rgba(255, 255, 255, 0.28), transparent 60%), + linear-gradient(135deg, $success 0%, $primary-dark 100%); + border-radius: 30px; + box-shadow: $elevation-2; display: flex; align-items: center; justify-content: center; margin: 0 auto 1.5rem auto; svg { - width: 40px; - height: 40px; + width: 48px; + height: 48px; color: $text-inverse; } } diff --git a/assets/scss/_surfaces.scss b/assets/scss/_surfaces.scss index ba23dd2..68bf92e 100644 --- a/assets/scss/_surfaces.scss +++ b/assets/scss/_surfaces.scss @@ -34,6 +34,24 @@ --surface-accent: var(--color-border-medium); --surface-deep: var(--color-text-secondary); } + &--investment { + --surface-bg: rgba(14, 165, 233, 0.12); + --surface-ink: var(--color-text-primary); + --surface-accent: rgba(14, 165, 233, 0.5); + --surface-deep: #0284c7; + } + &--loan { + --surface-bg: rgba(217, 119, 6, 0.12); + --surface-ink: var(--color-text-primary); + --surface-accent: rgba(217, 119, 6, 0.5); + --surface-deep: #b45309; + } + &--gift { + --surface-bg: rgba(124, 58, 237, 0.12); + --surface-ink: var(--color-text-primary); + --surface-accent: rgba(124, 58, 237, 0.5); + --surface-deep: #6d28d9; + } } // In dark mode the surface bg becomes a subtle tint so it sits on the page @@ -65,6 +83,21 @@ --surface-accent: var(--color-border-medium); --surface-deep: var(--color-text-secondary); } + .surface--investment { + --surface-bg: rgba(14, 165, 233, 0.16); + --surface-accent: rgba(56, 189, 248, 0.5); + --surface-deep: #38bdf8; + } + .surface--loan { + --surface-bg: rgba(217, 119, 6, 0.16); + --surface-accent: rgba(251, 191, 36, 0.5); + --surface-deep: #fbbf24; + } + .surface--gift { + --surface-bg: rgba(124, 58, 237, 0.16); + --surface-accent: rgba(167, 139, 250, 0.5); + --surface-deep: #a78bfa; + } } .display-1 { diff --git a/assets/scss/_transaction-form.scss b/assets/scss/_transaction-form.scss index 54a2161..340a117 100644 --- a/assets/scss/_transaction-form.scss +++ b/assets/scss/_transaction-form.scss @@ -675,6 +675,50 @@ font-size: $font-size-sm; } +.intent-pills { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.intent-pill { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 14px; + border: 1px solid $border-color; + border-radius: 999px; + background: $bg-white; + color: $text-secondary; + font-size: $font-size-sm; + font-weight: $font-medium; + cursor: pointer; + transition: + background 0.15s ease, + border-color 0.15s ease, + color 0.15s ease; + + &:hover { + border-color: $primary; + color: $text-primary; + } + + .intent-pill-icon { + width: 16px; + height: 16px; + } +} + +.intent-pill--active { + background: $primary; + border-color: $primary; + color: white; + + &:hover { + color: white; + } +} + .wallet-field-wrapper, .group-field-wrapper { position: relative; diff --git a/assets/scss/_utilities.scss b/assets/scss/_utilities.scss index 4c37667..8c96ba5 100644 --- a/assets/scss/_utilities.scss +++ b/assets/scss/_utilities.scss @@ -133,9 +133,9 @@ justify-content: center; align-items: center; padding: 0; - width: 36px; - height: $navbar-icon-button-height; - border-radius: 10px; + width: 28px; + height: 28px; + border-radius: 8px; background-color: transparent; border: 1px solid transparent; color: $text-secondary; @@ -164,8 +164,8 @@ } .icon { - width: 18px; - height: 18px; + width: 16px; + height: 16px; color: currentColor; } } diff --git a/assets/scss/_variables.scss b/assets/scss/_variables.scss index 106de2f..a0b4f65 100644 --- a/assets/scss/_variables.scss +++ b/assets/scss/_variables.scss @@ -252,7 +252,7 @@ $font-semibold: 600; $font-bold: 700; // Navbar -$navbar-height: 65px; +$navbar-height: 52px; $navbar-icon-button-height: 36px; // Sidebar diff --git a/assets/scss/base.scss b/assets/scss/base.scss index 5ee6c9b..4f36f50 100644 --- a/assets/scss/base.scss +++ b/assets/scss/base.scss @@ -160,8 +160,59 @@ button { } /* Print styles */ +// The canvas report is printed in place: a cloned copy is appended to
+// and everything else is hidden, so the print uses the app's real, already +// loaded styles. Hidden on screen; shown only when printing. +.print-canvas-portal { + display: none; +} + @media print { + // Zero the page margin so the browser stops drawing its title/URL/date + // header and footer; the portal supplies its own inner padding instead. + @page { + margin: 0; + } + body { background: none; } + + body.printing-canvas > *:not(.print-canvas-portal) { + display: none !important; + } + + body.printing-canvas .print-canvas-portal { + display: block !important; + position: static; + padding: 16mm; + max-width: 100%; + } + + body.printing-canvas .print-canvas-portal * { + -webkit-print-color-adjust: exact; + print-color-adjust: exact; + } + + // The report is cloned from an on-screen scroll container with a fixed + // height; reset that so it flows to its content instead of reserving a whole + // empty page. + body.printing-canvas .print-canvas-portal, + body.printing-canvas .print-canvas-portal > * { + height: auto !important; + min-height: 0 !important; + max-height: none !important; + overflow: visible !important; + } + + body.printing-canvas .print-canvas-portal * { + max-height: none !important; + overflow: visible !important; + } + + body.printing-canvas .print-canvas-portal svg, + body.printing-canvas .print-canvas-portal .apexcharts-canvas { + max-width: 100% !important; + height: auto !important; + } } diff --git a/components/EmptyState.vue b/components/EmptyState.vue index 1ec39fc..4e33b43 100644 --- a/components/EmptyState.vue +++ b/components/EmptyState.vue @@ -1,6 +1,8 @@{{ error }}
+ + +{{ detail.user.email }}
+
+