@volter/teams 0.1.89 → 0.1.90

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 (2) hide show
  1. package/package.json +1 -1
  2. package/viewer/viewer.mjs +26 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/teams",
3
- "version": "0.1.89",
3
+ "version": "0.1.90",
4
4
  "type": "module",
5
5
  "description": "Volter Teams, the neutral team plane: machine identity and daemon core, the local door and server link, admission from machine grants, product doors, and the Teams server's neutral routes (company RFC 0008).",
6
6
  "license": "MIT",
package/viewer/viewer.mjs CHANGED
@@ -224,7 +224,27 @@ async function showPane(connect, key, control) {
224
224
  fit.fit();
225
225
  let pane = null;
226
226
  // Only a controller sizes the pane; an observer sees it at the size its controller keeps.
227
- new ResizeObserver(() => { fit.fit(); if (control) pane?.resize(terminal.cols, terminal.rows); }).observe(element);
227
+ // xterm fitting changes layout. Defer it out of the observer's delivery cycle
228
+ // so a normal pane resize does not become a browser ResizeObserver failure.
229
+ let fitFrame = null;
230
+ let sentSize = null;
231
+ const observer = new ResizeObserver(() => {
232
+ if (fitFrame !== null) return;
233
+ fitFrame = requestAnimationFrame(() => {
234
+ fitFrame = null;
235
+ fit.fit();
236
+ const size = `${terminal.cols}x${terminal.rows}`;
237
+ if (control && pane && size !== sentSize) {
238
+ sentSize = size;
239
+ pane.resize(terminal.cols, terminal.rows);
240
+ }
241
+ });
242
+ });
243
+ observer.observe(element);
244
+ window.addEventListener('pagehide', () => {
245
+ observer.disconnect();
246
+ if (fitFrame !== null) cancelAnimationFrame(fitFrame);
247
+ }, { once: true });
228
248
  if (control) terminal.onData((data) => pane?.write(data));
229
249
  const attach = async () => {
230
250
  status('Connecting…');
@@ -237,11 +257,12 @@ async function showPane(connect, key, control) {
237
257
  pane.on('close', (reason) => {
238
258
  pane = null;
239
259
  channel.close();
240
- // The server ends a channel when its token does: reopen with a fresh one. Any other close is shown and stays.
241
- if (/access_revoked/.test(String(reason))) { status('Access ended: the workplace no longer grants this pane.'); return; }
242
- if (!/token_expired/.test(String(reason))) { status(`Disconnected: ${reason ?? 'closed'}`); return; }
260
+ // Revocation stays ended. A fresh manual retry asks the workplace for authority again.
261
+ if (/access_revoked/.test(String(reason))) { failed('terminal', new Error('Access ended: the workplace no longer grants this pane.')); return; }
262
+ // Token expiry reconnects; other closes reach the host's existing unavailable / retry state.
263
+ if (!/token_expired/.test(String(reason))) { failed('terminal', new Error(`Disconnected: ${reason ?? 'closed'}`)); return; }
243
264
  status('Reconnecting…');
244
- attach().catch((error) => status(error.message));
265
+ attach().catch((error) => failed('terminal', error));
245
266
  });
246
267
  };
247
268
  await attach();