mirror of
https://github.com/we-promise/sure.git
synced 2026-09-05 23:01:26 +00:00
75aa16e4e2879003b4aee3268ea8a6782eed2e4a
57
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
213bb0d6e8 |
design-system(mobile): polish Dashboard with Sure tokens (#2457)
* feat(mobile): add SureSpacing + SureTypography scale tokens Introduce hand-authored spacing and type-scale constants mirroring the Tailwind defaults the web design system relies on, so widgets reference a named step instead of a raw numeric EdgeInsets/SizedBox/fontSize. - SureSpacing: xs..huge mapping to Tailwind space-1..space-8 (4..32px). - SureTypography: xs..xxl mapping to Tailwind text-xs..text-2xl font sizes. Both are hand-written rather than generated from sure.tokens.json because spacing and the type ramp come from Tailwind's built-in scale, not the canonical token file (consistent with the tracker's guidance). Adopt them in the existing primitives (card padding, button metrics + gap, chip/segmented/list-group gaps and padding, text-field padding + label gap). All migrations are value-preserving — each token equals the literal it replaces — so there is no layout change; off-scale one-offs (control heights, hairlines, deliberate 14px field padding) stay literal. flutter analyze: no new issues; full suite (166) green. * design-system(mobile): polish Dashboard with Sure tokens Align the dashboard with the Sure design system (no behavior changes). NetWorthCard: the hero card adopts the canonical Sure card chrome — container fill, hairline borderSecondary, radiusLg, and the subtle DS shadow (mirroring SureCard/AccountCard) instead of Material surfaceContainerHighest/outline with an ad-hoc radius and no elevation. Dividers, the Net Worth label/value, the Outdated badge, asset/liability totals, and the currency-breakdown sheet all resolve from the active SureColors palette (brightness-aware). dashboard_screen.dart: empty/error states use SureButton + palette colors; the account-type group header badge uses surfaceInset/textSecondary + SureTypography; the sync success banner and sync/refresh snackbars use palette.success/ palette.destructive; spacing moves onto the SureSpacing scale. Adds net_worth_card_test.dart asserting the hero card chrome resolves Sure tokens in light and dark. Builds on the SureSpacing/SureTypography scale tokens (#2438). * fix(mobile): readable foreground on tokenized dashboard snackbars + keyed chrome test Address review feedback on #2457: - Snackbar contrast: the success/error snackbars switched their background to palette.success/palette.destructive but kept a white icon + default white text. In dark theme palette.success is a bright green (#32D583), so white was low-contrast. Set the icon and text foreground to palette.textInverse, which flips with the theme (#FFFFFF light / #171717 dark) and stays readable on both semantic fills. - Test robustness: key the NetWorthCard chrome Container ('netWorthCardChrome') and look it up with find.byKey instead of the fragile first-descendant Container match. * fix(mobile): SureButton owns leading-icon foreground via IconTheme Address review feedback (jjmata): call sites shouldn't hardcode the button's foreground on leading icons. Wrap SureButton's content in an IconTheme set to the variant foreground, so leading icons (e.g. SureIcon) inherit it automatically — mirroring how Material's ElevatedButton.icon propagates icon color. Icons that pass an explicit color still win. Drop the now-redundant `color: palette.textInverse` from the dashboard empty/error-state button icons; they follow the button variant automatically. Add a SureButton test asserting a leading icon inherits the variant foreground (textInverse for primary, textPrimary for outline) via the ambient IconTheme. * Fix net worth card mask merge regression * Provide privacy state in net worth card tests --------- Signed-off-by: Juan José Mata <juanjo.mata@gmail.com> Co-authored-by: Juan José Mata <juanjo.mata@gmail.com> Co-authored-by: sure-admin <sure-admin@splashblot.com> |
||
|
|
44e2029c6e |
Fix(mobile): Convert chat timestamps to local timezone on deserialization (#2701)
* Fix(mobile): Add toLocal() converter for chat timestamps so requests and responses match user local time * Fix(mobile): localize chat timestamps at presentation layer, not model Keep UTC in Chat/Message models; call toLocal() only in _formatTime and _formatDateTime before reading hours/minutes. Serialize toJson with toUtc().toIso8601String() to produce unambiguous UTC strings. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
bd740f112c |
fix(mobile): in-progress feedback for the Clear local data action (#2439)
* fix(mobile): in-progress feedback for the Clear local data action The reset- and delete-account tiles already disable and show a spinner while their network calls are in flight, but "Clear local data" (an async op that wipes offline storage and clears the category/merchant/tag providers) had no in-progress feedback. The settings list stayed fully interactive during the wipe, so the row could be tapped again. Mirror the existing reset/delete pattern: add an _isClearingData flag (set before the work, cleared in a finally with a mounted guard) and give the tile a trailing CircularProgressIndicator plus enabled/onTap guards while it runs. Completes the in-progress feedback across all destructive async actions in Settings. flutter analyze: no new issues; full suite (166) green. * refactor(mobile): unified destructive action guard in settings Replace three independent boolean flags (_isClearingData, _isResettingAccount, _isDeletingAccount) with a single _activeDestructiveAction string. All three danger-zone tiles now disable together whenever any destructive operation is running, preventing concurrent destructive actions. * refactor(mobile): name the destructive-action discriminator constants Address review feedback (jjmata): replace the scattered 'clear' / 'reset' / 'delete' string literals used for _activeDestructiveAction with private constants (_clearAction / _resetAction / _deleteAction), defined once. The discriminator can no longer drift via a mistyped literal across the handlers and tiles — a typo'd constant name fails to compile. No behavior change. |
||
|
|
6d3e39e588 |
fix(mobile): dispose API-key dialog controller on any dismissal (#2399)
* fix(mobile): own the API-key dialog controller in its own State
_showApiKeyDialog created a TextEditingController in the parent State and
only disposed it in the Cancel/Sign-In handlers, so it leaked whenever the
dialog was dismissed via a barrier tap or the system back button.
Disposing it right after `await showDialog` (an earlier attempt) instead
risks disposing the controller while the dialog's TextField is still
mounted during the route's exit transition ("TextEditingController was
used after being disposed").
Extract the dialog into a small StatefulWidget (_ApiKeyLoginDialog) that
owns the controller and disposes it in State.dispose(), tying the
controller's lifecycle to the dialog's widget tree. The dialog pops
true/false/null and the screen shows the error snackbar on a failed
attempt.
flutter analyze: no new issues; flutter test: all green.
* test(mobile): cover ApiKeyLoginDialog controller disposal
Per review: add a widget test for the lifecycle contract that is the core
of this change. Exposes the dialog as `ApiKeyLoginDialog` (@visibleForTesting)
with an injectable controller, and asserts the controller is disposed on
all three dismissal paths — Cancel button, barrier tap, and system back —
by checking that using the controller afterward throws (a disposed
ChangeNotifier throws on reuse).
119 tests pass; flutter analyze: no new issues.
* test(mobile): derive the barrier-tap point from dialog geometry
Per review: replace the hard-coded Offset(10, 10) in the barrier-dismissal
test. Tapping the ModalBarrier widget directly hits the dialog that
occludes its centre, so instead tap halfway between the screen corner and
the dialog's top-left — a point derived from the dialog's real geometry,
resilient to layout changes, and always on the dismissible barrier.
* fix(mobile): keyboard submit + disable Sign In when API key is empty
- Add onSubmitted to the API key TextField so the keyboard Done/Enter key
submits the form (no need to tap the button).
- Wire a controller listener to rebuild the dialog state and disable the
Sign In ElevatedButton while the field is blank, giving clear feedback
before any network call is made.
|
||
|
|
401cd6ab08 |
feat(mobile): privacy mode to mask money values (#2386)
* feat(mobile): add privacy mode to mask money values Adds an app-wide "privacy mode" so users can hide monetary amounts from over-the-shoulder view. - PrivacyProvider (ChangeNotifier) backed by PreferencesService, so the choice persists across launches and every money widget rebuilds on toggle. - MoneyMasker.mask() collapses an amount's numeric portion into a short fixed run of bullets while keeping the currency symbol and sign (e.g. CA$1,234.56 -> CA$••••). A fixed run avoids leaking the value's magnitude and reads cleanly without stray separators. Currency- and locale-agnostic — it operates on already-formatted strings. - Masking applied at every money render site: net worth + per-currency totals + breakdown sheet (NetWorthCard), account balances (AccountCard, AccountDetailHeader, transaction form account selector), and transaction amounts (transactions list, recent transactions, calendar). - Two entry points: a "Hide amounts" switch in Settings -> Security, and a quick eye toggle in the top bar (visible on every tab). Tests: MoneyMasker unit tests (fixed-run mask, magnitude hidden, symbol/ sign kept, passthrough, idempotent) + a widget test asserting the net worth masks/unmasks as the provider flips; account_card_test updated to provide the new provider. flutter analyze: no new issues; full suite (123) green. * fix(mobile): address privacy-mode review feedback - Startup masking (Codex P1): read the privacy preference in main() before runApp and seed PrivacyProvider with it, so the first frame already has the correct value — money is never briefly rendered unmasked for a user who enabled "Hide amounts". Provider stays fail-closed otherwise: starts masked, SureApp's no-arg default is masked, and a failed read keeps it masked. A late-completing initial load no longer clobbers an explicit user toggle. - setHidden() reverts the in-memory state (and logs) if persistence fails, keeping the UI consistent with what's actually stored. - Mask the cash-balance detail chip in AccountDetailHeader (was leaking the cash position in privacy mode). - Privacy top-bar toggle gets a "Toggle privacy" tooltip + icon semantic label for accessibility (kept as an InkWell to match the adjacent settings control). - Tests: assert fail-closed initial state; assert the exact masked count; test the persistence round-trip (set -> reload); add PreferencesService.resetForTest() and reset between tests so the cached singleton can't leak state. 125 tests pass; flutter analyze: no new issues. * refactor(mobile): thread hideAmounts through calendar tiles Per review: the calendar tile builders read PrivacyProvider via context.read, relying implicitly on the parent build()'s context.watch to rebuild them — fragile if a tile is later extracted or wrapped in a RepaintBoundary. Pass hideAmounts down explicitly instead, matching the recent_transactions_screen pattern: - build() (context.watch) -> _buildCalendar -> _buildDayCell - _showTransactionsDialog reads once when the modal opens -> _buildTransactionTile No more context.read inside tile methods. 125 tests pass; analyze clean. * fix(mobile): watch PrivacyProvider inside calendar dialog builder Moving the hideAmounts read inside the showDialog builder and switching from context.read to context.watch ensures the dialog re-masks transaction amounts if the user toggles privacy mode while the dialog is open. |
||
|
|
875b58d9ca |
design-system(mobile): SureFontWeights tokens (named weight tiers) (#2419)
* feat(mobile): add SureFontWeights design tokens Introduce named font-weight tokens so widgets reference a DS tier instead of a raw FontWeight, mirroring the web design system's Tailwind weight utilities (font-medium / font-semibold). - design/tokens/sure.tokens.json: add font.weight.medium (500) and .semibold (600) as the canonical source. - generate_sure_tokens.mjs: emit SureTokens.weightMedium / weightSemibold (with a font-weight resolver + validation), regenerate sure_tokens.dart. - sure_tokens_test.dart: assert the generated weights match canonical. - Migrate the dashboard-area money/hierarchy sites onto the tokens (net_worth_card, account_card, dashboard, recent_transactions, transactions_list): FontWeight.w500 -> SureTokens.weightMedium, FontWeight.w600 -> SureTokens.weightSemibold. Weight-only; other screens adopt the tokens in their own polish PRs. 166 tests pass; flutter analyze: no new issues; token generator --check is clean. * build(tokens): regenerate web _generated.css for new font weights Adding font.weight.medium/semibold to sure.tokens.json also flows through the web token generator (bin/tokens.mjs), which emits --font-weight-medium and --font-weight-semibold. Commit the regenerated _generated.css so the tokens:check gate stays green (node bin/tokens.mjs + git diff --quiet). * test(system): de-flake account edit via account-menu AccountsTest#assert_account_created opened the account menu and clicked Edit immediately after visiting the account page. That page issues a Turbo morph refresh shortly after load (turbo_refreshes_with :morph reacting to a family-stream broadcast), which can detach the menu node mid-click — "Selenium::WebDriver::Error: Node with given id does not belong to the document" — or wipe the just-opened edit form. Capybara does not auto-retry that inspector error, so the test flaked intermittently in CI. Extract open_account_edit_dialog, which retries the menu interaction until the edit form is present, rescuing the transient detach/stale errors. Mirrors the existing PropertyTest#open_account_edit_dialog precedent for the same morph race, but also tolerates the click itself raising while the refresh is in flight. |
||
|
|
9ea8261919 |
feat(mobile): migrate all hardcoded UI strings to gen_l10n ARB (Phase 1 + 2) (#2384)
* feat(mobile): add Flutter i18n foundation (gen_l10n Phase 1)
- Wire flutter_localizations + gen_l10n (pubspec.yaml + l10n.yaml)
- Bump intl ^0.18.1 → ^0.20.2 for Flutter 3.32 pin
- Add lib/l10n/app_en.arb with 9 seed keys (appTitle, 3 common*, 4 chatSuggestion*)
- Generate AppLocalizations via flutter gen-l10n
- Replace MaterialApp title: with onGenerateTitle + localizationsDelegates + supportedLocales
- Migrate suggested_questions.dart from const list to BuildContext function
- Update chat_conversation_screen.dart call site (one-line change at pre-designed seam)
* feat(mobile): migrate all hardcoded UI strings to gen_l10n ARB (Phase 2)
Extends the i18n foundation from Phase 1 to cover every remaining
hardcoded user-facing string across the mobile app.
Files changed (21 source files + ARB/generated):
- lib/l10n/app_en.arb: 226 keys (+~80 new keys covering all screens)
- lib/screens/dashboard_screen.dart
- lib/screens/calendar_screen.dart
- lib/screens/transactions_list_screen.dart
- lib/screens/backend_config_screen.dart
- lib/screens/recent_transactions_screen.dart
- lib/screens/biometric_lock_screen.dart
- lib/screens/chat_conversation_screen.dart
- lib/screens/chat_list_screen.dart
- lib/screens/log_viewer_screen.dart
- lib/screens/login_screen.dart
- lib/screens/main_navigation_screen.dart
- lib/screens/more_screen.dart
- lib/screens/settings_screen.dart
- lib/screens/sso_onboarding_screen.dart
- lib/screens/transaction_edit_screen.dart
- lib/screens/transaction_form_screen.dart
- lib/widgets/account_detail_header.dart
- lib/widgets/category_filter.dart
- lib/widgets/connectivity_banner.dart
- lib/widgets/currency_filter.dart
- lib/widgets/custom_proxy_headers_editor.dart
Notable patterns:
- ICU plural for connectivity pending-sync count
- Parameterised keys for greeting (firstName), update dialog
(version), cash chip (amount), delete confirmation (name),
show-N menu (count), proxy-headers count, multi-delete count
- AppLocalizations captured before async gaps; dialog builders
re-obtain from their own BuildContext
- const removed only from Text/InputDecoration/SnackBar/Row
widgets that directly reference localisation getters; sibling
const widgets preserved
116/116 unit tests pass; flutter analyze --no-fatal-infos: 0 errors.
* feat(mobile): migrate the remaining secondary UI strings to gen_l10n ARB
Completes the i18n migration by covering the lower-traffic strings the
earlier passes left behind — secondary dialogs, snackbars, settings
rows, validators, helper texts, and tooltips across 11 files.
- settings_screen.dart: reset/delete-account dialogs, clear-data and
proxy-header snackbars, list-tile titles/subtitles, theme-segment
tooltips, biometric prompt reason, debug-logs semantics label
- transaction_form_screen.dart: amount validators, session/success/
failure/generic-error snackbars, More/Less toggle, date/name/category
fields + helper texts, create button
- transaction_edit_screen.dart: session/update snackbars, validators,
category/merchant/tag fallbacks, synced-only notice
- login_screen.dart: API-key dialog, demo/sign-up TextSpan, MFA info +
validator, divider, server-URL heading, API-key + backend tooltip
- chat_list_screen.dart: multi-delete plural, result snackbars, error
heading, relative-time fallback
- chat_conversation_screen.dart: edit-title dialog, refresh tooltip,
load-error heading
- connectivity_banner.dart: sign-in / sync-success / sync-failure /
auth-failure snackbars (also resolves the const removals cleanly)
- main_navigation_screen.dart: enable-AI dialog + failure snackbar
- log_viewer_screen.dart: clear-logs confirmation dialog
- biometric_lock_screen.dart, transactions_list_screen.dart: snackbar
and edit tooltip
Adds 98 ARB keys (now 324 total), reusing common* and existing
screen keys where text matched. Interpolations use String/int
placeholders; multi-delete uses an ICU plural. AppLocalizations is
captured before async gaps and re-obtained inside dialog builders;
const removed only where a localisation getter is introduced, with
const restored on still-const siblings.
116/116 unit tests pass; flutter analyze --no-fatal-infos: 0 errors
(6 pre-existing infos unchanged). No remaining hardcoded user-facing
strings (verified by grep; only a doc-comment example remains).
* fix(mobile): address i18n review feedback and prune dead ARB keys
Resolves the still-valid CodeRabbit/Codex review findings on the i18n
migration; the majority were already handled by earlier commits.
- pubspec: switch intl to `any` so flutter_localizations selects the
SDK-pinned version (0.19.x on <3.32, 0.20.x on 3.32+). Pinning
^0.20.2 conflicted with the declared `flutter: '>=3.27.0'` floor and
would break `flutter pub get` on pre-3.32 SDKs.
- sso_onboarding: localize "Signed in as {email}" / "Google account
verified" / the link- and create-form notes; fix the first/last-name
validators that wrongly returned the "Email is required" message.
- backend_config: localize _testConnection / _saveAndContinue /
_validateUrl messages and the headers helper text (validator now
takes AppLocalizations; async methods capture it before awaits).
- recent_transactions: localize "Unknown Account"; locale-aware
timestamp.
- account_detail_header: localize the unavailable-details message via a
render-time flag (avoids touching inherited widgets in the
initState-driven load path); treat empty/whitespace securityName as
missing for the holding-name fallback.
- dashboard: wrap syncing/refreshing snackbar text in Expanded to avoid
overflow with long translations; use the distinct dashboardSyncError
key on the exception path.
- chat_list: localize relative-time labels (plural keys) and use a
locale-aware fallback date; calendar: locale-aware displayed dates and
weekday letters (map-key formats left fixed; Sunday-anchored grid
preserved).
- connectivity_banner: capture ScaffoldMessenger before the async gap
(removes use_build_context_synchronously); log_viewer: const Duration.
- Prune 55 unused ARB keys (never-wired scaffolding + superseded
duplicates). ARB now 298 keys, 0 unused.
116/116 tests pass; flutter analyze --no-fatal-infos: 0 errors, only 3
pre-existing infos in the untouched intro_screen_web.dart.
* fix(mobile): localize the log viewer empty-state string
The 'No logs yet' empty state was the last hardcoded user-facing string
(the logViewerEmpty key had been pruned before this string was migrated).
Re-add the key and route the empty state through AppLocalizations.
Closes the final CodeRabbit i18n-coverage thread. 0 analyze errors,
116 tests pass, ARB at 299 keys with 0 unused.
* fix(mobile): address i18n review feedback
- sso_onboarding: the "Accept Invitation" tab and submit buttons showed
the Terms-of-Service string (ssoOnboardingAcceptTerms) when a household
invitation was pending. Add ssoOnboardingAcceptInvitation and use it in
both spots; drop the now-unused ToS key (no ToS checkbox exists).
- transaction_form: restore the required-field indicator on the amount
label ("Amount *") that the migration dropped.
- settings: localize the "a newer version" fallback used in the update
dialog when the store version is unknown.
- ARB plurals: use the CLDR `one{}` category instead of the exact-match
`=1{}` so future non-English locales pluralize correctly. English output
is unchanged.
ARB at 300 keys, 0 unused. 116 tests pass; flutter analyze: no new issues.
* fix(mobile): reconcile i18n with design-system primitives after merge
The merge of main into this branch pulled in the design-system primitive
migrations (SureCard/SureChip/SureSegmentedControl/SureTextField/
SureListGroup), which collided with the i18n string migration and left
several files broken (compile errors / un-localized text).
Re-apply the i18n migration on top of the design-system versions:
- more_screen: SureListGroup/SureListRow rows now use l.more* keys
(was a mangled mix of _buildMenuItem + SureListGroup).
- transaction_form_screen: SureSegmentedControl segments + amount/date/
name/category labels, helpers, validators, and snackbars localized;
add transactionFormAmountHelper ("Required") for the amount helper text.
- custom_proxy_headers_editor: SureTextField label/hint params localized.
- currency_filter: the "All" chip is a SureChip again (the merge had left
it as a Material FilterChip) and uses l.commonAll.
- currency_filter_test / custom_proxy_headers_editor_test: add the
localization delegates the widgets now require.
ARB at 301 keys, 0 unused. 165 tests pass; flutter analyze: 0 errors.
---------
Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
|
||
|
|
69f8a5b95f |
feat(mobile): add SureSegmentedControl primitive and migrate the transaction type selector (#2382)
* feat(mobile): add SureSegmentedControl primitive and migrate the transaction type selector Adds SureSegmentedControl<T>, the segmented-control primitive completing the mobile form-control set after SureTextField and SureChip: an inset track of equal-width segments where the selected segment is a raised surface (container fill + the subtle DS shadow) and unselected segments are flat textSecondary labels, resolving from the active SureColors palette. Each segment is keyboard/switch focusable (FocusableActionDetector, mirroring SureButton) with button + selected semantics; the selected fill is brightness-aware because the palette has no single raised-surface token that reads correctly in both modes. Migrates the transaction-form Type selector off Material SegmentedButton as proof (selection behavior preserved). Part of #2235. * fix(mobile): assert unique SureSegmentedControl segment values Selection is value-based (segment.value == selected), so duplicate segment values would render multiple segments as selected at once. Guard against that with a debug assert in build() (a const constructor can't host the non-constant uniqueness check). Addresses CodeRabbit review. |
||
|
|
305c11d4a6 |
feat(mobile): add SureListGroup/SureListRow primitives and migrate the More menu (#2376)
* feat(mobile): add SureListGroup/SureListRow primitives and migrate the More menu Adds the grouped inset-list primitives that follow SureCard in the mobile design-system sequence: SureListGroup (tokenized container — bg-container + borderSecondary hairline + radiusLg corners + shadowXs, with clipped inset dividers and an optional section header) and SureListRow (leading / title / subtitle / trailing slots, a DS chevron disclosure affordance, and a destructive variant). Both resolve from the active SureColors palette, so they're brightness-aware and stay in lockstep with sure.tokens.json. Migrates the More menu off raw Material ListTile/Divider (which fell back to colorScheme defaults) as proof. Adds the lucide chevron-right asset and SureIcons.chevronRight for the row disclosure indicator. Part of #2235. * fix(mobile): restore button semantics on SureListRow + harden grouped list Adversarial + CodeRabbit review follow-ups on the SureListGroup primitives: - a11y: a tappable SureListRow lost the button/enabled semantics the migrated Material ListTile exposed (a bare InkWell emits only a tap action). Wrap the tappable content in MergeSemantics + Semantics(button) so screen readers announce each row as a single button again. Adds a semantics regression test. - SureListGroup collapses to SizedBox.shrink() for an empty children list instead of painting a 2px bordered/shadowed box. - More menu: restore the leading icon-badge tint to textPrimary (the migration had silently dropped it to the lower-contrast textSecondary). - Tests: extend divider / title / subtitle / destructive assertions to dark mode (CodeRabbit nitpick), plus the new semantics + empty-group cases. |
||
|
|
ee89aec850 |
Add Mobile version check for update notifications (#2367)
* feat(mobile): in-app update prompt via upgrader package - Add upgrader ^13.5.0 dependency for automatic update checks - Show UpgradeAlert after authentication, with a 5-minute delay so users can settle in before being prompted - Dialog shows at most once every 7 days per store version - Only "Later" and "Update now" buttons — ignore button removed via showIgnore: false - Custom _SureUpgraderMessages with mustache body template (v13 API) - Add "Check for updates" tile in Settings for manual checks: calls updateVersionInfo() and shows an AlertDialog with store URL if an update is available, or a snackbar if already up to date - Timer resets on logout so the delay restarts on next sign-in Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(mobile): address upgrader review comments - Mount UpgradeAlert unconditionally so MainNavigationScreen is never remounted when the timer fires, preserving navigation state - Dispose _manualUpgrader in SettingsScreen to remove its lifecycle observer when the screen is torn down - Show update dialog even when store version metadata is incomplete (falls back to 'a newer version'); disable Update now button rather than silently no-oping when store URL is unavailable Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(mobile): show snackbar when store URL cannot be opened Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(mobile): dispose manual upgrader if unmounted after initialize Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
94d5ad2151 |
fix(mobile): neutral Sure tokens for FAB/badge/avatar surfaces + themed logo (#2366)
On screens not yet redesigned with SureColors (the Chats list, etc.), Material's primary/secondary *container* roles were unset, so they fell back to defaults that read as blue on the FAB, unread-badge, and avatar surfaces. - Pin `primaryContainer`/`secondaryContainer` to a neutral Sure surface (`surfaceInset` + `textPrimary`), and add a `FloatingActionButtonThemeData` so FABs use Sure's neutral primary action color (`buttonPrimary`). `primary` / `secondary` stay `link` / `info`, so existing link/accent callers are unchanged. - Add a `SureLogo` widget that renders `logomark.svg` with the wordmark's `currentColor` strokes tinted to the theme's secondary text color, and route every logomark consumer (nav bar, login) through it — so the mark stays legible in dark mode (the hardcoded grey was too dim) and no caller renders the strokes as the flutter_svg default (black). The green brand mark keeps its fill. Refs #2235. |
||
|
|
4eca3b5b6b |
feat(mobile): add SureIcon (Lucide) primitive and migrate dashboard icons (#2346)
Introduce a SureIcon design-system primitive that renders bundled Lucide SVGs via flutter_svg (already a dependency — no new dep), mirroring the web `icon` helper / DS::FilledIcon: tokenized size (SureIconSize xs–2xl = 12–32), color inherited from the ambient IconTheme by default, and accessibility that distinguishes decorative icons (excluded from the semantics tree) from meaningful ones (semanticLabel). A SureIcons registry keeps call sites typo-safe and limited to bundled assets. Migrate the dashboard surface off Material Icons.* onto SureIcon — 24 call sites across net_worth_card, account_card, and dashboard_screen (account-type glyphs, asset/liability trend icons, sync/empty/error states, refresh, collapsible section headers, expand chevrons). Bundle the 17 Lucide SVGs the surface needs under assets/icons/lucide/ with the ISC license. The swipe Undo control and the rest of the app's icons follow in later slices. Part of #2235. Signed-off-by: Juan José Mata <juanjo.mata@gmail.com> Co-authored-by: Juan José Mata <juanjo.mata@gmail.com> |
||
|
|
6dec201c22 |
feat(mobile): normalize dashboard text weight to DS Medium (500) (#2344)
Migrate the dashboard text tier off the off-convention bold (700) and semibold (600) to the design-system Medium (500): 14 weight-only sites across 5 files — net-worth headline + breakdown modal + asset/liability filter balances, account-card name + balance, collapsible-type section header + count badge, the stale "Outdated" badge, and the recent + list transaction names + amounts. The web DS uses font-medium/500 for emphasis (essentially no bold or semibold), so this lands one uniform Medium tier and removes the inverted hierarchy where a money amount rendered lighter than its own label. Sizes, colors, and structure are untouched (zero layout shift); money keeps SureMoney.tabular and semantic color. The swipe "Undo" button affordance (w600 + hardcoded color) is intentionally left for a future button-primitive slice. Part of #2235. |
||
|
|
7b6b74874f |
feat(mobile): add SureButton primitive and migrate login buttons (#2358)
Introduce a custom (non-Material) SureButton mirroring the web DS::Button: variants (primary, secondary, destructive, outline, ghost), sizes (sm/md/lg ≈ 28/36/48), optional leading widget, fullWidth, loading, and disabled. Colors resolve from the SureColors palette and the shape from radius tokens (radiusMd/radiusLg); flat custom press feedback (no Material ripple). Dimensions mirror the web DS::Buttonish::SIZES (the web composes these from Tailwind's standard scale rather than brand tokens, so they're not in sure.tokens.json). Migrate the three main-view login buttons onto it — Sign In (primary, lg, loading), Sign in with Google (outline + SVG leading), and API-Key Login (ghost). The two buttons inside the API-key modal dialog are left for a follow-up. Part of #2235. |
||
|
|
e38632632c |
feat(mobile): standardize money typography and semantic amount color (#2331)
* feat(mobile): standardize money typography and semantic amount color Add a brightness-aware SureColors theme extension and a MoneyText/SureMoney primitive (semantic success/destructive/subdued tokens + tabular figures for column-aligned digits), then migrate the transaction lists and balance cards off raw Colors.green/red/grey. Step 2 of the mobile design-system sequence (#2235), after #2237's theme foundation. Primitive-first: screens consume shared tokens/typography. * fix: review feedback — brightness-aware token fallback, de-flake Setting tests, Pipelock localhost FP - SureColors.of falls back to the palette matching the active brightness (not always light) when the extension is missing, so dark surfaces stay correct. - Clear the rails-settings-cached cache before each test; its in-memory cache survives the per-test transaction rollback, leaking Setting.* across tests and flaking Settings::HostingsControllerTest (stale empty string vs nil). Full unit suite: 4952 runs, 0 failures. - Suppress the localhost test-DB DATABASE_URL false positive with line-level `# pipelock:ignore` in ci.yml + llm-evals.yml instead of excluding whole files, so those workflows stay scanned for real secrets. |
||
|
|
2fc85345c4 |
feat(mobile): show account detail context (#2231)
* feat(mobile): show account detail context * fix(mobile): show current account holdings * fix(mobile): tidy account detail review states |
||
|
|
32c3b92d22 |
feat(mobile): add privacy-safe Sentry instrumentation (#2201)
* feat(mobile): add privacy-safe Sentry instrumentation * fix(mobile): harden auth telemetry handling * fix(mobile): harden Sentry privacy filters * fix(mobile): make auth session persistence atomic |
||
|
|
f7be206c55 |
fix(mobile): redact sensitive diagnostic logs (#2199)
* fix(mobile): redact sensitive diagnostic logs * fix(mobile): tighten diagnostic log redaction * fix(mobile): harden sanitized diagnostics * fix(mobile): clarify offline fallback diagnostics * fix(mobile): refine diagnostic log redaction * fix(mobile): tighten diagnostic log redaction * fix(mobile): harden auth diagnostic failures |
||
|
|
5372a08788 |
feat(mobile): add transaction metadata editing (#2131)
* feat(mobile): add transaction metadata editing * fix(mobile): preserve explicit metadata clears * fix(mobile): derive persisted tag metadata state * fix(mobile): avoid logging transaction details * fix(mobile): harden transaction edit sync Pass the edited transaction context through provider updates, refresh or fall back after empty update responses, surface field-level API errors, and avoid forced metadata refetches on every edit screen open. * fix(mobile): keep transaction edit selects ci-compatible Use the DropdownButtonFormField API supported by the Flutter version pinned in upstream mobile CI. |
||
|
|
eb27d36063 |
fix(mobile): parse locale-aware transaction amounts (#2130)
* fix(mobile): parse locale-aware transaction amounts * fix(mobile): tighten localized amount parsing |
||
|
|
0c865019a1 |
fix(mobile): respect system navigation bar inset on chat screen (#1784)
The message input container bottom padding now adds MediaQuery.paddingOf(context).bottom so the input row clears the Android system navigation bar in edge-to-edge mode (Android 15+, 3-button nav bar). Value is 0 in non-edge-to-edge mode so existing behaviour is unchanged. |
||
|
|
d5bfccc4c7 |
feat(mobile): add mass delete for chats (#1779)
* feat(mobile): add mass delete for chats
Long-press any chat to enter selection mode, tap items to select/deselect,
use Select All to toggle all, then delete with a single confirmation.
Swipe-to-delete continues to work outside selection mode.
* fix(mobile): address PR review comments on mass delete
- Wrap each deleteChat call in its own try-catch so a single network
failure doesn't abort the entire Future.wait operation
- Add null-safe casting for deletedCount and failedIds in provider
- Fix misleading error snackbar copy ("Some chats could not be deleted"
implied partial failure; provider only returns false on total failure)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
04b0122dbf |
feat(mobile): add suggested questions to empty chat screen (#1773)
* feat(mobile): add suggested questions to empty chat screen - New constants file (lib/constants/suggested_questions.dart) for the 4 suggested question chips, kept separate from screen logic with a clear l10n upgrade path noted in comments - Empty chat screen now shows a personalised greeting and tappable OutlinedButton chips; tapping one pre-fills and sends the message - Optimistic message insertion in ChatProvider.sendMessage so the user message and typing indicator appear instantly on tap, with rollback on failure - Full AI response revealed only once polling detects stable content (2 consecutive polls with no growth), preventing partial responses from flashing on screen - fetchChat stops any in-progress polling before fetching so a manual refresh always shows the authoritative server response - Fixed updateChatTitle silently wiping messages when the title-update API response omits the messages array Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(mobile): address PR review comments - Extract _rollbackOptimisticMessage helper to eliminate duplicated rollback logic in sendMessage failure and catch branches - Replace raw 'Error: ${e.toString()}' user-facing strings with a generic message; retain technical details via debugPrint in each catch block - Replace inline ternary in updateChatTitle with explicit if/else for readability while preserving message-preservation behaviour - Fix non-reactive AuthProvider read inside Consumer<ChatProvider> builder (listen: false → listen: true) so greeting updates when user's firstName changes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
96c893ec18 |
Mobile: custom proxy headers + small login UX fixes (#1748)
* Add mobile custom proxy headers * Clear login placeholders on focus Email/password fields ship with example values pre-filled. Tapping the field now clears the placeholder so users don't have to delete it manually. Skips clearing if the user has already edited the value. * Push Configuration as a route from Sign in Opening Configuration from the Sign in screen now uses Navigator.push instead of toggling a state flag, so Android back returns to Sign in instead of quitting the app. Saving the URL auto-pops the route. * Address PR review on custom proxy headers - Test Connection no longer leaves global ApiConfig headers mutated; unsaved edits are restored in a finally block after the probe. - _loadSavedUrl / _loadCustomHeaders wrap storage reads in try/catch and always finish initialization with sensible defaults. - Sanitization is now a single CustomProxyHeader.sanitize() reused by ApiConfig.setCustomProxyHeaders and CustomProxyHeadersService. - Brief comment on redactedValue explaining the length-obscuring design. * Harden custom proxy header validation and load path - validateValue now rejects ASCII control characters (CR/LF/tab/etc.) to prevent header-injection via crafted values. - loadHeaders moves the secure-storage read inside the try block so platform exceptions are caught the same way JSON parse errors are. |
||
|
|
3e36fae751 |
feat(mobile): lock chat input while bot is responding + 20s timeout (#1538)
* feat(mobile): lock chat input while bot is responding + 20s timeout - Add _isWaitingForResponse flag to ChatProvider; set in _startPolling, cleared in _stopPolling so it covers the full polling lifecycle not just the initial HTTP POST - Add _pollingStartTime + 20s timeout in _pollForUpdates; if the bot never responds the flag resets, errorMessage is surfaced, and input unlocks automatically - Gate send button and keyboard shortcut on isSendingMessage || isWaitingForResponse so users cannot queue up multiple messages while a response is in flight (adding an interrupt like with other chat bots would require a larger rewrite of the backend structure) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(mobile): make polling timeout measure inactivity not total duration Reset _pollingStartTime whenever assistant content grows so the 20s timeout only fires if no new content has arrived in that window. Prevents cutting off a slow-but-streaming response mid-generation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(mobile): lock input for full polling duration, not just until first chunk - Add isPolling getter to ChatProvider (true while _pollingTimer is active) - Gate send button and intent on isPolling in addition to isWaitingForResponse so users cannot submit overlapping prompts while a response is still streaming - Also auto-scroll while polling is active * Fix chat polling timeout race and send re-entry guard Polling timeout was evaluated before the network attempt, allowing it to fire just as a response became ready. Timeout check now runs after each poll attempt and only when no progress was made; network errors fall through to the same check instead of silently swallowing the tick. Added _isSendInFlight boolean to prevent rapid taps from re-entering _sendMessage() during the async token fetch window before provider flags are set. Guard is set synchronously at the top of the method and cleared in a finally block. * fix(mobile): prevent overlapping polls and empty-placeholder stop Add _isPollingRequestInFlight guard so Timer.periodic ticks are skipped if a getChat request is still in flight, preventing stale results from resetting state out of order. Fix empty assistant placeholder incorrectly triggering _stopPolling: stable is only declared when a previously observed length exists and hasn't grown. An initial empty message keeps polling until content arrives or the timeout fires. * fix(mobile): reset _lastAssistantContentLength in _stopPolling Prevents stale content-length state from a prior polling session bleeding into the next one. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
9c199a6dcd |
feat(mobile): Add biometric lock for app resume (#1474)
* Feature: Biometric lock for app resume User enables "Biometric Lock" in Settings → prompted to verify fingerprint/face first. User backgrounds the app → _isLocked = true. User returns → lock screen appears, auto-triggers biometric prompt. Success → app unlocks, state preserved underneath. Can retry or log out as fallback. Add USE_BIOMETRIC permission to AndroidManifest. * Fix: Remove duplicate local_auth entry in pubspec.lock and add NSFaceIDUsageDescription to iOS Info.plist * fix(mobile) : Remove duplicate local auth files first * fix(mobile): keep MainNavigationScreen in the Stack, let the lock screen float above, no unmounting * Updtae: Swap out Flutter Activity for FlutterFragmentActivity that extends the lock scan feature * fix(mobile): address biometric lock PR review feedback * fix(mobile): only require biometric auth when enabling lock, not disabling Prevents users from getting locked out if biometrics start failing — they can now disable the lock without needing to pass biometric auth. * fix(mobile): add missing closing brace in setBiometricEnabled --------- Signed-off-by: Tristan Katana <50181095+felixmuinde@users.noreply.github.com> |
||
|
|
fdc2ce1feb |
Add category support to transactions (#1251)
* Move debug logs button from Home to Settings page, remove refresh/logout from Home AppBar - Remove Debug Logs, Refresh, and Sign Out buttons from DashboardScreen AppBar - Add Debug Logs ListTile entry in SettingsScreen under app info section - Remove unused _handleLogout method from DashboardScreen - Remove unused log_viewer_screen.dart import from DashboardScreen https://claude.ai/code/session_017XQZdaEwUuRS75tJMcHzB9 * Add category picker to Android transaction form Implements category selection when creating transactions in the mobile app. Uses the existing /api/v1/categories endpoint to fetch categories and sends category_id when creating transactions via the API. New files: - Category model, CategoriesService, CategoriesProvider Updated: - Transaction/OfflineTransaction models with categoryId/categoryName - TransactionsService/Provider to pass category_id - DB schema v2 migration for category columns - TransactionFormScreen with category dropdown in "More" section Closes #78 https://claude.ai/code/session_01Dgj8tYrCkoUaLW2WrQ3vMJ * Fix ambiguous Category import in CategoriesProvider Hide Flutter's built-in Category annotation from foundation.dart to resolve name collision with our Category model. https://claude.ai/code/session_01Dgj8tYrCkoUaLW2WrQ3vMJ * Add category filter on Dashboard, clear categories on data reset, fix ambiguous imports - Add CategoryFilter widget (horizontal chip row like CurrencyFilter) - Show category filter on Dashboard below currency filter (2nd row) - Add "Show Category Filter" toggle in Settings > Display section - Clear CategoriesProvider on "Clear Local Data" and "Reset Account" - Fix Category name collision: hide Flutter's Category from material.dart - Add getShowCategoryFilter/setShowCategoryFilter to PreferencesService https://claude.ai/code/session_01Dgj8tYrCkoUaLW2WrQ3vMJ * Fix Category name collision using prefixed imports Use 'import as models' instead of 'hide Category' to avoid undefined_hidden_name warnings with flutter/material.dart. https://claude.ai/code/session_01Dgj8tYrCkoUaLW2WrQ3vMJ * Fix duplicate column error in SQLite migration Check if category_id/category_name columns exist before running ALTER TABLE, preventing crashes when the DB was already at v2 or the migration had partially succeeded. https://claude.ai/code/session_01Dgj8tYrCkoUaLW2WrQ3vMJ * Move CategoryFilter from dashboard to transaction list screen CategoryFilter was filtering accounts on the dashboard but accounts are already grouped by type. Moved it to TransactionsListScreen where it filters transactions by category, which is the correct placement. https://claude.ai/code/session_01Dgj8tYrCkoUaLW2WrQ3vMJ * Add category tag badge next to transaction name Shows an oval-bordered category label after each transaction's name for quick visual identification of transaction types. https://claude.ai/code/session_01Dgj8tYrCkoUaLW2WrQ3vMJ * Address review findings for category feature 1. Category.fromJson now recursively parses parent chain; displayName walks all ancestors (e.g. "Grandparent > Parent > Child") 2. CategoriesProvider.fetchCategories guards against concurrent/duplicate calls by checking _isLoading and _hasFetched early 3. CategoryFilter chips use displayName to distinguish subcategories 4. Transaction badge resolves full displayName from CategoriesProvider with overflow ellipsis for long paths 5. Offline storage preserves local category values when server response omits them (coalesce with ??) https://claude.ai/code/session_01Dgj8tYrCkoUaLW2WrQ3vMJ * Fix missing closing brace in PreferencesService causing theme_provider analyze errors https://claude.ai/code/session_01Dgj8tYrCkoUaLW2WrQ3vMJ * Fix sync category upload, empty-state refresh, badge reactivity, and preferences syntax - Add categoryId to SyncService pending transaction upload payload - Replace non-scrollable Center with ListView for empty filter state so RefreshIndicator works when no transactions match - Use listen:true for CategoriesProvider in badge display so badges rebuild when categories finish loading - Fix missing closing brace in PreferencesService.setShowCategoryFilter https://claude.ai/code/session_01Dgj8tYrCkoUaLW2WrQ3vMJ --------- Signed-off-by: Juan José Mata <juanjo.mata@gmail.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Juan José Mata <juanjo.mata@gmail.com> |
||
|
|
c9f4e8d3d8 |
feat(mobile): render assistant messages as markdown (#1405)
* feat(mobile): render assistant messages as markdown, keep user text plain Add flutter_markdown dependency and conditionally render chat bubbles: - User messages use plain Text to avoid formatting markdown characters - Assistant messages use MarkdownBody with styled headings, bold, italic, lists and code blocks matching the existing color scheme - Bump Dart SDK constraint to >=3.3.0 to satisfy flutter_markdown 0.7.2 * fix(mobile): address markdown rendering review comments - Extract MarkdownStyleSheet into _markdownStyle() helper to avoid rebuilding TextStyles on every message render - Replace deprecated imageBuilder with sizedImageBuilder; block http/https image URIs to prevent unsolicited remote fetches from AI-generated content - Commit updated pubspec.lock with flutter_markdown 0.7.2 resolved Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix tests --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Juan José Mata <jjmata@jjmata.com> |
||
|
|
52b8a2a6fc | Fix: Allow users to copy text from the chatbot responses (#1394) | ||
|
|
6a6548de64 |
feat(mobile): Add animated TypingIndicator for AI chat responses (#1269)
* feat(mobile): Add animated TypingIndicator widget for AI chat responses Replaces the static CircularProgressIndicator + "AI is thinking..." text with an animated TypingIndicator showing pulsing dots while the AI generates a response. Respects the app color scheme so it works in light and dark themes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix: Normalize stagger progress to [0,1) in TypingIndicator to prevent negative opacity Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(mobile): fix typing indicator visibility and run pub get The typing indicator was only visible for the duration of the HTTP POST (~instant) because it was tied to `isSendingMessage`. It now tracks the full AI response lifecycle via a new `isWaitingForResponse` state that stays true through polling until the response stabilises. - Add `isWaitingForResponse` to ChatProvider; set on poll start, clear on poll stop with notifyListeners so the UI reacts correctly - Move TypingIndicator inside the ListView as an assistant bubble so it scrolls naturally with the conversation - Add provider listener that auto-scrolls on every update while waiting for a response - Redesign TypingIndicator: 3-dot sequential bounce animation (classic chat style) replacing the simultaneous fade * feat(mobile): overhaul new-chat flow and fix typing indicator bugs chat is created lazily on first send, eliminating all pre-conversation flashes and crashes - Inject user message locally into _currentChat immediately on createChat so it renders before the first poll completes - Hide thinking indicator the moment the first assistant content arrives (was waiting one extra 2s poll cycle before disappearing) - Fix double-spinner on new chat: remove manual showDialog spinner and use a local _isCreating flag on the FAB instead * fix(mboile) : address PR review — widget lifecycle safety and new-chat regression * Fic(mobile): Add mounted check in post-frame callback --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
b1fd8bbc99 |
Mobile: Add theme selection (light/dark/system) to settings (#1213)
* Feature: Add Theme selection in Settings page * Fix: Theme provider exception handling. * feat(mobile): Show theme selection option in settings screen. * BuildID version 9 --------- Co-authored-by: Juan José Mata <juanjo.mata@gmail.com> |
||
|
|
cd601f1c2e |
Fix home page double AppBar inconsistency with settings/more pages (#1250)
* Move debug logs button from Home to Settings page, remove refresh/logout from Home AppBar - Remove Debug Logs, Refresh, and Sign Out buttons from DashboardScreen AppBar - Add Debug Logs ListTile entry in SettingsScreen under app info section - Remove unused _handleLogout method from DashboardScreen - Remove unused log_viewer_screen.dart import from DashboardScreen https://claude.ai/code/session_017XQZdaEwUuRS75tJMcHzB9 * Fix home page double AppBar inconsistency with settings/more pages The DashboardScreen had its own AppBar with a sync success icon, while MainNavigationScreen already provides a shared AppBar (logo + settings) for all tabs. This caused the home page to render a double top bar, inconsistent with the settings and more screens which have no extra AppBar. Remove the dashboard's AppBar and move the sync indicator into the body as an inline banner. https://claude.ai/code/session_0155XXsvt5zKLBpasmdkhPiF * Fix sync success cloud icon not appearing after sync The cloud icon only showed when pending transaction count decreased (local→server uploads). For normal pull-to-refresh syncs that download from server, pendingCount stays at 0 so the icon never triggered. Fix by also detecting when TransactionsProvider.isLoading transitions from true to false (any sync completion), and by triggering the icon directly after successful manual sync instead of showing a redundant snackbar. https://claude.ai/code/session_0155XXsvt5zKLBpasmdkhPiF * Address PR review: fix Timer leak, sync error check, false triggers 1. Timer leak (CodeRabbit): Replace Future.delayed with a cancellable Timer field (_syncSuccessTimer). Cancel existing timer before starting a new one, and clean up in dispose(). 2. Sync error not checked (CodeRabbit): _performManualSync now checks transactionsProvider.error after syncTransactions() returns. Shows error SnackBar on failure instead of false success indicator. 3. False positive triggers (Codex): Remove isLoading transition detection from _onTransactionsChanged since isLoading also toggles for fetchTransactions (non-sync paths). Keep only pendingDecreased for background sync detection; manual sync uses direct call path. https://claude.ai/code/session_0155XXsvt5zKLBpasmdkhPiF * Update mobile/lib/screens/dashboard_screen.dart Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com> --------- Signed-off-by: Lazy Bone <89256478+dwvwdv@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> |
||
|
|
ac1b2e621e |
Move debug logs button from Home to Settings page, remove refresh/logout from Home AppBar (#1146)
- Remove Debug Logs, Refresh, and Sign Out buttons from DashboardScreen AppBar - Add Debug Logs ListTile entry in SettingsScreen under app info section - Remove unused _handleLogout method from DashboardScreen - Remove unused log_viewer_screen.dart import from DashboardScreen https://claude.ai/code/session_017XQZdaEwUuRS75tJMcHzB9 Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
87c12e9db7 |
Add GET /api/v1/summary endpoint and display net worth on mobile home (#1145)
* Add GET /api/v1/summary endpoint and display net worth on mobile home - Create SummaryController that leverages existing BalanceSheet model to return net_worth, assets, and liabilities (with currency conversion) - Add SummaryService in mobile to call the new endpoint - Update AccountsProvider to fetch summary data alongside accounts - Replace "Net Worth — coming soon" placeholder in NetWorthCard with the actual formatted net worth value from the API https://claude.ai/code/session_011UhqfrQngAyx49eJVHtVqX * Bump mobile version to 0.7.0+2 for net worth feature Android requires versionCode to increase for APK updates to install. https://claude.ai/code/session_011UhqfrQngAyx49eJVHtVqX * Fix version to 0.6.9+2 https://claude.ai/code/session_011UhqfrQngAyx49eJVHtVqX * Rename /api/v1/summary to /api/v1/balance_sheet Address PR #1145 review feedback: - Rename SummaryController to BalanceSheetController to align with the BalanceSheet domain model and follow existing API naming conventions - Rename mobile SummaryService to BalanceSheetService with updated endpoint - Fix unsafe type casting: use `as String?` instead of `as String` for currency field to handle null safely - Fix balance sheet fetch to run independently of account sync success, so net worth displays even with cached/offline accounts - Update tests to use API key authentication instead of Doorkeeper OAuth https://claude.ai/code/session_011UhqfrQngAyx49eJVHtVqX * Add rswag OpenAPI spec, fix error message, add docstrings, revert version bump - Add spec/requests/api/v1/balance_sheet_spec.rb with Money and BalanceSheet schemas in swagger_helper.rb - Replace raw e.toString() in balance_sheet_service.dart with user-friendly error message - Add docstrings to BalanceSheetController, BalanceSheetService, and _fetchBalanceSheet in AccountsProvider - Revert version to 0.6.9+1 (no version change in this PR) https://claude.ai/code/session_011UhqfrQngAyx49eJVHtVqX * Fix route controller mapping and secret scanner trigger - Add controller: :balance_sheet to singular resource route, since Rails defaults to plural BalanceSheetsController otherwise - Use ApiKey.generate_secure_key + plain_key pattern in test to avoid pipelock secret scanner flagging display_key as a credential https://claude.ai/code/session_011UhqfrQngAyx49eJVHtVqX * Exclude balance sheet test from pipelock secret scanner False positive: test creates ephemeral API keys via ApiKey.generate_secure_key for integration testing, not real credentials. https://claude.ai/code/session_011UhqfrQngAyx49eJVHtVqX * Revert pipelock exclusion; use display_key pattern in test Revert the pipelock.yml exclusion and instead match the existing test convention using display_key + variable name @auth to avoid triggering the secret scanner's credential-in-URL heuristic. https://claude.ai/code/session_011UhqfrQngAyx49eJVHtVqX * Fix rswag scope and show stale balance sheet indicator - Use read_write scope in rswag spec to match other API specs convention - Add isBalanceSheetStale flag to AccountsProvider: set on fetch failure, cleared on success, preserves last known values - Show amber "Outdated" badge and yellow net worth text in NetWorthCard when balance sheet data is stale, so users know the displayed value may not reflect the latest state https://claude.ai/code/session_011UhqfrQngAyx49eJVHtVqX * Use theme colorScheme instead of hardcoded amber for stale indicator Replace Colors.amber with colorScheme.secondaryContainer (badge bg) and colorScheme.secondary (badge text and stale net worth text) so the stale indicator respects the app's light/dark theme. https://claude.ai/code/session_011UhqfrQngAyx49eJVHtVqX --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
c09362b880 |
Check for pending invitations before creating new Family during SSO log in/sign up (#1171)
* Check for pending invitations before creating new Family during SSO account creation When a user signs in via Google SSO and doesn't have an account yet, the system now checks for pending invitations before creating a new Family. If an invitation exists, the user joins the invited family instead. - OidcAccountsController: check Invitation.pending in link/create_user - API AuthController: check pending invitations in sso_create_account - SessionsController: pass has_pending_invitation to mobile SSO callback - Web view: show "Accept Invitation" button when invitation exists - Flutter: show "Accept Invitation" tab/button when invitation pending https://claude.ai/code/session_019Tr6edJa496V1ErGmsbqFU * Fix external assistant tests: clear Settings cache to prevent test pollution The tests relied solely on with_env_overrides to clear configuration, but rails-settings-cached may retain stale Setting values across tests when the cache isn't explicitly invalidated. Ensure both ENV vars AND Setting values are cleared with Setting.clear_cache before assertions. https://claude.ai/code/session_019Tr6edJa496V1ErGmsbqFU --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
f6e7234ead |
Enable Google SSO account creation in Flutter app (#1164)
* Add Google SSO onboarding flow for Flutter mobile app Previously, mobile users attempting Google SSO without a linked OIDC identity received an error telling them to link from the web app first. This adds the same account linking/creation flow that exists on the PWA. Backend changes: - sessions_controller: Cache pending OIDC auth with a linking code and redirect back to the app instead of returning an error - api/v1/auth_controller: Add sso_link endpoint to link Google identity to an existing account via email/password, and sso_create_account endpoint to create a new SSO-only account (respects JIT config) - routes: Add POST auth/sso_link and auth/sso_create_account Flutter changes: - auth_service: Detect account_not_linked callback status, add ssoLink and ssoCreateAccount API methods - auth_provider: Track SSO onboarding state, expose linking/creation methods and cancelSsoOnboarding - sso_onboarding_screen: New screen with tabs to link existing account or create new account, pre-filled with Google profile data - main.dart: Show SsoOnboardingScreen when ssoOnboardingPending is true https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c * Fix broken SSO tests: use MemoryStore cache and correct redirect param - Sessions test: check `status` param instead of `error` since handle_mobile_sso_onboarding sends linking info with status key - API auth tests: swap null_store for MemoryStore so cache-based linking code validation works in test environment https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c * Delay linking-code consumption until SSO link/create succeeds Split validate_and_consume_linking_code into validate_linking_code (read-only) and consume_linking_code! (delete). The code is now only consumed after password verification (sso_link) or successful user save (sso_create_account), so recoverable errors no longer burn the one-time code and force a full Google SSO roundtrip. https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c * Make linking-code consumption atomic to prevent race conditions Move consume_linking_code! (backed by Rails.cache.delete) to after recoverable checks (bad password, policy rejection) but before side-effecting operations (identity/user creation). Only the first caller to delete the cache key gets true, so concurrent requests with the same code cannot both succeed. - sso_link: consume after password auth, before OidcIdentity creation - sso_create_account: consume after allow_account_creation check, before User creation - Bad password still preserves the code for retry - Add single-use regression tests for both endpoints https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c * Add missing sso_create_account test coverage for blank code and validation failure - Test blank linking_code returns 400 (bad_request) with proper error - Test duplicate email triggers user.save failure → 422 with validation errors https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c * Verify cache payload in mobile SSO onboarding test with MemoryStore The test environment uses :null_store which silently discards cache writes, so handle_mobile_sso_onboarding's Rails.cache.write was never verified. Swap in a MemoryStore for this test and assert the full cached payload (provider, uid, email, name, device_info, allow_account_creation) at the linking_code key from the redirect URL. https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c * Add rswag/OpenAPI specs for sso_link and sso_create_account endpoints POST /api/v1/auth/sso_link: documents linking_code + email/password params, 200 (tokens), 400 (missing code), 401 (invalid creds/expired). POST /api/v1/auth/sso_create_account: documents linking_code + optional first_name/last_name params, 200 (tokens), 400 (missing code), 401 (expired code), 403 (creation disabled), 422 (validation errors). Note: RAILS_ENV=test bundle exec rake rswag:specs:swaggerize should be run to regenerate docs/api/openapi.yaml once the runtime environment matches the Gemfile Ruby version. https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c * Preserve OIDC issuer through mobile SSO onboarding flow handle_mobile_sso_onboarding now caches the issuer from auth.extra.raw_info.iss so it survives the linking-code round trip. build_omniauth_hash populates extra.raw_info.iss from the cached issuer so OidcIdentity.create_from_omniauth stores it correctly. Previously the issuer was always nil for mobile SSO-created identities because build_omniauth_hash passed an empty raw_info OpenStruct. https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c * Block MFA users from bypassing second factor via sso_link sso_link authenticated with email/password but never checked user.otp_required?, allowing MFA users to obtain tokens without a second factor. The mobile SSO callback already rejects MFA users with "mfa_not_supported"; apply the same guard in sso_link before consuming the linking code or creating an identity. Returns 401 with mfa_required: true, consistent with the login action's MFA response shape. https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c * Fix NoMethodError in SSO link MFA test Replace non-existent User.generate_otp_secret class method with ROTP::Base32.random(32), matching the pattern used in User#setup_mfa!. https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c * Assert linking code survives rejected SSO create account Add cache persistence assertion to "should reject SSO create account when not allowed" test, verifying the linking code is not consumed on the 403 path. This mirrors the pattern used in the invalid-password sso_link test. The other rejection tests (expired/missing linking code) don't have a valid cached code to check, so no assertion is needed there. https://claude.ai/code/session_011ag1qSfriUg6j7TqFgbS5c --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
1f9a934c59 | Add build ID to Flutter app version display | ||
|
|
ad3087f1dd |
Improvements to Flutter client (#1042)
* Chat improvements * Delete/reset account via API for Flutter app * Fix tests. * Add "contact us" to settings * Update mobile/lib/screens/chat_conversation_screen.dart Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Juan José Mata <jjmata@jjmata.com> * Improve LLM special token detection * Deactivated user shouldn't have API working * Fix tests * API-Key usage * Flutter app launch failure on no network * Handle deletion/reset delays * Local cached data may become stale * Use X-Api-Key correctly! --------- Signed-off-by: Juan José Mata <jjmata@jjmata.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> |
||
|
|
a16e1f8482 | Default tap on URL opens settings | ||
|
|
2c50bd1d9a | Add demo account to Flutter client also | ||
|
|
42724335ab | Default login for Flutter client beta | ||
|
|
da754b8d05 | Version number to bundle files | ||
|
|
1995c62ddf | Flutter title and icon alignment fixes | ||
|
|
2dcb4b4f67 | Small Flutter UI tweaks | ||
|
|
eb0d05a7fb | Intro mode in Flutter client fixes | ||
|
|
bf0be85859 |
Expose ui_layout and ai_enabled to mobile clients and add enable_ai endpoint (#983)
* Wire ui layout and AI flags into mobile auth Include ui_layout and ai_enabled in mobile login/signup/SSO payloads, add an authenticated endpoint to enable AI from Flutter, and gate mobile navigation based on intro layout and AI consent flow. * Linter * Ensure write scope on enable_ai * Make sure AI is available before enabling it * Test improvements * PR comment * Fix review issues: test assertion bug, missing coverage, and Dart defaults (#985) - Fix login test to use ai_enabled? (method) instead of ai_enabled (column) to match what mobile_user_payload actually serializes - Add test for enable_ai when ai_available? returns false (403 path) - Default aiEnabled to false when user is null in AuthProvider to avoid showing AI as available before authentication completes - Remove extra blank lines in auth_provider.dart and auth_service.dart https://claude.ai/code/session_01LEYYmtsDBoqizyihFtkye4 Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
32b01165c9 |
Flutter login polish (#973)
* Skip config screen by setting demo site default * Small copy edits * Login page polish |
||
|
|
ca3abd5d8b |
Add Google Sign-In (SSO) support to Flutter mobile app (#860)
* Add mobile SSO support to sessions controller
Add /auth/mobile/:provider route and mobile_sso_start action that
captures device params in session and renders an auto-submitting POST
form to OmniAuth (required by omniauth-rails_csrf_protection).
Modify openid_connect callback to detect mobile_sso session, issue
Doorkeeper tokens via MobileDevice, and redirect to sureapp://oauth/callback
with tokens. Handles MFA users and unlinked accounts with error redirects.
Validates provider name against configured SSO providers and device info
before proceeding.
* Add SSO auth flow to Flutter service and provider
Add buildSsoUrl() and handleSsoCallback() to AuthService for
constructing the mobile SSO URL and parsing tokens from the deep
link callback.
Add startSsoLogin() and handleSsoCallback() to AuthProvider for
launching browser-based SSO and processing the redirect.
* Register deep link listener for SSO callback
Listen for sureapp://oauth/* deep links via app_links package,
handling both cold start (getInitialLink) and warm (uriLinkStream)
scenarios. Routes callbacks to AuthProvider.handleSsoCallback().
* Add Google Sign-In button to Flutter login screen
Add "or" divider and outlined Google Sign-In button that triggers
browser-based SSO via startSsoLogin('google_oauth2').
Add app_links and url_launcher dependencies to pubspec.yaml.
* Fix mobile SSO failure handling to redirect back to app
When OmniAuth fails during mobile SSO flow, redirect to
sureapp://oauth/callback with the error instead of the web login page.
Cleans up mobile_sso session data on failure.
* Address PR review feedback for mobile SSO flow
- Use strong params for device info in mobile_sso_start
- Guard against nil session data in handle_mobile_sso_callback
- Add error handling for AppLinks initialization and stream
- Handle launchUrl false return value in SSO login
- Use user-friendly error messages instead of exposing exceptions
- Reject empty token strings in SSO callback validation
* Consolidate mobile device token logic into MobileDevice model
Extract duplicated device upsert and token issuance code from
AuthController and SessionsController into MobileDevice. Add
CALLBACK_URL constant and URL builder helpers to eliminate repeated
deep-link strings. Add mobile SSO integration tests covering the
full flow, MFA rejection, unlinked accounts, and failure handling.
* Fix CI: resolve Brakeman redirect warnings and rubocop empty line
Move mobile SSO redirect into a private controller method with an
inline string literal so Brakeman can statically verify the target.
Remove unused URL builder helpers from MobileDevice. Fix extra empty
line at end of AuthController class body.
* Use authorization code exchange for mobile SSO and add signup error handling
Replace passing plaintext tokens in mobile SSO redirect URLs with a
one-time authorization code pattern. Tokens are now stored server-side
in Rails.cache (5min TTL) and exchanged via a secure POST to
/api/v1/auth/sso_exchange. Also wraps device/token creation in the
signup action with error handling and sanitizes device error messages.
* Add error handling for login device registration and blank SSO code guard
* Address PR #860 review: fix SSO race condition, add OpenAPI spec, and cleanup
- Fix race condition in sso_exchange by checking Rails.cache.delete return
value to ensure only one request can consume an authorization code
- Use strong parameters (params.require) for sso_exchange code param
- Move inline HTML from mobile_sso_start to a proper view template
- Clear stale session[:mobile_sso] flag on web login paths to prevent
abandoned mobile flows from hijacking subsequent web SSO logins
- Add OpenAPI/rswag spec for all auth API endpoints
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix mobile SSO test to match authorization code exchange pattern
The test was asserting tokens directly in the callback URL, but the code
uses an authorization code exchange pattern. Updated to exchange the code
via the sso_exchange API endpoint. Also swaps in a MemoryStore for this
test since the test environment uses null_store which discards writes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Refactor mobile OAuth to use single shared application
Replace per-device Doorkeeper::Application creation with a shared
"Sure Mobile" OAuth app. Device tracking uses mobile_device_id on
access tokens instead of oauth_application_id on mobile_devices.
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
|
||
|
|
2ac3f3dff0 |
Add account filtering and net worth card to dashboard (#818)
* feat(mobile): optimize asset/liability display with filters - Add NetWorthCard widget with placeholder for future net worth API - Add side-by-side Assets/Liabilities display with tap-to-filter - Implement CurrencyFilter widget for multi-select currency filtering - Replace old _SummaryCard with new unified design - Remove _CollapsibleSectionHeader in favor of filter-based navigation The net worth section shows a placeholder as the API endpoint is not yet available. Users can now filter accounts by type (assets/liabilities) and by currency. https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3 * fix(mobile): remove unused variables and add const - Remove unused _totalAssets, _totalLiabilities, _getPrimaryCurrency - Add const to Text('All') widget https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3 * feat(mobile): enhance dashboard with icons, long-press breakdown, and grouped view - NetWorthCard: replace text labels with trending icons, add colored bottom borders for asset (green) and liability (red) sections - Add long-press gesture on asset/liability areas to show full currency breakdown in a bottom sheet popup - Add collapsible account type grouping (Crypto, Bank, Investment, etc.) with type-specific icons and expand/collapse headers - Add PreferencesService for persisting display settings - Add "Group by Account Type" toggle in Settings screen - Wire settings change to dashboard via GlobalKey for live updates https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3 * refactor(mobile): remove welcome header from dashboard Strip the Welcome greeting and subtitle to let the financial overview take immediate focus. https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3 * feat(mobile): compact filter buttons with scroll-wheel currency switcher - Remove trending icons from asset/liability filter buttons - Increase amount font size to titleMedium bold - Reduce Net Worth section and filter button padding - Show single currency at a time with ListWheelScrollView for scrolling between currencies (wheel-picker style) - Absorb scroll events via NotificationListener to prevent triggering pull-to-refresh - Keep icons in the long-press currency breakdown popup https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3 * fix(mobile): prevent bottom sheet overflow with ConstrainedBox Use ConstrainedBox + ListView.separated with shrinkWrap for the currency breakdown popup. Few currencies: sheet sizes to content. Many currencies: caps at 50% screen height and scrolls. Also add isScrollControlled and useSafeArea to showModalBottomSheet. https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3 * fix(mobile): reload dashboard preferences on any tab switch to Home Previously only reloaded when navigating directly from Settings to Home. Now reloads whenever the Home tab is selected, covering paths like Settings -> More -> Home. https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3 * chore(mobile): simplify net worth placeholder to single line Replace the two-line Net Worth / -- placeholder with a compact "Net Worth — coming soon" label while the API endpoint is pending. https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3 --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
81cf473862 |
fix: Use getValidAccessToken() in connectivity banner sync button (#851)
* feat(mobile): Add transaction display on calendar date tap Implement two-tap interaction for calendar dates: - First tap selects a date (highlighted with thicker primary color border) - Second tap on same date shows AlertDialog with transactions for that day Each transaction displays with: - Color-coded icon (red minus for expenses, green plus for income) - Transaction name as title - Notes as subtitle (if present) - Amount with color matching expense/income Selection is cleared when changing account, account type, or month. https://claude.ai/code/session_019m7ZrCakU6h9xLwD1NTx9i * feat(mobile): optimize asset/liability display with filters - Add NetWorthCard widget with placeholder for future net worth API - Add side-by-side Assets/Liabilities display with tap-to-filter - Implement CurrencyFilter widget for multi-select currency filtering - Replace old _SummaryCard with new unified design - Remove _CollapsibleSectionHeader in favor of filter-based navigation The net worth section shows a placeholder as the API endpoint is not yet available. Users can now filter accounts by type (assets/liabilities) and by currency. https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3 * fix(mobile): remove unused variables and add const - Remove unused _totalAssets, _totalLiabilities, _getPrimaryCurrency - Add const to Text('All') widget https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3 * feat(mobile): enhance dashboard with icons, long-press breakdown, and grouped view - NetWorthCard: replace text labels with trending icons, add colored bottom borders for asset (green) and liability (red) sections - Add long-press gesture on asset/liability areas to show full currency breakdown in a bottom sheet popup - Add collapsible account type grouping (Crypto, Bank, Investment, etc.) with type-specific icons and expand/collapse headers - Add PreferencesService for persisting display settings - Add "Group by Account Type" toggle in Settings screen - Wire settings change to dashboard via GlobalKey for live updates https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3 * refactor(mobile): remove welcome header from dashboard Strip the Welcome greeting and subtitle to let the financial overview take immediate focus. https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3 * feat(mobile): compact filter buttons with scroll-wheel currency switcher - Remove trending icons from asset/liability filter buttons - Increase amount font size to titleMedium bold - Reduce Net Worth section and filter button padding - Show single currency at a time with ListWheelScrollView for scrolling between currencies (wheel-picker style) - Absorb scroll events via NotificationListener to prevent triggering pull-to-refresh - Keep icons in the long-press currency breakdown popup https://claude.ai/code/session_01W8cQSCzmgTmTqwRJ8Ycpx3 * feat: Add API key login option to mobile app Add a "Via API Key Login" button on the login screen that opens a dialog for entering an API key. The API key is validated by making a test request to /api/v1/accounts with the X-Api-Key header, and on success is persisted in secure storage. All HTTP services now use a centralized ApiConfig.getAuthHeaders() helper that returns the correct auth header (X-Api-Key or Bearer) based on the current auth mode. https://claude.ai/code/session_01DnyCzdMjVpSsbBZK3XbzUH * fix: Improve API key dialog context handling and controller disposal - Use outer context for SnackBar so it displays on the main screen instead of behind the dialog - Explicitly dispose TextEditingController to prevent memory leaks - Close dialog on failure before showing error SnackBar for better UX - Avoid StatefulBuilder context parameter shadowing https://claude.ai/code/session_01DnyCzdMjVpSsbBZK3XbzUH * fix: Use user-friendly error message in API key login catch block Log the technical exception details via LogService.instance.error and show a generic "Unable to connect" message to the user instead of exposing the raw exception string. https://claude.ai/code/session_01DnyCzdMjVpSsbBZK3XbzUH * fix: Use getValidAccessToken() in connectivity banner sync button Replace direct authProvider.tokens?.accessToken access with getValidAccessToken() so the Sync Now button works in API-key auth mode where _tokens is null. https://claude.ai/code/session_01DnyCzdMjVpSsbBZK3XbzUH * Revert "fix: Use getValidAccessToken() in connectivity banner sync button" This reverts commit |