fenix-claude-plugin 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +13 -0
- package/.mcp.json +11 -0
- package/README.md +55 -0
- package/artifacts/practice-dashboard.html +469 -0
- package/artifacts/project-overview.html +481 -0
- package/artifacts/user-dashboard.html +368 -0
- package/commands/fenix-install.md +15 -0
- package/commands/fenix-setup.md +14 -0
- package/package.json +16 -0
- package/skills/fenix-application/CUSTOMIZE.md +1 -0
- package/skills/fenix-application/SKILL.md +95 -0
- package/skills/fenix-application/VERSION +1 -0
- package/skills/fenix-application/assets/claims_editor.html +87 -0
- package/skills/fenix-application/assets/figures_editor.html +87 -0
- package/skills/fenix-application/assets/spec_editor.html +87 -0
- package/skills/fenix-application/references/claims.md +30 -0
- package/skills/fenix-application/references/disclosure.md +33 -0
- package/skills/fenix-application/references/editors.md +52 -0
- package/skills/fenix-application/references/figure-description.md +67 -0
- package/skills/fenix-application/references/figures-guidance.md +122 -0
- package/skills/fenix-application/references/figures.md +28 -0
- package/skills/fenix-application/references/patent-review-advantages.md +35 -0
- package/skills/fenix-application/references/patent-review-checklist.md +36 -0
- package/skills/fenix-application/references/patent-review-claim-formats.md +71 -0
- package/skills/fenix-application/references/patent-review-profanity.md +57 -0
- package/skills/fenix-application/references/patent-review.md +82 -0
- package/skills/fenix-application/references/patent-safe.md +44 -0
- package/skills/fenix-application/references/patent.md +18 -0
- package/skills/fenix-application/references/spec-guidance.md +195 -0
- package/skills/fenix-application/references/spec.md +48 -0
- package/skills/fenix-application/scripts/calculate_figure_layout.py +32 -0
- package/skills/fenix-application/scripts/parse_claims.py +39 -0
- package/skills/fenix-office-action/CUSTOMIZE.md +1 -0
- package/skills/fenix-office-action/SKILL.md +40 -0
- package/skills/fenix-office-action/VERSION +1 -0
- package/skills/fenix-office-action/references/oa-response.md +10 -0
- package/skills/fenix-project/CUSTOMIZE.md +1 -0
- package/skills/fenix-project/SKILL.md +41 -0
- package/skills/fenix-project/VERSION +1 -0
- package/skills/fenix-project/references/create-project.md +10 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Project Overview</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root{
|
|
9
|
+
color-scheme: dark;
|
|
10
|
+
--bg:#0c0e13; --card:#14171f; --card2:#191d27; --ink:#c3c8d4; --muted:#7e8598;
|
|
11
|
+
--line:#242936; --line2:#2f3543;
|
|
12
|
+
--accent:#818cf8; --accent2:#6366f1; --accentbg:#1c2030; --accentglow:#2a2f4a;
|
|
13
|
+
--red:#f87171; --redbg:#2a1417; --orange:#fb923c; --orangebg:#2a1c10;
|
|
14
|
+
--amber:#fbbf24; --amberbg:#2a230f; --green:#34d399; --greenbg:#0f2a20;
|
|
15
|
+
--gray:#8b92a6; --graybg:#1b1f29; --purple:#a78bfa; --purplebg:#211a33;
|
|
16
|
+
}
|
|
17
|
+
*{box-sizing:border-box}
|
|
18
|
+
body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif}
|
|
19
|
+
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
|
|
20
|
+
.wrap{max-width:980px;margin:0 auto;padding:22px 20px 80px}
|
|
21
|
+
|
|
22
|
+
.top{display:flex;align-items:center;gap:13px;margin-bottom:18px}
|
|
23
|
+
.logo{display:inline-flex;align-items:center;justify-content:center;width:46px;height:46px;border-radius:11px;border:1px solid var(--line2);background:#0a0c11;line-height:0;flex-shrink:0}
|
|
24
|
+
.logo img{display:block;width:38px;height:38px;object-fit:contain}
|
|
25
|
+
h1{font-size:22px;margin:0;letter-spacing:-.02em;font-weight:800}
|
|
26
|
+
.sub{color:var(--muted);font-size:12.5px;margin-top:3px}
|
|
27
|
+
|
|
28
|
+
/* combobox */
|
|
29
|
+
.combo{position:relative;margin-bottom:20px;max-width:580px}
|
|
30
|
+
.combo input[type=text]{width:100%;padding:10px 34px 10px 13px;border:1px solid var(--line2);border-radius:9px;background:var(--card2);color:var(--ink);font-size:13px}
|
|
31
|
+
.combo input[type=text]::placeholder{color:var(--muted)}
|
|
32
|
+
.combo input[type=text]:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(129,140,248,.18)}
|
|
33
|
+
.combo .caret{position:absolute;right:13px;top:50%;transform:translateY(-50%);pointer-events:none;color:var(--muted);font-size:10px}
|
|
34
|
+
.combo-list{position:absolute;z-index:20;left:0;right:0;top:calc(100% + 5px);background:var(--card);border:1px solid var(--line2);border-radius:10px;box-shadow:0 14px 40px rgba(0,0,0,.45);max-height:340px;overflow-y:auto;padding:5px}
|
|
35
|
+
.combo-list[hidden]{display:none}
|
|
36
|
+
.combo-item{padding:9px 11px;border-radius:7px;font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
37
|
+
.combo-item:hover,.combo-item.active{background:var(--accentbg);color:var(--ink)}
|
|
38
|
+
.combo-empty{padding:11px;font-size:13px;color:var(--muted)}
|
|
39
|
+
|
|
40
|
+
/* KPI strip */
|
|
41
|
+
.kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:11px;margin-bottom:14px}
|
|
42
|
+
.kpi{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:15px 16px;position:relative;overflow:hidden}
|
|
43
|
+
.kpi .n{font-size:26px;font-weight:800;letter-spacing:-.03em;line-height:1.05;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
|
|
44
|
+
.kpi .l{color:var(--muted);font-size:11.5px;margin-top:7px}
|
|
45
|
+
.kpi.accent .n{color:var(--accent)}
|
|
46
|
+
.kpi.red{background:linear-gradient(180deg,var(--redbg),var(--card));border-color:#3a1f24}
|
|
47
|
+
.kpi.red .n{color:var(--red)}
|
|
48
|
+
.kpi.orange .n{color:var(--orange)}
|
|
49
|
+
.kpi.amber .n{color:var(--amber)}
|
|
50
|
+
.kpi.green .n{color:var(--green)}
|
|
51
|
+
.kpi .days{display:inline-block;margin-top:8px;padding:1px 8px;border-radius:6px;font-size:10.5px;font-weight:700}
|
|
52
|
+
|
|
53
|
+
/* panels */
|
|
54
|
+
.panel{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:18px;margin-bottom:14px}
|
|
55
|
+
.panel h3{margin:0 0 14px;font-size:11.5px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);font-weight:700}
|
|
56
|
+
.panel h3.mt{margin-top:18px}
|
|
57
|
+
|
|
58
|
+
.matter-title{font-size:19px;font-weight:800;letter-spacing:-.01em;margin:0 0 4px}
|
|
59
|
+
.matter-num{font-size:13px;color:var(--muted);margin:0 0 13px}
|
|
60
|
+
.pills{display:flex;gap:8px;flex-wrap:wrap}
|
|
61
|
+
.pill{display:inline-block;padding:2px 11px;border-radius:20px;font-size:11px;font-weight:700;white-space:nowrap;background:var(--graybg);color:var(--gray)}
|
|
62
|
+
.pill.type{background:var(--accentbg);color:var(--accent)}
|
|
63
|
+
|
|
64
|
+
.kv{display:grid;grid-template-columns:140px 1fr;gap:8px 16px;font-size:13px}
|
|
65
|
+
.kv .k{color:var(--muted)}
|
|
66
|
+
.kv .v{font-weight:600}
|
|
67
|
+
|
|
68
|
+
/* rejections — neutral badges, distinguished by the §number itself, not by rainbow color */
|
|
69
|
+
.rej{display:flex;align-items:flex-start;gap:13px;padding:11px 0;border-top:1px solid var(--line)}
|
|
70
|
+
.rej:first-child{border-top:none}
|
|
71
|
+
.statute{flex:0 0 auto;font-weight:800;font-size:12.5px;padding:5px 11px;border-radius:8px;min-width:62px;text-align:center;background:var(--card2);color:var(--accent);border:1px solid var(--line2);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
|
|
72
|
+
.rej .claims{font-size:13px;padding-top:2px}
|
|
73
|
+
.rej .claims .meta{color:var(--muted);font-size:11.5px;margin-top:3px}
|
|
74
|
+
|
|
75
|
+
/* timeline */
|
|
76
|
+
ol.timeline{list-style:none;margin:0;padding:0}
|
|
77
|
+
ol.timeline li{display:flex;align-items:center;gap:13px;padding:10px 0;border-top:1px solid var(--line)}
|
|
78
|
+
ol.timeline li:first-child{border-top:none}
|
|
79
|
+
.dot{flex:0 0 auto;width:11px;height:11px;border-radius:50%;background:var(--gray)}
|
|
80
|
+
.dot.active{background:var(--accent);box-shadow:0 0 8px var(--accent)}
|
|
81
|
+
.dot.done{background:var(--green)}
|
|
82
|
+
.tl-name{flex:1 1 auto;font-weight:600}
|
|
83
|
+
.tl-name .st{font-weight:500;color:var(--muted);font-size:12px;margin-left:8px;text-transform:capitalize}
|
|
84
|
+
.tl-date{flex:0 0 auto;font-size:12.5px;color:var(--muted)}
|
|
85
|
+
.tl-date.overdue{color:var(--red);font-weight:700}
|
|
86
|
+
|
|
87
|
+
/* claims / figures / sections */
|
|
88
|
+
.claim{padding:9px 0;border-top:1px solid var(--line);font-size:13px}
|
|
89
|
+
.claim:first-child{border-top:none}
|
|
90
|
+
.claim .cn{font-weight:800;margin-right:6px;color:var(--accent);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
|
|
91
|
+
.fig{padding:7px 0;font-size:13px}
|
|
92
|
+
.sec{padding:10px 0;border-top:1px solid var(--line)}
|
|
93
|
+
.sec:first-child{border-top:none}
|
|
94
|
+
.sec .sname{font-weight:700;font-size:13px;margin-bottom:4px}
|
|
95
|
+
.sec .stext{font-size:12.5px;color:var(--muted);white-space:pre-wrap;max-height:240px;overflow:auto;line-height:1.55}
|
|
96
|
+
|
|
97
|
+
.empty{color:var(--muted);font-style:italic;padding:9px 0}
|
|
98
|
+
.center{text-align:center;color:var(--muted);padding:60px 0}
|
|
99
|
+
.err{color:var(--red)}
|
|
100
|
+
.spin{width:26px;height:26px;border:3px solid var(--line2);border-top-color:var(--accent);border-radius:50%;animation:s .7s linear infinite;margin:0 auto 13px}
|
|
101
|
+
@keyframes s{to{transform:rotate(360deg)}}
|
|
102
|
+
a.dash{color:var(--accent);text-decoration:none;font-weight:700;font-size:12.5px}
|
|
103
|
+
a.dash:hover{text-decoration:underline}
|
|
104
|
+
@media(max-width:680px){ .kpis{grid-template-columns:repeat(2,1fr)} }
|
|
105
|
+
</style>
|
|
106
|
+
</head>
|
|
107
|
+
<body>
|
|
108
|
+
<div class="wrap">
|
|
109
|
+
<div class="top">
|
|
110
|
+
<span class="logo" role="img" aria-label="Fenix logo"><img src="data:image/png;base64,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" alt="Fenix logo" width="38" height="38"></span>
|
|
111
|
+
<div>
|
|
112
|
+
<h1>Project Overview</h1>
|
|
113
|
+
<div class="sub">Pick any active matter to see its full record — live from Fenix, refreshed each load.</div>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div class="combo">
|
|
118
|
+
<input type="text" id="combo" placeholder="Loading matters…" autocomplete="off" role="combobox" aria-expanded="false" aria-autocomplete="list" disabled>
|
|
119
|
+
<span class="caret">▼</span>
|
|
120
|
+
<div id="combolist" class="combo-list" role="listbox" hidden></div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div id="content">
|
|
124
|
+
<div class="center">Select a matter above to load its detail.</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<script>
|
|
129
|
+
const FENIX = {
|
|
130
|
+
active: "mcp__25de2133-8f0a-47c4-b66d-b520d6be5fe7__list_active_projects",
|
|
131
|
+
project: "mcp__25de2133-8f0a-47c4-b66d-b520d6be5fe7__get_project",
|
|
132
|
+
tasks: "mcp__25de2133-8f0a-47c4-b66d-b520d6be5fe7__list_project_tasks",
|
|
133
|
+
};
|
|
134
|
+
const LS_KEY = "fenix_deepview_last";
|
|
135
|
+
let ALL = [];
|
|
136
|
+
|
|
137
|
+
// ---- helpers ----
|
|
138
|
+
function unwrap(res) {
|
|
139
|
+
if (res == null) return null;
|
|
140
|
+
if (typeof res === "string") { try { return JSON.parse(res); } catch (e) { return res; } }
|
|
141
|
+
if (Array.isArray(res)) return res;
|
|
142
|
+
if (typeof res === "object" && Array.isArray(res.content)) {
|
|
143
|
+
const t = res.content.map(c => (c && c.text) || "").join("");
|
|
144
|
+
try { return JSON.parse(t); } catch (e) { return res; }
|
|
145
|
+
}
|
|
146
|
+
if (res.result !== undefined) return unwrap(res.result);
|
|
147
|
+
return res;
|
|
148
|
+
}
|
|
149
|
+
async function call(name, args) {
|
|
150
|
+
const r = await window.cowork.callMcpTool(name, args || {});
|
|
151
|
+
return unwrap(r);
|
|
152
|
+
}
|
|
153
|
+
function esc(s) {
|
|
154
|
+
return String(s == null ? "" : s).replace(/[&<>"]/g, c => ({"&":"&","<":"<",">":">","\"":"""}[c]));
|
|
155
|
+
}
|
|
156
|
+
const today = (() => { const n = new Date(); return new Date(n.getFullYear(), n.getMonth(), n.getDate()); })();
|
|
157
|
+
function pd(raw) {
|
|
158
|
+
if (!raw) return null;
|
|
159
|
+
let s = String(raw).trim(); if (!s) return null;
|
|
160
|
+
s = s.replace(/(\d+)(st|nd|rd|th)/gi, "$1");
|
|
161
|
+
let iso = s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})/);
|
|
162
|
+
if (iso) return new Date(+iso[1], +iso[2] - 1, +iso[3]);
|
|
163
|
+
let mdy = s.match(/^(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2,4})$/);
|
|
164
|
+
if (mdy) { let mo = +mdy[1], d = +mdy[2], y = +mdy[3]; if (y < 100) y += 2000; return new Date(y, mo - 1, d); }
|
|
165
|
+
let t = Date.parse(s); if (isNaN(t)) return null;
|
|
166
|
+
const dt = new Date(t); return new Date(dt.getFullYear(), dt.getMonth(), dt.getDate());
|
|
167
|
+
}
|
|
168
|
+
function dleft(d) { return d ? Math.round((d - today) / 86400000) : null; }
|
|
169
|
+
function fmt(d) { return d ? d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }) : "—"; }
|
|
170
|
+
function urg(dl) {
|
|
171
|
+
if (dl === null) return { bg: "var(--graybg)", fg: "var(--gray)", lab: "No date", kpi: "" };
|
|
172
|
+
if (dl < 0) return { bg: "var(--redbg)", fg: "var(--red)", lab: Math.abs(dl) + "d overdue", kpi: "red" };
|
|
173
|
+
if (dl <= 7) return { bg: "var(--redbg)", fg: "var(--red)", lab: dl + "d left", kpi: "red" };
|
|
174
|
+
if (dl <= 21) return { bg: "var(--orangebg)", fg: "var(--orange)", lab: dl + "d left", kpi: "orange" };
|
|
175
|
+
if (dl <= 45) return { bg: "var(--amberbg)", fg: "var(--amber)", lab: dl + "d left", kpi: "amber" };
|
|
176
|
+
return { bg: "var(--greenbg)", fg: "var(--green)", lab: dl + "d left", kpi: "green" };
|
|
177
|
+
}
|
|
178
|
+
function nonEmpty(v) {
|
|
179
|
+
if (v == null) return false;
|
|
180
|
+
if (Array.isArray(v)) return v.length > 0;
|
|
181
|
+
if (typeof v === "object") return Object.keys(v).length > 0;
|
|
182
|
+
return String(v).trim() !== "";
|
|
183
|
+
}
|
|
184
|
+
function claimList(arr) {
|
|
185
|
+
if (!nonEmpty(arr)) return "—";
|
|
186
|
+
if (Array.isArray(arr)) return '<span class="mono">' + esc(arr.join(", ")) + "</span>";
|
|
187
|
+
return '<span class="mono">' + esc(arr) + "</span>";
|
|
188
|
+
}
|
|
189
|
+
function statusPill(s) {
|
|
190
|
+
const l = (s || "").toLowerCase();
|
|
191
|
+
let bg = "var(--graybg)", fg = "var(--gray)", t = s || "—";
|
|
192
|
+
if (l === "active") { bg = "var(--accentbg)"; fg = "var(--accent)"; t = "Active"; }
|
|
193
|
+
else if (l === "in-progress") { bg = "var(--amberbg)"; fg = "var(--amber)"; t = "In progress"; }
|
|
194
|
+
else if (l === "completed") { bg = "var(--greenbg)"; fg = "var(--green)"; t = "Completed"; }
|
|
195
|
+
return '<span class="pill" style="background:' + bg + ';color:' + fg + '">' + esc(t) + "</span>";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ---- combobox ----
|
|
199
|
+
const combo = document.getElementById("combo");
|
|
200
|
+
const combolist = document.getElementById("combolist");
|
|
201
|
+
let comboMatches = [];
|
|
202
|
+
let activeIdx = -1;
|
|
203
|
+
let selectedId = "";
|
|
204
|
+
let lastTyped = "";
|
|
205
|
+
|
|
206
|
+
function openList() { combolist.hidden = false; combo.setAttribute("aria-expanded", "true"); }
|
|
207
|
+
function closeList() { combolist.hidden = true; combo.setAttribute("aria-expanded", "false"); activeIdx = -1; }
|
|
208
|
+
function projLabel(p) {
|
|
209
|
+
const mn = (p.matterNum && p.matterNum !== "Default Matter") ? p.matterNum : ("(" + p.projectId.slice(-6) + ")");
|
|
210
|
+
const ty = p.project_type ? " — " + p.project_type : "";
|
|
211
|
+
const cl = (p.clientName && p.clientName !== "Default Client") ? " · " + p.clientName : "";
|
|
212
|
+
return mn + ty + cl;
|
|
213
|
+
}
|
|
214
|
+
function rankType(t) {
|
|
215
|
+
t = (t || "").toLowerCase();
|
|
216
|
+
if (t.includes("office action") || t === "oa" || t === "rtoa") return 0;
|
|
217
|
+
return 1;
|
|
218
|
+
}
|
|
219
|
+
function matchesFor(f) {
|
|
220
|
+
f = (f || "").toLowerCase().trim();
|
|
221
|
+
if (!f) return ALL.slice();
|
|
222
|
+
return ALL.filter(p => projLabel(p).toLowerCase().includes(f));
|
|
223
|
+
}
|
|
224
|
+
function renderList(filter) {
|
|
225
|
+
comboMatches = matchesFor(filter);
|
|
226
|
+
combolist.innerHTML = "";
|
|
227
|
+
if (!comboMatches.length) {
|
|
228
|
+
const d = document.createElement("div");
|
|
229
|
+
d.className = "combo-empty"; d.textContent = "No matching matters";
|
|
230
|
+
combolist.appendChild(d);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
comboMatches.forEach((p, i) => {
|
|
234
|
+
const d = document.createElement("div");
|
|
235
|
+
d.className = "combo-item" + (i === activeIdx ? " active" : "");
|
|
236
|
+
d.setAttribute("role", "option");
|
|
237
|
+
d.textContent = projLabel(p);
|
|
238
|
+
d.addEventListener("mousedown", ev => { ev.preventDefault(); choose(p); });
|
|
239
|
+
combolist.appendChild(d);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function setActive(i) {
|
|
243
|
+
const items = combolist.querySelectorAll(".combo-item");
|
|
244
|
+
if (!items.length) { activeIdx = -1; return; }
|
|
245
|
+
activeIdx = (i + items.length) % items.length;
|
|
246
|
+
items.forEach((el, idx) => el.classList.toggle("active", idx === activeIdx));
|
|
247
|
+
items[activeIdx].scrollIntoView({ block: "nearest" });
|
|
248
|
+
}
|
|
249
|
+
function choose(p) {
|
|
250
|
+
selectedId = p.projectId;
|
|
251
|
+
combo.value = projLabel(p);
|
|
252
|
+
lastTyped = combo.value;
|
|
253
|
+
closeList();
|
|
254
|
+
loadProject(p.projectId);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function loadPicker() {
|
|
258
|
+
try {
|
|
259
|
+
const raw = await call(FENIX.active, {});
|
|
260
|
+
const arr = Array.isArray(raw) ? raw : (raw && Array.isArray(raw.projects) ? raw.projects : []);
|
|
261
|
+
ALL = arr.filter(p => p && p.projectId);
|
|
262
|
+
ALL.sort((a, b) => {
|
|
263
|
+
const rt = rankType(a.project_type) - rankType(b.project_type);
|
|
264
|
+
if (rt) return rt;
|
|
265
|
+
const da = pd(a.due_date), db = pd(b.due_date);
|
|
266
|
+
if (da && db) return da - db;
|
|
267
|
+
if (da) return -1;
|
|
268
|
+
if (db) return 1;
|
|
269
|
+
return 0;
|
|
270
|
+
});
|
|
271
|
+
combo.disabled = false;
|
|
272
|
+
combo.placeholder = "Search " + ALL.length + " matters…";
|
|
273
|
+
renderList("");
|
|
274
|
+
const last = localStorage.getItem(LS_KEY);
|
|
275
|
+
const lp = last && ALL.find(p => p.projectId === last);
|
|
276
|
+
if (lp) { selectedId = lp.projectId; combo.value = projLabel(lp); lastTyped = combo.value; loadProject(lp.projectId); }
|
|
277
|
+
} catch (e) {
|
|
278
|
+
combo.placeholder = "Failed to load matters";
|
|
279
|
+
document.getElementById("content").innerHTML = '<div class="center err">Could not load the matter list: ' + esc(e.message || e) + "</div>";
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ---- detail ----
|
|
284
|
+
function kpiStrip(proj, full) {
|
|
285
|
+
const oa = proj.oa_data || {};
|
|
286
|
+
const d = pd(proj.due_date);
|
|
287
|
+
const dl = dleft(d);
|
|
288
|
+
const u = urg(dl);
|
|
289
|
+
let cards = "";
|
|
290
|
+
cards += '<div class="kpi ' + (u.kpi || "accent") + '"><div class="n">' + fmt(d) + '</div><div class="l">Due date</div>'
|
|
291
|
+
+ (dl !== null ? '<div class="days" style="background:' + u.bg + ';color:' + u.fg + '">' + u.lab + "</div>" : "") + "</div>";
|
|
292
|
+
const totalClaims = oa.total_number_of_claims || full.claimCount;
|
|
293
|
+
if (nonEmpty(totalClaims) && String(totalClaims) !== "0")
|
|
294
|
+
cards += '<div class="kpi"><div class="n">' + esc(totalClaims) + '</div><div class="l">Total claims</div></div>';
|
|
295
|
+
const indep = full.independentClaimCount || (oa.independent_claims || []).length;
|
|
296
|
+
if (indep) cards += '<div class="kpi accent"><div class="n">' + esc(indep) + '</div><div class="l">Independent claims</div></div>';
|
|
297
|
+
if (full.figureCount) cards += '<div class="kpi"><div class="n">' + esc(full.figureCount) + '</div><div class="l">Figures</div></div>';
|
|
298
|
+
return '<div class="kpis">' + cards + "</div>";
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function headerPanel(proj, matter) {
|
|
302
|
+
let fields = "";
|
|
303
|
+
const K = (k, v) => '<div class="k">' + k + '</div><div class="v">' + v + "</div>";
|
|
304
|
+
if (nonEmpty(matter.applicationNum)) fields += K("Application No.", '<span class="mono">' + esc(matter.applicationNum) + "</span>");
|
|
305
|
+
if (nonEmpty(matter.filingDate)) fields += K("Filed", '<span class="mono">' + fmt(pd(matter.filingDate)) + "</span>");
|
|
306
|
+
if (nonEmpty(proj.assign_date)) fields += K("Assigned", '<span class="mono">' + fmt(pd(proj.assign_date)) + "</span>");
|
|
307
|
+
|
|
308
|
+
return '<div class="panel">'
|
|
309
|
+
+ '<div class="matter-title">' + esc(matter.inventionTitle || "Untitled matter") + "</div>"
|
|
310
|
+
+ '<div class="matter-num mono">' + esc(matter.matterNum || "") + "</div>"
|
|
311
|
+
+ '<div class="pills">'
|
|
312
|
+
+ (proj.project_type ? '<span class="pill type">' + esc(proj.project_type) + "</span>" : "")
|
|
313
|
+
+ ((matter.clientName || proj.clientName) ? '<span class="pill">' + esc(matter.clientName || proj.clientName) + "</span>" : "")
|
|
314
|
+
+ (proj.status ? statusPill(proj.status) : "")
|
|
315
|
+
+ "</div>"
|
|
316
|
+
+ (fields ? '<div class="kv" style="margin-top:15px">' + fields + "</div>" : "")
|
|
317
|
+
+ "</div>";
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function oaCard(oa) {
|
|
321
|
+
let html = '<div class="panel"><h3>Office Action</h3><div class="kv">';
|
|
322
|
+
if (nonEmpty(oa.oa_date)) html += '<div class="k">OA date</div><div class="v mono">' + fmt(pd(oa.oa_date)) + "</div>";
|
|
323
|
+
if (nonEmpty(oa.oa_type)) html += '<div class="k">OA type</div><div class="v">' + esc(oa.oa_type) + "</div>";
|
|
324
|
+
if (nonEmpty(oa.response_type)) html += '<div class="k">Response</div><div class="v">' + esc(oa.response_type) + "</div>";
|
|
325
|
+
if (nonEmpty(oa.applicant)) html += '<div class="k">Applicant</div><div class="v">' + esc(oa.applicant) + "</div>";
|
|
326
|
+
if (nonEmpty(oa.examiner_last)) html += '<div class="k">Examiner</div><div class="v">' + esc(oa.examiner_last) + "</div>";
|
|
327
|
+
if (nonEmpty(oa.independent_claims)) html += '<div class="k">Indep. claims</div><div class="v">' + claimList(oa.independent_claims) + "</div>";
|
|
328
|
+
html += "</div>";
|
|
329
|
+
|
|
330
|
+
const rej = oa.oa_rejections || [];
|
|
331
|
+
html += '<h3 class="mt">Rejections</h3>';
|
|
332
|
+
if (nonEmpty(rej)) {
|
|
333
|
+
rej.forEach(r => {
|
|
334
|
+
const refs = (r.status_refs || []).length;
|
|
335
|
+
html += '<div class="rej">'
|
|
336
|
+
+ '<span class="statute">§' + esc(r.status_type) + "</span>"
|
|
337
|
+
+ '<div class="claims"><div>Claims ' + claimList(r.status_claims) + "</div>"
|
|
338
|
+
+ (refs ? '<div class="meta">' + refs + " cited reference" + (refs > 1 ? "s" : "") + "</div>" : "")
|
|
339
|
+
+ "</div></div>";
|
|
340
|
+
});
|
|
341
|
+
} else {
|
|
342
|
+
html += '<div class="empty">No rejections recorded.</div>';
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const am = oa.amendments || {};
|
|
346
|
+
if (nonEmpty(am.amend) || nonEmpty(am.canx) || nonEmpty(am.added) || nonEmpty(am.pending_claims)) {
|
|
347
|
+
html += '<h3 class="mt">Amendment plan</h3><div class="kv">';
|
|
348
|
+
if (nonEmpty(am.amend)) html += '<div class="k">Amend</div><div class="v">' + claimList(am.amend) + "</div>";
|
|
349
|
+
if (nonEmpty(am.canx)) html += '<div class="k">Cancel</div><div class="v">' + claimList(am.canx) + "</div>";
|
|
350
|
+
if (nonEmpty(am.added)) html += '<div class="k">Add</div><div class="v">' + claimList(am.added) + "</div>";
|
|
351
|
+
if (nonEmpty(am.pending_claims)) html += '<div class="k">Pending</div><div class="v">' + claimList(am.pending_claims) + "</div>";
|
|
352
|
+
if (nonEmpty(am.pending_claims_ind)) html += '<div class="k">Pending indep.</div><div class="v">' + claimList(am.pending_claims_ind) + "</div>";
|
|
353
|
+
html += "</div>";
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const cd = oa.claim_dispositions || {};
|
|
357
|
+
const dispRows = [["allowed_claims","Allowed"],["rejected_claims","Rejected"],["objected_claims","Objected"],["withdrawn_claims","Withdrawn"],["restriction_or_election","Restriction/election"]]
|
|
358
|
+
.filter(function(row) { return nonEmpty(cd[row[0]]); });
|
|
359
|
+
if (dispRows.length) {
|
|
360
|
+
html += '<h3 class="mt">Claim dispositions</h3><div class="kv">';
|
|
361
|
+
dispRows.forEach(function(row) { html += '<div class="k">' + row[1] + '</div><div class="v">' + claimList(cd[row[0]]) + "</div>"; });
|
|
362
|
+
html += "</div>";
|
|
363
|
+
}
|
|
364
|
+
html += "</div>";
|
|
365
|
+
return html;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function patentCard(full) {
|
|
369
|
+
const claims = (full.parsedClaims && full.parsedClaims.length) ? full.parsedClaims
|
|
370
|
+
: (Array.isArray(full.claims) ? full.claims : []);
|
|
371
|
+
const figs = Array.isArray(full.figures) ? full.figures : [];
|
|
372
|
+
const sections = full.sections && typeof full.sections === "object" ? full.sections : {};
|
|
373
|
+
const secNames = full.sectionNames && full.sectionNames.length ? full.sectionNames : Object.keys(sections);
|
|
374
|
+
|
|
375
|
+
let html = "";
|
|
376
|
+
if (claims.length) {
|
|
377
|
+
html += '<div class="panel"><h3>Claims (' + claims.length + ")</h3>";
|
|
378
|
+
claims.forEach((c, i) => {
|
|
379
|
+
let num, text;
|
|
380
|
+
if (typeof c === "string") { num = i + 1; text = c; }
|
|
381
|
+
else { num = c.number || c.num || (i + 1); text = c.text || c.claimText || c.body || JSON.stringify(c); }
|
|
382
|
+
html += '<div class="claim"><span class="cn">' + esc(num) + ".</span>" + esc(text) + "</div>";
|
|
383
|
+
});
|
|
384
|
+
html += "</div>";
|
|
385
|
+
}
|
|
386
|
+
if (figs.length) {
|
|
387
|
+
html += '<div class="panel"><h3>Figures (' + figs.length + ")</h3>";
|
|
388
|
+
figs.forEach((f, i) => {
|
|
389
|
+
let label, desc;
|
|
390
|
+
if (typeof f === "string") { label = "Fig. " + (i + 1); desc = f; }
|
|
391
|
+
else { label = f.label || f.name || ("Fig. " + (f.number || i + 1)); desc = f.description || f.caption || ""; }
|
|
392
|
+
html += '<div class="fig"><strong>' + esc(label) + "</strong>" + (desc ? " — " + esc(desc) : "") + "</div>";
|
|
393
|
+
});
|
|
394
|
+
html += "</div>";
|
|
395
|
+
}
|
|
396
|
+
if (secNames.length) {
|
|
397
|
+
html += '<div class="panel"><h3>Specification sections</h3>';
|
|
398
|
+
secNames.forEach(n => {
|
|
399
|
+
const v = sections[n];
|
|
400
|
+
const txt = typeof v === "string" ? v : (v && v.text ? v.text : JSON.stringify(v));
|
|
401
|
+
html += '<div class="sec"><div class="sname">' + esc(n) + '</div><div class="stext">' + esc(txt) + "</div></div>";
|
|
402
|
+
});
|
|
403
|
+
html += "</div>";
|
|
404
|
+
}
|
|
405
|
+
return html;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function timelineCard(tasks) {
|
|
409
|
+
if (!nonEmpty(tasks)) return "";
|
|
410
|
+
let html = '<div class="panel"><h3>Milestones</h3><ol class="timeline">';
|
|
411
|
+
tasks.forEach(t => {
|
|
412
|
+
const st = (t.status || "").toLowerCase();
|
|
413
|
+
let dotCls = "dot", stLabel = t.status || "";
|
|
414
|
+
if (st === "completed" || st === "complete" || st === "done") { dotCls += " done"; }
|
|
415
|
+
else if (st === "active") { dotCls += " active"; }
|
|
416
|
+
const d = pd(t.dueDate);
|
|
417
|
+
const dl = dleft(d);
|
|
418
|
+
const open = !(st === "completed" || st === "complete" || st === "done");
|
|
419
|
+
const overdue = open && dl != null && dl < 0;
|
|
420
|
+
html += "<li>"
|
|
421
|
+
+ '<span class="' + dotCls + '"></span>'
|
|
422
|
+
+ '<span class="tl-name">' + esc(t.name || "Task") + (stLabel ? '<span class="st">' + esc(stLabel) + "</span>" : "") + "</span>"
|
|
423
|
+
+ '<span class="tl-date mono' + (overdue ? " overdue" : "") + '">' + fmt(d) + "</span>"
|
|
424
|
+
+ "</li>";
|
|
425
|
+
});
|
|
426
|
+
html += "</ol></div>";
|
|
427
|
+
return html;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async function loadProject(pid) {
|
|
431
|
+
const c = document.getElementById("content");
|
|
432
|
+
c.innerHTML = '<div class="center"><div class="spin"></div>Loading matter…</div>';
|
|
433
|
+
localStorage.setItem(LS_KEY, pid);
|
|
434
|
+
try {
|
|
435
|
+
const [full, tasksRaw] = await Promise.all([
|
|
436
|
+
call(FENIX.project, { projectId: pid }).catch(() => null),
|
|
437
|
+
call(FENIX.tasks, { projectId: pid }).catch(() => null),
|
|
438
|
+
]);
|
|
439
|
+
if (!full) { c.innerHTML = '<div class="center err">No data returned for this matter.</div>'; return; }
|
|
440
|
+
const proj = full.project || {};
|
|
441
|
+
const matter = full.matter || {};
|
|
442
|
+
const oa = proj.oa_data || {};
|
|
443
|
+
const tasks = (tasksRaw && Array.isArray(tasksRaw.tasks)) ? tasksRaw.tasks : (Array.isArray(tasksRaw) ? tasksRaw : []);
|
|
444
|
+
const isOA = nonEmpty(oa.oa_date) || nonEmpty(oa.oa_rejections) || /office action|^oa$|rtoa/i.test(proj.project_type || "");
|
|
445
|
+
|
|
446
|
+
let html = headerPanel(proj, matter);
|
|
447
|
+
html += kpiStrip(proj, full);
|
|
448
|
+
if (isOA) html += oaCard(oa);
|
|
449
|
+
html += patentCard(full);
|
|
450
|
+
html += timelineCard(tasks);
|
|
451
|
+
if (nonEmpty(proj.notes)) html += '<div class="panel"><h3>Notes</h3><div class="stext">' + esc(proj.notes) + "</div></div>";
|
|
452
|
+
|
|
453
|
+
const dash = full.dashboardUrl || (tasksRaw && tasksRaw.dashboardUrl);
|
|
454
|
+
if (dash) html += '<div style="margin-top:4px"><a class="dash" href="' + esc(dash) + '" target="_blank" rel="noopener">Open in Fenix dashboard →</a></div>';
|
|
455
|
+
|
|
456
|
+
c.innerHTML = html;
|
|
457
|
+
} catch (e) {
|
|
458
|
+
c.innerHTML = '<div class="center err">Failed to load matter: ' + esc(e.message || e) + "</div>";
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// ---- wire up ----
|
|
463
|
+
combo.addEventListener("focus", () => { combo.select(); activeIdx = -1; renderList(""); openList(); });
|
|
464
|
+
combo.addEventListener("input", e => { lastTyped = e.target.value; activeIdx = -1; renderList(e.target.value); openList(); });
|
|
465
|
+
combo.addEventListener("keydown", e => {
|
|
466
|
+
if (e.key === "ArrowDown") { e.preventDefault(); if (combolist.hidden) { renderList(lastTyped); openList(); } setActive(activeIdx + 1); }
|
|
467
|
+
else if (e.key === "ArrowUp") { e.preventDefault(); setActive(activeIdx - 1); }
|
|
468
|
+
else if (e.key === "Enter") { if (activeIdx >= 0 && comboMatches[activeIdx]) { e.preventDefault(); choose(comboMatches[activeIdx]); } }
|
|
469
|
+
else if (e.key === "Escape") { closeList(); combo.blur(); }
|
|
470
|
+
});
|
|
471
|
+
combo.addEventListener("blur", () => {
|
|
472
|
+
const sel = ALL.find(p => p.projectId === selectedId);
|
|
473
|
+
if (sel && combo.value !== projLabel(sel)) combo.value = projLabel(sel);
|
|
474
|
+
});
|
|
475
|
+
document.addEventListener("mousedown", e => {
|
|
476
|
+
if (!combolist.hidden && !e.target.closest(".combo")) closeList();
|
|
477
|
+
});
|
|
478
|
+
loadPicker();
|
|
479
|
+
</script>
|
|
480
|
+
</body>
|
|
481
|
+
</html>
|