@worca/app 1.2.0-rc.1 → 1.2.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -2
- package/package.json +1 -1
- package/src/cli/worca-cc.mjs +358 -19
- package/src/core/ask/models.mjs +7 -1
- package/src/core/ask/turn.mjs +5 -0
- package/src/core/config.mjs +33 -4
- package/src/core/folder-dialog.mjs +21 -12
- package/src/core/marketplaces.mjs +13 -0
- package/src/core/model-env.mjs +41 -0
- package/src/core/model-test.mjs +2 -1
- package/src/core/plugin-manifest.mjs +45 -8
- package/src/core/run-harness.mjs +7 -0
- package/src/core/settings.mjs +61 -0
- package/src/core/title.mjs +72 -9
- package/src/core/ui-instance.mjs +262 -0
- package/src/core/workflow-export.mjs +1169 -0
- package/src/core/workflow-share.mjs +191 -0
- package/src/core/workflows.mjs +35 -4
- package/src/shared/graph/validate.mjs +5 -3
- package/ui/public/app.js +653 -22
- package/ui/public/ask-panel.mjs +4 -2
- package/ui/public/export-slug.mjs +30 -0
- package/ui/public/graph/composer.mjs +24 -3
- package/ui/public/graph/inspector.mjs +4 -1
- package/ui/public/index.html +127 -1
- package/ui/public/models-view.mjs +34 -3
- package/ui/public/style.css +65 -5
- package/ui/server.mjs +305 -90
package/ui/public/ask-panel.mjs
CHANGED
|
@@ -829,8 +829,9 @@ export function createAskPanel({ doc, win, fetch, sendWs, confirm, getPageContex
|
|
|
829
829
|
};
|
|
830
830
|
const list = st.catalog && Array.isArray(st.catalog.models) ? st.catalog.models : [];
|
|
831
831
|
const wantedEntry = catalogEntry(wanted.model);
|
|
832
|
-
// Unknown stored/default id -> the backend default -> the first model we do
|
|
833
|
-
|
|
832
|
+
// Unknown stored/default id -> the backend default -> the first model we do
|
|
833
|
+
// have that is not a hidden built-in (#422; a hidden id is still a valid pick).
|
|
834
|
+
const entry = wantedEntry || catalogEntry(fallback.model) || list.find((m) => m && !m.hidden) || list[0] || null;
|
|
834
835
|
if (!entry) { updatePickerButton(); return; } // empty catalog: keep what we have
|
|
835
836
|
const effort = wantedEntry ? wanted.effort : fallback.effort;
|
|
836
837
|
const next = { model: entry.id, effort: coerceEffort(entry, effort) };
|
|
@@ -885,6 +886,7 @@ export function createAskPanel({ doc, win, fetch, sendWs, confirm, getPageContex
|
|
|
885
886
|
const seen = new Set();
|
|
886
887
|
for (const m of st.catalog ? st.catalog.models : []) {
|
|
887
888
|
if (!m || typeof m.id !== 'string') continue;
|
|
889
|
+
if (m.hidden && m.id !== st.picker.model) continue; // hidden built-in (#422); the current pick stays
|
|
888
890
|
if (m.custom === 'global') { primary.push(m); continue; } // user models are never demoted
|
|
889
891
|
const fam = familyKey(m);
|
|
890
892
|
// The picked model always shows up front so its ✓ is visible and it is one click away.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// ui/public/export-slug.mjs
|
|
2
|
+
// Pure, DOM-free slug logic for the Export-to-Claude-Code modal, in its own module so
|
|
3
|
+
// it can be unit-tested (and drift-guarded against src/core/skills.mjs isValidSkillName)
|
|
4
|
+
// without importing the whole browser app. app.js imports exportSlugPreview from here.
|
|
5
|
+
|
|
6
|
+
// slugifyLocal(s) -> mirrors src/core/artifacts.mjs slugify so the export modal previews
|
|
7
|
+
// the same default slug the server would derive from a workflow name.
|
|
8
|
+
export function slugifyLocal(s) {
|
|
9
|
+
const out = String(s ?? '')
|
|
10
|
+
.normalize('NFKD')
|
|
11
|
+
.replace(/[̀-ͯ]/g, '') // strip combining marks
|
|
12
|
+
.toLowerCase()
|
|
13
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
14
|
+
.replace(/-{2,}/g, '-')
|
|
15
|
+
.replace(/^-+|-+$/g, '');
|
|
16
|
+
return out || 'untitled';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// exportSlugPreview(rawSlug, workflowName) -> { slug, preview, valid }
|
|
20
|
+
// An explicit slug is used verbatim (and must be a valid skill name); otherwise the
|
|
21
|
+
// workflow name is slugified and capped at 48. `preview` is the "/<slug>" the user types.
|
|
22
|
+
export function exportSlugPreview(rawSlug, workflowName) {
|
|
23
|
+
const explicit = typeof rawSlug === 'string' && rawSlug.trim();
|
|
24
|
+
const slug = explicit ? rawSlug.trim() : (slugifyLocal(workflowName).slice(0, 48) || 'workflow');
|
|
25
|
+
// MUST stay in step with isValidSkillName in src/core/skills.mjs — the core's assertName
|
|
26
|
+
// rejects on Apply what this reports invalid. skills.mjs pulls node:fs so it cannot be
|
|
27
|
+
// imported in the browser; export-slug.test.mjs guards these two against drift.
|
|
28
|
+
const valid = /^[A-Za-z0-9._-]+$/.test(slug) && slug !== '.' && slug !== '..';
|
|
29
|
+
return { slug, preview: `/${slug}`, valid };
|
|
30
|
+
}
|
|
@@ -175,12 +175,33 @@ export function createComposer(hostEls, { doc = globalThis.document, api, raf =
|
|
|
175
175
|
paintInspector();
|
|
176
176
|
if (hooks.onRender) hooks.onRender();
|
|
177
177
|
}
|
|
178
|
+
/** A canvas nobody has touched: the two bookends, no wires, no edits. */
|
|
179
|
+
function isPristine() {
|
|
180
|
+
return !dirty && tpl.wires.length === 0 && tpl.nodes.length === 2
|
|
181
|
+
&& tpl.nodes.every((n) => n.kind === 'task' || n.kind === 'end');
|
|
182
|
+
}
|
|
178
183
|
function paintChrome() {
|
|
184
|
+
// "Unfinished" (a mandatory port still to wire — validate.mjs flags those
|
|
185
|
+
// `incomplete`) is not "wrong". Save is gated by BOTH, but only real mistakes
|
|
186
|
+
// earn the red chip; unfinished wiring gets a quiet to-do chip, and a canvas
|
|
187
|
+
// nobody has touched yet shows no chip at all — a fresh drawing has nothing
|
|
188
|
+
// to shout about.
|
|
179
189
|
const n = lastReport.errors.length;
|
|
180
|
-
|
|
190
|
+
const real = lastReport.errors.filter((e) => !e.incomplete).length;
|
|
191
|
+
const todo = n - real;
|
|
192
|
+
if (hostEls.saveBtn) {
|
|
193
|
+
hostEls.saveBtn.disabled = n > 0 || !ready;
|
|
194
|
+
hostEls.saveBtn.title = real
|
|
195
|
+
? 'Fix the errors to save'
|
|
196
|
+
: todo ? 'Wire Task → … → End to save' : '';
|
|
197
|
+
}
|
|
181
198
|
if (hostEls.errors) {
|
|
182
|
-
|
|
183
|
-
hostEls.errors.
|
|
199
|
+
const quiet = !real && todo > 0 && !isPristine();
|
|
200
|
+
hostEls.errors.hidden = !real && !quiet;
|
|
201
|
+
hostEls.errors.classList.toggle('is-incomplete', quiet);
|
|
202
|
+
hostEls.errors.textContent = real
|
|
203
|
+
? `${real} error${real === 1 ? '' : 's'}`
|
|
204
|
+
: quiet ? `${todo} port${todo === 1 ? '' : 's'} to wire` : '';
|
|
184
205
|
}
|
|
185
206
|
}
|
|
186
207
|
|
|
@@ -93,8 +93,11 @@ export function renderNodeInspector(node, { template, portsFn, meta = null, mode
|
|
|
93
93
|
|
|
94
94
|
if (node.kind === 'agent') {
|
|
95
95
|
root.appendChild(head(doc, (meta && meta.displayName) || node.key || node.id, `${node.key} · ${node.id}`));
|
|
96
|
+
// Hidden built-ins (#422) leave the list unless one is THIS node's stored
|
|
97
|
+
// pick — it still resolves at run time and must stay visible here.
|
|
98
|
+
const offered = models.filter((m) => m && (!m.hidden || m.id === node.config.model));
|
|
96
99
|
body.appendChild(select(doc, 'ins-model', 'model', 'Model',
|
|
97
|
-
[{ value: '', text: 'inherit' }, ...
|
|
100
|
+
[{ value: '', text: 'inherit' }, ...offered.map((m) => ({ value: m.id, text: m.label || m.id }))], node.config.model));
|
|
98
101
|
body.appendChild(select(doc, 'ins-effort', 'effort', 'Effort',
|
|
99
102
|
[{ value: '', text: 'default' }, ...efforts.map((e) => ({ value: e, text: e }))], node.config.effort));
|
|
100
103
|
if (meta && meta.fanOut) {
|
package/ui/public/index.html
CHANGED
|
@@ -975,7 +975,17 @@
|
|
|
975
975
|
<section class="card gv-saved">
|
|
976
976
|
<div class="saved-head">
|
|
977
977
|
<div><b>Saved pipelines</b><span class="cnt" id="gv-saved-count"></span></div>
|
|
978
|
+
<!-- Import a workflow another Worca user shared as JSON (#421): the
|
|
979
|
+
file goes through POST /api/workflows/import-json — the same validator
|
|
980
|
+
the composer's Save uses; a taken name gets a " (2)" suffix. -->
|
|
981
|
+
<div class="saved-head-actions">
|
|
982
|
+
<button type="button" id="gv-import-btn" class="btn btn-ghost btn-mini" title="Import a pipeline shared as a JSON file">Import…</button>
|
|
983
|
+
<input id="gv-import-file" type="file" accept=".json,application/json" hidden />
|
|
984
|
+
</div>
|
|
978
985
|
</div>
|
|
986
|
+
<!-- One tab per domain; the row itself no longer repeats the domain.
|
|
987
|
+
Rendered by gvRenderSaved(); hidden while the library is empty. -->
|
|
988
|
+
<div class="gv-saved-tabs" id="gv-saved-tabs" role="tablist" aria-label="Pipeline domains" hidden></div>
|
|
979
989
|
<div class="saved-list" id="gv-saved-list"></div>
|
|
980
990
|
<div class="gv-archived" id="gv-archived" hidden></div>
|
|
981
991
|
<!-- A refused DELETE (the built-in, a 404, a 500) has to land
|
|
@@ -1146,6 +1156,39 @@
|
|
|
1146
1156
|
</div>
|
|
1147
1157
|
</section>
|
|
1148
1158
|
|
|
1159
|
+
<!-- Title generation (#422): which model writes run/chat titles.
|
|
1160
|
+
Empty = the model of the run or chat itself (title.mjs
|
|
1161
|
+
resolveTitleModel), so a first-party-free install needs no setup.
|
|
1162
|
+
The select is built from the project-less catalog; a stored id
|
|
1163
|
+
that left the catalog paints disabled + "not installed". -->
|
|
1164
|
+
<section class="card settings-card" id="title-model-settings-card">
|
|
1165
|
+
<div class="card-head">
|
|
1166
|
+
<div class="label-row">
|
|
1167
|
+
<h2>Title generation</h2>
|
|
1168
|
+
<button type="button" class="info-tip" aria-label="About title generation">i<span class="tip-content hidden">
|
|
1169
|
+
Every run and chat gets a short LLM-written title. By default it is written by the
|
|
1170
|
+
model the run or chat itself uses, so an install without a first-party Anthropic
|
|
1171
|
+
account needs no setup here. Pick a model to write every title with it instead;
|
|
1172
|
+
the change applies to the next title, no restart. A non-empty
|
|
1173
|
+
<code>WORCA_TITLE_MODEL</code> in the environment overrides this setting.
|
|
1174
|
+
</span></button>
|
|
1175
|
+
</div>
|
|
1176
|
+
</div>
|
|
1177
|
+
<div class="field">
|
|
1178
|
+
<div class="label-row">
|
|
1179
|
+
<label for="titleModel">Model</label>
|
|
1180
|
+
</div>
|
|
1181
|
+
<select id="titleModel" class="select" aria-label="Title model"></select>
|
|
1182
|
+
</div>
|
|
1183
|
+
<small class="hint" id="titleModelEnvNote"></small>
|
|
1184
|
+
<div class="add-project-actions" style="margin-top:14px">
|
|
1185
|
+
<button type="button" id="titleModelTest" class="btn btn-ghost btn-mini" title="Send one tiny prompt to the selected model">Test</button>
|
|
1186
|
+
<button type="button" id="titleModelReset" class="btn btn-ghost btn-mini">Use default</button>
|
|
1187
|
+
<button type="button" id="titleModelSave" class="btn btn-primary btn-mini">Save</button>
|
|
1188
|
+
</div>
|
|
1189
|
+
<small class="hint" id="titleModelMsg"></small>
|
|
1190
|
+
</section>
|
|
1191
|
+
|
|
1149
1192
|
<!-- Spawn-debug diagnostics: the stored side of WORCA_DEBUG_SPAWN.
|
|
1150
1193
|
claude-runner.mjs reads the setting per spawn (UI server AND CLI);
|
|
1151
1194
|
a non-empty env var overrides it. This card only flips the gate —
|
|
@@ -1243,7 +1286,7 @@
|
|
|
1243
1286
|
<p id="plugins-msg" class="form-msg" aria-live="polite"></p>
|
|
1244
1287
|
<div class="card hidden" id="marketplace-add-row" style="padding:16px 20px;margin-bottom:14px">
|
|
1245
1288
|
<div class="path-row">
|
|
1246
|
-
<input id="marketplace-url" class="input" type="text" placeholder="https://github.com/owner/repo · owner/repo · /abs/path/to/repo" spellcheck="false" />
|
|
1289
|
+
<input id="marketplace-url" class="input" type="text" placeholder="https://github.com/owner/repo · owner/repo · /abs/path/to/repo · /path/to/a/plugin-folder" spellcheck="false" />
|
|
1247
1290
|
<button type="button" id="marketplace-add" class="btn btn-primary btn-mini">Add</button>
|
|
1248
1291
|
</div>
|
|
1249
1292
|
</div>
|
|
@@ -1311,6 +1354,89 @@
|
|
|
1311
1354
|
</div>
|
|
1312
1355
|
</div>
|
|
1313
1356
|
|
|
1357
|
+
<!-- ===== Export workflow → Claude Code skill modal ===== -->
|
|
1358
|
+
<div id="export-modal" class="viewer-modal hidden" role="dialog" aria-modal="true" aria-labelledby="export-title">
|
|
1359
|
+
<div class="card">
|
|
1360
|
+
<div class="card-head">
|
|
1361
|
+
<h2 id="export-title">Export pipeline</h2>
|
|
1362
|
+
</div>
|
|
1363
|
+
<p id="export-subtitle" class="confirm-message"></p>
|
|
1364
|
+
<!-- The form; swapped for #export-done after a successful export. -->
|
|
1365
|
+
<div id="export-form">
|
|
1366
|
+
<!-- ONE Export… entry point per row; the dialog asks for the format first.
|
|
1367
|
+
json = the saved graph as a file another Worca user imports (#421);
|
|
1368
|
+
skill = a runnable Claude Code skill under .claude/ (#403);
|
|
1369
|
+
plugin = a Worca plugin folder bundling agents + skills (#421). -->
|
|
1370
|
+
<div class="export-field">
|
|
1371
|
+
<span class="export-label">Format</span>
|
|
1372
|
+
<div id="export-format" class="seg" role="group" aria-label="Export format">
|
|
1373
|
+
<button type="button" class="seg-btn on" data-format="json">JSON file</button>
|
|
1374
|
+
<button type="button" class="seg-btn" data-format="skill">Claude Code skill</button>
|
|
1375
|
+
<button type="button" class="seg-btn" data-format="plugin">Worca plugin</button>
|
|
1376
|
+
</div>
|
|
1377
|
+
<span id="export-format-hint" class="hint"></span>
|
|
1378
|
+
</div>
|
|
1379
|
+
<div id="export-dest-field" class="export-field hidden">
|
|
1380
|
+
<span class="export-label">Location</span>
|
|
1381
|
+
<div id="export-dest" class="seg" role="group" aria-label="Skill location">
|
|
1382
|
+
<button type="button" class="seg-btn on" data-dest="global">Global (~/.claude)</button>
|
|
1383
|
+
<button type="button" class="seg-btn" data-dest="project">Project folder</button>
|
|
1384
|
+
</div>
|
|
1385
|
+
</div>
|
|
1386
|
+
<div id="export-folder-field" class="export-field hidden">
|
|
1387
|
+
<span class="export-label" id="export-folder-label">Folder</span>
|
|
1388
|
+
<div class="export-folder-row">
|
|
1389
|
+
<input type="text" id="export-folder" class="input" placeholder="/path/to/repo" />
|
|
1390
|
+
<button type="button" id="export-browse" class="btn btn-ghost btn-mini">Browse…</button>
|
|
1391
|
+
</div>
|
|
1392
|
+
</div>
|
|
1393
|
+
<div id="export-plugin-field" class="export-field hidden">
|
|
1394
|
+
<span class="export-label">Plugin name</span>
|
|
1395
|
+
<input type="text" id="export-plugin-name" class="input" placeholder="(defaults to the folder name)" />
|
|
1396
|
+
<!-- Live: what the manifest will actually be called — kebab-case, derived
|
|
1397
|
+
from the typed name or, when blank, from the folder's basename. -->
|
|
1398
|
+
<span id="export-plugin-name-preview" class="hint"></span>
|
|
1399
|
+
<!-- Re-export only: a changed export into an existing plugin folder bumps
|
|
1400
|
+
the manifest's patch version (0.1.0 → 0.1.1) so the recipient's
|
|
1401
|
+
`worca plugin update` sees it. A first export is always 0.1.0. -->
|
|
1402
|
+
<label class="confirm-checkbox">
|
|
1403
|
+
<input type="checkbox" id="export-keep-version" />
|
|
1404
|
+
<span>Don't bump the plugin version when updating an existing plugin folder<br><span class="hint">A changed re-export normally goes 0.1.0 → 0.1.1 so recipients see an update. A first export is always 0.1.0.</span></span>
|
|
1405
|
+
</label>
|
|
1406
|
+
</div>
|
|
1407
|
+
<div class="export-field" id="export-slug-field">
|
|
1408
|
+
<span class="export-label">Slug</span>
|
|
1409
|
+
<input type="text" id="export-slug" class="input" placeholder="(defaults to the workflow name)" />
|
|
1410
|
+
<span id="export-slug-preview" class="hint"></span>
|
|
1411
|
+
</div>
|
|
1412
|
+
<label class="confirm-checkbox" id="export-agents-field">
|
|
1413
|
+
<input type="checkbox" id="export-include-agents" checked />
|
|
1414
|
+
<span>Include referenced agents (recommended)</span>
|
|
1415
|
+
</label>
|
|
1416
|
+
<div id="export-plan" class="export-plan hidden"></div>
|
|
1417
|
+
<p id="export-msg" class="confirm-message"></p>
|
|
1418
|
+
</div>
|
|
1419
|
+
<!-- Result view: what was written, where, and the next step for the format.
|
|
1420
|
+
Replaces the form (not a second dialog) once an export succeeded. -->
|
|
1421
|
+
<div id="export-done" class="export-done hidden" role="status" aria-live="polite">
|
|
1422
|
+
<div class="export-done-head">
|
|
1423
|
+
<span class="export-done-check" aria-hidden="true">✓</span>
|
|
1424
|
+
<b id="export-done-title"></b>
|
|
1425
|
+
</div>
|
|
1426
|
+
<dl id="export-done-lines" class="export-done-lines"></dl>
|
|
1427
|
+
<p id="export-done-next" class="export-done-next"></p>
|
|
1428
|
+
</div>
|
|
1429
|
+
<div class="confirm-actions">
|
|
1430
|
+
<button type="button" id="export-cancel" class="btn btn-ghost btn-mini">Cancel</button>
|
|
1431
|
+
<!-- Preview = dry run (optional). Export runs the dry run itself and writes
|
|
1432
|
+
unless it hits a conflict, which it then shows for per-file resolution. -->
|
|
1433
|
+
<button type="button" id="export-plan-btn" class="btn btn-ghost btn-mini">Preview</button>
|
|
1434
|
+
<button type="button" id="export-apply-btn" class="btn btn-primary btn-mini">Export</button>
|
|
1435
|
+
<button type="button" id="export-done-close" class="btn btn-primary btn-mini hidden">Done</button>
|
|
1436
|
+
</div>
|
|
1437
|
+
</div>
|
|
1438
|
+
</div>
|
|
1439
|
+
|
|
1314
1440
|
<!-- ===== "Ship it?" PR confirm modal (History detail) ===== -->
|
|
1315
1441
|
<!-- No z-index of its own: #confirm-modal.viewer-modal{z-index:60} (style.css:781)
|
|
1316
1442
|
exists because #confirm-modal precedes #plugin-modal in the DOM, and this
|
|
@@ -77,7 +77,7 @@ export function suggestDuplicateId(id, takenIds = []) {
|
|
|
77
77
|
* `globals` come MASKED from GET /api/models. `predefinedShadowedIds` marks
|
|
78
78
|
* built-ins currently overridden by a global entry.
|
|
79
79
|
*/
|
|
80
|
-
export function renderModelsList({ globals = [], legacy = [], plugins = [], predefined = [], efforts = [], projectName = '' } = {}, { doc = globalThis.document } = {}) {
|
|
80
|
+
export function renderModelsList({ globals = [], legacy = [], plugins = [], predefined = [], efforts = [], hideBuiltin = false, projectName = '' } = {}, { doc = globalThis.document } = {}) {
|
|
81
81
|
const root = h(doc, 'div', 'mv-list');
|
|
82
82
|
const predefLc = new Set(predefined.map((m) => m.id.toLowerCase()));
|
|
83
83
|
const pluginLc = new Set(plugins.map((m) => m.id.toLowerCase()));
|
|
@@ -89,6 +89,30 @@ export function renderModelsList({ globals = [], legacy = [], plugins = [], pred
|
|
|
89
89
|
return s;
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
+
// The endpoint-routed badge (#422): honest disclosure that worca points the
|
|
93
|
+
// CLI's internal haiku/sonnet/opus/fable lookups at this model's own id
|
|
94
|
+
// (config.mjs#resolveModelEnv), so nothing falls back to the Anthropic API.
|
|
95
|
+
// The masked env still carries its KEYS, so presence is decidable here.
|
|
96
|
+
const routedBadge = (m) => {
|
|
97
|
+
if (!m.env || !Object.prototype.hasOwnProperty.call(m.env, 'ANTHROPIC_BASE_URL')) return null;
|
|
98
|
+
const b = h(doc, 'span', 'badge waiting mv-routed', 'endpoint-routed');
|
|
99
|
+
b.title = "worca points Claude Code's internal haiku/sonnet/opus/fable lookups at this model, so it never falls back to the Anthropic API.";
|
|
100
|
+
return b;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// ── Hide built-in models (#422) — one checkbox, top of the pane ──
|
|
104
|
+
const hideRow = h(doc, 'div', 'mv-hide-builtin-row');
|
|
105
|
+
const hideLabel = h(doc, 'label', 'check-row');
|
|
106
|
+
const hideCb = h(doc, 'input', 'mv-hide-builtin');
|
|
107
|
+
hideCb.type = 'checkbox';
|
|
108
|
+
hideCb.checked = !!hideBuiltin;
|
|
109
|
+
hideLabel.appendChild(hideCb);
|
|
110
|
+
hideLabel.appendChild(doc.createTextNode(" Hide built-in models (you don't use a first-party Anthropic account)"));
|
|
111
|
+
hideRow.appendChild(hideLabel);
|
|
112
|
+
hideRow.appendChild(h(doc, 'small', 'hint',
|
|
113
|
+
'Drops the built-ins from every model picker — new pipelines, the composer, Ask Worca, title generation. Cosmetic only: a run or reference that already names one keeps working.'));
|
|
114
|
+
root.appendChild(hideRow);
|
|
115
|
+
|
|
92
116
|
// ── Your models (global) ──
|
|
93
117
|
const yours = section('Your models', 'Defined once, available in every project. An entry with a built-in id overrides that built-in.');
|
|
94
118
|
if (!globals.length) {
|
|
@@ -102,6 +126,8 @@ export function renderModelsList({ globals = [], legacy = [], plugins = [], pred
|
|
|
102
126
|
head.appendChild(h(doc, 'b', 'mv-name', m.label || m.id));
|
|
103
127
|
if (predefLc.has(m.id.toLowerCase())) head.appendChild(h(doc, 'span', 'badge violet mv-shadow', 'overrides built-in'));
|
|
104
128
|
else if (pluginLc.has(m.id.toLowerCase())) head.appendChild(h(doc, 'span', 'badge violet mv-shadow', 'overrides plugin'));
|
|
129
|
+
const rb = routedBadge(m);
|
|
130
|
+
if (rb) head.appendChild(rb);
|
|
105
131
|
// The §4.6 "unreliable" badge is meaningless once an override GOVERNS this
|
|
106
132
|
// model's spend — and the backend only lifts the stored flag on the model's
|
|
107
133
|
// next result event, so suppress it here the moment pricing is pinned.
|
|
@@ -169,6 +195,8 @@ export function renderModelsList({ globals = [], legacy = [], plugins = [], pred
|
|
|
169
195
|
head.appendChild(h(doc, 'b', 'mv-name', m.label || m.id));
|
|
170
196
|
head.appendChild(h(doc, 'span', 'badge waiting mv-origin', `plugin: ${m.plugin}`));
|
|
171
197
|
if (globalLc.has(m.id.toLowerCase())) head.appendChild(h(doc, 'span', 'badge violet mv-shadowed', 'overridden by your copy'));
|
|
198
|
+
const prb = routedBadge(m);
|
|
199
|
+
if (prb) head.appendChild(prb);
|
|
172
200
|
// Same rule as a global card: a manifest-pinned price governs the spend,
|
|
173
201
|
// so the §4.6 "unreliable" flag says nothing about it.
|
|
174
202
|
if (m.costUnreliable && !m.cost) head.appendChild(h(doc, 'span', 'badge waiting mv-cost', 'cost not verified'));
|
|
@@ -199,8 +227,11 @@ export function renderModelsList({ globals = [], legacy = [], plugins = [], pred
|
|
|
199
227
|
}
|
|
200
228
|
|
|
201
229
|
// ── Built-ins (read-only) ──
|
|
202
|
-
const builtins = section('Built-in models',
|
|
203
|
-
|
|
230
|
+
const builtins = section('Built-in models', hideBuiltin
|
|
231
|
+
? `Hidden from every picker (${predefined.length} built-in${predefined.length === 1 ? '' : 's'}) — untick the box above to show them.`
|
|
232
|
+
: 'Shipped with worca. Add a model with the same id to override its label, efforts, or routing.');
|
|
233
|
+
builtins.classList.add(hideBuiltin ? 'mv-builtins-hidden' : 'mv-builtins-shown');
|
|
234
|
+
for (const m of hideBuiltin ? [] : predefined) {
|
|
204
235
|
const row = h(doc, 'div', 'mv-builtin');
|
|
205
236
|
row.dataset.id = m.id;
|
|
206
237
|
row.appendChild(h(doc, 'b', 'mv-name', m.label));
|
package/ui/public/style.css
CHANGED
|
@@ -959,6 +959,11 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
|
|
|
959
959
|
/* Confirm dialog must stack ABOVE the plugin/wizard modal (equal .viewer-modal z-index,
|
|
960
960
|
but #confirm-modal precedes #plugin-modal in the DOM). */
|
|
961
961
|
#confirm-modal.viewer-modal{z-index:60;}
|
|
962
|
+
/* The folder browser is a leaf picker opened FROM other modals (add-project,
|
|
963
|
+
export). It precedes #export-modal in the DOM, so at equal .viewer-modal
|
|
964
|
+
z-index the export modal would paint over it, hiding the Browse… picker.
|
|
965
|
+
Elevate it above every other modal so it is always usable when open. */
|
|
966
|
+
#folder-browser.viewer-modal{z-index:70;}
|
|
962
967
|
.viewer-modal .card{width:min(860px,100%);max-height:86vh;overflow:auto;}
|
|
963
968
|
/* folder browser modal (native-dialog fallback). Must come AFTER the
|
|
964
969
|
.viewer-modal rules: `.folder-modal .card` ties with `.viewer-modal .card`
|
|
@@ -1056,6 +1061,39 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
|
|
|
1056
1061
|
.pl-remove:disabled{opacity:.6;cursor:default;}
|
|
1057
1062
|
.pl-remove svg{display:block;}
|
|
1058
1063
|
|
|
1064
|
+
/* Export dialog (JSON file / Claude Code skill / Worca plugin). The row's
|
|
1065
|
+
Export… button itself is a .btn-ghost (see .gv-saved .pl-export). */
|
|
1066
|
+
.export-field{margin:12px 0;}
|
|
1067
|
+
.export-field .hint{display:block;margin-top:6px;}
|
|
1068
|
+
/* A checkbox row directly under a text input needs its own breathing room:
|
|
1069
|
+
.confirm-checkbox ships margin:0 (it is built for the confirm dialog's stack). */
|
|
1070
|
+
.export-field .confirm-checkbox{margin-top:10px;}
|
|
1071
|
+
/* Result view after a successful export (replaces #export-form in the same card). */
|
|
1072
|
+
.export-done{margin:12px 0 4px;}
|
|
1073
|
+
.export-done.hidden{display:none;}
|
|
1074
|
+
.export-done-head{display:flex;align-items:center;gap:10px;font-size:14px;margin-bottom:12px;}
|
|
1075
|
+
.export-done-check{display:grid;place-items:center;width:26px;height:26px;border-radius:999px;
|
|
1076
|
+
background:var(--green-bg,#e6f4ea);color:var(--green-ink,#1e6b3a);font-weight:700;font-size:14px;}
|
|
1077
|
+
.export-done-lines{display:grid;grid-template-columns:max-content 1fr;gap:6px 14px;margin:0;font-size:12.5px;}
|
|
1078
|
+
.export-done-lines dt{color:var(--ink-3);font-weight:600;}
|
|
1079
|
+
.export-done-lines dd{margin:0;color:var(--ink);word-break:break-all;}
|
|
1080
|
+
.export-done-lines code{font:12px var(--mono);background:var(--field);border-radius:6px;padding:1px 6px;}
|
|
1081
|
+
.export-done-next{margin:14px 0 0;padding:10px 12px;border-radius:10px;background:var(--field);font-size:12.5px;color:var(--ink-2);}
|
|
1082
|
+
.export-done-next code{font:12px var(--mono);}
|
|
1083
|
+
.export-label{display:block;font-weight:600;font-size:12.5px;color:var(--ink-2);margin-bottom:6px;}
|
|
1084
|
+
.export-folder-row{display:flex;gap:8px;align-items:center;}
|
|
1085
|
+
.export-folder-row .input{flex:1 1 auto;}
|
|
1086
|
+
.export-plan{margin:12px 0;max-height:240px;overflow:auto;border:1px solid var(--line-2);border-radius:10px;padding:8px;font-size:12.5px;}
|
|
1087
|
+
.export-row{padding:4px 2px;display:flex;flex-wrap:wrap;gap:6px;align-items:center;}
|
|
1088
|
+
.export-conflict-row{flex-direction:column;align-items:flex-start;}
|
|
1089
|
+
.export-tag{display:inline-block;min-width:58px;text-align:center;border-radius:6px;padding:1px 6px;font-weight:700;font-size:11px;}
|
|
1090
|
+
.export-create{background:var(--green-bg,#e6f7ec);color:var(--green-ink,#137a3b);}
|
|
1091
|
+
.export-update{background:var(--field);color:var(--ink);}
|
|
1092
|
+
.export-no-op{background:var(--field);color:var(--ink-3);}
|
|
1093
|
+
.export-warn,.export-conflict{background:var(--red-bg,#fde8e8);color:var(--red-ink,#b42323);}
|
|
1094
|
+
.export-orphan{background:var(--field);color:var(--ink-3);}
|
|
1095
|
+
.export-choices{display:flex;gap:12px;margin:4px 0 2px 64px;}
|
|
1096
|
+
.export-choice{font-size:12.5px;color:var(--ink-2);cursor:pointer;}
|
|
1059
1097
|
/* Per-agent fan-out toggle in the New Pipeline rows. */
|
|
1060
1098
|
.fanout-toggle {
|
|
1061
1099
|
display: inline-flex;
|
|
@@ -1586,6 +1624,8 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
|
|
|
1586
1624
|
/* ---- Models view (configurable-models-design.md §4.10) ---- */
|
|
1587
1625
|
.mv-list{display:grid;gap:18px;}
|
|
1588
1626
|
.mv-section{display:grid;gap:10px;}
|
|
1627
|
+
.mv-hide-builtin-row{display:grid;gap:6px;padding:10px 12px;border:1px solid var(--line-2,#e6e6ec);border-radius:10px;}
|
|
1628
|
+
.mv-hide-builtin-row .hint{margin:0;}
|
|
1589
1629
|
.mv-section-title{margin:4px 0 0;font-size:13px;text-transform:uppercase;letter-spacing:.04em;opacity:.75;}
|
|
1590
1630
|
.mv-card{padding:16px 20px;display:flex;align-items:center;gap:14px;}
|
|
1591
1631
|
.mv-body{flex:1 1 auto;min-width:0;}
|
|
@@ -3459,6 +3499,8 @@ input.gv-name:focus-visible{outline:none;}
|
|
|
3459
3499
|
padding:4px 11px;font:600 11.5px var(--sans);cursor:pointer;flex:0 0 auto;white-space:nowrap;}
|
|
3460
3500
|
.gv-errchip:hover{filter:brightness(.97);}
|
|
3461
3501
|
.gv-errchip[hidden]{display:none;}
|
|
3502
|
+
/* Unfinished wiring is a to-do, not a mistake: the same chip in a quiet dress. */
|
|
3503
|
+
.gv-errchip.is-incomplete{background:var(--field);color:var(--ink-2);border-color:var(--line-2);}
|
|
3462
3504
|
/* Toolbar density: .btn-ghost/.btn-go are PAGE-level pills (13–14px of vertical
|
|
3463
3505
|
padding, 20–28px horizontal). In a 52px bar they wrap their own labels, so
|
|
3464
3506
|
the bar re-states them at .btn-mini metrics — same skin, toolbar size. */
|
|
@@ -3637,11 +3679,27 @@ input.gv-name:focus-visible{outline:none;}
|
|
|
3637
3679
|
padding:16px 20px;border-bottom:1px solid var(--line);}
|
|
3638
3680
|
.saved-head b{font-size:15px;font-weight:600;letter-spacing:-.01em;}
|
|
3639
3681
|
.saved-head .cnt{color:var(--ink-3);font-size:12.5px;font-weight:600;margin-left:9px;}
|
|
3682
|
+
.saved-head-actions{display:flex;align-items:center;gap:8px;}
|
|
3683
|
+
/* One tab per domain — the same pill tabs the History detail uses, one size down. */
|
|
3684
|
+
.gv-saved-tabs{display:flex;align-items:center;flex-wrap:wrap;gap:8px;padding:12px 20px;border-bottom:1px solid var(--line);}
|
|
3685
|
+
.gv-saved-tabs[hidden]{display:none;}
|
|
3686
|
+
.gv-saved-tab{display:flex;align-items:center;gap:8px;padding:7px 13px;border:1.5px solid var(--line);
|
|
3687
|
+
border-radius:999px;background:var(--panel);font:600 12.5px var(--sans);color:var(--ink-2);cursor:pointer;}
|
|
3688
|
+
.gv-saved-tab:hover{border-color:var(--ink-3);color:var(--ink);}
|
|
3689
|
+
.gv-saved-tab.active{background:var(--ink);color:#fff;border-color:var(--ink);}
|
|
3690
|
+
.gv-saved-tab:focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
|
|
3691
|
+
.gv-saved-tab-badge{padding:1px 7px;border-radius:999px;background:var(--field);color:var(--ink-3);font:600 10.5px var(--mono);}
|
|
3692
|
+
.gv-saved-tab.active .gv-saved-tab-badge{background:var(--blue-bg);color:var(--blue-ink);}
|
|
3640
3693
|
.saved-list{display:flex;flex-direction:column;}
|
|
3641
3694
|
.gv-saved .pl-item{border-bottom:1px solid var(--line);}
|
|
3642
3695
|
.gv-saved .pl-item:last-child{border-bottom:none;}
|
|
3643
3696
|
.gv-saved .pl-row{display:flex;align-items:center;gap:14px;padding:12px 20px;transition:background .12s;}
|
|
3644
3697
|
.gv-saved .pl-row:hover{background:var(--field);}
|
|
3698
|
+
/* The row IS the Open action for a v2 pipeline. */
|
|
3699
|
+
.gv-saved .pl-row.pl-openable{cursor:pointer;}
|
|
3700
|
+
.gv-saved .pl-row.pl-openable:focus-visible{outline:2px solid var(--ink);outline-offset:-2px;}
|
|
3701
|
+
.gv-saved .pl-new{display:inline-block;margin-left:8px;padding:2px 7px;border-radius:999px;vertical-align:middle;
|
|
3702
|
+
background:var(--blue-bg);color:var(--blue-ink);font:700 10px var(--mono);letter-spacing:.06em;}
|
|
3645
3703
|
/* The thumbnail is a fixed TILE at the head of the row: the SVG carries its own
|
|
3646
3704
|
width/height, so a percentage box would leave it stranded in dead space. */
|
|
3647
3705
|
.gv-saved .pl-thumb{flex:0 0 auto;width:140px;height:54px;display:grid;place-items:center;overflow:hidden;
|
|
@@ -3654,14 +3712,16 @@ input.gv-name:focus-visible{outline:none;}
|
|
|
3654
3712
|
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
3655
3713
|
.gv-saved .pl-meta{margin-top:4px;font-size:10.5px;font-weight:700;letter-spacing:.05em;
|
|
3656
3714
|
text-transform:uppercase;color:var(--ink-3);}
|
|
3657
|
-
.gv-saved .pl-
|
|
3715
|
+
.gv-saved .pl-export{flex:0 0 auto;padding:7px 15px;font-size:12.5px;}
|
|
3658
3716
|
.gv-saved .pl-legacy,.gv-saved .pl-origin{flex:0 0 auto;background:var(--field);border:1px solid var(--line);color:var(--ink-3);
|
|
3659
3717
|
border-radius:999px;padding:3px 10px;font-size:11px;font-weight:600;}
|
|
3660
3718
|
.gv-saved .pl-origin{font-family:var(--mono);letter-spacing:-.01em;}
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3719
|
+
/* Delete: the bin icon in the pipeline Archive button's dress (.hd-archive — red
|
|
3720
|
+
ink on a red-bg hairline pill, fills on hover), icon-only at row-action size. */
|
|
3721
|
+
.gv-saved .pl-del{flex:0 0 auto;width:34px;height:34px;border-radius:999px;border:1.5px solid var(--red-bg);
|
|
3722
|
+
background:var(--panel);display:grid;place-items:center;cursor:pointer;color:var(--red-ink);transition:.12s;}
|
|
3723
|
+
.gv-saved .pl-del svg{width:15px;height:15px;stroke-width:1.9;}
|
|
3724
|
+
.gv-saved .pl-del:hover{border-color:var(--red);background:var(--red-bg);}
|
|
3665
3725
|
.gv-saved .pl-del:focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
|
|
3666
3726
|
.gv-saved .pl-empty{padding:30px 20px;text-align:center;color:var(--ink-3);font-size:13px;}
|
|
3667
3727
|
.gv-archived{display:flex;flex-wrap:wrap;align-items:center;gap:7px;
|