From 4fcffe8c48dae18aa284e1891e9bcacbaa8e16e0 Mon Sep 17 00:00:00 2001 From: Debasish Bose Date: Fri, 11 Sep 2026 10:59:50 +1000 Subject: [PATCH 1/4] Fix empty object list in the New subject area modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The picker sourced its Available/Included objects by fetching the FIRST existing subject area's detail — so a model with no subject areas showed an empty picker (you couldn't create your first area with any members), and any fetch failure blanked it with "TypeError: Failed to fetch". A subject area is a filtered view of the model, so the object pool is the whole model, not one area's membership. Source the pool from the projected model instead: ModelWorkspace passes its conceptual entities in as `objects`, so the picker is always populated with no network call and works with zero existing areas. The only thing that still needs a saved area is the "add related objects" expansion (it walks a real area's graph on the server); its Preview button is now disabled with a hint when no area exists yet, instead of failing. --- canvas/src/sme/ModelWorkspace.tsx | 19 +++++++++ canvas/src/sme/NewSubjectAreaModal.tsx | 39 ++++++++++++------- .../mdl_server/static/assets/sme-DsrrbJWz.js | 2 + .../mdl_server/static/assets/sme-bmuaEwPQ.js | 2 - .../server/src/mdl_server/static/sme.html | 2 +- 5 files changed, 48 insertions(+), 16 deletions(-) create mode 100644 packages/server/src/mdl_server/static/assets/sme-DsrrbJWz.js delete mode 100644 packages/server/src/mdl_server/static/assets/sme-bmuaEwPQ.js diff --git a/canvas/src/sme/ModelWorkspace.tsx b/canvas/src/sme/ModelWorkspace.tsx index 9642dbc..508439e 100644 --- a/canvas/src/sme/ModelWorkspace.tsx +++ b/canvas/src/sme/ModelWorkspace.tsx @@ -140,6 +140,25 @@ export function ModelWorkspace({ {creatingArea && ( + e.conceptual + ? [ + { + id: e.conceptual.id, + kind: "conceptual_entity" as const, + name: e.conceptual.name, + definition: e.conceptual.definition ?? null, + synonyms: [], + subject_area: e.conceptual.subject_area ?? null, + // the picker only reads id/name/definition; the rest of the + // GlossaryTerm shape is filled with empties to satisfy the type. + stewardship: null, + ontology: null, + where_used: [], + }, + ] + : [], + )} onClose={() => setCreatingArea(false)} onCreate={(name, definition, members) => { // Client-minted so the area keeps its identity from preview to PR, and diff --git a/canvas/src/sme/NewSubjectAreaModal.tsx b/canvas/src/sme/NewSubjectAreaModal.tsx index c6ca11b..75f8782 100644 --- a/canvas/src/sme/NewSubjectAreaModal.tsx +++ b/canvas/src/sme/NewSubjectAreaModal.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useState } from "react"; -import { expandSubjectArea, fetchSubjectArea, fetchSubjectAreas } from "../api"; +import { expandSubjectArea, fetchSubjectAreas } from "../api"; import type { ClosureHop, GlossaryTerm } from "../types"; import { Pane } from "./SubjectAreaEditor"; @@ -10,16 +10,21 @@ import { Pane } from "./SubjectAreaEditor"; * intention. This is the same Available/Included picker the Subject areas tab * uses, with the expansion preview, wrapped around the create. */ export function NewSubjectAreaModal({ + objects, onCreate, onClose, }: { + /** every conceptual object in the model — the pool the two panes partition. Passed + * in from the projected model so the picker is populated even when the model has + * NO subject areas yet (creating the first one must still list its objects). */ + objects: GlossaryTerm[]; /** name plus the members to seed it with; both staged as one proposal */ onCreate: (name: string, definition: string, members: string[]) => void; onClose: () => void; }) { const [name, setName] = useState(""); const [definition, setDefinition] = useState(""); - const [all, setAll] = useState([]); + const [all] = useState(objects); const [members, setMembers] = useState([]); const [picked, setPicked] = useState>(new Set()); const [filter, setFilter] = useState(""); @@ -30,20 +35,19 @@ export function NewSubjectAreaModal({ const [seedAreaId, setSeedAreaId] = useState(""); const [error, setError] = useState(null); - // Every conceptual object in the model, from any existing area's detail — the - // two panes are a partition of that set. Expansion needs a real area id to POST - // against, so borrow the first one; it only reads the graph. + // The object pool comes from `objects` (the projected model), so the picker is + // populated with no network call and works when the model has zero areas. The only + // thing that needs an existing area is the "add related objects" EXPANSION, which + // POSTs against a saved area's graph — borrow any one for that; if there are none, + // expansion is simply unavailable (a brand-new, unsaved area has no graph to walk). useEffect(() => { fetchSubjectAreas() .then((d) => { - const first = d.subject_areas[0]; - if (!first) return; - setSeedAreaId(first.id); - return fetchSubjectArea(first.id).then((detail) => - setAll([...detail.included, ...detail.available]), - ); + if (d.subject_areas[0]) setSeedAreaId(d.subject_areas[0].id); }) - .catch((e) => setError(String(e))); + .catch(() => { + /* no area to seed expansion from; the picker still works from `objects` */ + }); }, []); const memberSet = useMemo(() => new Set(members), [members]); @@ -201,7 +205,16 @@ export function NewSubjectAreaModal({ onChange={(e) => setLevels(Math.max(1, Math.min(10, Number(e.target.value))))} /> - diff --git a/packages/server/src/mdl_server/static/assets/sme-DsrrbJWz.js b/packages/server/src/mdl_server/static/assets/sme-DsrrbJWz.js new file mode 100644 index 0000000..2a15df7 --- /dev/null +++ b/packages/server/src/mdl_server/static/assets/sme-DsrrbJWz.js @@ -0,0 +1,2 @@ +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/ModelDiagram-C1PzETqC.js","assets/client-DOmNpTln.js","assets/styles-DG4k7QGS.js","assets/styles-B4bhxAsE.css"])))=>i.map(i=>d[i]); +import{r as n,j as e,c as Ae,R as Re}from"./client-DOmNpTln.js";import{p as je,b as xe,c as $e,e as Me,i as De,d as fe,h as Pe,j as be,R as Oe,M as Ie,O as Te,L as Le,k as Be,l as Fe,A as qe,m as ze,n as We,o as Ge,q as He,s as Ke,f as ie,r as Ve,t as Ue,u as Ye}from"./styles-DG4k7QGS.js";/* empty css */const Qe="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function ce(s,t){let a="";for(let r=0;r>=5n;return a}function ge(){const s=BigInt(Date.now())&(1n<<48n)-1n,t=new Uint8Array(10);crypto.getRandomValues(t);let a=0n;for(const r of t)a=a<<8n|BigInt(r);return ce(s,10)+ce(a,16)}const Je={create_entity:["id","conceptual_id"],create_relationship:["id","from_fk_id"],create_subject_area:["id"],create_term:["id"],create_domain:["id"],create_code_set:["id"],create_key_group:["id"],create_category:["id"],add_attribute:["id"]};function Xe(s,t){const a=Je[s];if(!a)return t;const r={...t};for(const l of a)r[l]||(r[l]=ge());return r}function ve(s,t){if(s.startsWith("create_")||s==="add_attribute")return`${s}:${t.id??t.attribute_id}`;const a=t.id??t.entity_id??"",r=t.attribute_id??"";return r?`${s}:${a}:${r}`:`${s}:${a}`}function Ze({subjectArea:s,describe:t}){const[a,r]=n.useState([]),[l,x]=n.useState(null),[c,i]=n.useState(!1),u=n.useRef(null),N=n.useCallback(async(m,v)=>{v=Xe(m,v),m==="set_alignment"&&v.status===void 0&&(v={...v,status:"proposed"});const S=ve(m,v),{label:C,before:T,after:_}=t(m,v);return r(g=>{const k=g.filter(E=>E.key!==S);if(m.startsWith("delete_")){const E=v.id??v.entity_id;if(E&&g.some(h=>h.op.startsWith("create_")&&h.payload.id===E))return k.filter(h=>h.payload.id!==E)}return[...k,{op:m,payload:v,label:C,before:T,after:_,key:S}]}),{ok:!0,fingerprint:"",created_id:v.id??null,diagnostics:[]}},[t]);n.useEffect(()=>{if(!a.length){x(null);return}const m=setTimeout(()=>{var S;(S=u.current)==null||S.abort();const v=new AbortController;u.current=v,i(!0),je(a.map(C=>({op:C.op,payload:C.payload})),s,v.signal).then(x).catch(C=>{C.name!=="AbortError"&&x(null)}).finally(()=>i(!1))},250);return()=>clearTimeout(m)},[a,s]);const p=n.useCallback(m=>{r(v=>v.filter(S=>S.key!==m))},[]),d=n.useCallback(()=>r([]),[]);return n.useMemo(()=>({pending:a,previewDoc:(l==null?void 0:l.model)??null,previewDiff:(l==null?void 0:l.diff)??null,diagnostics:(l==null?void 0:l.diagnostics)??[],failure:l&&l.failed_index!==null?{index:l.failed_index,error:l.error??"invalid change"}:null,busy:c,exec:N,drop:p,clear:d}),[a,l,c,N,p,d])}function es(s){const t=new Map;for(const r of s)for(const l of["id","conceptual_id","from_fk_id"]){const x=r.payload[l];r.op.startsWith("create_")&&typeof x=="string"&&t.set(x,r.key)}if(!t.size)return new Set;const a=new Set;for(const r of s)if(!r.op.startsWith("create_"))for(const l of Object.values(r.payload))typeof l=="string"&&t.has(l)&&(a.add(r.key),a.add(t.get(l)));return a}function ss({user:s}){const[t,a]=n.useState(null);return n.useEffect(()=>{xe(s).then(a).catch(()=>{})},[s]),!(t!=null&&t.ok)||!t.git?null:t.read_only?e.jsxs("div",{className:"git-banner",children:[e.jsx("code",{children:t.branch}),e.jsx("span",{children:"browse only — editing is disabled on this deployment"})]}):t.dirty?e.jsxs("div",{className:"git-banner warn",children:[e.jsx("code",{children:t.branch}),e.jsx("span",{children:"uncommitted changes in the working tree — a proposal will be refused until they are committed or discarded"})]}):t.on_base?e.jsxs("div",{className:"git-banner",children:[e.jsx("code",{children:t.branch}),e.jsx("span",{children:"protected — your edits become a pull request"})]}):e.jsxs("div",{className:"git-banner",children:[e.jsx("code",{children:t.branch}),e.jsx("span",{children:"your proposal branch"}),t.behind?e.jsxs("span",{className:"dim",children:["· ",t.base_branch," is ",t.behind," ahead"]}):null]})}function ns({exec:s,canEdit:t,onImported:a}){const[r,l]=n.useState(null),[x,c]=n.useState([]),[i,u]=n.useState("postgres"),N=n.useRef(null);n.useEffect(()=>{$e().then(d=>c(d.formats)).catch(()=>c([]))},[]),n.useEffect(()=>{if(!r)return;const d=m=>{N.current&&!N.current.contains(m.target)&&l(null)};return document.addEventListener("mousedown",d),()=>document.removeEventListener("mousedown",d)},[r]);const p=x.find(d=>d.id==="sql");return e.jsxs("div",{className:"ie-wrap",ref:N,children:[e.jsx("button",{className:"erd-action ghost"+(r==="export"?" on":""),onClick:()=>l(r==="export"?null:"export"),children:"Export ▾"}),t&&e.jsx("button",{className:"erd-action ghost"+(r==="import"?" on":""),onClick:()=>l(r==="import"?null:"import"),children:"Import ▾"}),r==="export"&&e.jsxs("div",{className:"ie-menu",role:"menu",children:[(p==null?void 0:p.dialects)&&e.jsxs("label",{className:"ie-dialect",children:[e.jsx("span",{children:"SQL dialect"}),e.jsx("select",{value:i,onChange:d=>u(d.target.value),children:p.dialects.map(d=>e.jsx("option",{value:d,children:d},d))})]}),x.map(d=>e.jsx("a",{className:"ie-item",href:Me(d.id,d.id==="sql"?i:void 0),download:!0,onClick:()=>l(null),children:d.label},d.id))]}),r==="import"&&e.jsx(as,{exec:s,onClose:()=>l(null),onImported:d=>{l(null),a==null||a(d)}})]})}const oe=[{id:"sql",label:"SQL DDL",hint:"CREATE TABLE … PRIMARY KEY / FOREIGN KEY"},{id:"mermaid",label:"Mermaid erDiagram",hint:"structural: entities, attributes, relationships"},{id:"json-schema",label:"JSON Schema",hint:"each object definition becomes an entity"}];function as({exec:s,onClose:t,onImported:a}){var _;const[r,l]=n.useState("sql"),[x,c]=n.useState("postgres"),[i,u]=n.useState(""),[N,p]=n.useState(!1),[d,m]=n.useState(null),[v,S]=n.useState([]),C=g=>{g&&g.text().then(u).catch(()=>m("could not read that file"))},T=async()=>{p(!0),m(null),S([]);try{const g=await De(r,i,r==="sql"?x:void 0);if(!g.changes.length){m(g.warnings[0]??"nothing to import from that document"),p(!1);return}for(const k of g.changes)await s(k.op,k.payload);S(g.warnings),a(g.tables)}catch(g){m(g instanceof Error?g.message:String(g))}finally{p(!1)}};return e.jsxs("div",{className:"ie-menu ie-import",role:"dialog",children:[e.jsx("div",{className:"ie-tabs",children:oe.map(g=>e.jsx("button",{className:"ie-tab"+(r===g.id?" active":""),onClick:()=>l(g.id),children:g.label},g.id))}),e.jsx("p",{className:"ie-hint",children:(_=oe.find(g=>g.id===r))==null?void 0:_.hint}),r==="sql"&&e.jsxs("label",{className:"ie-dialect",children:[e.jsx("span",{children:"dialect"}),e.jsx("select",{value:x,onChange:g=>c(g.target.value),children:["postgres","snowflake","mysql","duckdb"].map(g=>e.jsx("option",{children:g},g))})]}),e.jsx("textarea",{className:"ie-textarea",placeholder:`Paste ${r} here, or choose a file…`,value:i,onChange:g=>u(g.target.value),rows:8}),e.jsx("input",{type:"file",className:"ie-file",accept:".sql,.mmd,.md,.json,.txt",onChange:g=>{var k;return C(((k=g.target.files)==null?void 0:k[0])??null)}}),r==="mermaid"&&e.jsx("p",{className:"ie-warn",children:"Mermaid is a diagram format: types and keys are approximate and relationships are inferred from the lines."}),v.map((g,k)=>e.jsx("p",{className:"ie-warn",children:g},k)),d&&e.jsx("p",{className:"ie-error",children:d}),e.jsxs("div",{className:"ie-foot",children:[e.jsx("button",{className:"erd-action ghost",onClick:t,children:"Cancel"}),e.jsx("button",{className:"erd-action",disabled:N||!i.trim(),onClick:T,children:N?"Importing…":"Import & review"})]})]})}function ts({exec:s,canEdit:t,stagedMembers:a}){const[r,l]=n.useState([]),[x,c]=n.useState(""),[i,u]=n.useState(null),[N,p]=n.useState(""),[d,m]=n.useState(new Set),[v,S]=n.useState("ancestors"),[C,T]=n.useState(1),[_,g]=n.useState(null),[k,E]=n.useState(new Set),[$,h]=n.useState(null);n.useEffect(()=>{fe().then(o=>{l(o.subject_areas),c(y=>{var M;return y||((M=o.subject_areas[0])==null?void 0:M.id)||""})}).catch(o=>h(String(o)))},[]),n.useEffect(()=>{x&&(g(null),m(new Set),Pe(x).then(u).catch(o=>h(String(o))))},[x]);const A=n.useMemo(()=>a[x]??(i==null?void 0:i.members)??[],[a,x,i]),R=n.useMemo(()=>{const o=new Map;for(const y of[...(i==null?void 0:i.included)??[],...(i==null?void 0:i.available)??[]])o.set(y.id,y);return o},[i]),D=N.trim().toLowerCase(),L=o=>!D||o.name.toLowerCase().includes(D),j=n.useMemo(()=>new Set(A),[A]),P=n.useMemo(()=>A.map(o=>R.get(o)).filter(o=>!!o&&L(o)),[A,R,D]),F=n.useMemo(()=>[...R.values()].filter(o=>!j.has(o.id)&&L(o)),[R,j,D]),O=n.useCallback(o=>{if(!i)return;const y=o.filter(z=>!(i.members??[]).includes(z)).length,M=(i.members??[]).filter(z=>!o.includes(z)).length;s("set_subject_area_members",{id:i.id,members:o,__label:`${i.name}: ${o.length} objects (+${y} −${M})`})},[i,s]),q=(o,y)=>{const M=y?[...A,...o.filter(z=>!j.has(z))]:A.filter(z=>!o.includes(z));O(M),m(new Set)},W=(o,y,M)=>{const z=new Set(o);z.has(y)?z.delete(y):z.add(y),M(z)},U=()=>{if(!i)return;const o=d.size?[...d]:A;if(!o.length){h("add an object first, or select one to expand from");return}h(null),be(i.id,{seeds:o,direction:v,levels:C}).then(y=>{g(y.hops),E(new Set(y.added))}).catch(y=>h(String(y)))},V=()=>{q([...k],!0),g(null)};if($&&!i)return e.jsx("p",{className:"sme-splash error",children:$});if(!r.length)return e.jsx("p",{className:"sme-placeholder",children:"No subject areas yet."});const b=((i==null?void 0:i.inconsistent)??[]).filter(o=>!j.has(o));return e.jsxs("div",{className:"sa-editor",children:[e.jsx("nav",{className:"sme-nav",children:r.map(o=>e.jsxs("button",{className:"sme-sa"+(o.id===x?" active":""),onClick:()=>c(o.id),title:o.definition??void 0,children:[o.name,e.jsx("span",{className:"sme-sa-count",children:(a[o.id]??o.members).length})]},o.id))}),e.jsxs("div",{className:"sa-main",children:[e.jsxs("div",{className:"sa-head",children:[e.jsx("h2",{children:(i==null?void 0:i.name)??"…"}),(i==null?void 0:i.definition)&&e.jsx("p",{className:"sme-muted",children:i.definition}),e.jsx("input",{className:"sme-search sa-filter",type:"search",placeholder:"Filter both lists…",value:N,onChange:o=>p(o.target.value)})]}),b.length>0&&t&&e.jsxs("div",{className:"sa-nudge",children:[b.length," object",b.length>1?"s are":" is"," homed in this area but not included.",e.jsxs("button",{className:"sme-link",onClick:()=>q(b,!0),children:["Add ",b.length>1?"them all":"it"]})]}),e.jsxs("div",{className:"sa-panes",children:[e.jsx(J,{title:`Available (${F.length})`,rows:F,picked:d,onToggle:o=>W(d,o,m),homedHere:(i==null?void 0:i.homed_here)??[]}),e.jsxs("div",{className:"sa-controls",children:[e.jsx("button",{className:"sme-secondary",disabled:!t||!d.size,onClick:()=>q([...d].filter(o=>!j.has(o)),!0),title:"Add the selected objects",children:"→"}),e.jsx("button",{className:"sme-secondary",disabled:!t||!F.length,onClick:()=>q(F.map(o=>o.id),!0),title:"Add everything matching the filter",children:"»"}),e.jsx("button",{className:"sme-secondary",disabled:!t||!d.size,onClick:()=>q([...d].filter(o=>j.has(o)),!1),title:"Remove the selected objects",children:"←"}),e.jsx("button",{className:"sme-secondary",disabled:!t||!P.length,onClick:()=>q(P.map(o=>o.id),!1),title:"Remove everything matching the filter",children:"«"})]}),e.jsx(J,{title:`Included (${P.length})`,rows:P,picked:d,onToggle:o=>W(d,o,m),homedHere:(i==null?void 0:i.homed_here)??[]})]}),e.jsxs("div",{className:"sa-expand",children:[e.jsx("h3",{children:"Add related objects"}),e.jsxs("div",{className:"sa-expand-row",children:[e.jsxs("select",{value:v,onChange:o=>S(o.target.value),children:[e.jsx("option",{value:"ancestors",children:"ancestors — the parents its keys point at"}),e.jsx("option",{value:"descendants",children:"descendants — what points at it"}),e.jsx("option",{value:"both",children:"both"})]}),e.jsxs("label",{children:["levels",e.jsx("input",{type:"number",min:1,max:10,value:C,onChange:o=>T(Math.max(1,Math.min(10,Number(o.target.value))))})]}),e.jsx("button",{className:"sme-secondary",onClick:U,disabled:!t,children:"Preview"})]}),$&&e.jsx("p",{className:"sme-error-line",children:$}),_&&e.jsxs("div",{className:"sa-hops",children:[_.length===0&&e.jsx("p",{className:"sme-muted",children:"nothing new to add"}),_.map(o=>e.jsxs("label",{className:"sa-hop",children:[e.jsx("input",{type:"checkbox",checked:k.has(o.id),onChange:()=>W(k,o.id,E)}),e.jsx("span",{className:"sa-hop-name",children:o.name}),e.jsxs("span",{className:"sa-hop-why",children:["via ",o.via_name," · ",o.direction.replace(/s$/,""),", level ",o.level]})]},o.id)),_.length>0&&e.jsxs("div",{className:"sa-hop-actions",children:[e.jsx("button",{className:"sme-secondary",onClick:()=>g(null),children:"Cancel"}),e.jsxs("button",{className:"sme-primary",onClick:V,disabled:!k.size,children:["Add ",k.size," object",k.size===1?"":"s"]})]})]})]})]})]})}function J({title:s,rows:t,picked:a,onToggle:r,homedHere:l}){const x=n.useMemo(()=>new Set(l),[l]);return e.jsxs("div",{className:"sa-pane",children:[e.jsx("h3",{children:s}),e.jsxs("ul",{children:[t.map(c=>e.jsxs("li",{className:"sa-row"+(a.has(c.id)?" picked":""),onClick:()=>r(c.id),children:[e.jsx("input",{type:"checkbox",readOnly:!0,checked:a.has(c.id)}),e.jsx("span",{className:"sa-row-name",children:c.name}),x.has(c.id)?e.jsx("span",{className:"sme-badge",children:"home"}):e.jsx("span",{className:"sme-badge borrowed",children:"borrowed"})]},c.id)),!t.length&&e.jsx("li",{className:"sme-empty",children:"nothing here"})]})]})}function rs({objects:s,onCreate:t,onClose:a}){const[r,l]=n.useState(""),[x,c]=n.useState(""),[i]=n.useState(s),[u,N]=n.useState([]),[p,d]=n.useState(new Set),[m,v]=n.useState(""),[S,C]=n.useState("ancestors"),[T,_]=n.useState(1),[g,k]=n.useState(null),[E,$]=n.useState(new Set),[h,A]=n.useState(""),[R,D]=n.useState(null);n.useEffect(()=>{fe().then(b=>{b.subject_areas[0]&&A(b.subject_areas[0].id)}).catch(()=>{})},[]);const L=n.useMemo(()=>new Set(u),[u]),j=m.trim().toLowerCase(),P=b=>!j||b.name.toLowerCase().includes(j),F=n.useMemo(()=>u.map(b=>i.find(o=>o.id===b)).filter(b=>!!b&&P(b)),[u,i,j]),O=n.useMemo(()=>i.filter(b=>!L.has(b.id)&&P(b)),[i,L,j]),q=(b,o)=>{N(y=>o?[...y,...b.filter(M=>!y.includes(M))]:y.filter(M=>!b.includes(M))),d(new Set)},W=(b,o,y)=>{const M=new Set(b);M.has(o)?M.delete(o):M.add(o),y(M)},U=()=>{const b=p.size?[...p]:u;if(!b.length){D("pick an object to expand from first");return}h&&(D(null),be(h,{seeds:b,direction:S,levels:T}).then(o=>{const y=o.hops.filter(M=>!L.has(M.id));k(y),$(new Set(y.map(M=>M.id)))}).catch(o=>D(String(o))))},V=()=>{r.trim()&&t(r.trim(),x.trim(),u)};return e.jsx("div",{className:"sme-modal-backdrop",onClick:a,children:e.jsxs("div",{className:"sme-modal sa-modal",onClick:b=>b.stopPropagation(),children:[e.jsx("h2",{children:"New subject area"}),e.jsx("p",{className:"sme-hint",children:"A subject area is a filtered view of the model. Name it and choose what it contains — both land as one proposal."}),e.jsxs("div",{className:"sme-field-row",children:[e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Name"}),e.jsx("input",{autoFocus:!0,value:r,placeholder:"e.g. UseCase-ApraStressTesting",onChange:b=>l(b.target.value),onKeyDown:b=>b.key==="Enter"&&u.length===0&&V()})]}),e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Definition (optional)"}),e.jsx("input",{value:x,placeholder:"what this view is for",onChange:b=>c(b.target.value)})]})]}),e.jsx("input",{className:"sme-search sa-filter",type:"search",placeholder:"Filter both lists…",value:m,onChange:b=>v(b.target.value)}),e.jsxs("div",{className:"sa-panes",children:[e.jsx(J,{title:`Available (${O.length})`,rows:O,picked:p,onToggle:b=>W(p,b,d),homedHere:[]}),e.jsxs("div",{className:"sa-controls",children:[e.jsx("button",{className:"sme-secondary",disabled:!p.size,onClick:()=>q([...p].filter(b=>!L.has(b)),!0),title:"Add the selected objects",children:"→"}),e.jsx("button",{className:"sme-secondary",disabled:!O.length,onClick:()=>q(O.map(b=>b.id),!0),title:"Add everything matching the filter",children:"»"}),e.jsx("button",{className:"sme-secondary",disabled:!p.size,onClick:()=>q([...p].filter(b=>L.has(b)),!1),title:"Remove the selected objects",children:"←"}),e.jsx("button",{className:"sme-secondary",disabled:!F.length,onClick:()=>q(F.map(b=>b.id),!1),title:"Remove everything matching the filter",children:"«"})]}),e.jsx(J,{title:`Included (${F.length})`,rows:F,picked:p,onToggle:b=>W(p,b,d),homedHere:[]})]}),e.jsxs("div",{className:"sa-expand",children:[e.jsx("h3",{children:"Add related objects"}),e.jsxs("div",{className:"sa-expand-row",children:[e.jsxs("select",{value:S,onChange:b=>C(b.target.value),children:[e.jsx("option",{value:"ancestors",children:"ancestors — the parents its keys point at"}),e.jsx("option",{value:"descendants",children:"descendants — what points at it"}),e.jsx("option",{value:"both",children:"both"})]}),e.jsxs("label",{children:["levels",e.jsx("input",{type:"number",min:1,max:10,value:T,onChange:b=>_(Math.max(1,Math.min(10,Number(b.target.value))))})]}),e.jsx("button",{className:"sme-secondary",onClick:U,disabled:!h,title:h?"Preview related objects to add":"Available once the model has at least one saved subject area to walk the graph from",children:"Preview"})]}),g&&e.jsxs("div",{className:"sa-hops",children:[g.length===0&&e.jsx("p",{className:"sme-muted",children:"nothing new to add"}),g.map(b=>e.jsxs("label",{className:"sa-hop",children:[e.jsx("input",{type:"checkbox",checked:E.has(b.id),onChange:()=>W(E,b.id,$)}),e.jsx("span",{className:"sa-hop-name",children:b.name}),e.jsxs("span",{className:"sa-hop-why",children:["via ",b.via_name," · ",b.direction.replace(/s$/,""),", level ",b.level]})]},b.id)),g.length>0&&e.jsxs("div",{className:"sa-hop-actions",children:[e.jsx("button",{className:"sme-secondary",onClick:()=>k(null),children:"Cancel"}),e.jsxs("button",{className:"sme-primary",disabled:!E.size,onClick:()=>{q([...E],!0),k(null)},children:["Add ",E.size]})]})]})]}),R&&e.jsx("p",{className:"sme-error-line",children:R}),e.jsxs("div",{className:"sme-modal-foot",children:[e.jsx("button",{className:"sme-secondary",onClick:a,children:"Cancel"}),e.jsxs("button",{className:"sme-primary",disabled:!r.trim(),onClick:V,children:["Create",u.length>0&&` with ${u.length} object${u.length===1?"":"s"}`]})]})]})})}const ls=new Set(["set_definition","set_stewardship","set_subject_area","set_pattern","rename_entity","add_attribute","update_attribute","delete_attribute","set_alignment","clear_alignment","set_term_map","clear_term_map","update_synonyms","set_object_definition","set_subject_area_members","create_subject_area","update_subject_area","create_entity","delete_entity","create_relationship","rename_relationship","update_relationship","delete_relationship"]);function is({doc:s,subjectArea:t,onSubjectArea:a,exec:r,canEdit:l,direct:x=!1,onSelectEntity:c,onImported:i,busy:u}){var $;const[N,p]=n.useState(null),[d,m]=n.useState(null),[v,S]=n.useState(!1),[C,T]=n.useState(null),[_]=n.useState(""),g=n.useRef(null),k=n.useMemo(()=>({canEdit:l,mode:x?"direct":"staged",allow:x?void 0:ls,canArbitrate:x,canCommit:x}),[l,x]),E=n.useCallback(h=>{p(h),h&&(c==null||c(h))},[c]);return e.jsxs("div",{className:"sme-model-view",children:[e.jsxs("nav",{className:"sme-nav",children:[e.jsxs("div",{className:"sme-nav-head",children:[e.jsx("span",{children:"Subject areas"}),l&&e.jsx("button",{className:"sme-nav-add",title:"New subject area",onClick:()=>S(!0),children:"+"})]}),e.jsxs("button",{className:"sme-sa"+(t===""?" active":""),onClick:()=>a(""),children:["Whole model",e.jsx("span",{className:"sme-sa-count",children:(($=s.counts)==null?void 0:$.entities_total)??s.entities.length})]}),s.subject_areas.map(h=>e.jsxs("button",{className:"sme-sa"+(t===h.id?" active":""),onClick:()=>a(h.id),title:h.definition??void 0,children:[h.name,e.jsx("span",{className:"sme-sa-count",children:h.member_count??0})]},h.id))]}),v&&e.jsx(rs,{objects:s.entities.flatMap(h=>h.conceptual?[{id:h.conceptual.id,kind:"conceptual_entity",name:h.conceptual.name,definition:h.conceptual.definition??null,synonyms:[],subject_area:h.conceptual.subject_area??null,stewardship:null,ontology:null,where_used:[]}]:[]),onClose:()=>S(!1),onCreate:(h,A,R)=>{const D=ge();r("create_subject_area",{name:h,...A?{definition:A}:{},id:D}),R.length&&r("set_subject_area_members",{id:D,members:R}),S(!1),a(D)}}),e.jsxs("div",{className:"erd-wrap",children:[e.jsxs("div",{className:"erd-bar",children:[e.jsxs("span",{className:"erd-count",children:[s.entities.length," entities · ",s.relationships.length," relationships",s.scope&&" · scoped"]}),l&&e.jsx("button",{className:"erd-action",onClick:()=>{var h;return(h=g.current)==null?void 0:h.newEntity()},title:"Add an entity (n)",children:"+ Entity"}),e.jsx("span",{className:"erd-hint",children:"drag between entities to relate them"}),e.jsxs("span",{className:"erd-panels",children:[e.jsx("button",{className:"erd-action ghost"+(d==="ontology"?" on":""),onClick:()=>m(h=>h==="ontology"?null:"ontology"),children:"Ontology"}),e.jsx("button",{className:"erd-action ghost"+(d==="layers"?" on":""),onClick:()=>m(h=>h==="layers"?null:"layers"),children:"Layers"}),e.jsx(ns,{exec:r,canEdit:l,onImported:i})]}),u&&e.jsx("span",{className:"erd-busy",children:"updating…"}),!l&&e.jsx("span",{className:"sme-chip",children:"read-only"}),C&&e.jsx("span",{className:"erd-error",children:C})]}),e.jsxs("div",{className:"erd-canvas canvas-wrap",children:[e.jsx(Oe,{children:e.jsx(Ie,{doc:s,caps:k,exec:r,selectedId:N,onSelect:E,query:_,onError:T,handleRef:g})}),d&&e.jsxs("aside",{className:"side-panel",children:[e.jsxs("div",{className:"detail-header",children:[e.jsx("h2",{children:d==="ontology"?"Ontology":"Ontology layers"}),e.jsx("button",{className:"icon-btn",onClick:()=>m(null),children:"✕"})]}),d==="ontology"?e.jsx(Te,{}):e.jsx(Le,{refreshKey:0,onFocusEntity:h=>{var A;m(null),(A=g.current)==null||A.focusEntity(h)}})]})]})]})]})}function cs({user:s,onView:t}){const[a,r]=n.useState(null),[l,x]=n.useState(null);if(n.useEffect(()=>{Be(s).then(r).catch(i=>x(String(i)))},[s]),l)return e.jsx("p",{className:"sme-splash error",children:l});if(!a)return e.jsx("div",{className:"sme-splash",children:"◮ looking for your proposals…"});if(!a.git)return e.jsx("p",{className:"sme-placeholder",children:"This model isn't in a git repository."});const c=a.proposals.filter(i=>!i.merged).length;return e.jsxs("div",{className:"pr-wrap",children:[e.jsxs("div",{className:"pr-head",children:[e.jsx("h2",{children:"My proposals"}),e.jsxs("span",{className:"meta",children:[c," open · base: ",a.base]})]}),!a.proposals.length&&e.jsx("p",{className:"sme-placeholder",children:"Nothing proposed yet. Edits you submit for review will appear here."}),a.proposals.map(i=>e.jsx(ds,{p:i,gh:a.gh,onView:()=>t(i.branch)},i.branch)),!a.gh&&a.proposals.length>0&&e.jsxs("div",{className:"pr-degrade",children:["Review status isn't available here (",e.jsx("code",{children:"gh"})," is not installed or not signed in). Branch, contents, merge state and conflicts all still come from git — each card links out to the compare view."]})]})}function os(s,t){var a,r;return s.merged?{kind:"accepted",text:"✅ Accepted"}:s.pushed?t&&((a=s.pr)==null?void 0:a.state)==="CLOSED"?{kind:"declined",text:"✖ Declined"}:t&&((r=s.pr)==null?void 0:r.reviews)==="CHANGES_REQUESTED"?{kind:"changes",text:"💬 Changes requested"}:{kind:"awaiting",text:"⏳ Awaiting review"}:{kind:"local",text:"📥 Saved locally"}}function ds({p:s,gh:t,onView:a}){const r=os(s,t);return e.jsxs("div",{className:"pr-card"+(r.kind==="attention"?" attention":""),children:[e.jsxs("div",{className:"pr-top",children:[e.jsx("span",{className:"pr-title",children:s.title}),e.jsx("span",{className:"pr-chip "+r.kind,children:r.text})]}),e.jsxs("div",{className:"pr-branch",children:[s.branch," · ",s.created,s.behind>0&&` · ${s.behind} behind`]}),e.jsxs("div",{className:"pr-foot",children:[e.jsx("span",{className:"pr-link",children:s.merged?e.jsxs(e.Fragment,{children:["merged",t&&s.pr?` · PR #${s.pr.number}`:""]}):s.pushed?t&&s.pr?e.jsxs("a",{href:s.pr.url,target:"_blank",rel:"noreferrer",children:["PR #",s.pr.number]}):s.compare_url?e.jsx("a",{href:s.compare_url,target:"_blank",rel:"noreferrer",children:"open the compare view →"}):"pushed":"not sent yet — no connection to the shared repo"}),e.jsx("span",{className:"pr-btns",children:e.jsx("button",{className:"sme-secondary",onClick:a,children:"View"})})]})]})}function hs({changes:s,routeAdvice:t,identity:a,user:r,onUser:l,onDrop:x,onClose:c,onProposed:i}){const u=!!(a&&a.source!=="anonymous"),[N,p]=n.useState(r||(localStorage.getItem("mdl.sme.user")??"")),[d,m]=n.useState(s.length===1?`Update ${s[0].label.toLowerCase()}`:"Glossary updates"),[v,S]=n.useState(""),[C,T]=n.useState(!1),_=Ne(C?v:d),[g,k]=n.useState(""),[E,$]=n.useState(!1),[h,A]=n.useState(null),[R,D]=n.useState(null),L=()=>{!u&&!N.trim()||(u||(localStorage.setItem("mdl.sme.user",N.trim()),l==null||l(N.trim())),$(!0),D(null),Fe({user:u?"":N.trim(),slug:_,title:d,body:g.trim(),changes:s.map(j=>({op:j.op,payload:j.payload}))}).then(A).catch(j=>D(j instanceof qe?j.message:String(j))).finally(()=>$(!1)))};return h!=null&&h.ok?e.jsx("div",{className:"sme-modal-backdrop",onClick:()=>i(h),children:e.jsxs("div",{className:"sme-modal",onClick:j=>j.stopPropagation(),children:[e.jsx("h2",{children:"✓ Sent for review"}),e.jsx("p",{children:h.message}),h.pr_url&&e.jsx("p",{children:e.jsx("a",{href:h.pr_url,target:"_blank",rel:"noreferrer",children:"View pull request →"})}),h.compare_url&&e.jsx("p",{children:e.jsx("a",{href:h.compare_url,target:"_blank",rel:"noreferrer",children:"Open the pull request →"})}),e.jsxs("p",{className:"sme-muted",children:["Your suggestion is on branch ",e.jsx("code",{children:h.branch}),". A steward will review it."]}),e.jsx("div",{className:"sme-modal-foot",children:e.jsx("button",{className:"sme-primary",onClick:()=>i(h),children:"Done"})})]})}):e.jsx("div",{className:"sme-modal-backdrop",onClick:c,children:e.jsxs("div",{className:"sme-modal",onClick:j=>j.stopPropagation(),children:[e.jsxs("div",{className:"sme-card-head",children:[e.jsx("h2",{children:"Submit for review"}),e.jsx("button",{className:"sme-secondary",onClick:c,children:"✕"})]}),e.jsx("h3",{children:"Your changes"}),e.jsx("ul",{className:"sme-diff",children:s.map((j,P)=>e.jsxs("li",{children:[e.jsxs("div",{className:"sme-diff-head",children:[e.jsx("b",{children:j.label}),e.jsx("button",{className:"sme-link danger",onClick:()=>x(P),children:"discard"})]}),e.jsx("div",{className:"sme-diff-before",children:j.before}),e.jsx("div",{className:"sme-diff-after",children:j.after})]},P))}),u?e.jsxs("p",{className:"sme-signed-in",children:["Signed in as ",e.jsx("strong",{children:a.name}),a.email?` (${a.email})`:""," — this proposal is attributed to you."]}):e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Your name (for attribution)"}),e.jsx("input",{value:N,onChange:j=>p(j.target.value),placeholder:"e.g. a.hough"})]}),e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Title"}),e.jsx("input",{value:d,onChange:j=>m(j.target.value)})]}),e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Branch name (edit to add a ticket id, e.g. PROJ-123)"}),e.jsx("input",{value:C?v:_,onChange:j=>{T(!0),S(j.target.value)},placeholder:"clarify-counterparty"})]}),e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Note for the reviewer (optional)"}),e.jsx("textarea",{rows:2,value:g,onChange:j=>k(j.target.value)})]}),e.jsx(ms,{cl:t,user:u?a.name:N,changeSlug:_}),R&&e.jsx("p",{className:"sme-error-line",children:R}),e.jsxs("div",{className:"sme-modal-foot",children:[e.jsx("button",{className:"sme-secondary",onClick:c,children:"Keep editing"}),e.jsx("button",{className:"sme-primary",disabled:E||!u&&!N.trim()||s.length===0,onClick:L,children:E?"Submitting…":"Submit for review"})]})]})})}function ms({cl:s,user:t,changeSlug:a}){if(!(s!=null&&s.primary))return null;const r=s.reviewers_actual??s.reviewers,l=Ne(t||"you"),x=s.routes.length>1;return e.jsxs(e.Fragment,{children:[x&&e.jsxs("div",{className:"sub-advice",children:[e.jsx("span",{className:"i",children:"ⓘ"})," These changes span routes"," ",e.jsx("strong",{children:s.routes.join(" and ")}),". The whole proposal will be reviewed as route ",s.primary," (",s.primary_name,") — the strictest gate wins, so it needs"," ",r.join(", ")," plus ",e.jsx("code",{children:s.gates[s.gates.length-1]}),". Proposing the meaning-only changes separately would get them reviewed faster."]}),e.jsxs("div",{className:"sub-dest",children:[e.jsxs("div",{className:"row",children:[e.jsx("span",{className:"k",children:"Goes to"}),e.jsxs("code",{children:["sme/",l,"/",a||"…"]})]}),e.jsxs("div",{className:"row",children:[e.jsx("span",{className:"k",children:"Reviewers"}),e.jsx("span",{children:r.join(", ")})]})]})]})}function Ne(s){return s.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")||"change"}const us="modulepreload",ps=function(s){return"/"+s},de={},js=function(t,a,r){let l=Promise.resolve();if(a&&a.length>0){document.getElementsByTagName("link");const c=document.querySelector("meta[property=csp-nonce]"),i=(c==null?void 0:c.nonce)||(c==null?void 0:c.getAttribute("nonce"));l=Promise.allSettled(a.map(u=>{if(u=ps(u),u in de)return;de[u]=!0;const N=u.endsWith(".css"),p=N?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${u}"]${p}`))return;const d=document.createElement("link");if(d.rel=N?"stylesheet":us,N||(d.as="script"),d.crossOrigin="",d.href=u,i&&d.setAttribute("nonce",i),document.head.appendChild(d),N)return new Promise((m,v)=>{d.addEventListener("load",m),d.addEventListener("error",()=>v(new Error(`Unable to preload CSS for ${u}`)))})}))}function x(c){const i=new Event("vite:preloadError",{cancelable:!0});if(i.payload=c,window.dispatchEvent(i),!i.defaultPrevented)throw c}return l.then(c=>{for(const i of c||[])i.status==="rejected"&&x(i.reason);return t().catch(x)})};function xs({diff:s,selectable:t,selected:a,onToggle:r}){var i;const[l,x]=n.useState(((i=s.objects[0])==null?void 0:i.ulid)??null),c=n.useMemo(()=>s.objects.find(u=>u.ulid===l)??s.objects[0]??null,[s,l]);return s.objects.length?e.jsxs("div",{className:"rv-body",children:[e.jsx("div",{className:"rv-list",children:s.objects.map(u=>e.jsx(bs,{obj:u,active:u.ulid===((c==null?void 0:c.ulid)??""),checked:(a==null?void 0:a.has(u.ulid))??!0,selectable:t,onSelect:()=>x(u.ulid),onToggle:()=>r==null?void 0:r(u.ulid)},u.ulid))}),e.jsx("div",{className:"rv-detail",children:c&&e.jsx(gs,{obj:c})})]}):e.jsxs("p",{className:"sme-placeholder",children:["No model changes against ",s.base.label,"."]})}function fs(s){return s.renamed?"renamed":s.severity==="breaking"?"breaking":s.change}function bs({obj:s,active:t,checked:a,selectable:r,onSelect:l,onToggle:x}){return e.jsxs("div",{className:"rv-obj"+(t?" active":""),onClick:l,children:[e.jsxs("div",{className:"rv-obj-head",children:[r&&e.jsx("input",{type:"checkbox",checked:a,onClick:c=>c.stopPropagation(),onChange:x,"aria-label":`include ${s.name_before??s.name_after}`}),e.jsx("span",{className:"rv-obj-name",children:s.name_before??s.name_after}),e.jsx("span",{className:"rv-obj-kind",children:s.object_kind_label}),e.jsxs("span",{className:"sev "+(s.renamed?"renamed":s.severity),children:[s.severity==="breaking"&&"⚠ ",fs(s)]})]}),e.jsxs("ul",{className:"rv-obj-fields",children:[s.fields.map((c,i)=>e.jsx("li",{children:c.label},c.field+i)),s.children.flatMap(c=>c.fields.map((i,u)=>e.jsxs("li",{children:[i.label,": ",c.name_before??c.name_after]},c.ulid+u)))]})]})}function gs({obj:s}){const t=s.children.flatMap(a=>a.fields.map(r=>({field:r,on:a.name_before??a.name_after??""})));return e.jsxs(e.Fragment,{children:[e.jsx("h2",{children:s.renamed?`${s.name_before} → ${s.name_after}`:s.name_before??s.name_after}),e.jsxs("div",{className:"rv-detail-kind",children:[s.object_kind_label,s.renamed&&" · renamed",s.path&&e.jsxs("span",{className:"rv-src",children:[" · ",s.path]})]}),s.fields.map((a,r)=>e.jsx(he,{f:a},a.field+r)),t.map(({field:a,on:r},l)=>e.jsx(he,{f:a,on:r},"c"+l)),s.severity==="breaking"&&e.jsxs("div",{className:"rv-note",children:[e.jsx("span",{className:"i",children:"ⓘ"}),e.jsxs("span",{children:["This is a structural change (route B). It will need an architect's review, and CI will run ",e.jsx("code",{children:"mdl drift --check"}),"."]})]})]})}function he({f:s,on:t}){var r,l,x;const a=t?`${s.label}: ${t}`:s.label;if((r=s.breaks)!=null&&r.length)return e.jsxs("div",{className:"rv-section",children:[e.jsx("h3",{children:a}),e.jsxs("div",{className:"rv-break",children:[e.jsxs("div",{className:"rv-break-head",children:[e.jsx("span",{children:"⚠"}),e.jsxs("span",{children:[t??String(s.before??"")," removed"]}),s.detail&&e.jsx("span",{className:"rv-break-meta",children:s.detail})]}),e.jsxs("div",{className:"rv-break-body",children:["This is realised by ",s.breaks.length," dbt model",s.breaks.length>1?"s":"",". Removing it will break ",s.breaks.length>1?"them":"it","."]}),e.jsx("ul",{className:"rv-usage",children:s.breaks.map(c=>e.jsxs("li",{children:[e.jsxs("span",{className:"m",children:["→ ",c.name]}),e.jsxs("span",{className:"p",children:[c.target," · ",c.materialization]})]},c.name))})]})]});if(s.kind==="definition_changed"){const c=String(s.before??""),i=String(s.after??"");return e.jsxs("div",{className:"rv-section",children:[e.jsx("h3",{children:a}),e.jsxs("div",{className:"rv-ba",children:[e.jsxs("div",{className:"rv-ba-col",children:[e.jsx("div",{className:"rv-ba-label",children:"before"}),e.jsx("div",{className:"rv-ba-text",children:ue(c,i,"del")})]}),e.jsxs("div",{className:"rv-ba-col",children:[e.jsx("div",{className:"rv-ba-label",children:"after"}),e.jsx("div",{className:"rv-ba-text",children:ue(i,c,"ins")})]})]})]})}if(Array.isArray(s.before)||Array.isArray(s.after)){const c=new Set(((l=s.before)==null?void 0:l.map(String))??[]),i=new Set(((x=s.after)==null?void 0:x.map(String))??[]),u=[...i].filter(p=>!c.has(p)),N=[...c].filter(p=>!i.has(p));return e.jsxs("div",{className:"rv-section",children:[e.jsx("h3",{children:a}),e.jsxs("div",{className:"rv-chips",children:[u.map(p=>e.jsxs("span",{className:"rv-chip-add",children:["+ ",p]},"a"+p)),N.map(p=>e.jsxs("span",{className:"rv-chip-del",children:["− ",p]},"r"+p))]})]})}return e.jsxs("div",{className:"rv-section",children:[e.jsx("h3",{children:a}),e.jsxs("div",{className:"rv-scalar",children:[e.jsx("span",{className:"from",children:me(s.before)}),e.jsx("span",{className:"arrow",children:"→"}),e.jsx("span",{className:"to",children:me(s.after)})]})]})}function me(s){return s==null||s===""?"—":typeof s=="boolean"?s?"yes":"no":typeof s=="object"?JSON.stringify(s):String(s)}function ue(s,t,a){const r=s.split(/(\s+)/),l=t.split(/(\s+)/),x=m=>m.toLowerCase().replace(/[.,;:]/g,""),c=r.length,i=l.length,u=Array.from({length:c+1},()=>new Array(i+1).fill(0));for(let m=c-1;m>=0;m--)for(let v=i-1;v>=0;v--)u[m][v]=x(r[m])===x(l[v])?u[m+1][v+1]+1:Math.max(u[m+1][v],u[m][v+1]);const N=new Set;let p=0,d=0;for(;p=u[p][d+1]?p++:d++;return r.map((m,v)=>!m.trim()||N.has(v)?e.jsx("span",{children:m},v):a==="del"?e.jsx("del",{children:m},v):e.jsx("ins",{children:m},v))}const vs=n.lazy(()=>js(()=>import("./ModelDiagram-C1PzETqC.js"),__vite__mapDeps([0,1,2,3])).then(s=>({default:s.ModelDiagram})));function Ns({stagedDiff:s,stagedRoute:t,user:a,onBack:r,onSubmit:l,excluded:x,onToggleObject:c,selectable:i}){const[u,N]=n.useState(null),[p,d]=n.useState(null),[m,v]=n.useState(null),[S,C]=n.useState(null),[T,_]=n.useState(null),[g,k]=n.useState("changes"),E=n.useCallback(()=>{xe(a).then(h=>{C(h);const A=h.base_branch??"main";s?N(s):ze("HEAD").then(R=>R.ok?N(R):_(R.error??"could not read the model")).catch(R=>_(String(R))),t?d(t):We("HEAD").then(d).catch(()=>{}),Ge(A).then(v).catch(()=>{})}).catch(h=>_(String(h)))},[a,s,t]);if(n.useEffect(E,[E]),T)return e.jsxs("div",{className:"rv-shell",children:[e.jsx(pe,{onBack:r,diff:null}),e.jsx("p",{className:"sme-splash error",children:e.jsx("pre",{children:T})})]});if(!u)return e.jsx("div",{className:"sme-splash",children:"◮ reading your changes…"});const $=new Set(u.objects.map(h=>h.ulid).filter(h=>!x.has(h)));return e.jsxs("div",{className:"rv-shell",children:[e.jsx(pe,{onBack:r,diff:u,tab:g,onTab:k}),g==="changes"?e.jsx(xs,{diff:u,selectable:i,selected:$,onToggle:c}):e.jsx(n.Suspense,{fallback:e.jsx("div",{className:"sme-splash",children:"◮ drawing the model…"}),children:e.jsx(vs,{severityByUlid:ws(u)})}),e.jsxs("div",{className:"rv-foot",children:[e.jsx(ys,{cl:p}),e.jsx(Ss,{conf:m,ctx:S}),e.jsxs("div",{className:"rv-actions",children:[e.jsx("button",{className:"sme-secondary",onClick:r,children:"Keep editing"}),e.jsx("button",{className:"sme-primary",disabled:!u.objects.length||(S?!S.can_propose:!1),title:S&&!S.can_propose?"the working tree has uncommitted changes":"",onClick:()=>l(p),children:"Submit for review →"})]})]})]})}function ws(s){const t=new Map;for(const a of s.objects)t.set(a.ulid,a.severity);return t}function pe({onBack:s,diff:t,tab:a,onTab:r}){const l=t==null?void 0:t.counts;return e.jsxs("div",{className:"rv-head",children:[e.jsxs("div",{children:[e.jsx("button",{className:"rv-back",onClick:s,children:"‹ Back to terms"}),e.jsx("h1",{className:"rv-title",children:"Review your changes"}),l&&e.jsxs("div",{className:"rv-sub",children:[l.objects," object",l.objects===1?"":"s"," ·"," ",(l.breaking??0)+(l.additive??0)+(l.cosmetic??0)," changes",(t==null?void 0:t.has_breaking)&&e.jsxs("span",{className:"warn",children:[" · ⚠ ",l.breaking," breaking"]})," · comparing to ",e.jsx("code",{children:t==null?void 0:t.base.label})]})]}),r&&e.jsxs("div",{className:"rv-tabs",children:[e.jsxs("button",{className:"rv-tab"+(a==="changes"?" active":""),onClick:()=>r("changes"),children:["Changes ",(t==null?void 0:t.objects.length)??0]}),e.jsx("button",{className:"rv-tab"+(a==="diagram"?" active":""),onClick:()=>r("diagram"),children:"Diagram"})]})]})}function ys({cl:s}){if(!(s!=null&&s.primary))return null;const t=s.reviewers_actual??s.reviewers;return e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"rv-route",children:[e.jsxs("span",{className:"rv-route-badge",children:["Route ",s.primary," · ",s.primary_name]}),e.jsx("span",{className:"k",children:"Reviewers"}),e.jsx("span",{children:t.join(", ")}),s.reviewers_actual&&e.jsx("span",{className:"rv-src",children:"from .github/CODEOWNERS"})]}),e.jsxs("div",{className:"rv-gates",children:["CI gates"," ",s.gates.map(a=>e.jsx("code",{children:a},a))]})]})}function Ss({conf:s,ctx:t}){return s!=null&&s.ok?s.clean?e.jsxs("div",{className:"rv-conflict ok",children:[e.jsx("span",{children:"✓"}),e.jsxs("span",{children:["Merges cleanly onto ",s.base]}),s.behind>0&&e.jsxs("span",{className:"detail",children:["· ",s.base," is ",s.behind," commit",s.behind===1?"":"s"," ahead"]}),(t==null?void 0:t.dirty)&&e.jsx("span",{className:"detail",children:"· uncommitted changes in the working tree"})]}):e.jsxs("div",{className:"rv-conflict bad",children:[e.jsx("span",{children:"⚠"}),e.jsxs("span",{children:[s.base," has changed and ",s.files.length," file",s.files.length===1?"":"s"," now conflict"]}),e.jsx("span",{className:"detail",children:s.files.map(a=>a.path).join(", ")})]}):null}function ks({term:s,canEdit:t,pendingCount:a,onEdit:r}){var l,x;return e.jsxs("div",{className:"sme-card",children:[e.jsxs("div",{className:"sme-card-head",children:[e.jsxs("div",{children:[e.jsx("h1",{children:s.name}),((l=s.subject_area)==null?void 0:l.name)&&e.jsx("span",{className:"sme-chip",children:s.subject_area.name}),s.kind==="term"&&e.jsx("span",{className:"sme-chip",children:"glossary term"})]}),t&&e.jsx("button",{className:"sme-primary",onClick:r,children:a>0?"Continue editing":"Suggest an edit"})]}),e.jsx("p",{className:"sme-definition",children:s.definition??e.jsx("em",{children:"No definition yet."})}),s.synonyms.length>0&&e.jsxs("section",{children:[e.jsx("h3",{children:"Also called"}),e.jsx("div",{children:s.synonyms.map(c=>e.jsx("span",{className:"sme-chip",children:c},c))})]}),s.stewardship&&(s.stewardship.owner||s.stewardship.steward)&&e.jsxs("section",{children:[e.jsx("h3",{children:"Who owns this"}),s.stewardship.owner&&e.jsxs("div",{className:"sme-kv",children:[e.jsx("span",{className:"k",children:"owner"}),e.jsx("span",{children:s.stewardship.owner})]}),s.stewardship.steward&&e.jsxs("div",{className:"sme-kv",children:[e.jsx("span",{className:"k",children:"steward"}),e.jsx("span",{children:s.stewardship.steward})]})]}),((x=s.ontology)==null?void 0:x.aligns_to)&&e.jsxs("section",{children:[e.jsx("h3",{children:"Standard it maps to"}),e.jsxs("div",{className:"sme-kv",children:[e.jsx("span",{className:"k",children:"aligned to"}),e.jsx("code",{children:s.ontology.aligns_to}),s.ontology.status==="proposed"&&e.jsx("span",{className:"sme-badge proposed",children:"awaiting architect"})]})]}),e.jsxs("section",{children:[e.jsx("h3",{children:"Where it's used"}),s.where_used.length===0?e.jsx("p",{className:"sme-muted",children:s.kind==="term"?"A glossary term — not yet realised by a data model.":"Not yet realised by any data model."}):e.jsx("ul",{className:"sme-usage",children:s.where_used.map(c=>e.jsxs("li",{children:[e.jsx("span",{className:"sme-usage-model",children:c.logical_entity}),c.unmanaged&&e.jsx("span",{className:"sme-badge",children:"engineer-owned"}),c.physical.length>0&&e.jsx("span",{className:"sme-usage-phys",children:c.physical.map(i=>i.name).join(", ")})]},c.logical_id))})]})]})}function _s({term:s,pending:t,onStage:a,onDone:r,catalogOwned:l=[],catalog:x=null}){var A,R,D,L;const c=j=>l.includes(j),i=j=>t.find(P=>P.label===j),u=(j,P)=>{const F=i(j);return F?F.after:P},[N,p]=n.useState(u("Definition",s.definition??"")),[d,m]=n.useState(s.synonyms),[v,S]=n.useState(""),[C,T]=n.useState(((A=s.stewardship)==null?void 0:A.owner)??""),[_,g]=n.useState(((R=s.stewardship)==null?void 0:R.steward)??""),k=()=>{N.trim()!==(s.definition??"").trim()&&a({op:"set_definition",payload:{id:s.id,definition:N.trim()},label:"Definition",before:s.definition??"(none)",after:N.trim()||"(none)"})},E=(j,P)=>{var F,O;a({op:"set_stewardship",payload:{id:s.id,owner:j||null,steward:P||null},label:"Stewardship",before:`owner: ${((F=s.stewardship)==null?void 0:F.owner)??"—"} · steward: ${((O=s.stewardship)==null?void 0:O.steward)??"—"}`,after:`owner: ${j||"—"} · steward: ${P||"—"}`})},$=j=>{m(j),a({op:"update_synonyms",payload:{id:s.id,synonyms:j},label:"Also called",before:s.synonyms.join(", ")||"(none)",after:j.join(", ")||"(none)"})},h=s.kind==="term";return e.jsxs("div",{className:"sme-editor",children:[e.jsxs("div",{className:"sme-card-head",children:[e.jsxs("h1",{children:["Suggest an edit — ",s.name]}),e.jsx("button",{className:"sme-secondary",onClick:r,children:"Back"})]}),e.jsxs("p",{className:"sme-hint",children:["You can refine what this term ",e.jsx("em",{children:"means"}),". Structure (keys, relationships, how it's built) stays with the data team."]}),(c("definition")||c("synonyms")||c("stewardship"))&&x&&e.jsxs("div",{className:"sme-catalog-banner",children:["Definitions, synonyms, and ownership are mastered in ",e.jsx("b",{children:x.name}),". Edit them there — this glossary mirrors them read-only.",x.url&&e.jsxs(e.Fragment,{children:[" ",e.jsxs("a",{href:x.url,target:"_blank",rel:"noreferrer",children:["Open in ",x.name," →"]})]}),e.jsx("div",{className:"sme-catalog-note",children:"You can still propose a standard mapping below."})]}),e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Definition"}),c("definition")?e.jsx("p",{className:"sme-readonly-value",children:s.definition??"—"}):e.jsx("textarea",{rows:4,value:N,onChange:j=>p(j.target.value),onBlur:k,placeholder:"A plain-language business definition…"})]}),e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Also called (synonyms)"}),c("synonyms")?e.jsx("div",{className:"sme-syn-row",children:s.synonyms.length?s.synonyms.map(j=>e.jsx("span",{className:"sme-chip",children:j},j)):e.jsx("span",{className:"sme-muted",children:"none"})}):e.jsxs("div",{className:"sme-syn-row",children:[d.map(j=>e.jsxs("span",{className:"sme-chip removable",onClick:()=>$(d.filter(P=>P!==j)),children:[j," ✕"]},j)),e.jsx("input",{value:v,placeholder:"add + Enter",onChange:j=>S(j.target.value),onKeyDown:j=>{j.key==="Enter"&&v.trim()&&($([...d,v.trim()]),S(""))}})]})]}),!h&&(c("stewardship")?e.jsxs("div",{className:"sme-field-row",children:[e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Owner"}),e.jsx("p",{className:"sme-readonly-value",children:((D=s.stewardship)==null?void 0:D.owner)??"—"})]}),e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Steward"}),e.jsx("p",{className:"sme-readonly-value",children:((L=s.stewardship)==null?void 0:L.steward)??"—"})]})]}):e.jsxs("div",{className:"sme-field-row",children:[e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Owner"}),e.jsx("input",{value:C,onChange:j=>T(j.target.value),onBlur:()=>E(C,_)})]}),e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Steward"}),e.jsx("input",{value:_,onChange:j=>g(j.target.value),onBlur:()=>E(C,_)})]})]})),e.jsx(Es,{term:s,onStage:a}),e.jsx("p",{className:"sme-note",children:t.length>0?`${t.length} change(s) staged — use “Submit for review” at the top when you're done.`:"Changes you make are collected until you submit them for review."})]})}const Cs=["skos:exactMatch","skos:closeMatch","skos:broadMatch","skos:narrowMatch"];function Es({term:s,onStage:t}){var d;const[a,r]=n.useState(!1),[l,x]=n.useState(s.name),[c,i]=n.useState([]),[u,N]=n.useState("skos:closeMatch"),p=m=>{x(m),He(m).then(v=>i(v.results)).catch(()=>i([]))};return a?e.jsxs("div",{className:"sme-field sme-align",children:[e.jsx("span",{children:"Propose a standard mapping (a suggestion; the architect confirms)"}),e.jsx("input",{value:l,onChange:m=>p(m.target.value),placeholder:"Search standards…",autoFocus:!0}),e.jsx("div",{className:"sme-align-results",children:c.map(m=>e.jsxs("button",{className:"sme-align-card",onClick:()=>{var v;t({op:"set_alignment",payload:{id:s.id,aligns_to:m.prefixed,alignment:u,status:"proposed"},label:"Standard mapping",before:((v=s.ontology)==null?void 0:v.aligns_to)??"(none)",after:`${m.prefixed} (${u}, proposed)`}),r(!1)},children:[e.jsx("strong",{children:m.label})," ",e.jsx("code",{children:m.prefixed}),m.definition&&e.jsx("span",{className:"sme-align-def",children:m.definition})]},m.iri))}),e.jsx("select",{value:u,onChange:m=>N(m.target.value),children:Cs.map(m=>e.jsx("option",{children:m},m))})]}):e.jsxs("div",{className:"sme-field",children:[e.jsx("span",{children:"Standard mapping"}),e.jsxs("div",{children:[(d=s.ontology)!=null&&d.aligns_to?e.jsx("code",{className:"sme-current-align",children:s.ontology.aligns_to}):e.jsx("span",{className:"sme-muted",children:"not mapped to a standard"}),e.jsx("button",{className:"sme-link",onClick:()=>r(!0),children:"Propose a mapping…"})]})]})}function As(){const[s,t]=n.useState(null),[a,r]=n.useState(!0),[l,x]=n.useState(null),[c,i]=n.useState(""),[u,N]=n.useState(null),[p,d]=n.useState(""),[m,v]=n.useState(""),[S,C]=n.useState(null),[T,_]=n.useState(!1),[g,k]=n.useState([]),[E,$]=n.useState(!1),[h,A]=n.useState(window.location.hash==="#proposals"?"proposals":"browse"),[R,D]=n.useState(new Set),[L,j]=n.useState(()=>localStorage.getItem("mdl.sme.user")??""),[P,F]=n.useState(null),[O,q]=n.useState(null),[W,U]=n.useState(!1),[V,b]=n.useState(null),o=n.useCallback((f,w)=>{var ae;const I=Q=>{var te;const K=O==null?void 0:O.entities.find(re=>{var le;return re.id===Q||((le=re.conceptual)==null?void 0:le.id)===Q});return((te=K==null?void 0:K.conceptual)==null?void 0:te.name)??(K==null?void 0:K.name)??"object"},B=I(w.id??w.entity_id);switch(f){case"create_entity":return{label:`New entity: ${w.name}`,before:"—",after:String(w.name??"")};case"create_relationship":return{label:`New relationship: ${I(w.from_entity)} → ${I(w.to_entity)}`,before:"—",after:String(w.cardinality??"many_to_one")};case"delete_entity":return{label:`Deleted ${B}`,before:B,after:"—"};case"delete_relationship":return{label:"Relationship removed",before:"",after:"—"};case"update_relationship":return{label:"Relationship changed",before:"",after:[w.cardinality,w.optionality,w.identifying?"identifying":""].filter(Boolean).join(", ")};case"set_definition":return{label:`Definition: ${B}`,before:"",after:String(w.definition??"")};case"add_attribute":return{label:`Attribute added to ${B}`,before:"—",after:String(w.name??"")};case"delete_attribute":return{label:`Attribute removed from ${B}`,before:"",after:"—"};case"update_attribute":return{label:`Attribute changed on ${B}`,before:"",after:JSON.stringify(w)};case"rename_entity":return{label:`Renamed ${B}`,before:B,after:String(w.name??"")};case"set_subject_area":return{label:`Subject area: ${B}`,before:"",after:String(w.subject_area??"none")};case"set_stewardship":return{label:`Stewardship: ${B}`,before:"",after:String(w.steward??"")};case"set_subject_area_members":{const Q=w.__label,K=((ae=w.members)==null?void 0:ae.length)??0;return{label:Q??"Subject area members",before:"",after:`${K} object${K===1?"":"s"}`}}case"set_alignment":return{label:`Alignment: ${B}`,before:"",after:String(w.aligns_to??"")};default:return{label:`${f.replace(/_/g," ")}: ${B}`,before:"",after:""}}},[O]),y=Ze({subjectArea:p,describe:o}),M=n.useCallback(async(f,w)=>{const I=(O==null?void 0:O.fingerprint)??"",B=await Ke(f,w,I);return await ie(p||void 0).then(q),B},[O,p]),z=n.useMemo(()=>{const f={};for(const w of y.pending)w.op==="set_subject_area_members"&&(f[w.payload.id]=w.payload.members??[]);return f},[y.pending]),G=n.useMemo(()=>[...g,...y.pending],[g,y.pending]),[we,X]=n.useState(null),[ye,Z]=n.useState(null);n.useEffect(()=>{if(!G.length){X(null),Z(null);return}const f=new AbortController,w=G.map(I=>({op:I.op,payload:I.payload}));return je(w,p||void 0,f.signal).then(I=>X(I.diff??null)).catch(I=>{I.name!=="AbortError"&&X(null)}),Ve(w).then(Z).catch(()=>Z(null)),()=>f.abort()},[G,p]);const Se=n.useMemo(()=>es(G),[G]).size{A(f),window.location.hash=f==="browse"?"":`#${f}`},[]);n.useEffect(()=>{const f=()=>{const w=window.location.hash;A(w==="#proposals"?"proposals":w==="#review"?"review":w==="#model"?"model":w==="#areas"?"areas":"browse")};return f(),window.addEventListener("hashchange",f),()=>window.removeEventListener("hashchange",f)},[]);const ee=n.useCallback(()=>{Ue({subject_area:p,q:m}).then(t).catch(f=>N(String(f)))},[p,m]);n.useEffect(ee,[ee]),n.useEffect(()=>{ie(p||void 0).then(f=>{r(f.read_only),i(f.project.name),q(f),U(!!f.direct),F(f.identity??null),f.identity&&f.identity.source!=="anonymous"&&f.identity.name&&j(f.identity.name)}).catch(()=>{}),Ye().then(x).catch(()=>{})},[p]);const se=n.useMemo(()=>(s==null?void 0:s.terms.find(f=>f.id===S))??null,[s,S]),ke=n.useCallback(f=>{const w=f.key??ve(f.op,f.payload);k(I=>[...I.filter(B=>B.key!==w),{...f,key:w}])},[]),_e=f=>k(w=>w.filter((I,B)=>B!==f)),Ce=()=>{k([]),$(!1),_(!1),ee()};if(u)return e.jsxs("div",{className:"sme-splash error",children:[e.jsx("h1",{children:"◮ Glossary"}),e.jsx("pre",{children:u}),e.jsxs("p",{children:["Is ",e.jsx("code",{children:"mdl glossary"})," pointed at a model?"]})]});if(!s)return e.jsx("div",{className:"sme-splash",children:"◮ loading glossary…"});const Y=!a,ne=(l==null?void 0:l.catalog_owned_fields)??[],Ee=ne.length>0;return e.jsxs("div",{className:"sme",children:[e.jsxs("header",{className:"sme-top",children:[e.jsxs("div",{className:"sme-brand",children:[e.jsx("span",{className:"sme-logo",children:"◮"}),e.jsx("span",{children:"Glossary"}),e.jsx("span",{className:"sme-project",children:c}),a&&e.jsx("span",{className:"sme-chip",children:"read-only"}),Ee&&e.jsxs("span",{className:"sme-chip catalog",title:`Definitions are mastered in ${l==null?void 0:l.catalog_name}`,children:["mirrors ",l==null?void 0:l.catalog_name]})]}),e.jsx("input",{className:"sme-search",type:"search",placeholder:"Search terms & definitions…",value:m,onChange:f=>v(f.target.value)}),e.jsxs("nav",{className:"sme-tabs",children:[e.jsx("button",{className:"sme-tab"+(h==="browse"?" active":""),onClick:()=>H("browse"),children:"Terms"}),e.jsx("button",{className:"sme-tab"+(h==="model"?" active":""),onClick:()=>H("model"),children:"Model"}),e.jsx("button",{className:"sme-tab"+(h==="areas"?" active":""),onClick:()=>H("areas"),children:"Subject areas"}),e.jsx("button",{className:"sme-tab"+(h==="proposals"?" active":""),onClick:()=>H("proposals"),children:"My proposals"})]}),G.length>0&&e.jsxs("button",{className:"sme-tray",onClick:()=>H("review"),children:[G.length," change",G.length>1?"s":""," · Review →"]})]}),h==="browse"&&e.jsx(ss,{user:L}),h==="review"?e.jsx(Ns,{stagedDiff:we,stagedRoute:ye,user:L,selectable:Se,excluded:R,onToggleObject:f=>D(w=>{const I=new Set(w);return I.has(f)?I.delete(f):I.add(f),I}),onBack:()=>H("browse"),onSubmit:f=>{b(f),$(!0)}}):h==="model"?y.previewDoc??O?e.jsx(is,{doc:y.previewDoc??O,subjectArea:p,onSubjectArea:d,exec:W?M:y.exec,canEdit:Y,direct:W,onImported:()=>H("review"),busy:y.busy}):e.jsx("div",{className:"sme-splash",children:"◮ loading the model…"}):h==="areas"?e.jsx(ts,{exec:y.exec,canEdit:Y,stagedMembers:z}):h==="proposals"?e.jsx(cs,{user:L,onView:()=>H("review")}):e.jsxs("div",{className:"sme-body",children:[e.jsxs("nav",{className:"sme-nav",children:[e.jsx("button",{className:"sme-sa"+(p===""?" active":""),onClick:()=>d(""),children:"All subject areas"}),s.subject_areas.map(f=>e.jsx("button",{className:"sme-sa"+(p===f.id?" active":""),onClick:()=>d(f.id),title:f.definition??void 0,children:f.name},f.id))]}),e.jsxs("ul",{className:"sme-list",children:[s.terms.map(f=>{var w;return e.jsxs("li",{className:"sme-list-item"+(f.id===S?" active":""),onClick:()=>{C(f.id),_(!1)},children:[e.jsx("span",{className:"sme-term-name",children:f.name}),f.kind==="term"&&e.jsx("span",{className:"sme-kind",children:"term"}),((w=f.ontology)==null?void 0:w.status)==="proposed"&&e.jsx("span",{className:"sme-badge proposed",title:"alignment awaiting architect",children:"proposed"}),e.jsx("span",{className:"sme-term-def",children:f.definition??"no definition yet"})]},f.id)}),s.terms.length===0&&e.jsx("li",{className:"sme-empty",children:"no terms match"})]}),e.jsx("section",{className:"sme-detail",children:se?T&&Y?e.jsx(_s,{term:se,pending:g,onStage:ke,onDone:()=>_(!1),catalogOwned:ne,catalog:l?{name:l.catalog_name,url:l.catalog_url}:null}):e.jsx(ks,{term:se,canEdit:Y,pendingCount:g.length,onEdit:()=>_(!0)}):e.jsx("div",{className:"sme-placeholder",children:"Select a term to see its definition."})})]}),E&&e.jsx(hs,{changes:G,routeAdvice:V,identity:P,user:L,onUser:f=>{j(f),localStorage.setItem("mdl.sme.user",f)},onDrop:_e,onClose:()=>$(!1),onProposed:()=>{Ce(),y.clear(),D(new Set),H("proposals")}})]})}Ae.createRoot(document.getElementById("root")).render(e.jsx(Re.StrictMode,{children:e.jsx(As,{})})); diff --git a/packages/server/src/mdl_server/static/assets/sme-bmuaEwPQ.js b/packages/server/src/mdl_server/static/assets/sme-bmuaEwPQ.js deleted file mode 100644 index 65af07e..0000000 --- a/packages/server/src/mdl_server/static/assets/sme-bmuaEwPQ.js +++ /dev/null @@ -1,2 +0,0 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/ModelDiagram-C1PzETqC.js","assets/client-DOmNpTln.js","assets/styles-DG4k7QGS.js","assets/styles-B4bhxAsE.css"])))=>i.map(i=>d[i]); -import{r as n,j as e,c as Re,R as $e}from"./client-DOmNpTln.js";import{p as je,b as xe,c as Me,e as De,i as Pe,d as fe,h as ge,j as be,R as Oe,M as Ie,O as Te,L as Le,k as Be,l as Fe,A as qe,m as ze,n as We,o as Ge,q as He,s as Ke,f as le,r as Ve,t as Ue,u as Ye}from"./styles-DG4k7QGS.js";/* empty css */const Qe="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function ce(s,t){let a="";for(let r=0;r>=5n;return a}function ve(){const s=BigInt(Date.now())&(1n<<48n)-1n,t=new Uint8Array(10);crypto.getRandomValues(t);let a=0n;for(const r of t)a=a<<8n|BigInt(r);return ce(s,10)+ce(a,16)}const Je={create_entity:["id","conceptual_id"],create_relationship:["id","from_fk_id"],create_subject_area:["id"],create_term:["id"],create_domain:["id"],create_code_set:["id"],create_key_group:["id"],create_category:["id"],add_attribute:["id"]};function Xe(s,t){const a=Je[s];if(!a)return t;const r={...t};for(const i of a)r[i]||(r[i]=ve());return r}function Ne(s,t){if(s.startsWith("create_")||s==="add_attribute")return`${s}:${t.id??t.attribute_id}`;const a=t.id??t.entity_id??"",r=t.attribute_id??"";return r?`${s}:${a}:${r}`:`${s}:${a}`}function Ze({subjectArea:s,describe:t}){const[a,r]=n.useState([]),[i,x]=n.useState(null),[c,l]=n.useState(!1),m=n.useRef(null),N=n.useCallback(async(h,v)=>{v=Xe(h,v),h==="set_alignment"&&v.status===void 0&&(v={...v,status:"proposed"});const S=Ne(h,v),{label:C,before:T,after:_}=t(h,v);return r(b=>{const k=b.filter(E=>E.key!==S);if(h.startsWith("delete_")){const E=v.id??v.entity_id;if(E&&b.some(p=>p.op.startsWith("create_")&&p.payload.id===E))return k.filter(p=>p.payload.id!==E)}return[...k,{op:h,payload:v,label:C,before:T,after:_,key:S}]}),{ok:!0,fingerprint:"",created_id:v.id??null,diagnostics:[]}},[t]);n.useEffect(()=>{if(!a.length){x(null);return}const h=setTimeout(()=>{var S;(S=m.current)==null||S.abort();const v=new AbortController;m.current=v,l(!0),je(a.map(C=>({op:C.op,payload:C.payload})),s,v.signal).then(x).catch(C=>{C.name!=="AbortError"&&x(null)}).finally(()=>l(!1))},250);return()=>clearTimeout(h)},[a,s]);const u=n.useCallback(h=>{r(v=>v.filter(S=>S.key!==h))},[]),d=n.useCallback(()=>r([]),[]);return n.useMemo(()=>({pending:a,previewDoc:(i==null?void 0:i.model)??null,previewDiff:(i==null?void 0:i.diff)??null,diagnostics:(i==null?void 0:i.diagnostics)??[],failure:i&&i.failed_index!==null?{index:i.failed_index,error:i.error??"invalid change"}:null,busy:c,exec:N,drop:u,clear:d}),[a,i,c,N,u,d])}function es(s){const t=new Map;for(const r of s)for(const i of["id","conceptual_id","from_fk_id"]){const x=r.payload[i];r.op.startsWith("create_")&&typeof x=="string"&&t.set(x,r.key)}if(!t.size)return new Set;const a=new Set;for(const r of s)if(!r.op.startsWith("create_"))for(const i of Object.values(r.payload))typeof i=="string"&&t.has(i)&&(a.add(r.key),a.add(t.get(i)));return a}function ss({user:s}){const[t,a]=n.useState(null);return n.useEffect(()=>{xe(s).then(a).catch(()=>{})},[s]),!(t!=null&&t.ok)||!t.git?null:t.read_only?e.jsxs("div",{className:"git-banner",children:[e.jsx("code",{children:t.branch}),e.jsx("span",{children:"browse only — editing is disabled on this deployment"})]}):t.dirty?e.jsxs("div",{className:"git-banner warn",children:[e.jsx("code",{children:t.branch}),e.jsx("span",{children:"uncommitted changes in the working tree — a proposal will be refused until they are committed or discarded"})]}):t.on_base?e.jsxs("div",{className:"git-banner",children:[e.jsx("code",{children:t.branch}),e.jsx("span",{children:"protected — your edits become a pull request"})]}):e.jsxs("div",{className:"git-banner",children:[e.jsx("code",{children:t.branch}),e.jsx("span",{children:"your proposal branch"}),t.behind?e.jsxs("span",{className:"dim",children:["· ",t.base_branch," is ",t.behind," ahead"]}):null]})}function ns({exec:s,canEdit:t,onImported:a}){const[r,i]=n.useState(null),[x,c]=n.useState([]),[l,m]=n.useState("postgres"),N=n.useRef(null);n.useEffect(()=>{Me().then(d=>c(d.formats)).catch(()=>c([]))},[]),n.useEffect(()=>{if(!r)return;const d=h=>{N.current&&!N.current.contains(h.target)&&i(null)};return document.addEventListener("mousedown",d),()=>document.removeEventListener("mousedown",d)},[r]);const u=x.find(d=>d.id==="sql");return e.jsxs("div",{className:"ie-wrap",ref:N,children:[e.jsx("button",{className:"erd-action ghost"+(r==="export"?" on":""),onClick:()=>i(r==="export"?null:"export"),children:"Export ▾"}),t&&e.jsx("button",{className:"erd-action ghost"+(r==="import"?" on":""),onClick:()=>i(r==="import"?null:"import"),children:"Import ▾"}),r==="export"&&e.jsxs("div",{className:"ie-menu",role:"menu",children:[(u==null?void 0:u.dialects)&&e.jsxs("label",{className:"ie-dialect",children:[e.jsx("span",{children:"SQL dialect"}),e.jsx("select",{value:l,onChange:d=>m(d.target.value),children:u.dialects.map(d=>e.jsx("option",{value:d,children:d},d))})]}),x.map(d=>e.jsx("a",{className:"ie-item",href:De(d.id,d.id==="sql"?l:void 0),download:!0,onClick:()=>i(null),children:d.label},d.id))]}),r==="import"&&e.jsx(as,{exec:s,onClose:()=>i(null),onImported:d=>{i(null),a==null||a(d)}})]})}const oe=[{id:"sql",label:"SQL DDL",hint:"CREATE TABLE … PRIMARY KEY / FOREIGN KEY"},{id:"mermaid",label:"Mermaid erDiagram",hint:"structural: entities, attributes, relationships"},{id:"json-schema",label:"JSON Schema",hint:"each object definition becomes an entity"}];function as({exec:s,onClose:t,onImported:a}){var _;const[r,i]=n.useState("sql"),[x,c]=n.useState("postgres"),[l,m]=n.useState(""),[N,u]=n.useState(!1),[d,h]=n.useState(null),[v,S]=n.useState([]),C=b=>{b&&b.text().then(m).catch(()=>h("could not read that file"))},T=async()=>{u(!0),h(null),S([]);try{const b=await Pe(r,l,r==="sql"?x:void 0);if(!b.changes.length){h(b.warnings[0]??"nothing to import from that document"),u(!1);return}for(const k of b.changes)await s(k.op,k.payload);S(b.warnings),a(b.tables)}catch(b){h(b instanceof Error?b.message:String(b))}finally{u(!1)}};return e.jsxs("div",{className:"ie-menu ie-import",role:"dialog",children:[e.jsx("div",{className:"ie-tabs",children:oe.map(b=>e.jsx("button",{className:"ie-tab"+(r===b.id?" active":""),onClick:()=>i(b.id),children:b.label},b.id))}),e.jsx("p",{className:"ie-hint",children:(_=oe.find(b=>b.id===r))==null?void 0:_.hint}),r==="sql"&&e.jsxs("label",{className:"ie-dialect",children:[e.jsx("span",{children:"dialect"}),e.jsx("select",{value:x,onChange:b=>c(b.target.value),children:["postgres","snowflake","mysql","duckdb"].map(b=>e.jsx("option",{children:b},b))})]}),e.jsx("textarea",{className:"ie-textarea",placeholder:`Paste ${r} here, or choose a file…`,value:l,onChange:b=>m(b.target.value),rows:8}),e.jsx("input",{type:"file",className:"ie-file",accept:".sql,.mmd,.md,.json,.txt",onChange:b=>{var k;return C(((k=b.target.files)==null?void 0:k[0])??null)}}),r==="mermaid"&&e.jsx("p",{className:"ie-warn",children:"Mermaid is a diagram format: types and keys are approximate and relationships are inferred from the lines."}),v.map((b,k)=>e.jsx("p",{className:"ie-warn",children:b},k)),d&&e.jsx("p",{className:"ie-error",children:d}),e.jsxs("div",{className:"ie-foot",children:[e.jsx("button",{className:"erd-action ghost",onClick:t,children:"Cancel"}),e.jsx("button",{className:"erd-action",disabled:N||!l.trim(),onClick:T,children:N?"Importing…":"Import & review"})]})]})}function ts({exec:s,canEdit:t,stagedMembers:a}){const[r,i]=n.useState([]),[x,c]=n.useState(""),[l,m]=n.useState(null),[N,u]=n.useState(""),[d,h]=n.useState(new Set),[v,S]=n.useState("ancestors"),[C,T]=n.useState(1),[_,b]=n.useState(null),[k,E]=n.useState(new Set),[$,p]=n.useState(null);n.useEffect(()=>{fe().then(o=>{i(o.subject_areas),c(y=>{var M;return y||((M=o.subject_areas[0])==null?void 0:M.id)||""})}).catch(o=>p(String(o)))},[]),n.useEffect(()=>{x&&(b(null),h(new Set),ge(x).then(m).catch(o=>p(String(o))))},[x]);const A=n.useMemo(()=>a[x]??(l==null?void 0:l.members)??[],[a,x,l]),R=n.useMemo(()=>{const o=new Map;for(const y of[...(l==null?void 0:l.included)??[],...(l==null?void 0:l.available)??[]])o.set(y.id,y);return o},[l]),D=N.trim().toLowerCase(),L=o=>!D||o.name.toLowerCase().includes(D),j=n.useMemo(()=>new Set(A),[A]),P=n.useMemo(()=>A.map(o=>R.get(o)).filter(o=>!!o&&L(o)),[A,R,D]),F=n.useMemo(()=>[...R.values()].filter(o=>!j.has(o.id)&&L(o)),[R,j,D]),O=n.useCallback(o=>{if(!l)return;const y=o.filter(z=>!(l.members??[]).includes(z)).length,M=(l.members??[]).filter(z=>!o.includes(z)).length;s("set_subject_area_members",{id:l.id,members:o,__label:`${l.name}: ${o.length} objects (+${y} −${M})`})},[l,s]),q=(o,y)=>{const M=y?[...A,...o.filter(z=>!j.has(z))]:A.filter(z=>!o.includes(z));O(M),h(new Set)},W=(o,y,M)=>{const z=new Set(o);z.has(y)?z.delete(y):z.add(y),M(z)},U=()=>{if(!l)return;const o=d.size?[...d]:A;if(!o.length){p("add an object first, or select one to expand from");return}p(null),be(l.id,{seeds:o,direction:v,levels:C}).then(y=>{b(y.hops),E(new Set(y.added))}).catch(y=>p(String(y)))},V=()=>{q([...k],!0),b(null)};if($&&!l)return e.jsx("p",{className:"sme-splash error",children:$});if(!r.length)return e.jsx("p",{className:"sme-placeholder",children:"No subject areas yet."});const g=((l==null?void 0:l.inconsistent)??[]).filter(o=>!j.has(o));return e.jsxs("div",{className:"sa-editor",children:[e.jsx("nav",{className:"sme-nav",children:r.map(o=>e.jsxs("button",{className:"sme-sa"+(o.id===x?" active":""),onClick:()=>c(o.id),title:o.definition??void 0,children:[o.name,e.jsx("span",{className:"sme-sa-count",children:(a[o.id]??o.members).length})]},o.id))}),e.jsxs("div",{className:"sa-main",children:[e.jsxs("div",{className:"sa-head",children:[e.jsx("h2",{children:(l==null?void 0:l.name)??"…"}),(l==null?void 0:l.definition)&&e.jsx("p",{className:"sme-muted",children:l.definition}),e.jsx("input",{className:"sme-search sa-filter",type:"search",placeholder:"Filter both lists…",value:N,onChange:o=>u(o.target.value)})]}),g.length>0&&t&&e.jsxs("div",{className:"sa-nudge",children:[g.length," object",g.length>1?"s are":" is"," homed in this area but not included.",e.jsxs("button",{className:"sme-link",onClick:()=>q(g,!0),children:["Add ",g.length>1?"them all":"it"]})]}),e.jsxs("div",{className:"sa-panes",children:[e.jsx(J,{title:`Available (${F.length})`,rows:F,picked:d,onToggle:o=>W(d,o,h),homedHere:(l==null?void 0:l.homed_here)??[]}),e.jsxs("div",{className:"sa-controls",children:[e.jsx("button",{className:"sme-secondary",disabled:!t||!d.size,onClick:()=>q([...d].filter(o=>!j.has(o)),!0),title:"Add the selected objects",children:"→"}),e.jsx("button",{className:"sme-secondary",disabled:!t||!F.length,onClick:()=>q(F.map(o=>o.id),!0),title:"Add everything matching the filter",children:"»"}),e.jsx("button",{className:"sme-secondary",disabled:!t||!d.size,onClick:()=>q([...d].filter(o=>j.has(o)),!1),title:"Remove the selected objects",children:"←"}),e.jsx("button",{className:"sme-secondary",disabled:!t||!P.length,onClick:()=>q(P.map(o=>o.id),!1),title:"Remove everything matching the filter",children:"«"})]}),e.jsx(J,{title:`Included (${P.length})`,rows:P,picked:d,onToggle:o=>W(d,o,h),homedHere:(l==null?void 0:l.homed_here)??[]})]}),e.jsxs("div",{className:"sa-expand",children:[e.jsx("h3",{children:"Add related objects"}),e.jsxs("div",{className:"sa-expand-row",children:[e.jsxs("select",{value:v,onChange:o=>S(o.target.value),children:[e.jsx("option",{value:"ancestors",children:"ancestors — the parents its keys point at"}),e.jsx("option",{value:"descendants",children:"descendants — what points at it"}),e.jsx("option",{value:"both",children:"both"})]}),e.jsxs("label",{children:["levels",e.jsx("input",{type:"number",min:1,max:10,value:C,onChange:o=>T(Math.max(1,Math.min(10,Number(o.target.value))))})]}),e.jsx("button",{className:"sme-secondary",onClick:U,disabled:!t,children:"Preview"})]}),$&&e.jsx("p",{className:"sme-error-line",children:$}),_&&e.jsxs("div",{className:"sa-hops",children:[_.length===0&&e.jsx("p",{className:"sme-muted",children:"nothing new to add"}),_.map(o=>e.jsxs("label",{className:"sa-hop",children:[e.jsx("input",{type:"checkbox",checked:k.has(o.id),onChange:()=>W(k,o.id,E)}),e.jsx("span",{className:"sa-hop-name",children:o.name}),e.jsxs("span",{className:"sa-hop-why",children:["via ",o.via_name," · ",o.direction.replace(/s$/,""),", level ",o.level]})]},o.id)),_.length>0&&e.jsxs("div",{className:"sa-hop-actions",children:[e.jsx("button",{className:"sme-secondary",onClick:()=>b(null),children:"Cancel"}),e.jsxs("button",{className:"sme-primary",onClick:V,disabled:!k.size,children:["Add ",k.size," object",k.size===1?"":"s"]})]})]})]})]})]})}function J({title:s,rows:t,picked:a,onToggle:r,homedHere:i}){const x=n.useMemo(()=>new Set(i),[i]);return e.jsxs("div",{className:"sa-pane",children:[e.jsx("h3",{children:s}),e.jsxs("ul",{children:[t.map(c=>e.jsxs("li",{className:"sa-row"+(a.has(c.id)?" picked":""),onClick:()=>r(c.id),children:[e.jsx("input",{type:"checkbox",readOnly:!0,checked:a.has(c.id)}),e.jsx("span",{className:"sa-row-name",children:c.name}),x.has(c.id)?e.jsx("span",{className:"sme-badge",children:"home"}):e.jsx("span",{className:"sme-badge borrowed",children:"borrowed"})]},c.id)),!t.length&&e.jsx("li",{className:"sme-empty",children:"nothing here"})]})]})}function rs({onCreate:s,onClose:t}){const[a,r]=n.useState(""),[i,x]=n.useState(""),[c,l]=n.useState([]),[m,N]=n.useState([]),[u,d]=n.useState(new Set),[h,v]=n.useState(""),[S,C]=n.useState("ancestors"),[T,_]=n.useState(1),[b,k]=n.useState(null),[E,$]=n.useState(new Set),[p,A]=n.useState(""),[R,D]=n.useState(null);n.useEffect(()=>{fe().then(g=>{const o=g.subject_areas[0];if(o)return A(o.id),ge(o.id).then(y=>l([...y.included,...y.available]))}).catch(g=>D(String(g)))},[]);const L=n.useMemo(()=>new Set(m),[m]),j=h.trim().toLowerCase(),P=g=>!j||g.name.toLowerCase().includes(j),F=n.useMemo(()=>m.map(g=>c.find(o=>o.id===g)).filter(g=>!!g&&P(g)),[m,c,j]),O=n.useMemo(()=>c.filter(g=>!L.has(g.id)&&P(g)),[c,L,j]),q=(g,o)=>{N(y=>o?[...y,...g.filter(M=>!y.includes(M))]:y.filter(M=>!g.includes(M))),d(new Set)},W=(g,o,y)=>{const M=new Set(g);M.has(o)?M.delete(o):M.add(o),y(M)},U=()=>{const g=u.size?[...u]:m;if(!g.length){D("pick an object to expand from first");return}p&&(D(null),be(p,{seeds:g,direction:S,levels:T}).then(o=>{const y=o.hops.filter(M=>!L.has(M.id));k(y),$(new Set(y.map(M=>M.id)))}).catch(o=>D(String(o))))},V=()=>{a.trim()&&s(a.trim(),i.trim(),m)};return e.jsx("div",{className:"sme-modal-backdrop",onClick:t,children:e.jsxs("div",{className:"sme-modal sa-modal",onClick:g=>g.stopPropagation(),children:[e.jsx("h2",{children:"New subject area"}),e.jsx("p",{className:"sme-hint",children:"A subject area is a filtered view of the model. Name it and choose what it contains — both land as one proposal."}),e.jsxs("div",{className:"sme-field-row",children:[e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Name"}),e.jsx("input",{autoFocus:!0,value:a,placeholder:"e.g. UseCase-ApraStressTesting",onChange:g=>r(g.target.value),onKeyDown:g=>g.key==="Enter"&&m.length===0&&V()})]}),e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Definition (optional)"}),e.jsx("input",{value:i,placeholder:"what this view is for",onChange:g=>x(g.target.value)})]})]}),e.jsx("input",{className:"sme-search sa-filter",type:"search",placeholder:"Filter both lists…",value:h,onChange:g=>v(g.target.value)}),e.jsxs("div",{className:"sa-panes",children:[e.jsx(J,{title:`Available (${O.length})`,rows:O,picked:u,onToggle:g=>W(u,g,d),homedHere:[]}),e.jsxs("div",{className:"sa-controls",children:[e.jsx("button",{className:"sme-secondary",disabled:!u.size,onClick:()=>q([...u].filter(g=>!L.has(g)),!0),title:"Add the selected objects",children:"→"}),e.jsx("button",{className:"sme-secondary",disabled:!O.length,onClick:()=>q(O.map(g=>g.id),!0),title:"Add everything matching the filter",children:"»"}),e.jsx("button",{className:"sme-secondary",disabled:!u.size,onClick:()=>q([...u].filter(g=>L.has(g)),!1),title:"Remove the selected objects",children:"←"}),e.jsx("button",{className:"sme-secondary",disabled:!F.length,onClick:()=>q(F.map(g=>g.id),!1),title:"Remove everything matching the filter",children:"«"})]}),e.jsx(J,{title:`Included (${F.length})`,rows:F,picked:u,onToggle:g=>W(u,g,d),homedHere:[]})]}),e.jsxs("div",{className:"sa-expand",children:[e.jsx("h3",{children:"Add related objects"}),e.jsxs("div",{className:"sa-expand-row",children:[e.jsxs("select",{value:S,onChange:g=>C(g.target.value),children:[e.jsx("option",{value:"ancestors",children:"ancestors — the parents its keys point at"}),e.jsx("option",{value:"descendants",children:"descendants — what points at it"}),e.jsx("option",{value:"both",children:"both"})]}),e.jsxs("label",{children:["levels",e.jsx("input",{type:"number",min:1,max:10,value:T,onChange:g=>_(Math.max(1,Math.min(10,Number(g.target.value))))})]}),e.jsx("button",{className:"sme-secondary",onClick:U,children:"Preview"})]}),b&&e.jsxs("div",{className:"sa-hops",children:[b.length===0&&e.jsx("p",{className:"sme-muted",children:"nothing new to add"}),b.map(g=>e.jsxs("label",{className:"sa-hop",children:[e.jsx("input",{type:"checkbox",checked:E.has(g.id),onChange:()=>W(E,g.id,$)}),e.jsx("span",{className:"sa-hop-name",children:g.name}),e.jsxs("span",{className:"sa-hop-why",children:["via ",g.via_name," · ",g.direction.replace(/s$/,""),", level ",g.level]})]},g.id)),b.length>0&&e.jsxs("div",{className:"sa-hop-actions",children:[e.jsx("button",{className:"sme-secondary",onClick:()=>k(null),children:"Cancel"}),e.jsxs("button",{className:"sme-primary",disabled:!E.size,onClick:()=>{q([...E],!0),k(null)},children:["Add ",E.size]})]})]})]}),R&&e.jsx("p",{className:"sme-error-line",children:R}),e.jsxs("div",{className:"sme-modal-foot",children:[e.jsx("button",{className:"sme-secondary",onClick:t,children:"Cancel"}),e.jsxs("button",{className:"sme-primary",disabled:!a.trim(),onClick:V,children:["Create",m.length>0&&` with ${m.length} object${m.length===1?"":"s"}`]})]})]})})}const is=new Set(["set_definition","set_stewardship","set_subject_area","set_pattern","rename_entity","add_attribute","update_attribute","delete_attribute","set_alignment","clear_alignment","set_term_map","clear_term_map","update_synonyms","set_object_definition","set_subject_area_members","create_subject_area","update_subject_area","create_entity","delete_entity","create_relationship","rename_relationship","update_relationship","delete_relationship"]);function ls({doc:s,subjectArea:t,onSubjectArea:a,exec:r,canEdit:i,direct:x=!1,onSelectEntity:c,onImported:l,busy:m}){var $;const[N,u]=n.useState(null),[d,h]=n.useState(null),[v,S]=n.useState(!1),[C,T]=n.useState(null),[_]=n.useState(""),b=n.useRef(null),k=n.useMemo(()=>({canEdit:i,mode:x?"direct":"staged",allow:x?void 0:is,canArbitrate:x,canCommit:x}),[i,x]),E=n.useCallback(p=>{u(p),p&&(c==null||c(p))},[c]);return e.jsxs("div",{className:"sme-model-view",children:[e.jsxs("nav",{className:"sme-nav",children:[e.jsxs("div",{className:"sme-nav-head",children:[e.jsx("span",{children:"Subject areas"}),i&&e.jsx("button",{className:"sme-nav-add",title:"New subject area",onClick:()=>S(!0),children:"+"})]}),e.jsxs("button",{className:"sme-sa"+(t===""?" active":""),onClick:()=>a(""),children:["Whole model",e.jsx("span",{className:"sme-sa-count",children:(($=s.counts)==null?void 0:$.entities_total)??s.entities.length})]}),s.subject_areas.map(p=>e.jsxs("button",{className:"sme-sa"+(t===p.id?" active":""),onClick:()=>a(p.id),title:p.definition??void 0,children:[p.name,e.jsx("span",{className:"sme-sa-count",children:p.member_count??0})]},p.id))]}),v&&e.jsx(rs,{onClose:()=>S(!1),onCreate:(p,A,R)=>{const D=ve();r("create_subject_area",{name:p,...A?{definition:A}:{},id:D}),R.length&&r("set_subject_area_members",{id:D,members:R}),S(!1),a(D)}}),e.jsxs("div",{className:"erd-wrap",children:[e.jsxs("div",{className:"erd-bar",children:[e.jsxs("span",{className:"erd-count",children:[s.entities.length," entities · ",s.relationships.length," relationships",s.scope&&" · scoped"]}),i&&e.jsx("button",{className:"erd-action",onClick:()=>{var p;return(p=b.current)==null?void 0:p.newEntity()},title:"Add an entity (n)",children:"+ Entity"}),e.jsx("span",{className:"erd-hint",children:"drag between entities to relate them"}),e.jsxs("span",{className:"erd-panels",children:[e.jsx("button",{className:"erd-action ghost"+(d==="ontology"?" on":""),onClick:()=>h(p=>p==="ontology"?null:"ontology"),children:"Ontology"}),e.jsx("button",{className:"erd-action ghost"+(d==="layers"?" on":""),onClick:()=>h(p=>p==="layers"?null:"layers"),children:"Layers"}),e.jsx(ns,{exec:r,canEdit:i,onImported:l})]}),m&&e.jsx("span",{className:"erd-busy",children:"updating…"}),!i&&e.jsx("span",{className:"sme-chip",children:"read-only"}),C&&e.jsx("span",{className:"erd-error",children:C})]}),e.jsxs("div",{className:"erd-canvas canvas-wrap",children:[e.jsx(Oe,{children:e.jsx(Ie,{doc:s,caps:k,exec:r,selectedId:N,onSelect:E,query:_,onError:T,handleRef:b})}),d&&e.jsxs("aside",{className:"side-panel",children:[e.jsxs("div",{className:"detail-header",children:[e.jsx("h2",{children:d==="ontology"?"Ontology":"Ontology layers"}),e.jsx("button",{className:"icon-btn",onClick:()=>h(null),children:"✕"})]}),d==="ontology"?e.jsx(Te,{}):e.jsx(Le,{refreshKey:0,onFocusEntity:p=>{var A;h(null),(A=b.current)==null||A.focusEntity(p)}})]})]})]})]})}function cs({user:s,onView:t}){const[a,r]=n.useState(null),[i,x]=n.useState(null);if(n.useEffect(()=>{Be(s).then(r).catch(l=>x(String(l)))},[s]),i)return e.jsx("p",{className:"sme-splash error",children:i});if(!a)return e.jsx("div",{className:"sme-splash",children:"◮ looking for your proposals…"});if(!a.git)return e.jsx("p",{className:"sme-placeholder",children:"This model isn't in a git repository."});const c=a.proposals.filter(l=>!l.merged).length;return e.jsxs("div",{className:"pr-wrap",children:[e.jsxs("div",{className:"pr-head",children:[e.jsx("h2",{children:"My proposals"}),e.jsxs("span",{className:"meta",children:[c," open · base: ",a.base]})]}),!a.proposals.length&&e.jsx("p",{className:"sme-placeholder",children:"Nothing proposed yet. Edits you submit for review will appear here."}),a.proposals.map(l=>e.jsx(ds,{p:l,gh:a.gh,onView:()=>t(l.branch)},l.branch)),!a.gh&&a.proposals.length>0&&e.jsxs("div",{className:"pr-degrade",children:["Review status isn't available here (",e.jsx("code",{children:"gh"})," is not installed or not signed in). Branch, contents, merge state and conflicts all still come from git — each card links out to the compare view."]})]})}function os(s,t){var a,r;return s.merged?{kind:"accepted",text:"✅ Accepted"}:s.pushed?t&&((a=s.pr)==null?void 0:a.state)==="CLOSED"?{kind:"declined",text:"✖ Declined"}:t&&((r=s.pr)==null?void 0:r.reviews)==="CHANGES_REQUESTED"?{kind:"changes",text:"💬 Changes requested"}:{kind:"awaiting",text:"⏳ Awaiting review"}:{kind:"local",text:"📥 Saved locally"}}function ds({p:s,gh:t,onView:a}){const r=os(s,t);return e.jsxs("div",{className:"pr-card"+(r.kind==="attention"?" attention":""),children:[e.jsxs("div",{className:"pr-top",children:[e.jsx("span",{className:"pr-title",children:s.title}),e.jsx("span",{className:"pr-chip "+r.kind,children:r.text})]}),e.jsxs("div",{className:"pr-branch",children:[s.branch," · ",s.created,s.behind>0&&` · ${s.behind} behind`]}),e.jsxs("div",{className:"pr-foot",children:[e.jsx("span",{className:"pr-link",children:s.merged?e.jsxs(e.Fragment,{children:["merged",t&&s.pr?` · PR #${s.pr.number}`:""]}):s.pushed?t&&s.pr?e.jsxs("a",{href:s.pr.url,target:"_blank",rel:"noreferrer",children:["PR #",s.pr.number]}):s.compare_url?e.jsx("a",{href:s.compare_url,target:"_blank",rel:"noreferrer",children:"open the compare view →"}):"pushed":"not sent yet — no connection to the shared repo"}),e.jsx("span",{className:"pr-btns",children:e.jsx("button",{className:"sme-secondary",onClick:a,children:"View"})})]})]})}function hs({changes:s,routeAdvice:t,identity:a,user:r,onUser:i,onDrop:x,onClose:c,onProposed:l}){const m=!!(a&&a.source!=="anonymous"),[N,u]=n.useState(r||(localStorage.getItem("mdl.sme.user")??"")),[d,h]=n.useState(s.length===1?`Update ${s[0].label.toLowerCase()}`:"Glossary updates"),[v,S]=n.useState(""),[C,T]=n.useState(!1),_=we(C?v:d),[b,k]=n.useState(""),[E,$]=n.useState(!1),[p,A]=n.useState(null),[R,D]=n.useState(null),L=()=>{!m&&!N.trim()||(m||(localStorage.setItem("mdl.sme.user",N.trim()),i==null||i(N.trim())),$(!0),D(null),Fe({user:m?"":N.trim(),slug:_,title:d,body:b.trim(),changes:s.map(j=>({op:j.op,payload:j.payload}))}).then(A).catch(j=>D(j instanceof qe?j.message:String(j))).finally(()=>$(!1)))};return p!=null&&p.ok?e.jsx("div",{className:"sme-modal-backdrop",onClick:()=>l(p),children:e.jsxs("div",{className:"sme-modal",onClick:j=>j.stopPropagation(),children:[e.jsx("h2",{children:"✓ Sent for review"}),e.jsx("p",{children:p.message}),p.pr_url&&e.jsx("p",{children:e.jsx("a",{href:p.pr_url,target:"_blank",rel:"noreferrer",children:"View pull request →"})}),p.compare_url&&e.jsx("p",{children:e.jsx("a",{href:p.compare_url,target:"_blank",rel:"noreferrer",children:"Open the pull request →"})}),e.jsxs("p",{className:"sme-muted",children:["Your suggestion is on branch ",e.jsx("code",{children:p.branch}),". A steward will review it."]}),e.jsx("div",{className:"sme-modal-foot",children:e.jsx("button",{className:"sme-primary",onClick:()=>l(p),children:"Done"})})]})}):e.jsx("div",{className:"sme-modal-backdrop",onClick:c,children:e.jsxs("div",{className:"sme-modal",onClick:j=>j.stopPropagation(),children:[e.jsxs("div",{className:"sme-card-head",children:[e.jsx("h2",{children:"Submit for review"}),e.jsx("button",{className:"sme-secondary",onClick:c,children:"✕"})]}),e.jsx("h3",{children:"Your changes"}),e.jsx("ul",{className:"sme-diff",children:s.map((j,P)=>e.jsxs("li",{children:[e.jsxs("div",{className:"sme-diff-head",children:[e.jsx("b",{children:j.label}),e.jsx("button",{className:"sme-link danger",onClick:()=>x(P),children:"discard"})]}),e.jsx("div",{className:"sme-diff-before",children:j.before}),e.jsx("div",{className:"sme-diff-after",children:j.after})]},P))}),m?e.jsxs("p",{className:"sme-signed-in",children:["Signed in as ",e.jsx("strong",{children:a.name}),a.email?` (${a.email})`:""," — this proposal is attributed to you."]}):e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Your name (for attribution)"}),e.jsx("input",{value:N,onChange:j=>u(j.target.value),placeholder:"e.g. a.hough"})]}),e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Title"}),e.jsx("input",{value:d,onChange:j=>h(j.target.value)})]}),e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Branch name (edit to add a ticket id, e.g. PROJ-123)"}),e.jsx("input",{value:C?v:_,onChange:j=>{T(!0),S(j.target.value)},placeholder:"clarify-counterparty"})]}),e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Note for the reviewer (optional)"}),e.jsx("textarea",{rows:2,value:b,onChange:j=>k(j.target.value)})]}),e.jsx(ms,{cl:t,user:m?a.name:N,changeSlug:_}),R&&e.jsx("p",{className:"sme-error-line",children:R}),e.jsxs("div",{className:"sme-modal-foot",children:[e.jsx("button",{className:"sme-secondary",onClick:c,children:"Keep editing"}),e.jsx("button",{className:"sme-primary",disabled:E||!m&&!N.trim()||s.length===0,onClick:L,children:E?"Submitting…":"Submit for review"})]})]})})}function ms({cl:s,user:t,changeSlug:a}){if(!(s!=null&&s.primary))return null;const r=s.reviewers_actual??s.reviewers,i=we(t||"you"),x=s.routes.length>1;return e.jsxs(e.Fragment,{children:[x&&e.jsxs("div",{className:"sub-advice",children:[e.jsx("span",{className:"i",children:"ⓘ"})," These changes span routes"," ",e.jsx("strong",{children:s.routes.join(" and ")}),". The whole proposal will be reviewed as route ",s.primary," (",s.primary_name,") — the strictest gate wins, so it needs"," ",r.join(", ")," plus ",e.jsx("code",{children:s.gates[s.gates.length-1]}),". Proposing the meaning-only changes separately would get them reviewed faster."]}),e.jsxs("div",{className:"sub-dest",children:[e.jsxs("div",{className:"row",children:[e.jsx("span",{className:"k",children:"Goes to"}),e.jsxs("code",{children:["sme/",i,"/",a||"…"]})]}),e.jsxs("div",{className:"row",children:[e.jsx("span",{className:"k",children:"Reviewers"}),e.jsx("span",{children:r.join(", ")})]})]})]})}function we(s){return s.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")||"change"}const us="modulepreload",ps=function(s){return"/"+s},de={},js=function(t,a,r){let i=Promise.resolve();if(a&&a.length>0){document.getElementsByTagName("link");const c=document.querySelector("meta[property=csp-nonce]"),l=(c==null?void 0:c.nonce)||(c==null?void 0:c.getAttribute("nonce"));i=Promise.allSettled(a.map(m=>{if(m=ps(m),m in de)return;de[m]=!0;const N=m.endsWith(".css"),u=N?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${m}"]${u}`))return;const d=document.createElement("link");if(d.rel=N?"stylesheet":us,N||(d.as="script"),d.crossOrigin="",d.href=m,l&&d.setAttribute("nonce",l),document.head.appendChild(d),N)return new Promise((h,v)=>{d.addEventListener("load",h),d.addEventListener("error",()=>v(new Error(`Unable to preload CSS for ${m}`)))})}))}function x(c){const l=new Event("vite:preloadError",{cancelable:!0});if(l.payload=c,window.dispatchEvent(l),!l.defaultPrevented)throw c}return i.then(c=>{for(const l of c||[])l.status==="rejected"&&x(l.reason);return t().catch(x)})};function xs({diff:s,selectable:t,selected:a,onToggle:r}){var l;const[i,x]=n.useState(((l=s.objects[0])==null?void 0:l.ulid)??null),c=n.useMemo(()=>s.objects.find(m=>m.ulid===i)??s.objects[0]??null,[s,i]);return s.objects.length?e.jsxs("div",{className:"rv-body",children:[e.jsx("div",{className:"rv-list",children:s.objects.map(m=>e.jsx(gs,{obj:m,active:m.ulid===((c==null?void 0:c.ulid)??""),checked:(a==null?void 0:a.has(m.ulid))??!0,selectable:t,onSelect:()=>x(m.ulid),onToggle:()=>r==null?void 0:r(m.ulid)},m.ulid))}),e.jsx("div",{className:"rv-detail",children:c&&e.jsx(bs,{obj:c})})]}):e.jsxs("p",{className:"sme-placeholder",children:["No model changes against ",s.base.label,"."]})}function fs(s){return s.renamed?"renamed":s.severity==="breaking"?"breaking":s.change}function gs({obj:s,active:t,checked:a,selectable:r,onSelect:i,onToggle:x}){return e.jsxs("div",{className:"rv-obj"+(t?" active":""),onClick:i,children:[e.jsxs("div",{className:"rv-obj-head",children:[r&&e.jsx("input",{type:"checkbox",checked:a,onClick:c=>c.stopPropagation(),onChange:x,"aria-label":`include ${s.name_before??s.name_after}`}),e.jsx("span",{className:"rv-obj-name",children:s.name_before??s.name_after}),e.jsx("span",{className:"rv-obj-kind",children:s.object_kind_label}),e.jsxs("span",{className:"sev "+(s.renamed?"renamed":s.severity),children:[s.severity==="breaking"&&"⚠ ",fs(s)]})]}),e.jsxs("ul",{className:"rv-obj-fields",children:[s.fields.map((c,l)=>e.jsx("li",{children:c.label},c.field+l)),s.children.flatMap(c=>c.fields.map((l,m)=>e.jsxs("li",{children:[l.label,": ",c.name_before??c.name_after]},c.ulid+m)))]})]})}function bs({obj:s}){const t=s.children.flatMap(a=>a.fields.map(r=>({field:r,on:a.name_before??a.name_after??""})));return e.jsxs(e.Fragment,{children:[e.jsx("h2",{children:s.renamed?`${s.name_before} → ${s.name_after}`:s.name_before??s.name_after}),e.jsxs("div",{className:"rv-detail-kind",children:[s.object_kind_label,s.renamed&&" · renamed",s.path&&e.jsxs("span",{className:"rv-src",children:[" · ",s.path]})]}),s.fields.map((a,r)=>e.jsx(he,{f:a},a.field+r)),t.map(({field:a,on:r},i)=>e.jsx(he,{f:a,on:r},"c"+i)),s.severity==="breaking"&&e.jsxs("div",{className:"rv-note",children:[e.jsx("span",{className:"i",children:"ⓘ"}),e.jsxs("span",{children:["This is a structural change (route B). It will need an architect's review, and CI will run ",e.jsx("code",{children:"mdl drift --check"}),"."]})]})]})}function he({f:s,on:t}){var r,i,x;const a=t?`${s.label}: ${t}`:s.label;if((r=s.breaks)!=null&&r.length)return e.jsxs("div",{className:"rv-section",children:[e.jsx("h3",{children:a}),e.jsxs("div",{className:"rv-break",children:[e.jsxs("div",{className:"rv-break-head",children:[e.jsx("span",{children:"⚠"}),e.jsxs("span",{children:[t??String(s.before??"")," removed"]}),s.detail&&e.jsx("span",{className:"rv-break-meta",children:s.detail})]}),e.jsxs("div",{className:"rv-break-body",children:["This is realised by ",s.breaks.length," dbt model",s.breaks.length>1?"s":"",". Removing it will break ",s.breaks.length>1?"them":"it","."]}),e.jsx("ul",{className:"rv-usage",children:s.breaks.map(c=>e.jsxs("li",{children:[e.jsxs("span",{className:"m",children:["→ ",c.name]}),e.jsxs("span",{className:"p",children:[c.target," · ",c.materialization]})]},c.name))})]})]});if(s.kind==="definition_changed"){const c=String(s.before??""),l=String(s.after??"");return e.jsxs("div",{className:"rv-section",children:[e.jsx("h3",{children:a}),e.jsxs("div",{className:"rv-ba",children:[e.jsxs("div",{className:"rv-ba-col",children:[e.jsx("div",{className:"rv-ba-label",children:"before"}),e.jsx("div",{className:"rv-ba-text",children:ue(c,l,"del")})]}),e.jsxs("div",{className:"rv-ba-col",children:[e.jsx("div",{className:"rv-ba-label",children:"after"}),e.jsx("div",{className:"rv-ba-text",children:ue(l,c,"ins")})]})]})]})}if(Array.isArray(s.before)||Array.isArray(s.after)){const c=new Set(((i=s.before)==null?void 0:i.map(String))??[]),l=new Set(((x=s.after)==null?void 0:x.map(String))??[]),m=[...l].filter(u=>!c.has(u)),N=[...c].filter(u=>!l.has(u));return e.jsxs("div",{className:"rv-section",children:[e.jsx("h3",{children:a}),e.jsxs("div",{className:"rv-chips",children:[m.map(u=>e.jsxs("span",{className:"rv-chip-add",children:["+ ",u]},"a"+u)),N.map(u=>e.jsxs("span",{className:"rv-chip-del",children:["− ",u]},"r"+u))]})]})}return e.jsxs("div",{className:"rv-section",children:[e.jsx("h3",{children:a}),e.jsxs("div",{className:"rv-scalar",children:[e.jsx("span",{className:"from",children:me(s.before)}),e.jsx("span",{className:"arrow",children:"→"}),e.jsx("span",{className:"to",children:me(s.after)})]})]})}function me(s){return s==null||s===""?"—":typeof s=="boolean"?s?"yes":"no":typeof s=="object"?JSON.stringify(s):String(s)}function ue(s,t,a){const r=s.split(/(\s+)/),i=t.split(/(\s+)/),x=h=>h.toLowerCase().replace(/[.,;:]/g,""),c=r.length,l=i.length,m=Array.from({length:c+1},()=>new Array(l+1).fill(0));for(let h=c-1;h>=0;h--)for(let v=l-1;v>=0;v--)m[h][v]=x(r[h])===x(i[v])?m[h+1][v+1]+1:Math.max(m[h+1][v],m[h][v+1]);const N=new Set;let u=0,d=0;for(;u=m[u][d+1]?u++:d++;return r.map((h,v)=>!h.trim()||N.has(v)?e.jsx("span",{children:h},v):a==="del"?e.jsx("del",{children:h},v):e.jsx("ins",{children:h},v))}const vs=n.lazy(()=>js(()=>import("./ModelDiagram-C1PzETqC.js"),__vite__mapDeps([0,1,2,3])).then(s=>({default:s.ModelDiagram})));function Ns({stagedDiff:s,stagedRoute:t,user:a,onBack:r,onSubmit:i,excluded:x,onToggleObject:c,selectable:l}){const[m,N]=n.useState(null),[u,d]=n.useState(null),[h,v]=n.useState(null),[S,C]=n.useState(null),[T,_]=n.useState(null),[b,k]=n.useState("changes"),E=n.useCallback(()=>{xe(a).then(p=>{C(p);const A=p.base_branch??"main";s?N(s):ze("HEAD").then(R=>R.ok?N(R):_(R.error??"could not read the model")).catch(R=>_(String(R))),t?d(t):We("HEAD").then(d).catch(()=>{}),Ge(A).then(v).catch(()=>{})}).catch(p=>_(String(p)))},[a,s,t]);if(n.useEffect(E,[E]),T)return e.jsxs("div",{className:"rv-shell",children:[e.jsx(pe,{onBack:r,diff:null}),e.jsx("p",{className:"sme-splash error",children:e.jsx("pre",{children:T})})]});if(!m)return e.jsx("div",{className:"sme-splash",children:"◮ reading your changes…"});const $=new Set(m.objects.map(p=>p.ulid).filter(p=>!x.has(p)));return e.jsxs("div",{className:"rv-shell",children:[e.jsx(pe,{onBack:r,diff:m,tab:b,onTab:k}),b==="changes"?e.jsx(xs,{diff:m,selectable:l,selected:$,onToggle:c}):e.jsx(n.Suspense,{fallback:e.jsx("div",{className:"sme-splash",children:"◮ drawing the model…"}),children:e.jsx(vs,{severityByUlid:ws(m)})}),e.jsxs("div",{className:"rv-foot",children:[e.jsx(ys,{cl:u}),e.jsx(Ss,{conf:h,ctx:S}),e.jsxs("div",{className:"rv-actions",children:[e.jsx("button",{className:"sme-secondary",onClick:r,children:"Keep editing"}),e.jsx("button",{className:"sme-primary",disabled:!m.objects.length||(S?!S.can_propose:!1),title:S&&!S.can_propose?"the working tree has uncommitted changes":"",onClick:()=>i(u),children:"Submit for review →"})]})]})]})}function ws(s){const t=new Map;for(const a of s.objects)t.set(a.ulid,a.severity);return t}function pe({onBack:s,diff:t,tab:a,onTab:r}){const i=t==null?void 0:t.counts;return e.jsxs("div",{className:"rv-head",children:[e.jsxs("div",{children:[e.jsx("button",{className:"rv-back",onClick:s,children:"‹ Back to terms"}),e.jsx("h1",{className:"rv-title",children:"Review your changes"}),i&&e.jsxs("div",{className:"rv-sub",children:[i.objects," object",i.objects===1?"":"s"," ·"," ",(i.breaking??0)+(i.additive??0)+(i.cosmetic??0)," changes",(t==null?void 0:t.has_breaking)&&e.jsxs("span",{className:"warn",children:[" · ⚠ ",i.breaking," breaking"]})," · comparing to ",e.jsx("code",{children:t==null?void 0:t.base.label})]})]}),r&&e.jsxs("div",{className:"rv-tabs",children:[e.jsxs("button",{className:"rv-tab"+(a==="changes"?" active":""),onClick:()=>r("changes"),children:["Changes ",(t==null?void 0:t.objects.length)??0]}),e.jsx("button",{className:"rv-tab"+(a==="diagram"?" active":""),onClick:()=>r("diagram"),children:"Diagram"})]})]})}function ys({cl:s}){if(!(s!=null&&s.primary))return null;const t=s.reviewers_actual??s.reviewers;return e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"rv-route",children:[e.jsxs("span",{className:"rv-route-badge",children:["Route ",s.primary," · ",s.primary_name]}),e.jsx("span",{className:"k",children:"Reviewers"}),e.jsx("span",{children:t.join(", ")}),s.reviewers_actual&&e.jsx("span",{className:"rv-src",children:"from .github/CODEOWNERS"})]}),e.jsxs("div",{className:"rv-gates",children:["CI gates"," ",s.gates.map(a=>e.jsx("code",{children:a},a))]})]})}function Ss({conf:s,ctx:t}){return s!=null&&s.ok?s.clean?e.jsxs("div",{className:"rv-conflict ok",children:[e.jsx("span",{children:"✓"}),e.jsxs("span",{children:["Merges cleanly onto ",s.base]}),s.behind>0&&e.jsxs("span",{className:"detail",children:["· ",s.base," is ",s.behind," commit",s.behind===1?"":"s"," ahead"]}),(t==null?void 0:t.dirty)&&e.jsx("span",{className:"detail",children:"· uncommitted changes in the working tree"})]}):e.jsxs("div",{className:"rv-conflict bad",children:[e.jsx("span",{children:"⚠"}),e.jsxs("span",{children:[s.base," has changed and ",s.files.length," file",s.files.length===1?"":"s"," now conflict"]}),e.jsx("span",{className:"detail",children:s.files.map(a=>a.path).join(", ")})]}):null}function ks({term:s,canEdit:t,pendingCount:a,onEdit:r}){var i,x;return e.jsxs("div",{className:"sme-card",children:[e.jsxs("div",{className:"sme-card-head",children:[e.jsxs("div",{children:[e.jsx("h1",{children:s.name}),((i=s.subject_area)==null?void 0:i.name)&&e.jsx("span",{className:"sme-chip",children:s.subject_area.name}),s.kind==="term"&&e.jsx("span",{className:"sme-chip",children:"glossary term"})]}),t&&e.jsx("button",{className:"sme-primary",onClick:r,children:a>0?"Continue editing":"Suggest an edit"})]}),e.jsx("p",{className:"sme-definition",children:s.definition??e.jsx("em",{children:"No definition yet."})}),s.synonyms.length>0&&e.jsxs("section",{children:[e.jsx("h3",{children:"Also called"}),e.jsx("div",{children:s.synonyms.map(c=>e.jsx("span",{className:"sme-chip",children:c},c))})]}),s.stewardship&&(s.stewardship.owner||s.stewardship.steward)&&e.jsxs("section",{children:[e.jsx("h3",{children:"Who owns this"}),s.stewardship.owner&&e.jsxs("div",{className:"sme-kv",children:[e.jsx("span",{className:"k",children:"owner"}),e.jsx("span",{children:s.stewardship.owner})]}),s.stewardship.steward&&e.jsxs("div",{className:"sme-kv",children:[e.jsx("span",{className:"k",children:"steward"}),e.jsx("span",{children:s.stewardship.steward})]})]}),((x=s.ontology)==null?void 0:x.aligns_to)&&e.jsxs("section",{children:[e.jsx("h3",{children:"Standard it maps to"}),e.jsxs("div",{className:"sme-kv",children:[e.jsx("span",{className:"k",children:"aligned to"}),e.jsx("code",{children:s.ontology.aligns_to}),s.ontology.status==="proposed"&&e.jsx("span",{className:"sme-badge proposed",children:"awaiting architect"})]})]}),e.jsxs("section",{children:[e.jsx("h3",{children:"Where it's used"}),s.where_used.length===0?e.jsx("p",{className:"sme-muted",children:s.kind==="term"?"A glossary term — not yet realised by a data model.":"Not yet realised by any data model."}):e.jsx("ul",{className:"sme-usage",children:s.where_used.map(c=>e.jsxs("li",{children:[e.jsx("span",{className:"sme-usage-model",children:c.logical_entity}),c.unmanaged&&e.jsx("span",{className:"sme-badge",children:"engineer-owned"}),c.physical.length>0&&e.jsx("span",{className:"sme-usage-phys",children:c.physical.map(l=>l.name).join(", ")})]},c.logical_id))})]})]})}function _s({term:s,pending:t,onStage:a,onDone:r,catalogOwned:i=[],catalog:x=null}){var A,R,D,L;const c=j=>i.includes(j),l=j=>t.find(P=>P.label===j),m=(j,P)=>{const F=l(j);return F?F.after:P},[N,u]=n.useState(m("Definition",s.definition??"")),[d,h]=n.useState(s.synonyms),[v,S]=n.useState(""),[C,T]=n.useState(((A=s.stewardship)==null?void 0:A.owner)??""),[_,b]=n.useState(((R=s.stewardship)==null?void 0:R.steward)??""),k=()=>{N.trim()!==(s.definition??"").trim()&&a({op:"set_definition",payload:{id:s.id,definition:N.trim()},label:"Definition",before:s.definition??"(none)",after:N.trim()||"(none)"})},E=(j,P)=>{var F,O;a({op:"set_stewardship",payload:{id:s.id,owner:j||null,steward:P||null},label:"Stewardship",before:`owner: ${((F=s.stewardship)==null?void 0:F.owner)??"—"} · steward: ${((O=s.stewardship)==null?void 0:O.steward)??"—"}`,after:`owner: ${j||"—"} · steward: ${P||"—"}`})},$=j=>{h(j),a({op:"update_synonyms",payload:{id:s.id,synonyms:j},label:"Also called",before:s.synonyms.join(", ")||"(none)",after:j.join(", ")||"(none)"})},p=s.kind==="term";return e.jsxs("div",{className:"sme-editor",children:[e.jsxs("div",{className:"sme-card-head",children:[e.jsxs("h1",{children:["Suggest an edit — ",s.name]}),e.jsx("button",{className:"sme-secondary",onClick:r,children:"Back"})]}),e.jsxs("p",{className:"sme-hint",children:["You can refine what this term ",e.jsx("em",{children:"means"}),". Structure (keys, relationships, how it's built) stays with the data team."]}),(c("definition")||c("synonyms")||c("stewardship"))&&x&&e.jsxs("div",{className:"sme-catalog-banner",children:["Definitions, synonyms, and ownership are mastered in ",e.jsx("b",{children:x.name}),". Edit them there — this glossary mirrors them read-only.",x.url&&e.jsxs(e.Fragment,{children:[" ",e.jsxs("a",{href:x.url,target:"_blank",rel:"noreferrer",children:["Open in ",x.name," →"]})]}),e.jsx("div",{className:"sme-catalog-note",children:"You can still propose a standard mapping below."})]}),e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Definition"}),c("definition")?e.jsx("p",{className:"sme-readonly-value",children:s.definition??"—"}):e.jsx("textarea",{rows:4,value:N,onChange:j=>u(j.target.value),onBlur:k,placeholder:"A plain-language business definition…"})]}),e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Also called (synonyms)"}),c("synonyms")?e.jsx("div",{className:"sme-syn-row",children:s.synonyms.length?s.synonyms.map(j=>e.jsx("span",{className:"sme-chip",children:j},j)):e.jsx("span",{className:"sme-muted",children:"none"})}):e.jsxs("div",{className:"sme-syn-row",children:[d.map(j=>e.jsxs("span",{className:"sme-chip removable",onClick:()=>$(d.filter(P=>P!==j)),children:[j," ✕"]},j)),e.jsx("input",{value:v,placeholder:"add + Enter",onChange:j=>S(j.target.value),onKeyDown:j=>{j.key==="Enter"&&v.trim()&&($([...d,v.trim()]),S(""))}})]})]}),!p&&(c("stewardship")?e.jsxs("div",{className:"sme-field-row",children:[e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Owner"}),e.jsx("p",{className:"sme-readonly-value",children:((D=s.stewardship)==null?void 0:D.owner)??"—"})]}),e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Steward"}),e.jsx("p",{className:"sme-readonly-value",children:((L=s.stewardship)==null?void 0:L.steward)??"—"})]})]}):e.jsxs("div",{className:"sme-field-row",children:[e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Owner"}),e.jsx("input",{value:C,onChange:j=>T(j.target.value),onBlur:()=>E(C,_)})]}),e.jsxs("label",{className:"sme-field",children:[e.jsx("span",{children:"Steward"}),e.jsx("input",{value:_,onChange:j=>b(j.target.value),onBlur:()=>E(C,_)})]})]})),e.jsx(Es,{term:s,onStage:a}),e.jsx("p",{className:"sme-note",children:t.length>0?`${t.length} change(s) staged — use “Submit for review” at the top when you're done.`:"Changes you make are collected until you submit them for review."})]})}const Cs=["skos:exactMatch","skos:closeMatch","skos:broadMatch","skos:narrowMatch"];function Es({term:s,onStage:t}){var d;const[a,r]=n.useState(!1),[i,x]=n.useState(s.name),[c,l]=n.useState([]),[m,N]=n.useState("skos:closeMatch"),u=h=>{x(h),He(h).then(v=>l(v.results)).catch(()=>l([]))};return a?e.jsxs("div",{className:"sme-field sme-align",children:[e.jsx("span",{children:"Propose a standard mapping (a suggestion; the architect confirms)"}),e.jsx("input",{value:i,onChange:h=>u(h.target.value),placeholder:"Search standards…",autoFocus:!0}),e.jsx("div",{className:"sme-align-results",children:c.map(h=>e.jsxs("button",{className:"sme-align-card",onClick:()=>{var v;t({op:"set_alignment",payload:{id:s.id,aligns_to:h.prefixed,alignment:m,status:"proposed"},label:"Standard mapping",before:((v=s.ontology)==null?void 0:v.aligns_to)??"(none)",after:`${h.prefixed} (${m}, proposed)`}),r(!1)},children:[e.jsx("strong",{children:h.label})," ",e.jsx("code",{children:h.prefixed}),h.definition&&e.jsx("span",{className:"sme-align-def",children:h.definition})]},h.iri))}),e.jsx("select",{value:m,onChange:h=>N(h.target.value),children:Cs.map(h=>e.jsx("option",{children:h},h))})]}):e.jsxs("div",{className:"sme-field",children:[e.jsx("span",{children:"Standard mapping"}),e.jsxs("div",{children:[(d=s.ontology)!=null&&d.aligns_to?e.jsx("code",{className:"sme-current-align",children:s.ontology.aligns_to}):e.jsx("span",{className:"sme-muted",children:"not mapped to a standard"}),e.jsx("button",{className:"sme-link",onClick:()=>r(!0),children:"Propose a mapping…"})]})]})}function As(){const[s,t]=n.useState(null),[a,r]=n.useState(!0),[i,x]=n.useState(null),[c,l]=n.useState(""),[m,N]=n.useState(null),[u,d]=n.useState(""),[h,v]=n.useState(""),[S,C]=n.useState(null),[T,_]=n.useState(!1),[b,k]=n.useState([]),[E,$]=n.useState(!1),[p,A]=n.useState(window.location.hash==="#proposals"?"proposals":"browse"),[R,D]=n.useState(new Set),[L,j]=n.useState(()=>localStorage.getItem("mdl.sme.user")??""),[P,F]=n.useState(null),[O,q]=n.useState(null),[W,U]=n.useState(!1),[V,g]=n.useState(null),o=n.useCallback((f,w)=>{var ae;const I=Q=>{var te;const K=O==null?void 0:O.entities.find(re=>{var ie;return re.id===Q||((ie=re.conceptual)==null?void 0:ie.id)===Q});return((te=K==null?void 0:K.conceptual)==null?void 0:te.name)??(K==null?void 0:K.name)??"object"},B=I(w.id??w.entity_id);switch(f){case"create_entity":return{label:`New entity: ${w.name}`,before:"—",after:String(w.name??"")};case"create_relationship":return{label:`New relationship: ${I(w.from_entity)} → ${I(w.to_entity)}`,before:"—",after:String(w.cardinality??"many_to_one")};case"delete_entity":return{label:`Deleted ${B}`,before:B,after:"—"};case"delete_relationship":return{label:"Relationship removed",before:"",after:"—"};case"update_relationship":return{label:"Relationship changed",before:"",after:[w.cardinality,w.optionality,w.identifying?"identifying":""].filter(Boolean).join(", ")};case"set_definition":return{label:`Definition: ${B}`,before:"",after:String(w.definition??"")};case"add_attribute":return{label:`Attribute added to ${B}`,before:"—",after:String(w.name??"")};case"delete_attribute":return{label:`Attribute removed from ${B}`,before:"",after:"—"};case"update_attribute":return{label:`Attribute changed on ${B}`,before:"",after:JSON.stringify(w)};case"rename_entity":return{label:`Renamed ${B}`,before:B,after:String(w.name??"")};case"set_subject_area":return{label:`Subject area: ${B}`,before:"",after:String(w.subject_area??"none")};case"set_stewardship":return{label:`Stewardship: ${B}`,before:"",after:String(w.steward??"")};case"set_subject_area_members":{const Q=w.__label,K=((ae=w.members)==null?void 0:ae.length)??0;return{label:Q??"Subject area members",before:"",after:`${K} object${K===1?"":"s"}`}}case"set_alignment":return{label:`Alignment: ${B}`,before:"",after:String(w.aligns_to??"")};default:return{label:`${f.replace(/_/g," ")}: ${B}`,before:"",after:""}}},[O]),y=Ze({subjectArea:u,describe:o}),M=n.useCallback(async(f,w)=>{const I=(O==null?void 0:O.fingerprint)??"",B=await Ke(f,w,I);return await le(u||void 0).then(q),B},[O,u]),z=n.useMemo(()=>{const f={};for(const w of y.pending)w.op==="set_subject_area_members"&&(f[w.payload.id]=w.payload.members??[]);return f},[y.pending]),G=n.useMemo(()=>[...b,...y.pending],[b,y.pending]),[ye,X]=n.useState(null),[Se,Z]=n.useState(null);n.useEffect(()=>{if(!G.length){X(null),Z(null);return}const f=new AbortController,w=G.map(I=>({op:I.op,payload:I.payload}));return je(w,u||void 0,f.signal).then(I=>X(I.diff??null)).catch(I=>{I.name!=="AbortError"&&X(null)}),Ve(w).then(Z).catch(()=>Z(null)),()=>f.abort()},[G,u]);const ke=n.useMemo(()=>es(G),[G]).size{A(f),window.location.hash=f==="browse"?"":`#${f}`},[]);n.useEffect(()=>{const f=()=>{const w=window.location.hash;A(w==="#proposals"?"proposals":w==="#review"?"review":w==="#model"?"model":w==="#areas"?"areas":"browse")};return f(),window.addEventListener("hashchange",f),()=>window.removeEventListener("hashchange",f)},[]);const ee=n.useCallback(()=>{Ue({subject_area:u,q:h}).then(t).catch(f=>N(String(f)))},[u,h]);n.useEffect(ee,[ee]),n.useEffect(()=>{le(u||void 0).then(f=>{r(f.read_only),l(f.project.name),q(f),U(!!f.direct),F(f.identity??null),f.identity&&f.identity.source!=="anonymous"&&f.identity.name&&j(f.identity.name)}).catch(()=>{}),Ye().then(x).catch(()=>{})},[u]);const se=n.useMemo(()=>(s==null?void 0:s.terms.find(f=>f.id===S))??null,[s,S]),_e=n.useCallback(f=>{const w=f.key??Ne(f.op,f.payload);k(I=>[...I.filter(B=>B.key!==w),{...f,key:w}])},[]),Ce=f=>k(w=>w.filter((I,B)=>B!==f)),Ee=()=>{k([]),$(!1),_(!1),ee()};if(m)return e.jsxs("div",{className:"sme-splash error",children:[e.jsx("h1",{children:"◮ Glossary"}),e.jsx("pre",{children:m}),e.jsxs("p",{children:["Is ",e.jsx("code",{children:"mdl glossary"})," pointed at a model?"]})]});if(!s)return e.jsx("div",{className:"sme-splash",children:"◮ loading glossary…"});const Y=!a,ne=(i==null?void 0:i.catalog_owned_fields)??[],Ae=ne.length>0;return e.jsxs("div",{className:"sme",children:[e.jsxs("header",{className:"sme-top",children:[e.jsxs("div",{className:"sme-brand",children:[e.jsx("span",{className:"sme-logo",children:"◮"}),e.jsx("span",{children:"Glossary"}),e.jsx("span",{className:"sme-project",children:c}),a&&e.jsx("span",{className:"sme-chip",children:"read-only"}),Ae&&e.jsxs("span",{className:"sme-chip catalog",title:`Definitions are mastered in ${i==null?void 0:i.catalog_name}`,children:["mirrors ",i==null?void 0:i.catalog_name]})]}),e.jsx("input",{className:"sme-search",type:"search",placeholder:"Search terms & definitions…",value:h,onChange:f=>v(f.target.value)}),e.jsxs("nav",{className:"sme-tabs",children:[e.jsx("button",{className:"sme-tab"+(p==="browse"?" active":""),onClick:()=>H("browse"),children:"Terms"}),e.jsx("button",{className:"sme-tab"+(p==="model"?" active":""),onClick:()=>H("model"),children:"Model"}),e.jsx("button",{className:"sme-tab"+(p==="areas"?" active":""),onClick:()=>H("areas"),children:"Subject areas"}),e.jsx("button",{className:"sme-tab"+(p==="proposals"?" active":""),onClick:()=>H("proposals"),children:"My proposals"})]}),G.length>0&&e.jsxs("button",{className:"sme-tray",onClick:()=>H("review"),children:[G.length," change",G.length>1?"s":""," · Review →"]})]}),p==="browse"&&e.jsx(ss,{user:L}),p==="review"?e.jsx(Ns,{stagedDiff:ye,stagedRoute:Se,user:L,selectable:ke,excluded:R,onToggleObject:f=>D(w=>{const I=new Set(w);return I.has(f)?I.delete(f):I.add(f),I}),onBack:()=>H("browse"),onSubmit:f=>{g(f),$(!0)}}):p==="model"?y.previewDoc??O?e.jsx(ls,{doc:y.previewDoc??O,subjectArea:u,onSubjectArea:d,exec:W?M:y.exec,canEdit:Y,direct:W,onImported:()=>H("review"),busy:y.busy}):e.jsx("div",{className:"sme-splash",children:"◮ loading the model…"}):p==="areas"?e.jsx(ts,{exec:y.exec,canEdit:Y,stagedMembers:z}):p==="proposals"?e.jsx(cs,{user:L,onView:()=>H("review")}):e.jsxs("div",{className:"sme-body",children:[e.jsxs("nav",{className:"sme-nav",children:[e.jsx("button",{className:"sme-sa"+(u===""?" active":""),onClick:()=>d(""),children:"All subject areas"}),s.subject_areas.map(f=>e.jsx("button",{className:"sme-sa"+(u===f.id?" active":""),onClick:()=>d(f.id),title:f.definition??void 0,children:f.name},f.id))]}),e.jsxs("ul",{className:"sme-list",children:[s.terms.map(f=>{var w;return e.jsxs("li",{className:"sme-list-item"+(f.id===S?" active":""),onClick:()=>{C(f.id),_(!1)},children:[e.jsx("span",{className:"sme-term-name",children:f.name}),f.kind==="term"&&e.jsx("span",{className:"sme-kind",children:"term"}),((w=f.ontology)==null?void 0:w.status)==="proposed"&&e.jsx("span",{className:"sme-badge proposed",title:"alignment awaiting architect",children:"proposed"}),e.jsx("span",{className:"sme-term-def",children:f.definition??"no definition yet"})]},f.id)}),s.terms.length===0&&e.jsx("li",{className:"sme-empty",children:"no terms match"})]}),e.jsx("section",{className:"sme-detail",children:se?T&&Y?e.jsx(_s,{term:se,pending:b,onStage:_e,onDone:()=>_(!1),catalogOwned:ne,catalog:i?{name:i.catalog_name,url:i.catalog_url}:null}):e.jsx(ks,{term:se,canEdit:Y,pendingCount:b.length,onEdit:()=>_(!0)}):e.jsx("div",{className:"sme-placeholder",children:"Select a term to see its definition."})})]}),E&&e.jsx(hs,{changes:G,routeAdvice:V,identity:P,user:L,onUser:f=>{j(f),localStorage.setItem("mdl.sme.user",f)},onDrop:Ce,onClose:()=>$(!1),onProposed:()=>{Ee(),y.clear(),D(new Set),H("proposals")}})]})}Re.createRoot(document.getElementById("root")).render(e.jsx($e.StrictMode,{children:e.jsx(As,{})})); diff --git a/packages/server/src/mdl_server/static/sme.html b/packages/server/src/mdl_server/static/sme.html index 3399be6..9f74b43 100644 --- a/packages/server/src/mdl_server/static/sme.html +++ b/packages/server/src/mdl_server/static/sme.html @@ -4,7 +4,7 @@ Modelith Glossary - + From 7ae23dd2b306f2fc338b565cfe80c553ff1fc1d0 Mon Sep 17 00:00:00 2001 From: Debasish Bose Date: Fri, 11 Sep 2026 11:06:43 +1000 Subject: [PATCH 2/4] Add Re-layout and Fit buttons to the Studio Model toolbar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ER canvas exposed relayout() and fitView(), but only the architect canvas wired them to buttons — the SME Studio Model tab had no way to auto-arrange or fit the diagram, so overlapping nodes could not be tidied (only the React Flow zoom controls were available, which pan/zoom but don't re-arrange). Surface both as ghost buttons next to "+ Entity", reusing the methods the canvas already provides. --- canvas/src/sme/ModelWorkspace.tsx | 14 ++++++++++++++ .../src/mdl_server/static/assets/sme-DsrrbJWz.js | 2 -- .../src/mdl_server/static/assets/sme-Dwro4B4w.js | 2 ++ packages/server/src/mdl_server/static/sme.html | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) delete mode 100644 packages/server/src/mdl_server/static/assets/sme-DsrrbJWz.js create mode 100644 packages/server/src/mdl_server/static/assets/sme-Dwro4B4w.js diff --git a/canvas/src/sme/ModelWorkspace.tsx b/canvas/src/sme/ModelWorkspace.tsx index 508439e..ed815eb 100644 --- a/canvas/src/sme/ModelWorkspace.tsx +++ b/canvas/src/sme/ModelWorkspace.tsx @@ -195,6 +195,20 @@ export function ModelWorkspace({ )} drag between entities to relate them + +