fenix-claude-plugin 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fenix",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Fenix patent automation for Claude — MCP connector plus patent drafting, office-action, and project skills. Provide one API key; the server infers your firm (client + database).",
5
5
  "author": {
6
6
  "name": "Fenix AI",
@@ -0,0 +1,205 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Claims Editor</title>
6
+ <style>
7
+ *{box-sizing:border-box;margin:0;padding:0}
8
+ body{font-family:system-ui,-apple-system,sans-serif;background:#fff7ed;min-height:100vh;padding:20px 16px}
9
+ .card{background:#fff;max-width:900px;margin:0 auto;border-radius:10px;box-shadow:0 1px 4px rgba(0,0,0,.12);overflow:hidden}
10
+ .hdr{display:flex;align-items:center;gap:12px;padding:14px 20px;border-bottom:1px solid #fed7aa}
11
+ .hdr h1{font-size:15px;font-weight:600;color:#1e293b}.hdr .rev{font-size:12px;color:#94a3b8}
12
+ .hdr .toggle-all{margin-left:auto;flex-shrink:0}
13
+ .bdy{padding:20px}.sub{font-size:13px;color:#64748b;margin-bottom:14px}
14
+ input,select,textarea{font-family:inherit;font-size:13px;border:1.5px solid #fed7aa;border-radius:5px;padding:6px 10px;outline:none;width:100%;transition:border-color .15s;background:#fff}
15
+ input:focus,select:focus,textarea:focus{border-color:#ea580c}
16
+ .field{margin-bottom:12px}label{display:block;font-size:12px;font-weight:600;color:#374151;margin-bottom:4px}
17
+ .acc{border:1px solid #fed7aa;overflow:hidden;margin-bottom:-1px}
18
+ .acc-hd{display:flex;align-items:center;gap:10px;padding:11px 14px;cursor:pointer;background:#fffbf5;user-select:none}
19
+ .acc-hd:hover{background:#fff7ed}
20
+ .acc-hd .chev{color:#ea580c;font-size:12px;transition:transform .15s;flex-shrink:0}
21
+ .acc.open .chev{transform:rotate(90deg)}
22
+ .acc-hd .ttl{font-weight:600;font-size:13px;color:#1e293b;flex-shrink:0}
23
+ .acc-hd .prev{font-size:12px;color:#94a3b8;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
24
+ .acc-bd{display:none;padding:14px;border-top:1px solid #fed7aa;background:#fff}
25
+ .acc.open .acc-bd{display:block}
26
+ .btn-sm{padding:4px 12px;background:#fff;border:1.5px solid #fed7aa;border-radius:5px;font-size:12px;cursor:pointer;color:#374151}
27
+ .btn-sm:hover{border-color:#ea580c}
28
+ .rm-item{margin-left:auto;color:#dc2626;background:none;border:none;cursor:pointer;font-size:13px;flex-shrink:0}
29
+ .bar{margin-top:16px;display:flex;align-items:center;gap:14px}
30
+ .cta{padding:9px 24px;background:#ea580c;color:#fff;border:none;border-radius:6px;font-size:14px;font-weight:600;cursor:pointer;transition:background .15s}
31
+ .cta:hover{background:#c2410c}.cta:disabled{opacity:.5;cursor:default}
32
+ #st{font-size:13px;font-weight:500}#st.ok{color:#16a34a}#st.err{color:#dc2626}#st.busy{color:#64748b}
33
+ textarea.sec{min-height:160px;font-family:Menlo,Consolas,monospace;line-height:1.6;resize:vertical}
34
+ .addbar{margin:6px 0 4px}
35
+ .diff{display:none;margin-top:16px;border:1px solid #fed7aa;border-radius:6px;overflow:hidden}
36
+ .diff.show{display:block}
37
+ .diff-hd{display:flex;align-items:center;gap:10px;padding:8px 12px;background:#fff7ed;font-size:12px;font-weight:600;color:#c2410c}
38
+ .diff pre{margin:0;padding:12px;font-size:12px;font-family:Menlo,Consolas,monospace;line-height:1.5;white-space:pre-wrap;max-height:240px;overflow:auto;color:#1e293b}
39
+ .ftr{padding:9px 20px;border-top:1px solid #fed7aa;font-size:11px;color:#94a3b8;display:flex;justify-content:space-between}
40
+ </style>
41
+ </head>
42
+ <body>
43
+ <div class="card">
44
+ <div class="hdr"><h1>Claims Editor</h1><span class="rev" id="revLbl"></span><button class="btn-sm toggle-all" id="toggleAll" onclick="toggleAll()" style="display:none">Expand all</button></div>
45
+ <div class="bdy">
46
+ <p class="sub" id="sub">Loading current claims from Fenix…</p>
47
+ <div id="acc"></div>
48
+ <div class="addbar"><button class="btn-sm" onclick="addItem()">+ Add claim</button></div>
49
+ <div class="bar"><button class="cta" id="saveBtn" onclick="save()" disabled>Save to Fenix</button><span id="st"></span></div>
50
+ <div class="diff" id="diffPanel">
51
+ <div class="diff-hd"><span>Saved. Paste this summary into chat so the assistant stays in sync:</span><button class="btn-sm" style="margin-left:auto" onclick="copyDiff()">Copy summary</button></div>
52
+ <pre id="diffText"></pre>
53
+ </div>
54
+ </div>
55
+ <div class="ftr"><span>Fenix Patent Assistant — live editor</span><span>v1</span></div>
56
+ </div>
57
+ <script>
58
+ // The assistant replaces __PROJECT_ID__ with the real Fenix project id before opening this artifact.
59
+ var PROJECT_ID = "__PROJECT_ID__";
60
+ // The assistant also substitutes this connector's MCP tool prefix here, e.g. "mcp__<connector-id>__"
61
+ // (the prefix differs per connection, so it cannot be hardcoded). If left unsubstituted, call()
62
+ // falls back to the bare tool name (which works on hosts that don't namespace MCP tools).
63
+ var SERVER_PREFIX = "__SERVER_PREFIX__";
64
+
65
+ // Baseline = the claim set last loaded from / saved to Fenix, keyed by claimNumber. The diff on
66
+ // Save is computed against this so the assistant only needs the delta, not the whole set.
67
+ var baseline = [];
68
+ var baselineRev = null;
69
+
70
+ // ---- Cowork MCP plumbing (same unwrap pattern as the read-only artifacts) ----
71
+ function unwrap(res){
72
+ if(res==null) return res;
73
+ if(typeof res==="string"){ try{ return JSON.parse(res); }catch(e){ return res; } }
74
+ if(Array.isArray(res)) return res;
75
+ if(typeof res==="object" && Array.isArray(res.content)){
76
+ var t=res.content.map(function(c){ return (c&&c.text)||""; }).join("");
77
+ try{ return JSON.parse(t); }catch(e){ return res; }
78
+ }
79
+ if(res && res.result!==undefined) return unwrap(res.result);
80
+ return res;
81
+ }
82
+ function toolName(n){ return (SERVER_PREFIX && SERVER_PREFIX.indexOf("__SERVER")!==0) ? SERVER_PREFIX + n : n; }
83
+ async function call(name,args){
84
+ var tries=[toolName(name)]; if(tries[0]!==name) tries.push(name);
85
+ var err; for(var i=0;i<tries.length;i++){ try{ return unwrap(await window.cowork.callMcpTool(tries[i],args||{})); }catch(e){ err=e; } }
86
+ throw err;
87
+ }
88
+
89
+ // ---- Accordion editor (same shape the clipboard editor produced) ----
90
+ function elt(tag,cls,txt){ var e=document.createElement(tag); if(cls)e.className=cls; if(txt!=null)e.textContent=txt; return e; }
91
+ function makeAcc(headerTitle,preview,bodyBuilder){
92
+ var wrap=elt("div","acc"); var hd=elt("div","acc-hd");
93
+ hd.appendChild(elt("span","chev","▶")); hd.appendChild(elt("span","ttl",headerTitle));
94
+ var prev=elt("span","prev",preview||""); hd.appendChild(prev);
95
+ var rm=elt("button","rm-item","Remove"); rm.onclick=function(ev){ ev.stopPropagation(); wrap.remove(); syncToggle(); }; hd.appendChild(rm);
96
+ hd.onclick=function(){ wrap.classList.toggle("open"); syncToggle(); };
97
+ var bd=elt("div","acc-bd"); bodyBuilder(bd,prev);
98
+ wrap.appendChild(hd); wrap.appendChild(bd); return wrap;
99
+ }
100
+ function claimBody(bd,prev,num,text){
101
+ var f1=elt("div","field"); f1.appendChild(elt("label",null,"Claim Number")); var ni=document.createElement("input"); ni.className="c-num"; ni.value=num||""; f1.appendChild(ni); bd.appendChild(f1);
102
+ var f2=elt("div","field"); f2.appendChild(elt("label",null,"Claim Text")); var ta=document.createElement("textarea"); ta.className="c-txt sec"; ta.value=text||""; ta.oninput=function(){ prev.textContent=(ta.value||"").slice(0,80); }; f2.appendChild(ta); bd.appendChild(f2);
103
+ }
104
+ function addClaim(num,text,orig){ var acc=document.getElementById("acc"); var w=makeAcc("Claim "+(num||"?"),(text||"").slice(0,80),function(bd,prev){ claimBody(bd,prev,num,text); }); w.__claim=orig||{}; acc.appendChild(w); return w; }
105
+ function addItem(){ var rows=document.querySelectorAll("#acc .c-num"); var last=rows.length?parseInt(rows[rows.length-1].value||"0"):0; addClaim(String(last+1),"",{}).classList.add("open"); syncToggle(); }
106
+ function syncToggle(){ var btn=document.getElementById("toggleAll"); var accs=document.querySelectorAll("#acc .acc"); if(!accs.length){ btn.style.display="none"; return; } btn.style.display=""; var anyClosed=Array.from(accs).some(function(a){ return !a.classList.contains("open"); }); btn.textContent=anyClosed?"Expand all":"Collapse all"; }
107
+ function toggleAll(){ var accs=document.querySelectorAll("#acc .acc"); var anyClosed=Array.from(accs).some(function(a){ return !a.classList.contains("open"); }); accs.forEach(function(a){ if(anyClosed){ a.classList.add("open"); } else { a.classList.remove("open"); } }); syncToggle(); }
108
+
109
+ // Collect the editor into the Fenix claims schema (one object per claim).
110
+ function collect(){
111
+ var out=[]; document.querySelectorAll("#acc .acc").forEach(function(a){
112
+ var n=a.querySelector(".c-num").value.trim(); var t=a.querySelector(".c-txt").value; var orig=a.__claim||{};
113
+ // Overlay edits onto the ORIGINAL claim so any fields not shown in the editor survive.
114
+ if(n||t.trim()){ var c=Object.assign({}, orig, { claimNumber:n, claimText:t.split(/\n/) }); if(!c.claimStatus) c.claimStatus="original"; out.push(c); }
115
+ }); return out;
116
+ }
117
+
118
+ // ---- Diff: new set vs baseline, by claimNumber. Text compared as joined lines. ----
119
+ function textOf(c){ return (Array.isArray(c.claimText)?c.claimText.join("\n"):String(c.claimText||"")).trim(); }
120
+ function byNum(arr){ var m={}; arr.forEach(function(c){ m[String(c.claimNumber)]=c; }); return m; }
121
+ function diff(oldArr,newArr){
122
+ var o=byNum(oldArr), n=byNum(newArr), changed=[], added=[], removed=[], unchanged=[];
123
+ Object.keys(n).forEach(function(k){
124
+ if(!(k in o)){ added.push(n[k]); }
125
+ else if(textOf(n[k])!==textOf(o[k])){ changed.push(n[k]); }
126
+ else { unchanged.push(k); }
127
+ });
128
+ Object.keys(o).forEach(function(k){ if(!(k in n)) removed.push(k); });
129
+ function num(c){ return parseInt(c.claimNumber)||0; }
130
+ changed.sort(function(a,b){ return num(a)-num(b); }); added.sort(function(a,b){ return num(a)-num(b); });
131
+ removed.sort(function(a,b){ return (parseInt(a)||0)-(parseInt(b)||0); }); unchanged.sort(function(a,b){ return (parseInt(a)||0)-(parseInt(b)||0); });
132
+ return { changed:changed, added:added, removed:removed, unchanged:unchanged };
133
+ }
134
+ function summaryText(d,rev){
135
+ var lines=["[CLAIMS SAVED - claimsRev "+(rev==null?"?":rev)+"]"];
136
+ if(!d.changed.length && !d.added.length && !d.removed.length){ lines.push("No claim changes (re-saved identical set)."); return lines.join("\n"); }
137
+ if(d.changed.length){ lines.push(""); lines.push("Changed claims (full new text):"); d.changed.forEach(function(c){ lines.push(c.claimNumber+". "+textOf(c)); }); }
138
+ if(d.added.length){ lines.push(""); lines.push("Added claims (full text):"); d.added.forEach(function(c){ lines.push(c.claimNumber+". "+textOf(c)); }); }
139
+ if(d.removed.length){ lines.push(""); lines.push("Removed claims: "+d.removed.join(", ")); }
140
+ if(d.unchanged.length){ lines.push(""); lines.push("Unchanged: "+d.unchanged.join(", ")); }
141
+ return lines.join("\n");
142
+ }
143
+
144
+ function setStatus(msg,cls){ var s=document.getElementById("st"); s.textContent=msg||""; s.className=cls||""; }
145
+
146
+ async function load(){
147
+ if(!(window.cowork && typeof window.cowork.callMcpTool==="function")){
148
+ document.getElementById("sub").textContent="";
149
+ setStatus("⚠ Open this as a LIVE ARTIFACT, not a file preview — it needs the Cowork bridge (window.cowork) to read and save Fenix data. Ask the assistant to open it as a live artifact.","err");
150
+ return;
151
+ }
152
+ try{
153
+ var data=await call("get_project",{ projectId:PROJECT_ID, include:["claims"] });
154
+ var claims=(data&&(data.parsedClaims||data.claims))||[];
155
+ baseline=JSON.parse(JSON.stringify(claims));
156
+ baselineRev=(data&&data.revs&&typeof data.revs.claims!=="undefined")?data.revs.claims:null;
157
+ document.getElementById("acc").innerHTML="";
158
+ claims.forEach(function(c){ addClaim(String(c.claimNumber||""), textOf(c), c); });
159
+ document.getElementById("sub").textContent="Open any claim to edit it. Add or remove claims as needed, then Save to Fenix.";
160
+ document.getElementById("revLbl").textContent=baselineRev==null?"":("rev "+baselineRev);
161
+ document.getElementById("saveBtn").disabled=false;
162
+ syncToggle();
163
+ }catch(e){
164
+ document.getElementById("sub").textContent="";
165
+ setStatus("Could not load claims: "+((e&&e.message)||e),"err");
166
+ }
167
+ }
168
+
169
+ async function save(){
170
+ var btn=document.getElementById("saveBtn"); btn.disabled=true; setStatus("Saving…","busy");
171
+ document.getElementById("diffPanel").classList.remove("show");
172
+ var claims=collect();
173
+ try{
174
+ var res=await call("save_project",Object.assign({ projectId:PROJECT_ID, type:"claims", claims:claims }, (baselineRev!=null?{baseRev:baselineRev}:{})));
175
+ if(res && res.conflict){ setStatus("⚠ Not saved — the claims changed in Fenix since you opened this editor (now at rev "+res.currentRev+"). Reload to get the latest, then re-apply your edits.","err"); return; }
176
+ if(res && res.isError){ throw new Error(res.error||"save failed"); }
177
+ var rev=(res&&typeof res.claimsRev!=="undefined")?res.claimsRev:null;
178
+ var d=diff(baseline,claims);
179
+ var summary=summaryText(d,rev);
180
+ // Advance the baseline to what we just saved.
181
+ baseline=JSON.parse(JSON.stringify(claims)); baselineRev=rev;
182
+ document.getElementById("revLbl").textContent=rev==null?"":("rev "+rev);
183
+ var nChanged=d.changed.length+d.added.length+d.removed.length;
184
+ setStatus("✓ Saved to Fenix"+(rev==null?"":" (rev "+rev+")")+(nChanged?" — "+nChanged+" claim(s) changed":""),"ok");
185
+ document.getElementById("diffText").textContent=summary;
186
+ document.getElementById("diffPanel").classList.add("show");
187
+ document.getElementById("diffPanel").__summary=summary;
188
+ // If a future Cowork host exposes an automatic push-to-chat, use it; otherwise the user copies.
189
+ try{ if(window.cowork && typeof window.cowork.sendPrompt==="function"){ window.cowork.sendPrompt(summary); } }catch(e){}
190
+ }catch(e){
191
+ setStatus("Save failed: "+((e&&e.message)||e),"err");
192
+ }finally{ btn.disabled=false; }
193
+ }
194
+
195
+ function copyDiff(){
196
+ var txt=document.getElementById("diffPanel").__summary||document.getElementById("diffText").textContent;
197
+ var done=function(){ setStatus("Summary copied — paste it into chat.","ok"); };
198
+ if(navigator.clipboard){ navigator.clipboard.writeText(txt).then(done).catch(fb); } else { fb(); }
199
+ function fb(){ var el=document.createElement("textarea"); el.value=txt; el.style.cssText="position:fixed;top:0;left:0;opacity:0;"; document.body.appendChild(el); el.select(); try{ document.execCommand("copy"); done(); }catch(e){} document.body.removeChild(el); }
200
+ }
201
+
202
+ window.onload=load;
203
+ </script>
204
+ </body>
205
+ </html>
@@ -0,0 +1,237 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Figures Editor</title>
6
+ <style>
7
+ *{box-sizing:border-box;margin:0;padding:0}
8
+ body{font-family:system-ui,-apple-system,sans-serif;background:#fff7ed;min-height:100vh;padding:20px 16px}
9
+ .card{background:#fff;max-width:900px;margin:0 auto;border-radius:10px;box-shadow:0 1px 4px rgba(0,0,0,.12);overflow:hidden}
10
+ .hdr{display:flex;align-items:center;gap:12px;padding:14px 20px;border-bottom:1px solid #fed7aa}
11
+ .hdr h1{font-size:15px;font-weight:600;color:#1e293b}.hdr .rev{font-size:12px;color:#94a3b8}
12
+ .hdr .toggle-all{margin-left:auto;flex-shrink:0}
13
+ .bdy{padding:20px}.sub{font-size:13px;color:#64748b;margin-bottom:14px}
14
+ input,select,textarea{font-family:inherit;font-size:13px;border:1.5px solid #fed7aa;border-radius:5px;padding:6px 10px;outline:none;width:100%;transition:border-color .15s;background:#fff}
15
+ input:focus,select:focus,textarea:focus{border-color:#ea580c}
16
+ .row2{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px}
17
+ .field{margin-bottom:12px}label{display:block;font-size:12px;font-weight:600;color:#374151;margin-bottom:4px}
18
+ .acc{border:1px solid #fed7aa;overflow:hidden;margin-bottom:-1px}
19
+ .acc-hd{display:flex;align-items:center;gap:10px;padding:11px 14px;cursor:pointer;background:#fffbf5;user-select:none}
20
+ .acc-hd:hover{background:#fff7ed}
21
+ .acc-hd .chev{color:#ea580c;font-size:12px;transition:transform .15s;flex-shrink:0}
22
+ .acc.open .chev{transform:rotate(90deg)}
23
+ .acc-hd .ttl{font-weight:600;font-size:13px;color:#1e293b;flex-shrink:0}
24
+ .acc-hd .badge{font-size:11px;font-weight:600;padding:1px 8px;border-radius:10px;background:#ffedd5;color:#c2410c;flex-shrink:0}
25
+ .acc-hd .prev{font-size:12px;color:#94a3b8;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
26
+ .acc-bd{display:none;padding:14px;border-top:1px solid #fed7aa;background:#fff}
27
+ .acc.open .acc-bd{display:block}
28
+ table{width:100%;border-collapse:collapse;margin-top:8px}
29
+ th,td{padding:6px 10px;border:1px solid #fed7aa;font-size:13px;text-align:left}
30
+ th{background:#fff7ed;font-weight:600;color:#374151}
31
+ td input{border:none;padding:0;width:100%;background:transparent}td input:focus{outline:1px solid #ea580c;border-radius:3px}
32
+ .tbl-bar{display:flex;justify-content:space-between;align-items:center;margin-top:8px}
33
+ .btn-sm{padding:4px 12px;background:#fff;border:1.5px solid #fed7aa;border-radius:5px;font-size:12px;cursor:pointer;color:#374151}
34
+ .btn-sm:hover{border-color:#ea580c}
35
+ .del-btn{color:#dc2626;background:none;border:none;cursor:pointer;font-size:16px;line-height:1;padding:0 4px}
36
+ .rm-item{margin-left:auto;color:#dc2626;background:none;border:none;cursor:pointer;font-size:13px;flex-shrink:0}
37
+ .bar{margin-top:16px;display:flex;align-items:center;gap:14px}
38
+ .cta{padding:9px 24px;background:#ea580c;color:#fff;border:none;border-radius:6px;font-size:14px;font-weight:600;cursor:pointer;transition:background .15s}
39
+ .cta:hover{background:#c2410c}.cta:disabled{opacity:.5;cursor:default}
40
+ #st{font-size:13px;font-weight:500}#st.ok{color:#16a34a}#st.err{color:#dc2626}#st.busy{color:#64748b}
41
+ .addbar{margin:6px 0 4px}
42
+ .diff{display:none;margin-top:16px;border:1px solid #fed7aa;border-radius:6px;overflow:hidden}
43
+ .diff.show{display:block}
44
+ .diff-hd{display:flex;align-items:center;gap:10px;padding:8px 12px;background:#fff7ed;font-size:12px;font-weight:600;color:#c2410c}
45
+ .diff pre{margin:0;padding:12px;font-size:12px;font-family:Menlo,Consolas,monospace;line-height:1.5;white-space:pre-wrap;max-height:240px;overflow:auto;color:#1e293b}
46
+ .ftr{padding:9px 20px;border-top:1px solid #fed7aa;font-size:11px;color:#94a3b8;display:flex;justify-content:space-between}
47
+ </style>
48
+ </head>
49
+ <body>
50
+ <div class="card">
51
+ <div class="hdr"><h1>Figures Editor</h1><span class="rev" id="revLbl"></span><button class="btn-sm toggle-all" id="toggleAll" onclick="toggleAll()" style="display:none">Expand all</button></div>
52
+ <div class="bdy">
53
+ <p class="sub" id="sub">Loading current figures from Fenix…</p>
54
+ <div id="acc"></div>
55
+ <div class="addbar"><button class="btn-sm" onclick="addItem()">+ Add figure</button></div>
56
+ <div class="bar"><button class="cta" id="saveBtn" onclick="save()" disabled>Save to Fenix</button><span id="st"></span></div>
57
+ <div class="diff" id="diffPanel">
58
+ <div class="diff-hd"><span>Saved. Paste this summary into chat so the assistant stays in sync:</span><button class="btn-sm" style="margin-left:auto" onclick="copyDiff()">Copy summary</button></div>
59
+ <pre id="diffText"></pre>
60
+ </div>
61
+ </div>
62
+ <div class="ftr"><span>Fenix Patent Assistant — live editor</span><span>v1</span></div>
63
+ </div>
64
+ <script>
65
+ // The assistant replaces __PROJECT_ID__ with the real Fenix project id before opening this artifact.
66
+ var PROJECT_ID = "__PROJECT_ID__";
67
+ // The assistant also substitutes this connector's MCP tool prefix here, e.g. "mcp__<connector-id>__"
68
+ // (the prefix differs per connection, so it cannot be hardcoded). If left unsubstituted, call()
69
+ // falls back to the bare tool name (which works on hosts that don't namespace MCP tools).
70
+ var SERVER_PREFIX = "__SERVER_PREFIX__";
71
+
72
+ var baseline = []; // figures last loaded from / saved to Fenix
73
+ var baselineRev = null;
74
+
75
+ function unwrap(res){
76
+ if(res==null) return res;
77
+ if(typeof res==="string"){ try{ return JSON.parse(res); }catch(e){ return res; } }
78
+ if(Array.isArray(res)) return res;
79
+ if(typeof res==="object" && Array.isArray(res.content)){
80
+ var t=res.content.map(function(c){ return (c&&c.text)||""; }).join("");
81
+ try{ return JSON.parse(t); }catch(e){ return res; }
82
+ }
83
+ if(res && res.result!==undefined) return unwrap(res.result);
84
+ return res;
85
+ }
86
+ function toolName(n){ return (SERVER_PREFIX && SERVER_PREFIX.indexOf("__SERVER")!==0) ? SERVER_PREFIX + n : n; }
87
+ async function call(name,args){
88
+ var tries=[toolName(name)]; if(tries[0]!==name) tries.push(name);
89
+ var err; for(var i=0;i<tries.length;i++){ try{ return unwrap(await window.cowork.callMcpTool(tries[i],args||{})); }catch(e){ err=e; } }
90
+ throw err;
91
+ }
92
+
93
+ function elt(tag,cls,txt){ var e=document.createElement(tag); if(cls)e.className=cls; if(txt!=null)e.textContent=txt; return e; }
94
+ function makeAcc(headerTitle,badge,preview,bodyBuilder){
95
+ var wrap=elt("div","acc"); var hd=elt("div","acc-hd");
96
+ hd.appendChild(elt("span","chev","▶")); hd.appendChild(elt("span","ttl",headerTitle));
97
+ if(badge!=null){ hd.appendChild(elt("span","badge",badge)); }
98
+ var prev=elt("span","prev",preview||""); hd.appendChild(prev);
99
+ var rm=elt("button","rm-item","Remove"); rm.onclick=function(ev){ ev.stopPropagation(); wrap.remove(); syncToggle(); }; hd.appendChild(rm);
100
+ hd.onclick=function(){ wrap.classList.toggle("open"); syncToggle(); };
101
+ var bd=elt("div","acc-bd"); bodyBuilder(bd,prev);
102
+ wrap.appendChild(hd); wrap.appendChild(bd); return wrap;
103
+ }
104
+ function figRow(tbody,el){
105
+ el=el||{};
106
+ var tr=document.createElement("tr");
107
+ tr.__el=el; // keep the full original element so undisplayed fields (angle, flipX/Y, action, etc.) survive save
108
+ var td0=document.createElement("td"); var i0=document.createElement("input"); i0.className="r-num"; i0.style.width="60px"; i0.value=el.number||""; td0.appendChild(i0); tr.appendChild(td0);
109
+ var td1=document.createElement("td"); var i1=document.createElement("input"); i1.className="r-lab"; i1.value=el.label||""; td1.appendChild(i1); tr.appendChild(td1);
110
+ var td2=document.createElement("td"); var i2=document.createElement("input"); i2.className="r-txt"; i2.value=el.text||""; td2.appendChild(i2); tr.appendChild(td2);
111
+ var td3=document.createElement("td"); var b=document.createElement("button"); b.className="del-btn"; b.textContent="x"; b.onclick=function(){ tr.remove(); }; td3.appendChild(b); tr.appendChild(td3);
112
+ tbody.appendChild(tr);
113
+ }
114
+ function figBody(bd,prev,fig){
115
+ var r=elt("div","row2");
116
+ var c1=elt("div","field"); c1.appendChild(elt("label",null,"Figure Number")); var nn=document.createElement("input"); nn.className="f-num"; nn.value=fig.number||""; c1.appendChild(nn); r.appendChild(c1);
117
+ var c2=elt("div","field"); c2.appendChild(elt("label",null,"Type")); var sel=document.createElement("select"); sel.className="f-type"; ["Flowchart","Device","Placeholder"].forEach(function(o){ var op=document.createElement("option"); op.textContent=o; sel.appendChild(op); }); sel.value=fig.type||"Flowchart"; c2.appendChild(sel); r.appendChild(c2); bd.appendChild(r);
118
+ var fb=elt("div","field"); fb.appendChild(elt("label",null,"Brief Description")); var bi=document.createElement("input"); bi.className="f-brief"; bi.value=fig.briefDescription||""; bi.oninput=function(){ prev.textContent=(bi.value||"").slice(0,70); }; fb.appendChild(bi); bd.appendChild(fb);
119
+ var lbl=elt("label",null,fig.type==="Device"?"Components":"Steps"); lbl.style.marginTop="6px"; bd.appendChild(lbl);
120
+ var tbl=document.createElement("table"); tbl.innerHTML="<thead><tr><th style='width:70px'>#</th><th style='width:170px'>Label</th><th>Description text</th><th style='width:34px'></th></tr></thead>"; var tbody=document.createElement("tbody"); tbl.appendChild(tbody); bd.appendChild(tbl);
121
+ var arr=(fig.type==="Device")?(fig.components||[]):(fig.steps||[]); arr.forEach(function(e){ figRow(tbody,e); });
122
+ var tb=elt("div","tbl-bar"); var ab=document.createElement("button"); ab.className="btn-sm"; ab.textContent="+ Add row"; ab.onclick=function(){ var rows=tbody.querySelectorAll("tr"); var last=rows.length?parseInt(rows[rows.length-1].querySelector(".r-num").value||"95"):95; figRow(tbody,{number:String(last+5)}); }; tb.appendChild(ab); tb.appendChild(elt("span",null,"")); bd.appendChild(tb);
123
+ }
124
+ function addFigure(fig){ fig=fig||{number:"",type:"Flowchart",briefDescription:"",steps:[],components:[]}; var acc=document.getElementById("acc"); var w=makeAcc("FIG. "+(fig.number||"?"),fig.type||"Flowchart",(fig.briefDescription||"").slice(0,70),function(bd,prev){ figBody(bd,prev,fig); }); w.__fig=fig; acc.appendChild(w); return w; }
125
+ function addItem(){ addFigure(null).classList.add("open"); syncToggle(); }
126
+ function syncToggle(){ var btn=document.getElementById("toggleAll"); var accs=document.querySelectorAll("#acc .acc"); if(!accs.length){ btn.style.display="none"; return; } btn.style.display=""; var anyClosed=Array.from(accs).some(function(a){ return !a.classList.contains("open"); }); btn.textContent=anyClosed?"Expand all":"Collapse all"; }
127
+ function toggleAll(){ var accs=document.querySelectorAll("#acc .acc"); var anyClosed=Array.from(accs).some(function(a){ return !a.classList.contains("open"); }); accs.forEach(function(a){ if(anyClosed){ a.classList.add("open"); } else { a.classList.remove("open"); } }); syncToggle(); }
128
+
129
+ function collect(){
130
+ var figs=[]; document.querySelectorAll("#acc .acc").forEach(function(a){
131
+ var type=a.querySelector(".f-type").value; var isDev=(type==="Device");
132
+ // Overlay the edited rows onto the ORIGINAL elements so undisplayed element fields
133
+ // (angle, flipX/flipY, action, name, etc.) are preserved -- not zeroed out.
134
+ var rows=Array.from(a.querySelectorAll("tbody tr")).map(function(tr){
135
+ var lab=tr.querySelector(".r-lab").value.trim();
136
+ return Object.assign({}, tr.__el||{}, {
137
+ type:isDev?"component":"step",
138
+ number:tr.querySelector(".r-num").value.trim(),
139
+ label:lab, text:tr.querySelector(".r-txt").value.trim(), name:lab,
140
+ });
141
+ });
142
+ // Start from the ORIGINAL figure so figure-level fields not shown in the editor
143
+ // (stencils, placeholders, configuration, the inactive steps/components array, etc.)
144
+ // survive. Only overwrite what the editor edits.
145
+ var fig=Object.assign({}, a.__fig||{}, {
146
+ number:a.querySelector(".f-num").value.trim(),
147
+ type:type,
148
+ briefDescription:a.querySelector(".f-brief").value.trim(),
149
+ });
150
+ fig[isDev?"components":"steps"]=rows; // edited active array; the other array stays as loaded
151
+ figs.push(fig);
152
+ }); return figs;
153
+ }
154
+
155
+ // ---- Diff by figure number ----
156
+ function rowsOf(f){ return (f.type==="Device"?f.components:f.steps)||[]; }
157
+ function figCanon(f){ return (f.type||"")+"||"+(f.briefDescription||"")+"||"+rowsOf(f).map(function(r){ return (r.number||"")+"|"+(r.label||"")+"|"+(r.text||""); }).join(";"); }
158
+ function byNum(arr){ var m={}; arr.forEach(function(f){ m[String(f.number)]=f; }); return m; }
159
+ function diff(oldArr,newArr){
160
+ var o=byNum(oldArr), n=byNum(newArr), changed=[], added=[], removed=[], unchanged=[];
161
+ Object.keys(n).forEach(function(k){ if(!(k in o)){ added.push(n[k]); } else if(figCanon(n[k])!==figCanon(o[k])){ changed.push(n[k]); } else { unchanged.push(k); } });
162
+ Object.keys(o).forEach(function(k){ if(!(k in n)) removed.push(k); });
163
+ function num(f){ return parseInt(f.number)||0; }
164
+ changed.sort(function(a,b){ return num(a)-num(b); }); added.sort(function(a,b){ return num(a)-num(b); });
165
+ removed.sort(function(a,b){ return (parseInt(a)||0)-(parseInt(b)||0); }); unchanged.sort(function(a,b){ return (parseInt(a)||0)-(parseInt(b)||0); });
166
+ return { changed:changed, added:added, removed:removed, unchanged:unchanged };
167
+ }
168
+ function figLines(f){ var L=["FIG. "+f.number+" ("+(f.type||"")+"): "+(f.briefDescription||"")]; rowsOf(f).forEach(function(r){ L.push(" "+(r.number||"")+" "+(r.label||"")+" — "+(r.text||"")); }); return L; }
169
+ function summaryText(d,rev){
170
+ var lines=["[FIGURES SAVED - figuresRev "+(rev==null?"?":rev)+"]"];
171
+ if(!d.changed.length && !d.added.length && !d.removed.length){ lines.push("No figure changes (re-saved identical set)."); return lines.join("\n"); }
172
+ if(d.changed.length){ lines.push(""); lines.push("Changed figures (full):"); d.changed.forEach(function(f){ lines=lines.concat(figLines(f)); }); }
173
+ if(d.added.length){ lines.push(""); lines.push("Added figures (full):"); d.added.forEach(function(f){ lines=lines.concat(figLines(f)); }); }
174
+ if(d.removed.length){ lines.push(""); lines.push("Removed figures: "+d.removed.join(", ")); }
175
+ if(d.unchanged.length){ lines.push(""); lines.push("Unchanged: "+d.unchanged.join(", ")); }
176
+ return lines.join("\n");
177
+ }
178
+
179
+ function setStatus(msg,cls){ var s=document.getElementById("st"); s.textContent=msg||""; s.className=cls||""; }
180
+
181
+ async function load(){
182
+ if(!(window.cowork && typeof window.cowork.callMcpTool==="function")){
183
+ document.getElementById("sub").textContent="";
184
+ setStatus("⚠ Open this as a LIVE ARTIFACT, not a file preview — it needs the Cowork bridge (window.cowork) to read and save Fenix data. Ask the assistant to open it as a live artifact.","err");
185
+ return;
186
+ }
187
+ try{
188
+ var data=await call("get_project",{ projectId:PROJECT_ID, include:["figures"] });
189
+ var figures=(data&&data.figures)||[];
190
+ baseline=JSON.parse(JSON.stringify(figures));
191
+ baselineRev=(data&&data.revs&&typeof data.revs.figures!=="undefined")?data.revs.figures:null;
192
+ document.getElementById("acc").innerHTML="";
193
+ figures.forEach(function(f){ addFigure(f); });
194
+ document.getElementById("sub").textContent="Open any figure to edit its details and steps/components. Add or remove as needed, then Save to Fenix.";
195
+ document.getElementById("revLbl").textContent=baselineRev==null?"":("rev "+baselineRev);
196
+ document.getElementById("saveBtn").disabled=false;
197
+ syncToggle();
198
+ }catch(e){
199
+ document.getElementById("sub").textContent="";
200
+ setStatus("Could not load figures: "+((e&&e.message)||e),"err");
201
+ }
202
+ }
203
+
204
+ async function save(){
205
+ var btn=document.getElementById("saveBtn"); btn.disabled=true; setStatus("Saving…","busy");
206
+ document.getElementById("diffPanel").classList.remove("show");
207
+ var figures=collect();
208
+ try{
209
+ var res=await call("save_project",Object.assign({ projectId:PROJECT_ID, type:"figures", figures:figures }, (baselineRev!=null?{baseRev:baselineRev}:{})));
210
+ if(res && res.conflict){ setStatus("⚠ Not saved — the figures changed in Fenix since you opened this editor (now at rev "+res.currentRev+"). Reload to get the latest, then re-apply your edits.","err"); return; }
211
+ if(res && res.isError){ throw new Error(res.error||"save failed"); }
212
+ var rev=(res&&typeof res.figuresRev!=="undefined")?res.figuresRev:null;
213
+ var d=diff(baseline,figures);
214
+ var summary=summaryText(d,rev);
215
+ baseline=JSON.parse(JSON.stringify(figures)); baselineRev=rev;
216
+ document.getElementById("revLbl").textContent=rev==null?"":("rev "+rev);
217
+ var nChanged=d.changed.length+d.added.length+d.removed.length;
218
+ setStatus("✓ Saved to Fenix"+(rev==null?"":" (rev "+rev+")")+(nChanged?" — "+nChanged+" figure(s) changed":""),"ok");
219
+ document.getElementById("diffText").textContent=summary;
220
+ document.getElementById("diffPanel").classList.add("show");
221
+ document.getElementById("diffPanel").__summary=summary;
222
+ try{ if(window.cowork && typeof window.cowork.sendPrompt==="function"){ window.cowork.sendPrompt(summary); } }catch(e){}
223
+ }catch(e){ setStatus("Save failed: "+((e&&e.message)||e),"err"); }
224
+ finally{ btn.disabled=false; }
225
+ }
226
+
227
+ function copyDiff(){
228
+ var txt=document.getElementById("diffPanel").__summary||document.getElementById("diffText").textContent;
229
+ var done=function(){ setStatus("Summary copied — paste it into chat.","ok"); };
230
+ if(navigator.clipboard){ navigator.clipboard.writeText(txt).then(done).catch(fb); } else { fb(); }
231
+ function fb(){ var el=document.createElement("textarea"); el.value=txt; el.style.cssText="position:fixed;top:0;left:0;opacity:0;"; document.body.appendChild(el); el.select(); try{ document.execCommand("copy"); done(); }catch(e){} document.body.removeChild(el); }
232
+ }
233
+
234
+ window.onload=load;
235
+ </script>
236
+ </body>
237
+ </html>