@deveye/types 0.15.2 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/package.json +6 -6
  2. package/src/domain/device.ts +4 -29
  3. package/src/domain/featureRegistry.ts +40 -108
  4. package/src/domain/home.ts +40 -101
  5. package/src/domain/live.ts +36 -87
  6. package/src/domain/metrics.ts +10 -14
  7. package/src/domain/notifications.ts +39 -110
  8. package/src/domain/project.ts +3 -162
  9. package/src/domain/report.ts +54 -95
  10. package/src/domain/secrecy.ts +3 -5
  11. package/src/domain/sharing.ts +33 -70
  12. package/src/domain/syncProtocol.ts +5 -12
  13. package/src/domain/user.ts +8 -14
  14. package/src/domain/workspace.ts +0 -3
  15. package/src/domain/workspaceRole.ts +34 -82
  16. package/src/features/agent.ts +306 -0
  17. package/src/features/live.ts +17 -37
  18. package/src/features/notify.ts +19 -57
  19. package/src/features/registry.ts +7 -44
  20. package/src/features/secrecy.ts +4 -9
  21. package/src/features/sharing.ts +12 -26
  22. package/src/features/user.ts +6 -11
  23. package/src/features/workspace.ts +6 -11
  24. package/src/http/auth.ts +6 -13
  25. package/src/http/device.ts +13 -17
  26. package/src/http/status.ts +6 -10
  27. package/src/index.ts +29 -932
  28. package/src/protocol/agent.ts +40 -70
  29. package/src/protocol/envelope.ts +3 -10
  30. package/src/sdk/client-ambient.d.ts +228 -21
  31. package/src/sdk/client.ts +277 -17
  32. package/src/sdk/devb.ts +120 -0
  33. package/src/sdk/manifest.test.ts +0 -1
  34. package/src/sdk/manifest.ts +94 -25
  35. package/src/sdk/providers.ts +310 -5
  36. package/src/sdk/server.ts +483 -19
  37. package/src/sdk/testing.test.ts +2 -2
  38. package/src/sdk/testing.ts +302 -26
  39. package/src/utils/version.ts +5 -8
  40. package/src/domain/audience.ts +0 -549
  41. package/src/domain/backup.ts +0 -355
  42. package/src/domain/credential.ts +0 -55
  43. package/src/domain/database.ts +0 -467
  44. package/src/domain/deploy.ts +0 -231
  45. package/src/domain/finance.ts +0 -477
  46. package/src/domain/git.ts +0 -419
  47. package/src/domain/mail.ts +0 -394
  48. package/src/domain/note.ts +0 -202
  49. package/src/domain/password.ts +0 -36
  50. package/src/domain/projectBoard.ts +0 -130
  51. package/src/domain/projectChat.ts +0 -46
  52. package/src/domain/projectHistory.ts +0 -82
  53. package/src/domain/projectLink.ts +0 -87
  54. package/src/domain/projectPlan.ts +0 -68
  55. package/src/domain/sentinel.ts +0 -623
  56. package/src/domain/uptime.ts +0 -216
  57. package/src/features/audience.ts +0 -275
  58. package/src/features/backup.ts +0 -230
  59. package/src/features/database.ts +0 -461
  60. package/src/features/deploy.ts +0 -245
  61. package/src/features/device.ts +0 -292
  62. package/src/features/deviceFiles.ts +0 -83
  63. package/src/features/deviceLogs.ts +0 -36
  64. package/src/features/deviceTerminal.ts +0 -57
  65. package/src/features/finance.ts +0 -360
  66. package/src/features/git.ts +0 -368
  67. package/src/features/mail.ts +0 -374
  68. package/src/features/metrics.ts +0 -185
  69. package/src/features/note.ts +0 -189
  70. package/src/features/password.ts +0 -67
  71. package/src/features/project.ts +0 -709
  72. package/src/features/sentinel.ts +0 -233
  73. package/src/features/uptime.ts +0 -186
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deveye/types",
3
- "version": "0.15.2",
3
+ "version": "0.16.1",
4
4
  "description": "Shared contracts (types + zod schemas) for DevEye apps",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -41,9 +41,9 @@
41
41
  "lint": "eslint .",
42
42
  "lint:fix": "eslint . --fix",
43
43
  "typecheck": "tsc --noEmit",
44
- "ci": "npm run lint && npm run typecheck && npm test",
45
- "prettier": "prettier --check .",
46
- "prettier:fix": "prettier --write .",
44
+ "ci": "npm run format:check && npm run lint && npm run typecheck && npm test",
45
+ "format:check": "prettier --check .",
46
+ "format:fix": "prettier --write .",
47
47
  "test": "tsx --test \"src/**/*.test.ts\""
48
48
  },
49
49
  "dependencies": {
@@ -57,8 +57,8 @@
57
57
  "@eslint/js": "^8.0.0",
58
58
  "@types/node": "^26.3.0",
59
59
  "@types/react": "^19.0.0",
60
- "@typescript-eslint/eslint-plugin": "^6.0.0",
61
- "@typescript-eslint/parser": "^6.0.0",
60
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
61
+ "@typescript-eslint/parser": "^8.0.0",
62
62
  "eslint": "^8.0.0",
63
63
  "eslint-plugin-prettier": "^5.5.4",
64
64
  "tsx": "^4.23.12",
@@ -91,18 +91,10 @@ export interface DeviceRow {
91
91
  id: string;
92
92
  owner_id: number;
93
93
  /**
94
- * Espace d'**appairage** : celui que visait le code de liaison. Il porte
95
- * l'unicité de l'empreinte (`uniq_workspace_fingerprint`) et sert le
96
- * ré-enrôlement depuis la route publique, qui n'a pas de session pour dire
97
- * autrement d' elle vient.
98
- *
99
- * Ce n'est plus la frontière d'accès : celle-ci est la table de jonction
100
- * `device_workspaces`, un appareil pouvant être partagé avec plusieurs
101
- * espaces. L'espace d'appairage y figure toujours et ne s'en retire pas.
102
- *
103
- * `null` quand cet espace a été supprimé : l'appareil survit — il perd son
104
- * origine, pas son existence — et reste joignable par les espaces avec
105
- * lesquels il est partagé.
94
+ * Espace d'appairage (celui du code de liaison) : porte l'unicité de
95
+ * l'empreinte (`uniq_workspace_fingerprint`) et sert le ré-enrôlement par la
96
+ * route publique. La frontière d'accès est `device_workspaces`, il figure
97
+ * toujours. `null` si cet espace a été supprimé : l'appareil survit.
106
98
  */
107
99
  workspace_id: number | null;
108
100
  name: string;
@@ -131,23 +123,6 @@ export interface DeviceRow {
131
123
  status_before_delete: string | null;
132
124
  /** Last self-destruct failure message (deletion aborted); null otherwise. */
133
125
  delete_error: string | null;
134
- /**
135
- * Sentinelle est-elle active sur cet appareil ? Éteinte par défaut : activer
136
- * la feature ne doit lire les journaux d'authentification de personne, c'est
137
- * un geste explicite appareil par appareil.
138
- */
139
- sentinel_enabled: number;
140
- /**
141
- * Fin de la fenêtre d'apprentissage, unix ms. `null` tant que Sentinelle n'a
142
- * jamais été activée ; une date passée signifie « apprentissage terminé ».
143
- */
144
- sentinel_learning_until: number | null;
145
- /** Cadence du manifeste de persistance, en minutes. */
146
- sentinel_integrity_minutes: number;
147
- /** Relever les issues d'authentification (interrupteur propre). */
148
- sentinel_auth_events: number;
149
- /** Unix ms du dernier manifeste reçu ; `null` = jamais mesuré. */
150
- sentinel_last_integrity_at: number | null;
151
126
  }
152
127
 
153
128
  /**
@@ -6,24 +6,10 @@ import {
6
6
  } from './workspaceRole';
7
7
 
8
8
  /**
9
- * Ce qu'est une fonctionnalité, dit **une fois**.
10
- *
11
- * Le même renseignement vivait à trois endroits : `HOME_FEATURE_IDS` pour les
12
- * tuiles, `WORKSPACE_FEATURE_IDS` pour les droits, et les intitulés recopiés à la
13
- * main dans `RoleDialog` **et** dans `FEATURE_CATALOG`. Trois copies d'un même
14
- * couple identifiant / libellé, c'est la garantie qu'une fonctionnalité ajoutée
15
- * n'arrivera que dans deux d'entre elles — et c'est exactement ce qui était
16
- * arrivé à `monitoring`, présent dans un tableau et absent de l'autre sans que
17
- * rien ne le dise.
18
- *
19
- * Ce registre porte donc le **descriptif** ; les deux enums gardent leur rôle,
20
- * qui est de dire *où* une fonctionnalité a le droit d'apparaître. Ils ne sont
21
- * volontairement pas fusionnés : leur écart est documenté et voulu (`devices`
22
- * s'accorde mais n'a pas de tuile, `monitoring` a une tuile mais ne s'accorde
23
- * pas — voir `workspaceRole.ts`).
24
- *
25
- * Les écrans qui parcourent les fonctionnalités — la coquille de réglages, la
26
- * matrice de permissions, l'écran des rôles — lisent celui-ci et rien d'autre.
9
+ * Ce qu'est une fonctionnalité, dit une fois : le descriptif que lisent les
10
+ * écrans qui parcourent les fonctionnalités (coquille de réglages, matrice de
11
+ * permissions, écran des rôles). `HOME_FEATURE_IDS` et `WORKSPACE_FEATURE_IDS`
12
+ * gardent leur rôle : dire une fonctionnalité a le droit d'apparaître.
27
13
  */
28
14
  export interface FeatureDescriptor {
29
15
  id: FeatureId;
@@ -38,18 +24,14 @@ export interface FeatureDescriptor {
38
24
  /** Classe d'icône de `Styles/icons.css`, sans le préfixe `icon-`. */
39
25
  icon: string;
40
26
  /**
41
- * Cette fonctionnalité sait prévenir. Décide de l'onglet « Notifications »
42
- * de ses réglages — et, à elle seule, de l'existence du bouton pour les
43
- * quatre qui n'ont rien d'autre à régler.
27
+ * Cette fonctionnalité sait prévenir : décide de l'onglet « Notifications »
28
+ * de ses réglages.
44
29
  */
45
30
  notifies: boolean;
46
31
  /**
47
- * Elle tient une liste d'entités adressables (un service, une base, une
48
- * cible) sur lesquelles des réglages peuvent porter individuellement.
49
- *
50
- * Faux ne veut pas dire « aucune donnée » : Sentinelle a bien des constats,
51
- * mais on ne règle pas un constat, on règle la surveillance. Le critère est
52
- * « peut-on ouvrir les réglages de **cet** élément ? ».
32
+ * Elle tient des entités adressables sur lesquelles des réglages portent
33
+ * individuellement. Le critère : « peut-on ouvrir les réglages de CET
34
+ * élément ? » (Sentinelle a des constats, mais on règle la surveillance).
53
35
  */
54
36
  hasItems: boolean;
55
37
  /**
@@ -58,42 +40,23 @@ export interface FeatureDescriptor {
58
40
  */
59
41
  itemNoun?: string;
60
42
  /**
61
- * Les **sources** de la fonctionnalité : des réglages d'espace réutilisables
62
- * (un jeton Dokploy, un jeton GitHub, une destination d'archives) que
63
- * chaque élément ne fait que **désigner**. Corriger une source corrige d'un
64
- * coup tout ce qui s'en sert.
65
- *
66
- * Présent, il ouvre l'onglet « Sources » des réglages **à l'échelle de la
67
- * fonctionnalité**, le seul endroit où les sources se créent et se
68
- * corrigent. Les dialogues d'élément, eux, ne font que choisir dans la
69
- * liste, avec un bouton qui mène ici. `hint` est la phrase de tête de
70
- * l'onglet : elle dit ce qu'on y gère et qui s'en sert.
71
- *
72
- * Les canaux de notification suivent la même logique sans passer par ce
73
- * champ : ce sont les sources des émetteurs (chaque feature a les siens,
74
- * migration 091), gérées dans leur onglet « Notifications », qui porte
75
- * aussi le routage, indissociable (voir `notifies`).
43
+ * Les sources de la fonctionnalité : des réglages d'espace réutilisables
44
+ * (un jeton, une destination) que chaque élément ne fait que désigner.
45
+ * Ouvre l'onglet « Sources » à l'échelle de la fonctionnalité, seul endroit
46
+ * elles se créent et se corrigent ; `hint` est la phrase de tête de
47
+ * l'onglet. Les canaux de notification suivent la même logique dans
48
+ * l'onglet « Notifications » (voir `notifies`).
76
49
  */
77
50
  sources?: { hint: string };
78
51
  /**
79
52
  * Un de ses éléments peut-il être rendu visible depuis un autre espace ?
80
- *
81
- * La réponse tient entièrement au **chiffrement**, pas au goût : un élément
82
- * partagé reste chiffré sous la clé de son espace d'origine — c'est le
83
- * levier L3 de `WORKSPACES.md`, et y renoncer voudrait dire re-chiffrer sous
84
- * session vivante, ce que ce document range explicitement hors périmètre.
85
- *
86
- * Or seule la clé de l'**étage ouvert** est résoluble par le serveur seul.
87
- * D'où trois cas :
88
- *
89
- * - `'open'` — toute la fonctionnalité vit à l'étage ouvert : partageable
90
- * sans condition (Uptime, Bases, Déploiement, Git, Audience, Sauvegardes) ;
91
- * - `'perItem'` — l'étage se choisit élément par élément. Une note
92
- * ordinaire se partage, une note privée non ; un compte mail « open »
93
- * oui, un compte « guarded » non. Le serveur tranche à la ligne, jamais
94
- * la fonctionnalité en bloc ;
95
- * - `'never'` — rien n'y est partageable, pour une raison propre à chaque
96
- * cas (voir la note sous le registre).
53
+ * Décidé par le chiffrement : un élément partagé reste chiffré sous la clé
54
+ * de son espace d'origine, et seule la clé de l'étage ouvert est résoluble
55
+ * par le serveur seul.
56
+ * - `'open'` : toute la fonctionnalité vit à l'étage ouvert ;
57
+ * - `'perItem'` : l'étage se choisit élément par élément, le serveur
58
+ * tranche à la ligne ;
59
+ * - `'never'` : rien n'y est partageable (voir la note sous le registre).
97
60
  */
98
61
  shareTier: 'open' | 'perItem' | 'never';
99
62
  }
@@ -290,13 +253,9 @@ export const FEATURE_REGISTRY: readonly (FeatureDescriptor & { id: WorkspaceFeat
290
253
  const BY_ID = new Map<string, FeatureDescriptor>(FEATURE_REGISTRY.map((f) => [f.id, f]));
291
254
 
292
255
  /**
293
- * Les modules **externes** enregistrés dans ce processus.
294
- *
295
- * Le registre natif est une constante ; celui-ci se remplit au chargement, une
296
- * fois par module installé, depuis la glue générée des deux bundles. Il ne
297
- * s'agit pas de chargement à chaud : la liste est figée à la compilation, la
298
- * carte n'existe que parce qu'un fichier ne peut pas être à la fois publié dans
299
- * ce package et généré par l'application qui l'installe.
256
+ * Les modules externes enregistrés dans ce processus, remplis au chargement
257
+ * par la glue générée. Pas de chargement à chaud : la liste est figée à la
258
+ * compilation.
300
259
  */
301
260
  const EXTERNAL_BY_ID = new Map<ExternalFeatureId, FeatureDescriptor>();
302
261
 
@@ -316,13 +275,10 @@ export function allFeatureDescriptors(): readonly FeatureDescriptor[] {
316
275
  }
317
276
 
318
277
  /**
319
- * Le descriptif d'une fonctionnalité, native ou externe.
320
- *
321
- * Lève plutôt que de rendre `undefined` : un id natif vient d'un enum fermé,
322
- * donc une absence est un oubli d'entrée dans ce fichier ; un id externe
323
- * inconnu signifie que la glue générée n'a pas tourné, pas un cas d'exécution
324
- * à traiter chez l'appelant. Les écrans qui veulent tolérer un module absent
325
- * (une tuile orpheline) passent par {@link maybeFeatureDescriptor}.
278
+ * Le descriptif d'une fonctionnalité, native ou externe. Lève plutôt que de
279
+ * rendre `undefined` : une absence est un oubli d'entrée ici, ou une glue
280
+ * générée qui n'a pas tourné. Pour tolérer un module absent, voir
281
+ * {@link maybeFeatureDescriptor}.
326
282
  */
327
283
  export function featureDescriptor(id: FeatureId): FeatureDescriptor {
328
284
  const found = maybeFeatureDescriptor(id);
@@ -341,19 +297,10 @@ export function featureLabel(id: FeatureId): string {
341
297
  }
342
298
 
343
299
  /**
344
- * Trois `never` méritent leur justification, parce qu'on pourrait croire le
345
- * contraire :
346
- *
347
- * - **`devices`** a **déjà** son partage inter-espaces, antérieur et d'une
348
- * autre nature : `device_workspaces` (migration 072) est une adhésion à part
349
- * entière, pas une projection. Le mécanisme d'ici ne s'y superpose pas.
350
- * - **`password`** vit toujours à l'étage gardé — c'est la promesse du coffre.
351
- * Le serveur sait certes le lire quand le chiffrement par mot de passe est
352
- * éteint, mais un partage dont la survie dépend d'un réglage de sécurité
353
- * qu'on encourage n'est pas un partage.
354
- * - **`cloudsync`** range ses contenus dans un magasin de blobs sur disque,
355
- * chiffrés par la BMK et non par une clé d'espace : ce serait un autre
356
- * chantier.
300
+ * Les trois `never` : `devices` a déjà son partage inter-espaces, d'une autre
301
+ * nature (`device_workspaces` est une adhésion, pas une projection) ;
302
+ * `password` vit toujours à l'étage gardé ; `cloudsync` range ses contenus
303
+ * dans un magasin de blobs chiffrés par la BMK, pas par une clé d'espace.
357
304
  */
358
305
 
359
306
  /** Les fonctionnalités dont un élément **pourrait** voyager, côté chiffrement. */
@@ -362,29 +309,14 @@ export const SHAREABLE_FEATURES: readonly WorkspaceFeatureId[] = FEATURE_REGISTR
362
309
  ).map((f) => f.id);
363
310
 
364
311
  /**
365
- * Celles dont la **lecture élargie est réellement branchée**.
366
- *
367
- * `shareTier` dit ce que le chiffrement autorise ; cette liste dit ce que le
368
- * code fait. L'écart est volontaire et temporaire : projeter suppose que le
369
- * listage de la fonctionnalité sache aller chercher les lignes projetées et
370
- * choisir le bon codec ligne par ligne. Tant que ce n'est pas fait, la case
371
- * cocherait et rien n'apparaîtrait de l'autre côté.
372
- *
373
- * Partagée entre client et serveur **exprès** : le serveur refuse, le client
374
- * n'affiche pas l'onglet. Deux listes séparées auraient donné un onglet qui ne
375
- * mène nulle part — précisément ce que la coquille de réglages refuse.
376
- *
377
- * Brancher une fonctionnalité de plus : `listVisible` / `findVisible` dans son
378
- * dépôt, le codec par ligne dans son listage, une entrée ici.
312
+ * Celles dont la lecture élargie est réellement branchée : `shareTier` dit ce
313
+ * que le chiffrement autorise, cette liste ce que le code fait. Partagée entre
314
+ * client et serveur : le serveur refuse, le client n'affiche pas l'onglet.
315
+ * Brancher une native : `listVisible` / `findVisible` dans son dépôt, le codec
316
+ * par ligne dans son listage, une entrée ici. Un module se déclare par son
317
+ * manifest (`shareTier` autre que `'never'`) et son entrée `items`.
379
318
  */
380
- export const SHARE_WIRED_FEATURES: readonly WorkspaceFeatureId[] = [
381
- 'uptime',
382
- 'database',
383
- 'deploy',
384
- 'git',
385
- 'audience',
386
- 'backup'
387
- ];
319
+ export const SHARE_WIRED_FEATURES: readonly WorkspaceFeatureId[] = ['backup'];
388
320
 
389
321
  /** Les fonctionnalités qui savent prévenir, dans l'ordre du registre. */
390
322
  export const NOTIFYING_FEATURES: readonly WorkspaceFeatureId[] = FEATURE_REGISTRY.filter(
@@ -8,20 +8,20 @@ import {
8
8
  } from './workspaceRole';
9
9
 
10
10
  /**
11
- * Disposition de l'accueil (par espace). La grille est composée de **sections**
12
- * ordonnées, chacune tenant des **tuiles** ordonnées de n'importe quels genres
13
- * appareil, fonctionnalité, raccourci, dossier. Les sections sont entièrement
14
- * modulaires : aucune par défaut, ajoutées / retirées / réordonnées librement.
15
- * Leur intitulé est facultatif — sans lui, la section se rend comme un simple
16
- * groupe légèrement espacé, sans titre.
17
- *
18
- * Stockée en clair : métadonnée de personnalisation non sensible (comme le
19
- * thème), jamais de charge zero-knowledge.
11
+ * Disposition de l'accueil (par espace) : des sections ordonnées, chacune
12
+ * tenant des tuiles ordonnées de n'importe quels genres (appareil,
13
+ * fonctionnalité, raccourci, dossier). Aucune section par défaut ; l'intitulé
14
+ * est facultatif. Stockée en clair : métadonnée de personnalisation, jamais de
15
+ * charge zero-knowledge.
20
16
  */
21
17
 
22
- /** Les seize tuiles de fonctionnalités natives. */
18
+ /**
19
+ * Les seize tuiles de fonctionnalités natives. Une tuile porte l'id de sa
20
+ * feature : la tuile Monitoring est celle de la feature `devices`, dont le
21
+ * module fournit la carte et la vue.
22
+ */
23
23
  export const nativeHomeFeatureIdSchema = z.enum([
24
- 'monitoring',
24
+ 'devices',
25
25
  'sentinel',
26
26
  'weather',
27
27
  'password',
@@ -50,22 +50,15 @@ export const homeFeatureIdSchema = z.union([nativeHomeFeatureIdSchema, externalF
50
50
  export type HomeFeatureId = NativeHomeFeatureId | ExternalFeatureId;
51
51
 
52
52
  /**
53
- * Compact widgets that can be pinned to the top-right of the navbar. Like the
54
- * grid features they are individually add/remove/reorderable; the default set is
55
- * empty (the navbar shows none until the user adds some).
53
+ * Compact widgets pinned to the top-right of the navbar, individually
54
+ * add/remove/reorderable; the default set is empty.
56
55
  * - `weather` → current temperature of the primary city.
57
- * - `devices` → online / total device count.
58
56
  * - `secrecy` → password-encryption lock state + re-validation countdown.
59
- * - `uptime` → services up / total monitored.
60
57
  * - `live` → qui d'autre est dans l'espace, et où (bulles cliquables).
58
+ * A module's widget is declared by its manifest (`topbarWidget`) and keyed by
59
+ * its feature id (`homeTopbarWidgetIdSchema`).
61
60
  */
62
- export const nativeHomeTopbarWidgetIdSchema = z.enum([
63
- 'weather',
64
- 'devices',
65
- 'secrecy',
66
- 'uptime',
67
- 'live'
68
- ]);
61
+ export const nativeHomeTopbarWidgetIdSchema = z.enum(['weather', 'secrecy', 'live']);
69
62
  export type NativeHomeTopbarWidgetId = z.infer<typeof nativeHomeTopbarWidgetIdSchema>;
70
63
 
71
64
  /**
@@ -77,12 +70,9 @@ export const homeTopbarWidgetIdSchema = z.union([nativeHomeTopbarWidgetIdSchema,
77
70
  export type HomeTopbarWidgetId = NativeHomeTopbarWidgetId | FeatureId;
78
71
 
79
72
  /**
80
- * Shortcut preview type, auto-detected from the URL's domain (the user never
81
- * picks it manually). Each value has a server-side adapter under
82
- * `src/Services/shortcutTemplates/` dedicated logic where a real source exists
83
- * (GitHub API, YouTube/Spotify/SoundCloud/TikTok oEmbed, Wikipedia REST, npm
84
- * registry), and the generic Open Graph + favicon adapter for the rest. `link`
85
- * is that generic adapter; unknown domains fall back to it.
73
+ * Shortcut preview type, auto-detected from the URL's domain. Each value has a
74
+ * server-side adapter under `src/Services/shortcutTemplates/`; `link` is the
75
+ * generic Open Graph + favicon adapter, and unknown domains fall back to it.
86
76
  */
87
77
  export const shortcutTemplateSchema = z.enum([
88
78
  'link',
@@ -125,38 +115,19 @@ export const shortcutItemSchema = z.object({
125
115
  export type ShortcutItem = z.infer<typeof shortcutItemSchema>;
126
116
 
127
117
  /**
128
- * Combien de tuiles tient une section, et combien de fonctionnalités tient un
129
- * dossier.
130
- *
131
- * Exporté, et pas seulement écrit dans le schéma : le client doit refuser
132
- * **avant** d'écrire. Une disposition qui dépasse le plafond ne passe plus la
133
- * validation, donc le serveur la rejette et le client la relit vide au
134
- * démarrage suivant, ce qui revient à un accueil effacé sans un mot. Le
135
- * plafond des dossiers n'est atteignable par aucun geste (il y a moins de
136
- * fonctionnalités que ça, et aucune ne peut être rangée deux fois), celui des
137
- * tuiles l'est en créant des dossiers à la chaîne.
118
+ * Plafonds d'une section et d'un dossier. Exportés parce que le client doit
119
+ * refuser avant d'écrire : une disposition qui dépasse ne passe plus la
120
+ * validation, et le client la relirait vide au démarrage suivant.
138
121
  */
139
122
  export const HOME_SECTION_MAX_TILES = 60;
140
123
  export const HOME_FOLDER_MAX_ITEMS = 20;
141
124
 
142
125
  /**
143
- * Un dossier de la grille : plusieurs fonctionnalités derrière une seule tuile.
144
- *
145
- * Il vit dans une section au milieu des tuiles ordinaires, parce que c'en est
146
- * une : même carte, même place dans la grille, même glisser-déposer. Ce qui
147
- * change est ce qui se passe au clic (côté client, les cartes qu'il tient se
148
- * déploient par-dessus l'accueil).
149
- *
150
- * Il ne range que des **fonctionnalités**, là où une section range tout : une
151
- * carte d'appareil et un raccourci sont déjà des tuiles courtes, les empiler
152
- * derrière une tuile de pleine hauteur coûterait plus de place qu'il n'en
153
- * gagnerait. C'est la seule asymétrie qui reste après l'unification, et elle
154
- * est de mise en page, pas de modèle.
155
- *
156
- * Les fonctionnalités qu'il tient comptent comme **posées sur l'accueil** : le
157
- * sélecteur d'ajout les exclut, exactement comme celles qui ont leur propre
158
- * tuile. Une fonctionnalité n'est donc jamais à deux endroits à la fois, et la
159
- * règle « pas deux fois la même » reste une seule règle.
126
+ * Un dossier de la grille : plusieurs fonctionnalités derrière une seule tuile,
127
+ * posée dans une section comme une tuile ordinaire. Il ne range que des
128
+ * fonctionnalités (les autres tuiles sont déjà courtes), et celles qu'il tient
129
+ * comptent comme posées sur l'accueil : une fonctionnalité n'est jamais à deux
130
+ * endroits à la fois.
160
131
  */
161
132
  export const homeFolderSchema = z.object({
162
133
  /** Discriminant : c'est lui qui distingue un dossier d'un raccourci. */
@@ -170,28 +141,11 @@ export const homeFolderSchema = z.object({
170
141
  export type HomeFolder = z.infer<typeof homeFolderSchema>;
171
142
 
172
143
  /**
173
- * Une tuile de l'accueil appareil, fonctionnalité, raccourci ou dossier.
174
- *
175
- * ## Un seul genre de section, donc un seul genre de tuile
176
- *
177
- * Les sections étaient auparavant typées (« appareils », « fonctionnalités »,
178
- * « raccourcis ») et ne tenaient qu'une sorte de tuile. Ça obligeait à choisir
179
- * le genre **avant** d'avoir quelque chose à poser, à ouvrir une popup pour
180
- * ajouter une section, et à trois sélecteurs d'ajout différents. Une section
181
- * n'est plus qu'une rangée de tuiles ; c'est la tuile qui sait ce qu'elle est.
182
- *
183
- * ## Chaque tuile garde l'écriture qu'elle avait
184
- *
185
- * Un appareil et une fonctionnalité **sont** leur identifiant (l'entité vit
186
- * ailleurs) ; un raccourci et un dossier portent l'objet lui-même, parce que
187
- * rien d'autre ne les décrit. Les deux familles d'identifiants ne peuvent pas
188
- * se confondre — les fonctionnalités forment un enum fermé, les appareils sont
189
- * des UUID — et c'est {@link homeTileKind} qui tranche, en un seul endroit.
190
- *
191
- * Conséquence utile : les dispositions écrites avant l'unification restent
192
- * valides telles quelles. Leurs sections portent encore un champ `kind`, qui
193
- * tombe à la lecture comme n'importe quelle clé inconnue, et la première
194
- * écriture le fait disparaître. Rien à migrer, rien à rattraper au chargement.
144
+ * Une tuile de l'accueil : appareil, fonctionnalité, raccourci ou dossier. Un
145
+ * appareil et une fonctionnalité SONT leur identifiant ; un raccourci et un
146
+ * dossier portent l'objet. Les deux familles d'identifiants ne se confondent
147
+ * pas (enum fermé contre UUID), et {@link homeTileKind} tranche en un seul
148
+ * endroit.
195
149
  */
196
150
  export const homeTileSchema = z.union([
197
151
  homeFolderSchema,
@@ -212,11 +166,8 @@ export type HomeTileKind = 'device' | 'feature' | 'shortcut' | 'folder';
212
166
  export const HOME_FEATURE_IDS = nativeHomeFeatureIdSchema.options;
213
167
 
214
168
  /**
215
- * Le genre d'une tuile.
216
- *
217
- * **Le seul endroit qui connaisse la forme de l'union** : tout le reste passe
218
- * par lui ou par les gardes ci-dessous, donc changer la représentation ne se
219
- * paye qu'ici.
169
+ * Le genre d'une tuile. Le seul endroit qui connaisse la forme de l'union :
170
+ * changer la représentation ne se paye qu'ici.
220
171
  */
221
172
  export function homeTileKind(tile: HomeTile): HomeTileKind {
222
173
  if (typeof tile !== 'string') return 'kind' in tile ? 'folder' : 'shortcut';
@@ -254,31 +205,19 @@ const sectionBase = {
254
205
  id: z.string().min(1).max(64),
255
206
  /** User-chosen heading; absent → the section renders untitled on the home. */
256
207
  title: z.string().max(40).optional(),
257
- /**
258
- * The section can be folded away from the home.
259
- *
260
- * Absent (the default) → it always shows, and there is nothing to click:
261
- * a chevron on a section nobody wants to fold is one more thing to ignore.
262
- */
208
+ /** The section can be folded away. Absent = always shown, no chevron. */
263
209
  collapsible: z.boolean().optional(),
264
210
  /**
265
- * It starts folded.
266
- *
267
- * Only meaningful alongside `collapsible` — a section that cannot be
268
- * unfolded but starts folded would simply be invisible. The home enforces
269
- * that pairing rather than trusting the flag on its own.
211
+ * Starts folded. Only meaningful alongside `collapsible`; the home enforces
212
+ * the pairing rather than trusting the flag on its own.
270
213
  */
271
214
  collapsed: z.boolean().optional()
272
215
  };
273
216
 
274
217
  /**
275
- * Une section : une rangée ordonnée de tuiles, de n'importe quels genres.
276
- *
277
- * Elle ne se distingue plus par ce qu'elle tient — un appareil, une
278
- * fonctionnalité et un raccourci cohabitent dans la même — mais par son seul
279
- * `id`. Une ligne peut donc mêler une carte de pleine hauteur et des cartes
280
- * courtes : c'est assumé, la grille aligne les hauts et laisse les cartes
281
- * courtes à leur taille.
218
+ * Une section : une rangée ordonnée de tuiles de n'importe quels genres,
219
+ * distinguée par son seul `id`. Une ligne peut mêler une carte de pleine
220
+ * hauteur et des cartes courtes.
282
221
  */
283
222
  export const homeSectionSchema = z.object({
284
223
  ...sectionBase,