feat(frontend): écran Appareils et parcours d'appairage nommé (#77 F1/F2)

Expose la gestion des appareils appairés introduite en B1-B4, sur une
surface unique partagée par le web et le desktop.

- Écran Appareils : liste, renommage, révocation unitaire ou globale,
  activité formatée, panneau de code éphémère.
- Le parcours d'appairage demande un nom d'appareil, pour qu'une
  révocation porte sur quelque chose d'identifiable par l'utilisateur.
- Gateways DeviceGateway en trois adapters (Tauri, HTTP, Mock), le port
  restant le seul contrat connu de la feature.

Les erreurs sont mappées localement et le message du serveur n'est jamais
affiché tel quel : un échec d'appairage ne doit pas devenir un oracle pour
qui teste des codes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 13:27:06 +02:00
parent 8fe93d1652
commit 3f1e132e88
27 changed files with 2013 additions and 135 deletions

View File

@ -34,6 +34,8 @@ import type {
MemoryType,
OpenCodeConfig,
EffectivePermissions,
PairedDevice,
PairingCode,
PermissionSet,
PageDirection,
Project,
@ -750,6 +752,31 @@ export interface EmbedderGateway {
describeEmbedderEngines(): Promise<EmbedderEngines>;
}
/**
* Paired-device management (ticket #77) — the access surface of the instance.
*
* Mounted identically on web and desktop, so it is a **port**, not a web
* concern: the desktop adapter reaches the same use cases through the Tauri
* composition root, the web adapter over HTTP. Revoking is authoritative
* server-side; revoking the *current* device ends this session, which callers
* detect from {@link PairedDevice.isCurrentDevice} before the call.
*/
export interface DeviceGateway {
/** Lists every paired device, most recently active first (backend order). */
listDevices(): Promise<PairedDevice[]>;
/**
* Generates a fresh single-use pairing code, invalidating any previous one.
* The code exists only in server memory until it is consumed or expires.
*/
createPairingCode(): Promise<PairingCode>;
/** Renames one device (1-40 characters). */
renameDevice(deviceId: string, name: string): Promise<void>;
/** Revokes one device; its live WebSockets are closed server-side (B3). */
revokeDevice(deviceId: string): Promise<void>;
/** Revokes every device, the current one included. */
revokeAllDevices(): Promise<void>;
}
/** Project and agent permission management (LP1). */
export interface PermissionGateway {
/** Reads the full project permission document. */
@ -1130,6 +1157,7 @@ export interface Gateways {
skill: SkillGateway;
memory: MemoryGateway;
embedder: EmbedderGateway;
device: DeviceGateway;
permission: PermissionGateway;
workState: WorkStateGateway;
conversation: ConversationGateway;