Compare commits

..

3 Commits

Author SHA1 Message Date
Matt Fiddaman
3017e0c865 release note 2026-03-26 17:14:39 +00:00
github-actions[bot]
b8f2c28fca Remove used release notes 2026-03-26 17:13:25 +00:00
matt-fidd
5c5f3f67df 🔖 (v26.4.0-pre) 2026-03-26 17:12:21 +00:00
162 changed files with 242 additions and 8175 deletions

View File

@@ -35,11 +35,7 @@ const CONFIG = {
'release-notes/**/*',
'upcoming-release-notes/**/*',
],
DOCS_FILES_PATTERNS: [
'packages/docs/**/*',
'!packages/docs/package.json',
'.github/actions/docs-spelling/*',
],
DOCS_FILES_PATTERN: 'packages/docs/**/*',
};
/**
@@ -61,29 +57,78 @@ function parseReleaseNotesCategory(content) {
return categoryMatch[1].trim();
}
/**
* Get the last commit SHA on or before a given date.
* @param {Octokit} octokit - The Octokit instance.
* @param {string} owner - Repository owner.
* @param {string} repo - Repository name.
* @param {Date} beforeDate - The date to find the last commit before.
* @returns {Promise<string|null>} The commit SHA or null if not found.
*/
async function getLastCommitBeforeDate(octokit, owner, repo, beforeDate) {
try {
// Get the default branch from the repository
const { data: repoData } = await octokit.repos.get({ owner, repo });
const defaultBranch = repoData.default_branch;
const { data: commits } = await octokit.repos.listCommits({
owner,
repo,
sha: defaultBranch,
until: beforeDate.toISOString(),
per_page: 1,
});
if (commits.length > 0) {
return commits[0].sha;
}
} catch {
// If error occurs, return null to fall back to default branch
}
return null;
}
/**
* Get the category and points for a PR by reading its release notes file.
* @param {Octokit} octokit - The Octokit instance.
* @param {string} owner - Repository owner.
* @param {string} repo - Repository name.
* @param {string|null} releaseNoteBlobSha - The blob SHA of the release notes file, or null if not found.
* @returns {Promise<Object>} Object with category and points.
* @param {number} prNumber - PR number.
* @param {Date} monthEnd - The end date of the month to use as base revision.
* @returns {Promise<Object>} Object with category and points, or null if error.
*/
async function getPRCategoryAndPoints(
octokit,
owner,
repo,
releaseNoteBlobSha,
prNumber,
monthEnd,
) {
try {
if (releaseNoteBlobSha) {
const { data: blob } = await octokit.git.getBlob({
owner,
repo,
file_sha: releaseNoteBlobSha,
});
const releaseNotesPath = `upcoming-release-notes/${prNumber}.md`;
const content = Buffer.from(blob.content, 'base64').toString('utf-8');
try {
// Get the last commit of the month to use as base revision
const commitSha = await getLastCommitBeforeDate(
octokit,
owner,
repo,
monthEnd,
);
// Try to read the release notes file from the last commit of the month
const { data: fileContent } = await octokit.repos.getContent({
owner,
repo,
path: releaseNotesPath,
ref: commitSha || undefined, // Use commit SHA if available, otherwise default branch
});
if (fileContent.content) {
// Decode base64 content
const content = Buffer.from(fileContent.content, 'base64').toString(
'utf-8',
);
const category = parseReleaseNotesCategory(content);
const tier = CONFIG.PR_CONTRIBUTION_POINTS.find(e =>
e.categories.includes(category),
@@ -231,25 +276,13 @@ async function countContributorPoints() {
),
);
const isDocsFile = file => {
const positivePatterns = CONFIG.DOCS_FILES_PATTERNS.filter(
p => !p.startsWith('!'),
);
const negativePatterns = CONFIG.DOCS_FILES_PATTERNS.filter(p =>
p.startsWith('!'),
);
return (
positivePatterns.some(p =>
minimatch(file.filename, p, { dot: true }),
) &&
negativePatterns.every(p =>
minimatch(file.filename, p, { dot: true }),
)
);
};
const docsFiles = filteredFiles.filter(isDocsFile);
const codeFiles = filteredFiles.filter(file => !isDocsFile(file));
const docsFiles = filteredFiles.filter(file =>
minimatch(file.filename, CONFIG.DOCS_FILES_PATTERN, { dot: true }),
);
const codeFiles = filteredFiles.filter(
file =>
!minimatch(file.filename, CONFIG.DOCS_FILES_PATTERN, { dot: true }),
);
const docsChanges = docsFiles.reduce(
(sum, file) => sum + file.additions + file.deletions,
@@ -296,15 +329,12 @@ async function countContributorPoints() {
// Award points to PR author if they are a core maintainer
const prAuthor = pr.user?.login;
if (prAuthor && orgMemberLogins.has(prAuthor)) {
const releaseNoteFile = modifiedFiles.find(
file =>
file.filename === `upcoming-release-notes/${pr.number}.md`,
);
const categoryAndPoints = await getPRCategoryAndPoints(
octokit,
owner,
repo,
releaseNoteFile?.sha ?? null,
pr.number,
until,
);
if (categoryAndPoints) {

7
my-video/.gitignore vendored
View File

@@ -1,7 +0,0 @@
node_modules/
out/
.playwright-cli/
.superpowers/
# Temp screenshots from playwright-cli
*.png
!public/screenshots/*.png

File diff suppressed because it is too large Load Diff

View File

@@ -1,158 +0,0 @@
# Actual Budget v26.4.0 Release Video — Design Spec
## Overview
A 52-second social media teaser video showcasing the most important features in Actual Budget v26.4.0. Built with Remotion (React), using real screen recordings captured via Playwright and synced to a music track at ~139 BPM.
**Format:** 1280x720, 30fps, ~52 seconds
**Style:** Branded & colorful — Actual brand colors, energetic transitions, playful feel
**Music:** `public/music.mp3` — upbeat tech track, 139 BPM. Using first 52 seconds only.
**Content:** Real screen recordings with bold text overlays (feature name + tagline)
## Music Analysis
- **BPM:** ~139 (beat interval: ~0.43s = ~13 frames at 30fps)
- **04s:** Quiet intro build-up (energy rises from silence to medium)
- **4s+:** Full energy drop, sustained loud level throughout
- **Track total:** 4:24, but we only use 052s
## Video Timeline
### Scene 1: Title Card (0.0s 4.0s | frames 0120)
- Actual Budget logo animates in during quiet intro
- "v26.4.0" and subtitle fade in on rising energy
- Beat drop at 4.0s triggers transition to first feature
- Background: dark gradient with brand colors
### Tier 1: Hero Features (4.0s 20.0s | frames 120600)
Each feature gets ~5.3s (160 frames / 12 beats). Screen recording fills most of the frame in a styled browser mockup. Text overlay appears on the first beat.
**Feature 1: Drag & Drop Transaction Reordering (4.0s 9.3s)**
- Screen recording: user dragging transactions to reorder within the same day
- Text: "Reorder transactions — your way"
- Playwright scenario: open an account, drag a transaction up/down within same-day group
**Feature 2: Concentric Donut Chart (9.3s 14.7s)**
- Screen recording: custom reports page showing the new donut chart visualization
- Text: "Beautiful category breakdowns"
- Playwright scenario: navigate to custom reports, show a donut chart with category data
**Feature 3: Payee Locations MVP (14.7s 20.0s)**
- Screen recording: payee management showing location data
- Text: "Know where you spend"
- Playwright scenario: open payees section, show payee with location info
### Tier 2: Quick Highlights (20.0s 44.0s | frames 6001320)
Each feature gets ~6s (180 frames / 14 beats). Same layout but slightly faster-paced transitions.
**Feature 4: Monthly Budget Cell Notes (20.0s 26.0s)**
- Screen recording: adding a note to a monthly budget cell
- Text: "Annotate your budget"
- Playwright scenario: click a budget cell, add a note, show the note indicator
**Feature 5: Actual CLI Tool (26.0s 32.0s)**
- Screen recording: terminal showing CLI commands querying budget data
- Text: "Your budget, from the command line"
- Note: This will be a terminal recording/mockup rather than Playwright, since it's a CLI tool
**Feature 6: Custom Theme Improvements (32.0s 38.0s)**
- Screen recording: switching themes, showing custom fonts and light/dark options
- Text: "Make it yours"
- Playwright scenario: open settings, switch between themes, toggle light/dark
**Feature 7: Import Improvements (38.0s 44.0s)**
- Screen recording: import dialog showing new options
- Text: "Smarter imports"
- Playwright scenario: open import dialog, show "import since" date filter, swap payee/memo toggle
### Scene 9: Outro (44.0s 52.0s | frames 13201560)
- Stats flash in sequence: "4 features · 45 enhancements · 32 bugfixes"
- CTA: "Update now — actualbudget.org"
- Logo + version badge fade out
- Music continues to natural phrase ending
## Visual Design
### Color Palette
- **Background:** Dark (#1a1a2e / #16213e gradient)
- **Tier 1 accent:** Cyan (#00d2ff)
- **Tier 2 accent:** Coral/Red (#e94560)
- **Outro accent:** Gold (#ffd700)
- **Text:** White (#ffffff) with subtle shadows
- **Brand purple:** Used for logo and accent elements
### Typography
- Feature names: Bold, large sans-serif
- Taglines: Regular weight, slightly smaller
- Stats/CTA: Bold, emphasized with accent color
### Transitions
- Scene transitions: slide/zoom synced to beat hits
- Screen recordings slide in from right
- Text overlays pop in with spring animation on beat
- Slight zoom-in on screen recordings during playback for energy
### Screen Recording Frame
- Styled browser mockup wrapper (rounded corners, subtle shadow)
- Dark chrome to match overall aesthetic
- Fills ~80% of frame width, centered
## Remotion Architecture
### Composition Structure
```
<MyComposition> (1560 frames, 30fps, 1280x720)
<Audio src="music.mp3" />
<TitleCard /> {frames 0-120}
<FeatureScene /> {frames 120-280} -- Drag & Drop
<FeatureScene /> {frames 280-440} -- Donut Chart
<FeatureScene /> {frames 440-600} -- Payee Locations
<FeatureScene /> {frames 600-780} -- Budget Notes
<FeatureScene /> {frames 780-960} -- CLI Tool
<FeatureScene /> {frames 960-1140} -- Themes
<FeatureScene /> {frames 1140-1320} -- Imports
<OutroCard /> {frames 1320-1560}
</MyComposition>
```
### Key Components
- **TitleCard:** Animated logo + version text with fade/scale entrance
- **FeatureScene:** Reusable component accepting screen recording source, title, tagline, accent color, and frame range. Handles slide-in animation, text overlay timing, and zoom effect.
- **OutroCard:** Sequential stat counter animations + CTA
- **BrowserFrame:** Decorative wrapper around screen recordings
### Screen Recordings
Captured as video files via Playwright (webm/mp4) and placed in `public/recordings/`. Each recording is pre-trimmed to show the key interaction for that feature.
## Playwright Recording Plan
Each recording captures a specific user interaction in the running Actual Budget app:
1. **drag-drop.webm** — Open checking account, drag transaction to reorder
2. **donut-chart.webm** — Navigate to reports, create/view donut chart
3. **payee-locations.webm** — Open payees, show payee with location
4. **budget-notes.webm** — Click budget cell, type a note, save
5. **cli-tool.webm** — (Terminal recording, not Playwright)
6. **themes.webm** — Settings > Themes, switch themes, toggle dark/light
7. **imports.webm** — File > Import, show new import options
The app needs to be running with demo data (`yarn start` + "View demo" setup) before capturing.
## Dependencies
- **Remotion 4.0.443** (already installed)
- **@remotion/player** — for preview
- **Tailwind CSS 4** (already installed)
- **Playwright** — for screen recordings (project dependency)
- **ffmpeg** — for audio trimming if needed

View File

@@ -1,3 +0,0 @@
import { config } from "@remotion/eslint-config-flat";
export default config;

File diff suppressed because it is too large Load Diff

View File

@@ -1,38 +0,0 @@
{
"name": "my-video",
"version": "1.0.0",
"description": "My Remotion video",
"repository": {},
"license": "UNLICENSED",
"private": true,
"dependencies": {
"@remotion/cli": "4.0.443",
"@remotion/google-fonts": "^4.0.443",
"@remotion/media": "^4.0.443",
"@remotion/tailwind-v4": "4.0.443",
"@remotion/transitions": "^4.0.443",
"react": "19.2.3",
"react-dom": "19.2.3",
"remotion": "4.0.443",
"tailwindcss": "4.0.0"
},
"devDependencies": {
"@playwright/test": "^1.59.1",
"@remotion/eslint-config-flat": "4.0.443",
"@types/react": "19.2.7",
"@types/web": "0.0.166",
"eslint": "9.19.0",
"playwright": "^1.59.1",
"prettier": "3.8.1",
"typescript": "5.9.3"
},
"scripts": {
"dev": "remotion studio",
"build": "remotion bundle",
"upgrade": "remotion upgrade",
"lint": "eslint src && tsc"
},
"sideEffects": [
"*.css"
]
}

View File

@@ -1,4 +0,0 @@
<svg width="30" height="32" viewBox="0 0 30 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.13785 30.4226L14.9372 1.11397C14.99 1.00184 15.1027 0.930283 15.2267 0.930283H15.8318C15.9542 0.930283 16.0659 1.00015 16.1195 1.11023L25.0219 19.3999L27.8131 18.3264C27.978 18.2629 28.1632 18.3452 28.2266 18.5102L28.9695 20.4417C29.033 20.6067 28.9507 20.7918 28.7857 20.8553L26.2121 21.8452L29.3875 28.3689C29.4648 28.5278 29.3987 28.7193 29.2398 28.7967L27.379 29.7024C27.2201 29.7798 27.0286 29.7136 26.9512 29.5547L23.6739 22.8215L1.6943 31.2754C1.52935 31.3389 1.3442 31.2566 1.28075 31.0916C1.28006 31.0898 1.27938 31.088 1.27872 31.0862L1.12666 30.6684C1.09749 30.5883 1.10152 30.4998 1.13785 30.4226ZM15.56 6.1518L5.85065 26.7737L22.4837 20.3762L15.56 6.1518Z" fill="white"/>
<path d="M21.7768 14.5682L22.7095 17.1121L1.50597 24.8867C1.34004 24.9476 1.1562 24.8624 1.09536 24.6964L0.382928 22.7534C0.322087 22.5875 0.407278 22.4037 0.573207 22.3428L21.7768 14.5682Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -1,13 +0,0 @@
/**
* Note: When using the Node.JS APIs, the config file
* doesn't apply. Instead, pass options directly to the APIs.
*
* All configuration options: https://remotion.dev/docs/config
*/
import { Config } from "@remotion/cli/config";
import { enableTailwind } from '@remotion/tailwind-v4';
Config.setVideoImageFormat("jpeg");
Config.setOverwriteOutput(true);
Config.overrideWebpackConfig(enableTailwind);

View File

@@ -1,42 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #1e1e2e;
color: #f8f8f2;
padding: 40px;
width: 1024px;
height: 576px;
}
pre {
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: 14px;
line-height: 1.7;
white-space: pre;
}
.prompt { color: #50fa7b; }
.dim { color: #6272a4; }
.red { color: #ff5555; }
</style>
</head>
<body>
<pre>
<span class="prompt">$</span> actual-cli accounts list --format table
<span class="dim"> id name offBudget closed balance_current</span>
99e8f789-c982-4618-b686-3b331985374b Bank of America false false 6,929.07
cbcec281-9899-4595-9ef3-5e33725555bb Ally Savings false false 3,425.74
5f8e1bc3-136a-4669-9e00-6e36088eebc3 Capital One false false 1,388.56
713d293e-ec6b-4813-aafd-6c9e63579375 HSBC false false <span class="red">-531.05</span>
2ce33e0b-0517-458c-98d6-796c7ede90f7 Vanguard 401k true false 4,399.38
6f6d9cb2-25ea-4b62-a6f6-0a4f2bb167ad Mortgage true false <span class="red">-301,380.72</span>
7f3ff788-9af8-4109-aef5-b0b2971097df House Asset true false 341,300.00
59aec8a4-0c61-4a4a-932e-4ae927f1adb6 Roth IRA true false 3,439.18
<span class="prompt">$</span> <span style="opacity:0.7">_</span>
</pre>
</body>
</html>

View File

@@ -1,89 +0,0 @@
import React from "react";
import { AbsoluteFill, interpolate, useCurrentFrame, useVideoConfig } from "remotion";
import { Audio } from "@remotion/media";
import { TransitionSeries, linearTiming } from "@remotion/transitions";
import { slide } from "@remotion/transitions/slide";
import { fade } from "@remotion/transitions/fade";
import { staticFile } from "remotion";
import {
FPS,
TITLE_DURATION,
TIER1_SCENE_DURATION,
TIER2_SCENE_DURATION,
OUTRO_DURATION,
TRANSITION_DURATION,
TIER1_FEATURES,
TIER2_FEATURES,
TOTAL_DURATION,
} from "./constants";
import { TitleCard } from "./components/TitleCard";
import { FeatureScene } from "./components/FeatureScene";
import { OutroCard } from "./components/OutroCard";
export function MyComposition() {
const fadeOutDuration = 2 * FPS; // 2 seconds fade out
return (
<AbsoluteFill>
<Audio
src={staticFile("music.mp3")}
volume={(f) =>
interpolate(f, [TOTAL_DURATION - fadeOutDuration, TOTAL_DURATION], [1, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
})
}
/>
<TransitionSeries>
{/* Title scene */}
<TransitionSeries.Sequence durationInFrames={TITLE_DURATION}>
<TitleCard />
</TransitionSeries.Sequence>
{/* Tier 1 feature scenes */}
{TIER1_FEATURES.map((feature) => (
<React.Fragment key={feature.screenshot}>
<TransitionSeries.Transition
presentation={slide({ direction: "from-right" })}
timing={linearTiming({ durationInFrames: TRANSITION_DURATION })}
/>
<TransitionSeries.Sequence
durationInFrames={TIER1_SCENE_DURATION}
premountFor={TRANSITION_DURATION}
>
<FeatureScene feature={feature} />
</TransitionSeries.Sequence>
</React.Fragment>
))}
{/* Tier 2 feature scenes */}
{TIER2_FEATURES.map((feature) => (
<React.Fragment key={feature.screenshot}>
<TransitionSeries.Transition
presentation={slide({ direction: "from-right" })}
timing={linearTiming({ durationInFrames: TRANSITION_DURATION })}
/>
<TransitionSeries.Sequence
durationInFrames={TIER2_SCENE_DURATION}
premountFor={TRANSITION_DURATION}
>
<FeatureScene feature={feature} />
</TransitionSeries.Sequence>
</React.Fragment>
))}
{/* Outro */}
<TransitionSeries.Transition
presentation={fade()}
timing={linearTiming({ durationInFrames: TRANSITION_DURATION })}
/>
<TransitionSeries.Sequence
durationInFrames={OUTRO_DURATION}
premountFor={TRANSITION_DURATION}
>
<OutroCard />
</TransitionSeries.Sequence>
</TransitionSeries>
</AbsoluteFill>
);
}

View File

@@ -1,19 +0,0 @@
import "./index.css";
import { Composition } from "remotion";
import { MyComposition } from "./Composition";
import { FPS, WIDTH, HEIGHT, TOTAL_DURATION } from "./constants";
export const RemotionRoot: React.FC = () => {
return (
<>
<Composition
id="ReleaseVideo"
component={MyComposition}
durationInFrames={TOTAL_DURATION}
fps={FPS}
width={WIDTH}
height={HEIGHT}
/>
</>
);
};

View File

@@ -1,50 +0,0 @@
import { type CSSProperties } from "react";
import { interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
type AnimatedTextProps = {
text: string;
delay?: number;
fontSize?: number;
fontWeight?: CSSProperties["fontWeight"];
color?: string;
style?: CSSProperties;
};
export function AnimatedText({
text,
delay = 0,
fontSize = 48,
fontWeight = "bold",
color = "#ffffff",
style,
}: AnimatedTextProps) {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const progress = spring({
frame: frame - delay,
fps,
config: {
damping: 20,
stiffness: 200,
},
});
const translateY = interpolate(progress, [0, 1], [30, 0]);
const opacity = interpolate(progress, [0, 1], [0, 1]);
return (
<div
style={{
fontSize,
fontWeight,
color,
transform: `translateY(${translateY}px)`,
opacity,
...style,
}}
>
{text}
</div>
);
}

View File

@@ -1,131 +0,0 @@
import { type ReactNode } from "react";
import { useCurrentFrame, useVideoConfig } from "remotion";
import { COLORS } from "../constants";
type BrowserFrameProps = {
children: ReactNode;
accentColor?: string;
};
export function BrowserFrame({
children,
accentColor = COLORS.accentCyan,
}: BrowserFrameProps) {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// Rotate once every 3 seconds
const angle = (frame / fps) * 120; // 120 degrees per second
return (
<div style={{ position: "relative", width: "100%", height: "100%" }}>
{/* Rotating gradient border layer */}
<div
style={{
position: "absolute",
inset: -2,
borderRadius: 14,
background: `conic-gradient(from ${angle}deg, transparent 0%, transparent 60%, ${accentColor} 75%, ${accentColor}cc 80%, transparent 95%, transparent 100%)`,
filter: "blur(4px)",
}}
/>
{/* Subtle static glow underneath */}
<div
style={{
position: "absolute",
inset: -1,
borderRadius: 13,
border: `1px solid ${accentColor}22`,
boxShadow: `0 0 30px ${accentColor}15`,
}}
/>
{/* Main frame content */}
<div
style={{
position: "relative",
borderRadius: 12,
overflow: "hidden",
display: "flex",
flexDirection: "column",
width: "100%",
height: "100%",
}}
>
{/* Title bar */}
<div
style={{
background: "#2d2d3a",
height: 40,
display: "flex",
alignItems: "center",
paddingLeft: 16,
paddingRight: 16,
flexShrink: 0,
position: "relative",
}}
>
{/* Traffic lights */}
<div style={{ display: "flex", gap: 8, zIndex: 1 }}>
<div
style={{
width: 12,
height: 12,
borderRadius: "50%",
background: "#ff5f57",
}}
/>
<div
style={{
width: 12,
height: 12,
borderRadius: "50%",
background: "#ffbd2e",
}}
/>
<div
style={{
width: 12,
height: 12,
borderRadius: "50%",
background: "#28c840",
}}
/>
</div>
{/* Centered title */}
<div
style={{
position: "absolute",
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<span
style={{
color: COLORS.textSecondary,
fontSize: 13,
fontFamily: "sans-serif",
}}
>
Actual Budget
</span>
</div>
</div>
{/* Content area */}
<div
style={{
flex: 1,
background: "#0f0f1a",
overflow: "hidden",
}}
>
{children}
</div>
</div>
</div>
);
}

View File

@@ -1,116 +0,0 @@
import { AbsoluteFill, Img, interpolate, spring, staticFile, useCurrentFrame, useVideoConfig } from "remotion";
import { loadFont } from "@remotion/google-fonts/Inter";
import { type Feature, COLORS, FRAMES_PER_BEAT } from "../constants";
import { AnimatedText } from "./AnimatedText";
import { BrowserFrame } from "./BrowserFrame";
const { fontFamily } = loadFont();
type FeatureSceneProps = {
feature: Feature;
};
export function FeatureScene({ feature }: FeatureSceneProps) {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// Browser frame slides in from right
const slideProgress = spring({
frame,
fps,
config: {
damping: 20,
stiffness: 200,
},
});
const translateX = interpolate(slideProgress, [0, 1], [400, 0]);
return (
<AbsoluteFill
style={{
background: `radial-gradient(ellipse at 30% 50%, ${feature.accentColor}18 0%, ${COLORS.bgDark} 60%)`,
display: "flex",
flexDirection: "row",
alignItems: "center",
padding: "60px 80px",
gap: 60,
fontFamily,
}}
>
{/* Background gradient overlay */}
<div
style={{
position: "absolute",
inset: 0,
background: `linear-gradient(135deg, ${COLORS.bgDark} 0%, ${COLORS.bgGradient} 100%)`,
zIndex: 0,
}}
/>
{/* Accent glow */}
<div
style={{
position: "absolute",
left: feature.screenshot ? -100 : "50%",
top: "50%",
transform: feature.screenshot ? "translateY(-50%)" : "translate(-50%, -50%)",
width: 500,
height: 500,
borderRadius: "50%",
background: `radial-gradient(ellipse at center, ${feature.accentColor}20 0%, transparent 70%)`,
zIndex: 0,
pointerEvents: "none",
}}
/>
{/* Text area */}
<div
style={{
flex: feature.screenshot ? "0 0 360px" : 1,
display: "flex",
flexDirection: "column",
alignItems: feature.screenshot ? "flex-start" : "center",
justifyContent: feature.screenshot ? "flex-start" : "center",
gap: 16,
zIndex: 1,
}}
>
<AnimatedText
text={feature.title}
delay={0}
fontSize={feature.screenshot ? 42 : 56}
fontWeight="bold"
color={feature.accentColor}
style={{ lineHeight: 1.2, textAlign: feature.screenshot ? "left" : "center" }}
/>
<AnimatedText
text={feature.tagline}
delay={FRAMES_PER_BEAT}
fontSize={feature.screenshot ? 22 : 28}
fontWeight={400}
color={COLORS.textSecondary}
style={{ lineHeight: 1.5, textAlign: feature.screenshot ? "left" : "center" }}
/>
</div>
{/* Browser frame with screenshot */}
{feature.screenshot && (
<div
style={{
flex: 1,
transform: `translateX(${translateX}px)`,
zIndex: 1,
}}
>
<BrowserFrame accentColor={feature.accentColor}>
<Img
src={staticFile(`screenshots/${feature.screenshot}`)}
style={{ width: "100%", display: "block" }}
/>
</BrowserFrame>
</div>
)}
</AbsoluteFill>
);
}

View File

@@ -1,201 +0,0 @@
import { AbsoluteFill, Img, interpolate, spring, staticFile, useCurrentFrame, useVideoConfig } from "remotion";
import { loadFont } from "@remotion/google-fonts/Inter";
import { COLORS, FRAMES_PER_BEAT, OUTRO_DURATION } from "../constants";
const { fontFamily } = loadFont();
const STATS = [
{ number: "4", label: "Features" },
{ number: "45", label: "Enhancements" },
{ number: "32", label: "Bugfixes" },
];
type StatCardProps = {
number: string;
label: string;
delay: number;
};
function StatCard({ number, label, delay }: StatCardProps) {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const progress = spring({
frame: frame - delay,
fps,
config: {
damping: 15,
stiffness: 120,
},
});
const translateY = interpolate(progress, [0, 1], [40, 0]);
const opacity = interpolate(progress, [0, 1], [0, 1]);
return (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 8,
transform: `translateY(${translateY}px)`,
opacity,
}}
>
<div
style={{
fontSize: 80,
fontWeight: "bold",
color: COLORS.accentGold,
lineHeight: 1,
}}
>
{number}
</div>
<div
style={{
fontSize: 22,
color: COLORS.textSecondary,
fontWeight: 500,
}}
>
{label}
</div>
</div>
);
}
export function OutroCard() {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const statsEndDelay = STATS.length * FRAMES_PER_BEAT * 2;
// CTA appears after stats
const ctaProgress = spring({
frame: frame - statsEndDelay,
fps,
config: {
damping: 20,
stiffness: 150,
},
});
const ctaOpacity = interpolate(ctaProgress, [0, 1], [0, 1]);
const ctaTranslateY = interpolate(ctaProgress, [0, 1], [20, 0]);
// Fade out in last 1 second (30 frames)
const fadeOutOpacity = interpolate(
frame,
[OUTRO_DURATION - 30, OUTRO_DURATION],
[1, 0],
{
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}
);
return (
<AbsoluteFill
style={{
background: `radial-gradient(ellipse at center, ${COLORS.bgGradient} 0%, ${COLORS.bgDark} 100%)`,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: 48,
fontFamily,
opacity: fadeOutOpacity,
}}
>
{/* Background glow */}
<div
style={{
position: "absolute",
width: 700,
height: 700,
borderRadius: "50%",
background: `radial-gradient(ellipse at center, ${COLORS.accentGold}10 0%, transparent 70%)`,
pointerEvents: "none",
}}
/>
{/* Stats row */}
<div
style={{
display: "flex",
flexDirection: "row",
gap: 80,
alignItems: "center",
}}
>
{STATS.map((stat, i) => (
<StatCard
key={stat.label}
number={stat.number}
label={stat.label}
delay={i * FRAMES_PER_BEAT * 2}
/>
))}
</div>
{/* CTA */}
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 12,
opacity: ctaOpacity,
transform: `translateY(${ctaTranslateY}px)`,
}}
>
<div
style={{
fontSize: 36,
fontWeight: "bold",
color: COLORS.white,
}}
>
Update now
</div>
<div
style={{
fontSize: 22,
color: COLORS.accentCyan,
fontWeight: 500,
}}
>
actualbudget.org
</div>
</div>
{/* Logo at bottom */}
<div
style={{
position: "absolute",
bottom: 40,
opacity: 0.3,
display: "flex",
alignItems: "center",
gap: 12,
}}
>
<Img
src={staticFile("logo.svg")}
style={{ width: 36, height: 36 }}
/>
<span
style={{
color: COLORS.textSecondary,
fontSize: 16,
fontWeight: 500,
}}
>
Actual Budget
</span>
</div>
</AbsoluteFill>
);
}

View File

@@ -1,124 +0,0 @@
import { AbsoluteFill, Img, interpolate, spring, staticFile, useCurrentFrame, useVideoConfig } from "remotion";
import { loadFont } from "@remotion/google-fonts/Inter";
import { COLORS } from "../constants";
const { fontFamily } = loadFont();
export function TitleCard() {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// Logo springs in with heavy config
const logoProgress = spring({
frame,
fps,
config: {
damping: 200,
},
});
const logoScale = interpolate(logoProgress, [0, 1], [0.5, 1]);
const logoOpacity = interpolate(logoProgress, [0, 1], [0, 1]);
// Version badge fades in at 1-2s (30-60 frames)
const badgeOpacity = interpolate(frame, [30, 60], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
// "Here's what's new" subtitle springs in at 2s (frame 60)
const subtitleProgress = spring({
frame: frame - 60,
fps,
config: {
damping: 200,
stiffness: 200,
},
});
const subtitleTranslateY = interpolate(subtitleProgress, [0, 1], [20, 0]);
const subtitleOpacity = interpolate(subtitleProgress, [0, 1], [0, 1]);
return (
<AbsoluteFill
style={{
background: `radial-gradient(ellipse at center, ${COLORS.bgGradient} 0%, ${COLORS.bgDark} 100%)`,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
fontFamily,
}}
>
{/* Radial glow */}
<div
style={{
position: "absolute",
width: 600,
height: 600,
borderRadius: "50%",
background: `radial-gradient(ellipse at center, ${COLORS.accentPurple}22 0%, transparent 70%)`,
pointerEvents: "none",
}}
/>
{/* Logo + title */}
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 24,
transform: `scale(${logoScale})`,
opacity: logoOpacity,
}}
>
<Img
src={staticFile("logo.svg")}
style={{ width: 120, height: 120 }}
/>
<div
style={{
fontSize: 64,
fontWeight: "bold",
color: COLORS.white,
letterSpacing: -1,
}}
>
Actual Budget
</div>
</div>
{/* Version badge */}
<div
style={{
marginTop: 20,
opacity: badgeOpacity,
background: COLORS.accentPurple,
color: COLORS.white,
fontSize: 22,
fontWeight: 600,
padding: "6px 20px",
borderRadius: 999,
letterSpacing: 1,
}}
>
v26.4.0
</div>
{/* Subtitle */}
<div
style={{
marginTop: 32,
fontSize: 32,
color: COLORS.textSecondary,
fontWeight: 400,
opacity: subtitleOpacity,
transform: `translateY(${subtitleTranslateY}px)`,
}}
>
{"Here's what's new"}
</div>
</AbsoluteFill>
);
}

View File

@@ -1,73 +0,0 @@
export const FPS = 30;
export const WIDTH = 1280;
export const HEIGHT = 720;
export const BPM = 139;
export const FRAMES_PER_BEAT = Math.round((60 / BPM) * FPS); // ~13
export const TITLE_DURATION = 120;
export const TIER1_SCENE_DURATION = 160;
export const TIER2_SCENE_DURATION = 180;
export const OUTRO_DURATION = 240;
export const TRANSITION_DURATION = FRAMES_PER_BEAT;
export const COLORS = {
bgDark: "#1a1a2e",
bgGradient: "#16213e",
accentCyan: "#00d2ff",
accentCoral: "#e94560",
accentGold: "#ffd700",
accentPurple: "#7c3aed",
white: "#ffffff",
textSecondary: "#94a3b8",
};
export type Feature = {
title: string;
tagline: string;
screenshot?: string;
accentColor: string;
};
export const TIER1_FEATURES: Feature[] = [
{
title: "Donut Chart Reports",
tagline: "Beautiful category breakdowns",
screenshot: "donut-chart.png",
accentColor: COLORS.accentCyan,
},
{
title: "Budget Notes",
tagline: "Monthly per-category notes",
screenshot: "budget-notes.png",
accentColor: COLORS.accentCyan,
},
];
export const TIER2_FEATURES: Feature[] = [
{
title: "Drag & Drop Reordering",
tagline: "Reorder transactions — your way",
screenshot: "drag-drop.png",
accentColor: COLORS.accentCyan,
},
{
title: "Actual CLI",
tagline: "Your budget, from the command line",
screenshot: "cli-tool.png",
accentColor: COLORS.accentCyan,
},
{
title: "And much more...",
tagline: "Thanks to all the contributors",
accentColor: COLORS.accentCoral,
},
];
// Total = TITLE + 3*TIER1 + 4*TIER2 + OUTRO - 8*TRANSITION
export const TOTAL_DURATION =
TITLE_DURATION +
TIER1_FEATURES.length * TIER1_SCENE_DURATION +
TIER2_FEATURES.length * TIER2_SCENE_DURATION +
OUTRO_DURATION -
(TIER1_FEATURES.length + TIER2_FEATURES.length + 1) * TRANSITION_DURATION;

View File

@@ -1 +0,0 @@
@import "tailwindcss";

View File

@@ -1,4 +0,0 @@
import { registerRoot } from "remotion";
import { RemotionRoot } from "./Root";
registerRoot(RemotionRoot);

View File

@@ -1,15 +0,0 @@
{
"compilerOptions": {
"target": "ES2018",
"module": "commonjs",
"jsx": "react-jsx",
"strict": true,
"noEmit": true,
"lib": ["es2015"],
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noUnusedLocals": true
},
"exclude": ["remotion.config.ts"]
}

View File

@@ -93,7 +93,6 @@
},
"resolutions": {
"adm-zip": "patch:adm-zip@npm%3A0.5.16#~/.yarn/patches/adm-zip-npm-0.5.16-4556fea098.patch",
"axios": "1.14.0",
"rollup": "4.40.1",
"socks": ">=2.8.3"
},

View File

@@ -1,6 +1,6 @@
{
"name": "@actual-app/api",
"version": "26.3.0",
"version": "v26.4.0-pre",
"description": "An API for Actual",
"license": "MIT",
"files": [

View File

@@ -1,6 +1,6 @@
{
"name": "@actual-app/web",
"version": "26.3.0",
"version": "v26.4.0-pre",
"license": "MIT",
"files": [
"build"

View File

@@ -71,12 +71,9 @@ function TransactionListWithPreviews() {
name: 'scheduled-transaction-menu',
options: {
transactionId: transaction.id,
onPost: async (transactionId, today = false) => {
onPost: async transactionId => {
const parts = transactionId.split('/');
await send('schedule/post-transaction', {
id: parts[1],
today,
});
await send('schedule/post-transaction', { id: parts[1] });
dispatch(
collapseModals({
rootModalName: 'scheduled-transaction-menu',

View File

@@ -82,12 +82,9 @@ function TransactionListWithPreviews() {
name: 'scheduled-transaction-menu',
options: {
transactionId: transaction.id,
onPost: async (transactionId, today = false) => {
onPost: async transactionId => {
const parts = transactionId.split('/');
await send('schedule/post-transaction', {
id: parts[1],
today,
});
await send('schedule/post-transaction', { id: parts[1] });
dispatch(
collapseModals({
rootModalName: 'scheduled-transaction-menu',

View File

@@ -82,12 +82,9 @@ function TransactionListWithPreviews() {
name: 'scheduled-transaction-menu',
options: {
transactionId: transaction.id,
onPost: async (transactionId, today = false) => {
onPost: async transactionId => {
const parts = transactionId.split('/');
await send('schedule/post-transaction', {
id: parts[1],
today,
});
await send('schedule/post-transaction', { id: parts[1] });
dispatch(
collapseModals({
rootModalName: 'scheduled-transaction-menu',

View File

@@ -1,6 +1,6 @@
{
"name": "desktop-electron",
"version": "26.3.0",
"version": "v26.4.0-pre",
"description": "A simple and powerful personal finance system",
"author": "Actual",
"main": "build/desktop-electron/index.js",

View File

@@ -0,0 +1,118 @@
---
title: Prerelease 26.4.0
description: New prerelease of Actual.
date: 2026-03-26T10:00
slug: prerelease-26.4.0
tags: [announcement, release]
hide_table_of_contents: false
authors: matt-fidd
---
Version: 26.4.0-pre
#### Features
- [#6157](https://github.com/actualbudget/actual/pull/6157) MVP for payee locations support, including YNAB5 import — thanks @mannkind
- [#6653](https://github.com/actualbudget/actual/pull/6653) Add drag&drop reordering for transactions within the same day — thanks @StephenBrown2
- [#7188](https://github.com/actualbudget/actual/pull/7188) Add a confirmation modal when users merge payees in /payees — thanks @AsgerMogensen
- [#7208](https://github.com/actualbudget/actual/pull/7208) Actual-cli: tool for accessing your budget via the command line. Useful for AI agents — thanks @MatissJanis
#### Enhancements
- [#6414](https://github.com/actualbudget/actual/pull/6414) Add the ability to use Formula Rules when setting the split amount. — thanks @sjones512
- [#6620](https://github.com/actualbudget/actual/pull/6620) Add notes to monthly budget cell — thanks @erwannc
- [#6693](https://github.com/actualbudget/actual/pull/6693) Display/save refill templates — thanks @jfdoming
- [#6903](https://github.com/actualbudget/actual/pull/6903) Add a new budgeted type to the custom report widget. — thanks @diepala
- [#6972](https://github.com/actualbudget/actual/pull/6972) Improve autocomplete sorting with tiered ranking for payee and category dropdowns — thanks @J-LCRX
- [#7005](https://github.com/actualbudget/actual/pull/7005) Change Titlebar 'Sync' to syncing icon only to alleviate confusion with Bank Sync. Add disabled state and aria disabled label when offline. — thanks @Juulz
- [#7038](https://github.com/actualbudget/actual/pull/7038) Introduce concentric donut chart for categories in custom reports — thanks @karimkodera
- [#7069](https://github.com/actualbudget/actual/pull/7069) Converted Electron backups to zip with metadata.json for easy importing — thanks @MikesGlitch
- [#7071](https://github.com/actualbudget/actual/pull/7071) Refactor account hooks to return full React Query states, enhancing data handling and component integration. — thanks @joel-jeremy
- [#7078](https://github.com/actualbudget/actual/pull/7078) Add BUDGET_QUERY and QUERY_EXTRACT functions for formula-based budget analysis. — thanks @sys044
- [#7095](https://github.com/actualbudget/actual/pull/7095) Add New Taiwan Dollar (TWD) to the list of available currencies. — thanks @yhc0712
- [#7101](https://github.com/actualbudget/actual/pull/7101) Add an option to swap payee/memo when importing transaction from file. — thanks @sylvercode
- [#7111](https://github.com/actualbudget/actual/pull/7111) Reload server version when visibility to the page changes — thanks @MatissJanis
- [#7139](https://github.com/actualbudget/actual/pull/7139) Add "only import transactions since" to import dialog — thanks @kivikakk
- [#7144](https://github.com/actualbudget/actual/pull/7144) Sort theme catalog items alphabetically by name for improved user interface organization. — thanks @MatissJanis
- [#7145](https://github.com/actualbudget/actual/pull/7145) Custom Themes: separate light and dark theme options when selecting "system default" theme. — thanks @MatissJanis
- [#7151](https://github.com/actualbudget/actual/pull/7151) Add "Notion-inspired Dark Mode" custom theme to the theme catalogue — thanks @vcruzdesigns
- [#7181](https://github.com/actualbudget/actual/pull/7181) Enable strict linting for template expressions, ensuring better error handling and message clarity. — thanks @MatissJanis
- [#7194](https://github.com/actualbudget/actual/pull/7194) Add light/dark mode specificity to custom themes — thanks @MatissJanis
- [#7207](https://github.com/actualbudget/actual/pull/7207) Add admin and password authentication requirements for changing passwords in sessions. — thanks @MatissJanis
- [#7236](https://github.com/actualbudget/actual/pull/7236) Custom Themes: allow adding custom overrides to themes via the textarea box — thanks @MatissJanis
- [#7239](https://github.com/actualbudget/actual/pull/7239) Custom Themes: allow using a custom font — thanks @MatissJanis
- [#7240](https://github.com/actualbudget/actual/pull/7240) cli: improved aql support — thanks @MatissJanis
#### Bugfixes
- [#6741](https://github.com/actualbudget/actual/pull/6741) When filtering for accounts in e.g. the net worth graph the modal closes the filter tooltip so it's impossible to add e.g. accounts to the filter. — thanks @mnil
- [#6926](https://github.com/actualbudget/actual/pull/6926) Import: added override option allowing users to control whether previously deleted (or merged) transactions are reimported. — thanks @totallynotjon
- [#7041](https://github.com/actualbudget/actual/pull/7041) [Mobile] Show running balance on upcoming transactions when respective setting is toggled — thanks @LeviBorodenko
- [#7047](https://github.com/actualbudget/actual/pull/7047) Make mobile account page colors more consistent — thanks @Juulz
- [#7057](https://github.com/actualbudget/actual/pull/7057) Fix skipping schedules that move before weekend — thanks @Triscal
- [#7092](https://github.com/actualbudget/actual/pull/7092) Stop font size fluctuations showing in summary cards — thanks @matt-fidd
- [#7118](https://github.com/actualbudget/actual/pull/7118) Fix budget analysis report padding when the value is large. — thanks @mnil
- [#7125](https://github.com/actualbudget/actual/pull/7125) Refactor error handling to isolate errors per account in SimpleFin batch synchronization. — thanks @MatissJanis
- [#7146](https://github.com/actualbudget/actual/pull/7146) Fix migrations retrieval when running the docker images — thanks @MikesGlitch
- [#7149](https://github.com/actualbudget/actual/pull/7149) Fix CSV import incorrectly parsing transaction amounts that contain trailing whitespace (e.g. amounts from Excel-saved CSV files). — thanks @mibragimov
- [#7152](https://github.com/actualbudget/actual/pull/7152) Handle missing accounts in SimpleFin batch sync with localized error messaging for users. — thanks @MatissJanis
- [#7155](https://github.com/actualbudget/actual/pull/7155) Fixed a privilege escalation issue affecting password changes — thanks @MatissJanis
- [#7166](https://github.com/actualbudget/actual/pull/7166) Fixed a bug where selecting an item in autocomplete on mobile required two taps. — thanks @Alieksieiev0
- [#7172](https://github.com/actualbudget/actual/pull/7172) Fix multi-tab sync by sharing a single backend via SharedWorker — thanks @MikesGlitch
- [#7177](https://github.com/actualbudget/actual/pull/7177) Fix schedule link being lost when merging transactions — thanks @okxint
- [#7179](https://github.com/actualbudget/actual/pull/7179) Number formatting: normalize apostrophe-dot thousands separator to U+2019 for consistency across Node/ICU versions. — thanks @StephenBrown2
- [#7185](https://github.com/actualbudget/actual/pull/7185) Preserve explicitly provided transaction category when importing transactions with matching payee rules. — thanks @api2062
- [#7191](https://github.com/actualbudget/actual/pull/7191) Fix Spending Analysis report to use tracking budgets when tracking budgeting is enabled — thanks @pranayseela
- [#7202](https://github.com/actualbudget/actual/pull/7202) Fix navigator access error in Node.js environments by adding existence checks and defaults. — thanks @MatissJanis
- [#7219](https://github.com/actualbudget/actual/pull/7219) Fix adm-zip dependency resolution in `@actual-app/core` — thanks @MatissJanis
- [#7245](https://github.com/actualbudget/actual/pull/7245) Fix schedule advancement tracking by saving timestamps only on successful sync completions. — thanks @dearlordylord
- [#7264](https://github.com/actualbudget/actual/pull/7264) This PR fixes a bug where the transaction context menu (right-click) would remain stuck on the screen if opened while a transaction cell was in edit mode. — thanks @nathaliaacouto
- [#7267](https://github.com/actualbudget/actual/pull/7267) Fix mobile transactions view hiding older transactions when hiding reconciled transactions is enabled — thanks @matt-fidd
- [#7268](https://github.com/actualbudget/actual/pull/7268) Fix transaction amounts sometimes not being saved when adding through the desktop view on a touch device — thanks @matt-fidd
- [#7272](https://github.com/actualbudget/actual/pull/7272) Fixed an issue that prevented category's leftover from being able to cover an overbudgeted amount. — thanks @JSkinnerUK
- [#7274](https://github.com/actualbudget/actual/pull/7274) Fixes a bug that prevented scheduled transactions from being included in the selected balance when selecting multiple transactions — thanks @iaewing
- [#7275](https://github.com/actualbudget/actual/pull/7275) Fix a typo in the Repair Transactions paragraph — thanks @tdavis6
#### Maintenance
- [#6809](https://github.com/actualbudget/actual/pull/6809) Remove cyclic dependency between API and loot-core — thanks @MatissJanis
- [#7044](https://github.com/actualbudget/actual/pull/7044) Bump ajv dependency — thanks @matt-fidd
- [#7050](https://github.com/actualbudget/actual/pull/7050) Remove reliance on the API package in YNAB importers — thanks @MatissJanis
- [#7051](https://github.com/actualbudget/actual/pull/7051) TypeScript: strengthen schedule type — thanks @MatissJanis
- [#7053](https://github.com/actualbudget/actual/pull/7053) TypeScript: move ImportTransactionsOpts from the api package to loot-core — thanks @MatissJanis
- [#7055](https://github.com/actualbudget/actual/pull/7055) Lint: add "--quiet" flag to stop reporting warnings — thanks @MatissJanis
- [#7062](https://github.com/actualbudget/actual/pull/7062) TypeScript: start using composite references — thanks @MatissJanis
- [#7073](https://github.com/actualbudget/actual/pull/7073) Bump bn.js dependency — thanks @matt-fidd
- [#7074](https://github.com/actualbudget/actual/pull/7074) Move some sync-server utilities to TypeScript — thanks @jfdoming
- [#7075](https://github.com/actualbudget/actual/pull/7075) Move migrations CI script to typescript + ci-actions — thanks @jfdoming
- [#7091](https://github.com/actualbudget/actual/pull/7091) TypeScript: clean up some types — thanks @MatissJanis
- [#7096](https://github.com/actualbudget/actual/pull/7096) Fix API reference documentation discrepancies — thanks @tifandotme
- [#7104](https://github.com/actualbudget/actual/pull/7104) Allow "unfreeze" workflow to work in fork PRs — thanks @MatissJanis
- [#7105](https://github.com/actualbudget/actual/pull/7105) Add reference to AGENTS.md in CLAUDE.md for improved documentation connectivity. — thanks @MatissJanis
- [#7107](https://github.com/actualbudget/actual/pull/7107) Skip AI-generated release notes for PRs originating from `release/*` branches to avoid redundancy. — thanks @jfdoming
- [#7117](https://github.com/actualbudget/actual/pull/7117) Automatically publish to Flathub when the GitHub release is published — thanks @MikesGlitch
- [#7140](https://github.com/actualbudget/actual/pull/7140) Bump `express-rate-limit` dependency version from 8.2.1 to 8.3.0 for improvements. — thanks @dependabot
- [#7141](https://github.com/actualbudget/actual/pull/7141) Adding more retries to the Docker test in the pipeline — thanks @MikesGlitch
- [#7142](https://github.com/actualbudget/actual/pull/7142) Refactor TypeScript typings by moving window import to globals.ts for cleaner configuration. — thanks @MatissJanis
- [#7153](https://github.com/actualbudget/actual/pull/7153) Establish centralized AI governance documentation for commit and pull request standards. — thanks @MatissJanis
- [#7163](https://github.com/actualbudget/actual/pull/7163) Lint: fix typescript/unbound-method issues — thanks @MatissJanis
- [#7168](https://github.com/actualbudget/actual/pull/7168) Lint: fix 'typescript/no-floating-promises' and 'typescript/require-array-sort-compare' issues — thanks @MatissJanis
- [#7180](https://github.com/actualbudget/actual/pull/7180) Refactor TypeScript build process to use project references for improved incremental builds. — thanks @MatissJanis
- [#7183](https://github.com/actualbudget/actual/pull/7183) TypeScript: upgrade to tsgo — thanks @MatissJanis
- [#7184](https://github.com/actualbudget/actual/pull/7184) Upgrade to Vite 8 — thanks @jfdoming
- [#7187](https://github.com/actualbudget/actual/pull/7187) Remove `deep-equal` package — thanks @jfdoming
- [#7193](https://github.com/actualbudget/actual/pull/7193) Elevate `typescript/no-for-in-array` lint rule from warning to error, refactor affected code. — thanks @MatissJanis
- [#7195](https://github.com/actualbudget/actual/pull/7195) Remove crdt import aliases in favour of package.json exports — thanks @MatissJanis
- [#7196](https://github.com/actualbudget/actual/pull/7196) lint: enable more lint rules that should be fixed over time — thanks @MatissJanis
- [#7199](https://github.com/actualbudget/actual/pull/7199) Fix documentation formatting by removing duplicated content for improved readability in accounts section. — thanks @gaffneyd4
- [#7200](https://github.com/actualbudget/actual/pull/7200) Publish loot-core package and rename it to "@actual-app/core" — thanks @MatissJanis
- [#7209](https://github.com/actualbudget/actual/pull/7209) api: simplify bundling by removing loot-core type inlining — thanks @MatissJanis
- [#7222](https://github.com/actualbudget/actual/pull/7222) Mid-month dependency update — thanks @matt-fidd
- [#7223](https://github.com/actualbudget/actual/pull/7223) Bump react types — thanks @matt-fidd
- [#7224](https://github.com/actualbudget/actual/pull/7224) Bump react-spring — thanks @matt-fidd
- [#7227](https://github.com/actualbudget/actual/pull/7227) Migrate `get-next-package-version.js` to TypeScript — thanks @jfdoming
- [#7229](https://github.com/actualbudget/actual/pull/7229) Add Yarn constraints to ensure consistent dependency versions across all workspace packages. — thanks @MatissJanis
- [#7230](https://github.com/actualbudget/actual/pull/7230) Run `yarn install` when switching branches if `yarn.lock` changed — thanks @MatissJanis
- [#7232](https://github.com/actualbudget/actual/pull/7232) Remove development theme from all configurations and components across multiple packages. — thanks @MatissJanis
- [#7234](https://github.com/actualbudget/actual/pull/7234) Bump GitHub actions versions — thanks @matt-fidd
- [#7248](https://github.com/actualbudget/actual/pull/7248) Add post-merge hook to automatically install dependencies when yarn.lock changes after merges. — thanks @MatissJanis

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -88,7 +88,7 @@
"csv-parse": "^6.1.0",
"csv-stringify": "^6.6.0",
"date-fns": "^4.1.0",
"handlebars": "^4.7.9",
"handlebars": "^4.7.8",
"lru-cache": "^11.2.6",
"md5": "^2.3.0",
"memoize-one": "^6.0.0",

View File

@@ -68,7 +68,10 @@ function getAccountBalance(account) {
}
async function updateAccountBalance(id: AccountEntity['id'], balance: number) {
await db.update('accounts', { id, balance_current: balance });
db.runQuery('UPDATE accounts SET balance_current = ? WHERE id = ?', [
balance,
id,
]);
}
async function getAccountOldestTransaction(id): Promise<TransactionEntity> {

View File

@@ -1,6 +1,6 @@
{
"name": "@actual-app/sync-server",
"version": "26.3.0",
"version": "v26.4.0-pre",
"description": "actual syncing server",
"license": "MIT",
"bin": {
@@ -33,7 +33,7 @@
"@actual-app/web": "workspace:*",
"bcrypt": "^6.0.0",
"better-sqlite3": "^12.6.2",
"convict": "^6.2.5",
"convict": "^6.2.4",
"cors": "^2.8.6",
"date-fns": "^4.1.0",
"debug": "^4.4.3",

View File

@@ -1,6 +0,0 @@
---
category: Features
authors: [mannkind]
---
MVP for payee locations support, including YNAB5 import

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [sjones512]
---
Add the ability to use Formula Rules when setting the split amount.

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [erwannc]
---
Add notes to monthly budget cell

View File

@@ -1,6 +0,0 @@
---
category: Features
authors: [StephenBrown2]
---
Add drag&drop reordering for transactions within the same day

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [jfdoming]
---
Display/save refill templates

View File

@@ -1,6 +0,0 @@
---
category: Bugfixes
authors: [mnil]
---
When filtering for accounts in e.g. the net worth graph the modal closes the filter tooltip so it's impossible to add e.g. accounts to the filter.

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [MatissJanis]
---
Remove cyclic dependency between API and loot-core

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [diepala]
---
Add a new budgeted type to the custom report widget.

View File

@@ -1,6 +0,0 @@
---
category: Bugfixes
authors: [totallynotjon]
---
Import: added override option allowing users to control whether previously deleted (or merged) transactions are reimported.

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [J-LCRX]
---
Improve autocomplete sorting with tiered ranking for payee and category dropdowns

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [Juulz]
---
Change Titlebar 'Sync' to syncing icon only to alleviate confusion with Bank Sync. Add disabled state and aria disabled label when offline.

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [karimkodera]
---
Introduce concentric donut chart for categories in custom reports

View File

@@ -1,6 +0,0 @@
---
category: Bugfixes
authors: [LeviBorodenko]
---
[Mobile] Show running balance on upcoming transactions when respective setting is toggled

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [matt-fidd]
---
Bump ajv dependency

View File

@@ -1,6 +0,0 @@
---
category: Bugfixes
authors: [Juulz]
---
Make mobile account page colors more consistent

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [MatissJanis]
---
Remove reliance on the API package in YNAB importers

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [MatissJanis]
---
TypeScript: strengthen schedule type

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [MatissJanis]
---
TypeScript: move ImportTransactionsOpts from the api package to loot-core

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [MatissJanis]
---
Lint: add "--quiet" flag to stop reporting warnings

View File

@@ -1,6 +0,0 @@
---
category: Bugfixes
authors: [Triscal]
---
Fix skipping schedules that move before weekend

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [MatissJanis]
---
TypeScript: start using composite references

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [MikesGlitch]
---
Converted Electron backups to zip with metadata.json for easy importing

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [joel-jeremy]
---
Refactor account hooks to return full React Query states, enhancing data handling and component integration.

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [matt-fidd]
---
Bump bn.js dependency

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [jfdoming]
---
Move some sync-server utilities to TypeScript

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [jfdoming]
---
Move migrations CI script to typescript + ci-actions

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [sys044]
---
Add BUDGET_QUERY and QUERY_EXTRACT functions for formula-based budget analysis.

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [MatissJanis]
---
TypeScript: clean up some types

View File

@@ -1,6 +0,0 @@
---
category: Bugfixes
authors: [matt-fidd]
---
Stop font size fluctuations showing in summary cards

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [yhc0712]
---
Add New Taiwan Dollar (TWD) to the list of available currencies.

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [tifandotme]
---
Fix API reference documentation discrepancies

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [sylvercode]
---
Add an option to swap payee/memo when importing transaction from file.

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [MatissJanis]
---
Allow "unfreeze" workflow to work in fork PRs

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [MatissJanis]
---
Add reference to AGENTS.md in CLAUDE.md for improved documentation connectivity.

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [jfdoming]
---
Skip AI-generated release notes for PRs originating from `release/*` branches to avoid redundancy.

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [MatissJanis]
---
Reload server version when visibility to the page changes

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [MikesGlitch]
---
Automatically publish to Flathub when the GitHub release is published

View File

@@ -1,6 +0,0 @@
---
category: Bugfixes
authors: [mnil]
---
Fix budget analysis report padding when the value is large.

View File

@@ -1,6 +0,0 @@
---
category: Bugfixes
authors: [MatissJanis]
---
Refactor error handling to isolate errors per account in SimpleFin batch synchronization.

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [kivikakk]
---
Add "only import transactions since" to import dialog

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [dependabot]
---
Bump `express-rate-limit` dependency version from 8.2.1 to 8.3.0 for improvements.

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [MikesGlitch]
---
Adding more retries to the Docker test in the pipeline

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [MatissJanis]
---
Refactor TypeScript typings by moving window import to globals.ts for cleaner configuration.

View File

@@ -1,6 +0,0 @@
---
category: Enhancements
authors: [MatissJanis]
---
Sort theme catalog items alphabetically by name for improved user interface organization.

View File

@@ -1,6 +0,0 @@
---
category: Enhancement
authors: [MatissJanis]
---
Custom Themes: separate light and dark theme options when selecting "system default" theme.

View File

@@ -1,6 +0,0 @@
---
category: Bugfix
authors: [MikesGlitch]
---
Fix migrations retrieval when running the docker images

View File

@@ -1,6 +0,0 @@
---
category: Bugfixes
authors: [mibragimov]
---
Fix CSV import incorrectly parsing transaction amounts that contain trailing whitespace (e.g. amounts from Excel-saved CSV files).

Some files were not shown because too many files have changed in this diff Show More