feat(frontend): panneau Settings Deployment et gateway serveur desktop (#68 F1+F2)

Donne à l'utilisateur la surface pour activer le serveur depuis l'app.

- `features/settings/` : `SettingsView`, `DeploymentSettings`, `useDeployment`.
- `adapters/desktopServer.ts` + port `DesktopServerGateway` et DTO associés ;
  adapters mock et http/unsupported alignés (le mode web n'expose pas le
  contrôle du serveur qui l'héberge).
- `ProjectsView` : le `showSettings: boolean` devient une navigation interne
  `AI Profiles` / `Deployment`. Le libellé alternant « Close AI Profiles »
  disparaît — Settings existait déjà dans cette vue, la surface évolue au lieu
  d'ajouter un `PanelId`.

CORRECTION D'UN BRIEF FAUX, remontée spontanément par DevFrontend et qui mérite
de survivre : le cadrage décrivait le mode `remoteProxyOtherMachine` avec deux
champs. `validate_settings` en exige un troisième, `lanBindAddress`, et rejette
loopback comme unspecified. Construit selon la spec, chaque save et chaque start
en mode 3 aurait échoué — le lot serait parti vert et cassé. Le panneau expose
donc un select alimenté par `candidateLanAddresses` fourni par le backend :
la règle « le frontend n'invente jamais une IP » tient.

QA ré-exécutée par Git avant merge : `npm run typecheck` exit 0 ·
`npx vitest run` 87 files / 789 passed, 0 échec.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 23:43:37 +02:00
parent 49e1d5a157
commit 7efa634f23
16 changed files with 1458 additions and 31 deletions

View File

@ -15,6 +15,7 @@ import type {
DomainEvent,
EmbedderEngines,
EmbedderProfile,
EmbeddedServerStatus,
FirstRunState,
GitBranches,
GitCommit,
@ -41,6 +42,8 @@ import type {
ProfileAvailability,
ResumableAgent,
ReplyChunk,
ServerExposurePreview,
ServerExposureSettings,
Skill,
SkillScope,
Sprint,
@ -678,6 +681,52 @@ export interface ModelServerGateway {
): Promise<ModelServerCommandPreview>;
}
/**
* Embedded web server lifecycle + exposure settings (ticket #68). Backs the
* desktop `Settings → Deployment` surface, so the user can expose IdeA to
* remote devices without a command line.
*
* **Desktop-only.** Only the Tauri transport implements it for real; the web
* transport rejects (a web client is *served by* this server and must not
* reconfigure it). See `desktop-only.test.ts`.
*
* The backend owns every network fact: LAN candidates and the proxy upstream
* URL come from {@link previewExposure}, never from client-side derivation.
*/
export interface DesktopServerGateway {
/** Reads the persisted exposure settings. */
getExposureSettings(): Promise<ServerExposureSettings>;
/**
* Validates and persists exposure settings. Rejects with a `GatewayError`
* (`INVALID`) when the config is inconsistent — e.g. a remote mode without an
* `https://` `publicOrigin`, or `remoteProxyOtherMachine` without a concrete
* `lanBindAddress` / a non-empty `trustedProxies`.
*/
saveExposureSettings(settings: ServerExposureSettings): Promise<void>;
/**
* Derives LAN candidates, the upstream URL and non-fatal warnings for a
* **draft** config, without persisting it. The single source of truth for the
* addresses the UI displays.
*/
previewExposure(
settings: ServerExposureSettings,
): Promise<ServerExposurePreview>;
/** Current server status. */
status(): Promise<EmbeddedServerStatus>;
/** Starts the server with the persisted settings; resolves with the new status. */
start(): Promise<EmbeddedServerStatus>;
/** Stops the server; resolves with the new status. */
stop(): Promise<EmbeddedServerStatus>;
/**
* Observes status transitions. The backend emits no status event today, so
* the Tauri adapter polls `embedded_server_status`; the schedule is a
* transport detail owned by the adapter, and callers must not depend on it.
*/
onStatusChanged(
handler: (status: EmbeddedServerStatus) => void,
): Promise<Unsubscribe>;
}
/** Input for {@link EmbedderGateway.saveEmbedderProfile}. */
export interface SaveEmbedderProfileInput {
profile: EmbedderProfile;
@ -1076,6 +1125,7 @@ export interface Gateways {
remote: RemoteGateway;
profile: ProfileGateway;
modelServer: ModelServerGateway;
desktopServer: DesktopServerGateway;
template: TemplateGateway;
skill: SkillGateway;
memory: MemoryGateway;