@miadi/stateloom-web 0.1.9 → 0.2.0

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 (68) 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/docs/route.js.nft.json +1 -1
  21. package/server/.next/server/app/api/file/route.js.nft.json +1 -1
  22. package/server/.next/server/app/api/rispec/route.js.nft.json +1 -1
  23. package/server/.next/server/app/api/watch/route.js.nft.json +1 -1
  24. package/server/.next/server/app/index.html +1 -1
  25. package/server/.next/server/app/index.rsc +3 -3
  26. package/server/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  27. package/server/.next/server/app/index.segments/_full.segment.rsc +3 -3
  28. package/server/.next/server/app/index.segments/_head.segment.rsc +1 -1
  29. package/server/.next/server/app/index.segments/_index.segment.rsc +2 -2
  30. package/server/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  31. package/server/.next/server/app/page.js.nft.json +1 -1
  32. package/server/.next/server/app/page_client-reference-manifest.js +1 -1
  33. package/server/.next/server/chunks/[root-of-the-server]__13c3d2a8._.js +1 -1
  34. package/server/.next/server/chunks/[root-of-the-server]__2046b812._.js +2 -2
  35. package/server/.next/server/chunks/[root-of-the-server]__87bf4b91._.js +1 -1
  36. package/server/.next/server/chunks/[root-of-the-server]__95b05635._.js +3 -3
  37. package/server/.next/server/chunks/ssr/{[root-of-the-server]__d5879c0c._.js → [root-of-the-server]__b1b2bf74._.js} +2 -2
  38. package/server/.next/server/chunks/ssr/[root-of-the-server]__edbb995c._.js +4 -3
  39. package/server/.next/server/pages/404.html +1 -1
  40. package/server/.next/server/pages/500.html +2 -2
  41. package/server/.next/static/chunks/8458f462f4687e07.js +11 -0
  42. package/server/.next/static/chunks/a8a37c487ef91a19.js +1 -0
  43. package/server/.next/static/chunks/c86e96e4d6eeb632.css +2 -0
  44. package/server/package-lock.json +2 -2
  45. package/server/package.json +1 -1
  46. package/server/src/app/api/file/route.ts +57 -12
  47. package/server/src/app/globals.css +15 -0
  48. package/server/src/app/page.tsx +52 -10
  49. package/server/src/components/BoardBoundary.tsx +46 -0
  50. package/server/src/components/erd/ErdWorkspace.tsx +32 -3
  51. package/server/src/components/forms.tsx +129 -0
  52. package/server/src/components/sequence/SequenceWorkspace.tsx +1024 -0
  53. package/server/src/components/system/KindIcon.tsx +71 -0
  54. package/server/src/components/system/SystemStrip.tsx +285 -0
  55. package/server/src/components/system/SystemWorkspace.tsx +766 -0
  56. package/server/src/lib/docParam.ts +4 -1
  57. package/server/src/lib/machineFocus.ts +126 -0
  58. package/server/src/lib/projectFile.ts +180 -20
  59. package/server/src/lib/safely.ts +16 -0
  60. package/server/src/lib/systemLoad.ts +362 -0
  61. package/server/src/lib/systemParam.ts +110 -0
  62. package/server/stateloom-web.json +2 -2
  63. package/server/.next/static/chunks/636e7eb4aeda72e0.css +0 -2
  64. package/server/.next/static/chunks/92628504da59dcc8.js +0 -10
  65. package/server/.next/static/chunks/aea5b1716c68391c.js +0 -1
  66. /package/server/.next/static/{6dAVJYqnbe6oEAABQ1yMG → KjjOvPCIL-ta0AZ8r8SFk}/_buildManifest.js +0 -0
  67. /package/server/.next/static/{6dAVJYqnbe6oEAABQ1yMG → KjjOvPCIL-ta0AZ8r8SFk}/_clientMiddlewareManifest.json +0 -0
  68. /package/server/.next/static/{6dAVJYqnbe6oEAABQ1yMG → KjjOvPCIL-ta0AZ8r8SFk}/_ssgManifest.js +0 -0
@@ -0,0 +1 @@
1
+ (globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,76800,e=>{"use strict";var t=e.i(30878);let o="stateloom:doc-change";function n(){return new URLSearchParams(window.location.search).get("doc")}function r(e){return e?`?doc=${encodeURIComponent(e)}`:""}function s(e){let t=new URL(window.location.href);e?t.searchParams.set("doc",e):t.searchParams.delete("doc"),t.searchParams.delete("focus"),window.history.pushState({},"",t),window.dispatchEvent(new Event(o))}function c(){let[e,r]=(0,t.useState)(null);return(0,t.useEffect)(()=>{let e=()=>r(n());return e(),window.addEventListener("popstate",e),window.addEventListener(o,e),()=>{window.removeEventListener("popstate",e),window.removeEventListener(o,e)}},[]),e}e.s(["DOC_CHANGE_EVENT",0,o,"docQuery",()=>r,"navigateToDoc",()=>s,"requestedDoc",()=>n,"useRequestedDoc",()=>c])},32058,e=>{e.v(e=>Promise.resolve().then(()=>e(76800)))}]);
@@ -0,0 +1,2 @@
1
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--color-red-100:#ffe2e2;--color-red-200:#ffcaca;--color-red-300:#ffa3a3;--color-red-400:#ff6568;--color-red-500:#fb2c36;--color-red-600:#e40014;--color-red-900:#82181a;--color-red-950:#460809;--color-amber-300:#ffd236;--color-amber-400:#fcbb00;--color-amber-700:#b75000;--color-yellow-400:#fac800;--color-yellow-500:#edb200;--color-yellow-600:#cd8900;--color-green-400:#05df72;--color-green-500:#00c758;--color-emerald-400:#00d294;--color-emerald-500:#00bb7f;--color-blue-200:#bedbff;--color-blue-300:#90c5ff;--color-blue-400:#54a2ff;--color-blue-500:#3080ff;--color-blue-600:#155dfc;--color-blue-900:#1c398e;--color-blue-950:#162456;--color-indigo-300:#a4b3ff;--color-gray-200:#e5e7eb;--color-gray-300:#d1d5dc;--color-gray-400:#99a1af;--color-gray-500:#6a7282;--color-gray-600:#4a5565;--color-gray-700:#364153;--color-gray-800:#1e2939;--color-gray-900:#101828;--color-gray-950:#030712;--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-md:28rem;--container-3xl:48rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--font-weight-medium:500;--font-weight-semibold:600;--tracking-normal:0em;--tracking-wide:.025em;--leading-snug:1.375;--leading-relaxed:1.625;--radius-lg:.5rem;--radius-2xl:1rem;--ease-out:cubic-bezier(0,0,.2,1);--blur-md:12px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-geist-sans);--default-mono-font-family:var(--font-geist-mono)}@supports (color:lab(0% 0 0)){:root,:host{--color-red-100:lab(92.243% 10.2865 3.83865);--color-red-200:lab(86.017% 19.8815 7.75869);--color-red-300:lab(76.5514% 36.422 15.5335);--color-red-400:lab(63.7053% 60.745 31.3109);--color-red-500:lab(55.4814% 75.0732 48.8528);--color-red-600:lab(48.4493% 77.4328 61.5452);--color-red-900:lab(28.5139% 44.5539 29.0463);--color-red-950:lab(13.003% 29.04 16.7519);--color-amber-300:lab(86.4156% 6.13147 78.3961);--color-amber-400:lab(80.1641% 16.6016 99.2089);--color-amber-700:lab(47.2709% 42.9082 69.2966);--color-yellow-400:lab(83.2664% 8.65132 106.895);--color-yellow-500:lab(76.3898% 14.5258 98.4589);--color-yellow-600:lab(62.7799% 22.4197 86.1544);--color-green-400:lab(78.503% -64.9265 39.7492);--color-green-500:lab(70.5521% -66.5147 45.8073);--color-emerald-400:lab(75.0771% -60.7313 19.4147);--color-emerald-500:lab(66.9756% -58.27 19.5419);--color-blue-200:lab(86.15% -4.04379 -21.0797);--color-blue-300:lab(77.5052% -6.4629 -36.42);--color-blue-400:lab(65.0361% -1.42065 -56.9802);--color-blue-500:lab(54.1736% 13.3369 -74.6839);--color-blue-600:lab(44.0605% 29.0279 -86.0352);--color-blue-900:lab(26.1542% 15.7545 -51.5504);--color-blue-950:lab(15.6723% 8.86232 -32.2945);--color-indigo-300:lab(74.0235% 8.54138 -41.6075);--color-gray-200:lab(91.6229% -.159115 -2.26791);--color-gray-300:lab(85.1236% -.612259 -3.7138);--color-gray-400:lab(65.9269% -.832707 -8.17473);--color-gray-500:lab(47.7841% -.393182 -10.0268);--color-gray-600:lab(35.6337% -1.58697 -10.8425);--color-gray-700:lab(27.1134% -.956401 -12.3224);--color-gray-800:lab(16.1051% -1.18239 -11.7533);--color-gray-900:lab(8.11897% .811279 -12.254);--color-gray-950:lab(1.90334% .278696 -5.48866)}}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing)*0)}.inset-x-0{inset-inline:calc(var(--spacing)*0)}.inset-x-4{inset-inline:calc(var(--spacing)*4)}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.top-0{top:calc(var(--spacing)*0)}.top-1{top:calc(var(--spacing)*1)}.top-2{top:calc(var(--spacing)*2)}.top-full{top:100%}.right-0{right:calc(var(--spacing)*0)}.right-1{right:calc(var(--spacing)*1)}.right-1\/2{right:50%}.right-3{right:calc(var(--spacing)*3)}.bottom-0{bottom:calc(var(--spacing)*0)}.bottom-3{bottom:calc(var(--spacing)*3)}.left-0{left:calc(var(--spacing)*0)}.left-1\/2{left:50%}.left-2{left:calc(var(--spacing)*2)}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.col-span-2{grid-column:span 2/span 2}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.-mx-1{margin-inline:calc(var(--spacing)*-1)}.mt-0\.5{margin-top:calc(var(--spacing)*.5)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.-mr-2{margin-right:calc(var(--spacing)*-2)}.mr-1{margin-right:calc(var(--spacing)*1)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.ml-0\.5{margin-left:calc(var(--spacing)*.5)}.ml-1{margin-left:calc(var(--spacing)*1)}.ml-2{margin-left:calc(var(--spacing)*2)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-0\.5{height:calc(var(--spacing)*.5)}.h-1\.5{height:calc(var(--spacing)*1.5)}.h-2{height:calc(var(--spacing)*2)}.h-4{height:calc(var(--spacing)*4)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-14{height:calc(var(--spacing)*14)}.h-16{height:calc(var(--spacing)*16)}.h-20{height:calc(var(--spacing)*20)}.h-24{height:calc(var(--spacing)*24)}.h-\[var\(--sheet-h\)\]{height:var(--sheet-h)}.h-\[var\(--strip-h\)\]{height:var(--strip-h)}.h-full{height:100%}.max-h-64{max-height:calc(var(--spacing)*64)}.max-h-\[60vh\]{max-height:60vh}.max-h-\[85dvh\]{max-height:85dvh}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-11{min-height:calc(var(--spacing)*11)}.w-1\.5{width:calc(var(--spacing)*1.5)}.w-2{width:calc(var(--spacing)*2)}.w-6{width:calc(var(--spacing)*6)}.w-10{width:calc(var(--spacing)*10)}.w-12{width:calc(var(--spacing)*12)}.w-24{width:calc(var(--spacing)*24)}.w-32{width:calc(var(--spacing)*32)}.w-56{width:calc(var(--spacing)*56)}.w-80{width:calc(var(--spacing)*80)}.w-\[min\(24rem\,calc\(100vw-0\.5rem\)\)\]{width:min(24rem,100vw - .5rem)}.w-full{width:100%}.w-px{width:1px}.max-w-\[7\.5rem\]{max-width:7.5rem}.max-w-\[30\%\]{max-width:30%}.max-w-\[40vw\]{max-width:40vw}.max-w-\[50vw\]{max-width:50vw}.max-w-\[60\%\]{max-width:60%}.max-w-\[75\%\]{max-width:75%}.max-w-\[80px\]{max-width:80px}.max-w-full{max-width:100%}.max-w-md{max-width:var(--container-md)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-11{min-width:calc(var(--spacing)*11)}.min-w-\[14px\]{min-width:14px}.min-w-\[16px\]{min-width:16px}.min-w-\[180px\]{min-width:180px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.-translate-x-1\/2{--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-4{--tw-translate-x:calc(var(--spacing)*4);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-5{--tw-translate-x:calc(var(--spacing)*5);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-0{--tw-translate-y:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-full{--tw-translate-y:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.cursor-pointer{cursor:pointer}.resize{resize:both}.resize-y{resize:vertical}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0\.5{gap:calc(var(--spacing)*.5)}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*.5)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*.5)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.-space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*-1)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*-1)*calc(1 - var(--tw-space-x-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overscroll-contain{overscroll-behavior:contain}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-t-2xl{border-top-left-radius:var(--radius-2xl);border-top-right-radius:var(--radius-2xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-blue-400{border-color:var(--color-blue-400)}.border-blue-500{border-color:var(--color-blue-500)}.border-blue-500\/60{border-color:#3080ff99}@supports (color:color-mix(in lab, red, red)){.border-blue-500\/60{border-color:color-mix(in oklab,var(--color-blue-500)60%,transparent)}}.border-gray-600{border-color:var(--color-gray-600)}.border-gray-700{border-color:var(--color-gray-700)}.border-gray-800{border-color:var(--color-gray-800)}.border-gray-800\/50{border-color:#1e293980}@supports (color:color-mix(in lab, red, red)){.border-gray-800\/50{border-color:color-mix(in oklab,var(--color-gray-800)50%,transparent)}}.border-red-900\/80{border-color:#82181acc}@supports (color:color-mix(in lab, red, red)){.border-red-900\/80{border-color:color-mix(in oklab,var(--color-red-900)80%,transparent)}}.\!bg-amber-700{background-color:var(--color-amber-700)!important}.bg-\[\#030712\]{background-color:#030712}.bg-amber-400{background-color:var(--color-amber-400)}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab, red, red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black)50%,transparent)}}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab, red, red)){.bg-black\/60{background-color:color-mix(in oklab,var(--color-black)60%,transparent)}}.bg-blue-400{background-color:var(--color-blue-400)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-blue-900\/30{background-color:#1c398e4d}@supports (color:color-mix(in lab, red, red)){.bg-blue-900\/30{background-color:color-mix(in oklab,var(--color-blue-900)30%,transparent)}}.bg-blue-900\/60{background-color:#1c398e99}@supports (color:color-mix(in lab, red, red)){.bg-blue-900\/60{background-color:color-mix(in oklab,var(--color-blue-900)60%,transparent)}}.bg-blue-950\/60{background-color:#16245699}@supports (color:color-mix(in lab, red, red)){.bg-blue-950\/60{background-color:color-mix(in oklab,var(--color-blue-950)60%,transparent)}}.bg-emerald-500{background-color:var(--color-emerald-500)}.bg-gray-600{background-color:var(--color-gray-600)}.bg-gray-700{background-color:var(--color-gray-700)}.bg-gray-800{background-color:var(--color-gray-800)}.bg-gray-800\/50{background-color:#1e293980}@supports (color:color-mix(in lab, red, red)){.bg-gray-800\/50{background-color:color-mix(in oklab,var(--color-gray-800)50%,transparent)}}.bg-gray-900{background-color:var(--color-gray-900)}.bg-gray-900\/50{background-color:#10182880}@supports (color:color-mix(in lab, red, red)){.bg-gray-900\/50{background-color:color-mix(in oklab,var(--color-gray-900)50%,transparent)}}.bg-gray-900\/90{background-color:#101828e6}@supports (color:color-mix(in lab, red, red)){.bg-gray-900\/90{background-color:color-mix(in oklab,var(--color-gray-900)90%,transparent)}}.bg-gray-900\/95{background-color:#101828f2}@supports (color:color-mix(in lab, red, red)){.bg-gray-900\/95{background-color:color-mix(in oklab,var(--color-gray-900)95%,transparent)}}.bg-gray-950{background-color:var(--color-gray-950)}.bg-gray-950\/70{background-color:#030712b3}@supports (color:color-mix(in lab, red, red)){.bg-gray-950\/70{background-color:color-mix(in oklab,var(--color-gray-950)70%,transparent)}}.bg-red-600{background-color:var(--color-red-600)}.bg-red-900{background-color:var(--color-red-900)}.bg-red-950\/80{background-color:#460809cc}@supports (color:color-mix(in lab, red, red)){.bg-red-950\/80{background-color:color-mix(in oklab,var(--color-red-950)80%,transparent)}}.bg-transparent{background-color:#0000}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-2\.5{padding-block:calc(var(--spacing)*2.5)}.py-3{padding-block:calc(var(--spacing)*3)}.pt-1{padding-top:calc(var(--spacing)*1)}.pt-2{padding-top:calc(var(--spacing)*2)}.pr-1{padding-right:calc(var(--spacing)*1)}.pb-0\.5{padding-bottom:calc(var(--spacing)*.5)}.pb-1{padding-bottom:calc(var(--spacing)*1)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pl-2{padding-left:calc(var(--spacing)*2)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-geist-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[9px\]{font-size:9px}.text-\[10\.5px\]{font-size:10.5px}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.leading-4{--tw-leading:calc(var(--spacing)*4);line-height:calc(var(--spacing)*4)}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-normal{--tw-tracking:var(--tracking-normal);letter-spacing:var(--tracking-normal)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.\!text-red-400{color:var(--color-red-400)!important}.\!text-white{color:var(--color-white)!important}.text-amber-300{color:var(--color-amber-300)}.text-amber-400{color:var(--color-amber-400)}.text-blue-200{color:var(--color-blue-200)}.text-blue-300{color:var(--color-blue-300)}.text-blue-400{color:var(--color-blue-400)}.text-blue-500{color:var(--color-blue-500)}.text-emerald-400{color:var(--color-emerald-400)}.text-emerald-500{color:var(--color-emerald-500)}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-green-400{color:var(--color-green-400)}.text-green-500{color:var(--color-green-500)}.text-indigo-300{color:var(--color-indigo-300)}.text-red-100{color:var(--color-red-100)}.text-red-200{color:var(--color-red-200)}.text-red-300{color:var(--color-red-300)}.text-red-400{color:var(--color-red-400)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-white{color:var(--color-white)}.text-yellow-400{color:var(--color-yellow-400)}.text-yellow-500{color:var(--color-yellow-500)}.text-yellow-600{color:var(--color-yellow-600)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-gray-900{--tw-ring-color:var(--color-gray-900)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-blur-md{--tw-backdrop-blur:blur(var(--blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[transform\,height\]{transition-property:transform,height;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-none{--tw-outline-style:none;outline-style:none}.\[scrollbar-width\:none\]{scrollbar-width:none}.placeholder\:text-gray-600::placeholder{color:var(--color-gray-600)}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media (hover:hover){.hover\:bg-blue-500:hover{background-color:var(--color-blue-500)}.hover\:bg-gray-600:hover{background-color:var(--color-gray-600)}.hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}.hover\:bg-gray-800:hover{background-color:var(--color-gray-800)}.hover\:bg-gray-800\/30:hover{background-color:#1e29394d}@supports (color:color-mix(in lab, red, red)){.hover\:bg-gray-800\/30:hover{background-color:color-mix(in oklab,var(--color-gray-800)30%,transparent)}}.hover\:text-blue-400:hover{color:var(--color-blue-400)}.hover\:text-gray-200:hover{color:var(--color-gray-200)}.hover\:text-gray-300:hover{color:var(--color-gray-300)}.hover\:text-gray-400:hover{color:var(--color-gray-400)}.hover\:text-red-400:hover{color:var(--color-red-400)}.hover\:text-white:hover{color:var(--color-white)}}.focus\:border-blue-500:focus{border-color:var(--color-blue-500)}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-40:disabled{opacity:.4}@media not all and (min-width:48rem){.max-md\:mt-0{margin-top:calc(var(--spacing)*0)}}@media (min-width:40rem){.sm\:inline{display:inline}}@media (min-width:48rem){.md\:pointer-events-auto{pointer-events:auto}.md\:static{position:static}.md\:z-auto{z-index:auto}.md\:block{display:block}.md\:flex{display:flex}.md\:hidden{display:none}.md\:inline{display:inline}.md\:h-8{height:calc(var(--spacing)*8)}.md\:h-auto{height:auto}.md\:max-h-\[80dvh\]{max-height:80dvh}.md\:max-h-\[calc\(100vh-160px\)\]{max-height:calc(100vh - 160px)}.md\:min-h-0{min-height:calc(var(--spacing)*0)}.md\:w-8{width:calc(var(--spacing)*8)}.md\:w-24{width:calc(var(--spacing)*24)}.md\:w-80{width:calc(var(--spacing)*80)}.md\:w-96{width:calc(var(--spacing)*96)}.md\:w-\[80\%\]{width:80%}.md\:max-w-3xl{max-width:var(--container-3xl)}.md\:max-w-\[11rem\]{max-width:11rem}.md\:max-w-\[16rem\]{max-width:16rem}.md\:max-w-none{max-width:none}.md\:min-w-0{min-width:calc(var(--spacing)*0)}.md\:min-w-\[140px\]{min-width:140px}.md\:translate-y-0{--tw-translate-y:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.md\:flex-row{flex-direction:row}.md\:flex-wrap{flex-wrap:wrap}.md\:items-center{align-items:center}.md\:overflow-y-auto{overflow-y:auto}.md\:rounded-none{border-radius:0}.md\:border-t-0{border-top-style:var(--tw-border-style);border-top-width:0}.md\:border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.md\:p-0{padding:calc(var(--spacing)*0)}.md\:px-2{padding-inline:calc(var(--spacing)*2)}.md\:px-3{padding-inline:calc(var(--spacing)*3)}.md\:py-1{padding-block:calc(var(--spacing)*1)}.md\:py-1\.5{padding-block:calc(var(--spacing)*1.5)}.md\:py-2{padding-block:calc(var(--spacing)*2)}.md\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.md\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.md\:shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.md\:transition-none{transition-property:none}}@media (min-width:64rem){.lg\:inline{display:inline}}.\[\&_label\]\:mt-0 label{margin-top:calc(var(--spacing)*0)}}:root{--background:#030712;--foreground:#e2e8f0}body{background:var(--background);color:var(--foreground);font-family:Arial,Helvetica,sans-serif}.toolbar-btn{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-700);background-color:var(--color-gray-800);padding-inline:calc(var(--spacing)*2.5);padding-block:calc(var(--spacing)*1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-300);border-radius:.25rem}@media (hover:hover){.toolbar-btn:hover{background-color:var(--color-gray-700)}}.toolbar-btn-sm{background-color:var(--color-gray-700);padding-inline:calc(var(--spacing)*1.5);padding-block:calc(var(--spacing)*.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-300);border-radius:.25rem}@media (hover:hover){.toolbar-btn-sm:hover{background-color:var(--color-gray-600)}}:root{--sm-dock-h:3.5rem}html,body{height:100%}html,body,.app-shell{touch-action:pan-x pan-y}body{overscroll-behavior:none;overflow:hidden}.app-shell{height:100dvh;padding-top:env(safe-area-inset-top);padding-left:env(safe-area-inset-left);padding-right:env(safe-area-inset-right)}.system-frame{--strip-h:2.375rem;padding-top:env(safe-area-inset-top)}.system-frame>.app-shell{height:calc(100vh - var(--strip-h) - env(safe-area-inset-top));height:calc(100dvh - var(--strip-h) - env(safe-area-inset-top));padding-top:0}@media (min-width:768px){.system-frame{--strip-h:2.25rem}}.dock-offset{padding-bottom:calc(var(--sm-dock-h) + env(safe-area-inset-bottom))}.dock-safe{padding-bottom:env(safe-area-inset-bottom)}.safe-x{padding-left:env(safe-area-inset-left);padding-right:env(safe-area-inset-right)}@media (min-width:768px){.dock-offset{padding-bottom:0}}.ui-scale-pill{z-index:45;right:calc(env(safe-area-inset-right) + 12px);bottom:calc(var(--sm-dock-h) + env(safe-area-inset-bottom) + 8px);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);color:#9ca3af;white-space:nowrap;background:#111827eb;border:1px solid #374151;border-radius:9999px;align-items:center;gap:8px;padding:6px 8px 6px 12px;font-size:12px;line-height:1;display:flex;position:fixed}.ui-scale-pill button{color:#93c5fd;background:#1f2937;border-radius:9999px;min-height:32px;padding:0 12px;font-size:12px;line-height:1}@media (min-width:768px){.ui-scale-pill{bottom:calc(env(safe-area-inset-bottom) + 12px)}}.toolbar-ident,.toolbar-strip{align-items:center;gap:.375rem;min-width:0;display:flex}.toolbar-strip{scrollbar-width:thin;-webkit-overflow-scrolling:touch;flex-wrap:nowrap;overflow:auto hidden}.toolbar-strip>*{flex:none}@media (min-width:768px){.toolbar-ident,.toolbar-strip{display:contents}}@media (pointer:coarse){.toolbar-btn,.toolbar-btn-sm{justify-content:center;align-items:center;min-width:2.75rem;min-height:2.75rem;padding-inline:.75rem;font-size:.8125rem;display:inline-flex}.toolbar-strip select,.toolbar-strip input{min-height:2.75rem}input:not([type=checkbox]),select,textarea{font-size:16px}input[type=checkbox]{width:1.15rem;height:1.15rem}.touch-targets button{min-width:2.75rem;min-height:2.75rem}.touch-targets select,.touch-targets input:not([type=checkbox]){min-height:2.75rem}.touch-targets textarea{min-height:3.5rem}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
2
+ .slc-pane{--slc-surface:#030712;--slc-node-fill:#0f172a;--slc-node-stroke:#475569;--slc-composite-fill:#0c1020;--slc-final-fill:#1e293b;--slc-accent:#3b82f6;--slc-accent-fill:#1e3a5f;--slc-accent-ink:#93c5fd;--slc-danger:#ef4444;--slc-draw-source:#f59e0b;--slc-active:#22c55e;--slc-edge:#94a3b8;--slc-chip-fill:#1e293b;--slc-chip-stroke:#334155;--slc-label:#e2e8f0;--slc-muted:#94a3b8;--slc-faint:#64748b;--slc-entry:#22c55e;--slc-exit:#f97316;--slc-composite:#6366f1;--slc-panel-bg:#111827e6;--slc-panel-border:#374151;--slc-panel-hover:#1f2937;touch-action:none;width:100%;height:100%;position:relative}.slc-surface{background:var(--slc-surface);width:100%;height:100%;display:block}.slc-node{fill:var(--slc-node-fill);stroke:var(--slc-node-stroke);stroke-width:1.5px}.slc-node--final{fill:var(--slc-final-fill)}.slc-node--composite{fill:var(--slc-composite-fill)}.slc-node--source{fill:var(--slc-accent-fill);stroke:var(--slc-draw-source)}.slc-node--error{stroke:var(--slc-danger);stroke-width:2px}.slc-node--selected{stroke:var(--slc-accent);stroke-width:2.5px}.slc-node-name{fill:var(--slc-label);pointer-events:none;-webkit-user-select:none;user-select:none;font-size:14px;font-weight:600}.slc-node-kind{fill:var(--slc-faint);pointer-events:none;-webkit-user-select:none;user-select:none;font-size:10px}.slc-node-entry{fill:var(--slc-entry);pointer-events:none;-webkit-user-select:none;user-select:none;font-size:9px}.slc-node-exit{fill:var(--slc-exit);pointer-events:none;-webkit-user-select:none;user-select:none;font-size:9px}.slc-node-warn{fill:var(--slc-danger);pointer-events:none;-webkit-user-select:none;user-select:none;font-size:12px}.slc-note-mark{fill:var(--slc-accent-ink);opacity:.75}.slc-node-warn-mark{fill:none;stroke:var(--slc-danger);stroke-width:1.4px;stroke-linejoin:round;stroke-linecap:round;pointer-events:none}.slc-node-drill{fill:var(--slc-composite);pointer-events:none;-webkit-user-select:none;user-select:none;font-size:9px}.slc-node--drawing{cursor:crosshair}.slc-node--drillable{cursor:pointer}.slc-node--draggable{cursor:grab}.slc-node--draggable:active{cursor:grabbing}.slc-node--static{cursor:default}.slc-edge{fill:none;stroke:var(--slc-edge);stroke-width:1.5px;cursor:pointer}.slc-edge:hover{stroke:var(--slc-accent-ink)}.slc-edge--selected{stroke:var(--slc-accent);stroke-width:2.5px}.slc-arrowhead{fill:var(--slc-edge)}.slc-arrowhead--selected{fill:var(--slc-accent)}.slc-chip{cursor:pointer}.slc-chip-plate{fill:var(--slc-chip-fill);stroke:var(--slc-chip-stroke);stroke-width:1px}.slc-chip-plate--selected{fill:var(--slc-accent-fill);stroke:var(--slc-accent)}.slc-chip-text{fill:var(--slc-muted);pointer-events:none;-webkit-user-select:none;user-select:none;font-size:11px;font-weight:500}.slc-chip-text--selected{fill:var(--slc-accent-ink)}.slc-chip-guard{fill:var(--slc-faint);-webkit-user-select:none;user-select:none;font-size:9px}.slc-chip-leader{stroke:var(--slc-chip-stroke);stroke-width:1px;stroke-dasharray:2 3;pointer-events:none}.slc-chip-leader--selected{stroke:var(--slc-accent)}.slc-glyph{fill:none;stroke:#7c93b0;stroke-linecap:round;stroke-linejoin:round;pointer-events:none}.slc-glyph--selected{stroke:var(--slc-accent-ink)}.slc-glyph .slc-glyph-dot{fill:#7c93b0}.slc-glyph--selected .slc-glyph-dot{fill:var(--slc-accent-ink)}.slc-draw-hint{fill:var(--slc-draw-source);pointer-events:none;-webkit-user-select:none;user-select:none;font-size:13px}.slc-empty{fill:var(--slc-faint);font-size:16px}.slc-crumbs{z-index:10;border:1px solid var(--slc-panel-border);background:var(--slc-panel-bg);border-radius:.5rem;align-items:center;gap:.25rem;padding:.375rem .75rem;font-size:.8125rem;line-height:1.25;display:flex;position:absolute;top:.5rem;left:.5rem}.slc-crumb-item{align-items:center;gap:.25rem;display:flex}.slc-crumb-sep{color:var(--slc-faint)}.slc-crumb{cursor:pointer;color:var(--slc-muted);font:inherit;background:0 0;border:0;padding:0}.slc-crumb:hover{color:var(--slc-accent-ink)}.slc-crumb--current{color:var(--slc-accent-ink);font-weight:600}.slc-hud{z-index:10;border:1px solid var(--slc-panel-border);background:var(--slc-panel-bg);color:var(--slc-muted);-webkit-user-select:none;user-select:none;border-radius:.5rem;align-items:center;gap:.125rem;padding:.25rem .375rem;font-size:.75rem;display:flex;position:absolute;bottom:.75rem;left:.75rem}.slc-hud-btn{color:inherit;font:inherit;cursor:pointer;background:0 0;border:0;border-radius:.25rem;padding:.125rem .375rem}.slc-hud-btn:hover:not(:disabled){background:var(--slc-panel-hover);color:var(--slc-label)}.slc-hud-btn:disabled{opacity:.4;cursor:default}.slc-hud-btn--scale{font-variant-numeric:tabular-nums;min-width:3.25rem}.slc-hud--coarse .slc-hud-btn{padding:.5rem .75rem}.slc-hud-sep{color:var(--slc-panel-border);padding:0 .125rem}.slc-hud-hint{color:var(--slc-faint);padding:0 .25rem;font-size:.625rem}.slc-picker{z-index:50;border:1px solid var(--slc-panel-border);background:var(--slc-panel-bg);border-radius:.5rem;min-width:160px;padding:.5rem;position:absolute;box-shadow:0 10px 25px #00000073}.slc-picker-title{color:var(--slc-muted);padding:0 .25rem .25rem;font-size:.75rem}.slc-picker-sub{color:var(--slc-faint);padding:0 .25rem .25rem;font-size:.75rem}.slc-picker-empty{color:var(--slc-faint);padding:.5rem .25rem;font-size:.75rem}.slc-picker-item{text-align:left;width:100%;color:var(--slc-label);font:inherit;cursor:pointer;background:0 0;border:0;border-radius:.25rem;padding:.25rem .5rem;font-size:.875rem;display:block}.slc-picker-item:hover{background:var(--slc-panel-hover)}.slc-picker-cancel{text-align:left;border:0;border-top:1px solid var(--slc-panel-border);width:100%;color:var(--slc-faint);font:inherit;cursor:pointer;background:0 0;margin-top:.25rem;padding:.25rem .5rem;font-size:.75rem;display:block}.slc-picker-cancel:hover{color:var(--slc-label)}@keyframes slc-node-in{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}@keyframes slc-node-out{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.9)}}@keyframes slc-edge-draw{0%{stroke-dashoffset:1px;opacity:0}to{stroke-dashoffset:0;opacity:1}}@keyframes slc-pulse{0%,to{filter:drop-shadow(0 0 1px #22c55e8c)}50%{filter:drop-shadow(0 0 6px #22c55ef2)}}.slc-node-enter{transform-box:fill-box;transform-origin:50%;animation:.22s ease-out both slc-node-in}.slc-node-exit-group{transform-box:fill-box;transform-origin:50%;pointer-events:none;animation:.2s ease-in forwards slc-node-out}.slc-edge-enter{stroke-dasharray:1;animation:.26s ease-out both slc-edge-draw}.slc-node--active{stroke:var(--slc-active);animation:1.2s ease-in-out infinite slc-pulse}@media (prefers-reduced-motion:reduce){.slc-node-enter,.slc-edge-enter{animation:none}.slc-node-exit-group{animation-duration:1ms}.slc-node--active{filter:drop-shadow(0 0 4px #22c55ed9);animation:none}}.slc-erd-line{fill:none;stroke:var(--slc-edge);stroke-width:1.5px}.slc-erd-mark{fill:none;stroke:var(--slc-edge);stroke-width:1.5px;stroke-linecap:round}.slc-erd-rel-label{fill:var(--slc-muted);pointer-events:none;-webkit-user-select:none;user-select:none;font-size:11px;font-weight:500}.slc-erd-rule{stroke:var(--slc-node-stroke);stroke-width:1px;pointer-events:none}.slc-erd-row{fill:var(--slc-muted);-webkit-user-select:none;user-select:none;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}.slc-erd-row--pk{fill:var(--slc-label);font-weight:600}.slc-erd-row--state{fill:var(--slc-accent-ink)}.slc-erd-row--empty{fill:var(--slc-faint);font-style:italic}.slc-erd-state{cursor:pointer}.slc-erd-state:hover .slc-erd-row--state{text-decoration:underline}.slc-erd-badge{fill:var(--slc-faint);letter-spacing:.04em;pointer-events:none;-webkit-user-select:none;user-select:none;font-size:9px;font-weight:600}.slc-erd-oval{fill:var(--slc-node-fill);stroke:var(--slc-node-stroke);stroke-width:1.2px}.slc-erd-oval-text{fill:var(--slc-muted);pointer-events:none;-webkit-user-select:none;user-select:none;font-size:11px}.slc-erd-oval-text--pk{fill:var(--slc-label);font-weight:600;text-decoration:underline}.slc-erd-attr-line{fill:none;stroke:var(--slc-chip-stroke);stroke-width:1px}.slc-erd-diamond{fill:var(--slc-chip-fill);stroke:var(--slc-edge);stroke-width:1.5px}.slc-erd-weak{fill:none;stroke:var(--slc-node-stroke);stroke-width:1.2px;pointer-events:none}.slc-erd-card{fill:var(--slc-label);pointer-events:none;-webkit-user-select:none;user-select:none;font-size:12px;font-weight:700}.slc-erd-state:hover .slc-erd-oval{stroke:var(--slc-accent)}.slc-erd-empty{color:var(--slc-faint);pointer-events:none;justify-content:center;align-items:center;font-size:16px;display:flex;position:absolute;inset:0}.slc-sqd-name{font-size:12px}.slc-sqd-name--object{text-decoration:underline}.slc-sqd-kind{fill:var(--slc-faint);pointer-events:none;-webkit-user-select:none;user-select:none;font-size:8.5px}.slc-sqd-header--involved{stroke:var(--slc-accent-ink);stroke-width:1.5px}.slc-sqd-participant,.slc-sqd-message{cursor:pointer}.slc-sqd-lifeline{fill:none;stroke:var(--slc-node-stroke);stroke-width:1px;stroke-dasharray:4 4;pointer-events:none}.slc-sqd-lifeline--selected{stroke:var(--slc-accent);stroke-width:1.5px}.slc-sqd-hit{fill:none;stroke:#0000;stroke-width:14px;cursor:pointer}.slc-sqd-hitbox{fill:#0000}.slc-sqd-line{fill:none;stroke:var(--slc-edge);stroke-width:1.5px;pointer-events:none}.slc-sqd-line--reply{stroke-dasharray:6 4}.slc-sqd-line--optional{opacity:.8}.slc-sqd-line--warn{stroke:var(--slc-draw-source)}.slc-sqd-line--error{stroke:var(--slc-danger)}.slc-sqd-line--selected{stroke:var(--slc-accent);stroke-width:2px}.slc-sqd-head{fill:var(--slc-edge);stroke:var(--slc-edge);stroke-width:1.2px;stroke-linejoin:round;pointer-events:none}.slc-sqd-head--open{fill:none;stroke-width:1.5px}.slc-sqd-head--error{fill:var(--slc-danger);stroke:var(--slc-danger)}.slc-sqd-head--selected{fill:var(--slc-accent);stroke:var(--slc-accent)}.slc-sqd-head--open.slc-sqd-head--selected,.slc-sqd-head--open.slc-sqd-head--error{fill:none}.slc-sqd-selected-plate{fill:var(--slc-accent-fill);opacity:.35;pointer-events:none}.slc-sqd-ref-plate{fill:var(--slc-chip-fill);stroke:var(--slc-chip-stroke);stroke-width:1px;pointer-events:none}.slc-sqd-ref-plate--selected{stroke:var(--slc-accent)}.slc-sqd-ref{fill:var(--slc-muted);pointer-events:none;-webkit-user-select:none;user-select:none;font-size:10px;font-weight:600}.slc-sqd-label{fill:var(--slc-label);pointer-events:none;-webkit-user-select:none;user-select:none;font-size:11px}.slc-sqd-label--selected{fill:var(--slc-accent-ink)}.slc-sqd-label--error{fill:var(--slc-danger)}.slc-sqd-event{fill:var(--slc-accent-fill);stroke:var(--slc-accent);stroke-width:.8px;pointer-events:none}.slc-sqd-event-text{fill:var(--slc-accent-ink);pointer-events:none;-webkit-user-select:none;user-select:none;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10px}.slc-sqd-opt{fill:none;stroke:var(--slc-faint);stroke-width:1px;stroke-dasharray:2 2;pointer-events:none}.slc-sqd-opt-text{fill:var(--slc-muted);pointer-events:none;-webkit-user-select:none;user-select:none;font-size:9.5px;font-style:italic}.slc-sqd-state{fill:var(--slc-entry);pointer-events:none;-webkit-user-select:none;user-select:none;font-size:11px;font-weight:600}.slc-sqd-frame{fill:none;stroke:var(--slc-composite);stroke-width:1.2px;pointer-events:none}.slc-sqd-frame-tab{fill:var(--slc-composite-fill);stroke:var(--slc-composite);stroke-width:1.2px;pointer-events:none}.slc-sqd-frame-heading{fill:var(--slc-muted);pointer-events:none;-webkit-user-select:none;user-select:none;font-size:11px}.slc-sqd-frame-kind{fill:var(--slc-label);font-weight:700}.slc-sqd-branch{fill:none;stroke:var(--slc-composite);stroke-width:1px;stroke-dasharray:1 3;pointer-events:none}
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@miadi/stateloom-web-source",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@miadi/stateloom-web-source",
9
- "version": "0.1.9",
9
+ "version": "0.2.0",
10
10
  "dependencies": {
11
11
  "@miadi/stateloom-canvas": "file:../bridge-canvas",
12
12
  "@miadi/stateloom-client": "file:../bridge-client",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miadi/stateloom-web-source",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "private": true,
5
5
  "//": [
6
6
  "This is the designer's SOURCE and is never published — hence private: true.",
@@ -1,6 +1,6 @@
1
1
  import { NextRequest, NextResponse } from "next/server";
2
- import { existsSync, readFileSync, writeFileSync, statSync } from "fs";
3
- import { resolveDocPath } from "@/lib/projectFile";
2
+ import { existsSync, readFileSync, statSync } from "fs";
3
+ import { resolveDocPath, writeDocFile } from "@/lib/projectFile";
4
4
 
5
5
  export const dynamic = "force-dynamic";
6
6
 
@@ -8,7 +8,17 @@ export const dynamic = "force-dynamic";
8
8
  // default project file answers exactly as before. Every doc passes the root
9
9
  // allowlist in lib/projectFile.ts — parameter and guard are one surface
10
10
  // (chart chart_1785683022927). A refusal names its reason and is a 403,
11
- // never a write.
11
+ // never a write. A request the file system cannot serve (a directory, a file
12
+ // that vanished) is a 4xx with the reason, never a 500 and a stack trace.
13
+
14
+ function reason(e: unknown): string {
15
+ const code = (e as { code?: string })?.code;
16
+ if (code === "EISDIR") return "that path is a directory, not a document";
17
+ if (code === "ELOOP") return "that path is a symbolic link, which is not written through";
18
+ if (code === "ENOENT") return "that document's folder does not exist";
19
+ if (code === "EACCES" || code === "EPERM") return "the server may not access that document";
20
+ return e instanceof Error ? e.message : String(e);
21
+ }
12
22
 
13
23
  export async function GET(req: NextRequest) {
14
24
  const resolution = resolveDocPath(req.nextUrl.searchParams.get("doc"));
@@ -16,28 +26,63 @@ export async function GET(req: NextRequest) {
16
26
  return NextResponse.json({ error: resolution.error }, { status: 403 });
17
27
  }
18
28
  const path = resolution.path;
19
- if (!existsSync(path)) {
20
- return NextResponse.json({ path, content: null, mtime: 0, exists: false });
29
+ try {
30
+ if (!existsSync(path)) {
31
+ return NextResponse.json({ path, content: null, mtime: 0, exists: false });
32
+ }
33
+ if (statSync(path).isDirectory()) {
34
+ return NextResponse.json({ error: reason({ code: "EISDIR" }) }, { status: 400 });
35
+ }
36
+ const content = readFileSync(path, "utf8");
37
+ const mtime = statSync(path).mtimeMs;
38
+ return NextResponse.json({ path, content, mtime, exists: true });
39
+ } catch (e) {
40
+ return NextResponse.json({ error: reason(e) }, { status: 400 });
21
41
  }
22
- const content = readFileSync(path, "utf8");
23
- const mtime = statSync(path).mtimeMs;
24
- return NextResponse.json({ path, content, mtime, exists: true });
25
42
  }
26
43
 
44
+ /**
45
+ * Body: `{ content: string, mtime?: number }`. With `mtime` — the one the
46
+ * writer read the document at — the write happens only if the file has not
47
+ * changed since; otherwise 409 with the current mtime, and the writer reloads.
48
+ * That is what keeps a page from writing back over an agent's edit it never saw.
49
+ */
27
50
  export async function PUT(req: NextRequest) {
28
51
  const resolution = resolveDocPath(req.nextUrl.searchParams.get("doc"));
29
52
  if (!resolution.ok) {
30
53
  return NextResponse.json({ error: resolution.error }, { status: 403 });
31
54
  }
32
55
  const path = resolution.path;
33
- const body = await req.json();
56
+ let body: { content?: unknown; mtime?: unknown };
57
+ try {
58
+ body = await req.json();
59
+ } catch {
60
+ return NextResponse.json({ error: "Body must be JSON: { content: string }" }, { status: 400 });
61
+ }
34
62
  if (typeof body?.content !== "string") {
35
63
  return NextResponse.json(
36
64
  { error: "Body must be { content: string }" },
37
65
  { status: 400 }
38
66
  );
39
67
  }
40
- writeFileSync(path, body.content, "utf8");
41
- const mtime = statSync(path).mtimeMs;
42
- return NextResponse.json({ path, mtime, ok: true });
68
+ try {
69
+ const exists = existsSync(path);
70
+ if (exists && statSync(path).isDirectory()) {
71
+ return NextResponse.json({ error: reason({ code: "EISDIR" }) }, { status: 400 });
72
+ }
73
+ if (typeof body.mtime === "number") {
74
+ const now = exists ? statSync(path).mtimeMs : 0;
75
+ if (now !== body.mtime) {
76
+ return NextResponse.json(
77
+ { error: "the document changed on disk since it was read", mtime: now, conflict: true },
78
+ { status: 409 }
79
+ );
80
+ }
81
+ }
82
+ writeDocFile(path, body.content);
83
+ const mtime = statSync(path).mtimeMs;
84
+ return NextResponse.json({ path, mtime, ok: true });
85
+ } catch (e) {
86
+ return NextResponse.json({ error: reason(e) }, { status: 400 });
87
+ }
43
88
  }
@@ -84,6 +84,21 @@ body {
84
84
  padding-right: env(safe-area-inset-right);
85
85
  }
86
86
 
87
+ /* The system strip (Spec 82) takes one row above whichever workspace is open;
88
+ the workspace gives that row back from its own full-screen height. */
89
+ .system-frame {
90
+ --strip-h: 2.375rem;
91
+ padding-top: env(safe-area-inset-top);
92
+ }
93
+ .system-frame > .app-shell {
94
+ height: calc(100vh - var(--strip-h) - env(safe-area-inset-top));
95
+ height: calc(100dvh - var(--strip-h) - env(safe-area-inset-top));
96
+ padding-top: 0;
97
+ }
98
+ @media (min-width: 768px) {
99
+ .system-frame { --strip-h: 2.25rem; }
100
+ }
101
+
87
102
  /* Phone only: hold content clear of the fixed tab dock. Zeroed at the `md`
88
103
  breakpoint because the dock is gone there and the sidebar is a real column. */
89
104
  .dock-offset {
@@ -12,11 +12,17 @@ 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 SequenceWorkspace from "@/components/sequence/SequenceWorkspace";
16
+ import SystemWorkspace from "@/components/system/SystemWorkspace";
17
+ import SystemStrip from "@/components/system/SystemStrip";
18
+ import BoardBoundary from "@/components/BoardBoundary";
15
19
  import IssueIcon from "@/components/IssueIcon";
16
- import { isErdfPath } from "@miadi/stateloom-protocol";
20
+ import { docKindOfPath, isSysdfPath, type DocKind } from "@miadi/stateloom-protocol";
17
21
  import { useRequestedDoc } from "@/lib/docParam";
22
+ import { useMachineFocus } from "@/lib/machineFocus";
18
23
  import { loadRuntimeConfig } from "@/lib/runtimeConfig";
19
24
  import { useSheetDrag } from "@/lib/useSheetDrag";
25
+ import { useUrlParam } from "@/lib/systemParam";
20
26
 
21
27
  type Tab = "properties" | "events" | "settings" | "validation";
22
28
 
@@ -48,32 +54,66 @@ function contextMenuPlacement(x: number, y: number): CSSProperties {
48
54
  }
49
55
 
50
56
  /**
51
- * The document's type is its extension (Spec 80): a `.erdf.json` opens the ERD
52
- * workspace, anything else the state designer. Decided before either mounts, so
53
- * the state designer's store and bridge never see an ERD: `?doc=` answers by its
54
- * extension, and without it the serving process names its default document.
57
+ * The document's type is its extension (Specs 80–82, `docKindOfPath`): a
58
+ * `.erdf.json` opens the ERD workspace, a `.sqdf.json` the sequence workspace,
59
+ * a `.sysdf.json` the system map, anything else the state designer. Decided
60
+ * before any of them mounts, so the state designer's store and bridge never see
61
+ * another kind: `?doc=` answers by its extension, and without it the serving
62
+ * process names its default document.
63
+ *
64
+ * With `?system=<.sysdf.json>` the document is read as a member of that system,
65
+ * and the system strip sits above whichever workspace is open.
55
66
  */
56
67
  export default function Home() {
57
68
  // `null` until its first effect has read the URL, and whenever there is no `?doc=`.
58
69
  const requested = useRequestedDoc();
59
- const [defaultKind, setDefaultKind] = useState<"machine" | "erd" | null>(null);
70
+ const systemParam = useUrlParam("system");
71
+ const [defaultKind, setDefaultKind] = useState<DocKind | null>(null);
60
72
 
61
73
  useEffect(() => {
62
74
  let live = true;
63
75
  loadRuntimeConfig().then((c) => {
64
- if (live) setDefaultKind(c.projectFile && isErdfPath(c.projectFile) ? "erd" : "machine");
76
+ if (live) setDefaultKind(c.projectFile ? docKindOfPath(c.projectFile) : "machine");
65
77
  });
66
78
  return () => {
67
79
  live = false;
68
80
  };
69
81
  }, []);
70
82
 
71
- const kind = requested ? (isErdfPath(requested) ? "erd" : "machine") : defaultKind;
83
+ const kind = requested ? docKindOfPath(requested) : defaultKind;
72
84
  if (kind === null) return <div className="app-shell bg-gray-950" />;
73
- return kind === "erd" ? <ErdWorkspace /> : <MachineDesigner />;
85
+ // Keyed by the document, so a switch between two documents of one kind starts
86
+ // that workspace clean. The state designer keeps its own store and re-keys its bridge.
87
+ const key = requested ?? "default";
88
+ const workspace =
89
+ kind === "erd" ? (
90
+ <ErdWorkspace key={key} />
91
+ ) : kind === "sequence" ? (
92
+ <SequenceWorkspace key={key} />
93
+ ) : kind === "system" ? (
94
+ <SystemWorkspace key={key} />
95
+ ) : (
96
+ <MachineDesigner />
97
+ );
98
+ // The last resort: whatever a malformed document breaks that the boards'
99
+ // own boundaries do not catch shows a message, not Next's "Application error".
100
+ const guarded = (
101
+ <BoardBoundary what="document" resetKey={requested}>
102
+ {workspace}
103
+ </BoardBoundary>
104
+ );
105
+ if (!systemParam || !isSysdfPath(systemParam)) return guarded;
106
+ return (
107
+ <div className="system-frame">
108
+ <SystemStrip systemPath={systemParam} />
109
+ {guarded}
110
+ </div>
111
+ );
74
112
  }
75
113
 
76
114
  function MachineDesigner() {
115
+ // `focus=` in, the selection out (Spec 82).
116
+ useMachineFocus();
77
117
  const [activeTab, setActiveTab] = useState<Tab>("properties");
78
118
  // Phone-only state. The panel column and the bottom sheet are the same
79
119
  // element: below `md` it is an overlay that starts closed so the canvas owns
@@ -122,7 +162,9 @@ function MachineDesigner() {
122
162
  <div className="flex flex-1 min-h-0 overflow-hidden relative">
123
163
  {/* Canvas */}
124
164
  <div className="flex-1 overflow-hidden">
125
- <Canvas />
165
+ <BoardBoundary what="state machine">
166
+ <Canvas />
167
+ </BoardBoundary>
126
168
  </div>
127
169
 
128
170
  {/* Scrim. Phone only — it is what makes "tap the canvas to dismiss"
@@ -0,0 +1,46 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Around each board and panel: a document an agent or a hand wrote in a shape
5
+ * the drawing does not expect shows one plain message where it would have
6
+ * drawn, and the rest of the page keeps working — never Next's "Application
7
+ * error". It tries again when `resetKey` changes (the document was edited).
8
+ */
9
+ import { Component, type ErrorInfo, type ReactNode } from "react";
10
+
11
+ interface Props {
12
+ children: ReactNode;
13
+ /** Change it to try drawing again (pass the document). */
14
+ resetKey?: unknown;
15
+ /** What could not be drawn: "sequence", "diagram", "panel"… */
16
+ what: string;
17
+ }
18
+
19
+ export default class BoardBoundary extends Component<Props, { error: Error | null }> {
20
+ state: { error: Error | null } = { error: null };
21
+
22
+ static getDerivedStateFromError(error: Error) {
23
+ return { error };
24
+ }
25
+
26
+ componentDidCatch(error: Error, info: ErrorInfo): void {
27
+ console.error(`[stateloom] the ${this.props.what} could not be drawn:`, error, info.componentStack);
28
+ }
29
+
30
+ componentDidUpdate(prev: Props): void {
31
+ if (this.state.error && prev.resetKey !== this.props.resetKey) this.setState({ error: null });
32
+ }
33
+
34
+ render(): ReactNode {
35
+ const { error } = this.state;
36
+ if (!error) return this.props.children;
37
+ return (
38
+ <div role="alert" className="flex h-full w-full items-center justify-center p-6 text-center text-sm text-amber-300">
39
+ <p className="max-w-md">
40
+ This {this.props.what} cannot be drawn: {error.message}. The file on disk is unchanged — correct it in the panel,
41
+ in the file, or with the agent that wrote it.
42
+ </p>
43
+ </div>
44
+ );
45
+ }
46
+ }
@@ -37,6 +37,7 @@ import {
37
37
  emptyErd,
38
38
  erdAutoLayout,
39
39
  isErdDefinition,
40
+ parseFocus,
40
41
  removeAttribute,
41
42
  removeEntity,
42
43
  removeRelationship,
@@ -52,12 +53,15 @@ import {
52
53
  type StateMachineDefinition,
53
54
  type Viewport,
54
55
  } from "@miadi/stateloom-protocol";
56
+ import BoardBoundary from "@/components/BoardBoundary";
55
57
  import DocSwitcher from "@/components/DocSwitcher";
56
58
  import IssueIcon from "@/components/IssueIcon";
57
59
  import LockIcon from "@/components/LockIcon";
58
60
  import { docQuery, navigateToDoc, useRequestedDoc } from "@/lib/docParam";
59
61
  import { loadRuntimeConfig } from "@/lib/runtimeConfig";
60
62
  import { useSheetDrag } from "@/lib/useSheetDrag";
63
+ import { urlParam, useFocusRequest, writeFocus } from "@/lib/systemParam";
64
+ import { safely } from "@/lib/safely";
61
65
 
62
66
  type Moved = Record<string, { x: number; y: number }>;
63
67
  type Tab = "entities" | "relations" | "notes" | "issues";
@@ -229,14 +233,20 @@ export default function ErdWorkspace() {
229
233
 
230
234
  const positions = useMemo<Record<string, LayoutBox>>(() => {
231
235
  if (!def) return {};
232
- const derived = erdAutoLayout(def, { notation });
236
+ // A malformed document lays out as nothing rather than taking the page down; the board says why.
237
+ const derived = safely<Record<string, LayoutBox>>(() => erdAutoLayout(def, { notation }), {});
233
238
  for (const [name, at] of Object.entries(moved)) {
234
239
  if (derived[name]) derived[name] = { ...derived[name], ...at };
235
240
  }
236
241
  return derived;
237
242
  }, [def, moved, notation]);
238
243
 
239
- const problems = useMemo(() => (def ? validateErd(def) : []), [def]);
244
+ const problems = useMemo<ErdValidationError[]>(() => {
245
+ if (!def) return [];
246
+ let failure = "";
247
+ const found = safely<ErdValidationError[] | null>(() => validateErd(def), null, (m) => (failure = m));
248
+ return found ?? [{ ruleId: "E000", message: `The document could not be checked (${failure}): a field is not the shape Spec 80 gives it` }];
249
+ }, [def]);
240
250
  const errorEntities = useMemo(
241
251
  () => problems.map((p) => (p.element ?? "").split(/[.[]/)[0]).filter(Boolean),
242
252
  [problems],
@@ -365,6 +375,23 @@ export default function ErdWorkspace() {
365
375
  sheet.close();
366
376
  };
367
377
 
378
+ // A focus from the URL, the system strip or an agent (Spec 82): select it and
379
+ // show it. `attribute:E.a` shows its entity. A person's selection becomes the
380
+ // focus in turn, so the strip can list what it links to.
381
+ const focusRequest = useFocusRequest();
382
+ const appliedFocus = useRef(-1);
383
+ useEffect(() => {
384
+ if (!def || appliedFocus.current === focusRequest.nonce) return;
385
+ appliedFocus.current = focusRequest.nonce;
386
+ const f = focusRequest.focus ? parseFocus(focusRequest.focus) : null;
387
+ const name = f?.kind === "entity" ? f.name : f?.kind === "attribute" ? f.name.split(".")[0] : null;
388
+ if (name && positions[name]) showOnBoard(name);
389
+ // eslint-disable-next-line react-hooks/exhaustive-deps
390
+ }, [def, focusRequest, positions]);
391
+ useEffect(() => {
392
+ if (selection && !urlParam("focus")?.startsWith(`attribute:${selection}.`)) writeFocus(`entity:${selection}`);
393
+ }, [selection]);
394
+
368
395
  const openTab = (next: Tab): void => {
369
396
  // Tapping the tab you are already reading closes the sheet, so the dock is
370
397
  // both switcher and dismiss control.
@@ -486,6 +513,7 @@ export default function ErdWorkspace() {
486
513
  <div className="relative flex min-h-0 flex-1">
487
514
  <div ref={boardRef} className="relative min-w-0 flex-1 overflow-hidden">
488
515
  {def && (
516
+ <BoardBoundary what="diagram" resetKey={def}>
489
517
  <EntityRelationshipCanvas
490
518
  definition={def}
491
519
  notation={notation}
@@ -502,6 +530,7 @@ export default function ErdWorkspace() {
502
530
  onOpenMachine={openMachine}
503
531
  emptyHint="No entities yet — add one in the panel, or let an agent call add_entity."
504
532
  />
533
+ </BoardBoundary>
505
534
  )}
506
535
 
507
536
  {/* Status costs no layout: it is a line over the board's corner. */}
@@ -844,7 +873,7 @@ export default function ErdWorkspace() {
844
873
  * for instance — the box takes it, unless someone is typing in it: a save of
845
874
  * their own words coming back must never pull the text out from under them.
846
875
  */
847
- function NotesField({
876
+ export function NotesField({
848
877
  label,
849
878
  value,
850
879
  placeholder,
@@ -0,0 +1,129 @@
1
+ "use client";
2
+
3
+ /**
4
+ * A small form for the panels of the sequence and system workspaces: text,
5
+ * select and checkbox fields, each with an initial value, and one submit.
6
+ *
7
+ * Used both to add (`reset` clears the text fields after a submit, keeping the
8
+ * selects, as the ERD panel's form does) and to edit (the host keys the form
9
+ * by the element it edits, so a changed element re-seeds it).
10
+ */
11
+ import { useState } from "react";
12
+
13
+ export const panelInput =
14
+ "w-full rounded border border-gray-700 bg-gray-950 px-2 py-1 text-xs text-gray-200 placeholder:text-gray-600";
15
+ export const panelButton = "rounded border border-gray-700 bg-gray-800 px-2 py-1 text-xs text-gray-200 hover:bg-gray-700";
16
+ export const panelHeading = "mb-1 mt-3 text-[11px] font-semibold uppercase tracking-wide text-gray-500";
17
+
18
+ export interface FormField {
19
+ name: string;
20
+ label?: string;
21
+ placeholder?: string;
22
+ /** A select. `""` is shown as `(label)`. */
23
+ options?: readonly string[];
24
+ /** A text field that suggests these (a datalist); any text is still accepted. */
25
+ suggest?: readonly string[];
26
+ checkbox?: boolean;
27
+ initial?: string | boolean;
28
+ /** Takes the whole row. */
29
+ wide?: boolean;
30
+ }
31
+
32
+ export type FormValues = Record<string, string | boolean>;
33
+
34
+ export function FieldForm({
35
+ id,
36
+ fields,
37
+ submit,
38
+ onSubmit,
39
+ reset = false,
40
+ required,
41
+ }: {
42
+ /** Prefix for datalist ids — unique per form on the page. */
43
+ id: string;
44
+ fields: FormField[];
45
+ submit: string;
46
+ onSubmit: (values: FormValues) => void;
47
+ reset?: boolean;
48
+ /** Fields that must not be empty for the submit to go through. */
49
+ required?: string[];
50
+ }) {
51
+ const initial = (): FormValues =>
52
+ Object.fromEntries(
53
+ fields.map((f) => [f.name, f.initial ?? (f.checkbox ? false : f.options ? (f.options[0] ?? "") : "")]),
54
+ );
55
+ const [values, setValues] = useState<FormValues>(initial);
56
+ const set = (name: string, value: string | boolean) => setValues((v) => ({ ...v, [name]: value }));
57
+ return (
58
+ <form
59
+ className="mt-1 grid grid-cols-2 gap-1"
60
+ onSubmit={(e) => {
61
+ e.preventDefault();
62
+ const merged = { ...initial(), ...values };
63
+ if ((required ?? []).some((n) => !String(merged[n] ?? "").trim())) return;
64
+ onSubmit(merged);
65
+ if (reset) {
66
+ setValues((prev) =>
67
+ Object.fromEntries(
68
+ fields.map((f) => [f.name, f.options || f.checkbox ? (prev[f.name] ?? "") : (f.initial ?? "")]),
69
+ ),
70
+ );
71
+ }
72
+ }}
73
+ >
74
+ {fields.map((f) => {
75
+ const title = f.label ?? f.name;
76
+ if (f.checkbox) {
77
+ return (
78
+ <label key={f.name} className="flex items-center gap-2 text-[11px] text-gray-400">
79
+ <input type="checkbox" checked={!!values[f.name]} onChange={(e) => set(f.name, e.target.checked)} />
80
+ {title}
81
+ </label>
82
+ );
83
+ }
84
+ if (f.options) {
85
+ return (
86
+ <select
87
+ key={f.name}
88
+ className={`${panelInput} ${f.wide ? "col-span-2" : ""}`}
89
+ aria-label={title}
90
+ title={title}
91
+ value={String(values[f.name] ?? "")}
92
+ onChange={(e) => set(f.name, e.target.value)}
93
+ >
94
+ {f.options.map((o) => (
95
+ <option key={o} value={o}>
96
+ {o || `(${title})`}
97
+ </option>
98
+ ))}
99
+ </select>
100
+ );
101
+ }
102
+ const listId = f.suggest?.length ? `${id}-${f.name}` : undefined;
103
+ return (
104
+ <span key={f.name} className={f.wide ? "col-span-2" : ""}>
105
+ <input
106
+ className={panelInput}
107
+ aria-label={title}
108
+ title={title}
109
+ placeholder={f.placeholder ?? title}
110
+ list={listId}
111
+ value={String(values[f.name] ?? "")}
112
+ onChange={(e) => set(f.name, e.target.value)}
113
+ />
114
+ {listId && (
115
+ <datalist id={listId}>
116
+ {f.suggest!.map((s) => (
117
+ <option key={s} value={s} />
118
+ ))}
119
+ </datalist>
120
+ )}
121
+ </span>
122
+ );
123
+ })}
124
+ <button type="submit" className={`${panelButton} col-span-2`}>
125
+ {submit}
126
+ </button>
127
+ </form>
128
+ );
129
+ }