Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
60e2dd3
release(desktop): release v1.4.0
DIYgod Mar 12, 2026
94f9087
fix(desktop): restore renderer api requests
DIYgod Mar 12, 2026
96b6eac
fix(desktop): hide cli settings tab for release
DIYgod Mar 12, 2026
ff1cf29
docs(release): remove cli notes from desktop changelog
DIYgod Mar 12, 2026
36f733b
Merge pull request #4916 from RSSNext/release/desktop/1.4.0
DIYgod Mar 12, 2026
45a9f7e
chore(sync): merge main into dev
DIYgod Mar 12, 2026
82b5791
chore(sync): merge mobile-main into dev
DIYgod Mar 12, 2026
6593d79
Merge pull request #4917 from RSSNext/codex/sync-release-to-dev-20260312
DIYgod Mar 12, 2026
df83cf6
fix(mobile): polish auth and settings ux
DIYgod Mar 13, 2026
aea75fe
fix(desktop): improve self-test coverage and ui polish
DIYgod Mar 13, 2026
a2a88d9
fix(mobile): improve ipad layouts and detail rendering
DIYgod Mar 13, 2026
b55e2f0
fix(electron): persist theme settings (#4900)
Eumenides-K Mar 16, 2026
3970490
fix: upgrade better-auth client compatibility
DIYgod Mar 16, 2026
3f31f50
fix: restore frontend ci for better-auth upgrade
DIYgod Mar 16, 2026
bd0d081
fix(mobile): refresh timeline after login
DIYgod Mar 17, 2026
9a8280d
feat(cli): switch desktop sync to folocli
DIYgod Mar 17, 2026
fa7ce0f
fix(cli): use npx latest and restore login flow
DIYgod Mar 17, 2026
4cc68d3
feat(landing): refresh landing, pricing, and deploy flow (#4921)
DIYgod Mar 17, 2026
312ec96
feat(landing): add audience tabs to hero
DIYgod Mar 17, 2026
b5ab3e0
feat(cloudflare): split desktop and ssr workers
DIYgod Mar 17, 2026
ef1c755
fix(cloudflare): deploy ssr before desktop routes
DIYgod Mar 17, 2026
338164b
fix(web): remove openpanel and fix ssr manifest
DIYgod Mar 17, 2026
4f4618d
fix(ssr): read injected env in browser
DIYgod Mar 17, 2026
ecf1ca5
ci: split Cloudflare deploy workflows
DIYgod Mar 17, 2026
4165611
feat(ai): enable French AI language requests
DIYgod Mar 20, 2026
5c86658
build(deps): bump nick-fields/retry from 3 to 4 (#4925)
dependabot[bot] Mar 21, 2026
507f401
feat(landing): add researchers trust band
DIYgod Mar 24, 2026
644dea8
chore(landing): update react to 19.2.4
DIYgod Mar 24, 2026
0a9c7d1
chore: update pnpm-lock
DIYgod Mar 24, 2026
2c1c978
fix(desktop): add auth origin headers for electron
DIYgod Mar 24, 2026
99081f8
fix(desktop): support two-factor auth in electron
DIYgod Mar 24, 2026
14c7729
fix(desktop): keep ai chat input within viewport
DIYgod Mar 24, 2026
032b66b
docs(skill): use AXe for iOS self-test interaction
DIYgod Mar 24, 2026
a9eaae8
fix(desktop): export pdf via electron ipc (#4943)
DIYgod Apr 2, 2026
22e0e90
fix(cli): verify browser login token (#4944)
DIYgod Apr 2, 2026
44614f7
fix(mobile): show stripe subscriptions on android (#4945)
DIYgod Apr 2, 2026
ce5e919
fix: pause scroll mark read after refresh (#4946)
DIYgod Apr 2, 2026
d27261d
fix(mobile): simplify iOS subscription management (#4947)
DIYgod Apr 2, 2026
03ccfa8
fix: keep hovered unread entries visible (#4948)
DIYgod Apr 2, 2026
1571396
fix: upgrade sdk packages and align app types (#4950)
DIYgod Apr 2, 2026
1c89c84
fix(mobile): add expo-network for EAS builds (#4951)
DIYgod Apr 2, 2026
714b4b5
docs(mobile): prepare release changelog
DIYgod Apr 2, 2026
cae21c1
release(mobile): release v0.4.1
DIYgod Apr 2, 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
10 changes: 8 additions & 2 deletions .agents/skills/mobile-self-test/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ This skill extends `../mobile-e2e/SKILL.md`. Read that skill first for the basel
- iOS register flow: `apps/mobile/e2e/flows/ios/register.yaml`
- Android register flow: `apps/mobile/e2e/flows/android/register.yaml`
- Shared auth flows: `apps/mobile/e2e/flows/shared/*.yaml`
- iOS interaction skill: `/Users/diygod/.agents/skills/axe/SKILL.md`
- Expo config: `apps/mobile/app.config.ts`
- Build profiles: `apps/mobile/eas.json`
- Mobile artifacts: `apps/mobile/e2e/artifacts/`
Expand Down Expand Up @@ -185,6 +186,10 @@ xcrun simctl install "$IOS_UDID" <PATH_TO_Folo.app>
xcrun simctl launch "$IOS_UDID" is.follow
```

### iOS interaction after launch

After the app is running on the dedicated iOS simulator, use the `$axe` skill for simulator interaction during visual validation. Use `$axe` as the default interaction layer for iOS screenshot-driven checks.

## Android workflow

Reuse the Java and Android SDK setup from `../mobile-e2e/SKILL.md`.
Expand Down Expand Up @@ -364,7 +369,7 @@ For auth-related work:

## Screenshot-driven visual testing

Once the app is in the right state, drive the rest of the validation with the visual toolchain available in the current environment. Screenshots are the source of truth for acceptance.
Once the app is in the right state, drive the rest of the validation visually. On iOS, use the `$axe` skill as the default interaction tool for this phase. On Android, use the interaction tooling available in the current environment. Screenshots are the source of truth for acceptance.

Create a timestamped artifact folder first:

Expand Down Expand Up @@ -430,4 +435,5 @@ If the client supports local image rendering, attach the key screenshots as imag

- If doctor, typecheck, build, install, or server startup fails, stop and report the exact failing command.
- If `local` mode cannot reach the local server, do not silently fall back to `prod`.
- If the visual flow cannot be completed because the environment lacks the required interaction tooling, report that limitation clearly and still return the screenshots you captured.
- If the iOS visual flow cannot be completed because `$axe` is unavailable or unusable, report that limitation clearly and still return the screenshots you captured.
- If the Android visual flow cannot be completed because the environment lacks suitable interaction tooling, report that limitation clearly and still return the screenshots you captured.
4 changes: 1 addition & 3 deletions .github/workflows/build-desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ env:
VITE_WEB_URL: ${{ vars.VITE_WEB_URL }}
VITE_API_URL: ${{ vars.VITE_API_URL }}
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
VITE_OPENPANEL_CLIENT_ID: ${{ vars.VITE_OPENPANEL_CLIENT_ID }}
VITE_OPENPANEL_API_URL: ${{ vars.VITE_OPENPANEL_API_URL }}
VITE_FIREBASE_CONFIG: ${{ vars.VITE_FIREBASE_CONFIG }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
NODE_OPTIONS: --max-old-space-size=8192
Expand Down Expand Up @@ -153,7 +151,7 @@ jobs:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
OSX_SIGN_KEYCHAIN_PATH: ${{ runner.temp }}/app-signing.keychain-db
OSX_SIGN_IDENTITY: ${{ secrets.OSX_SIGN_IDENTITY }}
uses: nick-fields/retry@v3
uses: nick-fields/retry@v4
with:
max_attempts: 3
timeout_minutes: 10
Expand Down
69 changes: 69 additions & 0 deletions .github/workflows/deploy-cloudflare-desktop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: "\u2601\ufe0f Deploy Desktop to Cloudflare"

on:
push:
branches: [main, dev]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
deploy:
name: Build & Deploy Desktop Web
runs-on: ubuntu-latest
env:
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
VITE_FIREBASE_CONFIG: ${{ vars.VITE_FIREBASE_CONFIG }}
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
lfs: true

- name: Checkout LFS objects
run: git lfs checkout

- name: Cache turbo build setup
uses: actions/cache@v5
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-

- uses: pnpm/action-setup@v4

- name: Use Node.js LTS
uses: actions/setup-node@v6
with:
node-version: lts/*
cache: "pnpm"

- name: Install dependencies
run: pnpm install

- name: Build desktop web (SPA)
working-directory: apps/desktop
env:
VITE_WEB_URL: ${{ github.ref == 'refs/heads/dev' && 'https://dev.folo.is' || 'https://app.folo.is' }}
VITE_API_URL: ${{ github.ref == 'refs/heads/dev' && 'https://api.dev.folo.is' || 'https://api.folo.is' }}
run: pnpm run build:web

- name: Deploy desktop web to Cloudflare (dev)
if: github.ref == 'refs/heads/dev'
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: apps/desktop
command: deploy --env dev

- name: Deploy desktop web to Cloudflare (prod)
if: github.ref == 'refs/heads/main'
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: apps/desktop
command: 'deploy --env=""'
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
name: "\u2601\ufe0f Deploy Landing to Cloudflare"

on:
push:
branches: [main, dev]
workflow_dispatch:
inputs:
confirm_production_deploy:
description: Deploy the selected branch to production (folo.is)
required: true
default: false
type: boolean

name: ☁️ Deploy to Cloudflare Workers
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
group: ${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && format('manual-prod-{0}', github.ref) || github.ref }}
cancel-in-progress: true

jobs:
deploy:
name: Build & Deploy
name: Build & Deploy Landing Worker
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [lts/*]
steps:
- name: Checkout code
uses: actions/checkout@v6
Expand All @@ -32,57 +38,49 @@ jobs:

- uses: pnpm/action-setup@v4

- name: Use Node.js ${{ matrix.node-version }}
- name: Use Node.js LTS
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
node-version: lts/*
cache: "pnpm"

- name: Install dependencies
run: pnpm install

- name: Build desktop web (SPA)
run: pnpm exec turbo run Folo#build:web
- name: Resolve deployment target
id: target
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
if [[ "${{ inputs.confirm_production_deploy }}" != "true" ]]; then
echo "Manual production deploy was not confirmed." >&2
exit 1
fi

- name: Build SSR Worker
working-directory: apps/ssr
run: pnpm run build:worker
echo "environment=prod" >> "$GITHUB_OUTPUT"
exit 0
fi

- name: Copy WASM file
run: cp node_modules/@resvg/resvg-wasm/index_bg.wasm apps/ssr/dist/worker/resvg.wasm
if [[ "${{ github.ref }}" == "refs/heads/dev" ]]; then
echo "environment=dev" >> "$GITHUB_OUTPUT"
exit 0
fi

echo "environment=prod" >> "$GITHUB_OUTPUT"

- name: Build Landing Worker
run: pnpm exec turbo run @follow/landing#cf:build

- name: Deploy to Cloudflare (dev)
if: github.ref == 'refs/heads/dev'
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: apps/ssr
command: deploy --env dev

- name: Deploy Landing to Cloudflare (dev)
if: github.ref == 'refs/heads/dev'
if: steps.target.outputs.environment == 'dev'
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: apps/landing
command: deploy --env dev --name landing-next-dev --routes landing.dev.folo.is/*

- name: Deploy to Cloudflare (prod)
if: github.ref == 'refs/heads/main'
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: apps/ssr
command: deploy

- name: Deploy Landing to Cloudflare (prod)
if: github.ref == 'refs/heads/main'
if: steps.target.outputs.environment == 'prod'
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
Expand Down
109 changes: 109 additions & 0 deletions .github/workflows/deploy-cloudflare-ssr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
name: "\u2601\ufe0f Deploy SSR to Cloudflare"

on:
push:
branches: [main, dev]
workflow_dispatch:
inputs:
confirm_production_deploy:
description: Deploy the selected branch to production (app.folo.is)
required: true
default: false
type: boolean

concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && format('manual-prod-{0}', github.ref) || github.ref }}
cancel-in-progress: true

jobs:
deploy:
name: Build & Deploy SSR Worker
runs-on: ubuntu-latest
env:
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
VITE_FIREBASE_CONFIG: ${{ vars.VITE_FIREBASE_CONFIG }}
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
lfs: true

- name: Checkout LFS objects
run: git lfs checkout

- name: Cache turbo build setup
uses: actions/cache@v5
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-

- uses: pnpm/action-setup@v4

- name: Use Node.js LTS
uses: actions/setup-node@v6
with:
node-version: lts/*
cache: "pnpm"

- name: Install dependencies
run: pnpm install

- name: Resolve deployment target
id: target
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
if [[ "${{ inputs.confirm_production_deploy }}" != "true" ]]; then
echo "Manual production deploy was not confirmed." >&2
exit 1
fi

echo "environment=prod" >> "$GITHUB_OUTPUT"
echo "vite_web_url=https://app.folo.is" >> "$GITHUB_OUTPUT"
echo "vite_api_url=https://api.folo.is" >> "$GITHUB_OUTPUT"
exit 0
fi

if [[ "${{ github.ref }}" == "refs/heads/dev" ]]; then
echo "environment=dev" >> "$GITHUB_OUTPUT"
echo "vite_web_url=https://dev.folo.is" >> "$GITHUB_OUTPUT"
echo "vite_api_url=https://api.dev.folo.is" >> "$GITHUB_OUTPUT"
exit 0
fi

echo "environment=prod" >> "$GITHUB_OUTPUT"
echo "vite_web_url=https://app.folo.is" >> "$GITHUB_OUTPUT"
echo "vite_api_url=https://api.folo.is" >> "$GITHUB_OUTPUT"

- name: Build desktop web (SSR assets)
working-directory: apps/desktop
env:
VITE_WEB_URL: ${{ steps.target.outputs.vite_web_url }}
VITE_API_URL: ${{ steps.target.outputs.vite_api_url }}
run: pnpm run build:web

- name: Build SSR Worker
working-directory: apps/ssr
run: pnpm run build:worker

- name: Copy WASM file
run: cp node_modules/@resvg/resvg-wasm/index_bg.wasm apps/ssr/dist/worker/resvg.wasm

- name: Deploy SSR Worker to Cloudflare (dev)
if: steps.target.outputs.environment == 'dev'
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: apps/ssr
command: deploy --env dev

- name: Deploy SSR Worker to Cloudflare (prod)
if: steps.target.outputs.environment == 'prod'
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: apps/ssr
command: 'deploy --env=""'
22 changes: 22 additions & 0 deletions .prettierrc.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/** @type {import("prettier").Config & import("prettier-plugin-tailwindcss").PluginOptions} */
export default {
semi: false,
singleQuote: false,
Expand All @@ -6,4 +7,25 @@ export default {
trailingComma: "all",
objectWrap: "preserve",
plugins: ["prettier-plugin-tailwindcss"],
tailwindConfig: "./apps/desktop/tailwind.config.ts",
overrides: [
{
files: "apps/mobile/**/*.{css,js,jsx,ts,tsx}",
options: {
tailwindConfig: "./apps/mobile/tailwind.config.ts",
},
},
{
files: "apps/mobile/web-app/html-renderer/**/*.{css,js,jsx,ts,tsx}",
options: {
tailwindConfig: "./apps/mobile/web-app/html-renderer/tailwind.config.ts",
},
},
{
files: "apps/ssr/**/*.{css,html,js,jsx,ts,tsx}",
options: {
tailwindConfig: "./apps/ssr/tailwind.config.ts",
},
},
],
}
Loading
Loading