Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Empty file modified .devcontainer/setup-user.sh
100755 → 100644
Empty file.
Empty file modified .husky/pre-commit
100755 → 100644
Empty file.
Empty file modified .yarn/releases/yarn-4.12.0.cjs
100755 → 100644
Empty file.
Binary file not shown.
Binary file not shown.
Empty file modified packages/common/y-octo/node/scripts/run-test.mts
100755 → 100644
Empty file.
Empty file modified packages/frontend/apps/android/App/gradlew
100755 → 100644
Empty file.
2 changes: 2 additions & 0 deletions packages/frontend/apps/electron-renderer/src/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { RouterProvider } from 'react-router-dom';

import { setupEffects } from './effects';
import { DesktopLanguageSync } from './language-sync';
import { DesktopPowerStateSync } from './power-state-sync';
import { DesktopThemeSync } from './theme-sync';

const { frameworkProvider } = setupEffects();
Expand Down Expand Up @@ -48,6 +49,7 @@ export function App() {
<AffineContext store={getCurrentStore()}>
<DesktopThemeSync />
<DesktopLanguageSync />
<DesktopPowerStateSync />
<RouterProvider
fallbackElement={<AppContainer fallback />}
router={router}
Expand Down
10 changes: 10 additions & 0 deletions packages/frontend/apps/electron-renderer/src/app/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,13 @@ html[data-active='true']:has([data-translucent='true']) {
opacity: 1;
transition: opacity 0.2s;
}

/* Disable animations when on battery power */
html.reduce-motion *,
html.reduce-motion *::before,
html.reduce-motion *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useEffect, useState } from 'react';

export function usePowerState() {
const [onBattery, setOnBattery] = useState(false);

useEffect(() => {
window.__apis.getPowerState().then(setOnBattery);
}, []);

return onBattery;
}
Comment on lines +3 to +11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Hook doesn't react to power state changes.

This hook queries the power state only once on mount and never updates. Combined with the main process not broadcasting power state changes, this means:

  1. If the user unplugs/plugs in their device after mount, the UI won't respond
  2. The reduce-motion behavior will remain stale until the app restarts
  3. The battery-saving feature is effectively broken for runtime power changes

Additionally, there's no error handling for the IPC call, which could cause the component to silently fail.

🔧 Proposed fix: Add event listener for power state updates
 import { useEffect, useState } from 'react';

 export function usePowerState() {
   const [onBattery, setOnBattery] = useState(false);

   useEffect(() => {
-    window.__apis.getPowerState().then(setOnBattery);
-  }, []);
+    // Get initial state
+    window.__apis.getPowerState()
+      .then(setOnBattery)
+      .catch(err => {
+        console.error('Failed to get power state:', err);
+      });
+
+    // Listen for power state changes
+    const handlePowerStateChange = (_event: any, onBattery: boolean) => {
+      setOnBattery(onBattery);
+    };
+
+    window.electron?.ipcRenderer.on('power-state-changed', handlePowerStateChange);
+
+    return () => {
+      window.electron?.ipcRenderer.removeListener('power-state-changed', handlePowerStateChange);
+    };
+  }, []);

   return onBattery;
 }

Note: This requires the main process changes suggested in packages/frontend/apps/electron/src/main/index.ts to broadcast power state changes.

Committable suggestion skipped: line range outside the PR's diff.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useEffect, useState } from 'react';

import { usePowerState } from './hooks/use-power-state';

export const DesktopPowerStateSync = () => {
const onBattery = usePowerState();
const [prefersReducedMotion, setPrefersReducedMotion] = useState(
() => window.matchMedia('(prefers-reduced-motion: reduce)').matches
);

useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
const handleChange = (e: MediaQueryListEvent) => {
setPrefersReducedMotion(e.matches);
};

// Modern browsers
if (mediaQuery.addEventListener) {
mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
}
// Fallback for older browsers
else if (mediaQuery.addListener) {
mediaQuery.addListener(handleChange);
return () => mediaQuery.removeListener(handleChange);
}
}, []);

useEffect(() => {
const enableAnimations = !onBattery && !prefersReducedMotion;
document.documentElement.classList.toggle(
'reduce-motion',
!enableAnimations
);
}, [onBattery, prefersReducedMotion]);

return null;
};
Empty file modified packages/frontend/apps/electron/scripts/generate-assets.ts
100755 → 100644
Empty file.
24 changes: 23 additions & 1 deletion packages/frontend/apps/electron/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import path from 'node:path';

import * as Sentry from '@sentry/electron/main';
import { IPCMode } from '@sentry/electron/main';
import { app, protocol } from 'electron';
import { app, ipcMain, powerMonitor, protocol } from 'electron';

import { createApplicationMenu } from './application-menu/create';
import { buildType, isDev, overrideSession } from './config';
Expand All @@ -20,6 +20,8 @@ import { registerUpdater } from './updater';
import { launch } from './windows-manager/launcher';
import { launchStage } from './windows-manager/stage';

let isOnBatteryPower = false;

app.enableSandbox();

if (isDev) {
Expand Down Expand Up @@ -95,6 +97,26 @@ app.on('window-all-closed', () => {
app.quit();
});

/**
* Monitor system power source changes
* Refs: https://www.electronjs.org/docs/latest/api/power-monitor
*/
powerMonitor.on('on-battery', () => {
isOnBatteryPower = true;
logger.info('Running on battery');
});

powerMonitor.on('on-ac', () => {
isOnBatteryPower = false;
logger.info('Running on AC power');
});

isOnBatteryPower = powerMonitor.isOnBatteryPower();
Comment on lines +104 to +114
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Electron powerMonitor API requirements app ready event listeners

💡 Result:

Short answer

  • powerMonitor is a main-process API and cannot be required or used until app has emitted the ready event. Use app.on('ready', …) or app.whenReady() (or check app.isReady()) before attaching listeners. [1][2]

Example

  • Recommended pattern:
    const { app, powerMonitor } = require('electron')
    app.whenReady().then(() => {
    powerMonitor.on('suspend', () => { console.log('suspend') })
    powerMonitor.on('resume', () => { console.log('resume') })
    })

Notes

  • powerMonitor emits events like 'suspend', 'resume', 'on-ac', 'on-battery' (Windows) and 'shutdown' (macOS/Linux). [1]
  • Put all powerMonitor usage in the main process after ready to avoid runtime errors. [1][2]

Sources
[1] Electron powerMonitor docs.
[2] Electron app lifecycle (ready / whenReady / isReady).


🏁 Script executed:

# First, let's examine the actual file to understand the code context
head -120 packages/frontend/apps/electron/src/main/index.ts | tail -50

Repository: toeverything/AFFiNE

Length of output: 1126


🏁 Script executed:

# Get more context to see app.whenReady() calls and overall structure
cat -n packages/frontend/apps/electron/src/main/index.ts | head -130

Repository: toeverything/AFFiNE

Length of output: 5290


🏁 Script executed:

# Check if app.whenReady() is used elsewhere in the file
rg "app\.whenReady" packages/frontend/apps/electron/src/main/index.ts

Repository: toeverything/AFFiNE

Length of output: 45


Wrap powerMonitor listeners in app.whenReady() before registration.

The Electron powerMonitor API cannot be used until the app has emitted the ready event. Currently, the event listeners at lines 104-114 are registered at module scope during initialization, which executes before app.whenReady() resolves. This violates Electron's API requirements and will cause runtime errors.

Move these listeners into an app.whenReady().then() block:

Example fix:
app.whenReady().then(() => {
  powerMonitor.on('on-battery', () => {
    isOnBatteryPower = true;
    logger.info('Running on battery');
  });

  powerMonitor.on('on-ac', () => {
    isOnBatteryPower = false;
    logger.info('Running on AC power');
  });

  isOnBatteryPower = powerMonitor.isOnBatteryPower();
});
🤖 Prompt for AI Agents
In @packages/frontend/apps/electron/src/main/index.ts around lines 104 - 114,
The powerMonitor listeners and the initial isOnBatteryPower read are being
registered at module scope before Electron is ready; wrap the registration and
the isOnBatteryPower assignment in app.whenReady().then(...) so that
powerMonitor.on('on-battery', ...), powerMonitor.on('on-ac', ...), and the
isOnBatteryPower = powerMonitor.isOnBatteryPower() call execute only after
app.whenReady() resolves, keeping the same handler logic and logger.info
messages.


ipcMain.handle('get-power-state', () => {
return isOnBatteryPower;
});
Comment on lines +116 to +118
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Power state changes are not propagated to the renderer.

The IPC handler only returns the current state when queried, but there's no mechanism to notify the renderer when power state changes. This means:

  1. The renderer's usePowerState hook queries once on mount and never updates
  2. If a user unplugs their device after the app loads, the UI will continue showing AC-powered behavior
  3. The on-battery and on-ac event handlers update isOnBatteryPower but don't send events to the renderer
💡 Proposed fix: Add event broadcasting to renderer
+/**
+ * Broadcast power state changes to all renderer windows
+ */
+function broadcastPowerStateChange(onBattery: boolean) {
+  const { BrowserWindow } = require('electron');
+  BrowserWindow.getAllWindows().forEach(window => {
+    window.webContents.send('power-state-changed', onBattery);
+  });
+}
+
 powerMonitor.on('on-battery', () => {
   isOnBatteryPower = true;
   logger.info('Running on battery');
+  broadcastPowerStateChange(true);
 });

 powerMonitor.on('on-ac', () => {
   isOnBatteryPower = false;
   logger.info('Running on AC power');
+  broadcastPowerStateChange(false);
 });

Then update the renderer's usePowerState hook to listen for these events.

Committable suggestion skipped: line range outside the PR's diff.


/**
* @see https://www.electronjs.org/docs/latest/api/app#event-activate-macos Event: 'activate'
*/
Expand Down

This file was deleted.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Empty file modified packages/frontend/apps/ios/apollo-codegen-chore.sh
100755 → 100644
Empty file.
Empty file modified packages/frontend/apps/ios/setup.sh
100755 → 100644
Empty file.
Empty file modified packages/frontend/apps/ios/update_code_sign_identity.sh
100755 → 100644
Empty file.
Empty file modified packages/frontend/core/public/manifest.json
100755 → 100644
Empty file.
Empty file modified scripts/cleanup-canary-releases.sh
100755 → 100644
Empty file.
Empty file modified scripts/set-version.sh
100755 → 100644
Empty file.
Empty file modified tools/cli/bin/cli.js
100755 → 100644
Empty file.
Empty file modified tools/cli/bin/runner.js
100755 → 100644
Empty file.
Empty file modified tools/cli/src/init.ts
100755 → 100644
Empty file.