@volter/world-console 2.0.6 → 2.0.7

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.
@@ -427,6 +427,9 @@ type Placed = { twin: string; position: number; entry: Entry };
427
427
  type Timeline = { entries: Placed[]; next: string | null };
428
428
  type Diff = { base: { id: string; at: string | null }; changes: Array<{ twin: string; entry: Entry }> };
429
429
  type Branch = { name: string; at?: { instant?: string }; createdAt: string; expiresAt: string | null };
430
+ /** The branches door: the host's snapshots of this World, and the World's own named branches (`volter world branch`),
431
+ * one of which, `checkedOut`, is the one this serve holds. */
432
+ type BranchesDoor = { branches: Branch[]; checkedOut?: string; named?: Array<{ name: string; running: boolean }> };
430
433
  /** What the World's session answered: its scope; `unknown` when the host has no session door (a write then says). */
431
434
  type Scope = 'pending' | 'read' | 'write' | 'unknown';
432
435
 
@@ -502,7 +505,7 @@ function WorldView({ name, token, twin, tab, arg, admin, snapshot, onReturn, ope
502
505
  const [clockVersion, setClockVersion] = useState(0);
503
506
  const clock = useDoor<Clock>(token, `/-/${name}/clock`, tick + clockVersion);
504
507
  const twins = useDoor<{ twins: Twin[] }>(token, `/-/${name}/twins`);
505
- const branches = useDoor<{ branches: Branch[] }>(token, `/-/${name}/branches`, clockVersion);
508
+ const branches = useDoor<BranchesDoor>(token, `/-/${name}/branches`, clockVersion);
506
509
  const map = useDoor<WorldMap>(token, `/-/${name}/map`, Math.floor(tick / 2));
507
510
  // branches are the host's: a host that does not answer the door has no "as of", and the console shows none
508
511
  const hasBranches = !(branches.state === 'error' && (branches.status === 404 || branches.status === 405));
@@ -613,13 +616,13 @@ function Connect({ name }: { name: string }): React.JSX.Element {
613
616
 
614
617
  /** The branch picker, between the World's pages and the branch's (where Neon's sits): this World live, or one of the
615
618
  * host's branches of it; a snapshot names its parent as the way back. */
616
- function BranchPicker({ name, snapshot, branches, opens }: { name: string; snapshot: Snapshot | null; branches: Load<{ branches: Branch[] }> | null; opens: (name: string) => boolean }): React.JSX.Element {
619
+ function BranchPicker({ name, snapshot, branches, opens }: { name: string; snapshot: Snapshot | null; branches: Load<BranchesDoor> | null; opens: (name: string) => boolean }): React.JSX.Element {
617
620
  const short = (n: string): string => n.split('/').pop() ?? n;
618
621
  const live = snapshot?.parent ?? name;
619
622
  const others = branches?.state === 'ok' ? branches.data.branches.filter((b) => b.name !== name) : [];
620
623
  return (
621
624
  <details className="menu branch-picker">
622
- <summary><svg className="branch-glyph" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><path fill="currentColor" d="M5 3.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Zm0 2.122a2.25 2.25 0 1 0-1.5 0v5.256a2.25 2.25 0 1 0 1.5 0V9.5h4.25A2.25 2.25 0 0 0 11.5 7.25v-.878a2.25 2.25 0 1 0-1.5 0v.878a.75.75 0 0 1-.75.75H5V5.372ZM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm6.5-9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z" /></svg><span className="branch-name">{snapshot ? short(name) : short(live)}</span><span className="badge">{snapshot ? 'snapshot' : 'live'}</span></summary>
625
+ <summary><svg className="branch-glyph" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><path fill="currentColor" d="M5 3.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Zm0 2.122a2.25 2.25 0 1 0-1.5 0v5.256a2.25 2.25 0 1 0 1.5 0V9.5h4.25A2.25 2.25 0 0 0 11.5 7.25v-.878a2.25 2.25 0 1 0-1.5 0v.878a.75.75 0 0 1-.75.75H5V5.372ZM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm6.5-9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z" /></svg><span className="branch-name">{snapshot ? short(name) : (branches?.state === 'ok' && branches.data.checkedOut) || short(live)}</span><span className="badge">{snapshot ? 'snapshot' : 'live'}</span></summary>
623
626
  <div className="popover">
624
627
  <p className="mode">Branch</p>
625
628
  <ul className="branch-list">
@@ -631,7 +634,17 @@ function BranchPicker({ name, snapshot, branches, opens }: { name: string; snaps
631
634
  : <span className="mode" title="Its token was handed to whoever made it">{short(b.name)}</span>}</li>
632
635
  ))}
633
636
  </ul>
634
- {branches?.state === 'ok' && others.length === 0 && !snapshot ? <p className="mode">No branches yet. “View an earlier moment…” opens one.</p> : null}
637
+ {branches?.state === 'ok' && (branches.data.named?.length ?? 0) > 1 ? (
638
+ <>
639
+ <p className="mode">This World's branches</p>
640
+ <ul className="branch-list">
641
+ {branches.data.named!.map((b) => (
642
+ <li key={b.name}><span aria-current={b.name === branches.data.checkedOut ? 'true' : undefined}>{b.name} <span className="mode">{b.name === branches.data.checkedOut ? 'served here' : <code>volter world checkout {b.name}</code>}</span></span></li>
643
+ ))}
644
+ </ul>
645
+ </>
646
+ ) : null}
647
+ {branches?.state === 'ok' && others.length === 0 && !snapshot && (branches.data.named?.length ?? 0) <= 1 ? <p className="mode">No branches yet. “View an earlier moment…” opens one.</p> : null}
635
648
  </div>
636
649
  </details>
637
650
  );
@@ -700,7 +713,7 @@ function BranchOverview({ name, token, map, vendors, tick }: { name: string; tok
700
713
  * Neon with how to connect and the branches. A World's live preview is its vendors' own screens; beside them, how an
701
714
  * app connects and the branches; under them, what just happened. Counts sit with what they count, not in a row. */
702
715
  const THUMB_SCALE = 0.2;
703
- function WorldOverview({ name, token, map, vendors, twins, session, branches, canWrite, tick, snapshotOf }: { name: string; token: string; map: Load<WorldMap>; vendors: string[]; twins: Load<{ twins: Twin[] }>; session: MirrorSession; branches: Load<{ branches: Branch[] }> | null; canWrite: boolean; tick: number; /** the World this one is a snapshot of, when it is one */ snapshotOf?: string }): React.JSX.Element {
716
+ function WorldOverview({ name, token, map, vendors, twins, session, branches, canWrite, tick, snapshotOf }: { name: string; token: string; map: Load<WorldMap>; vendors: string[]; twins: Load<{ twins: Twin[] }>; session: MirrorSession; branches: Load<BranchesDoor> | null; canWrite: boolean; tick: number; /** the World this one is a snapshot of, when it is one */ snapshotOf?: string }): React.JSX.Element {
704
717
  const diff = useDoor<Diff>(token, `/-/${name}/diff`, Math.floor(tick / 2));
705
718
  const recent = useDoor<Timeline>(token, `/-/${name}/timeline?limit=8`, tick);
706
719
  const rows = map.state === 'ok' ? map.data.twins : [];
@@ -867,7 +880,7 @@ function AsOf({ name, token, clock, refused }: { name: string; token: string; cl
867
880
  }
868
881
 
869
882
  /** Overview: what the World holds, one card per twin (a resource explorer), and the branches cut from it. */
870
- function Overview({ name, token, canWrite, branches, opens, onBranchesChanged, refused }: { name: string; token: string; canWrite: boolean; branches: Load<{ branches: Branch[] }> | null; opens: (name: string) => boolean; onBranchesChanged: () => void; refused: (r: Response) => void }): React.JSX.Element {
883
+ function Overview({ name, token, canWrite, branches, opens, onBranchesChanged, refused }: { name: string; token: string; canWrite: boolean; branches: Load<BranchesDoor> | null; opens: (name: string) => boolean; onBranchesChanged: () => void; refused: (r: Response) => void }): React.JSX.Element {
871
884
  const map = useDoor<WorldMap>(token, `/-/${name}/map`);
872
885
  const [note, setNote] = useState<string | null>(null);
873
886
  const remove = async (b: string): Promise<void> => {
@@ -918,7 +931,7 @@ function Overview({ name, token, canWrite, branches, opens, onBranchesChanged, r
918
931
  /** A World's branches, as a database's are (Neon's Branches page): each a copy of it as of a moment, made here (as of now
919
932
  * or a moment you pick, for as long as you say), opened read-only in this tab, compared with its parent (what it changed
920
933
  * since it branched), reset to its parent as it is now, or removed. Each is the World's own branches door. */
921
- function BranchList({ name, token, canWrite, branches, opens, note, remove, onChanged, refused }: { name: string; token: string; canWrite: boolean; branches: Load<{ branches: Branch[] }>; opens: (name: string) => boolean; note: string | null; remove: (b: string) => Promise<void>; onChanged: () => void; refused: (r: Response) => void }): React.JSX.Element {
934
+ function BranchList({ name, token, canWrite, branches, opens, note, remove, onChanged, refused }: { name: string; token: string; canWrite: boolean; branches: Load<BranchesDoor>; opens: (name: string) => boolean; note: string | null; remove: (b: string) => Promise<void>; onChanged: () => void; refused: (r: Response) => void }): React.JSX.Element {
922
935
  const short = (n: string): string => n.split('/').pop() ?? n;
923
936
  const list = branches.state === 'ok' ? branches.data.branches : [];
924
937
  const [making, setMaking] = useState(false); const [busy, setBusy] = useState(false); const [error, setError] = useState<string | null>(null);
@@ -973,7 +986,7 @@ function BranchList({ name, token, canWrite, branches, opens, note, remove, onCh
973
986
  <table className="dense" aria-label="Branches">
974
987
  <thead><tr><th id="branches">Branch</th><th>Parent</th><th>As of</th><th>Made</th><th>Goes away</th><th /></tr></thead>
975
988
  <tbody>
976
- <tr><td><b>{short(name)}</b> <span className="badge">default</span></td><td className="mode">—</td><td className="mode">live</td><td className="mode">—</td><td className="mode">kept</td><td /></tr>
989
+ <tr><td><b>{branches.data.checkedOut ?? short(name)}</b> <span className="badge">{branches.data.checkedOut ? 'served here' : 'default'}</span></td><td className="mode">—</td><td className="mode">live</td><td className="mode">—</td><td className="mode">kept</td><td /></tr>
977
990
  {list.map((b) => {
978
991
  const items = menu(b);
979
992
  return (
@@ -992,9 +1005,27 @@ function BranchList({ name, token, canWrite, branches, opens, note, remove, onCh
992
1005
  })}
993
1006
  </tbody>
994
1007
  </table>
995
- {list.length === 0 ? <p className="mode pad">No branches yet.</p> : null}
1008
+ {list.length === 0 ? <p className="mode pad">No snapshots yet.</p> : null}
996
1009
  </div>
997
1010
  ) : null}
1011
+ {branches.state === 'ok' && (branches.data.named?.length ?? 0) > 0 ? (
1012
+ <>
1013
+ <p className="lede">This World's own branches (<code>volter world branch</code>). One runs at a time; this page shows the one served here. Another is served by stopping this serve, <code>volter world checkout &lt;branch&gt;</code>, and serving again.</p>
1014
+ <div className="block">
1015
+ <table className="dense" aria-label="World branches">
1016
+ <thead><tr><th>Branch</th><th>State</th></tr></thead>
1017
+ <tbody>
1018
+ {branches.data.named!.map((b) => (
1019
+ <tr key={b.name}>
1020
+ <td><b>{b.name}</b>{b.name === branches.data.checkedOut ? <> <span className="badge">served here</span></> : null}</td>
1021
+ <td className="mode">{b.running ? 'running' : 'stopped'}</td>
1022
+ </tr>
1023
+ ))}
1024
+ </tbody>
1025
+ </table>
1026
+ </div>
1027
+ </>
1028
+ ) : null}
998
1029
  <Dialog open={making} onClose={() => { if (!busy) setMaking(false); }} title={`New branch of ${short(name)}`} footer={<><button type="button" onClick={() => setMaking(false)} disabled={busy}>Cancel</button><button type="submit" form="new-branch" className="primary" disabled={busy}>{busy ? 'Making…' : 'Make branch'}</button></>}>
999
1030
  <form id="new-branch" onSubmit={(e) => void create(e)} className="stack">
1000
1031
  <label><span>Name <span className="mode">(optional)</span></span><input name="branchLabel" value={label} onChange={(e) => setLabel(e.target.value.toLowerCase())} placeholder="checkout-flow" pattern="[a-z0-9][a-z0-9-]{0,23}" maxLength={24} /></label>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/world-console",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "description": "The dashboard of Volter World: a World's pages, the same on a laptop, a self-hosted host and the hosted cloud — mounted by the host at /-/console/. A pure frontend over the World's own doors; its kit (./kit) is the pieces the platform's pages share.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -23,8 +23,8 @@
23
23
  "react-dom": "^19.2.7"
24
24
  },
25
25
  "devDependencies": {
26
- "@volter/world-host": "2.0.5",
27
- "@volter/world-runtime": "2.0.6",
26
+ "@volter/world-host": "2.0.11",
27
+ "@volter/world-runtime": "2.0.12",
28
28
  "@volter/world-tooling": "0.1.0",
29
29
  "@types/bun": "^1.2.20",
30
30
  "@types/node": "^24.0.0",