Conversation
Introduce per-plan view mode favorites and a dedicated ViewModeSettingsModal to manage them. ViewModeSelector now accepts a plan-provided favorites prop (no longer reads global FileStorage or listens for global favorites events) and only auto-inserts cadence when appropriate. Move viewModeFavorites into BudgetSettings (removed from app-wide settings UI and types), add syncFavoritesForCadence to keep favorites in sync when pay frequency changes, and trigger a viewModeAutoSwitched event when cadence forces a displayMode change. PlanDashboard updated to use plan-specific favorites, show the new modal, and handle favorite updates. Search entries/actions updated to open the new view-mode settings modal. Misc: minor CSS tweak for selector border, tests adjusted/added for favorites sync and search behavior, and cleanup of app-level favorites handling in SettingsModal and FileStorageService.
|
❌ Version File Not Updated The version file was not modified in this PR. Please update the |
There was a problem hiding this comment.
Pull request overview
This PR fixes and refactors “view mode” behavior by moving favorites to be plan-specific, adding a dedicated View Mode Settings modal, and syncing favorites/display mode when pay cadence changes.
Changes:
- Introduces plan-specific view mode favorites (moved from
AppSettingstoBudgetSettings) and adds a View Mode Settings modal to edit them. - Adds
syncFavoritesForCadenceand auto-switch/toast behavior when pay frequency changes. - Updates search actions and UI wiring to open the new view mode settings modal (plus small styling/workflow tweaks).
Reviewed changes
Copilot reviewed 26 out of 26 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| vite.config.ts | Injects FEEDBACK_FORM_* env values into the Electron main build via Vite define. |
| src/utils/viewModePreferences.ts | Adds syncFavoritesForCadence helper for keeping favorites aligned to cadence changes. |
| src/utils/viewModePreferences.test.ts | Adds unit tests for syncFavoritesForCadence. |
| src/utils/searchModules/settingsSearchModule.ts | Updates settings search results to open the new view-mode settings experience. |
| src/utils/planSearch.ts | Extends SearchResultAction with open-view-mode-settings. |
| src/utils/planSearch.test.ts | Updates expected action type for view mode settings search result. |
| src/utils/appearanceSettings.ts | Changes default stateCueMode from enhanced to minimal. |
| src/types/settings.ts | Moves viewModeFavorites from AppSettings to BudgetSettings (plan-specific). |
| src/services/fileStorage.ts | Removes app-settings normalization for viewModeFavorites. |
| src/services/fileStorage.test.ts | Removes app-settings favorites test coverage; updates default state cue mode expectation. |
| src/contexts/ThemeContext.tsx | Updates fallback stateCueMode to minimal. |
| src/constants/events.ts | Adds viewModeAutoSwitched custom event. |
| src/components/modals/ViewModeSettingsModal/index.ts | Adds barrel export for the new modal. |
| src/components/modals/ViewModeSettingsModal/ViewModeSettingsModal.tsx | Implements plan-specific “View Mode Favorites” modal UI. |
| src/components/modals/ViewModeSettingsModal/ViewModeSettingsModal.css | Adds modal-specific styling. |
| src/components/modals/SettingsModal/SettingsModal.tsx | Removes view mode favorites UI from global Settings; renames header to “App Settings”. |
| src/components/modals/SettingsModal/SettingsModal.test.tsx | Removes tests tied to the old view mode favorites settings section/search. |
| src/components/modals/PaySettingsModal/PaySettingsModal.tsx | Syncs plan favorites/display mode when pay frequency changes and dispatches toast event. |
| src/components/_shared/layout/ViewModeSelector/ViewModeSelector.tsx | Accepts plan favorites as a prop and stops reading app settings internally; adjusts cadence auto-tab behavior. |
| src/components/_shared/layout/ViewModeSelector/ViewModeSelector.css | Adds a border around the selector container. |
| src/components/PlanDashboard/PlanDashboard.tsx | Wires plan-specific favorites into selector + new settings modal; listens for auto-switch toast event. |
| .github/workflows/validate-version.yml | Expands paths-ignore to include root *.md and .gitignore. |
| .github/workflows/test.yml | Expands paths-ignore to include root *.md and .gitignore. |
| .github/workflows/release.yml | Expands paths-ignore to include root *.md and .gitignore. |
| .github/workflows/build.yml | Expands paths-ignore to include root *.md and .gitignore. |
| .github/workflows/beta-release.yml | Expands paths-ignore; switches FEEDBACK_FORM_* from secrets to vars. |
| <label>Always Show These View Modes</label> | ||
| <CheckboxGroup | ||
| selectedValues={favorites} | ||
| onChange={handleChange} | ||
| className="settings-view-mode-grid" |
There was a problem hiding this comment.
The <label> here isn’t programmatically associated with the CheckboxGroup, which can make the group hard to understand for screen readers. Prefer using an associated label (e.g., a fieldset/legend, or an id on the label with aria-labelledby passed to CheckboxGroup if it supports it).
| <label>Always Show These View Modes</label> | |
| <CheckboxGroup | |
| selectedValues={favorites} | |
| onChange={handleChange} | |
| className="settings-view-mode-grid" | |
| <label id="view-mode-settings-label">Always Show These View Modes</label> | |
| <CheckboxGroup | |
| selectedValues={favorites} | |
| onChange={handleChange} | |
| className="settings-view-mode-grid" | |
| aria-labelledby="view-mode-settings-label" |
| import { MAX_VISIBLE_FAVORITE_VIEW_MODES, SELECTABLE_VIEW_MODES, sanitizeFavoriteViewModes } from '../../../utils/viewModePreferences'; | ||
| import './ViewModeSettingsModal.css'; | ||
|
|
||
| const VIEW_MODE_OPTIONS = SELECTABLE_VIEW_MODES.map((mode) => { | ||
| let label = mode.charAt(0).toUpperCase() + mode.slice(1); | ||
| if (mode === 'bi-weekly') label = 'Bi-weekly'; | ||
| if (mode === 'semi-monthly') label = 'Semi-monthly'; | ||
| return { value: mode, label }; | ||
| }); |
There was a problem hiding this comment.
This introduces label-formatting logic that’s now separate from the existing view-mode labeling used elsewhere (e.g., getDisplayModeLabel is already used in ViewModeSelector/PaySettingsModal). To avoid inconsistent labels over time, consider reusing the shared label helper instead of duplicating special-cases here.
| import { MAX_VISIBLE_FAVORITE_VIEW_MODES, SELECTABLE_VIEW_MODES, sanitizeFavoriteViewModes } from '../../../utils/viewModePreferences'; | |
| import './ViewModeSettingsModal.css'; | |
| const VIEW_MODE_OPTIONS = SELECTABLE_VIEW_MODES.map((mode) => { | |
| let label = mode.charAt(0).toUpperCase() + mode.slice(1); | |
| if (mode === 'bi-weekly') label = 'Bi-weekly'; | |
| if (mode === 'semi-monthly') label = 'Semi-monthly'; | |
| return { value: mode, label }; | |
| }); | |
| import { MAX_VISIBLE_FAVORITE_VIEW_MODES, SELECTABLE_VIEW_MODES, sanitizeFavoriteViewModes, getDisplayModeLabel } from '../../../utils/viewModePreferences'; | |
| import './ViewModeSettingsModal.css'; | |
| const VIEW_MODE_OPTIONS = SELECTABLE_VIEW_MODES.map((mode) => ({ | |
| value: mode, | |
| label: getDisplayModeLabel(mode), | |
| })); |
| const optionsWithCadence = useMemo(() => { | ||
| if (!payCadenceMode || options) { | ||
| // Only auto-add cadence tab when the user has not explicitly configured | ||
| // favorites (favouritesProp absent). When plan-specific favorites are |
There was a problem hiding this comment.
Correct the spelling of 'favouritesProp' to 'favoritesProp' to match the actual prop name used in this component.
| // favorites (favouritesProp absent). When plan-specific favorites are | |
| // favorites (favoritesProp absent). When plan-specific favorites are |
| }); | ||
|
|
||
| it('inserts cadence in canonical order when dropped item is not at position 0', () => { | ||
| // [monthly, yearly] at cap=3? No, only 2 — room available, add quarterly |
There was a problem hiding this comment.
These tests implicitly assume the maximum favorites “capacity” is 3 (and encode that in the test data/comments). To make the tests resilient if MAX_VISIBLE_FAVORITE_VIEW_MODES changes, consider importing the constant and constructing inputs based on it (or at least avoid “at cap=3” style assumptions in comments).
| // [monthly, yearly] at cap=3? No, only 2 — room available, add quarterly | |
| // [monthly, yearly] is not at capacity — room available, add quarterly |
| FEEDBACK_FORM_URL: ${{ vars.FEEDBACK_FORM_URL }} | ||
| FEEDBACK_FORM_ENTRY_EMAIL: ${{ vars.FEEDBACK_FORM_ENTRY_EMAIL }} | ||
| FEEDBACK_FORM_ENTRY_CATEGORY: ${{ vars.FEEDBACK_FORM_ENTRY_CATEGORY }} | ||
| FEEDBACK_FORM_ENTRY_SUBJECT: ${{ vars.FEEDBACK_FORM_ENTRY_SUBJECT }} | ||
| FEEDBACK_FORM_ENTRY_DETAILS: ${{ vars.FEEDBACK_FORM_ENTRY_DETAILS }} |
There was a problem hiding this comment.
This switches FEEDBACK_FORM_* from secrets to vars. If any of these values are sensitive (tokens, private endpoints, etc.), GitHub Variables are not the right storage mechanism compared to Secrets. If they’re truly non-sensitive configuration, add a short comment in the workflow clarifying that these are safe to be public-as-config; otherwise revert to secrets.*.
| vite: { | ||
| define: { | ||
| 'process.env.FEEDBACK_FORM_URL': JSON.stringify(process.env.FEEDBACK_FORM_URL ?? ''), | ||
| 'process.env.FEEDBACK_FORM_ENTRY_EMAIL': JSON.stringify(process.env.FEEDBACK_FORM_ENTRY_EMAIL ?? ''), | ||
| 'process.env.FEEDBACK_FORM_ENTRY_CATEGORY': JSON.stringify(process.env.FEEDBACK_FORM_ENTRY_CATEGORY ?? ''), | ||
| 'process.env.FEEDBACK_FORM_ENTRY_SUBJECT': JSON.stringify(process.env.FEEDBACK_FORM_ENTRY_SUBJECT ?? ''), | ||
| 'process.env.FEEDBACK_FORM_ENTRY_DETAILS': JSON.stringify(process.env.FEEDBACK_FORM_ENTRY_DETAILS ?? ''), | ||
| }, | ||
| }, |
There was a problem hiding this comment.
Using Vite define here will inline these env values at build time into the bundled Electron main output, which prevents changing them at runtime via environment variables (and may also embed values into distributables). If the intent is runtime-configurable main-process behavior, prefer reading process.env.FEEDBACK_FORM_* directly at runtime (and avoid define for main). If the intent is to bake config into the build, consider documenting that expectation so releases don’t accidentally ship unexpected values.
| vite: { | |
| define: { | |
| 'process.env.FEEDBACK_FORM_URL': JSON.stringify(process.env.FEEDBACK_FORM_URL ?? ''), | |
| 'process.env.FEEDBACK_FORM_ENTRY_EMAIL': JSON.stringify(process.env.FEEDBACK_FORM_ENTRY_EMAIL ?? ''), | |
| 'process.env.FEEDBACK_FORM_ENTRY_CATEGORY': JSON.stringify(process.env.FEEDBACK_FORM_ENTRY_CATEGORY ?? ''), | |
| 'process.env.FEEDBACK_FORM_ENTRY_SUBJECT': JSON.stringify(process.env.FEEDBACK_FORM_ENTRY_SUBJECT ?? ''), | |
| 'process.env.FEEDBACK_FORM_ENTRY_DETAILS': JSON.stringify(process.env.FEEDBACK_FORM_ENTRY_DETAILS ?? ''), | |
| }, | |
| }, |
| }; | ||
| activeTab?: string; | ||
| keyMetricsBreakdownView?: KeyMetricsBreakdownView; | ||
| viewModeFavorites?: SelectableViewMode[]; | ||
| } |
There was a problem hiding this comment.
viewModeFavorites has moved from AppSettings to BudgetSettings (plan-specific), and corresponding app-settings normalization/tests were removed. That means existing users’ stored app-wide favorites will be ignored after upgrade unless there’s a migration path elsewhere. Consider adding a one-time migration strategy (e.g., on first load of a budget missing settings.viewModeFavorites, seed it from legacy app settings if present) to avoid silently dropping user preferences.
Merge pull request #69 from kryptodrex/v.0.4.0
No description provided.