@miadi/stateloom-web 0.3.3 → 0.3.5

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 (104) hide show
  1. package/package.json +1 -1
  2. package/server/.next/BUILD_ID +1 -1
  3. package/server/.next/app-path-routes-manifest.json +1 -0
  4. package/server/.next/build-manifest.json +5 -5
  5. package/server/.next/prerender-manifest.json +3 -3
  6. package/server/.next/required-server-files.json +1 -1
  7. package/server/.next/routes-manifest.json +6 -0
  8. package/server/.next/server/app/_global-error/page/build-manifest.json +3 -3
  9. package/server/.next/server/app/_global-error/page.js.nft.json +1 -1
  10. package/server/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/server/.next/server/app/_global-error.html +2 -2
  12. package/server/.next/server/app/_global-error.rsc +1 -1
  13. package/server/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  14. package/server/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  15. package/server/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  16. package/server/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  17. package/server/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  18. package/server/.next/server/app/_not-found/page/build-manifest.json +3 -3
  19. package/server/.next/server/app/_not-found/page.js.nft.json +1 -1
  20. package/server/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  21. package/server/.next/server/app/_not-found.html +1 -1
  22. package/server/.next/server/app/_not-found.rsc +2 -2
  23. package/server/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  24. package/server/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  25. package/server/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  26. package/server/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  27. package/server/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  28. package/server/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  29. package/server/.next/server/app/api/docs/route.js.nft.json +1 -1
  30. package/server/.next/server/app/api/file/route.js.nft.json +1 -1
  31. package/server/.next/server/app/api/marks/route.js.nft.json +1 -1
  32. package/server/.next/server/app/api/rispec/route.js.nft.json +1 -1
  33. package/server/.next/server/app/api/system-of/route/app-paths-manifest.json +3 -0
  34. package/server/.next/server/app/api/system-of/route/build-manifest.json +11 -0
  35. package/server/.next/server/app/api/system-of/route/server-reference-manifest.json +4 -0
  36. package/server/.next/server/app/api/system-of/route.js +8 -0
  37. package/server/.next/server/app/api/system-of/route.js.map +5 -0
  38. package/server/.next/server/app/api/system-of/route.js.nft.json +1 -0
  39. package/server/.next/server/app/api/system-of/route_client-reference-manifest.js +2 -0
  40. package/server/.next/server/app/api/watch/route.js.nft.json +1 -1
  41. package/server/.next/server/app/index.html +1 -1
  42. package/server/.next/server/app/index.rsc +3 -3
  43. package/server/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  44. package/server/.next/server/app/index.segments/_full.segment.rsc +3 -3
  45. package/server/.next/server/app/index.segments/_head.segment.rsc +1 -1
  46. package/server/.next/server/app/index.segments/_index.segment.rsc +2 -2
  47. package/server/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  48. package/server/.next/server/app/page/build-manifest.json +3 -3
  49. package/server/.next/server/app/page.js.nft.json +1 -1
  50. package/server/.next/server/app/page_client-reference-manifest.js +1 -1
  51. package/server/.next/server/app-paths-manifest.json +1 -0
  52. package/server/.next/server/chunks/[root-of-the-server]__13c3d2a8._.js +1 -1
  53. package/server/.next/server/chunks/[root-of-the-server]__2046b812._.js +2 -2
  54. package/server/.next/server/chunks/[root-of-the-server]__87bf4b91._.js +1 -1
  55. package/server/.next/server/chunks/[root-of-the-server]__95b05635._.js +1 -1
  56. package/server/.next/server/chunks/[root-of-the-server]__aedef6d1._.js +1 -1
  57. package/server/.next/server/chunks/[root-of-the-server]__d21ea469._.js +3 -0
  58. package/server/.next/server/chunks/bridge-protocol_dist_index_179d02a5.js +1 -1
  59. package/server/.next/server/chunks/ssr/{2374f_next_dist_0c464f98._.js → 2374f_next_dist_491bc9ca._.js} +2 -2
  60. package/server/.next/server/chunks/ssr/{[root-of-the-server]__385c85a7._.js → [root-of-the-server]__0d719a8a._.js} +2 -2
  61. package/server/.next/server/chunks/ssr/[root-of-the-server]__199d50b6._.js +3 -0
  62. package/server/.next/server/chunks/ssr/[root-of-the-server]__5c28bab6._.js +3 -0
  63. package/server/.next/server/chunks/ssr/[root-of-the-server]__edbb995c._.js +6 -6
  64. package/server/.next/server/chunks/web__next-internal_server_app_api_system-of_route_actions_1988181c.js +3 -0
  65. package/server/.next/server/middleware-build-manifest.js +3 -3
  66. package/server/.next/server/pages/404.html +1 -1
  67. package/server/.next/server/pages/500.html +2 -2
  68. package/server/.next/server/server-reference-manifest.js +1 -1
  69. package/server/.next/server/server-reference-manifest.json +1 -1
  70. package/server/.next/static/chunks/0fd0d2a3caf9e5c4.js +14 -0
  71. package/server/.next/static/chunks/33df24ad2d3a04e7.js +1 -0
  72. package/server/.next/static/chunks/6052c4bfdd66ef5c.css +2 -0
  73. package/server/.next/static/chunks/b71bf89a3bbb72e1.js +5 -0
  74. package/server/.next/static/chunks/{turbopack-b3cae9131ae5086f.js → turbopack-3a774f5a5a8a9023.js} +1 -1
  75. package/server/package-lock.json +2 -2
  76. package/server/package.json +1 -1
  77. package/server/server.js +1 -1
  78. package/server/src/app/api/system-of/route.ts +22 -0
  79. package/server/src/app/globals.css +40 -2
  80. package/server/src/app/page.tsx +12 -8
  81. package/server/src/components/ChromeIcon.tsx +106 -0
  82. package/server/src/components/PropertiesPanel.tsx +3 -2
  83. package/server/src/components/Toolbar.tsx +37 -20
  84. package/server/src/components/erd/ErdWorkspace.tsx +2 -1
  85. package/server/src/components/sequence/SequenceWorkspace.tsx +10 -1
  86. package/server/src/components/stage/CompanionLayer.tsx +41 -6
  87. package/server/src/components/stage/StageLayer.tsx +206 -23
  88. package/server/src/components/stage/StageProvider.tsx +146 -25
  89. package/server/src/components/system/SystemWorkspace.tsx +26 -12
  90. package/server/src/lib/markRequest.ts +24 -0
  91. package/server/src/lib/systemOf.ts +80 -0
  92. package/server/src/lib/systemParam.ts +46 -1
  93. package/server/src/lib/useNarrow.ts +30 -0
  94. package/server/stateloom-web.json +2 -2
  95. package/server/tsconfig.tsbuildinfo +1 -1
  96. package/server/.next/server/chunks/ssr/[root-of-the-server]__a593e8b6._.js +0 -3
  97. package/server/.next/server/chunks/ssr/[root-of-the-server]__f25b51be._.js +0 -3
  98. package/server/.next/static/chunks/59a1ec89a1dc7e34.js +0 -14
  99. package/server/.next/static/chunks/662d1b0ce457c16b.js +0 -1
  100. package/server/.next/static/chunks/8f45e56f40c207bf.css +0 -2
  101. package/server/.next/static/chunks/96818f5620f039a1.js +0 -5
  102. /package/server/.next/static/{CZ4wRF1efsHnxjkcKHITP → 1YtenfowImzXV-wx5GWY1}/_buildManifest.js +0 -0
  103. /package/server/.next/static/{CZ4wRF1efsHnxjkcKHITP → 1YtenfowImzXV-wx5GWY1}/_clientMiddlewareManifest.json +0 -0
  104. /package/server/.next/static/{CZ4wRF1efsHnxjkcKHITP → 1YtenfowImzXV-wx5GWY1}/_ssgManifest.js +0 -0
@@ -0,0 +1,24 @@
1
+ "use client";
2
+
3
+ /**
4
+ * "Open that mark": something outside the board (the Talk dock's list of marks) asks the
5
+ * board showing `doc` to open the thread of mark `id`. The request waits here until that
6
+ * board has read its marks — the drawing may still be opening — and is taken once.
7
+ */
8
+ export const MARK_REQUEST_EVENT = "stateloom:mark-request";
9
+
10
+ let pending: { doc: string; id: string } | null = null;
11
+
12
+ export function requestMark(doc: string, id: string): void {
13
+ pending = { doc, id };
14
+ window.dispatchEvent(new Event(MARK_REQUEST_EVENT));
15
+ }
16
+
17
+ /** The mark asked for on `doc`, if any; it stays asked until `clearMarkRequest`. */
18
+ export function markRequested(doc: string | null): string | null {
19
+ return doc && pending?.doc === doc ? pending.id : null;
20
+ }
21
+
22
+ export function clearMarkRequest(): void {
23
+ pending = null;
24
+ }
@@ -0,0 +1,80 @@
1
+ import { readFileSync, readdirSync, realpathSync } from "fs";
2
+ import { dirname, join, sep } from "path";
3
+ import { isSysdfPath, isSystemDefinition, resolveMemberPath } from "@miadi/stateloom-protocol";
4
+ import { allowedDocRoots, chronicleRoot, resolveDocPath } from "./projectFile";
5
+
6
+ /** How many folders above the document are searched. */
7
+ const SEARCH_DEPTH = 8;
8
+
9
+ const realOr = (path: string): string => {
10
+ try {
11
+ return realpathSync(path);
12
+ } catch {
13
+ return path;
14
+ }
15
+ };
16
+
17
+ /** Could the file API serve something in `dir`, or in a folder below it? */
18
+ function underAnAdmittedRoot(dir: string): boolean {
19
+ const roots = allowedDocRoots();
20
+ const chronicle = chronicleRoot();
21
+ if (chronicle) roots.push(chronicle);
22
+ return roots.some((root) => dir === root || dir.startsWith(root + sep));
23
+ }
24
+
25
+ export type SystemOfResult = { ok: true; doc: string; system: string | null } | { ok: false; error: string };
26
+
27
+ /** The admitted, readable system at `path` lists `target`: its resolved path, else null. */
28
+ function listing(path: string, target: string): string | null {
29
+ const candidate = resolveDocPath(path);
30
+ if (!candidate.ok || !isSysdfPath(candidate.path)) return null;
31
+ let def: unknown;
32
+ try {
33
+ def = JSON.parse(readFileSync(candidate.path, "utf8"));
34
+ } catch {
35
+ return null;
36
+ }
37
+ if (!isSystemDefinition(def)) return null;
38
+ const listed = def.members.some((m) => !!m?.path && realOr(resolveMemberPath(candidate.path, m.path)) === target);
39
+ return listed ? candidate.path : null;
40
+ }
41
+
42
+ /**
43
+ * The system that lists `doc` (Spec 82), so a drawing opened alone (`?doc=`
44
+ * with no `system=`, the link an agent prints) is still read as a member of its
45
+ * system and the strip can lead back to the map and to the other drawings.
46
+ *
47
+ * `preferred` (the `system=` already in the URL) answers when it lists the
48
+ * document: a drawing two systems share stays in the one the person came from.
49
+ * Otherwise the rule is the MCP's (`systemFor` in mcp/src/system.ts): the first
50
+ * `.sysdf.json`, in name order, in the document's own folder and then in each
51
+ * parent folder, that names the document as a member. Every candidate goes
52
+ * through `resolveDocPath`, so this reads only systems the file API would serve
53
+ * and never names a path it refuses. The walk stops at the admitted roots.
54
+ */
55
+ export function systemOfDoc(doc: string, preferred?: string | null): SystemOfResult {
56
+ const resolution = resolveDocPath(doc);
57
+ if (!resolution.ok) return { ok: false, error: resolution.error };
58
+ const target = resolution.path;
59
+ if (isSysdfPath(target)) return { ok: true, doc: target, system: null };
60
+ const kept = preferred ? listing(preferred, target) : null;
61
+ if (kept) return { ok: true, doc: target, system: kept };
62
+
63
+ let dir = dirname(target);
64
+ for (let depth = 0; depth <= SEARCH_DEPTH && underAnAdmittedRoot(dir); depth += 1) {
65
+ let names: string[] = [];
66
+ try {
67
+ names = readdirSync(dir).filter((f) => !f.startsWith(".") && isSysdfPath(f)).sort();
68
+ } catch {
69
+ // An unreadable folder lists nothing.
70
+ }
71
+ for (const name of names) {
72
+ const system = listing(join(dir, name), target);
73
+ if (system) return { ok: true, doc: target, system };
74
+ }
75
+ const parent = dirname(dir);
76
+ if (parent === dir) break;
77
+ dir = parent;
78
+ }
79
+ return { ok: true, doc: target, system: null };
80
+ }
@@ -15,13 +15,17 @@
15
15
  * the strip can list its links.
16
16
  * - `stateloom:system-changed`: a member or the system file was written; every
17
17
  * holder of a loaded system reloads it.
18
+ * - `stateloom:system-found`: `system=` was written for the document already
19
+ * open (`useSystemOfDoc`); nothing else moved.
18
20
  */
19
21
  import { useEffect, useState } from "react";
22
+ import { isSysdfPath } from "@miadi/stateloom-protocol";
20
23
  import { DOC_CHANGE_EVENT } from "./docParam";
21
24
 
22
25
  export const FOCUS_EVENT = "stateloom:focus";
23
26
  export const FOCUS_REQUEST_EVENT = "stateloom:focus-request";
24
27
  export const SYSTEM_CHANGED_EVENT = "stateloom:system-changed";
28
+ export const SYSTEM_FOUND_EVENT = "stateloom:system-found";
25
29
  /** A one-line note for the system strip: `{ detail: { text, origin? } }`. */
26
30
  export const STRIP_NOTE_EVENT = "stateloom:strip-note";
27
31
 
@@ -36,7 +40,7 @@ export function useUrlParam(name: string): string | null {
36
40
  useEffect(() => {
37
41
  const update = () => setValue(urlParam(name));
38
42
  update();
39
- const events = ["popstate", DOC_CHANGE_EVENT, FOCUS_EVENT, FOCUS_REQUEST_EVENT];
43
+ const events = ["popstate", DOC_CHANGE_EVENT, FOCUS_EVENT, FOCUS_REQUEST_EVENT, SYSTEM_FOUND_EVENT];
40
44
  for (const e of events) window.addEventListener(e, update);
41
45
  return () => {
42
46
  for (const e of events) window.removeEventListener(e, update);
@@ -45,6 +49,47 @@ export function useUrlParam(name: string): string | null {
45
49
  return value;
46
50
  }
47
51
 
52
+ /**
53
+ * Read the open drawing as a member of the system that lists it. A drawing
54
+ * opened alone (`?doc=` and no `system=`, the link an agent prints) had no
55
+ * strip, so nothing led back to the map or on to the other drawings. And a
56
+ * drawing reached by the document switcher kept the strip of the system it was
57
+ * left from, even when another system lists it.
58
+ *
59
+ * The server answers (`/api/system-of`, lib/systemOf.ts): the system already in
60
+ * the URL when it lists the drawing, else the nearest one that does. When it
61
+ * names another system, `system=` is rewritten in place, without a history
62
+ * entry: the person did not move, the view learned where it is. When no system
63
+ * lists the drawing, the URL is left as it is, and a strip already on screen
64
+ * stays while the person browses.
65
+ */
66
+ export function useSystemOfDoc(doc: string | null, system: string | null): void {
67
+ useEffect(() => {
68
+ if (!doc || isSysdfPath(doc)) return;
69
+ let live = true;
70
+ const query = new URLSearchParams({ doc });
71
+ if (system) query.set("system", system);
72
+ fetch(`/api/system-of?${query.toString()}`, { cache: "no-store" })
73
+ .then((r) => (r.ok ? r.json() : null))
74
+ .then((body: { system?: string | null } | null) => {
75
+ const found = body?.system;
76
+ if (!live || !found || found === system) return;
77
+ // The person moved on while the server looked: this answer is for a view that is gone.
78
+ if (urlParam("doc") !== doc || urlParam("system") !== system) return;
79
+ const url = new URL(window.location.href);
80
+ url.searchParams.set("system", found);
81
+ window.history.replaceState(window.history.state, "", url);
82
+ window.dispatchEvent(new Event(SYSTEM_FOUND_EVENT));
83
+ })
84
+ .catch(() => {
85
+ // An older server has no /api/system-of: the drawing opens alone, as before.
86
+ });
87
+ return () => {
88
+ live = false;
89
+ };
90
+ }, [doc, system]);
91
+ }
92
+
48
93
  /** The query string for a member opened inside a system, focused on one element. */
49
94
  export function systemQuery(target: { doc: string; system?: string | null; focus?: string | null }): string {
50
95
  const params = new URLSearchParams();
@@ -0,0 +1,30 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+
5
+ /**
6
+ * A phone held upright: below 600 px the stage's caption, a mark's card and the Talk
7
+ * conversation share one sheet at the bottom of the board (Spec 83, lane U finding U2).
8
+ */
9
+ export const NARROW_QUERY = "(max-width: 599px)";
10
+
11
+ function matches(query: string): boolean {
12
+ try {
13
+ return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia(query).matches;
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+
19
+ export function useNarrow(query = NARROW_QUERY): boolean {
20
+ const [narrow, setNarrow] = useState(() => matches(query));
21
+ useEffect(() => {
22
+ if (typeof window.matchMedia !== "function") return;
23
+ const mq = window.matchMedia(query);
24
+ const update = () => setNarrow(mq.matches);
25
+ update();
26
+ mq.addEventListener("change", update);
27
+ return () => mq.removeEventListener("change", update);
28
+ }, [query]);
29
+ return narrow;
30
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.3.3",
3
- "designer": "0.3.3",
2
+ "package": "0.3.5",
3
+ "designer": "0.3.5",
4
4
  "next": "16.1.6"
5
5
  }