@soddong/agentic-application-methodology-workbench 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/CHANGELOG.md +15 -0
- package/README.md +115 -0
- package/dist/artifact-standard-document-loader.d.ts +35 -0
- package/dist/artifact-standard-document-loader.d.ts.map +1 -0
- package/dist/artifact-standard-document-loader.js +363 -0
- package/dist/artifact-standard-document-loader.js.map +1 -0
- package/dist/authoring-draft-store.d.ts +8 -0
- package/dist/authoring-draft-store.d.ts.map +1 -0
- package/dist/authoring-draft-store.js +232 -0
- package/dist/authoring-draft-store.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +54 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/seed-view-model-loader.d.ts +9 -0
- package/dist/seed-view-model-loader.d.ts.map +1 -0
- package/dist/seed-view-model-loader.js +359 -0
- package/dist/seed-view-model-loader.js.map +1 -0
- package/dist/server/workbench-server.d.ts +22 -0
- package/dist/server/workbench-server.d.ts.map +1 -0
- package/dist/server/workbench-server.js +1553 -0
- package/dist/server/workbench-server.js.map +1 -0
- package/dist/task-authoring-source.d.ts +9 -0
- package/dist/task-authoring-source.d.ts.map +1 -0
- package/dist/task-authoring-source.js +163 -0
- package/dist/task-authoring-source.js.map +1 -0
- package/dist/view-models.d.ts +211 -0
- package/dist/view-models.d.ts.map +1 -0
- package/dist/view-models.js +2 -0
- package/dist/view-models.js.map +1 -0
- package/dist/workbench-config.d.ts +37 -0
- package/dist/workbench-config.d.ts.map +1 -0
- package/dist/workbench-config.js +110 -0
- package/dist/workbench-config.js.map +1 -0
- package/docs/build.md +14 -0
- package/docs/configuration.md +65 -0
- package/docs/delivery.md +15 -0
- package/docs/operations.md +28 -0
- package/docs/publishing.md +19 -0
- package/docs/usage.md +113 -0
- package/package.json +26 -0
- package/schemas/workbench-config.schema.json +112 -0
|
@@ -0,0 +1,1553 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { applyStepDraftToSeed, loadActivityAuthoring, planStepDraftApply, saveAuthoringDraft } from "../authoring-draft-store.js";
|
|
3
|
+
import { loadArtifactStandardDocuments } from "../artifact-standard-document-loader.js";
|
|
4
|
+
import { loadWorkbenchConfig } from "../workbench-config.js";
|
|
5
|
+
import { loadActivityWorkbench, loadArtifactStandard, loadGovernance, loadLifecycleOverview, loadPhaseDetail, loadWorkbenchSummary } from "../seed-view-model-loader.js";
|
|
6
|
+
function jsonPayload(value, status = 200) {
|
|
7
|
+
return {
|
|
8
|
+
status,
|
|
9
|
+
contentType: "application/json; charset=utf-8",
|
|
10
|
+
body: JSON.stringify(value, null, 2)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function textPayload(body, contentType, status = 200) {
|
|
14
|
+
return {
|
|
15
|
+
status,
|
|
16
|
+
contentType,
|
|
17
|
+
body
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async function readRequestJson(request) {
|
|
21
|
+
const chunks = [];
|
|
22
|
+
for await (const chunk of request) {
|
|
23
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
24
|
+
}
|
|
25
|
+
const body = Buffer.concat(chunks).toString("utf8").trim();
|
|
26
|
+
return body.length === 0 ? {} : JSON.parse(body);
|
|
27
|
+
}
|
|
28
|
+
async function routeRequest(request, project) {
|
|
29
|
+
const requestUrl = new URL(request.url ?? "/", "http://localhost");
|
|
30
|
+
if (requestUrl.pathname === "/") {
|
|
31
|
+
return textPayload(renderWorkbenchHtml(project), "text/html; charset=utf-8");
|
|
32
|
+
}
|
|
33
|
+
if (requestUrl.pathname === "/assets/app.css") {
|
|
34
|
+
return textPayload(renderWorkbenchCss(), "text/css; charset=utf-8");
|
|
35
|
+
}
|
|
36
|
+
if (requestUrl.pathname === "/assets/app.js") {
|
|
37
|
+
return textPayload(renderWorkbenchJs(project), "text/javascript; charset=utf-8");
|
|
38
|
+
}
|
|
39
|
+
if (requestUrl.pathname === "/api/health") {
|
|
40
|
+
return jsonPayload({
|
|
41
|
+
status: "ok",
|
|
42
|
+
packageName: "@soddong/agentic-application-methodology-workbench",
|
|
43
|
+
version: "0.1.0",
|
|
44
|
+
projectRoot: project.projectRoot
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (requestUrl.pathname === "/api/workbench/config") {
|
|
48
|
+
return jsonPayload(project);
|
|
49
|
+
}
|
|
50
|
+
if (requestUrl.pathname === "/api/workbench/overview") {
|
|
51
|
+
return jsonPayload(loadWorkbenchSummary(project));
|
|
52
|
+
}
|
|
53
|
+
if (requestUrl.pathname === "/api/workbench/lifecycle") {
|
|
54
|
+
return jsonPayload(loadLifecycleOverview(project));
|
|
55
|
+
}
|
|
56
|
+
if (requestUrl.pathname.startsWith("/api/workbench/phases/")) {
|
|
57
|
+
return jsonPayload(loadPhaseDetail(project, decodeURIComponent(requestUrl.pathname.split("/").at(-1) ?? "")));
|
|
58
|
+
}
|
|
59
|
+
if (requestUrl.pathname === "/api/workbench/phases") {
|
|
60
|
+
return jsonPayload(loadPhaseDetail(project));
|
|
61
|
+
}
|
|
62
|
+
if (requestUrl.pathname.startsWith("/api/workbench/activities/")) {
|
|
63
|
+
const pathParts = requestUrl.pathname.split("/");
|
|
64
|
+
const activityCode = decodeURIComponent(pathParts.at(-2) ?? "");
|
|
65
|
+
if (pathParts.at(-1) === "authoring") {
|
|
66
|
+
return jsonPayload(loadActivityAuthoring(project, activityCode));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (requestUrl.pathname.startsWith("/api/workbench/authoring/steps/")) {
|
|
70
|
+
const stepCode = decodeURIComponent(requestUrl.pathname.split("/").at(-2) ?? "");
|
|
71
|
+
if (requestUrl.pathname.endsWith("/draft") && request.method === "PUT") {
|
|
72
|
+
return jsonPayload(saveAuthoringDraft(project, "step", stepCode, await readRequestJson(request)));
|
|
73
|
+
}
|
|
74
|
+
if (requestUrl.pathname.endsWith("/apply-plan") && request.method === "GET") {
|
|
75
|
+
return jsonPayload(planStepDraftApply(project, stepCode));
|
|
76
|
+
}
|
|
77
|
+
if (requestUrl.pathname.endsWith("/apply") && request.method === "POST") {
|
|
78
|
+
return jsonPayload(applyStepDraftToSeed(project, stepCode));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (requestUrl.pathname.startsWith("/api/workbench/authoring/tasks/") && request.method === "PUT") {
|
|
82
|
+
const taskCode = decodeURIComponent(requestUrl.pathname.split("/").at(-2) ?? "");
|
|
83
|
+
if (requestUrl.pathname.endsWith("/draft")) {
|
|
84
|
+
return jsonPayload(saveAuthoringDraft(project, "task", taskCode, await readRequestJson(request)));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (requestUrl.pathname.startsWith("/api/workbench/activities/")) {
|
|
88
|
+
return jsonPayload(loadActivityWorkbench(project, decodeURIComponent(requestUrl.pathname.split("/").at(-1) ?? "")));
|
|
89
|
+
}
|
|
90
|
+
if (requestUrl.pathname === "/api/workbench/activities") {
|
|
91
|
+
return jsonPayload(loadActivityWorkbench(project));
|
|
92
|
+
}
|
|
93
|
+
if (requestUrl.pathname.startsWith("/api/workbench/artifacts/") && requestUrl.pathname.endsWith("/documents")) {
|
|
94
|
+
const pathParts = requestUrl.pathname.split("/");
|
|
95
|
+
return jsonPayload(loadArtifactStandardDocuments(project, decodeURIComponent(pathParts.at(-2) ?? "")));
|
|
96
|
+
}
|
|
97
|
+
if (requestUrl.pathname.startsWith("/api/workbench/artifacts/")) {
|
|
98
|
+
return jsonPayload(loadArtifactStandard(project, decodeURIComponent(requestUrl.pathname.split("/").at(-1) ?? "")));
|
|
99
|
+
}
|
|
100
|
+
if (requestUrl.pathname === "/api/workbench/artifacts") {
|
|
101
|
+
return jsonPayload(loadArtifactStandard(project));
|
|
102
|
+
}
|
|
103
|
+
if (requestUrl.pathname === "/api/workbench/governance") {
|
|
104
|
+
return jsonPayload(loadGovernance(project));
|
|
105
|
+
}
|
|
106
|
+
return jsonPayload({ error: "not_found" }, 404);
|
|
107
|
+
}
|
|
108
|
+
function writePayload(response, payload) {
|
|
109
|
+
response.writeHead(payload.status, {
|
|
110
|
+
"content-type": payload.contentType,
|
|
111
|
+
"cache-control": "no-store"
|
|
112
|
+
});
|
|
113
|
+
response.end(payload.body);
|
|
114
|
+
}
|
|
115
|
+
export function createWorkbenchServer(options = {}) {
|
|
116
|
+
const host = options.host ?? "127.0.0.1";
|
|
117
|
+
const port = options.port ?? 4783;
|
|
118
|
+
const project = loadWorkbenchConfig(options.projectRoot ?? process.cwd());
|
|
119
|
+
const server = createServer((request, response) => {
|
|
120
|
+
try {
|
|
121
|
+
void routeRequest(request, project)
|
|
122
|
+
.then((payload) => writePayload(response, payload))
|
|
123
|
+
.catch((error) => {
|
|
124
|
+
writePayload(response, jsonPayload({
|
|
125
|
+
error: "internal_error",
|
|
126
|
+
message: error instanceof Error ? error.message : String(error)
|
|
127
|
+
}, 500));
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
writePayload(response, jsonPayload({
|
|
132
|
+
error: "internal_error",
|
|
133
|
+
message: error instanceof Error ? error.message : String(error)
|
|
134
|
+
}, 500));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
return {
|
|
138
|
+
server,
|
|
139
|
+
project,
|
|
140
|
+
start: async () => {
|
|
141
|
+
await new Promise((resolve, reject) => {
|
|
142
|
+
server.once("error", reject);
|
|
143
|
+
server.listen(port, host, () => {
|
|
144
|
+
server.off("error", reject);
|
|
145
|
+
resolve();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
return {
|
|
149
|
+
host,
|
|
150
|
+
port,
|
|
151
|
+
url: `http://${host}:${port}`,
|
|
152
|
+
project
|
|
153
|
+
};
|
|
154
|
+
},
|
|
155
|
+
stop: async () => {
|
|
156
|
+
if (!server.listening) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
await new Promise((resolve, reject) => {
|
|
160
|
+
server.close((error) => {
|
|
161
|
+
if (error) {
|
|
162
|
+
reject(error);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
resolve();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
export async function startWorkbenchServer(options = {}) {
|
|
173
|
+
const workbenchServer = createWorkbenchServer(options);
|
|
174
|
+
return await workbenchServer.start();
|
|
175
|
+
}
|
|
176
|
+
function renderWorkbenchHtml(project) {
|
|
177
|
+
const workbenchTitle = escapeHtml(project.config.title);
|
|
178
|
+
const tutorialTitle = escapeHtml(project.config.title.replace(/\bWorkbench\b/g, "Tutorial"));
|
|
179
|
+
const authoringModeControl = project.config.experienceMode === "authoring"
|
|
180
|
+
? `<button class="header-mode-badge" id="mode-toggle" type="button" aria-label="Switch to Tutorial" title="Switch to Tutorial" data-mode-target="tutorial">Tutorial</button>`
|
|
181
|
+
: `<span class="header-mode-badge readonly">Read-only</span>`;
|
|
182
|
+
return `<!doctype html>
|
|
183
|
+
<html lang="ko">
|
|
184
|
+
<head>
|
|
185
|
+
<meta charset="utf-8">
|
|
186
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
187
|
+
<title>${workbenchTitle}</title>
|
|
188
|
+
<link rel="stylesheet" href="/assets/app.css">
|
|
189
|
+
</head>
|
|
190
|
+
<body class="landing-active">
|
|
191
|
+
<button class="sidebar-open-button" id="sidebar-open" type="button" aria-label="Open navigation panel" title="Open navigation panel"></button>
|
|
192
|
+
<section class="landing-page" id="landing-page" aria-label="Methodology lifecycle model selection">
|
|
193
|
+
<div class="landing-shell">
|
|
194
|
+
<header class="landing-header">
|
|
195
|
+
<div>
|
|
196
|
+
<p class="landing-eyebrow">${escapeHtml(project.config.methodologyPackageName)}</p>
|
|
197
|
+
<h1>${workbenchTitle}</h1>
|
|
198
|
+
<p>방법론 라이프사이클 모델을 선택해 Phase, Stage, Activity, Task, Step 및 산출물 표준을 조회합니다.</p>
|
|
199
|
+
</div>
|
|
200
|
+
</header>
|
|
201
|
+
<div class="lifecycle-list" id="lifecycle-list"></div>
|
|
202
|
+
</div>
|
|
203
|
+
</section>
|
|
204
|
+
<div class="app-shell">
|
|
205
|
+
<aside class="sidebar" aria-label="Workbench navigation">
|
|
206
|
+
<div class="sidebar-header">
|
|
207
|
+
<div class="sidebar-title-block">
|
|
208
|
+
<div class="sidebar-title-row">
|
|
209
|
+
<strong id="workbench-title" data-workbench-title="${workbenchTitle}" data-tutorial-title="${tutorialTitle}">${workbenchTitle}</strong>
|
|
210
|
+
${authoringModeControl}
|
|
211
|
+
</div>
|
|
212
|
+
<span>${escapeHtml(project.config.methodologyPackageName)} / ${escapeHtml(project.config.bundleCode)}</span>
|
|
213
|
+
</div>
|
|
214
|
+
<button class="icon-button sidebar-close-button" id="sidebar-close" type="button" aria-label="Close navigation panel" title="Close navigation panel"></button>
|
|
215
|
+
</div>
|
|
216
|
+
<div class="sidebar-section">
|
|
217
|
+
<div class="phase-tab-list" id="phase-tabs" role="tablist" aria-label="Lifecycle phases">
|
|
218
|
+
</div>
|
|
219
|
+
<div class="methodology-tree" id="methodology-tree" role="tree"></div>
|
|
220
|
+
</div>
|
|
221
|
+
</aside>
|
|
222
|
+
<main class="workspace">
|
|
223
|
+
<section class="workbench-grid">
|
|
224
|
+
<section class="artifact-panel">
|
|
225
|
+
<div class="artifact-standard" id="artifact-standard"></div>
|
|
226
|
+
</section>
|
|
227
|
+
</section>
|
|
228
|
+
</main>
|
|
229
|
+
</div>
|
|
230
|
+
<script src="/assets/app.js" type="module"></script>
|
|
231
|
+
</body>
|
|
232
|
+
</html>`;
|
|
233
|
+
}
|
|
234
|
+
function renderWorkbenchCss() {
|
|
235
|
+
return `:root {
|
|
236
|
+
color-scheme: light;
|
|
237
|
+
--bg: #eef2f5;
|
|
238
|
+
--chrome: #e5ebef;
|
|
239
|
+
--surface: #ffffff;
|
|
240
|
+
--surface-subtle: #f6f8fa;
|
|
241
|
+
--surface-raised: #ffffff;
|
|
242
|
+
--border: #d5dde6;
|
|
243
|
+
--border-strong: #aeb9c6;
|
|
244
|
+
--text: #111827;
|
|
245
|
+
--text-soft: #1f2937;
|
|
246
|
+
--muted: #667085;
|
|
247
|
+
--muted-strong: #344054;
|
|
248
|
+
--accent: #0f766e;
|
|
249
|
+
--accent-soft: #e6f4f1;
|
|
250
|
+
--accent-strong: #115e59;
|
|
251
|
+
--blue: #2563eb;
|
|
252
|
+
--blue-soft: #eff6ff;
|
|
253
|
+
--danger: #b42318;
|
|
254
|
+
--warning: #a15c07;
|
|
255
|
+
--ok: #166534;
|
|
256
|
+
--radius-sm: 6px;
|
|
257
|
+
--radius-md: 8px;
|
|
258
|
+
--radius-lg: 10px;
|
|
259
|
+
--shadow: 0 1px 2px rgba(16, 24, 40, 0.08);
|
|
260
|
+
--shadow-md: 0 8px 20px rgba(16, 24, 40, 0.08);
|
|
261
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
262
|
+
}
|
|
263
|
+
* { box-sizing: border-box; }
|
|
264
|
+
body { margin: 0; min-width: 320px; background: var(--bg); color: var(--text); text-rendering: geometricPrecision; }
|
|
265
|
+
button, input, select, textarea { font: inherit; }
|
|
266
|
+
button { cursor: pointer; }
|
|
267
|
+
button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible, summary:focus-visible { outline: 3px solid rgba(15, 118, 110, 0.18); outline-offset: 2px; }
|
|
268
|
+
body.landing-active .app-shell, body.landing-active .sidebar-open-button { display: none; }
|
|
269
|
+
body:not(.landing-active) .landing-page { display: none; }
|
|
270
|
+
.landing-page { min-height: 100vh; padding: 28px; background: linear-gradient(180deg, #f8fafc 0%, #eef2f5 100%); overflow: auto; }
|
|
271
|
+
.landing-shell { display: grid; gap: 18px; max-width: 1180px; margin: 0 auto; }
|
|
272
|
+
.landing-header { display: grid; gap: 8px; padding: 28px 30px; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); box-shadow: var(--shadow-md); }
|
|
273
|
+
.landing-eyebrow { margin: 0 0 6px; color: var(--accent-strong); font-size: 13px; font-weight: 850; }
|
|
274
|
+
.landing-header h1 { max-width: 780px; font-size: 30px; line-height: 1.22; }
|
|
275
|
+
.landing-header p { max-width: 820px; color: var(--muted-strong); font-size: 16px; line-height: 1.55; }
|
|
276
|
+
.lifecycle-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); gap: 14px; }
|
|
277
|
+
.lifecycle-card { display: grid; grid-template-rows: auto auto 1fr auto; gap: 14px; min-height: 240px; padding: 20px; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); color: var(--text); box-shadow: var(--shadow); transition: border-color 120ms ease, box-shadow 120ms ease, transform 120ms ease; }
|
|
278
|
+
.lifecycle-card:hover { border-color: #8ab7b1; box-shadow: var(--shadow-md); transform: translateY(-1px); }
|
|
279
|
+
.lifecycle-card h2 { font-size: 22px; line-height: 1.3; }
|
|
280
|
+
.lifecycle-card p { color: var(--muted-strong); font-size: 15px; line-height: 1.5; }
|
|
281
|
+
.lifecycle-meta { display: flex; flex-wrap: wrap; gap: 7px; }
|
|
282
|
+
.lifecycle-meta span, .phase-pill { display: inline-flex; align-items: center; min-height: 25px; border: 1px solid var(--border); border-radius: 999px; background: var(--surface-subtle); color: var(--muted-strong); padding: 3px 8px; font-size: 12px; font-weight: 760; }
|
|
283
|
+
.phase-pill-list { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
284
|
+
.lifecycle-card-action { display: flex; justify-content: flex-end; }
|
|
285
|
+
.lifecycle-open-button { min-height: 36px; border: 1px solid #8ab7b1; border-radius: var(--radius-sm); background: var(--accent-soft); color: var(--accent-strong); padding: 7px 11px; font-size: 14px; font-weight: 850; }
|
|
286
|
+
.lifecycle-open-button:hover { background: #ffffff; border-color: #6aa79f; }
|
|
287
|
+
.app-shell { display: grid; grid-template-columns: 520px minmax(0, 1fr); height: 100vh; min-height: 640px; overflow: hidden; background: var(--bg); }
|
|
288
|
+
body.sidebar-collapsed .app-shell { grid-template-columns: 0 minmax(0, 1fr); }
|
|
289
|
+
.sidebar { display: grid; grid-template-rows: auto minmax(0, 1fr); gap: 12px; border-right: 1px solid var(--border); background: var(--chrome); padding: 12px; min-width: 0; overflow: hidden; transition: transform 160ms ease, opacity 160ms ease, padding 160ms ease, border-color 160ms ease; }
|
|
290
|
+
body.sidebar-collapsed .sidebar { transform: translateX(-100%); opacity: 0; pointer-events: none; padding: 0; border-right-color: transparent; }
|
|
291
|
+
.workspace { display: grid; grid-template-rows: minmax(0, 1fr); padding: 14px; min-width: 0; overflow: hidden; background: linear-gradient(180deg, #f8fafc 0%, var(--bg) 100%); }
|
|
292
|
+
.sidebar-open-button { position: fixed; top: 50%; left: 10px; z-index: 20; display: none; align-items: center; justify-content: center; width: 30px; height: 54px; transform: translateY(-50%); border: 1px solid var(--border-strong); border-radius: var(--radius-md); background: var(--surface); color: var(--accent-strong); font-size: 0; box-shadow: var(--shadow-md); }
|
|
293
|
+
body.sidebar-collapsed .sidebar-open-button { display: inline-flex; }
|
|
294
|
+
.sidebar-open-button::before, .sidebar-close-button::before { content: ""; display: block; width: 9px; height: 9px; border-top: 2px solid currentColor; border-right: 2px solid currentColor; }
|
|
295
|
+
.sidebar-open-button::before { transform: rotate(45deg); margin-left: -2px; }
|
|
296
|
+
.sidebar-header { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 10px; min-width: 0; padding: 12px; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface-raised); box-shadow: var(--shadow); }
|
|
297
|
+
.sidebar-title-block { display: grid; gap: 4px; min-width: 0; }
|
|
298
|
+
.sidebar-title-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 8px; min-width: 0; }
|
|
299
|
+
.sidebar-header strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); font-size: 16px; line-height: 1.3; font-weight: 850; }
|
|
300
|
+
.sidebar-header span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: 13px; line-height: 1.35; }
|
|
301
|
+
.sidebar-close-button { width: 34px; min-height: 34px; color: var(--muted-strong); font-size: 0; }
|
|
302
|
+
.sidebar-close-button::before { transform: rotate(-135deg); margin-left: 3px; }
|
|
303
|
+
.header-mode-badge { justify-self: end; display: inline-flex; align-items: center; justify-content: center; min-height: 28px; max-width: 120px; border-radius: 999px; border: 1px solid #8ab7b1; background: var(--accent-soft); color: var(--accent-strong); padding: 4px 9px; font-size: 12px; font-weight: 900; line-height: 1.1; white-space: nowrap; box-shadow: var(--shadow); }
|
|
304
|
+
button.header-mode-badge:hover { border-color: #6aa79f; background: #ffffff; }
|
|
305
|
+
body.tutorial-mode .header-mode-badge { border-color: #cbd5e1; background: #f8fafc; color: #475569; }
|
|
306
|
+
.header-mode-badge.readonly { border-color: var(--border); background: var(--surface-subtle); color: var(--muted-strong); box-shadow: none; }
|
|
307
|
+
.eyebrow { margin: 0 0 4px; color: var(--muted); font-size: 12px; font-weight: 700; text-transform: uppercase; }
|
|
308
|
+
.subtitle { margin: 6px 0 0; color: var(--muted); }
|
|
309
|
+
h1, h2, p { margin: 0; }
|
|
310
|
+
h1 { font-size: 24px; line-height: 1.25; }
|
|
311
|
+
h2 { font-size: 20px; line-height: 1.3; }
|
|
312
|
+
.panel-subtitle { color: var(--muted); font-size: 15px; line-height: 1.45; margin-top: 4px; }
|
|
313
|
+
.icon-button, .primary-button, .secondary-button { min-height: 40px; border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--surface); color: var(--text); transition: background 120ms ease, border-color 120ms ease, color 120ms ease, box-shadow 120ms ease; }
|
|
314
|
+
.icon-button:hover, .secondary-button:hover { border-color: var(--border-strong); background: #f9fafb; }
|
|
315
|
+
.icon-button { width: 34px; font-weight: 850; }
|
|
316
|
+
.primary-button { padding: 0 14px; border-color: var(--accent); background: var(--accent); color: #fff; font-weight: 800; box-shadow: 0 1px 2px rgba(15, 118, 110, 0.24); }
|
|
317
|
+
.primary-button:hover { background: var(--accent-strong); border-color: var(--accent-strong); }
|
|
318
|
+
.secondary-button { padding: 0 12px; font-weight: 800; background: var(--surface-subtle); }
|
|
319
|
+
.workbench-grid { display: grid; grid-template-columns: minmax(0, 1fr); min-height: 0; overflow: hidden; }
|
|
320
|
+
.panel { display: grid; grid-template-rows: auto minmax(0, 1fr); min-width: 0; min-height: 0; padding: 14px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); box-shadow: var(--shadow); overflow: hidden; }
|
|
321
|
+
.panel-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 14px; }
|
|
322
|
+
.status-pill { border-radius: 999px; background: #d8f3ee; color: var(--accent-strong); padding: 5px 9px; font-size: 14px; font-weight: 700; white-space: nowrap; }
|
|
323
|
+
.status-pill.neutral { background: var(--surface-subtle); color: var(--muted); }
|
|
324
|
+
.sidebar-section { display: grid; grid-template-rows: auto minmax(0, 1fr); min-height: 0; padding: 0; border: 1px solid var(--border); border-radius: var(--radius-lg); background: rgba(255,255,255,0.52); overflow: hidden; }
|
|
325
|
+
.phase-tab-list { display: flex; flex-wrap: wrap; align-items: end; gap: 3px; min-height: 48px; padding: 8px 9px 0; border-bottom: 1px solid #9fb3bf; background: linear-gradient(180deg, #d5e3e8 0%, #c3d4dc 100%); box-shadow: 0 1px 0 rgba(255,255,255,0.82) inset, 0 1px 2px rgba(15,23,42,0.05); }
|
|
326
|
+
.phase-tab { min-height: 40px; max-width: 100%; border: 1px solid transparent; border-bottom: 0; border-radius: var(--radius-md) var(--radius-md) 0 0; background: transparent; color: #334155; padding: 8px 10px 9px; font-size: 13px; font-weight: 780; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
327
|
+
.phase-tab:hover { background: rgba(255,255,255,0.55); color: #0f2530; }
|
|
328
|
+
.phase-tab.active { margin-bottom: -1px; border-color: #9fb3bf; background: rgba(255,255,255,0.88); color: #0f2530; box-shadow: 0 -1px 0 rgba(255,255,255,0.8) inset; }
|
|
329
|
+
.methodology-tree { display: grid; align-content: start; gap: 3px; min-height: 0; overflow: auto; padding: 9px 8px 10px 8px; }
|
|
330
|
+
.tree-group { position: relative; display: grid; gap: 2px; padding-bottom: 4px; }
|
|
331
|
+
.tree-node { --meta-width: 92px; position: relative; width: 100%; max-width: 100%; display: grid; grid-template-columns: 18px minmax(0, 1fr) var(--meta-width); align-items: center; gap: 8px; border: 1px solid transparent; border-radius: var(--radius-md); background: transparent; color: var(--text); text-align: left; min-height: 42px; padding: 8px 9px; transition: background 120ms ease, border-color 120ms ease, box-shadow 120ms ease; }
|
|
332
|
+
.tree-node.no-order { grid-template-columns: 18px minmax(0, 1fr) var(--meta-width); }
|
|
333
|
+
.tree-node:hover { border-color: #c7d2df; background: rgba(255,255,255,0.78); }
|
|
334
|
+
.tree-node.context { border-color: rgba(142, 173, 168, 0.42); background: linear-gradient(180deg, rgba(255,255,255,0.86) 0%, rgba(238,248,246,0.9) 100%); box-shadow: 0 1px 0 rgba(255,255,255,0.76) inset; }
|
|
335
|
+
.tree-node.active { border-color: #8fb9b4; background: linear-gradient(180deg, #f0faf8 0%, #ddf1ee 100%); box-shadow: 0 1px 0 rgba(255,255,255,0.84) inset, 0 1px 4px rgba(15, 118, 110, 0.08); }
|
|
336
|
+
.tree-node.stage { --meta-width: 220px; color: #111827; font-weight: 720; border-color: rgba(143,187,179,0.32); background: linear-gradient(180deg, rgba(255,255,255,0.74) 0%, rgba(240,248,247,0.82) 100%); }
|
|
337
|
+
.tree-node.activity { --meta-width: 144px; margin-left: 14px; width: calc(100% - 14px); color: #243b53; font-weight: 650; border-color: rgba(158,185,223,0.28); background: rgba(255,255,255,0.42); }
|
|
338
|
+
.tree-node.task { --meta-width: 126px; margin-left: 28px; width: calc(100% - 28px); color: #39495a; font-weight: 600; background: rgba(255,255,255,0.34); }
|
|
339
|
+
.tree-node.step { --meta-width: 120px; margin-left: 42px; width: calc(100% - 42px); color: #5b6878; font-weight: 500; }
|
|
340
|
+
.tree-caret { position: relative; display: inline-flex; flex: 0 0 18px; align-items: center; justify-content: center; width: 18px; height: 18px; border: 1px solid #b8c6d3; border-radius: 5px; background: #ffffff; color: #64748b; box-shadow: 0 1px 0 rgba(255,255,255,0.85) inset, 0 1px 2px rgba(15,23,42,0.06); font-size: 0; line-height: 0; }
|
|
341
|
+
.tree-caret::before, .tree-caret::after { content: ""; position: absolute; left: 5px; right: 5px; top: 50%; height: 2px; border-radius: 999px; background: currentColor; transform: translateY(-50%); }
|
|
342
|
+
.tree-caret::after { left: 50%; right: auto; top: 5px; bottom: 5px; width: 2px; height: auto; transform: translateX(-50%); }
|
|
343
|
+
.tree-node[aria-expanded="true"] > .tree-caret::after { display: none; }
|
|
344
|
+
.tree-node:hover .tree-caret { border-color: #9ab7c2; color: var(--muted-strong); background: #ffffff; }
|
|
345
|
+
.tree-node.active .tree-caret { border-color: #6aa79f; color: var(--accent-strong); background: #ffffff; }
|
|
346
|
+
.tree-caret-placeholder { position: relative; width: 18px; height: 18px; }
|
|
347
|
+
.tree-caret-placeholder::before { content: ""; position: absolute; left: 50%; top: 50%; width: 6px; height: 6px; border-radius: 999px; background: #94a3b8; transform: translate(-50%, -50%); }
|
|
348
|
+
.tree-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 15px; line-height: 1.25; font-weight: inherit; }
|
|
349
|
+
.tree-node.stage .tree-label { font-size: 17px; }
|
|
350
|
+
.tree-node.activity .tree-label { font-size: 16px; }
|
|
351
|
+
.tree-meta-group { display: flex; align-items: center; justify-content: flex-end; gap: 5px; width: 100%; max-width: 100%; min-width: 0; overflow: hidden; }
|
|
352
|
+
.tree-count { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--muted); font-size: 10.5px; font-weight: 620; text-align: right; white-space: nowrap; }
|
|
353
|
+
.tree-kind-badge, .tree-mode-chip, .tree-state-chip { display: inline-flex; align-items: center; justify-content: center; min-height: 21px; border-radius: 999px; border: 1px solid var(--border); padding: 2px 7px; font-size: 10.5px; font-weight: 680; line-height: 1; white-space: nowrap; }
|
|
354
|
+
.tree-kind-badge { flex: 0 0 38px; min-width: 38px; }
|
|
355
|
+
.tree-kind-badge.with-order { flex-basis: 58px; min-width: 58px; }
|
|
356
|
+
.tree-mode-chip, .tree-state-chip { flex: 0 0 auto; }
|
|
357
|
+
.tree-kind-badge.stage { border-color: #8fbbb3; background: #dff2ef; color: #175f58; }
|
|
358
|
+
.tree-kind-badge.activity { border-color: #9eb9df; background: #e5effb; color: #28568a; }
|
|
359
|
+
.tree-kind-badge.task { border-color: #9fb4c5; background: #dfe9f1; color: #2f4858; font-weight: 740; }
|
|
360
|
+
.tree-kind-badge.step { border-color: #dde4eb; background: #fbfcfe; color: #778292; font-weight: 560; }
|
|
361
|
+
.tree-mode-chip { background: #ffffff; color: var(--muted-strong); }
|
|
362
|
+
.tree-state-chip.valid { border-color: #bbd7c4; background: #ecfdf3; color: #166534; }
|
|
363
|
+
.tree-state-chip.issue { border-color: #f7c8c2; background: #fef3f2; color: #b42318; }
|
|
364
|
+
.tree-empty { color: var(--muted); font-size: 15px; line-height: 1.4; padding: 8px; }
|
|
365
|
+
.task-list { display: grid; align-content: start; gap: 10px; overflow: auto; min-height: 0; padding-right: 4px; }
|
|
366
|
+
.task-row { border: 1px solid var(--border); border-radius: 8px; background: #fbfcfd; padding: 10px; }
|
|
367
|
+
.task-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
|
|
368
|
+
.task-head strong { font-size: 13px; line-height: 1.35; }
|
|
369
|
+
.task-meta { color: var(--muted); font-size: 12px; white-space: nowrap; }
|
|
370
|
+
.step-list { display: grid; gap: 6px; }
|
|
371
|
+
.step-chip { width: 100%; display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 8px; border: 1px solid var(--border); border-radius: 7px; background: var(--surface); color: var(--text); text-align: left; padding: 8px; }
|
|
372
|
+
.step-chip:hover, .step-chip.active { border-color: var(--accent); background: var(--accent-soft); }
|
|
373
|
+
.step-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
|
|
374
|
+
.step-mode { color: var(--muted); font-size: 11px; font-weight: 800; }
|
|
375
|
+
.field-label { color: var(--muted); font-size: 14px; font-weight: 850; }
|
|
376
|
+
.inline-step-panel { position: relative; display: grid; gap: 0; margin-left: 42px; width: calc(100% - 42px); max-width: calc(100% - 42px); border: 1px solid #8ab7b1; border-radius: var(--radius-lg); background: var(--surface); box-shadow: var(--shadow-md); overflow: hidden; }
|
|
377
|
+
.inline-step-panel summary { list-style: none; }
|
|
378
|
+
.inline-step-panel summary::-webkit-details-marker { display: none; }
|
|
379
|
+
.inline-step-panel .tree-node.step { margin-left: 0; width: 100%; border: 0; border-radius: 0; }
|
|
380
|
+
.inline-step-body { display: grid; gap: 10px; padding: 12px; border-top: 1px solid var(--border); background: #fbfdfc; }
|
|
381
|
+
.editor-form { display: grid; gap: 12px; min-height: 0; }
|
|
382
|
+
.editor-section { display: grid; gap: 9px; padding: 10px; border: 1px solid var(--border); border-radius: var(--radius-md); background: #ffffff; box-shadow: 0 1px 0 rgba(255,255,255,0.8) inset; }
|
|
383
|
+
.editor-section-title { margin: 0; color: var(--muted-strong); font-size: 13px; line-height: 1.3; font-weight: 900; text-transform: uppercase; }
|
|
384
|
+
.editor-section-body { display: grid; gap: 10px; min-width: 0; }
|
|
385
|
+
.editor-section-compact .editor-section-body { grid-template-columns: minmax(0, 1fr) 148px; align-items: end; }
|
|
386
|
+
.editor-form label, .editor-field { display: grid; gap: 6px; color: var(--muted); font-size: 14px; font-weight: 850; min-width: 0; }
|
|
387
|
+
.editor-form[data-mode="LLM"] .script-field, .editor-form[data-mode="MANUAL"] .script-field { display: none; }
|
|
388
|
+
.editor-form input, .editor-form select, .editor-form textarea {
|
|
389
|
+
width: 100%;
|
|
390
|
+
border: 1px solid var(--border);
|
|
391
|
+
border-radius: var(--radius-sm);
|
|
392
|
+
background: #ffffff;
|
|
393
|
+
color: var(--text);
|
|
394
|
+
font: inherit;
|
|
395
|
+
font-size: 16px;
|
|
396
|
+
font-weight: 500;
|
|
397
|
+
padding: 9px 10px;
|
|
398
|
+
}
|
|
399
|
+
.editor-form input:focus, .editor-form select:focus, .editor-form textarea:focus { outline: 3px solid rgba(15, 118, 110, 0.14); border-color: var(--accent); }
|
|
400
|
+
.editor-form textarea { resize: vertical; min-height: 70px; line-height: 1.48; }
|
|
401
|
+
#editor-instruction { min-height: 120px; }
|
|
402
|
+
.form-actions { display: flex; justify-content: flex-end; gap: 8px; }
|
|
403
|
+
.validation-list { display: grid; gap: 6px; }
|
|
404
|
+
.validation-item { border-radius: 6px; padding: 8px 9px; font-size: 15px; line-height: 1.35; }
|
|
405
|
+
.validation-item.error { background: #fee2e2; color: #991b1b; }
|
|
406
|
+
.validation-item.warning { background: #fef3c7; color: #92400e; }
|
|
407
|
+
.validation-item.ok { background: #dcfce7; color: #166534; }
|
|
408
|
+
.compact-review { display: grid; gap: 8px; border: 1px solid var(--border); border-radius: var(--radius-md); background: #f7faf9; padding: 10px; min-height: 0; }
|
|
409
|
+
.compact-review-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
|
|
410
|
+
.compact-review-header p { color: var(--muted); font-size: 15px; line-height: 1.4; margin-top: 3px; word-break: break-word; }
|
|
411
|
+
.diff-list { display: grid; align-content: start; gap: 8px; overflow: auto; min-height: 0; padding-right: 4px; }
|
|
412
|
+
.diff-list.compact { max-height: 132px; }
|
|
413
|
+
.diff-empty { color: var(--muted); font-size: 15px; line-height: 1.45; }
|
|
414
|
+
.diff-item { border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden; background: var(--surface-subtle); }
|
|
415
|
+
.diff-field { padding: 8px 10px; font-size: 14px; font-weight: 850; border-bottom: 1px solid var(--border); }
|
|
416
|
+
.diff-values { display: grid; gap: 1px; }
|
|
417
|
+
.diff-before, .diff-after { display: grid; gap: 4px; padding: 8px 10px; font-size: 14px; line-height: 1.45; word-break: break-word; }
|
|
418
|
+
.diff-before { background: #fff7ed; color: #7c2d12; }
|
|
419
|
+
.diff-after { background: #ecfdf5; color: #14532d; }
|
|
420
|
+
.diff-label { font-weight: 800; opacity: 0.8; }
|
|
421
|
+
.artifact-panel { min-width: 0; min-height: 0; overflow: hidden; }
|
|
422
|
+
.artifact-standard { display: grid; grid-template-rows: minmax(0, 1fr); align-content: start; gap: 10px; overflow: auto; min-height: 0; height: 100%; }
|
|
423
|
+
.artifact-definition-card { border: 1px solid #cbd5e1; border-radius: 8px; background: #fbfcfd; overflow: hidden; }
|
|
424
|
+
.artifact-definition-card summary { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 10px; min-height: 44px; padding: 10px 12px; cursor: pointer; list-style: none; }
|
|
425
|
+
.artifact-definition-card summary::-webkit-details-marker { display: none; }
|
|
426
|
+
.artifact-definition-card summary::after { content: ""; display: block; width: 7px; height: 7px; border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted); transform: rotate(-45deg); transition: transform 140ms ease, border-color 120ms ease; }
|
|
427
|
+
.artifact-definition-card summary:hover::after { border-color: var(--accent-strong); }
|
|
428
|
+
.artifact-definition-card[open] summary::after { transform: rotate(45deg); }
|
|
429
|
+
.artifact-definition-card h3 { margin: 0; font-size: 15px; line-height: 1.35; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
430
|
+
.artifact-definition-body { display: grid; gap: 8px; padding: 0 14px 14px; border-top: 1px solid var(--border); }
|
|
431
|
+
.artifact-definition-body p { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.5; }
|
|
432
|
+
.standard-metrics { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; margin-top: 6px; }
|
|
433
|
+
.standard-metrics div { padding: 10px; border: 1px solid var(--border); border-radius: 8px; background: #fff; }
|
|
434
|
+
.standard-metrics span { display: block; color: var(--muted); font-size: 11px; font-weight: 800; text-transform: uppercase; }
|
|
435
|
+
.standard-metrics strong { display: block; margin-top: 2px; font-size: 18px; line-height: 1.1; }
|
|
436
|
+
.artifact-tabset { display: grid; grid-template-rows: auto minmax(0, 1fr); min-height: 0; height: 100%; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); overflow: hidden; box-shadow: var(--shadow-md); }
|
|
437
|
+
.artifact-tab-list { display: flex; flex-wrap: wrap; align-items: end; gap: 3px; padding: 8px 10px 0; background: #e8eef3; border-bottom: 1px solid var(--border); }
|
|
438
|
+
.artifact-tab { min-height: 46px; padding: 11px 14px 10px; border: 1px solid transparent; border-bottom: 0; border-radius: var(--radius-md) var(--radius-md) 0 0; background: transparent; color: #475569; font-weight: 850; font-size: 15px; transition: background 120ms ease, border-color 120ms ease, color 120ms ease; }
|
|
439
|
+
.artifact-tab:hover { background: rgba(255,255,255,0.6); color: var(--text-soft); }
|
|
440
|
+
.artifact-tab.active { margin-bottom: -1px; border-color: var(--border); background: var(--surface); color: var(--text); box-shadow: 0 -1px 0 rgba(255,255,255,0.8) inset; }
|
|
441
|
+
.artifact-tab-panels { min-height: 0; overflow: hidden; }
|
|
442
|
+
.artifact-tab-panel { display: flex; flex-direction: column; height: 100%; min-height: 0; padding: 18px; overflow: hidden; background: #f8fafc; }
|
|
443
|
+
.artifact-tab-panel[hidden] { display: none; }
|
|
444
|
+
.artifact-view-toolbar { display: inline-flex; align-items: center; justify-content: flex-end; gap: 6px; min-width: max-content; margin-left: auto; padding-bottom: 8px; }
|
|
445
|
+
.viewer-option-button { min-height: 32px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: #ffffff; color: var(--muted-strong); padding: 5px 9px; font-size: 13px; font-weight: 850; }
|
|
446
|
+
.viewer-option-button:hover { border-color: var(--border-strong); background: #f9fafb; }
|
|
447
|
+
.viewer-option-button.active { border-color: #8ab7b1; background: var(--accent-soft); color: var(--accent-strong); }
|
|
448
|
+
.document-action-row { display: flex; flex-wrap: wrap; gap: 8px; margin: 18px 0 0; padding-top: 14px; border-top: 1px solid var(--border); }
|
|
449
|
+
.document-action-row a, .document-action-row span { display: inline-flex; align-items: center; min-height: 32px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-subtle); color: var(--muted-strong); padding: 5px 9px; font-size: 13px; font-weight: 800; text-decoration: none; }
|
|
450
|
+
.document-action-row a:hover { border-color: var(--accent); color: var(--accent-strong); }
|
|
451
|
+
.rendered-document { flex: 1 1 auto; min-height: 0; height: auto; overflow: auto; padding: 40px 34px 48px; border: 1px solid var(--border); border-radius: var(--radius-lg); background: #fff; box-shadow: var(--shadow); }
|
|
452
|
+
.rendered-document-body { max-width: 1180px; margin: 0 auto; }
|
|
453
|
+
body.artifact-full-width .rendered-document-body,
|
|
454
|
+
body.sidebar-collapsed .rendered-document-body { max-width: none; margin: 0; }
|
|
455
|
+
body.artifact-full-width .document-action-row,
|
|
456
|
+
body.sidebar-collapsed .document-action-row { max-width: none; margin-left: 0; margin-right: 0; }
|
|
457
|
+
body.artifact-compact-padding .artifact-tab-panel { padding: 10px; }
|
|
458
|
+
body.artifact-compact-padding .rendered-document { padding: 18px 18px 22px; }
|
|
459
|
+
body.artifact-compact-padding .rendered-document h2 { margin-bottom: 14px; padding-bottom: 9px; font-size: 24px; }
|
|
460
|
+
body.artifact-compact-padding .rendered-document h3 { margin: 22px 0 8px; padding: 0 0 6px; font-size: 20px; }
|
|
461
|
+
body.artifact-compact-padding .rendered-document h4 { margin: 16px 0 6px; font-size: 17px; }
|
|
462
|
+
body.artifact-compact-padding .rendered-document p { margin-bottom: 9px; line-height: 1.45; }
|
|
463
|
+
body.artifact-compact-padding .rendered-document ul { margin-bottom: 11px; line-height: 1.45; }
|
|
464
|
+
body.artifact-compact-padding .document-callout { margin: 10px 0 14px; padding: 9px 11px; }
|
|
465
|
+
body.artifact-compact-padding .document-table-wrap { margin: 8px 0 14px; }
|
|
466
|
+
body.artifact-compact-padding .document-table th,
|
|
467
|
+
body.artifact-compact-padding .document-table td { padding: 5px 7px; }
|
|
468
|
+
.rendered-document h2 { margin: 0 0 32px; padding: 0 0 16px; border-bottom: 2px solid #d8e1ea; color: #0f172a; font-size: 28px; line-height: 1.28; font-weight: 900; }
|
|
469
|
+
.rendered-document h3 { margin: 46px 0 16px; padding: 0 0 9px; border-bottom: 1px solid #e2e8f0; color: #111827; font-size: 23px; line-height: 1.32; font-weight: 880; }
|
|
470
|
+
.rendered-document h4 { margin: 30px 0 10px; color: #334155; font-size: 18px; line-height: 1.36; font-weight: 850; }
|
|
471
|
+
.rendered-document h5, .rendered-document h6 { margin: 22px 0 8px; color: var(--muted-strong); font-size: 16px; line-height: 1.35; font-weight: 850; }
|
|
472
|
+
.rendered-document p { margin: 0 0 18px; color: #334155; font-size: 16px; line-height: 1.6; }
|
|
473
|
+
.rendered-document ul { margin: 0 0 22px 20px; padding: 0; color: #334155; font-size: 16px; line-height: 1.6; }
|
|
474
|
+
.rendered-document li { margin: 4px 0; }
|
|
475
|
+
.rendered-document blockquote { margin: 18px 0 24px; padding: 13px 15px; border: 1px solid #cbd5e1; border-radius: var(--radius-md); background: #f8fafc; color: #334155; font-style: italic; }
|
|
476
|
+
.rendered-document blockquote p:last-child { margin-bottom: 0; }
|
|
477
|
+
.document-callout { margin: 20px 0 30px; padding: 15px 17px; border: 1px solid #b7d7d2; border-radius: var(--radius-md); background: #f1faf8; color: #173f3a; box-shadow: 0 1px 0 rgba(255,255,255,0.75) inset; }
|
|
478
|
+
.document-callout-title { display: flex; align-items: center; gap: 8px; margin: 0 0 10px; color: var(--accent-strong); font-size: 15px; font-weight: 900; }
|
|
479
|
+
.document-callout-title::before { content: "i"; display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; border-radius: 999px; background: var(--accent); color: #ffffff; font-size: 12px; font-weight: 900; font-style: italic; }
|
|
480
|
+
.document-callout p { margin: 0 0 10px; }
|
|
481
|
+
.document-callout p:last-child { margin-bottom: 0; }
|
|
482
|
+
.rendered-document pre { overflow: auto; margin: 18px 0 26px; padding: 14px; border-radius: var(--radius-md); background: #111827; color: #e2e8f0; font-size: 15px; line-height: 1.45; }
|
|
483
|
+
.rendered-document code { border-radius: 4px; background: #eef2f6; padding: 1px 4px; font-size: 15px; }
|
|
484
|
+
.rendered-document pre code { background: transparent; padding: 0; color: inherit; }
|
|
485
|
+
.document-table-wrap { overflow: auto; margin: 16px 0 28px; border: 1px solid var(--border); border-radius: var(--radius-md); box-shadow: var(--shadow); }
|
|
486
|
+
.document-table { width: max-content; min-width: 100%; border-collapse: collapse; font-size: 15px; }
|
|
487
|
+
.document-table th, .document-table td { padding: 9px 10px; border-bottom: 1px solid var(--border); text-align: left; vertical-align: top; }
|
|
488
|
+
.document-table th { background: #eef3f7; color: var(--muted-strong); font-weight: 850; }
|
|
489
|
+
.document-table tbody tr:nth-child(even) td { background: #fbfcfd; }
|
|
490
|
+
.document-table tbody tr:hover td { background: #f6faf9; }
|
|
491
|
+
.document-table tr:last-child td { border-bottom: 0; }
|
|
492
|
+
.artifact-empty { color: var(--muted); font-size: 13px; line-height: 1.5; }
|
|
493
|
+
@media (max-width: 1180px) {
|
|
494
|
+
.app-shell { grid-template-columns: 480px minmax(0, 1fr); }
|
|
495
|
+
.editor-section-compact .editor-section-body { grid-template-columns: 1fr; }
|
|
496
|
+
}`;
|
|
497
|
+
}
|
|
498
|
+
function renderWorkbenchJs(project) {
|
|
499
|
+
return `const state = {
|
|
500
|
+
experienceMode: ${JSON.stringify(project.config.experienceMode)},
|
|
501
|
+
lifecycle: null,
|
|
502
|
+
phase: null,
|
|
503
|
+
selectedLifecycleCode: null,
|
|
504
|
+
selectedPhaseCode: null,
|
|
505
|
+
activity: null,
|
|
506
|
+
artifact: null,
|
|
507
|
+
artifactDocuments: null,
|
|
508
|
+
authoring: null,
|
|
509
|
+
selectedStep: null,
|
|
510
|
+
selectedTask: null,
|
|
511
|
+
expandedStageCodes: new Set(),
|
|
512
|
+
expandedActivityCodes: new Set(),
|
|
513
|
+
expandedTaskCodes: new Set(),
|
|
514
|
+
expandedStepDetailCodes: new Set(),
|
|
515
|
+
sidebarCollapsed: false,
|
|
516
|
+
workbenchMode: "authoring",
|
|
517
|
+
artifactFullWidth: false,
|
|
518
|
+
artifactCompactPadding: false
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
async function loadJson(path) {
|
|
522
|
+
const response = await fetch(path);
|
|
523
|
+
const body = await response.json();
|
|
524
|
+
if (!response.ok) {
|
|
525
|
+
throw new Error(body.message ?? "Failed to load " + path);
|
|
526
|
+
}
|
|
527
|
+
return body;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
async function putJson(path, value) {
|
|
531
|
+
const response = await fetch(path, {
|
|
532
|
+
method: "PUT",
|
|
533
|
+
headers: { "content-type": "application/json" },
|
|
534
|
+
body: JSON.stringify(value)
|
|
535
|
+
});
|
|
536
|
+
const body = await response.json();
|
|
537
|
+
if (!response.ok) {
|
|
538
|
+
throw new Error(body.message ?? "Failed to save " + path);
|
|
539
|
+
}
|
|
540
|
+
return body;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
async function postJson(path, value) {
|
|
544
|
+
const response = await fetch(path, {
|
|
545
|
+
method: "POST",
|
|
546
|
+
headers: { "content-type": "application/json" },
|
|
547
|
+
body: JSON.stringify(value ?? {})
|
|
548
|
+
});
|
|
549
|
+
const body = await response.json();
|
|
550
|
+
if (!response.ok) {
|
|
551
|
+
throw new Error(body.message ?? "Failed to post " + path);
|
|
552
|
+
}
|
|
553
|
+
return body;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function text(id, value) {
|
|
557
|
+
const element = document.getElementById(id);
|
|
558
|
+
if (element) {
|
|
559
|
+
element.textContent = String(value ?? "");
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function setValue(id, value) {
|
|
564
|
+
const element = document.getElementById(id);
|
|
565
|
+
if (element) {
|
|
566
|
+
element.value = String(value ?? "");
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function valueOf(id) {
|
|
571
|
+
const element = document.getElementById(id);
|
|
572
|
+
return element ? element.value : "";
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function renderLifecycleLanding() {
|
|
576
|
+
const container = document.getElementById("lifecycle-list");
|
|
577
|
+
if (!container) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
container.replaceChildren();
|
|
581
|
+
if (!state.lifecycle?.lifecycle) {
|
|
582
|
+
const empty = document.createElement("div");
|
|
583
|
+
empty.className = "artifact-empty";
|
|
584
|
+
empty.textContent = "Lifecycle model is not available.";
|
|
585
|
+
container.append(empty);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const lifecycle = state.lifecycle.lifecycle;
|
|
590
|
+
const methodology = state.lifecycle.methodology;
|
|
591
|
+
const phases = state.lifecycle.phases ?? [];
|
|
592
|
+
const card = document.createElement("article");
|
|
593
|
+
card.className = "lifecycle-card";
|
|
594
|
+
|
|
595
|
+
const head = document.createElement("div");
|
|
596
|
+
const title = document.createElement("h2");
|
|
597
|
+
title.textContent = lifecycle.name;
|
|
598
|
+
const description = document.createElement("p");
|
|
599
|
+
description.textContent = lifecycle.description ?? methodology.description ?? "";
|
|
600
|
+
head.append(title, description);
|
|
601
|
+
|
|
602
|
+
const meta = document.createElement("div");
|
|
603
|
+
meta.className = "lifecycle-meta";
|
|
604
|
+
[
|
|
605
|
+
methodology.name,
|
|
606
|
+
lifecycle.typeCode,
|
|
607
|
+
state.lifecycle.counts?.phases + " phases",
|
|
608
|
+
state.lifecycle.counts?.stages + " stages",
|
|
609
|
+
state.lifecycle.counts?.activities + " activities"
|
|
610
|
+
].filter(Boolean).forEach((item) => {
|
|
611
|
+
const chip = document.createElement("span");
|
|
612
|
+
chip.textContent = String(item);
|
|
613
|
+
meta.append(chip);
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
const phaseList = document.createElement("div");
|
|
617
|
+
phaseList.className = "phase-pill-list";
|
|
618
|
+
phases.forEach((phase) => {
|
|
619
|
+
const pill = document.createElement("span");
|
|
620
|
+
pill.className = "phase-pill";
|
|
621
|
+
pill.textContent = phase.name;
|
|
622
|
+
phaseList.append(pill);
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
const action = document.createElement("div");
|
|
626
|
+
action.className = "lifecycle-card-action";
|
|
627
|
+
const openButton = document.createElement("button");
|
|
628
|
+
openButton.type = "button";
|
|
629
|
+
openButton.className = "lifecycle-open-button";
|
|
630
|
+
openButton.textContent = "Open lifecycle model";
|
|
631
|
+
openButton.addEventListener("click", () => {
|
|
632
|
+
void enterLifecycle(lifecycle.code);
|
|
633
|
+
});
|
|
634
|
+
action.append(openButton);
|
|
635
|
+
card.append(head, meta, phaseList, action);
|
|
636
|
+
container.append(card);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
async function enterLifecycle(lifecycleCode) {
|
|
640
|
+
state.selectedLifecycleCode = lifecycleCode;
|
|
641
|
+
document.body.classList.remove("landing-active");
|
|
642
|
+
const firstPhase = state.lifecycle?.phases?.[0];
|
|
643
|
+
if (firstPhase) {
|
|
644
|
+
await selectPhase(firstPhase.code);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function renderPhaseTabs() {
|
|
649
|
+
const container = document.getElementById("phase-tabs");
|
|
650
|
+
if (!container) {
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
container.replaceChildren();
|
|
654
|
+
(state.lifecycle?.phases ?? []).forEach((phase) => {
|
|
655
|
+
const button = document.createElement("button");
|
|
656
|
+
button.type = "button";
|
|
657
|
+
button.className = phase.code === state.selectedPhaseCode ? "phase-tab active" : "phase-tab";
|
|
658
|
+
button.role = "tab";
|
|
659
|
+
button.setAttribute("aria-selected", phase.code === state.selectedPhaseCode ? "true" : "false");
|
|
660
|
+
button.title = phase.name + (phase.description ? " - " + phase.description : "");
|
|
661
|
+
button.textContent = displayPhaseTabLabel(phase.name);
|
|
662
|
+
button.addEventListener("click", () => {
|
|
663
|
+
void selectPhase(phase.code);
|
|
664
|
+
});
|
|
665
|
+
container.append(button);
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
async function selectPhase(phaseCode) {
|
|
670
|
+
state.selectedPhaseCode = phaseCode;
|
|
671
|
+
state.selectedStep = null;
|
|
672
|
+
state.selectedTask = null;
|
|
673
|
+
state.authoring = null;
|
|
674
|
+
state.expandedStageCodes = new Set();
|
|
675
|
+
state.expandedActivityCodes = new Set();
|
|
676
|
+
state.expandedTaskCodes = new Set();
|
|
677
|
+
state.expandedStepDetailCodes = new Set();
|
|
678
|
+
state.phase = await loadJson("/api/workbench/phases/" + encodeURIComponent(phaseCode));
|
|
679
|
+
renderPhaseTabs();
|
|
680
|
+
renderMethodologyTree();
|
|
681
|
+
const firstActivity = state.phase.stages.flatMap((stage) => stage.activities)[0];
|
|
682
|
+
if (firstActivity) {
|
|
683
|
+
await loadActivity(firstActivity.code);
|
|
684
|
+
} else {
|
|
685
|
+
state.activity = null;
|
|
686
|
+
await loadArtifactStandard(undefined);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function updateWorkbenchTitle() {
|
|
691
|
+
const title = document.getElementById("workbench-title");
|
|
692
|
+
if (!title) {
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
title.textContent = state.workbenchMode === "tutorial"
|
|
696
|
+
? title.dataset.tutorialTitle ?? title.textContent
|
|
697
|
+
: title.dataset.workbenchTitle ?? title.textContent;
|
|
698
|
+
document.title = title.textContent;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function setSidebarCollapsed(collapsed) {
|
|
702
|
+
state.sidebarCollapsed = collapsed;
|
|
703
|
+
document.body.classList.toggle("sidebar-collapsed", collapsed);
|
|
704
|
+
const closeButton = document.getElementById("sidebar-close");
|
|
705
|
+
const openButton = document.getElementById("sidebar-open");
|
|
706
|
+
closeButton?.setAttribute("aria-expanded", collapsed ? "false" : "true");
|
|
707
|
+
openButton?.setAttribute("aria-expanded", collapsed ? "false" : "true");
|
|
708
|
+
try {
|
|
709
|
+
window.localStorage.setItem("methodology-workbench-sidebar-collapsed", collapsed ? "true" : "false");
|
|
710
|
+
} catch {
|
|
711
|
+
// Ignore storage restrictions; the visible state is already updated.
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function initSidebarControls() {
|
|
716
|
+
try {
|
|
717
|
+
state.sidebarCollapsed = window.localStorage.getItem("methodology-workbench-sidebar-collapsed") === "true";
|
|
718
|
+
} catch {
|
|
719
|
+
state.sidebarCollapsed = false;
|
|
720
|
+
}
|
|
721
|
+
setSidebarCollapsed(state.sidebarCollapsed);
|
|
722
|
+
document.getElementById("sidebar-close")?.addEventListener("click", () => setSidebarCollapsed(true));
|
|
723
|
+
document.getElementById("sidebar-open")?.addEventListener("click", () => setSidebarCollapsed(false));
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function setWorkbenchMode(mode) {
|
|
727
|
+
state.workbenchMode = state.experienceMode === "execution"
|
|
728
|
+
? "tutorial"
|
|
729
|
+
: mode === "tutorial" ? "tutorial" : "authoring";
|
|
730
|
+
document.body.classList.toggle("tutorial-mode", state.workbenchMode === "tutorial");
|
|
731
|
+
const toggle = document.getElementById("mode-toggle");
|
|
732
|
+
if (toggle) {
|
|
733
|
+
const nextMode = state.workbenchMode === "authoring" ? "tutorial" : "authoring";
|
|
734
|
+
toggle.dataset.modeTarget = nextMode;
|
|
735
|
+
toggle.textContent = nextMode === "tutorial" ? "Tutorial" : "Authoring";
|
|
736
|
+
toggle.title = nextMode === "tutorial" ? "Switch to Tutorial" : "Switch to Authoring";
|
|
737
|
+
toggle.setAttribute("aria-label", toggle.title);
|
|
738
|
+
toggle.setAttribute("aria-pressed", state.workbenchMode === "tutorial" ? "true" : "false");
|
|
739
|
+
}
|
|
740
|
+
updateWorkbenchTitle();
|
|
741
|
+
if (state.experienceMode !== "execution") {
|
|
742
|
+
try {
|
|
743
|
+
window.localStorage.setItem("methodology-workbench-mode", state.workbenchMode);
|
|
744
|
+
} catch {
|
|
745
|
+
// Ignore storage restrictions; the visible state is already updated.
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
renderMethodologyTree();
|
|
749
|
+
if (state.artifact) {
|
|
750
|
+
renderArtifactStandard(state.artifact, state.artifactDocuments);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function initModeControls() {
|
|
755
|
+
if (state.experienceMode === "execution") {
|
|
756
|
+
state.workbenchMode = "tutorial";
|
|
757
|
+
} else {
|
|
758
|
+
try {
|
|
759
|
+
const storedMode = window.localStorage.getItem("methodology-workbench-mode");
|
|
760
|
+
state.workbenchMode = storedMode === "tutorial"
|
|
761
|
+
? "tutorial"
|
|
762
|
+
: "authoring";
|
|
763
|
+
} catch {
|
|
764
|
+
state.workbenchMode = "authoring";
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
document.getElementById("mode-toggle")?.addEventListener("click", (event) => {
|
|
768
|
+
const target = event.currentTarget?.dataset?.modeTarget;
|
|
769
|
+
setWorkbenchMode(target);
|
|
770
|
+
});
|
|
771
|
+
setWorkbenchMode(state.workbenchMode);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function applyArtifactViewOptions() {
|
|
775
|
+
document.body.classList.toggle("artifact-full-width", state.artifactFullWidth);
|
|
776
|
+
document.body.classList.toggle("artifact-compact-padding", state.artifactCompactPadding);
|
|
777
|
+
document.querySelectorAll("[data-artifact-option='full-width']").forEach((button) => {
|
|
778
|
+
button.classList.toggle("active", state.artifactFullWidth);
|
|
779
|
+
button.setAttribute("aria-pressed", state.artifactFullWidth ? "true" : "false");
|
|
780
|
+
});
|
|
781
|
+
document.querySelectorAll("[data-artifact-option='compact-padding']").forEach((button) => {
|
|
782
|
+
button.classList.toggle("active", state.artifactCompactPadding);
|
|
783
|
+
button.setAttribute("aria-pressed", state.artifactCompactPadding ? "true" : "false");
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function setArtifactViewOption(option, enabled) {
|
|
788
|
+
if (option === "full-width") {
|
|
789
|
+
state.artifactFullWidth = enabled;
|
|
790
|
+
}
|
|
791
|
+
if (option === "compact-padding") {
|
|
792
|
+
state.artifactCompactPadding = enabled;
|
|
793
|
+
}
|
|
794
|
+
try {
|
|
795
|
+
window.localStorage.setItem("methodology-workbench-artifact-full-width", state.artifactFullWidth ? "true" : "false");
|
|
796
|
+
window.localStorage.setItem("methodology-workbench-artifact-compact-padding", state.artifactCompactPadding ? "true" : "false");
|
|
797
|
+
} catch {
|
|
798
|
+
// Ignore storage restrictions; the visible state is already updated.
|
|
799
|
+
}
|
|
800
|
+
applyArtifactViewOptions();
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
function initArtifactViewOptions() {
|
|
804
|
+
try {
|
|
805
|
+
state.artifactFullWidth = window.localStorage.getItem("methodology-workbench-artifact-full-width") === "true";
|
|
806
|
+
state.artifactCompactPadding = window.localStorage.getItem("methodology-workbench-artifact-compact-padding") === "true";
|
|
807
|
+
} catch {
|
|
808
|
+
state.artifactFullWidth = false;
|
|
809
|
+
state.artifactCompactPadding = false;
|
|
810
|
+
}
|
|
811
|
+
applyArtifactViewOptions();
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function draftFor(kind, code) {
|
|
815
|
+
return (state.authoring?.drafts ?? []).find((draft) => draft.targetKind === kind && draft.targetCode === code);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function toggleStage(stageCode) {
|
|
819
|
+
if (state.expandedStageCodes.has(stageCode)) {
|
|
820
|
+
state.expandedStageCodes.delete(stageCode);
|
|
821
|
+
} else {
|
|
822
|
+
state.expandedStageCodes.add(stageCode);
|
|
823
|
+
}
|
|
824
|
+
renderMethodologyTree();
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
async function toggleActivity(activityCode) {
|
|
828
|
+
if (state.expandedActivityCodes.has(activityCode)) {
|
|
829
|
+
state.expandedActivityCodes.delete(activityCode);
|
|
830
|
+
renderMethodologyTree();
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
state.expandedActivityCodes.add(activityCode);
|
|
834
|
+
await loadActivity(activityCode, {
|
|
835
|
+
preserveExpansion: true,
|
|
836
|
+
preserveSelection: state.activity?.activity.code === activityCode
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function toggleTask(taskCode) {
|
|
841
|
+
if (state.expandedTaskCodes.has(taskCode)) {
|
|
842
|
+
state.expandedTaskCodes.delete(taskCode);
|
|
843
|
+
} else {
|
|
844
|
+
state.expandedTaskCodes.add(taskCode);
|
|
845
|
+
}
|
|
846
|
+
renderMethodologyTree();
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function field(labelText, element, className = "") {
|
|
850
|
+
const label = document.createElement("label");
|
|
851
|
+
label.className = ["editor-field", className].filter(Boolean).join(" ");
|
|
852
|
+
const labelSpan = document.createElement("span");
|
|
853
|
+
labelSpan.textContent = labelText;
|
|
854
|
+
label.append(labelSpan, element);
|
|
855
|
+
return label;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function editorSection(title, children, className = "") {
|
|
859
|
+
const section = document.createElement("section");
|
|
860
|
+
section.className = ["editor-section", className].filter(Boolean).join(" ");
|
|
861
|
+
const heading = document.createElement("h4");
|
|
862
|
+
heading.className = "editor-section-title";
|
|
863
|
+
heading.textContent = title;
|
|
864
|
+
const body = document.createElement("div");
|
|
865
|
+
body.className = "editor-section-body";
|
|
866
|
+
body.append(...children);
|
|
867
|
+
section.append(heading, body);
|
|
868
|
+
return section;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function syncEditorMode(form, mode) {
|
|
872
|
+
form.dataset.mode = mode === "SCRIPT" ? "SCRIPT" : mode === "MANUAL" ? "MANUAL" : "LLM";
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
function renderInlineStepEditor(step) {
|
|
876
|
+
const draft = draftFor("step", step.code);
|
|
877
|
+
const wrapper = document.createElement("div");
|
|
878
|
+
wrapper.className = "inline-step-body";
|
|
879
|
+
|
|
880
|
+
const form = document.createElement("form");
|
|
881
|
+
form.className = "editor-form";
|
|
882
|
+
form.id = "step-editor";
|
|
883
|
+
|
|
884
|
+
const titleInput = document.createElement("input");
|
|
885
|
+
titleInput.id = "editor-title";
|
|
886
|
+
titleInput.name = "title";
|
|
887
|
+
titleInput.autocomplete = "off";
|
|
888
|
+
titleInput.value = draft?.title ?? step.title ?? step.name ?? "";
|
|
889
|
+
|
|
890
|
+
const modeSelect = document.createElement("select");
|
|
891
|
+
modeSelect.id = "editor-execution-mode";
|
|
892
|
+
modeSelect.name = "executionModeCode";
|
|
893
|
+
["LLM", "SCRIPT", "MANUAL"].forEach((mode) => {
|
|
894
|
+
const option = document.createElement("option");
|
|
895
|
+
option.value = mode;
|
|
896
|
+
option.textContent = mode;
|
|
897
|
+
modeSelect.append(option);
|
|
898
|
+
});
|
|
899
|
+
modeSelect.value = draft?.executionModeCode ?? step.executionModeCode ?? "LLM";
|
|
900
|
+
modeSelect.addEventListener("change", () => syncEditorMode(form, modeSelect.value));
|
|
901
|
+
|
|
902
|
+
const scriptInput = document.createElement("input");
|
|
903
|
+
scriptInput.id = "editor-script-path";
|
|
904
|
+
scriptInput.name = "scriptPath";
|
|
905
|
+
scriptInput.autocomplete = "off";
|
|
906
|
+
scriptInput.placeholder = "methodology-authoring/step-assets/...";
|
|
907
|
+
scriptInput.value = draft?.scriptPath ?? "";
|
|
908
|
+
|
|
909
|
+
const instruction = document.createElement("textarea");
|
|
910
|
+
instruction.id = "editor-instruction";
|
|
911
|
+
instruction.name = "instruction";
|
|
912
|
+
instruction.rows = 8;
|
|
913
|
+
instruction.value = draft?.instruction ?? step.instruction ?? "";
|
|
914
|
+
|
|
915
|
+
const notes = document.createElement("textarea");
|
|
916
|
+
notes.id = "editor-notes";
|
|
917
|
+
notes.name = "notes";
|
|
918
|
+
notes.rows = 2;
|
|
919
|
+
notes.value = draft?.notes ?? "";
|
|
920
|
+
|
|
921
|
+
const validation = document.createElement("div");
|
|
922
|
+
validation.className = "validation-list";
|
|
923
|
+
validation.id = "editor-validation";
|
|
924
|
+
|
|
925
|
+
const review = document.createElement("div");
|
|
926
|
+
review.className = "compact-review";
|
|
927
|
+
review.innerHTML = "<div class='compact-review-header'><div><span class='field-label'>Apply Review</span><p id='review-target'>No draft selected</p></div><span class='status-pill neutral' id='review-status'>Idle</span></div><div class='diff-list compact' id='apply-plan'></div>";
|
|
928
|
+
|
|
929
|
+
const actions = document.createElement("div");
|
|
930
|
+
actions.className = "form-actions";
|
|
931
|
+
const apply = document.createElement("button");
|
|
932
|
+
apply.className = "secondary-button";
|
|
933
|
+
apply.id = "apply-draft";
|
|
934
|
+
apply.type = "button";
|
|
935
|
+
apply.textContent = "Apply Draft";
|
|
936
|
+
const save = document.createElement("button");
|
|
937
|
+
save.className = "primary-button";
|
|
938
|
+
save.type = "submit";
|
|
939
|
+
save.textContent = "Save Draft";
|
|
940
|
+
actions.append(apply, save);
|
|
941
|
+
|
|
942
|
+
syncEditorMode(form, modeSelect.value);
|
|
943
|
+
form.append(
|
|
944
|
+
editorSection("Step Definition", [
|
|
945
|
+
field("Title", titleInput),
|
|
946
|
+
field("Execution Mode", modeSelect)
|
|
947
|
+
], "editor-section-compact"),
|
|
948
|
+
editorSection("Execution Authoring", [
|
|
949
|
+
field("Script Path", scriptInput, "script-field"),
|
|
950
|
+
field("Instruction", instruction)
|
|
951
|
+
]),
|
|
952
|
+
editorSection("Notes and Review", [
|
|
953
|
+
field("Notes", notes),
|
|
954
|
+
validation,
|
|
955
|
+
review,
|
|
956
|
+
actions
|
|
957
|
+
])
|
|
958
|
+
);
|
|
959
|
+
form.addEventListener("submit", saveStepDraft);
|
|
960
|
+
apply.addEventListener("click", applySelectedDraft);
|
|
961
|
+
|
|
962
|
+
wrapper.append(form);
|
|
963
|
+
setTimeout(() => {
|
|
964
|
+
renderValidation(draft?.validation);
|
|
965
|
+
void refreshApplyPlan();
|
|
966
|
+
}, 0);
|
|
967
|
+
return wrapper;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
const TREE_KIND_LABELS = {
|
|
971
|
+
stage: { english: "Stage", korean: "단계" },
|
|
972
|
+
activity: { english: "Activity", korean: "활동" },
|
|
973
|
+
task: { english: "Task", korean: "작업" },
|
|
974
|
+
step: { english: "Step", korean: "절차" }
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
function treeKindTitle(kind) {
|
|
978
|
+
const label = TREE_KIND_LABELS[kind];
|
|
979
|
+
return label ? label.english + " / " + label.korean : kind;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
function displayPhaseTabLabel(value) {
|
|
983
|
+
return String(value ?? "")
|
|
984
|
+
.replace(/\\s+Phase$/i, "")
|
|
985
|
+
.replace(/\\s+페이즈$/, "");
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
function displayTreeLabel(value) {
|
|
989
|
+
return String(value ?? "")
|
|
990
|
+
.replace(/\\s+(Stage|Activity|Task|Step)$/i, "")
|
|
991
|
+
.replace(/\\s+(단계|활동|작업|절차)$/, "");
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
function formatTreeOrder(value) {
|
|
995
|
+
const numericValue = Number(value);
|
|
996
|
+
if (!Number.isFinite(numericValue) || numericValue < 1) {
|
|
997
|
+
return undefined;
|
|
998
|
+
}
|
|
999
|
+
return String(Math.trunc(numericValue)).padStart(2, "0");
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function treeKindBadge(kind, order) {
|
|
1003
|
+
const label = TREE_KIND_LABELS[kind];
|
|
1004
|
+
const formattedOrder = kind === "task" || kind === "step" ? formatTreeOrder(order) : undefined;
|
|
1005
|
+
const badge = document.createElement("span");
|
|
1006
|
+
badge.className = formattedOrder ? "tree-kind-badge with-order " + kind : "tree-kind-badge " + kind;
|
|
1007
|
+
badge.title = formattedOrder ? treeKindTitle(kind) + " " + formattedOrder : treeKindTitle(kind);
|
|
1008
|
+
badge.textContent = [label?.korean ?? kind, formattedOrder].filter(Boolean).join(" ");
|
|
1009
|
+
return badge;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
function treeCaret() {
|
|
1013
|
+
const caret = document.createElement("span");
|
|
1014
|
+
caret.className = "tree-caret";
|
|
1015
|
+
caret.setAttribute("aria-hidden", "true");
|
|
1016
|
+
return caret;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
function treeCaretPlaceholder() {
|
|
1020
|
+
const placeholder = document.createElement("span");
|
|
1021
|
+
placeholder.className = "tree-caret-placeholder";
|
|
1022
|
+
placeholder.setAttribute("aria-hidden", "true");
|
|
1023
|
+
return placeholder;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
function treeMetaGroup(kind, countText, order) {
|
|
1027
|
+
const meta = document.createElement("span");
|
|
1028
|
+
meta.className = "tree-meta-group";
|
|
1029
|
+
meta.append(treeKindBadge(kind, order));
|
|
1030
|
+
if (countText) {
|
|
1031
|
+
const count = document.createElement("span");
|
|
1032
|
+
count.className = "tree-count";
|
|
1033
|
+
count.textContent = countText;
|
|
1034
|
+
meta.append(count);
|
|
1035
|
+
}
|
|
1036
|
+
return meta;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
function joinTreeCounts(items) {
|
|
1040
|
+
return items.filter(Boolean).join(" · ");
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
function stepSummaryContent(step, order, expandable = false) {
|
|
1044
|
+
const stepCaret = expandable ? treeCaret() : treeCaretPlaceholder();
|
|
1045
|
+
const stepLabel = document.createElement("span");
|
|
1046
|
+
stepLabel.className = "tree-label";
|
|
1047
|
+
stepLabel.textContent = displayTreeLabel(step.title ?? step.name);
|
|
1048
|
+
const stepMeta = treeMetaGroup("step", undefined, order);
|
|
1049
|
+
const modeChip = document.createElement("span");
|
|
1050
|
+
modeChip.className = "tree-mode-chip";
|
|
1051
|
+
modeChip.textContent = step.executionModeCode ?? "";
|
|
1052
|
+
stepMeta.append(modeChip);
|
|
1053
|
+
const draft = draftFor("step", step.code);
|
|
1054
|
+
if (draft) {
|
|
1055
|
+
const draftChip = document.createElement("span");
|
|
1056
|
+
draftChip.className = draft.validation?.valid ? "tree-state-chip valid" : "tree-state-chip issue";
|
|
1057
|
+
draftChip.textContent = draft.validation?.valid ? "Draft" : "Issue";
|
|
1058
|
+
stepMeta.append(draftChip);
|
|
1059
|
+
}
|
|
1060
|
+
return [stepCaret, stepLabel, stepMeta];
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
function renderSelectedStepPanel(step, task, order) {
|
|
1064
|
+
const expanded = state.expandedStepDetailCodes.has(step.code);
|
|
1065
|
+
const panel = document.createElement("details");
|
|
1066
|
+
panel.className = "inline-step-panel";
|
|
1067
|
+
panel.open = expanded;
|
|
1068
|
+
panel.addEventListener("toggle", () => {
|
|
1069
|
+
if (panel.open) {
|
|
1070
|
+
state.expandedStepDetailCodes.add(step.code);
|
|
1071
|
+
} else {
|
|
1072
|
+
state.expandedStepDetailCodes.delete(step.code);
|
|
1073
|
+
}
|
|
1074
|
+
summary.setAttribute("aria-expanded", panel.open ? "true" : "false");
|
|
1075
|
+
});
|
|
1076
|
+
const summary = document.createElement("summary");
|
|
1077
|
+
summary.className = "tree-node step active";
|
|
1078
|
+
summary.setAttribute("role", "treeitem");
|
|
1079
|
+
summary.setAttribute("aria-expanded", expanded ? "true" : "false");
|
|
1080
|
+
summary.append(...stepSummaryContent(step, order, true));
|
|
1081
|
+
panel.append(summary);
|
|
1082
|
+
panel.append(renderInlineStepEditor(step, task));
|
|
1083
|
+
return panel;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
function renderStepButton(step, task, active = false, order = "") {
|
|
1087
|
+
const stepButton = document.createElement("button");
|
|
1088
|
+
stepButton.type = "button";
|
|
1089
|
+
stepButton.className = active ? "tree-node step active" : "tree-node step";
|
|
1090
|
+
stepButton.setAttribute("role", "treeitem");
|
|
1091
|
+
stepButton.append(...stepSummaryContent(step, order));
|
|
1092
|
+
stepButton.addEventListener("click", () => selectStep(step, task));
|
|
1093
|
+
return stepButton;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
function renderMethodologyTree() {
|
|
1097
|
+
const container = document.getElementById("methodology-tree");
|
|
1098
|
+
if (!container) {
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
container.replaceChildren();
|
|
1102
|
+
if (!state.phase) {
|
|
1103
|
+
const empty = document.createElement("div");
|
|
1104
|
+
empty.className = "tree-empty";
|
|
1105
|
+
empty.textContent = "Loading methodology tree.";
|
|
1106
|
+
container.append(empty);
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
state.phase.stages.forEach((stageEntry, stageIndex) => {
|
|
1110
|
+
const group = document.createElement("div");
|
|
1111
|
+
group.className = "tree-group";
|
|
1112
|
+
const stageCode = stageEntry.stage.code;
|
|
1113
|
+
const stageExpanded = state.expandedStageCodes.has(stageCode);
|
|
1114
|
+
const stageButton = document.createElement("button");
|
|
1115
|
+
stageButton.type = "button";
|
|
1116
|
+
stageButton.className = stageCode === state.activity?.stage?.code ? "tree-node stage context no-order" : "tree-node stage no-order";
|
|
1117
|
+
stageButton.setAttribute("role", "treeitem");
|
|
1118
|
+
stageButton.setAttribute("aria-expanded", stageExpanded ? "true" : "false");
|
|
1119
|
+
const stageTitle = document.createElement("span");
|
|
1120
|
+
stageTitle.className = "tree-label";
|
|
1121
|
+
stageTitle.textContent = displayTreeLabel(stageEntry.stage.name);
|
|
1122
|
+
const stageTaskCount = stageEntry.activities.reduce((total, activity) => total + (activity.taskCount ?? 0), 0);
|
|
1123
|
+
const stageStepCount = stageEntry.activities.reduce((total, activity) => total + (activity.stepCount ?? 0), 0);
|
|
1124
|
+
const stageMeta = treeMetaGroup("stage", joinTreeCounts([
|
|
1125
|
+
stageEntry.activities.length + " activities",
|
|
1126
|
+
stageTaskCount + " tasks",
|
|
1127
|
+
stageStepCount + " steps"
|
|
1128
|
+
]));
|
|
1129
|
+
stageButton.append(treeCaret(), stageTitle, stageMeta);
|
|
1130
|
+
stageButton.addEventListener("click", () => toggleStage(stageCode));
|
|
1131
|
+
group.append(stageButton);
|
|
1132
|
+
if (!stageExpanded) {
|
|
1133
|
+
container.append(group);
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
stageEntry.activities.forEach((activity, activityIndex) => {
|
|
1137
|
+
const activityExpanded = state.expandedActivityCodes.has(activity.code);
|
|
1138
|
+
const button = document.createElement("button");
|
|
1139
|
+
button.type = "button";
|
|
1140
|
+
button.className = activity.code === state.activity?.activity.code ? "tree-node activity context no-order" : "tree-node activity no-order";
|
|
1141
|
+
button.setAttribute("role", "treeitem");
|
|
1142
|
+
button.setAttribute("aria-expanded", activityExpanded ? "true" : "false");
|
|
1143
|
+
const title = document.createElement("span");
|
|
1144
|
+
title.className = "tree-label";
|
|
1145
|
+
title.textContent = displayTreeLabel(activity.name);
|
|
1146
|
+
const meta = treeMetaGroup("activity", joinTreeCounts([
|
|
1147
|
+
activity.taskCount + " tasks",
|
|
1148
|
+
activity.stepCount + " steps"
|
|
1149
|
+
]));
|
|
1150
|
+
button.append(treeCaret(), title, meta);
|
|
1151
|
+
button.addEventListener("click", () => toggleActivity(activity.code));
|
|
1152
|
+
group.append(button);
|
|
1153
|
+
if (activityExpanded && state.activity?.activity.code === activity.code) {
|
|
1154
|
+
state.activity.tasks.forEach((taskEntry, taskIndex) => {
|
|
1155
|
+
const taskExpanded = state.expandedTaskCodes.has(taskEntry.task.code);
|
|
1156
|
+
const taskButton = document.createElement("button");
|
|
1157
|
+
taskButton.type = "button";
|
|
1158
|
+
taskButton.className = taskEntry.task.code === state.selectedTask?.code ? "tree-node task context" : "tree-node task";
|
|
1159
|
+
taskButton.setAttribute("role", "treeitem");
|
|
1160
|
+
taskButton.setAttribute("aria-expanded", taskExpanded ? "true" : "false");
|
|
1161
|
+
const taskLabel = document.createElement("span");
|
|
1162
|
+
taskLabel.className = "tree-label";
|
|
1163
|
+
taskLabel.textContent = displayTreeLabel(taskEntry.task.name);
|
|
1164
|
+
const taskMeta = treeMetaGroup("task", taskEntry.steps.length + " steps", taskIndex + 1);
|
|
1165
|
+
taskButton.append(treeCaret(), taskLabel, taskMeta);
|
|
1166
|
+
taskButton.addEventListener("click", () => toggleTask(taskEntry.task.code));
|
|
1167
|
+
group.append(taskButton);
|
|
1168
|
+
if (taskExpanded) {
|
|
1169
|
+
taskEntry.steps.forEach((step, stepIndex) => {
|
|
1170
|
+
if (step.code === state.selectedStep?.code) {
|
|
1171
|
+
group.append(
|
|
1172
|
+
state.workbenchMode === "authoring"
|
|
1173
|
+
? renderSelectedStepPanel(step, taskEntry.task, stepIndex + 1)
|
|
1174
|
+
: renderStepButton(step, taskEntry.task, true, stepIndex + 1)
|
|
1175
|
+
);
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
group.append(renderStepButton(step, taskEntry.task, false, stepIndex + 1));
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
container.append(group);
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
function renderValidation(validation) {
|
|
1189
|
+
const container = document.getElementById("editor-validation");
|
|
1190
|
+
if (!container) {
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
container.replaceChildren();
|
|
1194
|
+
const issues = validation?.issues ?? [];
|
|
1195
|
+
if (issues.length === 0) {
|
|
1196
|
+
const item = document.createElement("div");
|
|
1197
|
+
item.className = "validation-item ok";
|
|
1198
|
+
item.textContent = "No validation issues.";
|
|
1199
|
+
container.append(item);
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
issues.forEach((issue) => {
|
|
1203
|
+
const item = document.createElement("div");
|
|
1204
|
+
item.className = "validation-item " + issue.severity;
|
|
1205
|
+
item.textContent = issue.field + ": " + issue.message;
|
|
1206
|
+
container.append(item);
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
function renderApplyPlan(plan) {
|
|
1211
|
+
const container = document.getElementById("apply-plan");
|
|
1212
|
+
if (!container) {
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
container.replaceChildren();
|
|
1216
|
+
if (!plan) {
|
|
1217
|
+
const empty = document.createElement("div");
|
|
1218
|
+
empty.className = "diff-empty";
|
|
1219
|
+
empty.textContent = "Select a step and save a draft.";
|
|
1220
|
+
container.append(empty);
|
|
1221
|
+
text("review-status", "Idle");
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
text("review-target", "Draft changes");
|
|
1225
|
+
text("review-status", plan.applicable ? "Ready" : "No Change");
|
|
1226
|
+
if ((plan.changedFields ?? []).length === 0) {
|
|
1227
|
+
const empty = document.createElement("div");
|
|
1228
|
+
empty.className = "diff-empty";
|
|
1229
|
+
empty.textContent = plan.validation?.valid ? "No seed field changes." : "Draft is not valid.";
|
|
1230
|
+
container.append(empty);
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
plan.changedFields.forEach((change) => {
|
|
1234
|
+
const item = document.createElement("div");
|
|
1235
|
+
item.className = "diff-item";
|
|
1236
|
+
const field = document.createElement("div");
|
|
1237
|
+
field.className = "diff-field";
|
|
1238
|
+
field.textContent = change.field;
|
|
1239
|
+
const values = document.createElement("div");
|
|
1240
|
+
values.className = "diff-values";
|
|
1241
|
+
const before = document.createElement("div");
|
|
1242
|
+
before.className = "diff-before";
|
|
1243
|
+
before.innerHTML = "<span class='diff-label'>Before</span><span></span>";
|
|
1244
|
+
before.children[1].textContent = String(change.before ?? "");
|
|
1245
|
+
const after = document.createElement("div");
|
|
1246
|
+
after.className = "diff-after";
|
|
1247
|
+
after.innerHTML = "<span class='diff-label'>After</span><span></span>";
|
|
1248
|
+
after.children[1].textContent = String(change.after ?? "");
|
|
1249
|
+
values.append(before, after);
|
|
1250
|
+
item.append(field, values);
|
|
1251
|
+
container.append(item);
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
function renderDocumentActions(items) {
|
|
1256
|
+
const row = document.createElement("div");
|
|
1257
|
+
row.className = "document-action-row";
|
|
1258
|
+
items.filter((item) => item?.relativePath).forEach((item) => {
|
|
1259
|
+
const element = item.sourcePath
|
|
1260
|
+
? document.createElement("span")
|
|
1261
|
+
: document.createElement("span");
|
|
1262
|
+
element.textContent = item.label;
|
|
1263
|
+
if (item.relativePath) {
|
|
1264
|
+
element.title = item.relativePath;
|
|
1265
|
+
}
|
|
1266
|
+
row.append(element);
|
|
1267
|
+
});
|
|
1268
|
+
return row;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
function renderDocumentPanel(documentVm, sourceItems = []) {
|
|
1272
|
+
const wrapper = document.createElement("div");
|
|
1273
|
+
wrapper.className = "rendered-document";
|
|
1274
|
+
const body = document.createElement("div");
|
|
1275
|
+
body.className = "rendered-document-body";
|
|
1276
|
+
body.innerHTML = documentVm?.html ?? "<p class='artifact-empty'>Document is not available.</p>";
|
|
1277
|
+
wrapper.append(body);
|
|
1278
|
+
const actions = renderDocumentActions(sourceItems);
|
|
1279
|
+
if (actions.childElementCount > 0) {
|
|
1280
|
+
wrapper.append(actions);
|
|
1281
|
+
}
|
|
1282
|
+
return wrapper;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
function renderArtifactViewToolbar() {
|
|
1286
|
+
const toolbar = document.createElement("div");
|
|
1287
|
+
toolbar.className = "artifact-view-toolbar";
|
|
1288
|
+
[
|
|
1289
|
+
["full-width", "Full Width"],
|
|
1290
|
+
["compact-padding", "Dense View"]
|
|
1291
|
+
].forEach(([option, label]) => {
|
|
1292
|
+
const button = document.createElement("button");
|
|
1293
|
+
button.type = "button";
|
|
1294
|
+
button.className = "viewer-option-button";
|
|
1295
|
+
button.dataset.artifactOption = option;
|
|
1296
|
+
button.setAttribute("aria-pressed", "false");
|
|
1297
|
+
button.textContent = label;
|
|
1298
|
+
button.addEventListener("click", () => {
|
|
1299
|
+
const next = option === "full-width" ? !state.artifactFullWidth : !state.artifactCompactPadding;
|
|
1300
|
+
setArtifactViewOption(option, next);
|
|
1301
|
+
});
|
|
1302
|
+
toolbar.append(button);
|
|
1303
|
+
});
|
|
1304
|
+
setTimeout(applyArtifactViewOptions, 0);
|
|
1305
|
+
return toolbar;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
function renderArtifactStandard(artifactView, documentSet) {
|
|
1309
|
+
const container = document.getElementById("artifact-standard");
|
|
1310
|
+
if (!container) {
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
container.replaceChildren();
|
|
1314
|
+
if (!artifactView?.artifact) {
|
|
1315
|
+
text("artifact-code", "No artifact selected");
|
|
1316
|
+
text("artifact-status", "None");
|
|
1317
|
+
const empty = document.createElement("div");
|
|
1318
|
+
empty.className = "artifact-empty";
|
|
1319
|
+
empty.textContent = "Select an Activity with a primary artifact standard.";
|
|
1320
|
+
container.append(empty);
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
const artifact = artifactView.artifact;
|
|
1324
|
+
const documents = documentSet?.documents ?? {};
|
|
1325
|
+
text("artifact-code", artifact.code);
|
|
1326
|
+
text("artifact-status", artifact.statusCode ?? artifact.typeCode ?? "Seed");
|
|
1327
|
+
|
|
1328
|
+
const tabset = document.createElement("section");
|
|
1329
|
+
tabset.className = "artifact-tabset";
|
|
1330
|
+
const tabList = document.createElement("div");
|
|
1331
|
+
tabList.className = "artifact-tab-list";
|
|
1332
|
+
tabList.role = "tablist";
|
|
1333
|
+
const panels = document.createElement("div");
|
|
1334
|
+
panels.className = "artifact-tab-panels";
|
|
1335
|
+
|
|
1336
|
+
const tabs = [
|
|
1337
|
+
{
|
|
1338
|
+
id: "concept-technique",
|
|
1339
|
+
label: "개념 및 기법서",
|
|
1340
|
+
render: () => renderDocumentPanel(documents.conceptTechnique, [
|
|
1341
|
+
{ label: documents.commonConceptFormat?.relativePath, relativePath: documents.commonConceptFormat?.relativePath },
|
|
1342
|
+
{ label: documents.commonConceptTemplate?.relativePath, relativePath: documents.commonConceptTemplate?.relativePath },
|
|
1343
|
+
{ label: documents.commonConceptSample?.relativePath, relativePath: documents.commonConceptSample?.relativePath }
|
|
1344
|
+
])
|
|
1345
|
+
},
|
|
1346
|
+
{
|
|
1347
|
+
id: "template",
|
|
1348
|
+
label: "산출물 서식 및 가이드",
|
|
1349
|
+
render: () => renderDocumentPanel(documents.template, [
|
|
1350
|
+
{ label: documents.formatMetadata?.relativePath, relativePath: documents.formatMetadata?.relativePath },
|
|
1351
|
+
{ label: documents.template?.relativePath, relativePath: documents.template?.relativePath }
|
|
1352
|
+
])
|
|
1353
|
+
},
|
|
1354
|
+
{
|
|
1355
|
+
id: "sample",
|
|
1356
|
+
label: "산출물 예시",
|
|
1357
|
+
render: () => renderDocumentPanel(documents.sample, [
|
|
1358
|
+
{ label: documents.sample?.relativePath, relativePath: documents.sample?.relativePath }
|
|
1359
|
+
])
|
|
1360
|
+
}
|
|
1361
|
+
];
|
|
1362
|
+
|
|
1363
|
+
tabs.forEach((tab, index) => {
|
|
1364
|
+
const button = document.createElement("button");
|
|
1365
|
+
button.type = "button";
|
|
1366
|
+
button.className = index === 0 ? "artifact-tab active" : "artifact-tab";
|
|
1367
|
+
button.role = "tab";
|
|
1368
|
+
button.setAttribute("aria-selected", index === 0 ? "true" : "false");
|
|
1369
|
+
button.textContent = tab.label;
|
|
1370
|
+
const panel = document.createElement("section");
|
|
1371
|
+
panel.className = "artifact-tab-panel";
|
|
1372
|
+
panel.role = "tabpanel";
|
|
1373
|
+
panel.hidden = index !== 0;
|
|
1374
|
+
panel.append(tab.render());
|
|
1375
|
+
button.addEventListener("click", () => {
|
|
1376
|
+
Array.from(tabList.children).forEach((item) => {
|
|
1377
|
+
const active = item === button;
|
|
1378
|
+
item.classList.toggle("active", active);
|
|
1379
|
+
item.setAttribute("aria-selected", active ? "true" : "false");
|
|
1380
|
+
});
|
|
1381
|
+
Array.from(panels.children).forEach((item) => {
|
|
1382
|
+
item.hidden = item !== panel;
|
|
1383
|
+
});
|
|
1384
|
+
});
|
|
1385
|
+
tabList.append(button);
|
|
1386
|
+
panels.append(panel);
|
|
1387
|
+
});
|
|
1388
|
+
|
|
1389
|
+
tabList.append(renderArtifactViewToolbar());
|
|
1390
|
+
tabset.append(tabList, panels);
|
|
1391
|
+
container.append(tabset);
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
async function loadArtifactStandard(artifactCode) {
|
|
1395
|
+
if (!artifactCode) {
|
|
1396
|
+
state.artifact = null;
|
|
1397
|
+
state.artifactDocuments = null;
|
|
1398
|
+
renderArtifactStandard(null, null);
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
const [artifact, documents] = await Promise.all([
|
|
1402
|
+
loadJson("/api/workbench/artifacts/" + encodeURIComponent(artifactCode)),
|
|
1403
|
+
loadJson("/api/workbench/artifacts/" + encodeURIComponent(artifactCode) + "/documents")
|
|
1404
|
+
]);
|
|
1405
|
+
state.artifact = artifact;
|
|
1406
|
+
state.artifactDocuments = documents;
|
|
1407
|
+
renderArtifactStandard(state.artifact, state.artifactDocuments);
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
async function refreshApplyPlan() {
|
|
1411
|
+
if (!state.selectedStep) {
|
|
1412
|
+
renderApplyPlan(null);
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
const draft = draftFor("step", state.selectedStep.code);
|
|
1416
|
+
if (!draft) {
|
|
1417
|
+
renderApplyPlan(null);
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
const plan = await loadJson("/api/workbench/authoring/steps/" + encodeURIComponent(state.selectedStep.code) + "/apply-plan");
|
|
1421
|
+
renderApplyPlan(plan);
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
function renderActivity(activity) {
|
|
1425
|
+
state.activity = activity;
|
|
1426
|
+
if (activity.stage?.code) {
|
|
1427
|
+
state.expandedStageCodes.add(activity.stage.code);
|
|
1428
|
+
}
|
|
1429
|
+
state.expandedActivityCodes.add(activity.activity.code);
|
|
1430
|
+
renderMethodologyTree();
|
|
1431
|
+
void loadArtifactStandard(activity.primaryArtifact?.code);
|
|
1432
|
+
const selectedExists = activity.tasks.some((taskEntry) => taskEntry.steps.some((step) => step.code === state.selectedStep?.code));
|
|
1433
|
+
if (!selectedExists) {
|
|
1434
|
+
const firstTask = activity.tasks[0];
|
|
1435
|
+
const firstStep = firstTask?.steps[0];
|
|
1436
|
+
if (firstTask && firstStep) {
|
|
1437
|
+
state.expandedTaskCodes.add(firstTask.task.code);
|
|
1438
|
+
void selectStep(firstStep, firstTask.task);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
async function selectStep(step, task) {
|
|
1444
|
+
state.selectedStep = step;
|
|
1445
|
+
state.selectedTask = task;
|
|
1446
|
+
state.expandedTaskCodes.add(task.code);
|
|
1447
|
+
renderMethodologyTree();
|
|
1448
|
+
if (state.artifact) {
|
|
1449
|
+
renderArtifactStandard(state.artifact, state.artifactDocuments);
|
|
1450
|
+
}
|
|
1451
|
+
if (state.workbenchMode !== "authoring") {
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
const draft = draftFor("step", step.code);
|
|
1455
|
+
text("editor-status", draft ? (draft.validation.valid ? "Draft Valid" : "Draft Issues") : "Seed");
|
|
1456
|
+
setValue("editor-title", draft?.title ?? step.title ?? step.name);
|
|
1457
|
+
setValue("editor-execution-mode", draft?.executionModeCode ?? step.executionModeCode ?? "LLM");
|
|
1458
|
+
setValue("editor-script-path", draft?.scriptPath ?? "");
|
|
1459
|
+
setValue("editor-instruction", draft?.instruction ?? step.instruction ?? "");
|
|
1460
|
+
setValue("editor-notes", draft?.notes ?? "");
|
|
1461
|
+
renderValidation(draft?.validation);
|
|
1462
|
+
await refreshApplyPlan();
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
async function loadActivity(activityCode, options = {}) {
|
|
1466
|
+
const activity = await loadJson("/api/workbench/activities/" + encodeURIComponent(activityCode));
|
|
1467
|
+
state.authoring = await loadJson("/api/workbench/activities/" + encodeURIComponent(activityCode) + "/authoring");
|
|
1468
|
+
if (!options.preserveSelection) {
|
|
1469
|
+
state.selectedStep = null;
|
|
1470
|
+
state.selectedTask = null;
|
|
1471
|
+
}
|
|
1472
|
+
if (!options.preserveExpansion) {
|
|
1473
|
+
state.expandedActivityCodes.add(activityCode);
|
|
1474
|
+
}
|
|
1475
|
+
renderActivity(activity);
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
async function saveStepDraft(event) {
|
|
1479
|
+
event.preventDefault();
|
|
1480
|
+
if (!state.selectedStep || !state.selectedTask || !state.activity) {
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
const payload = {
|
|
1484
|
+
activityCode: state.activity.activity.code,
|
|
1485
|
+
taskCode: state.selectedTask.code,
|
|
1486
|
+
title: valueOf("editor-title"),
|
|
1487
|
+
executionModeCode: valueOf("editor-execution-mode"),
|
|
1488
|
+
scriptPath: valueOf("editor-script-path"),
|
|
1489
|
+
instruction: valueOf("editor-instruction"),
|
|
1490
|
+
notes: valueOf("editor-notes")
|
|
1491
|
+
};
|
|
1492
|
+
const draft = await putJson("/api/workbench/authoring/steps/" + encodeURIComponent(state.selectedStep.code) + "/draft", payload);
|
|
1493
|
+
state.authoring = state.authoring ?? { drafts: [] };
|
|
1494
|
+
state.authoring.drafts = [
|
|
1495
|
+
...(state.authoring.drafts ?? []).filter((item) => item.targetCode !== draft.targetCode || item.targetKind !== draft.targetKind),
|
|
1496
|
+
draft
|
|
1497
|
+
];
|
|
1498
|
+
text("editor-status", draft.validation.valid ? "Draft Valid" : "Draft Issues");
|
|
1499
|
+
renderValidation(draft.validation);
|
|
1500
|
+
renderMethodologyTree();
|
|
1501
|
+
await refreshApplyPlan();
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
async function applySelectedDraft() {
|
|
1505
|
+
if (!state.selectedStep || !state.activity) {
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
const draft = draftFor("step", state.selectedStep.code);
|
|
1509
|
+
if (!draft) {
|
|
1510
|
+
renderValidation({ issues: [{ field: "draft", severity: "error", message: "Save a draft before applying it to seed." }] });
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
if (!draft.validation.valid) {
|
|
1514
|
+
text("editor-status", "Draft Issues");
|
|
1515
|
+
renderValidation(draft.validation);
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
const result = await postJson("/api/workbench/authoring/steps/" + encodeURIComponent(state.selectedStep.code) + "/apply", {});
|
|
1519
|
+
text("editor-status", result.applied ? "Applied" : "Not Applied");
|
|
1520
|
+
renderApplyPlan(result);
|
|
1521
|
+
const activityCode = state.activity.activity.code;
|
|
1522
|
+
const selectedStepCode = state.selectedStep.code;
|
|
1523
|
+
const activity = await loadJson("/api/workbench/activities/" + encodeURIComponent(activityCode));
|
|
1524
|
+
state.authoring = await loadJson("/api/workbench/activities/" + encodeURIComponent(activityCode) + "/authoring");
|
|
1525
|
+
renderActivity(activity);
|
|
1526
|
+
const taskEntry = activity.tasks.find((entry) => entry.steps.some((step) => step.code === selectedStepCode));
|
|
1527
|
+
const step = taskEntry?.steps.find((item) => item.code === selectedStepCode);
|
|
1528
|
+
if (taskEntry && step) {
|
|
1529
|
+
await selectStep(step, taskEntry.task);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
async function init() {
|
|
1534
|
+
initSidebarControls();
|
|
1535
|
+
initModeControls();
|
|
1536
|
+
initArtifactViewOptions();
|
|
1537
|
+
state.lifecycle = await loadJson("/api/workbench/lifecycle");
|
|
1538
|
+
renderLifecycleLanding();
|
|
1539
|
+
renderPhaseTabs();
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
init().catch((error) => {
|
|
1543
|
+
text("editor-step-code", error instanceof Error ? error.message : String(error));
|
|
1544
|
+
});`;
|
|
1545
|
+
}
|
|
1546
|
+
function escapeHtml(value) {
|
|
1547
|
+
return value
|
|
1548
|
+
.replaceAll("&", "&")
|
|
1549
|
+
.replaceAll("<", "<")
|
|
1550
|
+
.replaceAll(">", ">")
|
|
1551
|
+
.replaceAll('"', """);
|
|
1552
|
+
}
|
|
1553
|
+
//# sourceMappingURL=workbench-server.js.map
|