@volter/world-console 3.0.1 → 3.0.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.
@@ -535,10 +535,10 @@ const TABS = ['changes', 'events', 'trace', 'vendors', 'screens', 'canvas', 'bra
535
535
  type Tab = (typeof TABS)[number] | 'overview';
536
536
  /** A workspace screen of a twin's vendor, as its `GET /twin` lists it (Viewing a World). */
537
537
  type Workspace = { id: string; path: string };
538
- /** Where a twin's vendor screens are shown: its first workspace that names no parameter, at the twin's place in the
539
- * World; null when it lists none. */
538
+ /** Where a twin's vendor screens are shown: its root workspace (the vendor's home, as a dashboard's root is), else its
539
+ * first that names no parameter, at the twin's place in the World; null when it lists none. */
540
540
  const screenUrl = (name: string, t: { twin: string; screens?: Workspace[] } | undefined): string | null => {
541
- const shown = t?.screens?.find((w) => !/[{<]/.test(w.path));
541
+ const shown = t?.screens?.find((w) => w.path.replace(/\/+$/, '') === '') ?? t?.screens?.find((w) => !/[{<]/.test(w.path));
542
542
  return t && shown ? `${WORLDS}/${name}/${t.twin}${shown.path.startsWith('/') ? shown.path : `/${shown.path}`}` : null;
543
543
  };
544
544
  /** What an entry did, in words: the thing's kind and the vendor's verb, the thing's own label where it has one. */
@@ -611,6 +611,10 @@ function WorldView({ name, token, twin, tab, arg, sub, admin, snapshot, parentTo
611
611
 
612
612
  const current: Tab = (TABS as readonly string[]).includes(tab) ? (tab as Tab) : 'overview';
613
613
  const vendors = twins.state === 'ok' ? twins.data.twins.map((t) => t.twin) : [];
614
+ // an address naming no vendor of this World (mistyped, or guessed) says so, never shows as a vendor that stores nothing
615
+ if (twin && twins.state === 'ok' && !vendors.includes(twin)) {
616
+ return <main className="center"><Empty title={`${name} has no ${twin}`} action={<a href={`${BASE}/${name}`} onClick={(e) => { e.preventDefault(); go(name.split('/')); }}>Open {name}</a>}><p>No vendor of this World is called {twin}. Its changesets are under Changes › History.</p></Empty></main>;
617
+ }
614
618
  const chosen = current === 'vendors' && arg ? arg : undefined;
615
619
  const retry = (): void => { setScope('pending'); setAttempt((a) => a + 1); };
616
620
  // ALL SCREENS is a canvas app's own page: the whole window, the World's pages a click away (Figma's, tldraw's)
@@ -1791,12 +1795,19 @@ const changesetChanges = (c: ChangesetView): Array<{ twin: string; entry: Entry
1791
1795
  * here), or a push receipt `confirmed` (the remote performed it). Every action performed is Deployed, some is Partly
1792
1796
  * deployed; a refused or failed one is said as a failure; otherwise the push decides: every change replayed at the remote
1793
1797
  * is Pushed (the remote World, never a vendor), else Not sent. */
1794
- function sentOf(c: ChangesetView): { label: string; tone: 'done' | 'good' | 'warn' | 'bad' | 'plain'; performed: number; failed: number; total: number } {
1795
- const total = c.actions.length;
1798
+ /** Whether an action is the World's own (a door, a screen, the gap, the clock or a seed made it): no deploy offers it to
1799
+ * the vendor (the kernel's settlingEntries), so it is never counted as waiting to be performed. */
1800
+ const worldOwn = (a: ChangesetView['actions'][number]): boolean => {
1801
+ const input = ((a.action as { input?: Record<string, unknown> }).input ?? {});
1802
+ return !(typeof input.operationId === 'string' || (input.graphql !== undefined && input.graphql !== null));
1803
+ };
1804
+ function sentOf(c: ChangesetView): { label: string; tone: 'done' | 'good' | 'warn' | 'bad' | 'plain'; performed: number; failed: number; total: number; own: number } {
1805
+ const own = c.actions.filter(worldOwn).length;
1806
+ const total = c.actions.length - own;
1796
1807
  const pushed = c.applied?.receipts ?? []; const deployed = c.deployed ?? [];
1797
1808
  const performed = new Set([...deployed.filter((r) => r.status === 'deployed'), ...pushed.filter((r) => r.status === 'confirmed')].map((r) => r.actionId)).size;
1798
1809
  const failed = new Set([...deployed.filter((r) => r.status === 'refused' || r.status === 'failed'), ...pushed.filter((r) => r.status === 'failed')].map((r) => r.actionId)).size;
1799
- const n = { performed, failed, total };
1810
+ const n = { performed, failed, total, own };
1800
1811
  if (total > 0 && performed >= total) return { label: 'Deployed', tone: 'done', ...n };
1801
1812
  if (performed > 0) return { label: `Partly deployed: ${performed} of ${total}`, tone: 'warn', ...n };
1802
1813
  if (failed > 0) return { label: `${failed} refused or failed`, tone: 'bad', ...n };
@@ -1899,7 +1910,7 @@ function ChangesetPage({ name, token, signsAs, c, canWrite, rooted, refused, onC
1899
1910
  <h4>Where it went</h4>
1900
1911
  <div className="block where">
1901
1912
  {sent.performed > 0 ? (
1902
- <p className="pad">{sent.performed >= sent.total ? 'Deployed' : 'Partly deployed'}: {sent.performed} of {sent.total} change{sent.total === 1 ? '' : 's'} performed at {listed([...new Set([...here.map((r) => r.service), ...receipts.filter((r) => r.status === 'confirmed').map((r) => r.service)].map(vendorName))])}{here.length ? ` (${here.length} from here` : ' ('}{here.length && byRemote ? ', ' : ''}{byRemote ? `${byRemote} by the remote` : ''}).</p>
1913
+ <p className="pad">{sent.performed >= sent.total ? 'Deployed' : 'Partly deployed'}: {sent.performed} of {sent.total} change{sent.total === 1 ? '' : 's'}{sent.own ? ` (and ${sent.own} of the World's own, which stay in it)` : ''} performed at {listed([...new Set([...here.map((r) => r.service), ...receipts.filter((r) => r.status === 'confirmed').map((r) => r.service)].map(vendorName))])}{here.length ? ` (${here.length} from here` : ' ('}{here.length && byRemote ? ', ' : ''}{byRemote ? `${byRemote} by the remote` : ''}).</p>
1903
1914
  ) : sent.label === 'Pushed' ? (
1904
1915
  <p className="pad">Pushed to the remote{there ? <> <code>{there}</code></> : null}{c.applied ? <> · <At iso={c.applied.at} /></> : null}: every change was replayed there. No vendor has performed any of it.</p>
1905
1916
  ) : sent.failed === 0 ? (
@@ -2123,7 +2134,9 @@ function usePushPull(name: string, token: string, tick: number, refused: (r: Res
2123
2134
  };
2124
2135
  const there = origin.state === 'ok' && origin.data.origin ? originAddress(origin.data.origin) : null;
2125
2136
  const changes = diff.state === 'ok' ? diff.data.changes.length : null;
2126
- const waiting = sets.state === 'ok' ? unpushed(sets.data.changesets).sort((a, b) => a.createdAt.localeCompare(b.createdAt)) : [];
2137
+ // what waits to be pushed: this World's own changesets, and only when it has a remote to push them to (a shared World
2138
+ // holds what others pushed, which is theirs)
2139
+ const waiting = sets.state === 'ok' && there ? unpushed(sets.data.changesets.filter((c) => !c.pushedBy)).sort((a, b) => a.createdAt.localeCompare(b.createdAt)) : [];
2127
2140
  const push = async (): Promise<void> => {
2128
2141
  const b = await send<{ pushed: Array<{ changeset: string; outcome: string; receipts?: Array<{ status: string }>; refusal?: string[] }> }>('Push', 'origin/push', { method: 'POST' });
2129
2142
  if (!b) return;