fix(desktop, web): pont presse-papier Electron, sans quoi copier ne faisait rien dans l'app
CI / No em/en dashes (push) Successful in 3s
CI / Build & test (Node 24) (push) Successful in 10m48s
Release / Publish to Gitea npm registry (push) Successful in 10m52s
CI / Build & test (Node 22) (push) Successful in 11m6s
Desktop Release / Build Linux (AppImage + deb) (push) Successful in 13m38s
Desktop Release / Publish floating desktop-latest release (push) Successful in 11s
CI / Pack & boot smoke (Node 22) (push) Successful in 9m56s
Desktop Release / Build Windows (NSIS + portable) (push) Canceled after 0s
CI / No em/en dashes (push) Successful in 3s
CI / Build & test (Node 24) (push) Successful in 10m48s
Release / Publish to Gitea npm registry (push) Successful in 10m52s
CI / Build & test (Node 22) (push) Successful in 11m6s
Desktop Release / Build Linux (AppImage + deb) (push) Successful in 13m38s
Desktop Release / Publish floating desktop-latest release (push) Successful in 11s
CI / Pack & boot smoke (Node 22) (push) Successful in 9m56s
Desktop Release / Build Windows (NSIS + portable) (push) Canceled after 0s
git-arboretum 3.5.1, desktop 0.2.2.
Le copier/coller livre en 3.5.0 ne fonctionnait que dans un navigateur : verifie dans l'app packagee,
`navigator.clipboard` y rejette en NotAllowedError A LA LECTURE COMME A L'ECRITURE. La copie echouait
donc silencieusement dans le contexte meme ou le probleme avait ete signale.
- feat(desktop): `arboretumDesktop.clipboard` expose par le preload, relaye en IPC vers le module
`clipboard` d'Electron (lecture + ecriture, ecriture bornee a 1 M de caracteres). Le module
`clipboard` n'etant pas accessible a un preload sandboxe, le passage par le main est obligatoire.
Constantes de canaux isolees dans src/shared/ipc.ts pour que le bundle du preload ne tire pas
`ipcMain` avec lui.
- fix(web): acces au presse-papier en cascade, du plus fiable au plus degrade : pont de l'app de
bureau, puis `navigator.clipboard`, puis `document.execCommand('copy')` pour l'ecriture. Ce dernier
recours couvre aussi l'acces LAN en clair (http://<ip>:7317), ou l'API Clipboard est indisponible.
- test: quatre cas de plus sur l'ordre de la cascade et le retour a null quand tout est refuse
(501 tests). Pont IPC verifie dans un vrai Electron : writeText/readText repondent OK, alors que
`navigator.clipboard` y refuse. Le chemin complet frappe -> copie -> collage -> SIGINT reste
couvert par verify-clipboard.mjs (Chromium CDP, ALL GREEN).
This commit is contained in:
@@ -4,6 +4,13 @@ Notable changes to the Arboretum desktop app (`packages/desktop`). The daemon an
|
||||
extension keep their own changelogs in `packages/server/CHANGELOG.md` and
|
||||
`packages/vscode/CHANGELOG.md`.
|
||||
|
||||
## 0.2.2
|
||||
|
||||
- **Clipboard bridge.** The renderer cannot use `navigator.clipboard` (Electron rejects it with
|
||||
`NotAllowedError`), so copying a terminal selection did nothing in the app even after 0.2.1. The
|
||||
preload now exposes `arboretumDesktop.clipboard`, relaying to Electron's `clipboard` module over IPC
|
||||
(read and write, writes capped at 1M chars). Ships the daemon 3.5.1, whose SPA uses that bridge first.
|
||||
|
||||
## 0.2.1
|
||||
|
||||
Ships the daemon 3.5.0, which fixes the black window seen after updating the app.
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@arboretum/desktop",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@arboretum/desktop",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@arboretum/desktop",
|
||||
"private": true,
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"description": "Self-hosted multi-project AI IDE for git worktrees and Claude Code sessions",
|
||||
"homepage": "https://git-arboretum.com",
|
||||
"repository": {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { clipboard, ipcMain } from 'electron';
|
||||
import { CLIPBOARD_READ, CLIPBOARD_WRITE } from '../shared/ipc';
|
||||
|
||||
// Pont presse-papier pour le renderer.
|
||||
//
|
||||
// Pourquoi il est nécessaire : dans Electron, `navigator.clipboard.writeText` ET `readText`
|
||||
// échouent en `NotAllowedError` (vérifié dans l'app packagée). La SPA ne pouvait donc PAS copier
|
||||
// la sélection d'un terminal, alors que la même page y arrive dans un navigateur. Le module
|
||||
// `clipboard` n'étant pas exposé aux preloads sandboxés, on passe par IPC.
|
||||
//
|
||||
// Portée : l'app charge exclusivement sa propre SPA locale servie par son daemon, et un terminal
|
||||
// web est déjà de l'exécution de code par conception : le presse-papier n'élargit pas la surface.
|
||||
// On borne quand même la taille écrite pour qu'une boucle accidentelle ne remplisse pas la mémoire.
|
||||
const MAX_WRITE_CHARS = 1_000_000;
|
||||
|
||||
export function registerClipboardBridge(): void {
|
||||
ipcMain.handle(CLIPBOARD_READ, () => clipboard.readText());
|
||||
ipcMain.handle(CLIPBOARD_WRITE, (_event, text: unknown) => {
|
||||
if (typeof text !== 'string' || text.length === 0) return false;
|
||||
clipboard.writeText(text.slice(0, MAX_WRITE_CHARS));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { seedSessionCookie } from './auth';
|
||||
import { loadWindowState, saveWindowState } from './window-state';
|
||||
import { createTray } from './tray';
|
||||
import { installAppMenu } from './app-menu';
|
||||
import { registerClipboardBridge } from './clipboard';
|
||||
import { initUpdater } from './updater';
|
||||
import { resolveIconPath } from './paths';
|
||||
|
||||
@@ -39,6 +40,7 @@ async function bootstrap(): Promise<void> {
|
||||
const dataDir = join(app.getPath('userData'), 'daemon');
|
||||
daemon = await startDaemon({ dataDir, port: PORT, onLog: (l) => process.stdout.write(l) });
|
||||
await seedSessionCookie(PARTITION, daemon.url, daemon.token);
|
||||
registerClipboardBridge();
|
||||
createWindow(daemon.url);
|
||||
installAppMenu({ url: daemon.url, onQuit: quitApp });
|
||||
tray = createTray({ show: showWindow, quit: quitApp });
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { contextBridge } from 'electron';
|
||||
import { contextBridge, ipcRenderer } from 'electron';
|
||||
import { CLIPBOARD_READ, CLIPBOARD_WRITE } from '../shared/ipc';
|
||||
|
||||
// Preload minimal (sandbox activé) : expose seulement un marqueur permettant à la SPA de détecter
|
||||
// qu'elle tourne dans l'app de bureau. Aucun accès Node/fs exposé au renderer.
|
||||
// Preload minimal (sandbox activé) : un marqueur permettant à la SPA de détecter qu'elle tourne dans
|
||||
// l'app de bureau, plus un pont presse-papier. Aucun accès Node/fs exposé au renderer.
|
||||
//
|
||||
// Le pont existe parce que `navigator.clipboard` est refusé (NotAllowedError) dans le renderer
|
||||
// Electron : sans lui, impossible de copier la sélection d'un terminal depuis l'app. Le module
|
||||
// `clipboard` n'étant pas disponible dans un preload sandboxé, on relaie par IPC vers le main.
|
||||
contextBridge.exposeInMainWorld('arboretumDesktop', {
|
||||
isDesktop: true,
|
||||
clipboard: {
|
||||
readText: (): Promise<string> => ipcRenderer.invoke(CLIPBOARD_READ) as Promise<string>,
|
||||
writeText: (text: string): Promise<boolean> => ipcRenderer.invoke(CLIPBOARD_WRITE, text) as Promise<boolean>,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
// Noms des canaux IPC, partagés entre le process principal et le preload. Isolés ici pour que le
|
||||
// bundle du preload n'ait pas à importer un module du main (qui tire `ipcMain` avec lui).
|
||||
export const CLIPBOARD_READ = 'arboretum:clipboard-read';
|
||||
export const CLIPBOARD_WRITE = 'arboretum:clipboard-write';
|
||||
Reference in New Issue
Block a user