Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
5e098f8
docs(mobile): prepare release metadata
DIYgod May 12, 2026
3b336b9
release(mobile): release v0.5.2
DIYgod May 12, 2026
27f44a2
Merge pull request #5005 from RSSNext/main
github-actions[bot] May 12, 2026
c1d8439
Merge pull request #5004 from RSSNext/release/mobile/0.5.2
DIYgod May 12, 2026
e3e15fa
Merge pull request #5006 from RSSNext/mobile-main
DIYgod May 13, 2026
a577dee
fix(ota): include Expo config in manifests
DIYgod May 13, 2026
be3d4c5
docs(mobile): prepare release metadata
DIYgod May 13, 2026
79d2684
release(mobile): release v0.5.3
DIYgod May 13, 2026
0d0fd9f
chore(mobile): keep release pr mobile-only
DIYgod May 13, 2026
77ad41b
Merge pull request #5008 from RSSNext/release/mobile/0.5.3
DIYgod May 13, 2026
541712c
Merge pull request #5009 from RSSNext/mobile-main
DIYgod May 13, 2026
d3c1b8b
build(deps): bump cloudflare/wrangler-action from 3 to 4 (#5007)
dependabot[bot] May 13, 2026
a481234
fix(desktop): improve Obsidian integration metadata
DIYgod May 14, 2026
ed1cc36
chore(release): update version to 1.8.0 in package.json and release.json
DIYgod May 14, 2026
9bfcfc8
fix(desktop): sanitize Obsidian file path separators
DIYgod May 14, 2026
444f8e0
fix(desktop): prevent duplicate timeline page requests
DIYgod May 14, 2026
431ab0e
fix(desktop): preserve native image context menu
DIYgod May 14, 2026
8557b9f
fix(desktop): scroll timeline to top before refresh
DIYgod May 21, 2026
27b30e0
fix(desktop): pause scroll read on timeline changes
DIYgod May 22, 2026
07cbf35
fix: batch scroll mark-read requests
DIYgod May 26, 2026
c6f9424
ci: serialize pnpm dependency installs
DIYgod May 26, 2026
2b0b412
fix(mobile): avoid duplicate shared links
DIYgod May 26, 2026
9bce34a
fix: preserve local read state after scroll sync
DIYgod May 26, 2026
a52ff66
fix(desktop): clear stale web context menu
DIYgod May 26, 2026
dc43e39
fix: retry scroll mark-read after revisiting entries
DIYgod May 26, 2026
89e7c13
fix: update unread count for time-limited mark-read
DIYgod May 29, 2026
6920287
fix(collection): paginate starred entries by collection time
DIYgod May 29, 2026
df8b8d0
fix(actions): stabilize rule editor state
DIYgod May 29, 2026
b28d7a0
fix(download): route direct downloads through ota
DIYgod May 29, 2026
d3b1e93
fix(release): always build mobile release apk
DIYgod May 29, 2026
bf4f91f
docs(desktop): prepare release inputs
DIYgod May 29, 2026
760d640
docs(mobile): prepare release metadata
DIYgod May 29, 2026
57741fe
release(desktop): release v1.9.0
DIYgod May 29, 2026
aaa9916
docs(desktop): restore mobile release inputs
DIYgod May 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/scripts/resolve-mobile-release-config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export function resolveMobileReleaseConfig(input) {
if (config.mode === "store") {
return {
triggerStoreBuilds: true,
triggerReleaseApk: true,
triggerOtaPublish: false,
runtimeVersion: null,
channel: null,
Expand All @@ -42,6 +43,7 @@ export function resolveMobileReleaseConfig(input) {

return {
triggerStoreBuilds: false,
triggerReleaseApk: true,
triggerOtaPublish: true,
runtimeVersion: config.runtimeVersion,
channel: config.channel,
Expand Down Expand Up @@ -72,6 +74,7 @@ async function main() {
})

setGitHubOutput("trigger_store_builds", String(result.triggerStoreBuilds))
setGitHubOutput("trigger_release_apk", String(result.triggerReleaseApk))
setGitHubOutput("trigger_ota_publish", String(result.triggerOtaPublish))
setGitHubOutput("runtime_version", result.runtimeVersion ?? "")
setGitHubOutput("channel", result.channel ?? "")
Expand Down
4 changes: 3 additions & 1 deletion .github/scripts/resolve-mobile-release-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ describe("resolveMobileReleaseConfig", () => {
}),
).toEqual({
triggerStoreBuilds: true,
triggerReleaseApk: true,
triggerOtaPublish: false,
runtimeVersion: null,
channel: null,
releaseVersion: "0.4.2",
})
})

it("triggers OTA publish only for ota mode", async () => {
it("triggers OTA publish and release APK builds for ota mode", async () => {
const { resolveMobileReleaseConfig } = await import("./resolve-mobile-release-config.mjs")

expect(
Expand All @@ -38,6 +39,7 @@ describe("resolveMobileReleaseConfig", () => {
}),
).toEqual({
triggerStoreBuilds: false,
triggerReleaseApk: true,
triggerOtaPublish: true,
runtimeVersion: "0.4.1",
channel: "production",
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ jobs:
token: ${{ secrets.EXPO_TOKEN }}

- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile --child-concurrency=1

- name: Build mobile web assets
run: pnpm --dir apps/mobile/web-app build --outDir ../../../out/rn-web/html-renderer
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/build-ios-development.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
token: ${{ secrets.EXPO_TOKEN }}

- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile --child-concurrency=1

- name: Build mobile web assets
run: pnpm --dir apps/mobile/web-app build --outDir ../../../out/rn-web/html-renderer
Expand Down Expand Up @@ -106,7 +106,7 @@ jobs:
token: ${{ secrets.EXPO_TOKEN }}

- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile --child-concurrency=1

- name: Build mobile web assets
run: pnpm --dir apps/mobile/web-app build --outDir ../../../out/rn-web/html-renderer
Expand Down Expand Up @@ -157,7 +157,7 @@ jobs:
token: ${{ secrets.EXPO_TOKEN }}

- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile --child-concurrency=1

- name: Build mobile web assets
run: pnpm --dir apps/mobile/web-app build --outDir ../../../out/rn-web/html-renderer
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/build-ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:
token: ${{ secrets.EXPO_TOKEN }}

- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile --child-concurrency=1

- name: Build mobile web assets
run: pnpm --dir apps/mobile/web-app build --outDir ../../../out/rn-web/html-renderer
Expand Down Expand Up @@ -119,7 +119,7 @@ jobs:
token: ${{ secrets.EXPO_TOKEN }}

- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile --child-concurrency=1

- name: Build mobile web assets
run: pnpm --dir apps/mobile/web-app build --outDir ../../../out/rn-web/html-renderer
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-web.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
node-version: ${{ matrix.node-version }}
cache: "pnpm"
- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile --child-concurrency=1
- name: Build web and SSR server
run: |
npm exec turbo run Folo#build:web @follow/ssr#build
6 changes: 3 additions & 3 deletions .github/workflows/deploy-cloudflare-desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
cache: "pnpm"

- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile --child-concurrency=1

- name: Build desktop web (SPA)
working-directory: apps/desktop
Expand All @@ -52,7 +52,7 @@ jobs:

- name: Deploy desktop web to Cloudflare (dev)
if: github.ref == 'refs/heads/dev'
uses: cloudflare/wrangler-action@v3
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
Expand All @@ -61,7 +61,7 @@ jobs:

- name: Deploy desktop web to Cloudflare (prod)
if: github.ref == 'refs/heads/main'
uses: cloudflare/wrangler-action@v3
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/deploy-cloudflare-landing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
cache: "pnpm"

- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile --child-concurrency=1

- name: Resolve deployment target
id: target
Expand All @@ -72,7 +72,7 @@ jobs:

- name: Deploy Landing to Cloudflare (dev)
if: steps.target.outputs.environment == 'dev'
uses: cloudflare/wrangler-action@v3
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
Expand All @@ -81,7 +81,7 @@ jobs:

- name: Deploy Landing to Cloudflare (prod)
if: steps.target.outputs.environment == 'prod'
uses: cloudflare/wrangler-action@v3
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/deploy-cloudflare-ssr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
cache: "pnpm"

- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile --child-concurrency=1

- name: Resolve deployment target
id: target
Expand Down Expand Up @@ -92,7 +92,7 @@ jobs:

- name: Deploy SSR Worker to Cloudflare (dev)
if: steps.target.outputs.environment == 'dev'
uses: cloudflare/wrangler-action@v3
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
Expand All @@ -101,7 +101,7 @@ jobs:

- name: Deploy SSR Worker to Cloudflare (prod)
if: steps.target.outputs.environment == 'prod'
uses: cloudflare/wrangler-action@v3
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
node-version: ${{ matrix.node-version }}
cache: "pnpm"
- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile --child-concurrency=1
- name: Build web and SSR server
run: |
npm exec turbo run Folo#build:web @follow/ssr#build
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-ota.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
cache: "pnpm"

- name: Install dependencies
run: pnpm install --frozen-lockfile
run: pnpm install --frozen-lockfile --child-concurrency=1

- name: Export OTA bundle assets
working-directory: apps/mobile
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ jobs:
console.log('Desktop store build triggered successfully');

- name: Trigger Mobile Production APK Release Build
if: needs.create_tag.outputs.platform == 'mobile' && needs.create_tag.outputs.ref_name == 'mobile-main' && steps.release_mode.outputs.trigger_store_builds == 'true'
if: needs.create_tag.outputs.platform == 'mobile' && needs.create_tag.outputs.ref_name == 'mobile-main' && steps.release_mode.outputs.trigger_release_apk == 'true'
uses: actions/github-script@v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
19 changes: 19 additions & 0 deletions apps/desktop/changelog/1.9.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# What's new in v1.9.0

## Improvements

- Upgraded AI summaries to a more powerful Kimi model
- Improved Obsidian integration metadata and markdown handling
- Improved scroll mark-read reliability with batched updates, retry handling, and local read-state preservation
- Improved action rule editor state stability
- Routed direct desktop downloads through OTA-backed delivery

## No longer broken

- Fixed stale desktop context menus after web menu changes
- Fixed native image context menus being replaced unexpectedly
- Fixed duplicate timeline page requests
- Fixed timeline refreshes not returning to the top first
- Fixed starred collection pagination order
- Fixed Obsidian file path separator handling
- Fixed unread counts after time-limited mark-read actions
74 changes: 74 additions & 0 deletions apps/desktop/layer/main/src/ipc/services/integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import fsp from "node:fs/promises"
import os from "node:os"

import type { IpcContext } from "electron-ipc-decorator"
import path from "pathe"
import { afterEach, describe, expect, it, vi } from "vitest"

import { IntegrationService } from "./integration"

vi.mock("electron", () => ({
ipcMain: {
handle: vi.fn(),
},
shell: {
openExternal: vi.fn(),
},
}))

vi.mock("electron-ipc-decorator", () => ({
IpcMethod: () => (_target: unknown, _propertyKey: string, descriptor: PropertyDescriptor) =>
descriptor,
IpcService: class {},
}))

vi.mock("~/lib/store", () => ({
store: {
get: vi.fn(),
set: vi.fn(),
},
}))

vi.mock("~/logger", () => ({
logger: {
debug: vi.fn(),
error: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
},
}))

describe("IntegrationService", () => {
let vaultPath: string | undefined

afterEach(async () => {
if (!vaultPath) return

await fsp.rm(vaultPath, { force: true, recursive: true })
vaultPath = undefined
})

it("saves Obsidian titles with path separators as one markdown file", async () => {
vaultPath = await fsp.mkdtemp(path.join(os.tmpdir(), "folo-obsidian-"))
const service = new IntegrationService()
const context = {} as IpcContext

await expect(
service.saveToObsidian(context, {
url: "https://example.com",
title: "KAWA DESIGN 少女前线2:追放 索米·雪兔献礼 1/6比例手办",
content: "content",
author: "Folo",
publishedAt: "2026-05-14T04:20:44.405Z",
vaultPath,
}),
).resolves.toEqual({ success: true })

await expect(fsp.readdir(vaultPath)).resolves.toEqual([
"KAWA DESIGN 少女前线2:追放 索米·雪兔献礼 1_6比例手办.md",
])
await expect(
fsp.stat(path.join(vaultPath, "KAWA DESIGN 少女前线2:追放 索米·雪兔献礼 1")),
).rejects.toThrow()
})
})
Loading
Loading