From cb2d0c2d44c73f3e40080979a182c96a7c8ec322 Mon Sep 17 00:00:00 2001
From: Blomios
Date: Tue, 21 Jul 2026 18:32:03 +0200
Subject: [PATCH 1/2] =?UTF-8?q?feat(app-tauri):=20option=20de=20lancement?=
=?UTF-8?q?=20auto=20du=20serveur=20web=20au=20d=C3=A9marrage?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Ajoute le déclenchement du serveur embarqué dès le démarrage d'IdeA
selon la préférence utilisateur, sans action manuelle requise.
Co-Authored-By: Claude Sonnet 5
---
crates/app-tauri/src/embedded_server.rs | 219 +++++++++++++++++++++++-
crates/app-tauri/src/lib.rs | 12 ++
2 files changed, 230 insertions(+), 1 deletion(-)
diff --git a/crates/app-tauri/src/embedded_server.rs b/crates/app-tauri/src/embedded_server.rs
index 4a4a76b..4397dda 100644
--- a/crates/app-tauri/src/embedded_server.rs
+++ b/crates/app-tauri/src/embedded_server.rs
@@ -32,6 +32,9 @@ pub enum ServerExposureMode {
pub struct ServerExposureSettingsDto {
/// Exposure mode.
pub mode: ServerExposureMode,
+ /// Whether the embedded server should start automatically when IdeA boots.
+ #[serde(default)]
+ pub auto_start: bool,
/// TCP port to bind. `0` asks the OS for an ephemeral port.
pub port: u16,
/// Public HTTPS origin used by reverse-proxy modes.
@@ -267,7 +270,7 @@ impl EmbeddedServerController {
}
Err(message) => {
let err = ErrorDto {
- code: "PROCESS".to_owned(),
+ code: start_error_code(&message).to_owned(),
message,
};
self.mark_failed(err.clone());
@@ -276,6 +279,28 @@ impl EmbeddedServerController {
}
}
+ /// Starts the embedded server at application boot when persisted settings
+ /// opt into auto-start.
+ ///
+ /// # Errors
+ /// Returns an [`ErrorDto`] if persisted settings are invalid or start fails.
+ pub async fn auto_start_if_enabled(
+ &self,
+ core: Arc,
+ ) -> Result
)}
- {/* A failure carries the backend's message: it is the correction. */}
+ {/* A failure carries the backend's message: it is the correction.
+ A port conflict (#89 — most often surfaced by auto-start at
+ launch, but shown the same way for a manual start) gets a
+ dedicated, actionable message instead of the generic one. */}
{status.state === "failed" && status.error && (
-
- {status.error.message}
-
+ status.error.code === "PORT_IN_USE" ? (
+
+
+ Le serveur n'a pas démarré automatiquement. {status.error.message}
+
+
+
+
+
+
+ ) : (
+
+ {status.error.message}
+
+ )
)}
{vm.actionError && (
{vm.actionError}
)}
+
+ {/* Auto-start at launch (#89) — a persistence-only toggle, never a
+ start trigger; it never runs the server right now. */}
+
@@ -225,6 +283,7 @@ export function DeploymentSettings() {
{({ id }) => (
{
() => gateway.status(),
() => gateway.start(),
() => gateway.stop(),
- () => gateway.saveExposureSettings({ mode: "localOnly", port: 0, trustedProxies: [] }),
- () => gateway.previewExposure({ mode: "localOnly", port: 0, trustedProxies: [] }),
+ () =>
+ gateway.saveExposureSettings({
+ mode: "localOnly",
+ autoStart: false,
+ port: 0,
+ trustedProxies: [],
+ }),
+ () =>
+ gateway.previewExposure({
+ mode: "localOnly",
+ autoStart: false,
+ port: 0,
+ trustedProxies: [],
+ }),
]) {
const error: Partial = { code: "UNSUPPORTED_ON_WEB" };
await expect(call()).rejects.toMatchObject(error);
diff --git a/frontend/src/features/settings/useDeployment.ts b/frontend/src/features/settings/useDeployment.ts
index ae9a0a8..471dc1b 100644
--- a/frontend/src/features/settings/useDeployment.ts
+++ b/frontend/src/features/settings/useDeployment.ts
@@ -56,17 +56,29 @@ export interface DeploymentVm {
warnings: DiagnosticWarning[];
/** Why the draft is rejected, as told by the backend. Actionable, not decorative. */
validationError: string | null;
- /** Last save/start/stop failure. */
+ /** Last save/start/stop/auto-start-toggle failure. */
actionError: string | null;
/** True while a start/stop is in flight. */
busy: boolean;
+ /** True while the auto-start toggle's own save is in flight (#89). */
+ autoStartBusy: boolean;
setMode: (mode: ServerExposureMode) => void;
setPublicOrigin: (origin: string) => void;
setLanBindAddress: (address: string) => void;
setTrustedProxies: (raw: string) => void;
setPort: (port: number) => void;
+ /**
+ * Persists `autoStart` immediately (ticket #89) — never calls `start()`.
+ * Unlike the other setters, this does not update the draft optimistically:
+ * the local `settings.autoStart` only flips once the save has actually
+ * succeeded, so a rejected save (the same validations as `start`/`save`
+ * apply — e.g. a remote mode still missing its public origin or LAN/proxy)
+ * leaves the checkbox showing the persisted value, not a lie.
+ */
+ setAutoStart: (enabled: boolean) => Promise;
/** Persists the draft, then starts the server so what runs is what is shown. */
start: () => Promise;
+ /** Stops the running server. Never touches the persisted `autoStart` flag. */
stop: () => Promise;
}
@@ -82,6 +94,7 @@ export function useDeployment(): DeploymentVm {
const [validationError, setValidationError] = useState(null);
const [actionError, setActionError] = useState(null);
const [busy, setBusy] = useState(false);
+ const [autoStartBusy, setAutoStartBusy] = useState(false);
// Guards against a stale in-flight preview overwriting a newer one.
const previewSeq = useRef(0);
@@ -112,6 +125,7 @@ export function useDeployment(): DeploymentVm {
try {
const preview = await desktopServer.previewExposure({
mode: "localOnly",
+ autoStart: false,
port: 0,
trustedProxies: [],
});
@@ -204,6 +218,29 @@ export function useDeployment(): DeploymentVm {
);
const setPort = useCallback((port: number) => patch({ port }), [patch]);
+ const setAutoStart = useCallback(
+ async (enabled: boolean) => {
+ if (!settings) return;
+ const next = { ...settings, autoStart: enabled };
+ setAutoStartBusy(true);
+ setActionError(null);
+ try {
+ // Persist only — never `start()`. Auto-start is applied at the next
+ // app launch, not now.
+ await desktopServer.saveExposureSettings(next);
+ // Update the draft only after the save actually succeeded, so a
+ // rejected save (e.g. a remote mode still missing its public origin)
+ // never shows a checkbox state that was not actually persisted.
+ setSettings(next);
+ } catch (e) {
+ setActionError(messageOf(e));
+ } finally {
+ setAutoStartBusy(false);
+ }
+ },
+ [desktopServer, settings],
+ );
+
const start = useCallback(async () => {
if (!settings) return;
setBusy(true);
@@ -242,11 +279,13 @@ export function useDeployment(): DeploymentVm {
validationError,
actionError,
busy,
+ autoStartBusy,
setMode,
setPublicOrigin,
setLanBindAddress,
setTrustedProxies,
setPort,
+ setAutoStart,
start,
stop,
};