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:
@ -42,6 +42,7 @@ import {
|
||||
HttpTicketGateway,
|
||||
} from "./streamGateways";
|
||||
import {
|
||||
WebDesktopServerGateway,
|
||||
WebFocusedProjectGateway,
|
||||
WebRemoteGateway,
|
||||
WebWindowGateway,
|
||||
@ -121,6 +122,9 @@ export function createHttpWsGateways(config: HttpWsGatewaysConfig = {}): Gateway
|
||||
remote: new WebRemoteGateway(),
|
||||
profile: new HttpProfileGateway(http),
|
||||
modelServer: new HttpModelServerGateway(http),
|
||||
// Desktop-only (#68): the web client is served *by* the embedded server and
|
||||
// must not reconfigure it.
|
||||
desktopServer: new WebDesktopServerGateway(),
|
||||
template: new HttpTemplateGateway(http),
|
||||
skill: new HttpSkillGateway(http),
|
||||
memory: new HttpMemoryGateway(http),
|
||||
|
||||
@ -9,8 +9,15 @@
|
||||
* browser to replace `pickFolder`), it gets its own lot.
|
||||
*/
|
||||
|
||||
import type { GatewayError, Unsubscribe } from "@/domain";
|
||||
import type {
|
||||
EmbeddedServerStatus,
|
||||
GatewayError,
|
||||
ServerExposurePreview,
|
||||
ServerExposureSettings,
|
||||
Unsubscribe,
|
||||
} from "@/domain";
|
||||
import type {
|
||||
DesktopServerGateway,
|
||||
FocusedProject,
|
||||
FocusedProjectGateway,
|
||||
RemoteGateway,
|
||||
@ -75,3 +82,40 @@ export class WebRemoteGateway implements RemoteGateway {
|
||||
return unsupportedOnWeb("Remote (SSH/WSL) connection");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Web stub: the embedded server is desktop-only (ticket #68). A web client is
|
||||
* *served by* this very server, so letting it reconfigure or stop the server
|
||||
* would let a remote device saw off the branch it sits on. The desktop app owns
|
||||
* this surface; every call fails explicitly rather than reaching for Tauri.
|
||||
*/
|
||||
export class WebDesktopServerGateway implements DesktopServerGateway {
|
||||
// `async` on purpose: these must *reject* rather than throw synchronously, so
|
||||
// callers using `.then(ok, err)` (not just `await`) still see the failure.
|
||||
async getExposureSettings(): Promise<ServerExposureSettings> {
|
||||
return unsupportedOnWeb("Embedded server settings");
|
||||
}
|
||||
async saveExposureSettings(_settings: ServerExposureSettings): Promise<void> {
|
||||
return unsupportedOnWeb("Embedded server settings");
|
||||
}
|
||||
async previewExposure(
|
||||
_settings: ServerExposureSettings,
|
||||
): Promise<ServerExposurePreview> {
|
||||
return unsupportedOnWeb("Embedded server settings");
|
||||
}
|
||||
async status(): Promise<EmbeddedServerStatus> {
|
||||
return unsupportedOnWeb("Embedded server status");
|
||||
}
|
||||
async start(): Promise<EmbeddedServerStatus> {
|
||||
return unsupportedOnWeb("Starting the embedded server");
|
||||
}
|
||||
async stop(): Promise<EmbeddedServerStatus> {
|
||||
return unsupportedOnWeb("Stopping the embedded server");
|
||||
}
|
||||
onStatusChanged(
|
||||
_handler: (status: EmbeddedServerStatus) => void,
|
||||
): Promise<Unsubscribe> {
|
||||
// Never fires on web; a no-op unsubscribe keeps callers' teardown honest.
|
||||
return Promise.resolve(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user