Conversation
Restructure the ViewModeSelector markup and styles: remove the extra wrapper, move the settings button out of the option loop, ensure buttons use proper keys and className logic, and adjust CSS (selector height, reduced padding, font-size tweaks, and settings icon styling). Improve copy and formatting in ViewModeSettingsModal and tidy the checkbox disable expression. Increase MAX_VISIBLE_FAVORITE_VIEW_MODES from 3 to 6 to allow pinning more view modes.
Adjust header layout and button sizing: align items in .header-btn-group, set .header-btn-secondary to height: fit-content, and remove redundant .view-mode-selector-wrap rules. Add a null/undefined guard in PlanDashboard.tsx so sanitizeFavoriteViewModes is only called when viewModeFavorites is present (pass undefined otherwise) to avoid potential runtime errors.
Remove supplemental quarterly insertion and refactor syncFavoritesForCadence to accept an optional previousCadenceMode. The new logic removes a prior non-default cadence (so stale cadence tabs don't linger), preserves permanent defaults (monthly/yearly), and inserts the new cadence in canonical order while respecting MAX_VISIBLE_FAVORITE_VIEW_MODES. Also update buildViewModeSelectorOptions to stop auto-adding supplemental modes and expand unit tests to cover the new behaviors.
Change default payCadenceLabel from 'Your Pay Frequency' to 'Pay Frequency' to simplify the displayed label. Add padding to .view-mode-settings-button for improved spacing/alignment. Update unit test to look up the cadence button with a case-insensitive regex (/Weekly\s*Pay Frequency/i) so it matches the adjusted label and tolerant spacing/casing.
Introduce a Pay Settings modal and UI flow, plus cadence-aware initialization for view-mode favorites. Changes include: - Import new icons and PaySettingsModal component; add Banknote header button to open pay details and simplify "Copy Plan" label to "Copy". - Add showPaySettingsModal state and wire menu events (openPayOptions) and app actions to open the modal. Subscribe/unsubscribe to a new copy menu event. - Remove the old paySettingsSearchRequestKey prop/flow and instead show the PaySettingsModal directly; clear pending field highlights when opening/closing. - When a plan has no stored viewModeFavorites, pick the pay-frequency cadence as the initial displayMode and persist initial favorites (using syncFavoritesForCadence and DEFAULT_FAVORITE_VIEW_MODES) via updateBudgetSettings so the favorites modal reflects the cadence. - Guard favorites-change effect to no-op when favorites aren't stored yet to avoid clobbering the cadence initializer. - Update effect dependencies and minor wiring changes to ensure displayMode stays valid and integration with existing tab logic. This consolidates pay settings UX into a modal and ensures new plans get a sensible initial tab based on the user's chosen pay frequency.
Introduce a new TransientStatusIndicator component for transient, accessible status messages. Adds component implementation (props: message, variant, topRem, rightRem, zoomFactor), corresponding CSS for positioning, visual variants and responsive behavior, and an index export. Also updates the shared components barrel to export the new component.
Implement save-before-close behavior and refine UI/UX across the dashboard and app. - electron/main.ts: Replace async message box with a modal save prompt (showMessageBoxSync) that offers Save / Don't Save / Cancel; attempt a programmatic save via webContents before closing, show an error box if the save fails, and preserve window state. Improves protection against data loss when closing windows with unsaved changes. - src/App.tsx: Replace two ad-hoc zoom/undo status divs with the new TransientStatusIndicator component to centralize transient status rendering and account for zoom factor and variants. - src/components/PlanDashboard/PlanDashboard.css: Tighten header action spacing (gap adjustments), align items center, and tweak responsive layout/order for header button groups to improve compactness and alignment. - src/components/PlanDashboard/PlanDashboard.tsx: Refactor save logic into performSave() returning a boolean so callers (including the close handler) can detect success; provide handleSave wrapper for explicit save actions. Remove duplicated Save/Copy buttons from the header and adjust the Settings button disabled condition to not depend on loading. Update imports accordingly. These changes aim to prevent accidental data loss, simplify transient status handling, and tighten the dashboard header layout and save flow for programmatic use.
Introduce a previewable view mode selector and wire it into several tab views. Replaced the CompactViewModeSelector with a new ViewModeButton, added previewDisplayMode state and handleDisplayModePreview to allow temporary previewing of modes (effectiveDisplayMode used when rendering). Passed a viewModeControl prop into PayBreakdown, BillsManager, LoansManager, TaxBreakdown and SavingsManager so the selector can be rendered in each tab header. Also updated multiple tabs to use effectiveDisplayMode instead of the persistent displayMode, adjusted a header label in PayBreakdown (After-Tax -> Take Home Pay), and made a small CSS whitespace tweak.
|
✅ Version Update Detected Version has been correctly bumped from |
There was a problem hiding this comment.
Pull request overview
This PR prepares the v0.4.2 pre-release by updating versioning, improving close/save workflow and menu actions, and refreshing several UI primitives (headers, view-mode controls, transient status messaging) to reduce clutter and improve responsiveness.
Changes:
- Add app-version injection (
__APP_VERSION__) and use it in About/Feedback contexts; bump version to0.4.2. - Rework view-mode controls (remove favorites modal/logic, add new header controls + compact action menus across tab views).
- UI/UX refinements and bug fixes (demo data generation adjustments, glossary term updates, icon/styling tweaks, CSP update).
Reviewed changes
Copilot reviewed 73 out of 74 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| vite.config.ts | Load env via loadEnv, define __APP_VERSION__, and update Electron main env defines. |
| version | Bump app version to 0.4.2. |
| src/vite-env.d.ts | Declare __APP_VERSION__ for TypeScript. |
| src/utils/viewModePreferences.ts | Remove favorites-related utilities; keep selectable modes list. |
| src/utils/viewModePreferences.test.ts | Remove tests for removed favorites utilities. |
| src/utils/tabManagement.ts | Rename Metrics tab label to “Yearly Metrics”. |
| src/utils/tabManagement.test.ts | Update tab label expectation to match “Yearly Metrics”. |
| src/utils/searchRegistry.ts | Suppress duplicate-registration warning in dev (StrictMode/HMR). |
| src/utils/searchModules/paySettingsSearchModule.ts | Rename search result category to “Pay Details”. |
| src/utils/searchModules/paySettingsSearchModule.test.ts | Update fixtures to “Pay Details” naming. |
| src/utils/planSearch.ts | Update doc comment to “Pay Details”. |
| src/utils/demoDataGenerator.ts | Adjust demo salary distribution, bills mix, tax thresholds, and loan safety cap. |
| src/utils/demoDataGenerator.test.ts | Add regression test around loan/bill expense cap behavior. |
| src/types/settings.ts | Remove viewModeFavorites from settings types. |
| src/index.css | Increase icon stroke width CSS variable. |
| src/data/glossary.ts | Update “discretionary” term naming/definitions. |
| src/constants/events.ts | Add copyPlan menu event; remove viewModeFavoritesChanged. |
| src/constants/appearancePresets.ts | Small copy tweak (“original” look). |
| src/constants/appMeta.ts | Add APP_NAME / APP_VERSION constants. |
| src/components/views/SetupWizard/SetupWizard.tsx | Change currency dropdown display format. |
| src/components/tabViews/TaxBreakdown/TaxBreakdown.tsx | Add responsive header actions (compact action menu) + viewModeControl slot. |
| src/components/tabViews/TaxBreakdown/TaxBreakdown.css | Style action menu layout for Tax Breakdown header. |
| src/components/tabViews/SavingsManager/SavingsManager.tsx | Rename title and add viewModeControl slot to header. |
| src/components/tabViews/PayBreakdown/PayBreakdown.tsx | Remove embedded PaySettings modal usage; accept viewModeControl + open-pay-details callback; copy tweaks. |
| src/components/tabViews/PayBreakdown/PayBreakdown.test.tsx | Remove PaySettingsModal-related test/mocks. |
| src/components/tabViews/LoansManager/LoansManager.tsx | Add viewModeControl slot and refine empty-state + button copy/icons. |
| src/components/tabViews/KeyMetrics/KeyMetrics.tsx | Rename header to “Yearly Metrics” and add “Pay Details” header action. |
| src/components/tabViews/BillsManager/BillsManager.tsx | Add compact “Add Expense” action menu + viewModeControl slot; revise empty-state and discretionary labels. |
| src/components/tabViews/BillsManager/BillsManager.css | Add styling for compact action menu behavior. |
| src/components/modals/ViewModeSettingsModal/index.ts | Remove ViewMode favorites modal export. |
| src/components/modals/ViewModeSettingsModal/ViewModeSettingsModal.tsx | Remove ViewMode favorites modal implementation. |
| src/components/modals/ViewModeSettingsModal/ViewModeSettingsModal.css | Remove ViewMode favorites modal styling. |
| src/components/modals/SettingsModal/SettingsModal.tsx | Add header icon. |
| src/components/modals/SettingsModal/SettingsModal.css | Adjust preset grid responsiveness (more columns + breakpoints). |
| src/components/modals/PaySettingsModal/PaySettingsModal.tsx | Rename/retitle to “Pay Details”, add header icon + info box, and remove favorites syncing. |
| src/components/modals/PaySettingsModal/PaySettingsModal.css | Update comment to “Pay Details”. |
| src/components/modals/GlossaryModal/GlossaryModal.tsx | Add header icon. |
| src/components/modals/FeedbackModal/FeedbackModal.tsx | Add header icon; remove screenshot attachment flow; comment out some UI. |
| src/components/modals/AccountsModal/AccountsModal.tsx | Add header icon. |
| src/components/modals/AboutModal/AboutModal.tsx | Use APP_NAME/APP_VERSION and add header icon; copy tweaks. |
| src/components/_shared/layout/ViewModeSelector/ViewModeSelector.tsx | Simplify selector to use all selectable modes (remove favorites/settings button). |
| src/components/_shared/layout/ViewModeSelector/ViewModeSelector.test.tsx | Update tests for new default options behavior. |
| src/components/_shared/layout/ViewModeSelector/ViewModeSelector.css | Compact selector styling adjustments. |
| src/components/_shared/layout/ViewModeButton/index.ts | Export new ViewModeButton. |
| src/components/_shared/layout/ViewModeButton/ViewModeButton.tsx | Add new view-mode dropdown/preview control for headers. |
| src/components/_shared/layout/ViewModeButton/ViewModeButton.css | Styling for new ViewModeButton. |
| src/components/_shared/layout/PageHeader/PageHeader.css | Add inset focus rings to avoid sticky-header outline clipping. |
| src/components/_shared/layout/Modal/Modal.tsx | Support headerIcon prop in Modal header. |
| src/components/_shared/layout/Modal/Modal.css | Layout for modal header icon/title row. |
| src/components/_shared/layout/CompactViewModeSelector/index.ts | Export new compact selector component. |
| src/components/_shared/layout/CompactViewModeSelector/CompactViewModeSelector.tsx | Add compact hover/cycle view-mode selector with floating panel. |
| src/components/_shared/layout/CompactViewModeSelector/CompactViewModeSelector.css | Styling for compact selector. |
| src/components/_shared/index.ts | Export new shared primitives (ActionMenuButton, ViewModeButton, Compact selector, TransientStatusIndicator). |
| src/components/_shared/feedback/TransientStatusIndicator/index.ts | Export TransientStatusIndicator. |
| src/components/_shared/feedback/TransientStatusIndicator/TransientStatusIndicator.tsx | Add reusable fixed-position status indicator (zoom/undo/redo). |
| src/components/_shared/feedback/TransientStatusIndicator/TransientStatusIndicator.css | Styling for status indicator + mobile behavior. |
| src/components/_shared/controls/ActionMenuButton/index.ts | Export ActionMenuButton. |
| src/components/_shared/controls/ActionMenuButton/ActionMenuButton.tsx | Add reusable dropdown action menu button. |
| src/components/_shared/controls/ActionMenuButton/ActionMenuButton.css | Styling for ActionMenuButton. |
| src/components/PlanDashboard/PlanTabs/TabManagementModal.tsx | Switch to Modal headerIcon prop instead of custom header markup. |
| src/components/PlanDashboard/PlanTabs/PlanTabs.tsx | Minor formatting/clarity update to toggle icon rendering. |
| src/components/PlanDashboard/PlanTabs/PlanTabs.css | Increase toggle icon size/stroke for visibility. |
| src/components/PlanDashboard/PlanHistoryOverlay/PlanHistoryOverlay.css | Raise overlay z-index. |
| src/components/PlanDashboard/PlanDashboard.tsx | Introduce view-mode preview state + new header controls; route pay-details open via modal; add copy-plan menu handler; use app version in feedback context. |
| src/components/PlanDashboard/PlanDashboard.css | Header layout spacing tweaks for new controls. |
| src/App.tsx | Replace ad-hoc zoom/undo overlays with TransientStatusIndicator. |
| package.json | Bump app version to 0.4.2. |
| index.html | Add CSP meta tag. |
| electron/main.ts | Add “Copy Plan” menu item; rename “Pay Options” to “Pay Details”; adjust close/save prompting flow. |
| app_updates/v0.4.1-fixes.md | Remove outdated internal checklist doc. |
| RELEASE_NOTES.md | Update release notes for v0.4.2 items. |
| const salaryOptions = [32000, 38000, 45000, 52000, 60000, 70000, 82000, 92000, 98000, 110000, 130000, 210000]; | ||
| const variance = randomBetween(0.88, 1.12); | ||
| annualSalary = salaryOptions[Math.floor(Math.random() * salaryOptions.length)]; | ||
| annualGrossPay = annualSalary; | ||
| annualGrossPay = annualSalary * variance; | ||
| } |
There was a problem hiding this comment.
annualGrossPay is randomized with a variance multiplier, but the returned paySettings.annualSalary stays unadjusted. This makes the demo plan internally inconsistent (the UI will compute gross/net from paySettings.annualSalary, while taxes/bills/loans were generated off the larger/smaller annualGrossPay). Consider either applying the variance to annualSalary (so annualSalary matches annualGrossPay) or removing the variance and keeping gross derived directly from annualSalary.
| const monthlyAmount = billTemplate.basePercent == 0 | ||
| ? roundToCents(billTemplate.baseAmount * variance) | ||
| : roundToCents(monthlyGross * billTemplate.basePercent * variance); |
There was a problem hiding this comment.
Avoid loose equality here. billTemplate.basePercent is a number, so using === 0 is clearer and prevents unexpected coercions if this ever changes (e.g., parsed from JSON).
| <div | ||
| ref={panelRef} | ||
| className="view-mode-button__panel" | ||
| data-placement={placement} | ||
| role="menu" | ||
| aria-label="Select amount display mode" | ||
| onMouseLeave={clearPreview} | ||
| > | ||
| <div className="view-mode-button__hint">Hover to preview. Click to keep.</div> | ||
| <div className="view-mode-button__options"> | ||
| {resolvedOptions.map((option) => { | ||
| const isActive = option.value === activeMode; | ||
| const isCommitted = option.value === committedMode; | ||
| const isHighlighted = option.value === highlightedValue; | ||
|
|
||
| return ( | ||
| <button | ||
| key={option.value} | ||
| type="button" | ||
| className={`view-mode-button__option${isActive ? ' view-mode-button__option--active' : ''}${isCommitted ? ' view-mode-button__option--selected' : ''}`} | ||
| onMouseEnter={() => handlePreview(option.value)} | ||
| onFocus={() => handlePreview(option.value)} | ||
| onClick={() => handleCommit(option.value)} | ||
| role="menuitemradio" | ||
| aria-checked={isCommitted} | ||
| > | ||
| <span className="view-mode-button__option-main"> | ||
| <span className="view-mode-button__option-label">{option.label}</span> | ||
| {isHighlighted && ( | ||
| <span className="view-mode-button__option-badge"> | ||
| <span className="view-mode-button__dot" aria-hidden="true" /> | ||
| {highlightedLabel} | ||
| </span> | ||
| )} | ||
| </span> | ||
| {isCommitted && <Check className="ui-icon ui-icon-sm view-mode-button__check" aria-hidden="true" />} | ||
| </button> | ||
| ); | ||
| })} | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
The dropdown panel and option buttons are always rendered (even when expanded is false) and the options have no tabIndex/aria-hidden gating. This makes the hidden menu items reachable via keyboard navigation/screen readers and can trap focus on invisible elements. Render the panel only when expanded, or add hidden/aria-hidden={!expanded} and ensure the option buttons are removed from the tab order when collapsed.
| <div | |
| ref={panelRef} | |
| className="view-mode-button__panel" | |
| data-placement={placement} | |
| role="menu" | |
| aria-label="Select amount display mode" | |
| onMouseLeave={clearPreview} | |
| > | |
| <div className="view-mode-button__hint">Hover to preview. Click to keep.</div> | |
| <div className="view-mode-button__options"> | |
| {resolvedOptions.map((option) => { | |
| const isActive = option.value === activeMode; | |
| const isCommitted = option.value === committedMode; | |
| const isHighlighted = option.value === highlightedValue; | |
| return ( | |
| <button | |
| key={option.value} | |
| type="button" | |
| className={`view-mode-button__option${isActive ? ' view-mode-button__option--active' : ''}${isCommitted ? ' view-mode-button__option--selected' : ''}`} | |
| onMouseEnter={() => handlePreview(option.value)} | |
| onFocus={() => handlePreview(option.value)} | |
| onClick={() => handleCommit(option.value)} | |
| role="menuitemradio" | |
| aria-checked={isCommitted} | |
| > | |
| <span className="view-mode-button__option-main"> | |
| <span className="view-mode-button__option-label">{option.label}</span> | |
| {isHighlighted && ( | |
| <span className="view-mode-button__option-badge"> | |
| <span className="view-mode-button__dot" aria-hidden="true" /> | |
| {highlightedLabel} | |
| </span> | |
| )} | |
| </span> | |
| {isCommitted && <Check className="ui-icon ui-icon-sm view-mode-button__check" aria-hidden="true" />} | |
| </button> | |
| ); | |
| })} | |
| </div> | |
| </div> | |
| {expanded && ( | |
| <div | |
| ref={panelRef} | |
| className="view-mode-button__panel" | |
| data-placement={placement} | |
| role="menu" | |
| aria-label="Select amount display mode" | |
| onMouseLeave={clearPreview} | |
| > | |
| <div className="view-mode-button__hint">Hover to preview. Click to keep.</div> | |
| <div className="view-mode-button__options"> | |
| {resolvedOptions.map((option) => { | |
| const isActive = option.value === activeMode; | |
| const isCommitted = option.value === committedMode; | |
| const isHighlighted = option.value === highlightedValue; | |
| return ( | |
| <button | |
| key={option.value} | |
| type="button" | |
| className={`view-mode-button__option${isActive ? ' view-mode-button__option--active' : ''}${isCommitted ? ' view-mode-button__option--selected' : ''}`} | |
| onMouseEnter={() => handlePreview(option.value)} | |
| onFocus={() => handlePreview(option.value)} | |
| onClick={() => handleCommit(option.value)} | |
| role="menuitemradio" | |
| aria-checked={isCommitted} | |
| > | |
| <span className="view-mode-button__option-main"> | |
| <span className="view-mode-button__option-label">{option.label}</span> | |
| {isHighlighted && ( | |
| <span className="view-mode-button__option-badge"> | |
| <span className="view-mode-button__dot" aria-hidden="true" /> | |
| {highlightedLabel} | |
| </span> | |
| )} | |
| </span> | |
| {isCommitted && <Check className="ui-icon ui-icon-sm view-mode-button__check" aria-hidden="true" />} | |
| </button> | |
| ); | |
| })} | |
| </div> | |
| </div> | |
| )} |
| {`${curr.name} (${curr.symbol})`} | ||
| </option> |
There was a problem hiding this comment.
This option label no longer includes the currency code (e.g., USD vs CAD both show "$"), which makes selecting the correct currency ambiguous. Consider including the ISO code in the label (e.g., "US Dollar ($) — USD").
| @@ -262,13 +223,13 @@ const FeedbackModal: React.FC<FeedbackModalProps> = ({ isOpen, onClose, context, | |||
| /> | |||
| Include environment diagnostics (tab, plan metadata, browser info) | |||
| </label> | |||
| </div> | |||
| </div> */} | |||
|
|
|||
| <FormGroup label="Screenshot (optional)"> | |||
| {/* <FormGroup label="Screenshot (optional)"> | |||
| <input type="file" accept="image/*" onChange={handleScreenshotChange} /> | |||
| {screenshot && <small className="helper-text">Attached: {screenshot.fileName}</small>} | |||
| {screenshotError && <small className="error">{screenshotError}</small>} | |||
| </FormGroup> | |||
| </FormGroup> */} | |||
There was a problem hiding this comment.
Large sections of UI are now commented out (diagnostics checkbox + screenshot attachment), but related state/logic (includeDiagnostics, payload shape, etc.) remains. This leaves dead/commented code paths that are easy to drift and harder to maintain. Prefer either fully removing the unused feature (and associated state) or re-enabling it behind a proper feature flag/config instead of commented JSX.
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <meta | ||
| http-equiv="Content-Security-Policy" | ||
| content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' http://localhost:* ws://localhost:* https://* wss://*; object-src 'none'; base-uri 'self'" |
There was a problem hiding this comment.
The CSP currently allows connect-src https://* wss://*, which largely negates the value of restricting network egress and makes it harder to reason about where the renderer can send data. If the app only needs a small set of endpoints (or only needs broad access in dev), consider tightening connect-src to self + explicit domains and/or conditionally using a more permissive CSP only in development.
| content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' http://localhost:* ws://localhost:* https://* wss://*; object-src 'none'; base-uri 'self'" | |
| content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' http://localhost:* ws://localhost:*; object-src 'none'; base-uri 'self'" |
| // Show "Do you want to save?" dialog for unsaved plans | ||
| const response = dialog.showMessageBoxSync(window, { | ||
| type: 'question', | ||
| buttons: ['Save', "Don't Save", 'Cancel'], | ||
| defaultId: 0, | ||
| cancelId: 2, | ||
| title: 'Unsaved Changes', | ||
| message: 'You have unsaved changes', | ||
| detail: 'Do you want to save your plan before closing this window?', | ||
| title: 'Save Plan?', | ||
| message: 'This plan has unsaved changes. Do you want to save before closing?', | ||
| }); |
There was a problem hiding this comment.
dialog.showMessageBoxSync blocks the Electron main process event loop while the dialog is open. Since this runs on window close, it can still freeze other windows/menus and makes shutdown behavior less responsive. Prefer using the async dialog.showMessageBox(...) API and awaiting it (as before) to avoid blocking the main process thread.
| ); | ||
|
|
||
| if (hasUnsaved) { | ||
| const result = await dialog.showMessageBox(window, { | ||
| type: 'warning', | ||
| // Show "Do you want to save?" dialog for unsaved plans | ||
| const response = dialog.showMessageBoxSync(window, { | ||
| type: 'question', | ||
| buttons: ['Save', "Don't Save", 'Cancel'], | ||
| defaultId: 0, | ||
| cancelId: 2, | ||
| title: 'Unsaved Changes', | ||
| message: 'You have unsaved changes', | ||
| detail: 'Do you want to save your plan before closing this window?', | ||
| title: 'Save Plan?', | ||
| message: 'This plan has unsaved changes. Do you want to save before closing?', | ||
| }); |
There was a problem hiding this comment.
PR description/release notes say plans should auto-save on close (prompting only when the plan is new/unsaved), but this flow still always prompts whenever __hasUnsavedChanges is true. If the intended behavior is “silent save when a filePath exists”, consider checking for filePath (or exposing that from the renderer) and attempting __requestSaveBeforeClose() automatically, only falling back to a prompt when there’s no save path or when the save attempt fails.
| ref={panelRef} | ||
| className={`cvms__panel cvms__panel--${panelLayout} cvms__panel--align-${panelAlignment}`} | ||
| role="listbox" | ||
| aria-label="Select view mode" |
There was a problem hiding this comment.
When expanded is false, the listbox panel remains in the DOM with role="listbox" but is only visually hidden (opacity/pointer-events). Even though the options are removed from tab order, assistive tech can still encounter the collapsed listbox. Consider adding aria-hidden={!expanded} / hidden on the panel (and/or conditionally rendering it) so the collapsed UI isn’t announced.
| aria-label="Select view mode" | |
| aria-label="Select view mode" | |
| aria-hidden={!expanded} | |
| hidden={!expanded} |
| <Dropdown value={editCurrency} onChange={(e) => setEditCurrency(e.target.value)}> | ||
| {CURRENCIES.map((currency) => ( | ||
| <option key={currency.code} value={currency.code}> | ||
| {currency.symbol} - {currency.name} ({currency.code}) | ||
| {currency.name} ({currency.symbol}) | ||
| </option> | ||
| ))} |
There was a problem hiding this comment.
This dropdown label drops the currency code, which can be ambiguous for currencies sharing a symbol (USD/CAD/AUD, etc.). Consider including the ISO code in the visible label to prevent users selecting the wrong currency.
This pull request introduces several user experience improvements, interface refinements, and bug fixes across the application, with a focus on plan saving behavior, UI clarity, and code cleanup. The most significant updates include automatic plan saving on close, a more compact and responsive plan header, new menu actions, and enhanced status indicators. Additionally, several bug fixes and optimizations are included to ensure a smoother user experience.
User Experience & Workflow Improvements
UI & Layout Enhancements
TransientStatusIndicatorfor consistent, accessible status messages (such as zoom and undo/redo), replacing ad-hoc elements. [1] [2]Bug Fixes & Quality Improvements
index.htmlfor improved security.Codebase Cleanup
app_updates/v0.4.1-fixes.mdfile to keep documentation current.These changes collectively improve the application's usability, reliability, and maintainability.