@miadi/stateloom-web 0.1.8 → 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.
- package/README.md +8 -0
- package/package.json +1 -1
- package/server/.next/BUILD_ID +1 -1
- package/server/.next/build-manifest.json +2 -2
- package/server/.next/server/app/_global-error.html +2 -2
- package/server/.next/server/app/_global-error.rsc +1 -1
- package/server/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/server/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/server/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/server/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/server/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/server/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/server/.next/server/app/_not-found.html +1 -1
- package/server/.next/server/app/_not-found.rsc +2 -2
- package/server/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/server/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/server/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/server/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/server/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/server/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/server/.next/server/app/api/docs/route.js.nft.json +1 -1
- package/server/.next/server/app/api/file/route.js.nft.json +1 -1
- package/server/.next/server/app/api/rispec/route.js.nft.json +1 -1
- package/server/.next/server/app/api/watch/route.js.nft.json +1 -1
- package/server/.next/server/app/index.html +1 -1
- package/server/.next/server/app/index.rsc +3 -3
- package/server/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/server/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/server/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/server/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/server/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/server/.next/server/app/page.js.nft.json +1 -1
- package/server/.next/server/app/page_client-reference-manifest.js +1 -1
- package/server/.next/server/chunks/[root-of-the-server]__13c3d2a8._.js +1 -1
- package/server/.next/server/chunks/[root-of-the-server]__2046b812._.js +2 -2
- package/server/.next/server/chunks/[root-of-the-server]__87bf4b91._.js +1 -1
- package/server/.next/server/chunks/[root-of-the-server]__95b05635._.js +3 -3
- package/server/.next/server/chunks/ssr/{[root-of-the-server]__46d45ef8._.js → [root-of-the-server]__b1b2bf74._.js} +2 -2
- package/server/.next/server/chunks/ssr/[root-of-the-server]__edbb995c._.js +4 -3
- package/server/.next/server/pages/404.html +1 -1
- package/server/.next/server/pages/500.html +2 -2
- package/server/.next/static/chunks/8458f462f4687e07.js +11 -0
- package/server/.next/static/chunks/a8a37c487ef91a19.js +1 -0
- package/server/.next/static/chunks/c86e96e4d6eeb632.css +2 -0
- package/server/package-lock.json +2 -2
- package/server/package.json +1 -1
- package/server/src/app/api/file/route.ts +57 -12
- package/server/src/app/globals.css +15 -0
- package/server/src/app/page.tsx +69 -20
- package/server/src/components/BoardBoundary.tsx +46 -0
- package/server/src/components/IssueIcon.tsx +28 -0
- package/server/src/components/LockIcon.tsx +24 -0
- package/server/src/components/PropertiesPanel.tsx +10 -0
- package/server/src/components/SettingsPanel.tsx +11 -0
- package/server/src/components/erd/ErdWorkspace.tsx +198 -21
- package/server/src/components/forms.tsx +129 -0
- package/server/src/components/sequence/SequenceWorkspace.tsx +1024 -0
- package/server/src/components/system/KindIcon.tsx +71 -0
- package/server/src/components/system/SystemStrip.tsx +285 -0
- package/server/src/components/system/SystemWorkspace.tsx +766 -0
- package/server/src/lib/docParam.ts +4 -1
- package/server/src/lib/machineFocus.ts +126 -0
- package/server/src/lib/projectFile.ts +180 -20
- package/server/src/lib/safely.ts +16 -0
- package/server/src/lib/systemLoad.ts +362 -0
- package/server/src/lib/systemParam.ts +110 -0
- package/server/src/lib/useSheetDrag.ts +95 -0
- package/server/src/types/definition.ts +4 -0
- package/server/stateloom-web.json +2 -2
- package/server/tsconfig.tsbuildinfo +1 -1
- package/server/.next/static/chunks/8f6bd51bbcce6314.css +0 -2
- package/server/.next/static/chunks/a9e7748193d11ed7.js +0 -10
- package/server/.next/static/chunks/aea5b1716c68391c.js +0 -1
- /package/server/.next/static/{XCT3lnw_1F5LD0GG4F94- → KjjOvPCIL-ta0AZ8r8SFk}/_buildManifest.js +0 -0
- /package/server/.next/static/{XCT3lnw_1F5LD0GG4F94- → KjjOvPCIL-ta0AZ8r8SFk}/_clientMiddlewareManifest.json +0 -0
- /package/server/.next/static/{XCT3lnw_1F5LD0GG4F94- → 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}
|
package/server/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miadi/stateloom-web-source",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
9
|
+
"version": "0.2.0",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@miadi/stateloom-canvas": "file:../bridge-canvas",
|
|
12
12
|
"@miadi/stateloom-client": "file:../bridge-client",
|
package/server/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
-
import { existsSync, readFileSync,
|
|
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
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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 {
|
package/server/src/app/page.tsx
CHANGED
|
@@ -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,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
|
|
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";
|
|
19
|
+
import IssueIcon from "@/components/IssueIcon";
|
|
20
|
+
import { docKindOfPath, isSysdfPath, type DocKind } from "@miadi/stateloom-protocol";
|
|
16
21
|
import { useRequestedDoc } from "@/lib/docParam";
|
|
22
|
+
import { useMachineFocus } from "@/lib/machineFocus";
|
|
17
23
|
import { loadRuntimeConfig } from "@/lib/runtimeConfig";
|
|
24
|
+
import { useSheetDrag } from "@/lib/useSheetDrag";
|
|
25
|
+
import { useUrlParam } from "@/lib/systemParam";
|
|
18
26
|
|
|
19
27
|
type Tab = "properties" | "events" | "settings" | "validation";
|
|
20
28
|
|
|
@@ -46,38 +54,74 @@ function contextMenuPlacement(x: number, y: number): CSSProperties {
|
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
/**
|
|
49
|
-
* The document's type is its extension (
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
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.
|
|
53
66
|
*/
|
|
54
67
|
export default function Home() {
|
|
55
68
|
// `null` until its first effect has read the URL, and whenever there is no `?doc=`.
|
|
56
69
|
const requested = useRequestedDoc();
|
|
57
|
-
const
|
|
70
|
+
const systemParam = useUrlParam("system");
|
|
71
|
+
const [defaultKind, setDefaultKind] = useState<DocKind | null>(null);
|
|
58
72
|
|
|
59
73
|
useEffect(() => {
|
|
60
74
|
let live = true;
|
|
61
75
|
loadRuntimeConfig().then((c) => {
|
|
62
|
-
if (live) setDefaultKind(c.projectFile
|
|
76
|
+
if (live) setDefaultKind(c.projectFile ? docKindOfPath(c.projectFile) : "machine");
|
|
63
77
|
});
|
|
64
78
|
return () => {
|
|
65
79
|
live = false;
|
|
66
80
|
};
|
|
67
81
|
}, []);
|
|
68
82
|
|
|
69
|
-
const kind = requested ? (
|
|
83
|
+
const kind = requested ? docKindOfPath(requested) : defaultKind;
|
|
70
84
|
if (kind === null) return <div className="app-shell bg-gray-950" />;
|
|
71
|
-
|
|
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
|
+
);
|
|
72
112
|
}
|
|
73
113
|
|
|
74
114
|
function MachineDesigner() {
|
|
115
|
+
// `focus=` in, the selection out (Spec 82).
|
|
116
|
+
useMachineFocus();
|
|
75
117
|
const [activeTab, setActiveTab] = useState<Tab>("properties");
|
|
76
118
|
// Phone-only state. The panel column and the bottom sheet are the same
|
|
77
119
|
// element: below `md` it is an overlay that starts closed so the canvas owns
|
|
78
120
|
// the entire screen, and at `md` and up the responsive classes pin it open as
|
|
79
121
|
// the 320px right sidebar it has always been — this flag stops mattering.
|
|
80
122
|
const [sheetOpen, setSheetOpen] = useState(false);
|
|
123
|
+
// The sheet's handle answers a thumb: pull down to close, up to grow, tap to toggle.
|
|
124
|
+
const sheet = useSheetDrag({ onClose: () => setSheetOpen(false), restVh: 70, tallVh: 92 });
|
|
81
125
|
const errors = useDesignerStore((s) => s.errors);
|
|
82
126
|
const contextMenu = useDesignerStore((s) => s.contextMenu);
|
|
83
127
|
const hideContextMenu = useDesignerStore((s) => s.hideContextMenu);
|
|
@@ -86,11 +130,12 @@ function MachineDesigner() {
|
|
|
86
130
|
const setDrawMode = useDesignerStore((s) => s.setDrawMode);
|
|
87
131
|
const setDrawSource = useDesignerStore((s) => s.setDrawSource);
|
|
88
132
|
|
|
89
|
-
const tabs: { id: Tab; label: string; icon:
|
|
133
|
+
const tabs: { id: Tab; label: string; icon: ReactNode; badge?: number }[] = [
|
|
90
134
|
{ id: "properties", label: "Properties", icon: "🎛" },
|
|
91
135
|
{ id: "events", label: "Events", icon: "⚡" },
|
|
92
136
|
{ id: "settings", label: "Settings", icon: "⚙" },
|
|
93
|
-
|
|
137
|
+
// Drawn, not typed: "⚠" is a bright yellow colour emoji on iOS. See IssueIcon.
|
|
138
|
+
{ id: "validation", label: "Errors", icon: <IssueIcon />, badge: errors.length || undefined },
|
|
94
139
|
];
|
|
95
140
|
|
|
96
141
|
const activeLabel = tabs.find((t) => t.id === activeTab)?.label ?? "";
|
|
@@ -99,7 +144,8 @@ function MachineDesigner() {
|
|
|
99
144
|
// both switcher and dismiss control and no screen height is spent on a
|
|
100
145
|
// separate close affordance.
|
|
101
146
|
const toggleTab = (id: Tab) => {
|
|
102
|
-
|
|
147
|
+
if (sheetOpen && activeTab === id) sheet.close();
|
|
148
|
+
else setSheetOpen(true);
|
|
103
149
|
setActiveTab(id);
|
|
104
150
|
};
|
|
105
151
|
|
|
@@ -116,7 +162,9 @@ function MachineDesigner() {
|
|
|
116
162
|
<div className="flex flex-1 min-h-0 overflow-hidden relative">
|
|
117
163
|
{/* Canvas */}
|
|
118
164
|
<div className="flex-1 overflow-hidden">
|
|
119
|
-
<
|
|
165
|
+
<BoardBoundary what="state machine">
|
|
166
|
+
<Canvas />
|
|
167
|
+
</BoardBoundary>
|
|
120
168
|
</div>
|
|
121
169
|
|
|
122
170
|
{/* Scrim. Phone only — it is what makes "tap the canvas to dismiss"
|
|
@@ -125,7 +173,7 @@ function MachineDesigner() {
|
|
|
125
173
|
{sheetOpen && (
|
|
126
174
|
<div
|
|
127
175
|
className="fixed inset-0 z-20 bg-black/50 md:hidden"
|
|
128
|
-
onClick={
|
|
176
|
+
onClick={sheet.close}
|
|
129
177
|
aria-hidden="true"
|
|
130
178
|
/>
|
|
131
179
|
)}
|
|
@@ -136,21 +184,22 @@ function MachineDesigner() {
|
|
|
136
184
|
declaration — position, height, width, transform, radius, border
|
|
137
185
|
side — which is why the class list is long. */}
|
|
138
186
|
<aside
|
|
139
|
-
|
|
187
|
+
style={sheet.sheetStyle}
|
|
188
|
+
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
189
|
sheetOpen ? "translate-y-0" : "translate-y-full pointer-events-none"
|
|
141
190
|
} 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
191
|
>
|
|
143
192
|
{/* Sheet header (phone). The grab handle reads as "this thing is a
|
|
144
193
|
sheet"; the dock below already names the tabs, so repeating the
|
|
145
194
|
tab bar here would only cost height. */}
|
|
146
|
-
<div className="md:hidden">
|
|
195
|
+
<div className="md:hidden" {...sheet.zoneProps}>
|
|
147
196
|
<div className="flex justify-center pt-2 pb-1">
|
|
148
|
-
<span className="h-1 w-
|
|
197
|
+
<span className="h-1.5 w-12 rounded-full bg-gray-600" />
|
|
149
198
|
</div>
|
|
150
199
|
<div className="flex items-center justify-between border-b border-gray-800 px-4 pb-2">
|
|
151
200
|
<h2 className="text-sm font-semibold text-gray-300">{activeLabel}</h2>
|
|
152
201
|
<button
|
|
153
|
-
onClick={
|
|
202
|
+
onClick={sheet.close}
|
|
154
203
|
className="-mr-2 px-3 py-2 text-lg text-gray-400"
|
|
155
204
|
aria-label="Close panel"
|
|
156
205
|
>
|
|
@@ -215,7 +264,7 @@ function MachineDesigner() {
|
|
|
215
264
|
current ? "text-blue-400" : "text-gray-500"
|
|
216
265
|
}`}
|
|
217
266
|
>
|
|
218
|
-
<span className="text-base leading-none">{tab.icon}</span>
|
|
267
|
+
<span className="flex h-4 items-center text-base leading-none">{tab.icon}</span>
|
|
219
268
|
<span>{tab.label}</span>
|
|
220
269
|
{tab.badge !== undefined && tab.badge > 0 && (
|
|
221
270
|
<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,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
|
+
}
|
|
@@ -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"
|