@miadi/stateloom-web 0.1.7 → 0.1.9

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 (56) hide show
  1. package/README.md +8 -0
  2. package/package.json +1 -1
  3. package/server/.next/BUILD_ID +1 -1
  4. package/server/.next/build-manifest.json +2 -2
  5. package/server/.next/server/app/_global-error.html +2 -2
  6. package/server/.next/server/app/_global-error.rsc +1 -1
  7. package/server/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  8. package/server/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/server/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  10. package/server/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  11. package/server/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  12. package/server/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/server/.next/server/app/_not-found.html +1 -1
  14. package/server/.next/server/app/_not-found.rsc +2 -2
  15. package/server/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  16. package/server/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  17. package/server/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  18. package/server/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  19. package/server/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  20. package/server/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  21. package/server/.next/server/app/api/docs/route.js.nft.json +1 -1
  22. package/server/.next/server/app/api/file/route.js.nft.json +1 -1
  23. package/server/.next/server/app/api/rispec/route.js.nft.json +1 -1
  24. package/server/.next/server/app/api/watch/route.js.nft.json +1 -1
  25. package/server/.next/server/app/index.html +1 -1
  26. package/server/.next/server/app/index.rsc +3 -3
  27. package/server/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  28. package/server/.next/server/app/index.segments/_full.segment.rsc +3 -3
  29. package/server/.next/server/app/index.segments/_head.segment.rsc +1 -1
  30. package/server/.next/server/app/index.segments/_index.segment.rsc +2 -2
  31. package/server/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  32. package/server/.next/server/app/page.js.nft.json +1 -1
  33. package/server/.next/server/app/page_client-reference-manifest.js +1 -1
  34. package/server/.next/server/chunks/ssr/{[root-of-the-server]__46d45ef8._.js → [root-of-the-server]__d5879c0c._.js} +1 -1
  35. package/server/.next/server/chunks/ssr/[root-of-the-server]__edbb995c._.js +3 -3
  36. package/server/.next/server/pages/404.html +1 -1
  37. package/server/.next/server/pages/500.html +2 -2
  38. package/server/.next/static/chunks/636e7eb4aeda72e0.css +2 -0
  39. package/server/.next/static/chunks/92628504da59dcc8.js +10 -0
  40. package/server/package-lock.json +2 -2
  41. package/server/package.json +1 -1
  42. package/server/src/app/page.tsx +17 -10
  43. package/server/src/components/IssueIcon.tsx +28 -0
  44. package/server/src/components/LockIcon.tsx +24 -0
  45. package/server/src/components/PropertiesPanel.tsx +10 -0
  46. package/server/src/components/SettingsPanel.tsx +11 -0
  47. package/server/src/components/erd/ErdWorkspace.tsx +564 -172
  48. package/server/src/lib/useSheetDrag.ts +95 -0
  49. package/server/src/types/definition.ts +4 -0
  50. package/server/stateloom-web.json +2 -2
  51. package/server/tsconfig.tsbuildinfo +1 -1
  52. package/server/.next/static/chunks/486bc3bc16e91024.css +0 -2
  53. package/server/.next/static/chunks/b3f3eb72c25c4c6b.js +0 -10
  54. /package/server/.next/static/{DPL_H0tteH9tpIBvvsIfq → 6dAVJYqnbe6oEAABQ1yMG}/_buildManifest.js +0 -0
  55. /package/server/.next/static/{DPL_H0tteH9tpIBvvsIfq → 6dAVJYqnbe6oEAABQ1yMG}/_clientMiddlewareManifest.json +0 -0
  56. /package/server/.next/static/{DPL_H0tteH9tpIBvvsIfq → 6dAVJYqnbe6oEAABQ1yMG}/_ssgManifest.js +0 -0
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { useEffect, useState, type CSSProperties } from "react";
3
+ import { useEffect, useState, type CSSProperties, type ReactNode } from "react";
4
4
  import Canvas from "@/components/Canvas";
5
5
  import PropertiesPanel from "@/components/PropertiesPanel";
6
6
  import EventsPanel from "@/components/EventsPanel";
@@ -12,9 +12,11 @@ import DesignBridge from "@/components/DesignBridge";
12
12
  import UiScale from "@/components/UiScale";
13
13
  import { useDesignerStore } from "@/store/useDesignerStore";
14
14
  import ErdWorkspace from "@/components/erd/ErdWorkspace";
15
+ import IssueIcon from "@/components/IssueIcon";
15
16
  import { isErdfPath } from "@miadi/stateloom-protocol";
16
17
  import { useRequestedDoc } from "@/lib/docParam";
17
18
  import { loadRuntimeConfig } from "@/lib/runtimeConfig";
19
+ import { useSheetDrag } from "@/lib/useSheetDrag";
18
20
 
19
21
  type Tab = "properties" | "events" | "settings" | "validation";
20
22
 
@@ -78,6 +80,8 @@ function MachineDesigner() {
78
80
  // the entire screen, and at `md` and up the responsive classes pin it open as
79
81
  // the 320px right sidebar it has always been — this flag stops mattering.
80
82
  const [sheetOpen, setSheetOpen] = useState(false);
83
+ // The sheet's handle answers a thumb: pull down to close, up to grow, tap to toggle.
84
+ const sheet = useSheetDrag({ onClose: () => setSheetOpen(false), restVh: 70, tallVh: 92 });
81
85
  const errors = useDesignerStore((s) => s.errors);
82
86
  const contextMenu = useDesignerStore((s) => s.contextMenu);
83
87
  const hideContextMenu = useDesignerStore((s) => s.hideContextMenu);
@@ -86,11 +90,12 @@ function MachineDesigner() {
86
90
  const setDrawMode = useDesignerStore((s) => s.setDrawMode);
87
91
  const setDrawSource = useDesignerStore((s) => s.setDrawSource);
88
92
 
89
- const tabs: { id: Tab; label: string; icon: string; badge?: number }[] = [
93
+ const tabs: { id: Tab; label: string; icon: ReactNode; badge?: number }[] = [
90
94
  { id: "properties", label: "Properties", icon: "🎛" },
91
95
  { id: "events", label: "Events", icon: "⚡" },
92
96
  { id: "settings", label: "Settings", icon: "⚙" },
93
- { id: "validation", label: "Errors", icon: "⚠", badge: errors.length || undefined },
97
+ // Drawn, not typed: "⚠" is a bright yellow colour emoji on iOS. See IssueIcon.
98
+ { id: "validation", label: "Errors", icon: <IssueIcon />, badge: errors.length || undefined },
94
99
  ];
95
100
 
96
101
  const activeLabel = tabs.find((t) => t.id === activeTab)?.label ?? "";
@@ -99,7 +104,8 @@ function MachineDesigner() {
99
104
  // both switcher and dismiss control and no screen height is spent on a
100
105
  // separate close affordance.
101
106
  const toggleTab = (id: Tab) => {
102
- setSheetOpen((wasOpen) => !(wasOpen && activeTab === id));
107
+ if (sheetOpen && activeTab === id) sheet.close();
108
+ else setSheetOpen(true);
103
109
  setActiveTab(id);
104
110
  };
105
111
 
@@ -125,7 +131,7 @@ function MachineDesigner() {
125
131
  {sheetOpen && (
126
132
  <div
127
133
  className="fixed inset-0 z-20 bg-black/50 md:hidden"
128
- onClick={() => setSheetOpen(false)}
134
+ onClick={sheet.close}
129
135
  aria-hidden="true"
130
136
  />
131
137
  )}
@@ -136,21 +142,22 @@ function MachineDesigner() {
136
142
  declaration — position, height, width, transform, radius, border
137
143
  side — which is why the class list is long. */}
138
144
  <aside
139
- className={`safe-x fixed inset-x-0 bottom-0 z-30 h-[70dvh] flex flex-col overflow-hidden rounded-t-2xl border-t border-gray-800 bg-gray-900 shadow-2xl transition-transform duration-200 ease-out ${
145
+ style={sheet.sheetStyle}
146
+ className={`safe-x fixed inset-x-0 bottom-0 z-30 h-[var(--sheet-h)] flex flex-col overflow-hidden rounded-t-2xl border-t border-gray-800 bg-gray-900 shadow-2xl transition-[transform,height] duration-200 ease-out ${
140
147
  sheetOpen ? "translate-y-0" : "translate-y-full pointer-events-none"
141
148
  } md:static md:z-auto md:h-auto md:w-80 md:translate-y-0 md:pointer-events-auto md:rounded-none md:border-t-0 md:border-l md:shadow-none md:transition-none`}
142
149
  >
143
150
  {/* Sheet header (phone). The grab handle reads as "this thing is a
144
151
  sheet"; the dock below already names the tabs, so repeating the
145
152
  tab bar here would only cost height. */}
146
- <div className="md:hidden">
153
+ <div className="md:hidden" {...sheet.zoneProps}>
147
154
  <div className="flex justify-center pt-2 pb-1">
148
- <span className="h-1 w-10 rounded-full bg-gray-700" />
155
+ <span className="h-1.5 w-12 rounded-full bg-gray-600" />
149
156
  </div>
150
157
  <div className="flex items-center justify-between border-b border-gray-800 px-4 pb-2">
151
158
  <h2 className="text-sm font-semibold text-gray-300">{activeLabel}</h2>
152
159
  <button
153
- onClick={() => setSheetOpen(false)}
160
+ onClick={sheet.close}
154
161
  className="-mr-2 px-3 py-2 text-lg text-gray-400"
155
162
  aria-label="Close panel"
156
163
  >
@@ -215,7 +222,7 @@ function MachineDesigner() {
215
222
  current ? "text-blue-400" : "text-gray-500"
216
223
  }`}
217
224
  >
218
- <span className="text-base leading-none">{tab.icon}</span>
225
+ <span className="flex h-4 items-center text-base leading-none">{tab.icon}</span>
219
226
  <span>{tab.label}</span>
220
227
  {tab.badge !== undefined && tab.badge > 0 && (
221
228
  <span className="absolute top-1 right-1/2 translate-x-4 rounded-full bg-red-600 px-1 text-[10px] leading-4 text-white min-w-[16px]">
@@ -0,0 +1,28 @@
1
+ /**
2
+ * A warning triangle drawn as an outline in the current text colour.
3
+ *
4
+ * The "⚠" character is a full-colour emoji on iOS: a saturated yellow sign in
5
+ * a dock of grey glyphs, pulling the eye whether or not there is anything to
6
+ * report. Drawn here instead, it is as quiet as its neighbours and takes the
7
+ * tab's own colour — grey at rest, the accent when its tab is open. The count
8
+ * badge beside it is what says there is something to look at.
9
+ */
10
+ export default function IssueIcon({ size = 16 }: { size?: number }) {
11
+ return (
12
+ <svg
13
+ width={size}
14
+ height={size}
15
+ viewBox="0 0 24 24"
16
+ fill="none"
17
+ stroke="currentColor"
18
+ strokeWidth="1.6"
19
+ strokeLinecap="round"
20
+ strokeLinejoin="round"
21
+ aria-hidden="true"
22
+ >
23
+ <path d="M12 4.5 21 19.5H3Z" />
24
+ <path d="M12 10v4.5" />
25
+ <path d="M12 17.3v.01" />
26
+ </svg>
27
+ );
28
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * A padlock drawn as an outline in the current text colour — closed or open.
3
+ * Drawn rather than typed for the same reason as IssueIcon: the padlock
4
+ * characters are full-colour emoji on iOS, a gold lock on a board of grey.
5
+ */
6
+ export default function LockIcon({ locked, size = 16 }: { locked: boolean; size?: number }) {
7
+ return (
8
+ <svg
9
+ width={size}
10
+ height={size}
11
+ viewBox="0 0 24 24"
12
+ fill="none"
13
+ stroke="currentColor"
14
+ strokeWidth="1.6"
15
+ strokeLinecap="round"
16
+ strokeLinejoin="round"
17
+ aria-hidden="true"
18
+ >
19
+ <rect x="5" y="11" width="14" height="9" rx="2" />
20
+ {/* The shackle: seated in the body when locked, swung clear of it when not. */}
21
+ <path d={locked ? "M8 11V8a4 4 0 0 1 8 0v3" : "M8 11V8a4 4 0 0 1 7.5-1.9"} />
22
+ </svg>
23
+ );
24
+ }
@@ -116,6 +116,16 @@ export default function PropertiesPanel() {
116
116
  onChange={(e) => updateState(selection.id!, { description: e.target.value || undefined })}
117
117
  />
118
118
  </label>
119
+ {/* Notes: what was said about this state, kept in the document for whoever opens it next. */}
120
+ <label className="block">
121
+ <span className="text-xs text-gray-500">Notes</span>
122
+ <textarea
123
+ className="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1 text-xs text-gray-200 h-20"
124
+ placeholder="An open question, a decision, something to tell the agent…"
125
+ value={state.notes ?? ""}
126
+ onChange={(e) => updateState(selection.id!, { notes: e.target.value || undefined })}
127
+ />
128
+ </label>
119
129
 
120
130
  {/* onEntry actions */}
121
131
  <ActionEditor
@@ -38,6 +38,17 @@ export default function SettingsPanel() {
38
38
  />
39
39
  </label>
40
40
 
41
+ {/* Notes about the whole diagram, kept in the document for whoever opens it next. */}
42
+ <label className="block">
43
+ <span className="text-xs text-gray-500">Notes on this diagram</span>
44
+ <textarea
45
+ className="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1 text-xs text-gray-200 h-24"
46
+ placeholder="What was discussed, what is still open, what to tell the agent…"
47
+ value={settings.notes ?? ""}
48
+ onChange={(e) => updateSettings({ notes: e.target.value || undefined })}
49
+ />
50
+ </label>
51
+
41
52
  <label className="flex items-center gap-2 text-sm text-gray-300">
42
53
  <input
43
54
  type="checkbox"