Compare commits

..

3 Commits

Author SHA1 Message Date
Claude
320d66444a [AI] Drop verbose comment on SimpleFin sync dispatch
https://claude.ai/code/session_01DNkRSgqW5JEtYpZjxvj7Bi
2026-05-09 20:07:51 +00:00
Claude
17198863a4 [AI] Update release notes filename and author
https://claude.ai/code/session_01DNkRSgqW5JEtYpZjxvj7Bi
2026-05-09 20:04:41 +00:00
Claude
a9f8ae0e21 [AI] Update mobile bank sync indicators live during sync
Mobile's account list uses react-aria-components ListBox with the
items render-function pattern, which memoizes rows by item identity.
Without a dependencies prop, changes to syncingAccountIds,
failedAccounts, and updatedAccounts in Redux didn't cause the
per-account dots to re-render until the items array itself changed,
so the green/yellow/red indicators only updated after the full sync
finished.

Pass these Redux selections via the dependencies prop so the rows
re-render as state changes during sync. Also clear SimpleFin
accounts from accountsSyncing right after the batch call returns,
so their indicators reflect completion before the per-account loop
starts on the remaining accounts.

https://claude.ai/code/session_01DNkRSgqW5JEtYpZjxvj7Bi
2026-05-09 18:04:22 +00:00
14 changed files with 75 additions and 222 deletions

View File

@@ -117,7 +117,49 @@ jobs:
!packages/desktop-electron/dist/Actual-windows.exe
packages/desktop-electron/dist/*.AppImage
packages/desktop-electron/dist/*.flatpak
packages/desktop-electron/dist/*.appx
outputs:
version: ${{ steps.process_version.outputs.version }}
publish-microsoft-store:
needs: build
runs-on: windows-latest
environment: release
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
steps:
- name: Install StoreBroker
shell: powershell
run: |
Install-Module -Name StoreBroker -AcceptLicense -Force -Scope CurrentUser -Verbose
- name: Download Microsoft Store artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: actual-electron-windows-latest-appx
- name: Submit to Microsoft Store
shell: powershell
run: |
# Disable telemetry
$global:SBDisableTelemetry = $true
# Authenticate against the store
$pass = ConvertTo-SecureString -String '${{ secrets.MICROSOFT_STORE_CLIENT_SECRET }}' -AsPlainText -Force
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ${{ secrets.MICROSOFT_STORE_CLIENT_ID }},$pass
Set-StoreBrokerAuthentication -TenantId '${{ secrets.MICROSOFT_STORE_TENANT_ID }}' -Credential $cred
# Zip and create metadata files
$artifacts = Get-ChildItem -Path . -Filter *.appx | Select-Object -ExpandProperty FullName
New-StoreBrokerConfigFile -Path "$PWD/config.json" -AppId ${{ secrets.MICROSOFT_STORE_PRODUCT_ID }}
New-SubmissionPackage -ConfigPath "$PWD/config.json" -DisableAutoPackageNameFormatting -AppxPath $artifacts -OutPath "$PWD" -OutName submission
# Submit the app
# See https://github.com/microsoft/StoreBroker/blob/master/Documentation/USAGE.md#the-easy-way
Update-ApplicationSubmission `
-AppId ${{ secrets.MICROSOFT_STORE_PRODUCT_ID }} `
-SubmissionDataPath "submission.json" `
-PackagePath "submission.zip" `
-ReplacePackages `
-NoStatus `
-AutoCommit `
-Force

View File

@@ -1,113 +0,0 @@
name: Publish Microsoft Store
defaults:
run:
shell: bash
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: 'Release tag (e.g. v25.3.0)'
required: true
type: string
concurrency:
group: publish-microsoft-store
cancel-in-progress: false
jobs:
publish-microsoft-store:
runs-on: windows-latest
environment: release
steps:
- name: Resolve version
id: resolve_version
env:
EVENT_NAME: ${{ github.event_name }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
INPUT_TAG: ${{ inputs.tag }}
run: |
if [[ "$EVENT_NAME" == "release" ]]; then
TAG="$RELEASE_TAG"
else
TAG="$INPUT_TAG"
fi
if [[ -z "$TAG" ]]; then
echo "::error::No tag provided"
exit 1
fi
# Validate tag format (v-prefixed semver, e.g. v25.3.0 or v1.2.3-beta.1)
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
echo "::error::Invalid tag format: $TAG (expected v-prefixed semver, e.g. v25.3.0)"
exit 1
fi
VERSION="${TAG#v}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Resolved tag=$TAG version=$VERSION"
- name: Verify release assets exist
env:
GH_TOKEN: ${{ github.token }}
STEPS_RESOLVE_VERSION_OUTPUTS_TAG: ${{ steps.resolve_version.outputs.tag }}
run: |
TAG="${STEPS_RESOLVE_VERSION_OUTPUTS_TAG}"
echo "Checking release assets for tag $TAG..."
ASSETS=$(gh api "repos/${{ github.repository }}/releases/tags/$TAG" --jq '.assets[].name')
echo "Found assets:"
echo "$ASSETS"
if ! echo "$ASSETS" | grep -q "\.appx$"; then
echo "::error::No .appx assets found in release $TAG"
exit 1
fi
echo "Required .appx assets found."
- name: Download Microsoft Store artifacts
env:
GH_TOKEN: ${{ github.token }}
STEPS_RESOLVE_VERSION_OUTPUTS_TAG: ${{ steps.resolve_version.outputs.tag }}
run: |
TAG="${STEPS_RESOLVE_VERSION_OUTPUTS_TAG}"
gh release download "$TAG" --repo "${{ github.repository }}" --pattern "*.appx"
- name: Install StoreBroker
shell: powershell
run: |
Install-Module -Name StoreBroker -AcceptLicense -Force -Scope CurrentUser -Verbose
- name: Submit to Microsoft Store
shell: powershell
run: |
# Disable telemetry
$global:SBDisableTelemetry = $true
# Authenticate against the store
$pass = ConvertTo-SecureString -String '${{ secrets.MICROSOFT_STORE_CLIENT_SECRET }}' -AsPlainText -Force
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ${{ secrets.MICROSOFT_STORE_CLIENT_ID }},$pass
Set-StoreBrokerAuthentication -TenantId '${{ secrets.MICROSOFT_STORE_TENANT_ID }}' -Credential $cred
# Zip and create metadata files
$artifacts = Get-ChildItem -Path . -Filter *.appx | Select-Object -ExpandProperty FullName
New-StoreBrokerConfigFile -Path "$PWD/config.json" -AppId ${{ secrets.MICROSOFT_STORE_PRODUCT_ID }}
New-SubmissionPackage -ConfigPath "$PWD/config.json" -DisableAutoPackageNameFormatting -AppxPath $artifacts -OutPath "$PWD" -OutName submission
# Submit the app
# See https://github.com/microsoft/StoreBroker/blob/master/Documentation/USAGE.md#the-easy-way
Update-ApplicationSubmission `
-AppId ${{ secrets.MICROSOFT_STORE_PRODUCT_ID }} `
-SubmissionDataPath "submission.json" `
-PackagePath "submission.zip" `
-ReplacePackages `
-NoStatus `
-AutoCommit `
-Force

View File

@@ -82,17 +82,16 @@ jobs:
with:
download-translations: 'false'
- name: Build browser bundle
# REACT_APP_NETLIFY=true flips isNonProductionEnvironment() on in the
# bundle so the "Create test file" button (used by every e2e beforeEach
# via ConfigurationPage.createTestFile()) is still rendered in a
# production build. Without it, e2e tests would time out waiting for
# a button that was tree-shaken out.
# --skip-translations keeps VRT screenshots deterministic by rendering
# source-code English instead of upstream Weblate en.json (which can
# drift between snapshot capture and test runs).
# REACT_APP_NETLIFY=true keeps the "Create test file" button in the
# production bundle — every VRT test's beforeEach relies on it via
# ConfigurationPage.createTestFile().
env:
REACT_APP_NETLIFY: 'true'
run: yarn build:browser --skip-translations
run: |
yarn workspace plugins-service build
yarn workspace @actual-app/crdt build
yarn workspace @actual-app/core build:browser
yarn workspace @actual-app/web build:browser
- name: Upload build artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
@@ -258,7 +257,6 @@ jobs:
- name: Merge shard patches
id: create-patch
shell: bash
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
git config --global user.name "github-actions[bot]"

View File

@@ -590,6 +590,8 @@ export function useSyncAccountsMutation() {
accountIdsToSync = accountIdsToSync.filter(
id => !simpleFinAccounts.find(sfa => sfa.id === id),
);
dispatch(setAccountsSyncing({ ids: accountIdsToSync }));
}
// Loop through the accounts and perform sync operation.. one by one

View File

@@ -466,6 +466,7 @@ const AccountList = forwardRef<HTMLDivElement, AccountListProps>(
<ListBox
aria-label={ariaLabel}
items={accounts}
dependencies={[syncingAccountIds, failedAccounts, updatedAccounts]}
dragAndDropHooks={dragAndDropHooks}
ref={ref}
style={{

View File

@@ -190,11 +190,9 @@ export const FocusableAmountInput = memo(function FocusableAmountInput({
buttonProps,
onFocus,
onBlur,
onChangeValue,
...props
}: FocusableAmountInputProps) {
const [isNegative, setIsNegative] = useState(true);
const [liveValue, setLiveValue] = useState(Math.abs(value));
const maybeApplyNegative = (amount: number, negative: boolean) => {
const absValue = Math.abs(amount);
@@ -205,15 +203,6 @@ export const FocusableAmountInput = memo(function FocusableAmountInput({
props.onUpdateAmount?.(maybeApplyNegative(amount, negative));
};
const handleChangeValue = (text: string) => {
setLiveValue(currencyToAmount(text) || 0);
onChangeValue?.(text);
};
useEffect(() => {
setLiveValue(Math.abs(value));
}, [value]);
useEffect(() => {
if (sign) {
setIsNegative(sign === '-');
@@ -238,11 +227,10 @@ export const FocusableAmountInput = memo(function FocusableAmountInput({
value={value}
onFocus={onFocus}
onBlur={onBlur}
onChangeValue={handleChangeValue}
onUpdateAmount={amount => onUpdateAmount(amount, isNegative)}
focused={focused && !disabled}
style={{
...makeAmountFullStyle(maybeApplyNegative(liveValue, isNegative), {
...makeAmountFullStyle(value, {
zeroColor: isNegative ? theme.numberNegative : theme.numberNeutral,
positiveColor: theme.numberPositive,
negativeColor: theme.numberNegative,

View File

@@ -177,12 +177,15 @@ app.use('/', async (req, res) => {
}
try {
const { method = 'GET', headers: customHeaders = {} } = req.body || {};
// Extract method, body, and headers from the request body (sent by loot-core)
const {
method = 'GET',
body,
headers: customHeaders = {},
} = req.body || {};
if (typeof method !== 'string') {
return res.status(400).json({ error: 'Invalid method parameter' });
}
const methodNormalized = method.toUpperCase();
const methodNormalized =
typeof method === 'string' ? method.toUpperCase() : 'GET';
if (!['GET', 'HEAD'].includes(methodNormalized)) {
return res.status(405).json({ error: 'Method not allowed' });
}
@@ -215,8 +218,13 @@ app.use('/', async (req, res) => {
}
const response = await fetch(url.href, {
method: methodNormalized,
method,
headers: requestHeaders,
body: ['GET', 'HEAD'].includes(method)
? undefined
: typeof body === 'string'
? body
: JSON.stringify(body),
});
const contentType =

View File

@@ -414,55 +414,6 @@ describe('app-cors-proxy', () => {
expect(res.statusCode).toBe(405);
expect(res.body.error).toBe('Method not allowed');
});
it('should reject non-string method (array bypass)', async () => {
global.fetch.mockClear();
const res = await request(app)
.get('/')
.send({ method: ['POST'], body: { evil: true } })
.query({ url: 'https://api.github.com/repos/user/repo1' });
expect(res.statusCode).toBe(400);
expect(res.body.error).toBe('Invalid method parameter');
const proxyCalls = global.fetch.mock.calls.filter(
([url]) => url === 'https://api.github.com/repos/user/repo1',
);
expect(proxyCalls).toHaveLength(0);
});
it('should reject non-string method (object bypass)', async () => {
global.fetch.mockClear();
const res = await request(app)
.get('/')
.send({ method: { toString: () => 'POST' } })
.query({ url: 'https://github.com/user/repo1' });
expect(res.statusCode).toBe(400);
expect(res.body.error).toBe('Invalid method parameter');
const proxyCalls = global.fetch.mock.calls.filter(
([url]) => url === 'https://github.com/user/repo1',
);
expect(proxyCalls).toHaveLength(0);
});
it('should forward the validated method to fetch, not the raw input', async () => {
global.fetch.mockClear();
await request(app)
.get('/')
.send({ method: 'get' })
.query({ url: 'https://github.com/user/repo1' });
const proxyCall = global.fetch.mock.calls.find(
([url]) => url === 'https://github.com/user/repo1',
);
expect(proxyCall).toBeDefined();
expect(proxyCall[1].method).toBe('GET');
});
});
describe('GitHub authentication', () => {

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [MikesGlitch]
---
Alter desktop app publish workflow to publish to Microsoft store after release is published

View File

@@ -1,6 +0,0 @@
---
category: Bugfix
authors: [MatissJanis]
---
Mobile: add live value tracking for user input in mobile transactions.

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [MatissJanis]
---
Fix update-vrt workflow

View File

@@ -1,6 +0,0 @@
---
category: Maintenance
authors: [MatissJanis]
---
Fix /update-vrt merge step failing on Playwright container with `shopt: not found`

View File

@@ -0,0 +1,6 @@
---
category: Bugfixes
authors: [MatissJanis]
---
Fix mobile bank sync indicators not updating live during sync.

View File

@@ -1,6 +0,0 @@
---
category: Bugfixes
authors: [MatissJanis]
---
Fix an issue where the CORS proxy could be bypassed.