@miadi/stateloom-web 0.1.2 → 0.1.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 (49) hide show
  1. package/package.json +1 -1
  2. package/server/.next/BUILD_ID +1 -1
  3. package/server/.next/build-manifest.json +2 -2
  4. package/server/.next/server/app/_global-error.html +2 -2
  5. package/server/.next/server/app/_global-error.rsc +1 -1
  6. package/server/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  7. package/server/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/server/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  9. package/server/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  10. package/server/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/server/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. package/server/.next/server/app/_not-found.html +1 -1
  13. package/server/.next/server/app/_not-found.rsc +2 -2
  14. package/server/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  15. package/server/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  16. package/server/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  17. package/server/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  18. package/server/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  19. package/server/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  20. package/server/.next/server/app/api/file/route.js.nft.json +1 -1
  21. package/server/.next/server/app/api/rispec/route.js.nft.json +1 -1
  22. package/server/.next/server/app/api/watch/route.js.nft.json +1 -1
  23. package/server/.next/server/app/index.html +1 -1
  24. package/server/.next/server/app/index.rsc +3 -3
  25. package/server/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  26. package/server/.next/server/app/index.segments/_full.segment.rsc +3 -3
  27. package/server/.next/server/app/index.segments/_head.segment.rsc +1 -1
  28. package/server/.next/server/app/index.segments/_index.segment.rsc +2 -2
  29. package/server/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  30. package/server/.next/server/app/page.js.nft.json +1 -1
  31. package/server/.next/server/app/page_client-reference-manifest.js +1 -1
  32. package/server/.next/server/chunks/ssr/{[root-of-the-server]__bfd16096._.js → [root-of-the-server]__2b35b711._.js} +1 -1
  33. package/server/.next/server/chunks/ssr/[root-of-the-server]__edbb995c._.js +3 -3
  34. package/server/.next/server/pages/404.html +1 -1
  35. package/server/.next/server/pages/500.html +2 -2
  36. package/server/.next/static/chunks/50f310b5d321b604.css +2 -0
  37. package/server/.next/static/chunks/b8543b198154e75c.js +10 -0
  38. package/server/package.json +1 -1
  39. package/server/src/app/globals.css +4 -9
  40. package/server/src/components/PresenceChips.tsx +77 -0
  41. package/server/src/components/SocketBridgeProvider.tsx +6 -22
  42. package/server/src/components/Toolbar.tsx +5 -0
  43. package/server/stateloom-web.json +2 -2
  44. package/server/tsconfig.tsbuildinfo +1 -1
  45. package/server/.next/static/chunks/0a0f393b77d3422b.css +0 -2
  46. package/server/.next/static/chunks/e29b67abadc7c68a.js +0 -10
  47. /package/server/.next/static/{Q7HQy4udl4A_DaAl9bdUt → WuhCSMxaHZvNZ4hY20AEo}/_buildManifest.js +0 -0
  48. /package/server/.next/static/{Q7HQy4udl4A_DaAl9bdUt → WuhCSMxaHZvNZ4hY20AEo}/_clientMiddlewareManifest.json +0 -0
  49. /package/server/.next/static/{Q7HQy4udl4A_DaAl9bdUt → WuhCSMxaHZvNZ4hY20AEo}/_ssgManifest.js +0 -0
@@ -108,15 +108,10 @@ body {
108
108
  }
109
109
  }
110
110
 
111
- /* Presence chips ride the top-right corner. Under `viewport-fit=cover` that
112
- corner can be beneath a notch or the status bar, so the offset is measured
113
- from the safe area rather than the raw viewport edge. */
114
- .presence-dock {
115
- position: fixed;
116
- top: calc(env(safe-area-inset-top) + 0.5rem);
117
- right: calc(env(safe-area-inset-right) + 1rem);
118
- z-index: 50;
119
- }
111
+ /* `.presence-dock` lived here: a fixed top-right rail of one pill per peer.
112
+ It grew with the room and painted itself across the toolbar's own controls,
113
+ so presence moved into the toolbar flow as a single fixed-width control —
114
+ see components/PresenceChips.tsx. Nothing floats over the navigation now. */
120
115
 
121
116
  /* The way back. A pinch on the chrome moves the root font size, and every length
122
117
  in this stylesheet is a multiple of it — so at 175% the app is legible but the
@@ -0,0 +1,77 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { colorFor } from "@miadi/stateloom-protocol";
5
+ import { useDesignerStore } from "@/store/useDesignerStore";
6
+
7
+ /** Enough dots to read "several peers" at a glance; the count carries the rest. */
8
+ const MAX_DOTS = 4;
9
+
10
+ /**
11
+ * Who else is on this board. It used to float over the top-right corner as one
12
+ * pill per peer — which on a busy room meant a dozen chips painted across the
13
+ * toolbar's own controls, hiding Validate/Generate/RISE behind a colour bar
14
+ * nobody could dismiss. `pointer-events-none` kept the buttons clickable but
15
+ * you could no longer see what you were clicking.
16
+ *
17
+ * So presence stops floating: it is a toolbar control like any other, laid out
18
+ * in the flow beside the document identity it describes. Fixed width regardless
19
+ * of how many peers join — a stack of colour dots and a count — with the full
20
+ * roster one click away. Nothing can be covered by a room that grows.
21
+ */
22
+ export default function PresenceChips() {
23
+ const presence = useDesignerStore((s) => s.presence);
24
+ const [open, setOpen] = useState(false);
25
+
26
+ if (presence.length === 0) return null;
27
+
28
+ const byRole = new Map<string, number>();
29
+ for (const p of presence) byRole.set(p.role, (byRole.get(p.role) ?? 0) + 1);
30
+ const summary = [...byRole]
31
+ .map(([role, n]) => (n > 1 ? `${role} ×${n}` : role))
32
+ .join(" · ");
33
+
34
+ return (
35
+ <span className="relative inline-flex">
36
+ <button
37
+ onClick={() => setOpen((v) => !v)}
38
+ className="toolbar-btn inline-flex items-center gap-1.5"
39
+ title={`Connected: ${summary}`}
40
+ aria-expanded={open}
41
+ aria-label={`${presence.length} connected: ${summary}`}
42
+ >
43
+ <span className="flex -space-x-1">
44
+ {presence.slice(0, MAX_DOTS).map((p) => (
45
+ <span
46
+ key={p.clientId}
47
+ className="inline-block h-2 w-2 rounded-full ring-1 ring-gray-900"
48
+ style={{ backgroundColor: p.color ?? colorFor(p.clientId) }}
49
+ />
50
+ ))}
51
+ </span>
52
+ <span className="tabular-nums">{presence.length}</span>
53
+ </button>
54
+ {open && (
55
+ <div className="absolute left-0 top-full z-50 mt-1 max-h-64 w-56 overflow-y-auto rounded border border-gray-700 bg-gray-900 p-2 shadow-lg">
56
+ <div className="mb-1 text-[10px] uppercase tracking-wide text-gray-500">
57
+ connected · {summary}
58
+ </div>
59
+ {presence.map((p) => (
60
+ <div
61
+ key={p.clientId}
62
+ className="flex items-center gap-1.5 rounded px-1 py-1 text-xs text-gray-300"
63
+ title={p.clientId}
64
+ >
65
+ <span
66
+ className="inline-block h-2 w-2 shrink-0 rounded-full"
67
+ style={{ backgroundColor: p.color ?? colorFor(p.clientId) }}
68
+ />
69
+ <span className="shrink-0">{p.role}</span>
70
+ {p.name && <span className="truncate text-gray-500">{p.name}</span>}
71
+ </div>
72
+ ))}
73
+ </div>
74
+ )}
75
+ </span>
76
+ );
77
+ }
@@ -7,7 +7,6 @@ import {
7
7
  } from "@miadi/stateloom-react";
8
8
  import {
9
9
  diffDefinition,
10
- colorFor,
11
10
  type StateMachineDefinition,
12
11
  } from "@miadi/stateloom-protocol";
13
12
  import { useDesignerStore } from "@/store/useDesignerStore";
@@ -24,9 +23,13 @@ const OUTBOUND_DEBOUNCE_MS = 60;
24
23
  * Rendered only once a bridge URL is known — see DesignBridge, which resolves
25
24
  * it from the serving process at runtime so a published build carries nobody's
26
25
  * URL baked in.
26
+ *
27
+ * Transport only — it paints nothing. Presence lands in the store and the
28
+ * toolbar draws it (PresenceChips); the floating corner dock this used to
29
+ * render grew one pill per peer and ended up covering the toolbar it hovered
30
+ * over.
27
31
  */
28
32
  export default function SocketBridgeProvider({ url }: { url: string }) {
29
- const presence = useDesignerStore((s) => s.presence);
30
33
  // The requested `?doc=` (step 2): passed through to the file API, which
31
34
  // resolves and guards it; the join below keys the room by the RESOLVED path.
32
35
  const requested = useRequestedDoc();
@@ -126,24 +129,5 @@ export default function SocketBridgeProvider({ url }: { url: string }) {
126
129
  // an arbitrary definition, so the arriving full-def repaints the canvas.
127
130
  }, [url, requested]);
128
131
 
129
- if (presence.length === 0) return null;
130
-
131
- return (
132
- // `presence-dock` replaces the raw `top-2 right-4`: with viewport-fit=cover
133
- // that corner can sit under a notch or the status bar, so the offset is
134
- // measured from the safe-area inset instead. Still pointer-events-none, so
135
- // it never competes with the toolbar it floats over.
136
- <div className="presence-dock flex gap-1 pointer-events-none">
137
- {presence.map((p) => (
138
- <span
139
- key={p.clientId}
140
- title={`${p.role}${p.name ? ` · ${p.name}` : ""}`}
141
- className="text-[10px] leading-none px-2 py-1 rounded-full text-white/95 shadow font-medium"
142
- style={{ backgroundColor: p.color ?? colorFor(p.clientId) }}
143
- >
144
- {p.role}
145
- </span>
146
- ))}
147
- </div>
148
- );
132
+ return null;
149
133
  }
@@ -10,6 +10,7 @@ import {
10
10
  } from "@/lib/exportImage";
11
11
  import type { StateDef } from "@/types/definition";
12
12
  import DocSwitcher from "./DocSwitcher";
13
+ import PresenceChips from "./PresenceChips";
13
14
  import { docQuery, useRequestedDoc } from "@/lib/docParam";
14
15
 
15
16
  export default function Toolbar() {
@@ -224,6 +225,10 @@ export default function Toolbar() {
224
225
  <span className="text-[10px] text-gray-600" title={remoteMessage ?? "No file bound"}>○ no disk</span>
225
226
  )}
226
227
 
228
+ {/* Who else is on the board. In the flow, next to the document it
229
+ describes — it used to float over this toolbar and cover it. */}
230
+ <PresenceChips />
231
+
227
232
  <div className="flex-1" />
228
233
  </div>
229
234
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.1.2",
3
- "designer": "0.1.2",
2
+ "package": "0.1.3",
3
+ "designer": "0.1.3",
4
4
  "next": "16.1.6"
5
5
  }