meta-sovereign 0.53.2 → 0.53.3

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 (46) hide show
  1. package/README.md +2 -0
  2. package/js/src/server/local-certificate-check.js +82 -0
  3. package/js/src/server/local-tls.js +14 -19
  4. package/js/src/sources/telegram-chat-url.js +20 -8
  5. package/js/src/storage/doublets-mirror.js +19 -3
  6. package/js/src/storage/dual-store.js +11 -1
  7. package/js/src/web/app-navigation.js +61 -0
  8. package/js/src/web/app-route.js +205 -0
  9. package/js/src/web/app.css +233 -5
  10. package/js/src/web/app.js +14 -39
  11. package/js/src/web/app.min.js +92 -92
  12. package/js/src/web/automations-view.js +2 -2
  13. package/js/src/web/chat-archive-media.js +12 -4
  14. package/js/src/web/chat-calls.js +6 -2
  15. package/js/src/web/chat-history-load.js +1 -0
  16. package/js/src/web/chat-invite.js +3 -5
  17. package/js/src/web/chat-media.js +160 -62
  18. package/js/src/web/chat-message.js +3 -1
  19. package/js/src/web/chat-model.js +8 -1
  20. package/js/src/web/chat-photo.js +84 -71
  21. package/js/src/web/chat-remote-media.js +5 -2
  22. package/js/src/web/chat-round.js +16 -1
  23. package/js/src/web/chat-state.js +30 -16
  24. package/js/src/web/chat-view.js +58 -45
  25. package/js/src/web/chat-web-apps.js +18 -12
  26. package/js/src/web/locales/en-chat-more.js +8 -0
  27. package/js/src/web/locales/en.js +3 -2
  28. package/js/src/web/locales/hi-chat-more.js +8 -0
  29. package/js/src/web/locales/hi.js +3 -2
  30. package/js/src/web/locales/ru-chat-more.js +9 -0
  31. package/js/src/web/locales/ru.js +3 -2
  32. package/js/src/web/locales/zh-chat-more.js +8 -0
  33. package/js/src/web/locales/zh.js +3 -2
  34. package/js/src/web/media-loading.js +176 -0
  35. package/js/src/web/operator-model.js +11 -4
  36. package/js/src/web/operator-order.js +46 -0
  37. package/js/src/web/operator-prefs.js +1 -0
  38. package/js/src/web/operator-unread.js +2 -2
  39. package/js/src/web/operator-view.js +46 -15
  40. package/js/src/web/peer-avatar.js +14 -1
  41. package/js/src/web/person-panel.js +15 -8
  42. package/js/src/web/spinner.js +7 -2
  43. package/js/src/web/views.js +12 -18
  44. package/js/src/web/web-app-loading.js +81 -0
  45. package/js/src/web/web-apps-model.js +20 -1
  46. package/package.json +8 -2
@@ -514,6 +514,7 @@ main[data-view='chat'] > .web-mode-banner {
514
514
  font-size: 0.65rem;
515
515
  }
516
516
  .chat-main {
517
+ container: conversation / inline-size;
517
518
  position: relative;
518
519
  display: flex;
519
520
  flex-direction: column;
@@ -535,6 +536,52 @@ main[data-view='chat'] > .web-mode-banner {
535
536
  flex: 1 1 auto;
536
537
  min-width: 0;
537
538
  }
539
+ .chat-header-actions {
540
+ display: flex;
541
+ align-items: center;
542
+ flex-shrink: 0;
543
+ gap: var(--control-gap);
544
+ }
545
+ .chat-header-title > .meta,
546
+ .chat-title-row > strong {
547
+ overflow: hidden;
548
+ white-space: nowrap;
549
+ text-overflow: ellipsis;
550
+ min-width: 0;
551
+ }
552
+ @container conversation (max-width: 650px) {
553
+ .chat-header {
554
+ display: grid;
555
+ grid-template-columns: auto minmax(0, 1fr);
556
+ min-height: 6.5rem;
557
+ gap: var(--control-gap);
558
+ }
559
+ .chat-header-actions {
560
+ grid-column: 1 / -1;
561
+ justify-content: flex-end;
562
+ flex-wrap: wrap;
563
+ }
564
+ .chat-header-actions .chat-automation-label {
565
+ display: none;
566
+ }
567
+ .chat-header-actions .chat-menu-toggle {
568
+ padding-inline: 0.5rem;
569
+ }
570
+ .person-panel {
571
+ top: 6.5rem;
572
+ }
573
+ }
574
+ .chat-header {
575
+ flex-shrink: 0;
576
+ }
577
+ @media (max-width: 720px) {
578
+ .chat-header {
579
+ grid-template-columns: auto auto minmax(0, 1fr);
580
+ }
581
+ .chat-embedded .chat-header {
582
+ grid-template-columns: auto minmax(0, 1fr);
583
+ }
584
+ }
538
585
  .typing {
539
586
  color: var(--chat-accent);
540
587
  font-style: italic;
@@ -632,6 +679,9 @@ main[data-view='chat'] > .web-mode-banner {
632
679
  border-bottom: 1px solid var(--border);
633
680
  }
634
681
  .person-panel-body {
682
+ display: flex;
683
+ flex-direction: column;
684
+ gap: var(--control-gap);
635
685
  flex: 1;
636
686
  overflow-y: auto;
637
687
  padding: 0.6rem 1rem 1rem;
@@ -4199,6 +4249,100 @@ button.primary .recommended-tag {
4199
4249
  margin: 0;
4200
4250
  border-width: 2px;
4201
4251
  }
4252
+ /* Loading belongs to the media's reserved box, including before URL resolution. */
4253
+ .media-loading {
4254
+ position: absolute;
4255
+ inset: 0;
4256
+ z-index: 2;
4257
+ display: flex;
4258
+ flex-direction: column;
4259
+ align-items: center;
4260
+ justify-content: center;
4261
+ gap: var(--control-gap);
4262
+ color: #fff;
4263
+ pointer-events: none;
4264
+ text-align: center;
4265
+ }
4266
+ .media-loading > .spinner,
4267
+ .media-load-error {
4268
+ box-sizing: content-box;
4269
+ padding: 0.5rem;
4270
+ border-radius: 999px;
4271
+ background: rgb(0 0 0 / 55%);
4272
+ }
4273
+ .media-loading.is-failed button {
4274
+ pointer-events: auto;
4275
+ }
4276
+ .media-loading.is-small {
4277
+ position: static;
4278
+ display: inline-flex;
4279
+ }
4280
+ .media-loading.is-small > .spinner {
4281
+ padding: 0;
4282
+ background: transparent;
4283
+ }
4284
+ img[data-media-ready='false'] {
4285
+ opacity: 0;
4286
+ }
4287
+ .loading-image,
4288
+ .loading-player {
4289
+ position: relative;
4290
+ display: inline-grid;
4291
+ max-width: 100%;
4292
+ min-width: min(10rem, 100%);
4293
+ min-height: 6rem;
4294
+ overflow: hidden;
4295
+ }
4296
+ .loading-image > img {
4297
+ grid-area: 1 / 1;
4298
+ width: 100%;
4299
+ height: 100%;
4300
+ object-fit: contain;
4301
+ }
4302
+ .loading-image.media-poster {
4303
+ position: absolute;
4304
+ min-width: 0;
4305
+ min-height: 0;
4306
+ }
4307
+ .loading-image.is-compact {
4308
+ min-width: 0;
4309
+ min-height: 0;
4310
+ }
4311
+ .loading-image.is-compact .spinner,
4312
+ .peer-avatar .media-loading .spinner {
4313
+ width: 1rem;
4314
+ height: 1rem;
4315
+ padding: 0.125rem;
4316
+ }
4317
+ .loading-image.media-poster.blurred {
4318
+ filter: none;
4319
+ transform: none;
4320
+ }
4321
+ .loading-image.media-poster.blurred > img {
4322
+ filter: blur(10px);
4323
+ }
4324
+ .loading-player.loading-audio {
4325
+ min-height: 3.5rem;
4326
+ }
4327
+ .media-poster .media-loading {
4328
+ align-items: flex-end;
4329
+ justify-content: flex-end;
4330
+ padding: 0.5rem;
4331
+ }
4332
+ .media-poster .media-loading > .spinner {
4333
+ width: 1.25rem;
4334
+ height: 1.25rem;
4335
+ }
4336
+ :is(.bubble-video, .bubble-round):has(> video) > .media-poster {
4337
+ display: none;
4338
+ }
4339
+ .bubble-sticker {
4340
+ position: relative;
4341
+ }
4342
+ .bubble-sticker .loading-image {
4343
+ width: 100%;
4344
+ height: 100%;
4345
+ }
4202
4346
  @keyframes media-spin {
4203
4347
  to {
4204
4348
  transform: rotate(360deg);
@@ -4238,10 +4382,11 @@ button.primary .recommended-tag {
4238
4382
  align-items: flex-end;
4239
4383
  gap: 2px;
4240
4384
  height: 1.4rem;
4385
+ overflow: hidden;
4241
4386
  }
4242
4387
  .audio-wave i {
4243
4388
  flex: 1;
4244
- min-width: 2px;
4389
+ min-width: 0;
4245
4390
  border-radius: 2px;
4246
4391
  background: currentColor;
4247
4392
  opacity: 0.35;
@@ -4640,9 +4785,20 @@ button.primary .recommended-tag {
4640
4785
  flex: 1 1 auto;
4641
4786
  align-items: center;
4642
4787
  justify-content: center;
4643
- min-height: 60vh;
4788
+ min-height: 12rem;
4644
4789
  animation: loading-appear 0.25s ease-out 0.2s both;
4645
4790
  }
4791
+ /* A page loader uses the whole available view; pane loaders use their pane. */
4792
+ main:has(> .page-loading) {
4793
+ display: flex;
4794
+ flex-direction: column;
4795
+ min-height: calc(100dvh - var(--topbar-h));
4796
+ }
4797
+ .app-shell-compact main:has(> .page-loading) {
4798
+ min-height: calc(
4799
+ 100dvh - var(--topbar-h) - var(--bottom-nav-h) - var(--safe-bottom)
4800
+ );
4801
+ }
4646
4802
  @keyframes loading-appear {
4647
4803
  from {
4648
4804
  opacity: 0;
@@ -6121,7 +6277,35 @@ button[aria-busy='true']::before {
6121
6277
  display: flex;
6122
6278
  flex-direction: column;
6123
6279
  align-items: stretch;
6124
- gap: 0.25rem;
6280
+ gap: var(--control-gap);
6281
+ }
6282
+ .person-panel-body > * {
6283
+ flex-shrink: 0;
6284
+ min-width: 0;
6285
+ }
6286
+ .person-panel-body > :is(.person-section, .admin-actions) {
6287
+ margin-block: 0;
6288
+ }
6289
+ .person-panel-body > .person-section > button {
6290
+ width: 100%;
6291
+ }
6292
+ .person-panel-body > :is(button, a.button),
6293
+ .person-panel-body .peer-actions > button,
6294
+ .person-panel-body .peer-action {
6295
+ width: 100%;
6296
+ }
6297
+ .person-panel-body .contact-extras,
6298
+ .person-panel-body .contact-photo,
6299
+ .person-panel-body .contact-birthday,
6300
+ .person-panel-body .contact-note {
6301
+ gap: var(--control-gap);
6302
+ }
6303
+ .person-panel-body :is(.contact-photo, .contact-birthday, .contact-note) {
6304
+ flex-direction: column;
6305
+ align-items: stretch;
6306
+ }
6307
+ .telegram-open-hint {
6308
+ margin: var(--control-gap) 0 0;
6125
6309
  }
6126
6310
  .peer-action,
6127
6311
  .peer-reason {
@@ -8670,6 +8854,16 @@ span.bubble-effect {
8670
8854
  background: var(--bg-elev);
8671
8855
  pointer-events: none;
8672
8856
  }
8857
+ .web-app-loading.is-failed {
8858
+ flex-direction: column;
8859
+ gap: var(--control-gap);
8860
+ padding: 1.25rem;
8861
+ text-align: center;
8862
+ pointer-events: auto;
8863
+ }
8864
+ .web-app-loading.is-failed .row {
8865
+ justify-content: center;
8866
+ }
8673
8867
  .web-app-spinner {
8674
8868
  display: inline-block;
8675
8869
  width: 1.4rem;
@@ -12932,8 +13126,28 @@ audio.remote-player {
12932
13126
  scrollbar-width: thin;
12933
13127
  }
12934
13128
  .calls-content {
13129
+ display: flex;
13130
+ flex-direction: column;
13131
+ min-height: min(56vh, 460px);
13132
+ gap: var(--control-gap);
13133
+ }
13134
+ .calls-state {
13135
+ display: flex;
13136
+ flex: 1;
13137
+ align-items: center;
13138
+ justify-content: center;
12935
13139
  min-height: 12rem;
12936
13140
  }
13141
+ .person-panel-body
13142
+ :is(
13143
+ .admin-actions,
13144
+ .peer-actions,
13145
+ .contact-photo,
13146
+ .contact-note,
13147
+ .contact-birthday
13148
+ ) {
13149
+ gap: var(--control-gap);
13150
+ }
12937
13151
  .my-profile-btn {
12938
13152
  display: inline-flex;
12939
13153
  align-items: center;
@@ -12975,7 +13189,9 @@ audio.remote-player {
12975
13189
  color: #fff;
12976
13190
  }
12977
13191
  :root[data-theme='dark'][data-skin='t3'] .composer-send,
12978
- :root[data-theme='dark'][data-skin='t3'] button.primary {
13192
+ :root[data-theme='dark'][data-skin='t3'] button.primary,
13193
+ :root[data-theme='dark'][data-skin='t3'] .audio-play,
13194
+ :root[data-theme='dark'][data-skin='t3'] .reader-tab.active {
12979
13195
  background: #2c2d30;
12980
13196
  color: #fff;
12981
13197
  }
@@ -13032,7 +13248,12 @@ main[data-view='operator']:has(> .operator-workspace) {
13032
13248
  display: none;
13033
13249
  }
13034
13250
  .operator-empty {
13035
- margin: auto;
13251
+ display: flex;
13252
+ flex: 1;
13253
+ min-height: 12rem;
13254
+ flex-direction: column;
13255
+ align-items: center;
13256
+ justify-content: center;
13036
13257
  text-align: center;
13037
13258
  }
13038
13259
  .nav-icon {
@@ -13119,3 +13340,10 @@ main[data-view='operator']:has(> .operator-workspace) {
13119
13340
  :root[data-theme='dark'][data-skin='t3'] .bubble-check.is-checked {
13120
13341
  color: #000;
13121
13342
  }
13343
+
13344
+ .app-start-loading {
13345
+ display: flex;
13346
+ min-height: 100dvh;
13347
+ align-items: stretch;
13348
+ justify-content: center;
13349
+ }
package/js/src/web/app.js CHANGED
@@ -2,13 +2,7 @@
2
2
  // ./dom.js keeps the offline-first storage, discovery, WebRTC sync,
3
3
  // and server fallback contracts stable.
4
4
 
5
- import React, {
6
- useEffect,
7
- useLayoutEffect,
8
- useMemo,
9
- useRef,
10
- useState,
11
- } from 'react';
5
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
12
6
  import { createRoot } from 'react-dom/client';
13
7
  import { api } from './dom.js';
14
8
  import { installClientLog } from './client-log.js';
@@ -42,7 +36,9 @@ import { AddthemeHost, useApplyCloudTheme } from './cloud-themes.js';
42
36
  import { ContactLinkHost } from './contact-links.js';
43
37
  import { AppNotifications } from './app-notify.js';
44
38
  import { LockScreen, useLocalLock } from './passcode.js';
39
+ import { useAppNavigation } from './app-navigation.js';
45
40
  import { useNavigationCounts } from './navigation-counts.js';
41
+ import { PageLoading } from './spinner.js';
46
42
 
47
43
  // Browser e2e tests reach the booted api here: in the bundled build the
48
44
  // source modules are not each served, so `import('/dom.js')` fails.
@@ -95,7 +91,8 @@ const ViewNotifications = ({ active, t, onOpen }) =>
95
91
  : el(AppNotifications, { api, t, onOpen });
96
92
 
97
93
  const App = () => {
98
- const [active, setActive] = useState('chat');
94
+ const [route, navigate] = useAppNavigation();
95
+ const active = route.view;
99
96
  const counts = useNavigationCounts(api, active);
100
97
  const online = useOnlineMode();
101
98
  useApplyAppearance();
@@ -126,40 +123,12 @@ const App = () => {
126
123
  }
127
124
  }, [connected]);
128
125
 
129
- // Listen for cross-component "switch to view N and scroll to anchor"
130
- // events fired by the per-section "Connect first" CTAs (R-O7).
131
- useEffect(() => {
132
- const onNavigate = (event) => {
133
- const detail = event.detail ?? {};
134
- if (typeof detail.view === 'string') {
135
- setActive(detail.view);
136
- }
137
- if (typeof detail.anchor === 'string') {
138
- // Update the URL hash so the Connections view can open the
139
- // matching `id="conn-{provider}"` detail on first paint.
140
- if (globalThis.location) {
141
- globalThis.location.hash = detail.anchor;
142
- }
143
- }
144
- };
145
- globalThis.addEventListener('meta-sovereign:navigate', onNavigate);
146
- return () =>
147
- globalThis.removeEventListener('meta-sovereign:navigate', onNavigate);
148
- }, []);
149
126
  const openTutorial = () => {
150
127
  tutorial.reopen();
151
128
  setTutorialOpen(true);
152
129
  };
153
130
  const closeTutorial = () => setTutorialOpen(false);
154
131
  const View = useMemo(() => views[active] ?? views.chat, [active]);
155
- // Another section starts at its top, not at the old one's scroll.
156
- const shownView = useRef(active);
157
- useLayoutEffect(() => {
158
- if (shownView.current !== active) {
159
- shownView.current = active;
160
- globalThis.scrollTo?.(0, 0);
161
- }
162
- }, [active]);
163
132
 
164
133
  return el(LocaleContext.Provider, { value: localeApi }, [
165
134
  el(React.Fragment, { key: 'shell' }, [
@@ -207,7 +176,7 @@ const App = () => {
207
176
  key: 'shell-nav',
208
177
  width,
209
178
  active,
210
- onSelect: setActive,
179
+ onSelect: (view) => navigate({ view }),
211
180
  counts,
212
181
  },
213
182
  el(
@@ -248,7 +217,7 @@ const App = () => {
248
217
  key: 'notify',
249
218
  active,
250
219
  t,
251
- onOpen: () => setActive('chat'),
220
+ onOpen: () => navigate({ view: 'chat' }),
252
221
  }),
253
222
  el(TutorialOverlay, {
254
223
  key: 'tutorial-overlay',
@@ -269,7 +238,13 @@ const Root = () => {
269
238
  const lock = useLocalLock(api);
270
239
  return lock.phase === 'locked'
271
240
  ? el(LocaleContext.Provider, { value: localeApi }, el(LockScreen, { api }))
272
- : el(App);
241
+ : lock.phase === 'loading'
242
+ ? el(
243
+ LocaleContext.Provider,
244
+ { value: localeApi },
245
+ el('div', { className: 'app-start-loading' }, el(PageLoading))
246
+ )
247
+ : el(App);
273
248
  };
274
249
 
275
250
  const mount = document.getElementById('app');