@proulr/sdui-contract 0.1.20 → 0.1.22

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proulr/sdui-contract",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -387,6 +387,18 @@ export type SduiScreenChromeMetaWire = {
387
387
  self_managed?: boolean;
388
388
  hub_kind?: 'inbox' | null;
389
389
  wide_layout?: 'chat_master_detail' | 'vibes_rail' | null;
390
+ /**
391
+ * Screen id da listagem que ocupa o painel mestre no wide. Obrigatório quando a
392
+ * rota é a própria tela de detalhe (`/s/chat.thread?conversation_id=…`): sem ele
393
+ * o shell renderizaria o wire do detalhe também na master — thread duplicada.
394
+ */
395
+ wide_master_screen_id?: string;
396
+ /**
397
+ * Destino fixo do botão voltar, ignorando o histórico. Usado quando a tela que
398
+ * antecedeu esta deixou de fazer sentido (ex.: conversa do Randoms que virou DM —
399
+ * voltar cai na lista de mensagens, não na fila).
400
+ */
401
+ back_screen_id?: string;
390
402
  surface?: 'discover' | 'vibes' | null;
391
403
  header_slot?: boolean;
392
404
  pull_refresh?: boolean;
@@ -396,6 +408,18 @@ export type SduiScreenChromeMetaWire = {
396
408
 
397
409
  export type SduiScreenMetaWire = {
398
410
  cache_ttl_ms?: number;
411
+ /**
412
+ * Intervalo mínimo (ms) entre fetches desta tela — throttle do prefetch forçado
413
+ * (resume/focus), onde o `cache_ttl_ms` não chega porque o app revalida por
414
+ * evento, não por expiração.
415
+ */
416
+ min_fetch_interval_ms?: number;
417
+ /**
418
+ * Revisão do wire, calculada pelo bff-core a partir do mesmo digest que gera o
419
+ * ETag. Serve para o app comparar conteúdo entre fetches guardados no disco
420
+ * (onde o header HTTP não sobrevive) sem manter o ETag como estado de produto.
421
+ */
422
+ rev?: string;
399
423
  offline_policy?: SduiOfflinePolicy;
400
424
  bff?: string;
401
425
  chrome?: SduiScreenChromeMetaWire;
@@ -453,6 +477,10 @@ export type SduiScreenChromeMeta = {
453
477
  selfManaged?: boolean;
454
478
  hubKind?: 'inbox' | null;
455
479
  wideLayout?: 'chat_master_detail' | 'vibes_rail' | null;
480
+ /** Listing screen rendered in the wide master pane when this screen is the detail. */
481
+ wideMasterScreenId?: string;
482
+ /** Screen the back button goes to, bypassing history (the previous screen no longer makes sense). */
483
+ backScreenId?: string;
456
484
  surface?: 'discover' | 'vibes' | null;
457
485
  headerSlot?: boolean;
458
486
  pullRefresh?: boolean;
@@ -462,6 +490,9 @@ export type SduiScreenChromeMeta = {
462
490
 
463
491
  export type SduiScreenMeta = {
464
492
  cacheTtlMs: number;
493
+ minFetchIntervalMs?: number;
494
+ /** Revisão do wire (digest do bff-core) — mesma origem do ETag da resposta. */
495
+ rev?: string;
465
496
  offlinePolicy: SduiOfflinePolicy;
466
497
  bff?: string;
467
498
  chrome?: SduiScreenChromeMeta;
@@ -42,6 +42,25 @@ describe('decodeSduiScreenWire', () => {
42
42
  });
43
43
  });
44
44
 
45
+ it('maps meta.rev and throttles min_fetch_interval_ms to positive numbers', () => {
46
+ const screen = decodeSduiScreenWire({
47
+ schema_version: 1,
48
+ screen_id: 'chat.inbox',
49
+ blocks: [],
50
+ meta: { rev: 'abc123', min_fetch_interval_ms: 30_000 },
51
+ });
52
+ expect(screen.meta).toMatchObject({ rev: 'abc123', minFetchIntervalMs: 30_000 });
53
+
54
+ const junk = decodeSduiScreenWire({
55
+ schema_version: 1,
56
+ screen_id: 'chat.inbox',
57
+ blocks: [],
58
+ meta: { rev: '', min_fetch_interval_ms: 0 },
59
+ });
60
+ expect(junk.meta).not.toHaveProperty('rev');
61
+ expect(junk.meta).not.toHaveProperty('minFetchIntervalMs');
62
+ });
63
+
45
64
  it('decodes native_capability actions with capability_id', () => {
46
65
  const screen = decodeSduiScreenWire({
47
66
  schema_version: 1,
@@ -73,6 +92,8 @@ describe('decodeSduiScreenWire', () => {
73
92
  surface: 'discover',
74
93
  hub_kind: 'inbox',
75
94
  wide_layout: 'chat_master_detail',
95
+ wide_master_screen_id: 'chat.inbox',
96
+ back_screen_id: 'chat.inbox',
76
97
  owns_header: false,
77
98
  self_managed: false,
78
99
  auto_action_id: 'open_random_thread',
@@ -86,6 +107,8 @@ describe('decodeSduiScreenWire', () => {
86
107
  selfManaged: false,
87
108
  hubKind: 'inbox',
88
109
  wideLayout: 'chat_master_detail',
110
+ wideMasterScreenId: 'chat.inbox',
111
+ backScreenId: 'chat.inbox',
89
112
  surface: 'discover',
90
113
  headerSlot: true,
91
114
  pullRefresh: true,
@@ -76,6 +76,8 @@ function mapChromeMetaWire(
76
76
  selfManaged: wire.self_managed,
77
77
  hubKind: wire.hub_kind,
78
78
  wideLayout: wire.wide_layout,
79
+ wideMasterScreenId: wire.wide_master_screen_id,
80
+ backScreenId: wire.back_screen_id,
79
81
  surface: wire.surface,
80
82
  headerSlot: wire.header_slot,
81
83
  pullRefresh: wire.pull_refresh,
@@ -84,8 +86,14 @@ function mapChromeMetaWire(
84
86
  }
85
87
 
86
88
  function mapMetaWire(wire: SduiScreenMetaWire | undefined): SduiScreenMeta {
89
+ const minFetchIntervalMs =
90
+ typeof wire?.min_fetch_interval_ms === 'number' && wire.min_fetch_interval_ms > 0
91
+ ? wire.min_fetch_interval_ms
92
+ : undefined;
87
93
  return {
88
94
  cacheTtlMs: wire?.cache_ttl_ms ?? DEFAULT_CACHE_TTL_MS,
95
+ ...(minFetchIntervalMs === undefined ? {} : { minFetchIntervalMs }),
96
+ ...(typeof wire?.rev === 'string' && wire.rev ? { rev: wire.rev } : {}),
89
97
  offlinePolicy: wire?.offline_policy ?? DEFAULT_OFFLINE_POLICY,
90
98
  bff: wire?.bff,
91
99
  chrome: mapChromeMetaWire(wire?.chrome),