tabedzkiGitHubrznn7rznn7github-actions[bot] <github-actions[bot]@users.noreply.github.com>github-actions[bot] <matiss@mja.lv>Claude Fable 5
fe363a2643 [AI]: Replacing month picker with day picker (#8427)
* [AI] feat(reports): add browser-independent month range picker

Replace the report date dropdowns with a popover month range picker built
from React and Actual's own components instead of a native month input
(which does not work in Firefox). Includes start/end month grids,
quick-select presets folded into the popover, a draft selection that only
recomputes the report on close, today highlighting, and an unbounded future
range.

Assisted-by: ClaudeCode:claude-opus-4.8
Co-authored-by: rznn7 <160644362+rznn7@users.noreply.github.com>

* [AI] refactor(desktop-client): replace pikaday with react-aria in DateSelect

Swap the Pikaday-based day picker in DateSelect for react-aria-components'
Calendar primitives (backed by @internationalized/date). The user's
firstDayOfWeekIdx preference is encoded via the Unicode 'fw' locale
extension, and the prev/next arrows now use the existing SvgCheveron
icons instead of the two bundled PNGs.

Removes the pikaday and @types/pikaday dependencies, deletes the unused
DateSelect.left/right.png assets, and adds @internationalized/date as a
direct dependency.

Assisted-by: ClaudeCode:claude-opus-4.8

* [AI] fix(desktop-client): resolve CI failures in month picker PR

Remove four devDependencies (@juggle/resize-observer, @types/lodash, @types/promise-retry, memoize-one) that were added but never used, fixing the knip check. Dedupe the split @codemirror/state and @codemirror/view resolutions that the added/removed deps introduced, fixing typecheck. Pin shell-quote (a transitive dependency of launch-editor/npm-run-all) to >=1.8.4 via resolutions, matching the existing socks/serialize-javascript overrides, to clear the critical CVE flagged by Dependency Review.

Assisted-by: ClaudeCode:claude-sonnet-5

* updated yarn.lock

* Updated yarn.lock to address dependency issue

* address more dependency issues

* `yarn up -R '**'` to hopefully address all dependency issues

* fixed donutgraph.tsx typing

* [AI] feat(reports): add Month/Day granularity toggle to range picker

Add a Month/Day toggle above the quick-select column in the report range
picker so users can pick exact days, not just whole months. Switching to Day
expands the range to cover whole days (start -> first of month, end -> last of
month); switching back collapses to months. In day mode the picker emits
yyyy-MM-dd start/end.

Make the month-based reports tolerate day-level values: month range helpers in
monthUtils already coerce day strings to months, and this replaces the few
fragile 'start + "-01"' concatenations (BalanceForecast, BalanceForecastCard,
BudgetAnalysis, balanceForecastChartData) with firstDayOfMonth/getMonth so a
yyyy-MM-dd value is normalized instead of corrupted.

Assisted-by: ClaudeCode:claude-opus-4.8

* [AI] feat(reports): honor day granularity, live end-offset, format-aware picker

Task 3: derive the range picker trigger's display format from the committed
value's shape (yyyy-MM-dd vs yyyy-MM) via a new valueIsDay() helper instead of
the granularity flag, so the label stays correct (day vs month) after the
popover closes.

Task 1: let a live/sliding range end before the current month.
getLatestRange(offset, endOffset=0) and calculateTimeRange's sliding-window
branch derives endOffset from the saved frame (clamped >= 0) so the window's
gap-from-now is preserved as months roll over. Surface it with an 'Exclude
current month' checkbox in the picker popover (shifts the whole range back one
month, keeping its width), enabled for past ranges only.

Task 2: add a maintainer-facing granularities prop on MonthRangePicker (threaded
through Header), defaulting to ['month','day']. Pass ['month'] to hide the Day
toggle on month-only reports; Budget Analysis is locked to month. AgeOfMoney's
granularity stays a distinct concept.

Add unit tests for getLatestRange/calculateTimeRange endOffset and the picker
util helpers. Use named imports throughout.

Assisted-by: ClaudeCode:claude-opus-4.8
Co-authored-by: rznn7 <rznn7@users.noreply.github.com>

* [AI] feat(reports): single-calendar range picker, fix granularity reset on remount

Replaces the two side-by-side Start/End calendar grids in the reports date-range picker with a single calendar (RangeSelector) supporting click-to-pick range selection with a live hover preview, for both Month and Day granularity.

Also fixes the Month/Day toggle silently reverting after a pick: CashFlow, NetWorth, AgeOfMoney, and BalanceForecast unmount their Header/MonthRangePicker subtree on every date change (useReport nulls out data during refetch), which wiped the picker's internal granularity state. Lifted that state into each report so it survives the remount.

Assisted-by: ClaudeCode:claude-sonnet-5

* [AI] fix(reports): persist sliding-window endOffset so live ranges actually slide forward

calculateTimeRange's sliding-window branch re-derived the gap between a saved end date and 'now' on every call, using the stale, decaying persisted end. Since most reports only rewrite that persisted end when the user explicitly saves the widget, the derived gap grew by however much time had passed since the last save — meaning a live range never actually advanced once its gap was nonzero; it stayed pinned to the last-saved dates indefinitely. Caught by useFormulaExecution.test.ts's 'shifts each formula report query window when the current month changes' test, which was failing on this branch.

Adds TimeFrame.endOffset, persisted explicitly alongside start/end/mode instead of re-derived from decaying absolute dates. getLatestRange and calculateTimeRange now return the resolved endOffset as a 4th tuple element; MonthRangePicker computes it at commit time and threads it through Header's onChangeDates into every report that persists a timeFrame (CashFlow, NetWorth, AgeOfMoney, BalanceForecast, Calendar, Sankey, Crossover, Summary, BudgetAnalysis). Legacy saved widgets without endOffset fall back to the old derivation once, as a migration.

Assisted-by: ClaudeCode:claude-sonnet-5

* [AI] fix(reports): route day-grid clicks through pick directly

DayGrid's per-day onSelect was wired to selectMonth, a handler meant for
month-grid clicks and month-navigation day-carry-over. Fed a full day
string it built a malformed candidate (e.g. 2026-07-20-10) that never
string-matched the clicked day, so the range endpoint lost its selected
(primary) styling and only showed as part of the in-range band.

Assisted-by: ClaudeCode:claude-sonnet-5

* [AI] fix(reports): keep day-mode range width when reopening picker

openPopover unconditionally expanded start/end to the full month via
toDayStart/toDayEnd whenever reopening in day mode, even when the range
was already day-shaped from a prior precise pick. Only expand when the
incoming value is still month-shaped (an actual month-to-day switch).

Assisted-by: ClaudeCode:claude-sonnet-5

* [AI] fix(reports): address code review findings on month/day range picker

Fix clamp() returning granularity-mismatched bounds when min/max are month-shaped but the picker is in day mode. Add accessible labels to NavRow's prev/next icon buttons. Deduplicate the Pie shape index coercion in DonutGraph into a shared helper. Drop the unnecessary useMemo around MonthRangePicker's label string.

Skipped: the Header.tsx allMonths[allMonths.length - 1] access can't crash since every caller already guards on allMonths being non-empty before rendering Header. The RangeEndSelector.tsx dead-code finding refers to a file that no longer exists in this codebase. The DateSelect.tsx firstDayOfWeek suggestion doesn't apply: react-aria-components' public CalendarGrid props don't expose firstDayOfWeek in the installed version, so the locale '-u-fw-' extension is still the only way to configure it.

Assisted-by: ClaudeCode:claude-sonnet-5

* [AI] fix(reports): stop widening day-shaped bounds in balance forecast chart data

buildBalanceForecastChartData's Daily branch applied firstDayOfMonth/lastDayOfMonth to start/end unconditionally, which expanded an already day-shaped range (e.g. 2024-03-15) out to its whole month. Only expand when the bound is month-shaped (isValidYearMonth), otherwise use it as-is.

Assisted-by: ClaudeCode:claude-sonnet-5

* [AI] fix(reports): address CodeRabbit findings on month/day range picker (round 2)

Header: fall back minDate to the current month when allMonths is still empty (Summary/Calendar render before their async load populates it). RangeSelector/DayGrid: normalize min/max to day granularity in DayGrid so a month-shaped bound doesn't disable every day in its own month. AgeOfMoney: derive the picker's initial Month/Day mode from the restored range's shape instead of hardcoding month, since that mode isn't persisted separately from timeFrame. BudgetAnalysis: normalize start/end to yyyy-MM inside calculateIsConcise so a legacy day-shaped persisted timeFrame can't corrupt the date-fns parse. DonutGraph: read props.index directly instead of coercing a second shape-callback argument. MonthRangePicker: use && short-circuit JSX per the style guide. AgeOfMoney: drop an unnecessary useCallback with no deps, matching sibling reports. DayGrid/RangeSelector/MonthRangePicker/Header: thread firstDayOfWeekIdx through so the day grid's weekday header and alignment follow the user's configured first day of week instead of hardcoding Sunday-first.

Skipped: the Crossover.tsx 'double shift' finding — the extra -1 month shift is pre-existing, documented, intentional business logic (excludes the current month), applied symmetrically on both load and onChangeDates regardless of whether endOffset was legacy-derived or explicitly persisted; the suggested fix wouldn't touch that code path anyway.

Assisted-by: ClaudeCode:claude-sonnet-5

* Update category and authors in release notes

Address technical debt by introducing a day picker for the calendar.

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8427

* [AI] Address review feedback on month/day range picker and DateSelect

- Crossover: treat legacy widgets' missing endOffset as 0 (no double shift)
- Only offer "Exclude current month" in month mode for ranges ending at the
  current or previous month
- Cap past-oriented reports at the newest month; future stays for forecasts
- Don't commit on Month/Day switch so the popover survives report reloads
- Month-only picker for Calendar, Summary, Sankey and Crossover
- Transaction date picker: visible month-nav arrows that keep focus in the
  input, normal-sized heading, square day cells, tighter popover
- Give unselected grid cells a transparent border so selecting doesn't
  resize the grid
- Derive picker granularity from the committed value shape and drop the
  lifted per-report state and Header props
- Simplify per review: reuse ModeButton/monthUtils/Calendar firstDayOfWeek,
  remove allowFuture, add useLanguage, fix double render on keystroke,
  strip prose comments
- Restore master's yarn.lock (only the pikaday removal remains), drop the
  shell-quote resolution, revert unrelated DonutGraph change
- Release note: describe the feature, add MatissJanis to authors

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8427

* [AI] Cap day-mode picking at today and clamp exclude-toggle shifts

- A month-shaped max that reaches the current month no longer widens to the
  month's last day in day mode; day cells after today are disabled and the
  Month->Day draft widening clamps its end to today
- DayGrid and clamp() keep already day-shaped bounds exact instead of
  widening them back to whole months
- toggleExcludeCurrentMonth clamps the shifted range to minDate/maxDate like
  every other draft mutation
- granFor: function declaration per code style

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Use one SpaceBetween for consistent report header spacing

The header row was two SpaceBetween groups (gap 15 and gap 3) with no gap
between them, and reports wrapped their own controls in ad-hoc gap-10 Views.
Merge the mode toggle, date picker, filter button and report controls into a
single SpaceBetween, render header children through one too, and drop the
per-report wrappers (Balance Forecast, Cash Flow, Budget Analysis, Sankey).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Drop endOffset; sliding windows always anchor to the current month

Remove the persisted TimeFrame.endOffset and its wiring through
reportRanges, the range picker, Header and all report pages. A live
(sliding-window) range now always ends at the current month; "Exclude
current month" still shifts the draft range and commits it as static.

Also address code review findings: guard the "All time" preset while
allMonths is still loading, remount the range selector on granularity
switch so a month-shaped click-anchor can't produce a mixed-shape range,
only offer the exclude toggle when both of its states fit the configured
bounds, and give the month/day grid cells full localized accessible
names and a pressed state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8427

* [AI] Remove the "Exclude current month" checkbox

Its only unique capability was creating a live range ending last month via
endOffset, which is gone; picking the end month in the grid now does the
same thing. Deletes the toggle component, the allowExcludeCurrentMonth
prop and Header wiring, and the now-unused shiftMonths util.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Simplify month-range picker after cleanup review

Drop the util.ts aliases of shared months.ts helpers, derive picker
granularity from the draft value's shape instead of separate state,
normalize min/max bounds once in MonthRangePicker, inline the Header
preset builders, and make month-only the default granularity with
day-capable reports opting in explicitly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Rebuild day-mode picking on react-aria RangeCalendar

Replace the hand-rolled DayGrid with react-aria's RangeCalendar, which
adds keyboard navigation and built-in hover range preview, and use
month/year dropdowns (CalendarMonthPicker plus a bounds-based year
select) in the calendar header for quick jumping. The month grid stays
custom since react-aria has no month-granularity range picker.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Address review feedback and fix picker styling glitches

Validate the full yyyy-MM-dd shape in valueIsDay instead of negating
the year-month check, cap the year dropdown when the report has no
upper date bound (the far-future sentinel would render ~8000 options),
use the bare-button hover color so hovered dates and month/year
selects stay readable, and stretch the day grid to the calendar's
full width.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Simplify month-range picker after cleanup review

- YearSelect reads bounds from RangeCalendarStateContext instead of props
- Dedupe first-day-of-week pref decoding into getFirstDayOfWeek in DateSelect
- Hoist the day-calendar emotion css() call to module scope
- RangeSelector tracks viewYear directly instead of a month it only read the year from
- Inline the single-consumer Grid component into MonthGrid

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Let live report ranges slide by days for day-shaped windows

Day-shaped sliding-window ranges now keep their width in days and
re-anchor to today, so Live works with day granularity in reports.
Formula queries stay month-granular via asMonthSlidingTimeFrame.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8427

* [AI] Move the date range picker into the component library

Extract the reports MonthRangePicker into @actual-app/components as
DateRangePicker: translated strings arrive via a labels prop, date
formatting runs on Intl.DateTimeFormat with a BCP 47 locale string
instead of loot-core's date-fns wrappers, and the first-day-of-week
pref is resolved by the caller. Header.tsx supplies all of these; the
component library gains @internationalized/date and subpath import
mappings for the picker's internals.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Add VRT for the reports date range picker

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Add VRT snapshots for the reports date range picker

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Clean up the date range picker after review

- Move ModeButton into the component library and reuse it for the
  granularity toggle instead of a near-identical SegmentButton copy
- Move the day-shaped value check into loot-core as isValidYearMonthDay
  so reportRanges no longer imports it through the picker component
- Cache Intl.DateTimeFormat instances in formatDate
- Build lastDayOfMonth/currentDay on @internationalized/date
- Pass label subsets as one labels object instead of scalar props

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8427

* [AI] Revert "Update VRT screenshots"

This reverts commit 8cd01e5148.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Hoist DateRangePicker to src root, add ModeButton story, respect app date format

- Move DateRangePicker.tsx and its stories out of the date-range-picker/
  folder so the story sorts alphabetically in the Storybook sidenav; only
  child components remain in the folder.
- Add a Storybook file for ModeButton.
- Add an optional formatDayLabel prop so day-shaped trigger labels can
  follow the app's date format preference; reports Header now passes the
  dateFormat synced pref instead of falling back to locale-numeric dates.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Address review feedback on the date range picker

- Bound scheduled-occurrence counts to the visible chart range so day-mode
  ranges don't count occurrences outside the selected days
- Carry the running balance forward from points before a day-shaped start
  instead of starting the daily chart at zero
- Drop the day granularity from the tracking-budget forecast header, which
  only resolves monthly
- Validate the day against the month and year (incl. leap years) in
  isValidYearMonthDay
- Target the date range picker trigger by test id in the reports e2e test
- Drop a redundant useMemo in useLocale (React Compiler memoizes)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Keep the date range picker open when a quick-select preset is clicked

Presets now preview their range in the open picker draft, like manual
selection, and commit on close so live-range modes are preserved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* [AI] Improve date range picker colors and highlight the active preset

Style the calendar header dropdown options so they stay readable in dark
themes, introduce a datePickerRangeBackground theme variable so the light
theme's range band is purple like the other themes, and derive the active
quick-select preset from the current range so it stays highlighted when
reopening.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8427

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #8427

---------

Co-authored-by: rznn7 <160644362+rznn7@users.noreply.github.com>
Co-authored-by: rznn7 <rznn7@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <matiss@mja.lv>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 16:49:07 +00:00

Actualbudget

Getting Started

Actual is a local-first personal finance tool. It is 100% free and open-source, written in NodeJS, it has a synchronization element so that all your changes can move between devices without any heavy lifting.

If you are interested in contributing, or want to know how development works, see our contributing document we would love to have you.

Want to say thanks? Click the at the top of the page.

Installation

There are four ways to deploy Actual:

  1. One-click deployment via PikaPods (~1.40 $/month) - recommended for non-technical users
  2. Managed hosting via Fly.io (~1.50 $/month)
  3. Self-hosted by using a Docker image
  4. Local-only apps - downloadable Windows, Mac and Linux apps you can run on your device

Learn more in the installation instructions docs.

Ready to Start Budgeting?

Read about Envelope budgeting to know more about the idea behind Actual Budget.

Are you new to budgeting or want to start fresh?

Check out the community's Starting Fresh guide so you can quickly get up and running!

Are you migrating from other budgeting apps?

Check out the community's Migration guide to start jumping on the Actual Budget train!

Documentation

We have a wide range of documentation on how to use Actual, this is all available in our Community Documentation, this includes topics on Budgeting, Account Management, Tips & Tricks and some documentation for developers.

Contributing

Actual is a community driven product. Learn more about contributing to Actual.

Code structure

The Actual app is split up into a few packages:

  • loot-core - The core application that runs on any platform
  • desktop-client - The desktop UI
  • desktop-electron - The desktop app

More information on the project structure is available in our community documentation.

Feature Requests

Current feature requests can be seen here. Vote for your favorite requests by reacting 👍 to the top comment of the request.

To add new feature requests, open a new Issue of the "Feature Request" type.

Translation

Make Actual Budget accessible to more people by helping with the Internationalization of Actual. We are using a crowd sourcing tool to manage the translations, see our Weblate Project. Weblate proudly supports open-source software projects through their Libre plan.

Translation status

Repo Activity

Alt

Sponsors

Thanks to our wonderful sponsors who make Actual Budget possible!

Deploys by Netlify Built with Depot Docker GitHub Anthropic

S
Description
No description provided
Readme
1.1 GiB
Latest
2026-06-01 11:56:50 -05:00
Languages
TypeScript 93.6%
JavaScript 5.2%
CSS 0.6%
Shell 0.3%