Skip to content

Commit c31dfc9

Browse files
Merge branch 'main' into fix/question-option-description-contrast
2 parents 1378901 + 7b8d126 commit c31dfc9

411 files changed

Lines changed: 30877 additions & 1658 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
name: Mobile Showcase Screenshots
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
platform:
7+
description: Device platforms to capture
8+
required: true
9+
default: all
10+
type: choice
11+
options:
12+
- all
13+
- ios
14+
- android
15+
appearance:
16+
description: System appearances to capture
17+
required: true
18+
default: both
19+
type: choice
20+
options:
21+
- both
22+
- dark
23+
- light
24+
25+
permissions:
26+
contents: read
27+
28+
env:
29+
NODE_OPTIONS: --max-old-space-size=8192
30+
31+
jobs:
32+
ios:
33+
name: iPhone 6.9, iPhone 6.5, and iPad 13
34+
if: inputs.platform == 'all' || inputs.platform == 'ios'
35+
runs-on: blacksmith-12vcpu-macos-26
36+
timeout-minutes: 60
37+
steps:
38+
- name: Checkout
39+
uses: actions/checkout@v6
40+
41+
- name: Setup Vite+
42+
uses: voidzero-dev/setup-vp@v1
43+
with:
44+
node-version-file: package.json
45+
cache: true
46+
run-install: true
47+
48+
- name: Expose pnpm
49+
run: |
50+
pnpm_version="$(node --print "require('./package.json').packageManager.split('@').pop()")"
51+
vp_pnpm_bin="$HOME/.vite-plus/package_manager/pnpm/$pnpm_version/pnpm/bin"
52+
echo "$vp_pnpm_bin" >> "$GITHUB_PATH"
53+
"$vp_pnpm_bin/pnpm" --version
54+
55+
- name: Capture iOS showcase
56+
run: pnpm screenshots:mobile --platform ios --appearance "${{ inputs.appearance }}"
57+
58+
- name: Validate App Store Connect assets
59+
run: pnpm screenshots:mobile --platform ios --appearance "${{ inputs.appearance }}" --validate-only
60+
61+
- name: Upload iOS screenshots
62+
if: always()
63+
uses: actions/upload-artifact@v7
64+
with:
65+
name: app-store-connect-screenshots
66+
path: artifacts/app-store/screenshots/apple/
67+
if-no-files-found: warn
68+
retention-days: 14
69+
70+
android:
71+
name: Android phone, 7-inch tablet, and 10-inch tablet
72+
if: inputs.platform == 'all' || inputs.platform == 'android'
73+
runs-on: blacksmith-16vcpu-ubuntu-2404
74+
timeout-minutes: 60
75+
env:
76+
T3_SHOWCASE_ANDROID_ABI: x86_64
77+
steps:
78+
- name: Checkout
79+
uses: actions/checkout@v6
80+
81+
- name: Setup Vite+
82+
uses: voidzero-dev/setup-vp@v1
83+
with:
84+
node-version-file: package.json
85+
cache: true
86+
run-install: true
87+
88+
- name: Expose pnpm
89+
run: |
90+
pnpm_version="$(node --print "require('./package.json').packageManager.split('@').pop()")"
91+
vp_pnpm_bin="$HOME/.vite-plus/package_manager/pnpm/$pnpm_version/pnpm/bin"
92+
echo "$vp_pnpm_bin" >> "$GITHUB_PATH"
93+
"$vp_pnpm_bin/pnpm" --version
94+
95+
- name: Setup Java
96+
uses: actions/setup-java@v5
97+
with:
98+
distribution: temurin
99+
java-version: 17
100+
101+
- name: Setup Gradle cache
102+
uses: gradle/actions/setup-gradle@v5
103+
104+
- name: Enable KVM
105+
run: |
106+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS="static_node=kvm"' \
107+
| sudo tee /etc/udev/rules.d/99-kvm4all.rules
108+
sudo udevadm control --reload-rules
109+
sudo udevadm trigger --name-match=kvm
110+
111+
- name: Capture Android showcase
112+
uses: reactivecircus/android-emulator-runner@v2
113+
with:
114+
api-level: 36
115+
target: google_apis
116+
arch: x86_64
117+
profile: pixel_7_pro
118+
avd-name: Pixel_10_Pro
119+
cores: 8
120+
ram-size: 4096M
121+
disable-animations: false
122+
script: pnpm screenshots:mobile --platform android --appearance "${{ inputs.appearance }}"
123+
124+
- name: Validate Google Play assets
125+
run: pnpm screenshots:mobile --platform android --appearance "${{ inputs.appearance }}" --validate-only
126+
127+
- name: Upload Android screenshots
128+
if: always()
129+
uses: actions/upload-artifact@v7
130+
with:
131+
name: google-play-screenshots
132+
path: artifacts/app-store/screenshots/google-play/
133+
if-no-files-found: warn
134+
retention-days: 14

‎.gitignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ squashfs-root/
2727
.gstack/
2828
dist-electron/
2929
.electron-runtime/
30+
.showcase/
31+
apps/mobile/.showcase/
32+
artifacts/app-store/screenshots/
3033
node_modules/
3134
.alchemy/
3235
*.log

‎app.json‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"expo": {}
3+
}

‎apps/desktop/src/electron/ElectronUpdater.test.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const { autoUpdaterMock } = vi.hoisted(() => ({
1010
autoInstallOnAppQuit: true,
1111
channel: "latest",
1212
disableDifferentialDownload: false,
13+
fullChangelog: false,
1314
checkForUpdates: vi.fn(() => Promise.resolve(null)),
1415
downloadUpdate: vi.fn(() => Promise.resolve([])),
1516
on: vi.fn(),
@@ -33,6 +34,7 @@ describe("ElectronUpdater", () => {
3334
autoUpdaterMock.autoInstallOnAppQuit = true;
3435
autoUpdaterMock.channel = "latest";
3536
autoUpdaterMock.disableDifferentialDownload = false;
37+
autoUpdaterMock.fullChangelog = false;
3638
autoUpdaterMock.checkForUpdates.mockClear();
3739
autoUpdaterMock.checkForUpdates.mockImplementation(() => Promise.resolve(null));
3840
autoUpdaterMock.downloadUpdate.mockClear();
@@ -98,6 +100,18 @@ describe("ElectronUpdater", () => {
98100
}).pipe(Effect.provide(ElectronUpdater.layer)),
99101
);
100102

103+
it.effect("sets full changelog mode", () =>
104+
Effect.gen(function* () {
105+
const updater = yield* ElectronUpdater.ElectronUpdater;
106+
107+
yield* updater.setFullChangelog(true);
108+
assert.equal(autoUpdaterMock.fullChangelog, true);
109+
110+
yield* updater.setFullChangelog(false);
111+
assert.equal(autoUpdaterMock.fullChangelog, false);
112+
}).pipe(Effect.provide(ElectronUpdater.layer)),
113+
);
114+
101115
it.effect("preserves quit-and-install flags and the execution-time channel", () =>
102116
Effect.gen(function* () {
103117
const cause = new Error("quit and install failed");

‎apps/desktop/src/electron/ElectronUpdater.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export class ElectronUpdater extends Context.Service<
6666
readonly setAllowPrerelease: (value: boolean) => Effect.Effect<void>;
6767
readonly allowDowngrade: Effect.Effect<boolean>;
6868
readonly setAllowDowngrade: (value: boolean) => Effect.Effect<void>;
69+
readonly setFullChangelog: (value: boolean) => Effect.Effect<void>;
6970
readonly setDisableDifferentialDownload: (value: boolean) => Effect.Effect<void>;
7071
readonly checkForUpdates: Effect.Effect<void, ElectronUpdaterCheckForUpdatesError>;
7172
readonly downloadUpdate: Effect.Effect<void, ElectronUpdaterDownloadUpdateError>;
@@ -112,6 +113,11 @@ export const make = ElectronUpdater.of({
112113
autoUpdater.allowDowngrade = value;
113114
return Effect.void;
114115
}),
116+
setFullChangelog: (value) =>
117+
Effect.suspend(() => {
118+
autoUpdater.fullChangelog = value;
119+
return Effect.void;
120+
}),
115121
setDisableDifferentialDownload: (value) =>
116122
Effect.suspend(() => {
117123
autoUpdater.disableDifferentialDownload = value;

‎apps/desktop/src/ipc/DesktopIpcHandlers.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
getAppBranding,
3636
getLocalEnvironmentBootstraps,
3737
getLocalEnvironmentBearerToken,
38+
getWindowFullscreenState,
3839
openExternal,
3940
pickFolder,
4041
setTheme,
@@ -48,6 +49,7 @@ export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers"
4849
yield* PreviewIpc.installPreviewEventForwarding();
4950

5051
yield* ipc.handleSync(getAppBranding);
52+
yield* ipc.handleSync(getWindowFullscreenState);
5153
yield* ipc.handleSync(getLocalEnvironmentBootstraps);
5254
yield* ipc.handle(getLocalEnvironmentBearerToken);
5355

‎apps/desktop/src/ipc/channels.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ export const SET_THEME_CHANNEL = "desktop:set-theme";
44
export const CONTEXT_MENU_CHANNEL = "desktop:context-menu";
55
export const OPEN_EXTERNAL_CHANNEL = "desktop:open-external";
66
export const MENU_ACTION_CHANNEL = "desktop:menu-action";
7+
export const GET_WINDOW_FULLSCREEN_STATE_CHANNEL = "desktop:get-window-fullscreen-state";
8+
export const WINDOW_FULLSCREEN_STATE_CHANNEL = "desktop:window-fullscreen-state";
79
export const UPDATE_STATE_CHANNEL = "desktop:update-state";
810
export const UPDATE_GET_STATE_CHANNEL = "desktop:update-get-state";
911
export const UPDATE_SET_CHANNEL_CHANNEL = "desktop:update-set-channel";

‎apps/desktop/src/ipc/methods/window.test.ts‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import { assert, describe, it } from "@effect/vitest";
22
import * as Effect from "effect/Effect";
3+
import * as Layer from "effect/Layer";
34
import * as Option from "effect/Option";
45

6+
import type * as Electron from "electron";
7+
58
import * as DesktopBackendManager from "../../backend/DesktopBackendManager.ts";
69
import * as DesktopBackendPool from "../../backend/DesktopBackendPool.ts";
7-
import { getLocalEnvironmentBootstraps } from "./window.ts";
10+
import * as ElectronWindow from "../../electron/ElectronWindow.ts";
11+
import { getLocalEnvironmentBootstraps, getWindowFullscreenState } from "./window.ts";
812

913
const readyWslConfig: DesktopBackendManager.DesktopBackendStartConfig = {
1014
executablePath: "wsl.exe",
@@ -126,3 +130,19 @@ describe("getLocalEnvironmentBootstraps", () => {
126130
}).pipe(Effect.provide(DesktopBackendPool.layerTest([stoppedInstance])));
127131
});
128132
});
133+
134+
describe("getWindowFullscreenState", () => {
135+
it.effect("reads the current native window state", () => {
136+
const window = { isFullScreen: () => true } as Electron.BrowserWindow;
137+
138+
return Effect.gen(function* () {
139+
assert.isTrue(yield* getWindowFullscreenState.handler());
140+
}).pipe(
141+
Effect.provide(
142+
Layer.mock(ElectronWindow.ElectronWindow)({
143+
currentMainOrFirst: Effect.succeed(Option.some(window)),
144+
}),
145+
),
146+
);
147+
});
148+
});

‎apps/desktop/src/ipc/methods/window.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ export const getAppBranding = DesktopIpc.makeSyncIpcMethod({
5555
}),
5656
});
5757

58+
export const getWindowFullscreenState = DesktopIpc.makeSyncIpcMethod({
59+
channel: IpcChannels.GET_WINDOW_FULLSCREEN_STATE_CHANNEL,
60+
result: Schema.Boolean,
61+
handler: Effect.fn("desktop.ipc.window.getWindowFullscreenState")(function* () {
62+
const electronWindow = yield* ElectronWindow.ElectronWindow;
63+
const window = yield* electronWindow.currentMainOrFirst;
64+
return Option.isSome(window) && window.value.isFullScreen();
65+
}),
66+
});
67+
5868
export const getLocalEnvironmentBootstraps = DesktopIpc.makeSyncIpcMethod({
5969
channel: IpcChannels.GET_LOCAL_ENVIRONMENT_BOOTSTRAPS_CHANNEL,
6070
result: Schema.Array(DesktopEnvironmentBootstrapSchema),

‎apps/desktop/src/preload.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,19 @@ contextBridge.exposeInMainWorld("desktopBridge", {
116116
ipcRenderer.removeListener(IpcChannels.MENU_ACTION_CHANNEL, wrappedListener);
117117
};
118118
},
119+
getWindowFullscreenState: () =>
120+
ipcRenderer.sendSync(IpcChannels.GET_WINDOW_FULLSCREEN_STATE_CHANNEL) === true,
121+
onWindowFullscreenStateChange: (listener) => {
122+
const wrappedListener = (_event: Electron.IpcRendererEvent, fullscreen: unknown) => {
123+
if (typeof fullscreen !== "boolean") return;
124+
listener(fullscreen);
125+
};
126+
127+
ipcRenderer.on(IpcChannels.WINDOW_FULLSCREEN_STATE_CHANNEL, wrappedListener);
128+
return () => {
129+
ipcRenderer.removeListener(IpcChannels.WINDOW_FULLSCREEN_STATE_CHANNEL, wrappedListener);
130+
};
131+
},
119132
getUpdateState: () => ipcRenderer.invoke(IpcChannels.UPDATE_GET_STATE_CHANNEL),
120133
setUpdateChannel: (channel) =>
121134
ipcRenderer.invoke(IpcChannels.UPDATE_SET_CHANNEL_CHANNEL, channel),

0 commit comments

Comments
 (0)