opendoor-plugin 0.2.0 → 0.4.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/README.md +154 -22
- package/dist/browser/pi-web-plugin.js +1891 -1401
- package/dist/package.json +6 -29
- package/dist/pi-extension/index.js +136 -0
- package/dist/server-plugin.js +32 -21
- package/package.json +19 -10
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Generated from src/pi-web-plugin.ts. Do not edit directly.
|
|
1
|
+
// Generated from src/browser/pi-web-plugin.ts. Do not edit directly.
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
@@ -562,6 +562,21 @@ o4?.({ LitElement: i4 });
|
|
|
562
562
|
(s3.litElementVersions ??= []).push("4.2.2");
|
|
563
563
|
|
|
564
564
|
// src/doorstop-contract.ts
|
|
565
|
+
var ITEM_STATE_KEYS = [
|
|
566
|
+
"normative",
|
|
567
|
+
"non-normative",
|
|
568
|
+
"inactive",
|
|
569
|
+
"reviewed",
|
|
570
|
+
"unreviewed",
|
|
571
|
+
"suspect-link",
|
|
572
|
+
"no-child-links",
|
|
573
|
+
"no-links",
|
|
574
|
+
"unknown-link",
|
|
575
|
+
"missing-reference"
|
|
576
|
+
];
|
|
577
|
+
function isItemStateKey(value) {
|
|
578
|
+
return ITEM_STATE_KEYS.includes(value);
|
|
579
|
+
}
|
|
565
580
|
function formatUnknownError(error) {
|
|
566
581
|
return error instanceof Error ? error.message : String(error);
|
|
567
582
|
}
|
|
@@ -1303,10 +1318,12 @@ var OPENDOOR_SETTINGS_PATH = ".pi-web/opendoor.json";
|
|
|
1303
1318
|
var OPENDOOR_SETTINGS_VERSION = 1;
|
|
1304
1319
|
var DEFAULT_PUBLISH_TARGET = "./public";
|
|
1305
1320
|
var MAX_EXCLUDED_DIRECTORIES = 16;
|
|
1321
|
+
var MAX_SHOWN_ATTRIBUTES = 16;
|
|
1306
1322
|
var MAX_EXCLUDED_DIRECTORY_LENGTH = 64;
|
|
1307
1323
|
var DEFAULT_OPENDOOR_SETTINGS = Object.freeze({
|
|
1308
1324
|
publishTarget: DEFAULT_PUBLISH_TARGET,
|
|
1309
1325
|
excludedDirectories: Object.freeze([]),
|
|
1326
|
+
showAdditionalAttribute: Object.freeze([]),
|
|
1310
1327
|
commitAfterReview: false
|
|
1311
1328
|
});
|
|
1312
1329
|
var MISSING_FILE_ERROR = "Path does not exist";
|
|
@@ -1410,6 +1427,19 @@ function parseOpendoorSettings(value) {
|
|
|
1410
1427
|
excludedDirectories = parseExcludedDirectories(rawExcluded, diagnostics);
|
|
1411
1428
|
}
|
|
1412
1429
|
}
|
|
1430
|
+
let showAdditionalAttribute = [];
|
|
1431
|
+
const rawShown = value["showAdditionalAttribute"];
|
|
1432
|
+
if (rawShown !== void 0) {
|
|
1433
|
+
if (!Array.isArray(rawShown)) {
|
|
1434
|
+
diagnostics.push({
|
|
1435
|
+
severity: "warning",
|
|
1436
|
+
path: OPENDOOR_SETTINGS_PATH,
|
|
1437
|
+
message: `${OPENDOOR_SETTINGS_PATH} "showAdditionalAttribute" must be an array; using none`
|
|
1438
|
+
});
|
|
1439
|
+
} else {
|
|
1440
|
+
showAdditionalAttribute = parseShowAdditionalAttribute(rawShown, diagnostics);
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1413
1443
|
let commitAfterReview = false;
|
|
1414
1444
|
const rawCommit = value["commitAfterReview"];
|
|
1415
1445
|
if (rawCommit !== void 0) {
|
|
@@ -1423,7 +1453,7 @@ function parseOpendoorSettings(value) {
|
|
|
1423
1453
|
commitAfterReview = rawCommit;
|
|
1424
1454
|
}
|
|
1425
1455
|
}
|
|
1426
|
-
return { settings: { publishTarget, excludedDirectories, commitAfterReview }, diagnostics };
|
|
1456
|
+
return { settings: { publishTarget, excludedDirectories, showAdditionalAttribute, commitAfterReview }, diagnostics };
|
|
1427
1457
|
}
|
|
1428
1458
|
function parseExcludedDirectories(entries, diagnostics) {
|
|
1429
1459
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1452,6 +1482,33 @@ function parseExcludedDirectories(entries, diagnostics) {
|
|
|
1452
1482
|
}
|
|
1453
1483
|
return result;
|
|
1454
1484
|
}
|
|
1485
|
+
function parseShowAdditionalAttribute(entries, diagnostics) {
|
|
1486
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1487
|
+
const result = [];
|
|
1488
|
+
for (const entry of entries) {
|
|
1489
|
+
const name = typeof entry === "string" ? entry.trim() : "";
|
|
1490
|
+
if (name === "") {
|
|
1491
|
+
diagnostics.push({
|
|
1492
|
+
severity: "warning",
|
|
1493
|
+
path: OPENDOOR_SETTINGS_PATH,
|
|
1494
|
+
message: `${OPENDOOR_SETTINGS_PATH} "showAdditionalAttribute" entry "${truncateEcho(String(entry))}" is not a non-empty string and was dropped`
|
|
1495
|
+
});
|
|
1496
|
+
continue;
|
|
1497
|
+
}
|
|
1498
|
+
if (seen.has(name)) continue;
|
|
1499
|
+
if (result.length >= MAX_SHOWN_ATTRIBUTES) {
|
|
1500
|
+
diagnostics.push({
|
|
1501
|
+
severity: "warning",
|
|
1502
|
+
path: OPENDOOR_SETTINGS_PATH,
|
|
1503
|
+
message: `${OPENDOOR_SETTINGS_PATH} "showAdditionalAttribute" has more than ${String(MAX_SHOWN_ATTRIBUTES)} entries; the rest were ignored`
|
|
1504
|
+
});
|
|
1505
|
+
break;
|
|
1506
|
+
}
|
|
1507
|
+
seen.add(name);
|
|
1508
|
+
result.push(name);
|
|
1509
|
+
}
|
|
1510
|
+
return result;
|
|
1511
|
+
}
|
|
1455
1512
|
function normalizeRelativePath(value) {
|
|
1456
1513
|
const collapsed = value.replace(/\/{2,}/g, "/");
|
|
1457
1514
|
return collapsed.length > 1 && collapsed.endsWith("/") ? collapsed.slice(0, -1) : collapsed;
|
|
@@ -5728,7 +5785,23 @@ function serializeReferences(references) {
|
|
|
5728
5785
|
return JSON.stringify(references === void 0 ? [] : references);
|
|
5729
5786
|
}
|
|
5730
5787
|
|
|
5731
|
-
// src/doorstop-panel-
|
|
5788
|
+
// src/browser/doorstop-panel-navigation.ts
|
|
5789
|
+
var DOORSTOP_NAV_UID = "uid";
|
|
5790
|
+
var DOORSTOP_NAV_FILE = "file";
|
|
5791
|
+
var DOORSTOP_NAV_DOC = "doc";
|
|
5792
|
+
var DOORSTOP_NAV_FILTER = "filter";
|
|
5793
|
+
var DOORSTOP_NAV_SEARCH = "search";
|
|
5794
|
+
function readPanelNavigationQuery(navigation) {
|
|
5795
|
+
const output = {};
|
|
5796
|
+
if (navigation === void 0) return output;
|
|
5797
|
+
for (const [key, value] of Object.entries(navigation.query)) {
|
|
5798
|
+
const first = Array.isArray(value) ? value[0] : value;
|
|
5799
|
+
if (typeof first === "string" && first !== "") output[key] = first;
|
|
5800
|
+
}
|
|
5801
|
+
return output;
|
|
5802
|
+
}
|
|
5803
|
+
|
|
5804
|
+
// src/browser/doorstop-panel-controller.ts
|
|
5732
5805
|
var DoorstopWorkspaceController = class {
|
|
5733
5806
|
constructor(host, context, loadJob) {
|
|
5734
5807
|
this.loading = false;
|
|
@@ -5828,25 +5901,99 @@ var DoorstopWorkspaceController = class {
|
|
|
5828
5901
|
this.loadRequest = request;
|
|
5829
5902
|
return request;
|
|
5830
5903
|
}
|
|
5831
|
-
/** Select an item UID for the detail pane.
|
|
5904
|
+
/** Select an item UID for the detail pane. Plan Phase C step C2 — a
|
|
5905
|
+
* deliberate selection change also writes the `uid` deep-link key, so
|
|
5906
|
+
* the selection is bookmarkable/back-navigable. */
|
|
5832
5907
|
selectUid(uid) {
|
|
5833
5908
|
this.selectedUid = uid;
|
|
5909
|
+
this.setNavigation(DOORSTOP_NAV_UID, uid);
|
|
5910
|
+
this.setNavigation(DOORSTOP_NAV_FILE, void 0, { replace: true });
|
|
5834
5911
|
this.requestUpdate();
|
|
5835
5912
|
}
|
|
5836
|
-
/** Select a document by prefix for the document-detail pane.
|
|
5913
|
+
/** Select a document by prefix for the document-detail pane. C2 — writes
|
|
5914
|
+
* the `doc` deep-link key. */
|
|
5837
5915
|
selectDocument(prefix) {
|
|
5838
5916
|
this.selectedDocumentPrefix = prefix;
|
|
5917
|
+
this.setNavigation(DOORSTOP_NAV_DOC, prefix);
|
|
5918
|
+
this.setNavigation(DOORSTOP_NAV_FILE, void 0, { replace: true });
|
|
5839
5919
|
this.requestUpdate();
|
|
5840
5920
|
}
|
|
5841
5921
|
setStateFilter(filter) {
|
|
5842
5922
|
this.stateFilter = filter;
|
|
5843
5923
|
if (this.result !== void 0) this.selectedUid = selectionIn(this.result, filter, this.selectedUid);
|
|
5924
|
+
this.setNavigation(DOORSTOP_NAV_FILTER, filter);
|
|
5844
5925
|
this.requestUpdate();
|
|
5845
5926
|
}
|
|
5846
5927
|
setSearch(search) {
|
|
5847
5928
|
this.search = search;
|
|
5929
|
+
this.setNavigation(DOORSTOP_NAV_SEARCH, search === "" ? void 0 : search, { replace: true });
|
|
5848
5930
|
this.requestUpdate();
|
|
5849
5931
|
}
|
|
5932
|
+
/** Apply the panel's deep-link query (URL → state). Plan Phase C step C2:
|
|
5933
|
+
* `renderDoorstopPanel` calls this on EVERY render, idempotently — values
|
|
5934
|
+
* are compared first, so re-applying the same query is a no-op, and the
|
|
5935
|
+
* query wins exactly when it differs from the controller state (back/
|
|
5936
|
+
* forward navigation re-rendering the panel with an older URL; a user
|
|
5937
|
+
* interaction re-rendering with the query its own setter just wrote).
|
|
5938
|
+
*
|
|
5939
|
+
* READ-ONLY for the navigation: this method never writes keys back (the
|
|
5940
|
+
* setters own writes — writing here would loop render → set → render).
|
|
5941
|
+
*
|
|
5942
|
+
* Validation: an unknown `filter` value is ignored (the current filter
|
|
5943
|
+
* stands — `?filter=bogus` must not crash the panel), and `uid`/`doc`
|
|
5944
|
+
* values that no longer exist in a LOADED result are dropped by the
|
|
5945
|
+
* same selectionIn reconciliation a landed load applies; before the first
|
|
5946
|
+
* load lands they apply blindly (the landing load reconciles). The C3
|
|
5947
|
+
* `file` key (chat file link) resolves only against a loaded result. */
|
|
5948
|
+
applyNavigationQuery(query) {
|
|
5949
|
+
let changed = false;
|
|
5950
|
+
const filterValue = query[DOORSTOP_NAV_FILTER];
|
|
5951
|
+
if (filterValue === void 0) {
|
|
5952
|
+
if (this.stateFilter !== void 0) {
|
|
5953
|
+
this.stateFilter = void 0;
|
|
5954
|
+
changed = true;
|
|
5955
|
+
}
|
|
5956
|
+
} else if (isItemStateKey(filterValue) && this.stateFilter !== filterValue) {
|
|
5957
|
+
this.stateFilter = filterValue;
|
|
5958
|
+
changed = true;
|
|
5959
|
+
}
|
|
5960
|
+
const filePath = query[DOORSTOP_NAV_FILE];
|
|
5961
|
+
const fileItem = filePath !== void 0 && this.result !== void 0 ? this.result.index.items.find((candidate) => candidate.path === filePath) : void 0;
|
|
5962
|
+
const uid = fileItem !== void 0 ? fileItem.uid : query[DOORSTOP_NAV_UID];
|
|
5963
|
+
if (this.selectedUid !== uid) {
|
|
5964
|
+
this.selectedUid = uid;
|
|
5965
|
+
changed = true;
|
|
5966
|
+
}
|
|
5967
|
+
const documentPrefix = fileItem !== void 0 ? fileItem.documentPrefix : query[DOORSTOP_NAV_DOC];
|
|
5968
|
+
if (this.selectedDocumentPrefix !== documentPrefix) {
|
|
5969
|
+
this.selectedDocumentPrefix = documentPrefix;
|
|
5970
|
+
changed = true;
|
|
5971
|
+
}
|
|
5972
|
+
const search = query[DOORSTOP_NAV_SEARCH] ?? "";
|
|
5973
|
+
if (this.search !== search) {
|
|
5974
|
+
this.search = search;
|
|
5975
|
+
changed = true;
|
|
5976
|
+
}
|
|
5977
|
+
if (this.result !== void 0) {
|
|
5978
|
+
const keptUid = selectionIn(this.result, this.stateFilter, this.selectedUid);
|
|
5979
|
+
if (keptUid !== this.selectedUid) {
|
|
5980
|
+
this.selectedUid = keptUid;
|
|
5981
|
+
changed = true;
|
|
5982
|
+
}
|
|
5983
|
+
const keptDocument = documentSelectionIn(this.result, this.selectedDocumentPrefix);
|
|
5984
|
+
if (keptDocument !== this.selectedDocumentPrefix) {
|
|
5985
|
+
this.selectedDocumentPrefix = keptDocument;
|
|
5986
|
+
changed = true;
|
|
5987
|
+
}
|
|
5988
|
+
}
|
|
5989
|
+
if (changed) this.requestUpdate();
|
|
5990
|
+
}
|
|
5991
|
+
/** Write one deep-link key on the CURRENT context's navigation, when the
|
|
5992
|
+
* host supplies one. `undefined` clears the key (the selection is gone);
|
|
5993
|
+
* absent navigation (older hosts, tests without one) is a silent no-op. */
|
|
5994
|
+
setNavigation(key, value, options) {
|
|
5995
|
+
this.context.navigation?.set(key, value, options);
|
|
5996
|
+
}
|
|
5850
5997
|
/** Begin a doorstop run: record the op title (element disables action
|
|
5851
5998
|
* buttons) and notify. */
|
|
5852
5999
|
beginRun(title) {
|
|
@@ -5871,6 +6018,37 @@ var DoorstopWorkspaceController = class {
|
|
|
5871
6018
|
this.lastRun = void 0;
|
|
5872
6019
|
this.requestUpdate();
|
|
5873
6020
|
}
|
|
6021
|
+
/**
|
|
6022
|
+
* Commit the "Start review session" composer-insert as the last run (relay
|
|
6023
|
+
* `opendoor-dispatch-redesign` design §3). The insert happens in the
|
|
6024
|
+
* browser, so the record carries no process data: `status: "ok"` with an
|
|
6025
|
+
* empty stdout/stderr and NO `commit` field — which is exactly what makes
|
|
6026
|
+
* the status bar render its quiet ok badge, skip the commit-narration line
|
|
6027
|
+
* and the error pre, and stay collapsed (`lastRunHasMessage` is false, so
|
|
6028
|
+
* the bar does not auto-expand). The write goes through the single
|
|
6029
|
+
* {@link commitRun} point, so it survives `invalidate()`/`load()` and is
|
|
6030
|
+
* cleared only by `dismissRun()` or the next commit — every existing
|
|
6031
|
+
* run-outcome semantic is inherited unchanged.
|
|
6032
|
+
*
|
|
6033
|
+
* `at` is REQUIRED by {@link DoorstopLastRunView} (the run-start epoch ms
|
|
6034
|
+
* every `commitRun` caller sets); `durationMs` is 0 because no run was
|
|
6035
|
+
* measured — the insert is synchronous.
|
|
6036
|
+
*/
|
|
6037
|
+
commitAskReview(uid) {
|
|
6038
|
+
this.commitRun({
|
|
6039
|
+
op: "ask-review",
|
|
6040
|
+
title: `Opendoor: review dispatch ${uid}`,
|
|
6041
|
+
status: "ok",
|
|
6042
|
+
exitCode: null,
|
|
6043
|
+
signal: null,
|
|
6044
|
+
stdout: "",
|
|
6045
|
+
stderr: "",
|
|
6046
|
+
stdoutTruncated: false,
|
|
6047
|
+
stderrTruncated: false,
|
|
6048
|
+
durationMs: 0,
|
|
6049
|
+
at: Date.now()
|
|
6050
|
+
});
|
|
6051
|
+
}
|
|
5874
6052
|
/** Run the Stage action (plan Phase C step 12): stage every Doorstop-managed
|
|
5875
6053
|
* path of the loaded index (the element hands the {@link doorstopPaths}
|
|
5876
6054
|
* list in), or — for a per-item stage — exactly one item's path with
|
|
@@ -5951,12 +6129,12 @@ var DoorstopWorkspaceController = class {
|
|
|
5951
6129
|
this.beginRun(title);
|
|
5952
6130
|
const startedAt = Date.now();
|
|
5953
6131
|
try {
|
|
5954
|
-
const
|
|
5955
|
-
if (
|
|
6132
|
+
const peer = this.context.peer;
|
|
6133
|
+
if (peer?.request === void 0) {
|
|
5956
6134
|
this.commitGitError(op, title, startedAt, "Git actions need the paired opendoor backend");
|
|
5957
6135
|
return;
|
|
5958
6136
|
}
|
|
5959
|
-
const response = await
|
|
6137
|
+
const response = await peer.request(backendOperation, input);
|
|
5960
6138
|
const parsed = parseResponse(response);
|
|
5961
6139
|
this.commitRun({
|
|
5962
6140
|
op,
|
|
@@ -6029,7 +6207,7 @@ var DoorstopWorkspaceController = class {
|
|
|
6029
6207
|
* retries instead of showing a stale failure forever). A fetch already in
|
|
6030
6208
|
* flight for the same UID is joined (never stacked). Otherwise the fetch
|
|
6031
6209
|
* runs through
|
|
6032
|
-
* `
|
|
6210
|
+
* `peer.request(DOORSTOP_BASELINE_OPERATION, { uid, path })`, parses the
|
|
6033
6211
|
* response with the contract validator, and stamp-walks the returned blobs
|
|
6034
6212
|
* newest-first (`parseDoorstopItem` + the document's config via
|
|
6035
6213
|
* `computeItemStamp(item, config, true)`) until one matches
|
|
@@ -6071,12 +6249,12 @@ var DoorstopWorkspaceController = class {
|
|
|
6071
6249
|
this.baselineVersion += 1;
|
|
6072
6250
|
this.requestUpdate();
|
|
6073
6251
|
try {
|
|
6074
|
-
const
|
|
6075
|
-
if (
|
|
6252
|
+
const peer = this.context.peer;
|
|
6253
|
+
if (peer?.request === void 0) {
|
|
6076
6254
|
this.commitBaseline(uid, { key, view: { state: "no-git" } });
|
|
6077
6255
|
return;
|
|
6078
6256
|
}
|
|
6079
|
-
const response = await
|
|
6257
|
+
const response = await peer.request(DOORSTOP_BASELINE_OPERATION, { uid, path: item.path });
|
|
6080
6258
|
if (!this.host.isConnected) return;
|
|
6081
6259
|
const parsed = parseDoorstopBaselineResponse(response);
|
|
6082
6260
|
if (!parsed.git) {
|
|
@@ -6145,12 +6323,12 @@ var DoorstopWorkspaceController = class {
|
|
|
6145
6323
|
this.gitStatusInFlight = true;
|
|
6146
6324
|
this.requestUpdate();
|
|
6147
6325
|
try {
|
|
6148
|
-
const
|
|
6149
|
-
if (
|
|
6326
|
+
const peer = this.context.peer;
|
|
6327
|
+
if (peer?.request === void 0) {
|
|
6150
6328
|
this.gitStatusView = { state: "no-git" };
|
|
6151
6329
|
return;
|
|
6152
6330
|
}
|
|
6153
|
-
const response = await
|
|
6331
|
+
const response = await peer.request(DOORSTOP_GIT_STATUS_OPERATION, {});
|
|
6154
6332
|
if (!this.host.isConnected || this.gitStatusView?.state !== "loading") return;
|
|
6155
6333
|
const parsed = parseDoorstopGitStatusResponse(response);
|
|
6156
6334
|
this.gitStatusView = parsed.git ? { state: "ready", response: parsed } : { state: "no-git" };
|
|
@@ -6219,7 +6397,7 @@ function doorstopPaths(result) {
|
|
|
6219
6397
|
return paths;
|
|
6220
6398
|
}
|
|
6221
6399
|
|
|
6222
|
-
// src/doorstop-panel.ts
|
|
6400
|
+
// src/browser/doorstop-panel.ts
|
|
6223
6401
|
var DOORSTOP_WORKSPACE_STATE_LIMIT = 8;
|
|
6224
6402
|
var MAX_ITEM_DIGITS = 24;
|
|
6225
6403
|
async function loadDoorstopWorkspace(files) {
|
|
@@ -6352,6 +6530,15 @@ function escapeRegExp(value) {
|
|
|
6352
6530
|
var DoorstopWorkspaceRegistry = class {
|
|
6353
6531
|
constructor() {
|
|
6354
6532
|
this.workspaces = /* @__PURE__ */ new Map();
|
|
6533
|
+
/** Plan Phase C step C3 — the chat file-open index: per-workspace
|
|
6534
|
+
* item-file path → { uid, documentPrefix } (plus the landed result it was
|
|
6535
|
+
* built from, kept by identity), recorded from every LANDED
|
|
6536
|
+
* load result (renderDoorstopPanel records it whenever the controller
|
|
6537
|
+
* has a result), so the SYNCHRONOUS `fileOpenQuery` hook can answer from
|
|
6538
|
+
* the last landed discovery without running one. Same LRU bound and the
|
|
6539
|
+
* same workspace keys as the controller map; an evicted workspace drops
|
|
6540
|
+
* its file-open entries with its controller. */
|
|
6541
|
+
this.fileOpenEntries = /* @__PURE__ */ new Map();
|
|
6355
6542
|
}
|
|
6356
6543
|
/** Get-or-create the controller for a workspace (the render entry point). */
|
|
6357
6544
|
for(context) {
|
|
@@ -6373,10 +6560,67 @@ var DoorstopWorkspaceRegistry = class {
|
|
|
6373
6560
|
return controller;
|
|
6374
6561
|
}
|
|
6375
6562
|
/** Panel invalidation: re-run discovery + load unconditionally for the
|
|
6376
|
-
* connected workspace (paired or unpaired: the load job is browser-side, so there is no owned-workspace gate).
|
|
6377
|
-
|
|
6563
|
+
* connected workspace (paired or unpaired: the load job is browser-side, so there is no owned-workspace gate).
|
|
6564
|
+
*
|
|
6565
|
+
* Plan Phase C step C1 — the v4 typed invalidation: `invalidation` carries
|
|
6566
|
+
* the reason (`"manual"` Refresh action / `"mutation"` a workspace file
|
|
6567
|
+
* changed / `"agent-activity"` agent work finished) and the affected
|
|
6568
|
+
* resources. The rescan stays REASON-AGNOSTIC deliberately: every reason
|
|
6569
|
+
* may have changed item files, the load job re-reads everything anyway
|
|
6570
|
+
* (no partial invalidation is possible — the index is one unit), and the
|
|
6571
|
+
* controller's re-entrant guard coalesces storms (an invalidate while a
|
|
6572
|
+
* load is running JOINS that load; only an invalidate after it lands
|
|
6573
|
+
* starts a fresh one), so `"agent-activity"` bursts cannot stack loads.
|
|
6574
|
+
* The payload is accepted (so the contribution can forward it) but not
|
|
6575
|
+
* consulted. */
|
|
6576
|
+
invalidate(context, invalidation) {
|
|
6577
|
+
void invalidation;
|
|
6378
6578
|
return this.for(context).invalidate();
|
|
6379
6579
|
}
|
|
6580
|
+
/** Record the chat file-open index for one workspace from a LANDED result
|
|
6581
|
+
* (plan Phase C step C3). Called from renderDoorstopPanel on every render
|
|
6582
|
+
* that has a result, so the index always mirrors the last landed
|
|
6583
|
+
* discovery; never throws and triggers nothing (the hook must be free of
|
|
6584
|
+
* side effects — recording a plain index from a landed result is one). */
|
|
6585
|
+
recordFileOpenIndex(context, result) {
|
|
6586
|
+
const key = workspaceContextKey(context);
|
|
6587
|
+
const existing = this.fileOpenEntries.get(key);
|
|
6588
|
+
if (existing !== void 0 && existing.result === result) {
|
|
6589
|
+
this.fileOpenEntries.delete(key);
|
|
6590
|
+
this.fileOpenEntries.set(key, existing);
|
|
6591
|
+
return;
|
|
6592
|
+
}
|
|
6593
|
+
const entries = /* @__PURE__ */ new Map();
|
|
6594
|
+
for (const item of result.index.items) {
|
|
6595
|
+
entries.set(item.path, { uid: item.uid, documentPrefix: item.documentPrefix });
|
|
6596
|
+
}
|
|
6597
|
+
this.fileOpenEntries.delete(key);
|
|
6598
|
+
this.fileOpenEntries.set(key, { result, entries });
|
|
6599
|
+
this.boundFileOpenEntries();
|
|
6600
|
+
}
|
|
6601
|
+
/** The panel's chat file-open hook (plan Phase C step C3): does the last
|
|
6602
|
+
* landed discovery of this workspace know `path` as an item file? Returns
|
|
6603
|
+
* the panel's navigation query (`{ file: path }` — the key
|
|
6604
|
+
* applyNavigationQuery resolves to the item's uid and document
|
|
6605
|
+
* selection), or undefined when the panel does not own the path (the
|
|
6606
|
+
* host then asks the next panel, or serves the Files fallback). Before
|
|
6607
|
+
* the first load lands there is no index yet → undefined (the chat link
|
|
6608
|
+
* falls back to Files; the panel still deep-links once opened and
|
|
6609
|
+
* re-rendered). */
|
|
6610
|
+
fileOpenQuery(context, path) {
|
|
6611
|
+
const entries = this.fileOpenEntries.get(workspaceContextKey(context));
|
|
6612
|
+
if (entries === void 0) return void 0;
|
|
6613
|
+
return entries.entries.has(path) ? { [DOORSTOP_NAV_FILE]: path } : void 0;
|
|
6614
|
+
}
|
|
6615
|
+
/** Keep the file-open index at the same LRU bound as the controller map
|
|
6616
|
+
* (strictly oldest-first, mirroring evictOldest). */
|
|
6617
|
+
boundFileOpenEntries() {
|
|
6618
|
+
while (this.fileOpenEntries.size > DOORSTOP_WORKSPACE_STATE_LIMIT) {
|
|
6619
|
+
const key = this.fileOpenEntries.keys().next().value;
|
|
6620
|
+
if (key === void 0) break;
|
|
6621
|
+
this.fileOpenEntries.delete(key);
|
|
6622
|
+
}
|
|
6623
|
+
}
|
|
6380
6624
|
evictOldest() {
|
|
6381
6625
|
if (this.workspaces.size < DOORSTOP_WORKSPACE_STATE_LIMIT) return;
|
|
6382
6626
|
const key = this.workspaces.keys().next().value;
|
|
@@ -6384,6 +6628,7 @@ var DoorstopWorkspaceRegistry = class {
|
|
|
6384
6628
|
const controller = this.workspaces.get(key);
|
|
6385
6629
|
if (controller !== void 0) controller.release();
|
|
6386
6630
|
this.workspaces.delete(key);
|
|
6631
|
+
this.fileOpenEntries.delete(key);
|
|
6387
6632
|
}
|
|
6388
6633
|
};
|
|
6389
6634
|
var DoorstopPanelHost = class {
|
|
@@ -6395,127 +6640,94 @@ function workspaceContextKey(context) {
|
|
|
6395
6640
|
return JSON.stringify([context.machine.id, context.workspace.projectId, context.workspace.id]);
|
|
6396
6641
|
}
|
|
6397
6642
|
|
|
6398
|
-
// src/doorstop-
|
|
6399
|
-
var
|
|
6400
|
-
var
|
|
6401
|
-
var
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
"
|
|
6410
|
-
|
|
6411
|
-
"
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
case "normative":
|
|
6416
|
-
case "non-normative":
|
|
6417
|
-
case "reviewed":
|
|
6418
|
-
return "muted";
|
|
6419
|
-
case "unreviewed":
|
|
6420
|
-
case "no-child-links":
|
|
6421
|
-
case "no-links":
|
|
6422
|
-
return "warning";
|
|
6423
|
-
case "inactive":
|
|
6424
|
-
case "suspect-link":
|
|
6425
|
-
case "unknown-link":
|
|
6426
|
-
case "missing-reference":
|
|
6427
|
-
return "danger";
|
|
6643
|
+
// src/doorstop-session-metadata.ts
|
|
6644
|
+
var OPENDOOR_SESSION_METADATA_NAMESPACE = "opendoor";
|
|
6645
|
+
var DOORSTOP_SESSION_METADATA_SCHEMA = 1;
|
|
6646
|
+
var DOORSTOP_SESSION_METADATA_MAX_UIDS = 8;
|
|
6647
|
+
var DOORSTOP_SESSION_METADATA_MAX_UID_LENGTH = 32;
|
|
6648
|
+
var DOORSTOP_SESSION_METADATA_OPS = /* @__PURE__ */ new Set(["review"]);
|
|
6649
|
+
var DOORSTOP_SESSION_METADATA_UID_PATTERN = /^[\w.-]+$/u;
|
|
6650
|
+
function isMetadataUid(value) {
|
|
6651
|
+
return typeof value === "string" && value !== "" && value.length <= DOORSTOP_SESSION_METADATA_MAX_UID_LENGTH && DOORSTOP_SESSION_METADATA_UID_PATTERN.test(value);
|
|
6652
|
+
}
|
|
6653
|
+
function parseDoorstopSessionMetadata(value) {
|
|
6654
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
6655
|
+
const record = value;
|
|
6656
|
+
if (record["schema"] !== DOORSTOP_SESSION_METADATA_SCHEMA) return void 0;
|
|
6657
|
+
const op = record["op"];
|
|
6658
|
+
if (typeof op !== "string" || !DOORSTOP_SESSION_METADATA_OPS.has(op)) {
|
|
6659
|
+
return void 0;
|
|
6428
6660
|
}
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
return "has suspect links";
|
|
6661
|
+
const uidsValue = record["uids"];
|
|
6662
|
+
if (!Array.isArray(uidsValue) || uidsValue.length === 0 || uidsValue.length > DOORSTOP_SESSION_METADATA_MAX_UIDS) {
|
|
6663
|
+
return void 0;
|
|
6664
|
+
}
|
|
6665
|
+
const uids = [];
|
|
6666
|
+
for (const uid of uidsValue) {
|
|
6667
|
+
if (!isMetadataUid(uid)) return void 0;
|
|
6668
|
+
uids.push(uid);
|
|
6438
6669
|
}
|
|
6670
|
+
return {
|
|
6671
|
+
schema: DOORSTOP_SESSION_METADATA_SCHEMA,
|
|
6672
|
+
op,
|
|
6673
|
+
uids
|
|
6674
|
+
};
|
|
6439
6675
|
}
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
const
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6676
|
+
|
|
6677
|
+
// src/browser/doorstop-session-labels.ts
|
|
6678
|
+
var DOORSTOP_PANEL_LOCAL_ID = "workspace.doorstop";
|
|
6679
|
+
var DOORSTOP_SESSION_LABEL_ID = "doorstop.review";
|
|
6680
|
+
function createDoorstopSessionLabel(runtimePluginId) {
|
|
6681
|
+
const panelId = `${runtimePluginId}:${DOORSTOP_PANEL_LOCAL_ID}`;
|
|
6682
|
+
return {
|
|
6683
|
+
id: DOORSTOP_SESSION_LABEL_ID,
|
|
6684
|
+
order: 10,
|
|
6685
|
+
items: (context) => sessionLabelItems(context, panelId)
|
|
6686
|
+
};
|
|
6450
6687
|
}
|
|
6451
|
-
function
|
|
6452
|
-
const
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6688
|
+
function sessionLabelItems(context, panelId) {
|
|
6689
|
+
const value = context.session.metadata?.[OPENDOOR_SESSION_METADATA_NAMESPACE];
|
|
6690
|
+
const metadata = parseDoorstopSessionMetadata(value);
|
|
6691
|
+
if (metadata === void 0) return [];
|
|
6692
|
+
const firstUid = metadata.uids[0];
|
|
6693
|
+
if (firstUid === void 0) return [];
|
|
6694
|
+
const rest = metadata.uids.length - 1;
|
|
6695
|
+
const text = rest === 0 ? firstUid : `${firstUid} +${String(rest)}`;
|
|
6696
|
+
const target = doorstopReopenDestination(context.machine.id, context.workspace, panelId, firstUid);
|
|
6697
|
+
return [
|
|
6698
|
+
{
|
|
6699
|
+
type: "render",
|
|
6700
|
+
render: () => b2`<a
|
|
6701
|
+
class="doorstop-session-label"
|
|
6702
|
+
href="#"
|
|
6703
|
+
title=${`Open ${metadata.uids.join(", ")} in Requirements`}
|
|
6704
|
+
@click=${(event) => {
|
|
6705
|
+
event.preventDefault();
|
|
6706
|
+
event.stopPropagation();
|
|
6707
|
+
void context.navigate(target);
|
|
6708
|
+
}}
|
|
6709
|
+
>${text}</a>`
|
|
6460
6710
|
}
|
|
6461
|
-
|
|
6462
|
-
|
|
6711
|
+
];
|
|
6712
|
+
}
|
|
6713
|
+
function doorstopReopenDestination(machineId, workspace, panelId, uid) {
|
|
6714
|
+
return {
|
|
6715
|
+
machineId,
|
|
6716
|
+
projectId: workspace.projectId,
|
|
6717
|
+
workspaceId: workspace.id,
|
|
6718
|
+
view: "workspace",
|
|
6719
|
+
tool: panelId,
|
|
6720
|
+
query: { [DOORSTOP_NAV_UID]: uid }
|
|
6721
|
+
};
|
|
6463
6722
|
}
|
|
6723
|
+
|
|
6724
|
+
// src/browser/view-model/core.ts
|
|
6464
6725
|
function shortFingerprint(fingerprint) {
|
|
6465
6726
|
return fingerprint === null ? "none" : `${fingerprint.slice(0, 8)}\u2026`;
|
|
6466
6727
|
}
|
|
6467
|
-
var FINDINGS_EMPTY_MESSAGE = "No findings \u2014 the tree is clean.";
|
|
6468
|
-
var FINDINGS_EMPTY_HINT = "Findings are plugin-local; run `doorstop` validation in the terminal for the authoritative check.";
|
|
6469
|
-
var FINDINGS_PLUGIN_LOCAL_NOTE = "plugin-local findings \u2014 `doorstop` validation in the terminal is authoritative";
|
|
6470
|
-
var FINDING_SEVERITY_RANK = {
|
|
6471
|
-
error: 0,
|
|
6472
|
-
warning: 1,
|
|
6473
|
-
info: 2
|
|
6474
|
-
};
|
|
6475
|
-
function findingsViewRows(index) {
|
|
6476
|
-
const rows = [...index.findings, ...index.diagnostics];
|
|
6477
|
-
return rows.sort((a3, b3) => FINDING_SEVERITY_RANK[a3.severity] - FINDING_SEVERITY_RANK[b3.severity]);
|
|
6478
|
-
}
|
|
6479
|
-
function findingsViewCounts(rows) {
|
|
6480
|
-
const counts = { errors: 0, warnings: 0, info: 0 };
|
|
6481
|
-
for (const row of rows) {
|
|
6482
|
-
if (row.severity === "error") counts.errors += 1;
|
|
6483
|
-
else if (row.severity === "warning") counts.warnings += 1;
|
|
6484
|
-
else counts.info += 1;
|
|
6485
|
-
}
|
|
6486
|
-
return counts;
|
|
6487
|
-
}
|
|
6488
|
-
function findingsCountText(counts) {
|
|
6489
|
-
const errors = counts.errors === 1 ? "1 error" : `${String(counts.errors)} errors`;
|
|
6490
|
-
const warnings = counts.warnings === 1 ? "1 warning" : `${String(counts.warnings)} warnings`;
|
|
6491
|
-
return `${errors} \xB7 ${warnings} \xB7 ${String(counts.info)} info`;
|
|
6492
|
-
}
|
|
6493
6728
|
function doorstopPublishTarget(result) {
|
|
6494
6729
|
return result?.settings?.publishTarget ?? DEFAULT_OPENDOOR_SETTINGS.publishTarget;
|
|
6495
6730
|
}
|
|
6496
|
-
function doorstopCommitAfterReview(result) {
|
|
6497
|
-
return result?.settings?.commitAfterReview ?? DEFAULT_OPENDOOR_SETTINGS.commitAfterReview;
|
|
6498
|
-
}
|
|
6499
|
-
function commitOutcomeText(op, outcome) {
|
|
6500
|
-
const gitRun = op === "git-stage" || op === "git-commit" || op === "git-unstage";
|
|
6501
|
-
switch (outcome.status) {
|
|
6502
|
-
case "staged":
|
|
6503
|
-
return `staged ${String(outcome.staged ?? 0)} paths`;
|
|
6504
|
-
case "unstaged":
|
|
6505
|
-
return `unstaged ${String(outcome.unstaged ?? 0)} paths`;
|
|
6506
|
-
case "committed":
|
|
6507
|
-
return gitRun ? `committed ${outcome.sha ?? "<unknown sha>"}` : `commit: ${outcome.sha ?? "<unknown sha>"}`;
|
|
6508
|
-
case "clean":
|
|
6509
|
-
if (!gitRun) return "commit: clean (already committed)";
|
|
6510
|
-
if (op === "git-stage") return "clean \u2014 nothing to stage";
|
|
6511
|
-
if (op === "git-unstage") return "clean \u2014 nothing to unstage";
|
|
6512
|
-
return "clean \u2014 nothing staged";
|
|
6513
|
-
case "skipped":
|
|
6514
|
-
return gitRun ? "skipped" : "commit: skipped (not a git repository | review failed | deadline)";
|
|
6515
|
-
case "failed":
|
|
6516
|
-
return gitRun ? `failed \u2014 ${outcome.stderr ?? "git step errored"}` : `commit: failed \u2014 ${outcome.stderr ?? "git step errored"}`;
|
|
6517
|
-
}
|
|
6518
|
-
}
|
|
6519
6731
|
function lastRunHasMessage(lastRun) {
|
|
6520
6732
|
return lastRun.stdout !== "" || lastRun.stderr !== "" || (lastRun.errorMessage ?? "") !== "" || lastRun.commit !== void 0;
|
|
6521
6733
|
}
|
|
@@ -6591,7 +6803,6 @@ function doorstopPublishCommand(result, target = doorstopPublishTarget(result))
|
|
|
6591
6803
|
const argument = PUBLISH_TARGET_SAFE_TOKEN.test(target) ? target : quoteShellArgument(target);
|
|
6592
6804
|
return `doorstop publish all ${argument}`;
|
|
6593
6805
|
}
|
|
6594
|
-
var isValidTargetUid = isValidDoorstopUid;
|
|
6595
6806
|
function documentConfigFor(index, item) {
|
|
6596
6807
|
return index.byPrefix.get(item.documentPrefix) ?? {
|
|
6597
6808
|
directoryPath: "",
|
|
@@ -6603,24 +6814,6 @@ function documentConfigFor(index, item) {
|
|
|
6603
6814
|
extra: {}
|
|
6604
6815
|
};
|
|
6605
6816
|
}
|
|
6606
|
-
function suspectParentItems(item, index) {
|
|
6607
|
-
const suspects = [];
|
|
6608
|
-
for (const link of item.links) {
|
|
6609
|
-
if (link.fingerprint === null) continue;
|
|
6610
|
-
const target = index.byUid.get(link.uid);
|
|
6611
|
-
if (target === void 0) continue;
|
|
6612
|
-
if (link.fingerprint !== computeItemStamp(target, documentConfigFor(index, target), false)) {
|
|
6613
|
-
suspects.push(target);
|
|
6614
|
-
}
|
|
6615
|
-
}
|
|
6616
|
-
return suspects;
|
|
6617
|
-
}
|
|
6618
|
-
function firstChildDocumentPrefix(index, item) {
|
|
6619
|
-
for (const document2 of index.documents) {
|
|
6620
|
-
if (document2.parentPrefix === item.documentPrefix) return document2.prefix;
|
|
6621
|
-
}
|
|
6622
|
-
return void 0;
|
|
6623
|
-
}
|
|
6624
6817
|
function jsonishText(value) {
|
|
6625
6818
|
if (value === void 0) return "undefined";
|
|
6626
6819
|
const json2 = JSON.stringify(value);
|
|
@@ -6637,68 +6830,179 @@ function referenceListText(references) {
|
|
|
6637
6830
|
return parts.join(" ");
|
|
6638
6831
|
}).join(", ");
|
|
6639
6832
|
}
|
|
6640
|
-
function itemExcerpt(item) {
|
|
6641
|
-
const header = item.header;
|
|
6642
|
-
if (header !== void 0 && header !== "") return header;
|
|
6643
|
-
const firstLine = (item.text.split("\n")[0] ?? "").trim();
|
|
6644
|
-
if (firstLine === "") return "\u2014";
|
|
6645
|
-
return firstLine.length > 80 ? `${firstLine.slice(0, 80)}\u2026` : firstLine;
|
|
6646
|
-
}
|
|
6647
6833
|
|
|
6648
|
-
//
|
|
6649
|
-
var
|
|
6650
|
-
var
|
|
6651
|
-
var
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
}
|
|
6663
|
-
update(t5, e8) {
|
|
6664
|
-
return this.render(...e8);
|
|
6665
|
-
}
|
|
6834
|
+
// src/browser/view-model/item-list.ts
|
|
6835
|
+
var EMPTY_WORKSPACE_MESSAGE = "This workspace has no Doorstop documents \u2014 run `doorstop create REQ ./reqs` in the workspace to start a requirements tree.";
|
|
6836
|
+
var ALL_DOCUMENTS_LABEL = "All";
|
|
6837
|
+
var STATE_CHIP_LABELS = {
|
|
6838
|
+
normative: "normative",
|
|
6839
|
+
"non-normative": "non-normative",
|
|
6840
|
+
inactive: "inactive",
|
|
6841
|
+
reviewed: "reviewed",
|
|
6842
|
+
unreviewed: "unreviewed",
|
|
6843
|
+
"suspect-link": "suspect link",
|
|
6844
|
+
"no-child-links": "no child links",
|
|
6845
|
+
"no-links": "no links",
|
|
6846
|
+
"unknown-link": "unknown link",
|
|
6847
|
+
"missing-reference": "missing reference"
|
|
6666
6848
|
};
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6849
|
+
function stateChipKind(key) {
|
|
6850
|
+
switch (key) {
|
|
6851
|
+
case "normative":
|
|
6852
|
+
case "non-normative":
|
|
6853
|
+
case "reviewed":
|
|
6854
|
+
return "muted";
|
|
6855
|
+
case "unreviewed":
|
|
6856
|
+
case "no-child-links":
|
|
6857
|
+
case "no-links":
|
|
6858
|
+
return "warning";
|
|
6859
|
+
case "inactive":
|
|
6860
|
+
case "suspect-link":
|
|
6861
|
+
case "unknown-link":
|
|
6862
|
+
case "missing-reference":
|
|
6863
|
+
return "danger";
|
|
6864
|
+
}
|
|
6865
|
+
}
|
|
6866
|
+
function dotTitle(dot) {
|
|
6867
|
+
switch (dot) {
|
|
6868
|
+
case "ok":
|
|
6869
|
+
return "all items reviewed, no suspect links";
|
|
6870
|
+
case "unreviewed":
|
|
6871
|
+
return "has unreviewed changes";
|
|
6872
|
+
case "suspect":
|
|
6873
|
+
return "has suspect links";
|
|
6874
|
+
}
|
|
6875
|
+
}
|
|
6876
|
+
function documentStateDots(document2, items) {
|
|
6877
|
+
const own = items.filter((item) => item.documentPrefix === document2.prefix);
|
|
6878
|
+
const dots = [];
|
|
6879
|
+
if (own.length === 0) return dots;
|
|
6880
|
+
const hasSuspect = own.some((item) => item.stateKeys.includes("suspect-link"));
|
|
6881
|
+
const hasUnreviewed = own.some((item) => item.stateKeys.includes("unreviewed"));
|
|
6882
|
+
if (!hasSuspect && !hasUnreviewed) dots.push("ok");
|
|
6883
|
+
if (hasUnreviewed) dots.push("unreviewed");
|
|
6884
|
+
if (hasSuspect) dots.push("suspect");
|
|
6885
|
+
return dots;
|
|
6886
|
+
}
|
|
6887
|
+
function filteredItems(index, documentPrefix, stateFilter, search) {
|
|
6888
|
+
const needle = search.trim().toLowerCase();
|
|
6889
|
+
return index.items.filter((item) => {
|
|
6890
|
+
if (documentPrefix !== void 0 && documentPrefix !== "" && item.documentPrefix !== documentPrefix) {
|
|
6891
|
+
return false;
|
|
6681
6892
|
}
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
const s7 = !!i8[t5];
|
|
6686
|
-
s7 === this.st.has(t5) || this.nt?.has(t5) || (s7 ? (r8.add(t5), this.st.add(t5)) : (r8.remove(t5), this.st.delete(t5)));
|
|
6893
|
+
if (stateFilter !== void 0 && !item.stateKeys.includes(stateFilter)) return false;
|
|
6894
|
+
if (needle !== "" && !`${item.uid} ${item.header ?? ""} ${item.text}`.toLowerCase().includes(needle)) {
|
|
6895
|
+
return false;
|
|
6687
6896
|
}
|
|
6688
|
-
return
|
|
6897
|
+
return true;
|
|
6898
|
+
});
|
|
6899
|
+
}
|
|
6900
|
+
function doorstopShowAdditionalAttribute(result) {
|
|
6901
|
+
return result?.settings?.showAdditionalAttribute ?? DEFAULT_OPENDOOR_SETTINGS.showAdditionalAttribute;
|
|
6902
|
+
}
|
|
6903
|
+
function itemExcerpt(item) {
|
|
6904
|
+
const header = item.header;
|
|
6905
|
+
if (header !== void 0 && header !== "") return header;
|
|
6906
|
+
const firstLine = (item.text.split("\n")[0] ?? "").trim();
|
|
6907
|
+
if (firstLine === "") return "\u2014";
|
|
6908
|
+
return firstLine.length > 80 ? `${firstLine.slice(0, 80)}\u2026` : firstLine;
|
|
6909
|
+
}
|
|
6910
|
+
function itemTextRow(item) {
|
|
6911
|
+
return item.text.trim() === "" ? void 0 : item.text;
|
|
6912
|
+
}
|
|
6913
|
+
function shownAttributeRows(item, keys) {
|
|
6914
|
+
const rows = [];
|
|
6915
|
+
for (const key of keys) {
|
|
6916
|
+
if (!Object.prototype.hasOwnProperty.call(item.attributes, key)) continue;
|
|
6917
|
+
rows.push({ key, text: jsonishText(item.attributes[key]) });
|
|
6689
6918
|
}
|
|
6690
|
-
|
|
6919
|
+
return rows;
|
|
6920
|
+
}
|
|
6921
|
+
|
|
6922
|
+
// src/browser/view-model/palette.ts
|
|
6923
|
+
function doorstopCommitAfterReview(result) {
|
|
6924
|
+
return result?.settings?.commitAfterReview ?? DEFAULT_OPENDOOR_SETTINGS.commitAfterReview;
|
|
6925
|
+
}
|
|
6926
|
+
var isValidTargetUid = isValidDoorstopUid;
|
|
6927
|
+
function suspectParentItems(item, index) {
|
|
6928
|
+
const suspects = [];
|
|
6929
|
+
for (const link of item.links) {
|
|
6930
|
+
if (link.fingerprint === null) continue;
|
|
6931
|
+
const target = index.byUid.get(link.uid);
|
|
6932
|
+
if (target === void 0) continue;
|
|
6933
|
+
if (link.fingerprint !== computeItemStamp(target, documentConfigFor(index, target), false)) {
|
|
6934
|
+
suspects.push(target);
|
|
6935
|
+
}
|
|
6936
|
+
}
|
|
6937
|
+
return suspects;
|
|
6938
|
+
}
|
|
6939
|
+
function firstChildDocumentPrefix(index, item) {
|
|
6940
|
+
for (const document2 of index.documents) {
|
|
6941
|
+
if (document2.parentPrefix === item.documentPrefix) return document2.prefix;
|
|
6942
|
+
}
|
|
6943
|
+
return void 0;
|
|
6944
|
+
}
|
|
6945
|
+
|
|
6946
|
+
// src/browser/view-model/status-bar.ts
|
|
6947
|
+
function commitOutcomeText(op, outcome) {
|
|
6948
|
+
const gitRun = op === "git-stage" || op === "git-commit" || op === "git-unstage";
|
|
6949
|
+
switch (outcome.status) {
|
|
6950
|
+
case "staged":
|
|
6951
|
+
return `staged ${String(outcome.staged ?? 0)} paths`;
|
|
6952
|
+
case "unstaged":
|
|
6953
|
+
return `unstaged ${String(outcome.unstaged ?? 0)} paths`;
|
|
6954
|
+
case "committed":
|
|
6955
|
+
return gitRun ? `committed ${outcome.sha ?? "<unknown sha>"}` : `commit: ${outcome.sha ?? "<unknown sha>"}`;
|
|
6956
|
+
case "clean":
|
|
6957
|
+
if (!gitRun) return "commit: clean (already committed)";
|
|
6958
|
+
if (op === "git-stage") return "clean \u2014 nothing to stage";
|
|
6959
|
+
if (op === "git-unstage") return "clean \u2014 nothing to unstage";
|
|
6960
|
+
return "clean \u2014 nothing staged";
|
|
6961
|
+
case "skipped":
|
|
6962
|
+
return gitRun ? "skipped" : "commit: skipped (not a git repository | review failed | deadline)";
|
|
6963
|
+
case "failed":
|
|
6964
|
+
return gitRun ? `failed \u2014 ${outcome.stderr ?? "git step errored"}` : `commit: failed \u2014 ${outcome.stderr ?? "git step errored"}`;
|
|
6965
|
+
}
|
|
6966
|
+
}
|
|
6967
|
+
|
|
6968
|
+
// src/browser/view-model/findings.ts
|
|
6969
|
+
var FINDINGS_EMPTY_MESSAGE = "No findings \u2014 the tree is clean.";
|
|
6970
|
+
var FINDINGS_EMPTY_HINT = "Findings are plugin-local; run `doorstop` validation in the terminal for the authoritative check.";
|
|
6971
|
+
var FINDINGS_PLUGIN_LOCAL_NOTE = "plugin-local findings \u2014 `doorstop` validation in the terminal is authoritative";
|
|
6972
|
+
var FINDING_SEVERITY_RANK = {
|
|
6973
|
+
error: 0,
|
|
6974
|
+
warning: 1,
|
|
6975
|
+
info: 2
|
|
6976
|
+
};
|
|
6977
|
+
function findingsViewRows(index) {
|
|
6978
|
+
const rows = [...index.findings, ...index.diagnostics];
|
|
6979
|
+
return rows.sort((a3, b3) => FINDING_SEVERITY_RANK[a3.severity] - FINDING_SEVERITY_RANK[b3.severity]);
|
|
6980
|
+
}
|
|
6981
|
+
function findingsViewCounts(rows) {
|
|
6982
|
+
const counts = { errors: 0, warnings: 0, info: 0 };
|
|
6983
|
+
for (const row of rows) {
|
|
6984
|
+
if (row.severity === "error") counts.errors += 1;
|
|
6985
|
+
else if (row.severity === "warning") counts.warnings += 1;
|
|
6986
|
+
else counts.info += 1;
|
|
6987
|
+
}
|
|
6988
|
+
return counts;
|
|
6989
|
+
}
|
|
6990
|
+
function findingsCountText(counts) {
|
|
6991
|
+
const errors = counts.errors === 1 ? "1 error" : `${String(counts.errors)} errors`;
|
|
6992
|
+
const warnings = counts.warnings === 1 ? "1 warning" : `${String(counts.warnings)} warnings`;
|
|
6993
|
+
return `${errors} \xB7 ${warnings} \xB7 ${String(counts.info)} info`;
|
|
6994
|
+
}
|
|
6691
6995
|
|
|
6692
6996
|
// node_modules/lit-html/directive-helpers.js
|
|
6693
|
-
var { I:
|
|
6694
|
-
var
|
|
6997
|
+
var { I: t3 } = j;
|
|
6998
|
+
var i5 = (o8) => o8;
|
|
6695
6999
|
var r4 = (o8) => void 0 === o8.strings;
|
|
6696
7000
|
var s4 = () => document.createComment("");
|
|
6697
7001
|
var v2 = (o8, n7, e8) => {
|
|
6698
7002
|
const l3 = o8._$AA.parentNode, d3 = void 0 === n7 ? o8._$AB : n7._$AA;
|
|
6699
7003
|
if (void 0 === e8) {
|
|
6700
7004
|
const i8 = l3.insertBefore(s4(), d3), n8 = l3.insertBefore(s4(), d3);
|
|
6701
|
-
e8 = new
|
|
7005
|
+
e8 = new t3(i8, n8, o8, o8.options);
|
|
6702
7006
|
} else {
|
|
6703
7007
|
const t5 = e8._$AB.nextSibling, n8 = e8._$AM, c6 = n8 !== o8;
|
|
6704
7008
|
if (c6) {
|
|
@@ -6708,8 +7012,8 @@ var v2 = (o8, n7, e8) => {
|
|
|
6708
7012
|
if (t5 !== d3 || c6) {
|
|
6709
7013
|
let o9 = e8._$AA;
|
|
6710
7014
|
for (; o9 !== t5; ) {
|
|
6711
|
-
const t6 =
|
|
6712
|
-
|
|
7015
|
+
const t6 = i5(o9).nextSibling;
|
|
7016
|
+
i5(l3).insertBefore(o9, d3), o9 = t6;
|
|
6713
7017
|
}
|
|
6714
7018
|
}
|
|
6715
7019
|
}
|
|
@@ -6723,18 +7027,25 @@ var h3 = (o8) => {
|
|
|
6723
7027
|
o8._$AR(), o8._$AA.remove();
|
|
6724
7028
|
};
|
|
6725
7029
|
|
|
6726
|
-
// node_modules/lit-html/
|
|
6727
|
-
var
|
|
6728
|
-
|
|
6729
|
-
|
|
7030
|
+
// node_modules/lit-html/directive.js
|
|
7031
|
+
var t4 = { ATTRIBUTE: 1, CHILD: 2, PROPERTY: 3, BOOLEAN_ATTRIBUTE: 4, EVENT: 5, ELEMENT: 6 };
|
|
7032
|
+
var e4 = (t5) => (...e8) => ({ _$litDirective$: t5, values: e8 });
|
|
7033
|
+
var i6 = class {
|
|
7034
|
+
constructor(t5) {
|
|
6730
7035
|
}
|
|
6731
|
-
|
|
6732
|
-
return this.
|
|
7036
|
+
get _$AU() {
|
|
7037
|
+
return this._$AM._$AU;
|
|
6733
7038
|
}
|
|
6734
|
-
|
|
6735
|
-
|
|
7039
|
+
_$AT(t5, e8, i8) {
|
|
7040
|
+
this._$Ct = t5, this._$AM = e8, this._$Ci = i8;
|
|
6736
7041
|
}
|
|
6737
|
-
|
|
7042
|
+
_$AS(t5, e8) {
|
|
7043
|
+
return this.update(t5, e8);
|
|
7044
|
+
}
|
|
7045
|
+
update(t5, e8) {
|
|
7046
|
+
return this.render(...e8);
|
|
7047
|
+
}
|
|
7048
|
+
};
|
|
6738
7049
|
|
|
6739
7050
|
// node_modules/lit-html/async-directive.js
|
|
6740
7051
|
var s5 = (i8, t5) => {
|
|
@@ -6768,9 +7079,9 @@ function n4(i8, t5 = false, e8 = 0) {
|
|
|
6768
7079
|
else s5(this, i8);
|
|
6769
7080
|
}
|
|
6770
7081
|
var c4 = (i8) => {
|
|
6771
|
-
i8.type ==
|
|
7082
|
+
i8.type == t4.CHILD && (i8._$AP ??= n4, i8._$AQ ??= h4);
|
|
6772
7083
|
};
|
|
6773
|
-
var f3 = class extends
|
|
7084
|
+
var f3 = class extends i6 {
|
|
6774
7085
|
constructor() {
|
|
6775
7086
|
super(...arguments), this._$AN = void 0;
|
|
6776
7087
|
}
|
|
@@ -6794,7 +7105,7 @@ var f3 = class extends i5 {
|
|
|
6794
7105
|
};
|
|
6795
7106
|
|
|
6796
7107
|
// node_modules/lit-html/directives/ref.js
|
|
6797
|
-
var
|
|
7108
|
+
var e5 = () => new h5();
|
|
6798
7109
|
var h5 = class {
|
|
6799
7110
|
};
|
|
6800
7111
|
var o6 = /* @__PURE__ */ new WeakMap();
|
|
@@ -6824,59 +7135,6 @@ var n5 = e4(class extends f3 {
|
|
|
6824
7135
|
}
|
|
6825
7136
|
});
|
|
6826
7137
|
|
|
6827
|
-
// node_modules/lit-html/directives/repeat.js
|
|
6828
|
-
var u4 = (e8, s6, t5) => {
|
|
6829
|
-
const r8 = /* @__PURE__ */ new Map();
|
|
6830
|
-
for (let l3 = s6; l3 <= t5; l3++) r8.set(e8[l3], l3);
|
|
6831
|
-
return r8;
|
|
6832
|
-
};
|
|
6833
|
-
var c5 = e4(class extends i5 {
|
|
6834
|
-
constructor(e8) {
|
|
6835
|
-
if (super(e8), e8.type !== t3.CHILD) throw Error("repeat() can only be used in text expressions");
|
|
6836
|
-
}
|
|
6837
|
-
dt(e8, s6, t5) {
|
|
6838
|
-
let r8;
|
|
6839
|
-
void 0 === t5 ? t5 = s6 : void 0 !== s6 && (r8 = s6);
|
|
6840
|
-
const l3 = [], o8 = [];
|
|
6841
|
-
let i8 = 0;
|
|
6842
|
-
for (const s7 of e8) l3[i8] = r8 ? r8(s7, i8) : i8, o8[i8] = t5(s7, i8), i8++;
|
|
6843
|
-
return { values: o8, keys: l3 };
|
|
6844
|
-
}
|
|
6845
|
-
render(e8, s6, t5) {
|
|
6846
|
-
return this.dt(e8, s6, t5).values;
|
|
6847
|
-
}
|
|
6848
|
-
update(s6, [t5, r8, c6]) {
|
|
6849
|
-
const d3 = M2(s6), { values: p4, keys: a3 } = this.dt(t5, r8, c6);
|
|
6850
|
-
if (!Array.isArray(d3)) return this.ut = a3, p4;
|
|
6851
|
-
const h6 = this.ut ??= [], v3 = [];
|
|
6852
|
-
let m3, y3, x2 = 0, j2 = d3.length - 1, k2 = 0, w2 = p4.length - 1;
|
|
6853
|
-
for (; x2 <= j2 && k2 <= w2; ) if (null === d3[x2]) x2++;
|
|
6854
|
-
else if (null === d3[j2]) j2--;
|
|
6855
|
-
else if (h6[x2] === a3[k2]) v3[k2] = u3(d3[x2], p4[k2]), x2++, k2++;
|
|
6856
|
-
else if (h6[j2] === a3[w2]) v3[w2] = u3(d3[j2], p4[w2]), j2--, w2--;
|
|
6857
|
-
else if (h6[x2] === a3[w2]) v3[w2] = u3(d3[x2], p4[w2]), v2(s6, v3[w2 + 1], d3[x2]), x2++, w2--;
|
|
6858
|
-
else if (h6[j2] === a3[k2]) v3[k2] = u3(d3[j2], p4[k2]), v2(s6, d3[x2], d3[j2]), j2--, k2++;
|
|
6859
|
-
else if (void 0 === m3 && (m3 = u4(a3, k2, w2), y3 = u4(h6, x2, j2)), m3.has(h6[x2])) if (m3.has(h6[j2])) {
|
|
6860
|
-
const e8 = y3.get(a3[k2]), t6 = void 0 !== e8 ? d3[e8] : null;
|
|
6861
|
-
if (null === t6) {
|
|
6862
|
-
const e9 = v2(s6, d3[x2]);
|
|
6863
|
-
u3(e9, p4[k2]), v3[k2] = e9;
|
|
6864
|
-
} else v3[k2] = u3(t6, p4[k2]), v2(s6, d3[x2], t6), d3[e8] = null;
|
|
6865
|
-
k2++;
|
|
6866
|
-
} else h3(d3[j2]), j2--;
|
|
6867
|
-
else h3(d3[x2]), x2++;
|
|
6868
|
-
for (; k2 <= w2; ) {
|
|
6869
|
-
const e8 = v2(s6, v3[w2 + 1]);
|
|
6870
|
-
u3(e8, p4[k2]), v3[k2++] = e8;
|
|
6871
|
-
}
|
|
6872
|
-
for (; x2 <= j2; ) {
|
|
6873
|
-
const e8 = d3[x2++];
|
|
6874
|
-
null !== e8 && h3(e8);
|
|
6875
|
-
}
|
|
6876
|
-
return this.ut = a3, p3(s6, v3), E;
|
|
6877
|
-
}
|
|
6878
|
-
});
|
|
6879
|
-
|
|
6880
7138
|
// node_modules/@lit/reactive-element/decorators/property.js
|
|
6881
7139
|
var o7 = { attribute: true, type: String, converter: u, reflect: false, hasChanged: f };
|
|
6882
7140
|
var r6 = (t5 = o7, e8, r8) => {
|
|
@@ -6912,8 +7170,8 @@ function r7(r8) {
|
|
|
6912
7170
|
return n6({ ...r8, state: true, attribute: false });
|
|
6913
7171
|
}
|
|
6914
7172
|
|
|
6915
|
-
// src/
|
|
6916
|
-
var
|
|
7173
|
+
// src/browser/styles/base.ts
|
|
7174
|
+
var baseStyles = i`
|
|
6917
7175
|
:host {
|
|
6918
7176
|
flex: 1 1 auto;
|
|
6919
7177
|
min-height: 0;
|
|
@@ -6922,7 +7180,7 @@ var panelStyles = i`
|
|
|
6922
7180
|
overflow: hidden;
|
|
6923
7181
|
color: var(--pi-text);
|
|
6924
7182
|
background: var(--pi-bg);
|
|
6925
|
-
font:
|
|
7183
|
+
font: 14px system-ui, sans-serif;
|
|
6926
7184
|
}
|
|
6927
7185
|
|
|
6928
7186
|
button {
|
|
@@ -6948,7 +7206,7 @@ var panelStyles = i`
|
|
|
6948
7206
|
border-radius: 6px;
|
|
6949
7207
|
background: var(--pi-surface);
|
|
6950
7208
|
color: var(--pi-text);
|
|
6951
|
-
font-size:
|
|
7209
|
+
font-size: 13px;
|
|
6952
7210
|
padding: 4px 6px;
|
|
6953
7211
|
}
|
|
6954
7212
|
|
|
@@ -6957,13 +7215,16 @@ var panelStyles = i`
|
|
|
6957
7215
|
border-radius: 5px;
|
|
6958
7216
|
background: var(--pi-bg);
|
|
6959
7217
|
padding: 1px 4px;
|
|
6960
|
-
font:
|
|
7218
|
+
font: 12px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
6961
7219
|
}
|
|
6962
7220
|
|
|
6963
7221
|
.doorstop-muted {
|
|
6964
7222
|
color: var(--pi-muted);
|
|
6965
7223
|
}
|
|
7224
|
+
`;
|
|
6966
7225
|
|
|
7226
|
+
// src/browser/styles/project-actions.ts
|
|
7227
|
+
var projectActionsStyles = i`
|
|
6967
7228
|
/* --- project actions (region 1) --- */
|
|
6968
7229
|
.doorstop-project-actions {
|
|
6969
7230
|
flex: 0 0 auto;
|
|
@@ -7002,7 +7263,7 @@ var panelStyles = i`
|
|
|
7002
7263
|
.doorstop-doc-chip {
|
|
7003
7264
|
border-radius: 999px;
|
|
7004
7265
|
padding: 2px 9px;
|
|
7005
|
-
font-size:
|
|
7266
|
+
font-size: 13px;
|
|
7006
7267
|
}
|
|
7007
7268
|
|
|
7008
7269
|
.doorstop-doc-chip.is-selected {
|
|
@@ -7013,12 +7274,12 @@ var panelStyles = i`
|
|
|
7013
7274
|
|
|
7014
7275
|
.doorstop-doc-arrow {
|
|
7015
7276
|
color: var(--pi-muted);
|
|
7016
|
-
font-size:
|
|
7277
|
+
font-size: 12px;
|
|
7017
7278
|
}
|
|
7018
7279
|
|
|
7019
7280
|
.doorstop-doc-count {
|
|
7020
7281
|
color: var(--pi-muted);
|
|
7021
|
-
font-size:
|
|
7282
|
+
font-size: 12px;
|
|
7022
7283
|
}
|
|
7023
7284
|
|
|
7024
7285
|
.doorstop-dot {
|
|
@@ -7053,7 +7314,7 @@ var panelStyles = i`
|
|
|
7053
7314
|
border-radius: 999px;
|
|
7054
7315
|
color: var(--pi-warning);
|
|
7055
7316
|
padding: 1px 8px;
|
|
7056
|
-
font-size:
|
|
7317
|
+
font-size: 13px;
|
|
7057
7318
|
}
|
|
7058
7319
|
|
|
7059
7320
|
/* --- project-scoped git actions (plan-add-git-actions Phase D step 17) --- */
|
|
@@ -7076,7 +7337,7 @@ var panelStyles = i`
|
|
|
7076
7337
|
background: transparent;
|
|
7077
7338
|
color: var(--pi-muted);
|
|
7078
7339
|
padding: 2px 9px;
|
|
7079
|
-
font-size:
|
|
7340
|
+
font-size: 13px;
|
|
7080
7341
|
}
|
|
7081
7342
|
|
|
7082
7343
|
.doorstop-git-status-text {
|
|
@@ -7132,7 +7393,7 @@ var panelStyles = i`
|
|
|
7132
7393
|
background: transparent;
|
|
7133
7394
|
color: var(--pi-muted);
|
|
7134
7395
|
padding: 3px 10px;
|
|
7135
|
-
font-size:
|
|
7396
|
+
font-size: 13px;
|
|
7136
7397
|
}
|
|
7137
7398
|
|
|
7138
7399
|
.doorstop-view-tab + .doorstop-view-tab {
|
|
@@ -7143,7 +7404,10 @@ var panelStyles = i`
|
|
|
7143
7404
|
background: var(--pi-selection-bg);
|
|
7144
7405
|
color: var(--pi-accent);
|
|
7145
7406
|
}
|
|
7407
|
+
`;
|
|
7146
7408
|
|
|
7409
|
+
// src/browser/styles/item-list.ts
|
|
7410
|
+
var itemListStyles = i`
|
|
7147
7411
|
/* --- viewer (regions 2 + 3; the filters row and split stack) --- */
|
|
7148
7412
|
.doorstop-viewer {
|
|
7149
7413
|
flex: 1 1 auto;
|
|
@@ -7260,7 +7524,7 @@ var panelStyles = i`
|
|
|
7260
7524
|
.doorstop-item-row {
|
|
7261
7525
|
display: grid;
|
|
7262
7526
|
grid-template-columns: max-content max-content minmax(0, 1fr) max-content;
|
|
7263
|
-
gap: 8px;
|
|
7527
|
+
gap: 2px 8px;
|
|
7264
7528
|
align-items: baseline;
|
|
7265
7529
|
width: 100%;
|
|
7266
7530
|
border: 0;
|
|
@@ -7277,7 +7541,7 @@ var panelStyles = i`
|
|
|
7277
7541
|
|
|
7278
7542
|
.doorstop-item-level {
|
|
7279
7543
|
color: var(--pi-muted);
|
|
7280
|
-
font-size:
|
|
7544
|
+
font-size: 12px;
|
|
7281
7545
|
}
|
|
7282
7546
|
|
|
7283
7547
|
.doorstop-item-summary {
|
|
@@ -7288,6 +7552,49 @@ var panelStyles = i`
|
|
|
7288
7552
|
color: var(--pi-text-secondary);
|
|
7289
7553
|
}
|
|
7290
7554
|
|
|
7555
|
+
/* A fifth column exists only when showAdditionalAttribute configures
|
|
7556
|
+
at least one name; otherwise the default rows keep the original
|
|
7557
|
+
4-column track (and its single 8px summary→chips gap). */
|
|
7558
|
+
.doorstop-items.has-item-attrs .doorstop-item-row {
|
|
7559
|
+
grid-template-columns: max-content max-content minmax(0, 1fr) minmax(0, max-content) max-content;
|
|
7560
|
+
}
|
|
7561
|
+
|
|
7562
|
+
/* The configured key: value attribute pairs (settings field
|
|
7563
|
+
showAdditionalAttribute). The column collapses to zero when the row
|
|
7564
|
+
renders nothing here (no configured attribute the item has). The
|
|
7565
|
+
max-width bounds a pathologically long value so it cannot starve
|
|
7566
|
+
the summary's fr track before the ellipsis kicks in. */
|
|
7567
|
+
.doorstop-item-attrs {
|
|
7568
|
+
min-width: 0;
|
|
7569
|
+
max-width: 40ch;
|
|
7570
|
+
overflow: hidden;
|
|
7571
|
+
text-overflow: ellipsis;
|
|
7572
|
+
white-space: nowrap;
|
|
7573
|
+
color: var(--pi-muted);
|
|
7574
|
+
font-size: 12px;
|
|
7575
|
+
}
|
|
7576
|
+
|
|
7577
|
+
/* The item body ("text" configured in showAdditionalAttribute) on its own
|
|
7578
|
+
indented second line below level/uid/summary/chips. Full-width implicit
|
|
7579
|
+
grid row; 5ch inline indent; clamped to 2 lines with ellipsis — the full
|
|
7580
|
+
body lives in the detail pane. The span only exists when rendered, so no
|
|
7581
|
+
container modifier class is needed. */
|
|
7582
|
+
.doorstop-item-text {
|
|
7583
|
+
grid-column: 1 / -1;
|
|
7584
|
+
align-self: start;
|
|
7585
|
+
padding-inline-start: 5ch;
|
|
7586
|
+
min-width: 0;
|
|
7587
|
+
color: var(--pi-muted);
|
|
7588
|
+
/* Body-like content: reads one tier above the 12px metadata. */
|
|
7589
|
+
font-size: 13px;
|
|
7590
|
+
white-space: pre-wrap;
|
|
7591
|
+
overflow-wrap: anywhere;
|
|
7592
|
+
display: -webkit-box;
|
|
7593
|
+
-webkit-box-orient: vertical;
|
|
7594
|
+
-webkit-line-clamp: 2;
|
|
7595
|
+
overflow: hidden;
|
|
7596
|
+
}
|
|
7597
|
+
|
|
7291
7598
|
.doorstop-item-chips {
|
|
7292
7599
|
display: flex;
|
|
7293
7600
|
flex-wrap: wrap;
|
|
@@ -7301,8 +7608,14 @@ var panelStyles = i`
|
|
|
7301
7608
|
keyboard path is the region-4 Stage/Unstage buttons. The same class
|
|
7302
7609
|
hosts both directions: the plus (git add) icon and the unstage
|
|
7303
7610
|
minus (git reset) icon, chosen by {@link itemUnstageable} /
|
|
7304
|
-
{@link itemStageable} precedence.
|
|
7305
|
-
|
|
7611
|
+
{@link itemStageable} precedence.
|
|
7612
|
+
|
|
7613
|
+
The per-row "insert UID into the agent prompt" arrow shares the
|
|
7614
|
+
pointer-only <span> contract and this visual box; it keeps a
|
|
7615
|
+
distinct class because the +/reset icon and the arrow stay
|
|
7616
|
+
independently evolvable. */
|
|
7617
|
+
.doorstop-item-add,
|
|
7618
|
+
.doorstop-item-insert {
|
|
7306
7619
|
display: inline-flex;
|
|
7307
7620
|
align-items: center;
|
|
7308
7621
|
align-self: center;
|
|
@@ -7313,7 +7626,8 @@ var panelStyles = i`
|
|
|
7313
7626
|
cursor: pointer;
|
|
7314
7627
|
}
|
|
7315
7628
|
|
|
7316
|
-
.doorstop-item-add:hover
|
|
7629
|
+
.doorstop-item-add:hover,
|
|
7630
|
+
.doorstop-item-insert:hover {
|
|
7317
7631
|
color: var(--pi-text);
|
|
7318
7632
|
}
|
|
7319
7633
|
|
|
@@ -7321,6 +7635,7 @@ var panelStyles = i`
|
|
|
7321
7635
|
.doorstop-chip {
|
|
7322
7636
|
border-radius: 999px;
|
|
7323
7637
|
padding: 0 7px;
|
|
7638
|
+
/* Strict parity: pi-web keeps compact chips at 11px. */
|
|
7324
7639
|
font-size: 11px;
|
|
7325
7640
|
white-space: nowrap;
|
|
7326
7641
|
}
|
|
@@ -7344,10 +7659,126 @@ var panelStyles = i`
|
|
|
7344
7659
|
border: 1px solid var(--pi-success-border);
|
|
7345
7660
|
color: var(--pi-success);
|
|
7346
7661
|
}
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
.
|
|
7350
|
-
|
|
7662
|
+
`;
|
|
7663
|
+
var findingsStyles = i`
|
|
7664
|
+
/* --- findings view (spec §7.2) --- */
|
|
7665
|
+
.doorstop-findings-view {
|
|
7666
|
+
flex: 1 1 auto;
|
|
7667
|
+
min-height: 0;
|
|
7668
|
+
overflow: auto;
|
|
7669
|
+
padding: 10px 12px;
|
|
7670
|
+
display: grid;
|
|
7671
|
+
gap: 8px;
|
|
7672
|
+
align-content: start;
|
|
7673
|
+
}
|
|
7674
|
+
|
|
7675
|
+
.doorstop-findings-head {
|
|
7676
|
+
display: flex;
|
|
7677
|
+
flex-wrap: wrap;
|
|
7678
|
+
align-items: baseline;
|
|
7679
|
+
gap: 6px 10px;
|
|
7680
|
+
}
|
|
7681
|
+
|
|
7682
|
+
.doorstop-findings-counts {
|
|
7683
|
+
font-size: 13px;
|
|
7684
|
+
font-weight: 600;
|
|
7685
|
+
}
|
|
7686
|
+
|
|
7687
|
+
.doorstop-findings-note {
|
|
7688
|
+
font-size: 12px;
|
|
7689
|
+
}
|
|
7690
|
+
|
|
7691
|
+
.doorstop-findings-list {
|
|
7692
|
+
display: grid;
|
|
7693
|
+
gap: 3px;
|
|
7694
|
+
}
|
|
7695
|
+
|
|
7696
|
+
.doorstop-finding-row {
|
|
7697
|
+
display: flex;
|
|
7698
|
+
flex-wrap: wrap;
|
|
7699
|
+
align-items: baseline;
|
|
7700
|
+
gap: 6px;
|
|
7701
|
+
border-radius: 6px;
|
|
7702
|
+
padding: 5px 8px;
|
|
7703
|
+
}
|
|
7704
|
+
|
|
7705
|
+
.doorstop-finding-row.doorstop-error {
|
|
7706
|
+
border: 1px solid var(--pi-danger);
|
|
7707
|
+
background: color-mix(in srgb, var(--pi-danger) 9%, transparent);
|
|
7708
|
+
}
|
|
7709
|
+
|
|
7710
|
+
.doorstop-finding-row.doorstop-warning {
|
|
7711
|
+
border: 1px solid var(--pi-warning-border);
|
|
7712
|
+
background: color-mix(in srgb, var(--pi-warning) 9%, transparent);
|
|
7713
|
+
}
|
|
7714
|
+
|
|
7715
|
+
.doorstop-finding-row.doorstop-info {
|
|
7716
|
+
border: 1px solid var(--pi-border-muted);
|
|
7717
|
+
color: var(--pi-muted);
|
|
7718
|
+
}
|
|
7719
|
+
|
|
7720
|
+
.doorstop-finding-row .doorstop-severity {
|
|
7721
|
+
background: var(--pi-border-muted);
|
|
7722
|
+
color: var(--pi-muted);
|
|
7723
|
+
}
|
|
7724
|
+
|
|
7725
|
+
.doorstop-finding-row.doorstop-error .doorstop-severity {
|
|
7726
|
+
background: var(--pi-danger);
|
|
7727
|
+
color: var(--pi-bg);
|
|
7728
|
+
}
|
|
7729
|
+
|
|
7730
|
+
.doorstop-finding-row.doorstop-warning .doorstop-severity {
|
|
7731
|
+
background: var(--pi-warning);
|
|
7732
|
+
color: var(--pi-bg);
|
|
7733
|
+
}
|
|
7734
|
+
|
|
7735
|
+
.doorstop-finding-uid {
|
|
7736
|
+
border: 0;
|
|
7737
|
+
border-radius: 5px;
|
|
7738
|
+
background: transparent;
|
|
7739
|
+
color: inherit;
|
|
7740
|
+
padding: 0;
|
|
7741
|
+
font-weight: 600;
|
|
7742
|
+
}
|
|
7743
|
+
|
|
7744
|
+
.doorstop-finding-uid:hover {
|
|
7745
|
+
text-decoration: underline;
|
|
7746
|
+
cursor: pointer;
|
|
7747
|
+
}
|
|
7748
|
+
|
|
7749
|
+
.doorstop-finding-message {
|
|
7750
|
+
min-width: 0;
|
|
7751
|
+
overflow-wrap: anywhere;
|
|
7752
|
+
}
|
|
7753
|
+
|
|
7754
|
+
/* --- empty states --- */
|
|
7755
|
+
.doorstop-empty {
|
|
7756
|
+
margin: 10px 12px;
|
|
7757
|
+
border: 1px dashed var(--pi-border-muted);
|
|
7758
|
+
border-radius: 8px;
|
|
7759
|
+
color: var(--pi-muted);
|
|
7760
|
+
padding: 12px;
|
|
7761
|
+
overflow: auto;
|
|
7762
|
+
}
|
|
7763
|
+
|
|
7764
|
+
.doorstop-empty p {
|
|
7765
|
+
margin: 0;
|
|
7766
|
+
}
|
|
7767
|
+
|
|
7768
|
+
.doorstop-empty button {
|
|
7769
|
+
margin-top: 10px;
|
|
7770
|
+
}
|
|
7771
|
+
|
|
7772
|
+
.doorstop-empty .doorstop-muted {
|
|
7773
|
+
margin-top: 6px;
|
|
7774
|
+
}
|
|
7775
|
+
`;
|
|
7776
|
+
|
|
7777
|
+
// src/browser/styles/detail-pane.ts
|
|
7778
|
+
var detailPaneStyles = i`
|
|
7779
|
+
/* --- detail pane (region 3) --- */
|
|
7780
|
+
.doorstop-detail {
|
|
7781
|
+
padding: 12px;
|
|
7351
7782
|
display: grid;
|
|
7352
7783
|
gap: 10px;
|
|
7353
7784
|
align-content: start;
|
|
@@ -7362,14 +7793,14 @@ var panelStyles = i`
|
|
|
7362
7793
|
|
|
7363
7794
|
.doorstop-detail-head h3 {
|
|
7364
7795
|
margin: 0;
|
|
7365
|
-
font-size:
|
|
7796
|
+
font-size: 16px;
|
|
7366
7797
|
min-width: 0;
|
|
7367
7798
|
overflow-wrap: anywhere;
|
|
7368
7799
|
}
|
|
7369
7800
|
|
|
7370
7801
|
.doorstop-level {
|
|
7371
7802
|
color: var(--pi-muted);
|
|
7372
|
-
font-size:
|
|
7803
|
+
font-size: 13px;
|
|
7373
7804
|
}
|
|
7374
7805
|
|
|
7375
7806
|
.doorstop-flags {
|
|
@@ -7383,7 +7814,7 @@ var panelStyles = i`
|
|
|
7383
7814
|
border-radius: 999px;
|
|
7384
7815
|
color: var(--pi-muted);
|
|
7385
7816
|
padding: 0 7px;
|
|
7386
|
-
font-size:
|
|
7817
|
+
font-size: 12px;
|
|
7387
7818
|
}
|
|
7388
7819
|
|
|
7389
7820
|
.doorstop-flag.is-inactive {
|
|
@@ -7406,7 +7837,7 @@ var panelStyles = i`
|
|
|
7406
7837
|
|
|
7407
7838
|
.doorstop-changes-summary {
|
|
7408
7839
|
color: var(--pi-muted);
|
|
7409
|
-
font-size:
|
|
7840
|
+
font-size: 12px;
|
|
7410
7841
|
text-transform: uppercase;
|
|
7411
7842
|
letter-spacing: 0.03em;
|
|
7412
7843
|
cursor: pointer;
|
|
@@ -7419,12 +7850,12 @@ var panelStyles = i`
|
|
|
7419
7850
|
|
|
7420
7851
|
.doorstop-changes-notice {
|
|
7421
7852
|
margin: 6px 0 0;
|
|
7422
|
-
font-size:
|
|
7853
|
+
font-size: 13px;
|
|
7423
7854
|
}
|
|
7424
7855
|
|
|
7425
7856
|
.doorstop-changes-source {
|
|
7426
7857
|
margin: 6px 0 4px;
|
|
7427
|
-
font-size:
|
|
7858
|
+
font-size: 12px;
|
|
7428
7859
|
}
|
|
7429
7860
|
|
|
7430
7861
|
.doorstop-diff-lines {
|
|
@@ -7494,7 +7925,7 @@ var panelStyles = i`
|
|
|
7494
7925
|
|
|
7495
7926
|
.doorstop-field-name {
|
|
7496
7927
|
color: var(--pi-muted);
|
|
7497
|
-
font-size:
|
|
7928
|
+
font-size: 12px;
|
|
7498
7929
|
text-transform: uppercase;
|
|
7499
7930
|
letter-spacing: 0.03em;
|
|
7500
7931
|
}
|
|
@@ -7502,7 +7933,7 @@ var panelStyles = i`
|
|
|
7502
7933
|
.doorstop-field-chip {
|
|
7503
7934
|
border-radius: 5px;
|
|
7504
7935
|
border: 1px solid var(--pi-border-muted);
|
|
7505
|
-
font-size:
|
|
7936
|
+
font-size: 13px;
|
|
7506
7937
|
padding: 1px 6px;
|
|
7507
7938
|
overflow-wrap: anywhere;
|
|
7508
7939
|
}
|
|
@@ -7525,7 +7956,7 @@ var panelStyles = i`
|
|
|
7525
7956
|
|
|
7526
7957
|
.doorstop-section-title {
|
|
7527
7958
|
color: var(--pi-muted);
|
|
7528
|
-
font-size:
|
|
7959
|
+
font-size: 12px;
|
|
7529
7960
|
text-transform: uppercase;
|
|
7530
7961
|
letter-spacing: 0.03em;
|
|
7531
7962
|
margin: 0;
|
|
@@ -7586,7 +8017,7 @@ var panelStyles = i`
|
|
|
7586
8017
|
|
|
7587
8018
|
.doorstop-fingerprint {
|
|
7588
8019
|
color: var(--pi-muted);
|
|
7589
|
-
font-size:
|
|
8020
|
+
font-size: 12px;
|
|
7590
8021
|
/* The link row wraps, which could push the trash can onto a second
|
|
7591
8022
|
line on narrow panes. Let the fingerprint shrink and ellipsize
|
|
7592
8023
|
instead (min-width: 0 lifts the flex min-content floor). */
|
|
@@ -7611,7 +8042,7 @@ var panelStyles = i`
|
|
|
7611
8042
|
|
|
7612
8043
|
.doorstop-attribute dt {
|
|
7613
8044
|
color: var(--pi-muted);
|
|
7614
|
-
font-size:
|
|
8045
|
+
font-size: 13px;
|
|
7615
8046
|
}
|
|
7616
8047
|
|
|
7617
8048
|
.doorstop-attribute dd {
|
|
@@ -7652,10 +8083,14 @@ var panelStyles = i`
|
|
|
7652
8083
|
|
|
7653
8084
|
.doorstop-finding-severity {
|
|
7654
8085
|
flex: 0 0 auto;
|
|
8086
|
+
/* Strict parity: pi-web keeps compact chips at 11px. */
|
|
7655
8087
|
font-size: 11px;
|
|
7656
8088
|
font-weight: 600;
|
|
7657
8089
|
}
|
|
8090
|
+
`;
|
|
7658
8091
|
|
|
8092
|
+
// src/browser/styles/item-action-palette.ts
|
|
8093
|
+
var itemActionPaletteStyles = i`
|
|
7659
8094
|
/* --- item action palette (region 4), sized to the prompt footer --- */
|
|
7660
8095
|
.doorstop-action-palette {
|
|
7661
8096
|
flex: 0 0 auto;
|
|
@@ -7701,7 +8136,7 @@ var panelStyles = i`
|
|
|
7701
8136
|
|
|
7702
8137
|
.doorstop-op-error {
|
|
7703
8138
|
color: var(--pi-danger);
|
|
7704
|
-
font-size:
|
|
8139
|
+
font-size: 13px;
|
|
7705
8140
|
}
|
|
7706
8141
|
|
|
7707
8142
|
.doorstop-menu {
|
|
@@ -7734,115 +8169,10 @@ var panelStyles = i`
|
|
|
7734
8169
|
.doorstop-menu-item:hover:not(:disabled) {
|
|
7735
8170
|
background: var(--pi-selection-bg);
|
|
7736
8171
|
}
|
|
8172
|
+
`;
|
|
7737
8173
|
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
flex: 1 1 auto;
|
|
7741
|
-
min-height: 0;
|
|
7742
|
-
overflow: auto;
|
|
7743
|
-
padding: 10px 12px;
|
|
7744
|
-
display: grid;
|
|
7745
|
-
gap: 8px;
|
|
7746
|
-
align-content: start;
|
|
7747
|
-
}
|
|
7748
|
-
|
|
7749
|
-
.doorstop-findings-head {
|
|
7750
|
-
display: flex;
|
|
7751
|
-
flex-wrap: wrap;
|
|
7752
|
-
align-items: baseline;
|
|
7753
|
-
gap: 6px 10px;
|
|
7754
|
-
}
|
|
7755
|
-
|
|
7756
|
-
.doorstop-findings-counts {
|
|
7757
|
-
font-size: 12px;
|
|
7758
|
-
font-weight: 600;
|
|
7759
|
-
}
|
|
7760
|
-
|
|
7761
|
-
.doorstop-findings-note {
|
|
7762
|
-
font-size: 11px;
|
|
7763
|
-
}
|
|
7764
|
-
|
|
7765
|
-
.doorstop-findings-list {
|
|
7766
|
-
display: grid;
|
|
7767
|
-
gap: 3px;
|
|
7768
|
-
}
|
|
7769
|
-
|
|
7770
|
-
.doorstop-finding-row {
|
|
7771
|
-
display: flex;
|
|
7772
|
-
flex-wrap: wrap;
|
|
7773
|
-
align-items: baseline;
|
|
7774
|
-
gap: 6px;
|
|
7775
|
-
border-radius: 6px;
|
|
7776
|
-
padding: 5px 8px;
|
|
7777
|
-
}
|
|
7778
|
-
|
|
7779
|
-
.doorstop-finding-row.doorstop-error {
|
|
7780
|
-
border: 1px solid var(--pi-danger);
|
|
7781
|
-
background: color-mix(in srgb, var(--pi-danger) 9%, transparent);
|
|
7782
|
-
}
|
|
7783
|
-
|
|
7784
|
-
.doorstop-finding-row.doorstop-warning {
|
|
7785
|
-
border: 1px solid var(--pi-warning-border);
|
|
7786
|
-
background: color-mix(in srgb, var(--pi-warning) 9%, transparent);
|
|
7787
|
-
}
|
|
7788
|
-
|
|
7789
|
-
.doorstop-finding-row.doorstop-info {
|
|
7790
|
-
border: 1px solid var(--pi-border-muted);
|
|
7791
|
-
color: var(--pi-muted);
|
|
7792
|
-
}
|
|
7793
|
-
|
|
7794
|
-
.doorstop-finding-row .doorstop-severity {
|
|
7795
|
-
background: var(--pi-border-muted);
|
|
7796
|
-
color: var(--pi-muted);
|
|
7797
|
-
}
|
|
7798
|
-
|
|
7799
|
-
.doorstop-finding-row.doorstop-error .doorstop-severity {
|
|
7800
|
-
background: var(--pi-danger);
|
|
7801
|
-
color: var(--pi-bg);
|
|
7802
|
-
}
|
|
7803
|
-
|
|
7804
|
-
.doorstop-finding-row.doorstop-warning .doorstop-severity {
|
|
7805
|
-
background: var(--pi-warning);
|
|
7806
|
-
color: var(--pi-bg);
|
|
7807
|
-
}
|
|
7808
|
-
|
|
7809
|
-
.doorstop-finding-uid {
|
|
7810
|
-
border: 0;
|
|
7811
|
-
border-radius: 5px;
|
|
7812
|
-
background: transparent;
|
|
7813
|
-
color: inherit;
|
|
7814
|
-
padding: 0;
|
|
7815
|
-
font-weight: 600;
|
|
7816
|
-
}
|
|
7817
|
-
|
|
7818
|
-
.doorstop-finding-uid:hover {
|
|
7819
|
-
text-decoration: underline;
|
|
7820
|
-
cursor: pointer;
|
|
7821
|
-
}
|
|
7822
|
-
|
|
7823
|
-
.doorstop-finding-message {
|
|
7824
|
-
min-width: 0;
|
|
7825
|
-
overflow-wrap: anywhere;
|
|
7826
|
-
}
|
|
7827
|
-
|
|
7828
|
-
/* --- empty states --- */
|
|
7829
|
-
.doorstop-empty {
|
|
7830
|
-
margin: 10px 12px;
|
|
7831
|
-
border: 1px dashed var(--pi-border-muted);
|
|
7832
|
-
border-radius: 8px;
|
|
7833
|
-
color: var(--pi-muted);
|
|
7834
|
-
padding: 12px;
|
|
7835
|
-
overflow: auto;
|
|
7836
|
-
}
|
|
7837
|
-
|
|
7838
|
-
.doorstop-empty p {
|
|
7839
|
-
margin: 0;
|
|
7840
|
-
}
|
|
7841
|
-
|
|
7842
|
-
.doorstop-empty .doorstop-muted {
|
|
7843
|
-
margin-top: 6px;
|
|
7844
|
-
}
|
|
7845
|
-
|
|
8174
|
+
// src/browser/styles/status-bar.ts
|
|
8175
|
+
var statusBarStyles = i`
|
|
7846
8176
|
/* --- status bar (region 5), sized to the host center status bar --- */
|
|
7847
8177
|
.doorstop-status-bar {
|
|
7848
8178
|
flex: 0 0 auto;
|
|
@@ -7860,7 +8190,7 @@ var panelStyles = i`
|
|
|
7860
8190
|
border-top: 1px solid var(--pi-border);
|
|
7861
8191
|
background: var(--pi-bg);
|
|
7862
8192
|
color: var(--pi-muted);
|
|
7863
|
-
font:
|
|
8193
|
+
font: 13px system-ui, sans-serif;
|
|
7864
8194
|
}
|
|
7865
8195
|
|
|
7866
8196
|
.doorstop-status-bar-row {
|
|
@@ -7887,7 +8217,7 @@ var panelStyles = i`
|
|
|
7887
8217
|
|
|
7888
8218
|
.doorstop-last-run-commit {
|
|
7889
8219
|
margin: 0 0 6px;
|
|
7890
|
-
font-size:
|
|
8220
|
+
font-size: 13px;
|
|
7891
8221
|
overflow-wrap: anywhere;
|
|
7892
8222
|
}
|
|
7893
8223
|
|
|
@@ -7895,6 +8225,7 @@ var panelStyles = i`
|
|
|
7895
8225
|
flex: 0 0 auto; /* the badge label keeps its full width */
|
|
7896
8226
|
border-radius: 999px;
|
|
7897
8227
|
padding: 0 7px;
|
|
8228
|
+
/* Strict parity: pi-web keeps compact chips at 11px. */
|
|
7898
8229
|
font-size: 11px;
|
|
7899
8230
|
white-space: nowrap;
|
|
7900
8231
|
}
|
|
@@ -7927,12 +8258,12 @@ var panelStyles = i`
|
|
|
7927
8258
|
text-overflow: ellipsis;
|
|
7928
8259
|
white-space: nowrap;
|
|
7929
8260
|
color: var(--pi-muted);
|
|
7930
|
-
font-size:
|
|
8261
|
+
font-size: 13px;
|
|
7931
8262
|
}
|
|
7932
8263
|
|
|
7933
8264
|
.doorstop-last-run-dismiss {
|
|
7934
8265
|
flex: 0 0 auto;
|
|
7935
|
-
/* Buttons do not inherit font; match the bar's
|
|
8266
|
+
/* Buttons do not inherit font; match the bar's 13px system-ui. */
|
|
7936
8267
|
font: inherit;
|
|
7937
8268
|
}
|
|
7938
8269
|
|
|
@@ -7942,7 +8273,7 @@ var panelStyles = i`
|
|
|
7942
8273
|
background: var(--pi-surface);
|
|
7943
8274
|
color: var(--pi-text);
|
|
7944
8275
|
padding: 6px 8px;
|
|
7945
|
-
font:
|
|
8276
|
+
font: 12px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
7946
8277
|
white-space: pre-wrap;
|
|
7947
8278
|
word-break: break-word;
|
|
7948
8279
|
overflow-wrap: anywhere;
|
|
@@ -7954,21 +8285,297 @@ var panelStyles = i`
|
|
|
7954
8285
|
.doorstop-last-run-notice {
|
|
7955
8286
|
margin: 4px 0 0;
|
|
7956
8287
|
color: var(--pi-muted);
|
|
7957
|
-
font-size:
|
|
8288
|
+
font-size: 12px;
|
|
7958
8289
|
}
|
|
7959
8290
|
`;
|
|
7960
8291
|
|
|
7961
|
-
// src/doorstop-panel-
|
|
8292
|
+
// src/browser/doorstop-panel-styles.ts
|
|
8293
|
+
var panelStyles = i`${baseStyles}${projectActionsStyles}${itemListStyles}${detailPaneStyles}${itemActionPaletteStyles}${findingsStyles}${statusBarStyles}`;
|
|
8294
|
+
|
|
8295
|
+
// node_modules/lit-html/directives/class-map.js
|
|
8296
|
+
var e7 = e4(class extends i6 {
|
|
8297
|
+
constructor(t5) {
|
|
8298
|
+
if (super(t5), t5.type !== t4.ATTRIBUTE || "class" !== t5.name || t5.strings?.length > 2) throw Error("`classMap()` can only be used in the `class` attribute and must be the only part in the attribute.");
|
|
8299
|
+
}
|
|
8300
|
+
render(t5) {
|
|
8301
|
+
return " " + Object.keys(t5).filter((s6) => t5[s6]).join(" ") + " ";
|
|
8302
|
+
}
|
|
8303
|
+
update(s6, [i8]) {
|
|
8304
|
+
if (void 0 === this.st) {
|
|
8305
|
+
this.st = /* @__PURE__ */ new Set(), void 0 !== s6.strings && (this.nt = new Set(s6.strings.join(" ").split(/\s/).filter((t5) => "" !== t5)));
|
|
8306
|
+
for (const t5 in i8) i8[t5] && !this.nt?.has(t5) && this.st.add(t5);
|
|
8307
|
+
return this.render(i8);
|
|
8308
|
+
}
|
|
8309
|
+
const r8 = s6.element.classList;
|
|
8310
|
+
for (const t5 of this.st) t5 in i8 || (r8.remove(t5), this.st.delete(t5));
|
|
8311
|
+
for (const t5 in i8) {
|
|
8312
|
+
const s7 = !!i8[t5];
|
|
8313
|
+
s7 === this.st.has(t5) || this.nt?.has(t5) || (s7 ? (r8.add(t5), this.st.add(t5)) : (r8.remove(t5), this.st.delete(t5)));
|
|
8314
|
+
}
|
|
8315
|
+
return E;
|
|
8316
|
+
}
|
|
8317
|
+
});
|
|
8318
|
+
|
|
8319
|
+
// src/browser/doorstop-panel-icons.ts
|
|
7962
8320
|
var doorstopIconSvg = w`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2"/><rect width="8" height="4" x="8" y="3" rx="1"/><path d="m9 12 2 2 4-4"/></svg>`;
|
|
7963
8321
|
var refreshIconSvg = w`<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 4v6h6"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>`;
|
|
7964
8322
|
var validateIconSvg = w`<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="m9 12 2 2 4-4"/></svg>`;
|
|
7965
8323
|
var publishIconSvg = w`<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 19V5"/><path d="m5 12 7-7 7 7"/></svg>`;
|
|
7966
|
-
var gitStageIconSvg = w`<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><
|
|
7967
|
-
var gitUnstageIconSvg = w`<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><
|
|
8324
|
+
var gitStageIconSvg = w`<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 12h12"/><path d="M12 6v12"/></svg>`;
|
|
8325
|
+
var gitUnstageIconSvg = w`<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 12h12"/></svg>`;
|
|
7968
8326
|
var gitCommitIconSvg = w`<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>`;
|
|
8327
|
+
var insertPromptIconSvg = w`<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 19V5"/><path d="m5 12 7-7 7 7"/></svg>`;
|
|
7969
8328
|
var trashIconSvg = w`<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M10 11v6"/><path d="M14 11v6"/></svg>`;
|
|
7970
8329
|
|
|
7971
|
-
// src/
|
|
8330
|
+
// src/browser/sections/project-actions.ts
|
|
8331
|
+
function renderProjectActions(host) {
|
|
8332
|
+
const publishTarget = doorstopPublishTarget(host.result);
|
|
8333
|
+
const projectPath = host.context?.workspace.path ?? "";
|
|
8334
|
+
const projectLabel = projectPath === "" ? "Doorstop" : projectPath.split("/").filter(Boolean).pop() ?? projectPath;
|
|
8335
|
+
return b2`
|
|
8336
|
+
<section class="doorstop-project-actions">
|
|
8337
|
+
<strong class="doorstop-title" title=${projectPath === "" ? A : projectPath}>${doorstopIconSvg}${projectLabel}</strong>
|
|
8338
|
+
${renderViewToggle(host)}
|
|
8339
|
+
<div class="doorstop-toolbar-actions">
|
|
8340
|
+
${host.stale ? b2`<button type="button" class="doorstop-stale" title="Doorstop ran or files changed behind the panel — click to rescan" @click=${() => onRefreshClick(host)}>stale — refresh</button>` : A}
|
|
8341
|
+
${host.confirmSkipped ? b2`<span class="doorstop-muted doorstop-confirm-skipped" title="No confirmation dialog is available in this environment — publishing proceeded without one">confirmation skipped — publishing</span>` : A}
|
|
8342
|
+
<button type="button" class="doorstop-refresh" title="Re-read the workspace" @click=${() => onRefreshClick(host)}>${refreshIconSvg}Refresh</button>
|
|
8343
|
+
<button type="button" class="doorstop-validate" title="Run \`doorstop\` in the workspace (terminal when unpaired)" ?disabled=${host.runInProgress !== void 0} @click=${() => onValidateClick(host)}>${validateIconSvg}Run validation</button>
|
|
8344
|
+
<button type="button" class="doorstop-publish" title=${`Publish the tree to ${publishTarget}`} ?disabled=${host.runInProgress !== void 0} @click=${() => onPublishClick(host)}>${publishIconSvg}Publish HTML</button>
|
|
8345
|
+
${renderGitActions(host)}
|
|
8346
|
+
</div>
|
|
8347
|
+
</section>
|
|
8348
|
+
`;
|
|
8349
|
+
}
|
|
8350
|
+
function renderViewToggle(host) {
|
|
8351
|
+
return b2`
|
|
8352
|
+
<div class="doorstop-view-toggle" role="tablist" aria-label="Requirements panel view">
|
|
8353
|
+
<button
|
|
8354
|
+
type="button"
|
|
8355
|
+
role="tab"
|
|
8356
|
+
class=${e7({
|
|
8357
|
+
"doorstop-view-tab": true,
|
|
8358
|
+
"doorstop-view-items": true,
|
|
8359
|
+
"is-selected": host.view === "items"
|
|
8360
|
+
})}
|
|
8361
|
+
aria-selected=${host.view === "items" ? "true" : "false"}
|
|
8362
|
+
title="Item list and detail"
|
|
8363
|
+
@click=${() => {
|
|
8364
|
+
host.view = "items";
|
|
8365
|
+
}}
|
|
8366
|
+
>Items</button>
|
|
8367
|
+
<button
|
|
8368
|
+
type="button"
|
|
8369
|
+
role="tab"
|
|
8370
|
+
class=${e7({
|
|
8371
|
+
"doorstop-view-tab": true,
|
|
8372
|
+
"doorstop-view-findings": true,
|
|
8373
|
+
"is-selected": host.view === "findings"
|
|
8374
|
+
})}
|
|
8375
|
+
aria-selected=${host.view === "findings" ? "true" : "false"}
|
|
8376
|
+
title="Validation findings"
|
|
8377
|
+
@click=${() => {
|
|
8378
|
+
host.view = "findings";
|
|
8379
|
+
}}
|
|
8380
|
+
>Findings</button>
|
|
8381
|
+
</div>
|
|
8382
|
+
`;
|
|
8383
|
+
}
|
|
8384
|
+
function renderGitActions(host) {
|
|
8385
|
+
if (!host.backendActive()) return A;
|
|
8386
|
+
const hasDocuments = (host.result?.index.documents.length ?? 0) > 0;
|
|
8387
|
+
return b2`
|
|
8388
|
+
${renderGitStatus(host)}
|
|
8389
|
+
<button
|
|
8390
|
+
type="button"
|
|
8391
|
+
class="doorstop-git-stage"
|
|
8392
|
+
title="Stage all Doorstop-managed files (requirements, documents, configs)"
|
|
8393
|
+
?disabled=${host.runInProgress !== void 0 || !hasDocuments}
|
|
8394
|
+
@click=${() => onGitStageClick(host)}
|
|
8395
|
+
>${gitStageIconSvg}Stage all</button>
|
|
8396
|
+
<div class="doorstop-git-commit">
|
|
8397
|
+
<input
|
|
8398
|
+
type="text"
|
|
8399
|
+
class="doorstop-git-commit-input"
|
|
8400
|
+
placeholder="Commit message"
|
|
8401
|
+
aria-label="Git commit message"
|
|
8402
|
+
?disabled=${host.runInProgress !== void 0}
|
|
8403
|
+
${n5(host.gitCommitInputRef)}
|
|
8404
|
+
@input=${(event) => onGitCommitInput(host, event)}
|
|
8405
|
+
@keydown=${(event) => onGitCommitKeydown(host, event)}
|
|
8406
|
+
/>
|
|
8407
|
+
<button
|
|
8408
|
+
type="button"
|
|
8409
|
+
class="doorstop-git-commit-button"
|
|
8410
|
+
title="Commit the staged index with this message"
|
|
8411
|
+
?disabled=${host.runInProgress !== void 0 || host.gitCommitMessage.trim() === ""}
|
|
8412
|
+
@click=${() => onGitCommitClick(host)}
|
|
8413
|
+
>${gitCommitIconSvg}Commit</button>
|
|
8414
|
+
${host.gitActionError === void 0 ? A : b2`<span class="doorstop-op-error" role="alert">${host.gitActionError}</span>`}
|
|
8415
|
+
</div>
|
|
8416
|
+
`;
|
|
8417
|
+
}
|
|
8418
|
+
function renderGitStatus(host) {
|
|
8419
|
+
const view = host.gitStatusView;
|
|
8420
|
+
if (view === void 0) return A;
|
|
8421
|
+
const refresh = () => {
|
|
8422
|
+
void host.controller?.requestGitStatus();
|
|
8423
|
+
};
|
|
8424
|
+
switch (view.state) {
|
|
8425
|
+
case "loading":
|
|
8426
|
+
return b2`<button
|
|
8427
|
+
type="button"
|
|
8428
|
+
class="doorstop-git-status"
|
|
8429
|
+
title="Refreshing git status…"
|
|
8430
|
+
aria-busy=${host.gitStatusInFlight}
|
|
8431
|
+
@click=${refresh}
|
|
8432
|
+
><span class="doorstop-git-status-text">⎇ …</span></button>`;
|
|
8433
|
+
case "no-git":
|
|
8434
|
+
return b2`<button type="button" class="doorstop-git-status" title="Not a git repository — click to re-check" aria-busy=${host.gitStatusInFlight} @click=${refresh}><span class="doorstop-git-status-text">no git</span></button>`;
|
|
8435
|
+
case "error":
|
|
8436
|
+
return b2`<button
|
|
8437
|
+
type="button"
|
|
8438
|
+
class="doorstop-git-status doorstop-git-status-error"
|
|
8439
|
+
title=${`Git status unavailable \u2014 ${view.errorMessage ?? "request failed"} \u2014 click to retry`}
|
|
8440
|
+
aria-busy=${host.gitStatusInFlight}
|
|
8441
|
+
@click=${refresh}
|
|
8442
|
+
><span class="doorstop-git-status-text">git status error — retry</span></button>`;
|
|
8443
|
+
case "ready":
|
|
8444
|
+
return b2`<button type="button" class="doorstop-git-status" title="Git status — click to refresh" aria-busy=${host.gitStatusInFlight} @click=${refresh}><span class="doorstop-git-status-text">${gitStatusText(view.response)}</span></button>`;
|
|
8445
|
+
}
|
|
8446
|
+
}
|
|
8447
|
+
function onRefreshClick(host) {
|
|
8448
|
+
void host.controller?.invalidate();
|
|
8449
|
+
}
|
|
8450
|
+
function onValidateClick(host) {
|
|
8451
|
+
host.runDoorstop("validate", "Doorstop: validate", { op: "validate" }, "doorstop", true);
|
|
8452
|
+
}
|
|
8453
|
+
function onPublishClick(host) {
|
|
8454
|
+
const target = doorstopPublishTarget(host.result);
|
|
8455
|
+
if (typeof window.confirm === "function") {
|
|
8456
|
+
if (!window.confirm(`Publish the Doorstop tree as HTML to ${target} in the workspace terminal?`)) return;
|
|
8457
|
+
host.confirmSkipped = false;
|
|
8458
|
+
} else {
|
|
8459
|
+
host.confirmSkipped = true;
|
|
8460
|
+
}
|
|
8461
|
+
host.runDoorstop(
|
|
8462
|
+
"publish",
|
|
8463
|
+
"Doorstop: publish",
|
|
8464
|
+
{ op: "publish", target },
|
|
8465
|
+
doorstopPublishCommand(host.result, target),
|
|
8466
|
+
false
|
|
8467
|
+
);
|
|
8468
|
+
}
|
|
8469
|
+
function onGitStageClick(host) {
|
|
8470
|
+
const controller = host.controller;
|
|
8471
|
+
if (controller === void 0) return;
|
|
8472
|
+
if (controller.runInProgress !== void 0) return;
|
|
8473
|
+
const result = host.result;
|
|
8474
|
+
if (result === void 0) {
|
|
8475
|
+
host.gitActionError = "The workspace is not loaded \u2014 refresh first";
|
|
8476
|
+
return;
|
|
8477
|
+
}
|
|
8478
|
+
const paths = doorstopPaths(result);
|
|
8479
|
+
if (paths.length === 0) {
|
|
8480
|
+
host.gitActionError = "Nothing to stage \u2014 the workspace has no Doorstop-managed files";
|
|
8481
|
+
return;
|
|
8482
|
+
}
|
|
8483
|
+
host.gitActionError = void 0;
|
|
8484
|
+
void controller.runGitStage(paths);
|
|
8485
|
+
}
|
|
8486
|
+
function onGitCommitInput(host, event) {
|
|
8487
|
+
host.gitCommitMessage = event.target.value;
|
|
8488
|
+
}
|
|
8489
|
+
function onGitCommitClick(host) {
|
|
8490
|
+
gitCommitSubmit(host);
|
|
8491
|
+
}
|
|
8492
|
+
function onGitCommitKeydown(host, event) {
|
|
8493
|
+
const input = host.gitCommitInputRef.value;
|
|
8494
|
+
if (input === void 0) return;
|
|
8495
|
+
if (event.key === "Enter") {
|
|
8496
|
+
event.preventDefault();
|
|
8497
|
+
gitCommitSubmit(host);
|
|
8498
|
+
} else if (event.key === "Escape") {
|
|
8499
|
+
input.value = "";
|
|
8500
|
+
host.gitCommitMessage = "";
|
|
8501
|
+
host.gitActionError = void 0;
|
|
8502
|
+
}
|
|
8503
|
+
}
|
|
8504
|
+
function gitCommitSubmit(host) {
|
|
8505
|
+
const controller = host.controller;
|
|
8506
|
+
if (controller === void 0) return;
|
|
8507
|
+
if (controller.runInProgress !== void 0) return;
|
|
8508
|
+
const input = host.gitCommitInputRef.value;
|
|
8509
|
+
const message = input?.value.trim() ?? "";
|
|
8510
|
+
if (message === "") {
|
|
8511
|
+
host.gitActionError = "Enter a commit message";
|
|
8512
|
+
input?.focus();
|
|
8513
|
+
return;
|
|
8514
|
+
}
|
|
8515
|
+
host.gitActionError = void 0;
|
|
8516
|
+
void controller.runGitCommit(message).then(() => {
|
|
8517
|
+
const lastRun = controller.lastRun;
|
|
8518
|
+
if (lastRun?.op === "git-commit" && lastRun.status === "ok" && input !== void 0) {
|
|
8519
|
+
input.value = "";
|
|
8520
|
+
host.gitCommitMessage = "";
|
|
8521
|
+
}
|
|
8522
|
+
});
|
|
8523
|
+
}
|
|
8524
|
+
|
|
8525
|
+
// node_modules/lit-html/directives/repeat.js
|
|
8526
|
+
var u4 = (e8, s6, t5) => {
|
|
8527
|
+
const r8 = /* @__PURE__ */ new Map();
|
|
8528
|
+
for (let l3 = s6; l3 <= t5; l3++) r8.set(e8[l3], l3);
|
|
8529
|
+
return r8;
|
|
8530
|
+
};
|
|
8531
|
+
var c5 = e4(class extends i6 {
|
|
8532
|
+
constructor(e8) {
|
|
8533
|
+
if (super(e8), e8.type !== t4.CHILD) throw Error("repeat() can only be used in text expressions");
|
|
8534
|
+
}
|
|
8535
|
+
dt(e8, s6, t5) {
|
|
8536
|
+
let r8;
|
|
8537
|
+
void 0 === t5 ? t5 = s6 : void 0 !== s6 && (r8 = s6);
|
|
8538
|
+
const l3 = [], o8 = [];
|
|
8539
|
+
let i8 = 0;
|
|
8540
|
+
for (const s7 of e8) l3[i8] = r8 ? r8(s7, i8) : i8, o8[i8] = t5(s7, i8), i8++;
|
|
8541
|
+
return { values: o8, keys: l3 };
|
|
8542
|
+
}
|
|
8543
|
+
render(e8, s6, t5) {
|
|
8544
|
+
return this.dt(e8, s6, t5).values;
|
|
8545
|
+
}
|
|
8546
|
+
update(s6, [t5, r8, c6]) {
|
|
8547
|
+
const d3 = M2(s6), { values: p4, keys: a3 } = this.dt(t5, r8, c6);
|
|
8548
|
+
if (!Array.isArray(d3)) return this.ut = a3, p4;
|
|
8549
|
+
const h6 = this.ut ??= [], v3 = [];
|
|
8550
|
+
let m3, y3, x2 = 0, j2 = d3.length - 1, k2 = 0, w2 = p4.length - 1;
|
|
8551
|
+
for (; x2 <= j2 && k2 <= w2; ) if (null === d3[x2]) x2++;
|
|
8552
|
+
else if (null === d3[j2]) j2--;
|
|
8553
|
+
else if (h6[x2] === a3[k2]) v3[k2] = u3(d3[x2], p4[k2]), x2++, k2++;
|
|
8554
|
+
else if (h6[j2] === a3[w2]) v3[w2] = u3(d3[j2], p4[w2]), j2--, w2--;
|
|
8555
|
+
else if (h6[x2] === a3[w2]) v3[w2] = u3(d3[x2], p4[w2]), v2(s6, v3[w2 + 1], d3[x2]), x2++, w2--;
|
|
8556
|
+
else if (h6[j2] === a3[k2]) v3[k2] = u3(d3[j2], p4[k2]), v2(s6, d3[x2], d3[j2]), j2--, k2++;
|
|
8557
|
+
else if (void 0 === m3 && (m3 = u4(a3, k2, w2), y3 = u4(h6, x2, j2)), m3.has(h6[x2])) if (m3.has(h6[j2])) {
|
|
8558
|
+
const e8 = y3.get(a3[k2]), t6 = void 0 !== e8 ? d3[e8] : null;
|
|
8559
|
+
if (null === t6) {
|
|
8560
|
+
const e9 = v2(s6, d3[x2]);
|
|
8561
|
+
u3(e9, p4[k2]), v3[k2] = e9;
|
|
8562
|
+
} else v3[k2] = u3(t6, p4[k2]), v2(s6, d3[x2], t6), d3[e8] = null;
|
|
8563
|
+
k2++;
|
|
8564
|
+
} else h3(d3[j2]), j2--;
|
|
8565
|
+
else h3(d3[x2]), x2++;
|
|
8566
|
+
for (; k2 <= w2; ) {
|
|
8567
|
+
const e8 = v2(s6, v3[w2 + 1]);
|
|
8568
|
+
u3(e8, p4[k2]), v3[k2++] = e8;
|
|
8569
|
+
}
|
|
8570
|
+
for (; x2 <= j2; ) {
|
|
8571
|
+
const e8 = d3[x2++];
|
|
8572
|
+
null !== e8 && h3(e8);
|
|
8573
|
+
}
|
|
8574
|
+
return this.ut = a3, p3(s6, v3), E;
|
|
8575
|
+
}
|
|
8576
|
+
});
|
|
8577
|
+
|
|
8578
|
+
// src/browser/doorstop-prompts.ts
|
|
7972
8579
|
function andJoin(values) {
|
|
7973
8580
|
if (values.length === 0) return "";
|
|
7974
8581
|
if (values.length === 1) return values[0] ?? "";
|
|
@@ -8002,39 +8609,776 @@ function fixSuspectLinksPrompt(item, parents) {
|
|
|
8002
8609
|
const parentItems = andJoin(
|
|
8003
8610
|
parents.map((parent) => `${parent.uid} (\`${parent.path}\`)`)
|
|
8004
8611
|
);
|
|
8005
|
-
if (parents.length === 1) {
|
|
8006
|
-
return `${item.uid} has a suspect link to ${parents[0]?.uid}: the recorded fingerprint no longer matches because that parent changed after ${item.uid} was last reviewed. Read \`${item.path}\` and ${parentItems}, compare the two texts, and propose updated text for ${item.uid} that still traces correctly to the changed parent. Then run \`doorstop clear ${item.uid}\` in the workspace root to re-record the parent's current fingerprint, and run \`doorstop\` to confirm the suspect link is gone.`;
|
|
8612
|
+
if (parents.length === 1) {
|
|
8613
|
+
return `${item.uid} has a suspect link to ${parents[0]?.uid}: the recorded fingerprint no longer matches because that parent changed after ${item.uid} was last reviewed. Read \`${item.path}\` and ${parentItems}, compare the two texts, and propose updated text for ${item.uid} that still traces correctly to the changed parent. Then run \`doorstop clear ${item.uid}\` in the workspace root to re-record the parent's current fingerprint, and run \`doorstop\` to confirm the suspect link is gone.`;
|
|
8614
|
+
}
|
|
8615
|
+
return `${item.uid} has suspect links to ${andJoin(parents.map((parent) => parent.uid))}: the recorded fingerprints no longer match because those parents changed after ${item.uid} was last reviewed. Read \`${item.path}\` and the changed parents (${parentItems}), compare the texts, and propose updated text for ${item.uid} that still traces correctly to every changed parent. Then run \`doorstop clear ${item.uid}\` in the workspace root to re-record the parents' current fingerprints, and run \`doorstop\` to confirm no suspect link remains.`;
|
|
8616
|
+
}
|
|
8617
|
+
function draftChildRequirementPrompt(parent, childPrefix) {
|
|
8618
|
+
if (childPrefix.trim() === "") {
|
|
8619
|
+
throw new TypeError(
|
|
8620
|
+
"draftChildRequirementPrompt requires a non-empty child document prefix: `doorstop add` names its document by prefix"
|
|
8621
|
+
);
|
|
8622
|
+
}
|
|
8623
|
+
return `Draft a new ${childPrefix} requirement that traces to ${parent.uid} (\`${parent.path}\`). Run \`doorstop add ${childPrefix}\` in the workspace root and note the UID it prints for the new item. Link the new item to its parent with \`doorstop link <new-uid> ${parent.uid}\`, substituting the printed UID for <new-uid>. Then fill in the new item's text so it elaborates ${parent.uid} at ${childPrefix} level, referencing any relevant files (prefer \`doorstop edit <new-uid>\` when available, otherwise edit the item file the add command created \u2014 doorstop normalizes formatting on the next command run). Finally run \`doorstop\` in the workspace root and confirm the new item introduces no WARNING or ERROR before judging whether it is ready for \`doorstop review <new-uid>\`.`;
|
|
8624
|
+
}
|
|
8625
|
+
function referencePaths(item) {
|
|
8626
|
+
const paths = item.references?.map((reference) => reference.path) ?? [];
|
|
8627
|
+
return item.ref === "" ? paths : [item.ref, ...paths];
|
|
8628
|
+
}
|
|
8629
|
+
function reviewReadinessPrompt(item, children = []) {
|
|
8630
|
+
const parents = andJoin(item.links.map((link) => link.uid));
|
|
8631
|
+
const references = referencePaths(item);
|
|
8632
|
+
const childLinks = andJoin(
|
|
8633
|
+
children.map((child) => `${child.uid} (\`${child.path}\`)`)
|
|
8634
|
+
);
|
|
8635
|
+
const facts = [
|
|
8636
|
+
item.text.trim() === "" ? "its text is empty (that alone blocks review)" : "its text is filled in",
|
|
8637
|
+
item.links.length === 0 ? "it declares no parent links" : `its recorded parent links (${parents}) are not suspect or missing a fingerprint`,
|
|
8638
|
+
children.length === 0 ? "its child links are left to the `doorstop` run below" : `its child links are declared (${childLinks})`,
|
|
8639
|
+
references.length === 0 ? "it has no file references to verify" : `its file references exist (${references.map((path) => `\`${path}\``).join(", ")})`
|
|
8640
|
+
];
|
|
8641
|
+
return `Check whether ${item.uid} is ready for \`doorstop review ${item.uid}\`. Read \`${item.path}\` and verify that ${facts.join("; ")}. Then run \`doorstop\` in the workspace root and weigh any WARNING or ERROR it reports about ${item.uid} (for example a requirement that should link to ${item.uid} but does not). Report a verdict, and if ${item.uid} is not ready, the exact fixes to make first.`;
|
|
8642
|
+
}
|
|
8643
|
+
function composeReviewCommand(item) {
|
|
8644
|
+
return `/opendoor-review ${item.uid}
|
|
8645
|
+
Review ${item.uid} (\`${item.path}\`) against its child links and references; run \`doorstop\` validation and, when it is ready, \`doorstop review ${item.uid}\`.`;
|
|
8646
|
+
}
|
|
8647
|
+
function setupProjectPrompt() {
|
|
8648
|
+
return `Set up a Doorstop requirements project in this workspace. Use the \`ask_user\` tool to ask the user two questions before creating anything: what the first requirements document should be called (default: REQ) and whether item text should be shown in the opendoor panel (default: no). Run \`git init\` if the workspace is not a git repository. Then run \`doorstop create <chosen UID prefix> ./<prefix-derived folder: REQ to reqs, URD to urds>\` in the workspace root and \`doorstop\` to confirm it validates. If \`.pi-web/opendoor.json\` does not exist, create it with {"version": 1} plus "showAdditionalAttribute": ["text"] when the user opted into showing text; leave publish.target, discovery.excludedDirectories, and commitAfterReview at their defaults. Commit the created \`.doorstop.yml\`, requirements tree, and settings file.`;
|
|
8649
|
+
}
|
|
8650
|
+
|
|
8651
|
+
// node_modules/lit-html/directives/keyed.js
|
|
8652
|
+
var i7 = e4(class extends i6 {
|
|
8653
|
+
constructor() {
|
|
8654
|
+
super(...arguments), this.key = A;
|
|
8655
|
+
}
|
|
8656
|
+
render(r8, t5) {
|
|
8657
|
+
return this.key = r8, t5;
|
|
8658
|
+
}
|
|
8659
|
+
update(r8, [t5, e8]) {
|
|
8660
|
+
return t5 !== this.key && (p3(r8), this.key = t5), e8;
|
|
8661
|
+
}
|
|
8662
|
+
});
|
|
8663
|
+
|
|
8664
|
+
// src/browser/sections/detail-pane.ts
|
|
8665
|
+
function renderDetail(host, result) {
|
|
8666
|
+
const selectedUid = host.selectedUid;
|
|
8667
|
+
if (selectedUid === void 0) {
|
|
8668
|
+
return b2`<p class="doorstop-muted doorstop-standalone">Select an item in the list to inspect its details.</p>`;
|
|
8669
|
+
}
|
|
8670
|
+
const item = result.index.byUid.get(selectedUid);
|
|
8671
|
+
if (item === void 0) {
|
|
8672
|
+
return b2`<p class="doorstop-muted doorstop-standalone">This item is no longer available — refresh the panel.</p>`;
|
|
8673
|
+
}
|
|
8674
|
+
const children = result.index.childrenByUid.get(item.uid) ?? [];
|
|
8675
|
+
const findings = result.index.findings.filter((finding) => finding.uid === item.uid);
|
|
8676
|
+
return b2`
|
|
8677
|
+
<section class="doorstop-detail" aria-label="Item details">
|
|
8678
|
+
<div class="doorstop-detail-head">
|
|
8679
|
+
<code class="doorstop-detail-uid">${item.uid}</code>
|
|
8680
|
+
<span class="doorstop-level">level ${item.level}</span>
|
|
8681
|
+
${item.header === void 0 ? A : b2`<h3>${item.header}</h3>`}
|
|
8682
|
+
</div>
|
|
8683
|
+
${renderFlags(host, item)}
|
|
8684
|
+
<div class="doorstop-state-chip-row">
|
|
8685
|
+
${item.stateKeys.map((key) => host.renderStateChip(key))}
|
|
8686
|
+
</div>
|
|
8687
|
+
<h4 class="doorstop-section-title">Text</h4>
|
|
8688
|
+
<p class=${item.text === "" ? "doorstop-text doorstop-text-empty" : "doorstop-text"}>${item.text === "" ? "empty" : item.text}</p>
|
|
8689
|
+
${renderChangesSinceReview(host, item)}
|
|
8690
|
+
<h4 class="doorstop-section-title">Parent links</h4>
|
|
8691
|
+
${renderLinksOut(host, item, result.index)}
|
|
8692
|
+
<h4 class="doorstop-section-title">Child links</h4>
|
|
8693
|
+
${renderLinksIn(host, item, result.index, children)}
|
|
8694
|
+
<h4 class="doorstop-section-title">References</h4>
|
|
8695
|
+
${renderReferences(host, item, result.index)}
|
|
8696
|
+
${Object.entries(item.attributes).length === 0 ? A : b2`
|
|
8697
|
+
<h4 class="doorstop-section-title">Extended attributes</h4>
|
|
8698
|
+
<dl class="doorstop-attributes">
|
|
8699
|
+
${Object.entries(item.attributes).map(
|
|
8700
|
+
([key, value]) => b2`<div class="doorstop-attribute"><dt>${key}</dt><dd><code>${jsonishText(value)}</code></dd></div>`
|
|
8701
|
+
)}
|
|
8702
|
+
</dl>
|
|
8703
|
+
`}
|
|
8704
|
+
<h4 class="doorstop-section-title">Findings</h4>
|
|
8705
|
+
${findings.length === 0 ? b2`<p class="doorstop-muted">No local findings for this item.</p>` : b2`<div class="doorstop-findings" aria-label="Findings for this item">
|
|
8706
|
+
${findings.map((finding) => {
|
|
8707
|
+
const kind = finding.severity === "error" ? "doorstop-error" : finding.severity === "warning" ? "doorstop-warning" : "doorstop-info";
|
|
8708
|
+
return b2`<div class=${`doorstop-finding ${kind}`}><span class="doorstop-finding-severity">${finding.severity}</span><span>${finding.message}</span></div>`;
|
|
8709
|
+
})}
|
|
8710
|
+
</div>`}
|
|
8711
|
+
</section>
|
|
8712
|
+
`;
|
|
8713
|
+
}
|
|
8714
|
+
function renderFlags(host, item) {
|
|
8715
|
+
return b2`
|
|
8716
|
+
<div class="doorstop-flags" aria-label="Item flags">
|
|
8717
|
+
<span class=${e7({ "doorstop-flag": true, "is-inactive": !item.active })}>
|
|
8718
|
+
${item.active ? "active" : "inactive"}
|
|
8719
|
+
</span>
|
|
8720
|
+
<span class="doorstop-flag">${item.normative ? "normative" : "non-normative"}</span>
|
|
8721
|
+
<span class="doorstop-flag">${item.derived ? "derived" : "non-derived"}</span>
|
|
8722
|
+
</div>
|
|
8723
|
+
`;
|
|
8724
|
+
}
|
|
8725
|
+
function renderLinksOut(host, item, index) {
|
|
8726
|
+
if (item.links.length === 0) return b2`<p class="doorstop-muted">No parent links.</p>`;
|
|
8727
|
+
return b2`
|
|
8728
|
+
<div class="doorstop-links" aria-label="Parent links">
|
|
8729
|
+
${item.links.map((link) => renderLinkOut(host, item, link, index))}
|
|
8730
|
+
</div>
|
|
8731
|
+
`;
|
|
8732
|
+
}
|
|
8733
|
+
function renderLinkOut(host, item, link, index) {
|
|
8734
|
+
const remove = b2`
|
|
8735
|
+
<span
|
|
8736
|
+
class="doorstop-link-remove"
|
|
8737
|
+
aria-hidden="true"
|
|
8738
|
+
title=${`doorstop unlink ${item.uid} ${link.uid}`}
|
|
8739
|
+
@click=${(event) => {
|
|
8740
|
+
event.stopPropagation();
|
|
8741
|
+
unlinkLink(host, item, link.uid);
|
|
8742
|
+
}}
|
|
8743
|
+
>${trashIconSvg}</span>
|
|
8744
|
+
`;
|
|
8745
|
+
const target = index.byUid.get(link.uid);
|
|
8746
|
+
if (target === void 0) {
|
|
8747
|
+
return b2`
|
|
8748
|
+
<div class="doorstop-link-row">
|
|
8749
|
+
<code>${link.uid}</code>
|
|
8750
|
+
<span class="doorstop-chip doorstop-chip-danger">unknown</span>
|
|
8751
|
+
<span class="doorstop-fingerprint">not in the index — ${link.fingerprint === null ? "no recorded fingerprint" : `recorded ${shortFingerprint(link.fingerprint)}`}</span>
|
|
8752
|
+
${remove}
|
|
8753
|
+
</div>
|
|
8754
|
+
`;
|
|
8755
|
+
}
|
|
8756
|
+
const current = computeItemStamp(target, documentConfigFor(index, target), false);
|
|
8757
|
+
const suspect = link.fingerprint !== null && link.fingerprint !== current;
|
|
8758
|
+
return b2`
|
|
8759
|
+
<button
|
|
8760
|
+
type="button"
|
|
8761
|
+
class="doorstop-link-row"
|
|
8762
|
+
data-uid=${link.uid}
|
|
8763
|
+
title=${`Open ${link.uid}`}
|
|
8764
|
+
@click=${() => {
|
|
8765
|
+
host.controller?.selectUid(link.uid);
|
|
8766
|
+
}}
|
|
8767
|
+
>
|
|
8768
|
+
<code>${link.uid}</code>
|
|
8769
|
+
<span class=${suspect ? "doorstop-chip doorstop-chip-danger" : "doorstop-chip doorstop-chip-ok"}>${suspect ? "suspect" : "ok"}</span>
|
|
8770
|
+
<span class="doorstop-fingerprint">recorded ${shortFingerprint(link.fingerprint)} · current ${shortFingerprint(current)}</span>
|
|
8771
|
+
${remove}
|
|
8772
|
+
</button>
|
|
8773
|
+
`;
|
|
8774
|
+
}
|
|
8775
|
+
function renderLinksIn(host, item, index, children) {
|
|
8776
|
+
if (children.length === 0) {
|
|
8777
|
+
return b2`<p class="doorstop-muted">No child links.</p>`;
|
|
8778
|
+
}
|
|
8779
|
+
return b2`
|
|
8780
|
+
<div class="doorstop-links" aria-label="Child links">
|
|
8781
|
+
${children.map(
|
|
8782
|
+
(child) => b2`<button type="button" class="doorstop-link-row" data-uid=${child.uid} title=${`Open ${child.uid}`} @click=${() => {
|
|
8783
|
+
host.controller?.selectUid(child.uid);
|
|
8784
|
+
}}>
|
|
8785
|
+
<code>${child.uid}</code>
|
|
8786
|
+
<span class="doorstop-muted">${child.level}</span>
|
|
8787
|
+
</button>`
|
|
8788
|
+
)}
|
|
8789
|
+
</div>
|
|
8790
|
+
`;
|
|
8791
|
+
}
|
|
8792
|
+
function renderReferences(host, item, index) {
|
|
8793
|
+
const paths = [];
|
|
8794
|
+
if (item.ref !== "") paths.push(item.ref);
|
|
8795
|
+
if (item.references !== void 0) {
|
|
8796
|
+
for (const reference of item.references) paths.push(reference.path);
|
|
8797
|
+
}
|
|
8798
|
+
if (paths.length === 0) return b2`<p class="doorstop-muted">No file references.</p>`;
|
|
8799
|
+
return b2`
|
|
8800
|
+
<div class="doorstop-links" aria-label="File references">
|
|
8801
|
+
${paths.map((path) => {
|
|
8802
|
+
const found = index.knownFilePaths.has(path);
|
|
8803
|
+
return b2`
|
|
8804
|
+
<div class="doorstop-link-row">
|
|
8805
|
+
<code>${path}</code>
|
|
8806
|
+
${found ? A : b2`<span class="doorstop-chip doorstop-chip-danger">not found</span>`}
|
|
8807
|
+
</div>
|
|
8808
|
+
`;
|
|
8809
|
+
})}
|
|
8810
|
+
</div>
|
|
8811
|
+
`;
|
|
8812
|
+
}
|
|
8813
|
+
function renderChangesSinceReview(host, item) {
|
|
8814
|
+
if (!item.stateKeys.includes("unreviewed") || item.reviewed === null) return A;
|
|
8815
|
+
if (!host.backendActive()) return A;
|
|
8816
|
+
const view = host.controller?.baselineViewFor(item);
|
|
8817
|
+
return i7(
|
|
8818
|
+
item.uid,
|
|
8819
|
+
b2`
|
|
8820
|
+
<details class="doorstop-changes" @toggle=${(event) => onChangesToggle(host, event)}>
|
|
8821
|
+
<summary class="doorstop-changes-summary">Changes since review</summary>
|
|
8822
|
+
${view === void 0 || view.state === "loading" ? b2`<p class="doorstop-muted doorstop-changes-notice">Loading baseline…</p>` : renderBaselineView(host, view)}
|
|
8823
|
+
</details>
|
|
8824
|
+
`
|
|
8825
|
+
);
|
|
8826
|
+
}
|
|
8827
|
+
function renderBaselineView(host, view) {
|
|
8828
|
+
switch (view.state) {
|
|
8829
|
+
case "loading":
|
|
8830
|
+
return b2`<p class="doorstop-muted doorstop-changes-notice">Loading baseline…</p>`;
|
|
8831
|
+
case "no-git":
|
|
8832
|
+
return b2`<p class="doorstop-muted doorstop-changes-notice">No git history — previous version unavailable</p>`;
|
|
8833
|
+
case "no-match":
|
|
8834
|
+
return b2`<p class="doorstop-muted doorstop-changes-notice">Could not locate the reviewed version (history may have been rewritten)</p>`;
|
|
8835
|
+
case "error":
|
|
8836
|
+
return b2`<p class="doorstop-muted doorstop-changes-notice">Baseline unavailable — ${view.errorMessage ?? "request failed"}</p>`;
|
|
8837
|
+
case "ready": {
|
|
8838
|
+
const diff = view.diff;
|
|
8839
|
+
if (diff === void 0) return b2`<p class="doorstop-muted doorstop-changes-notice">No changes found.</p>`;
|
|
8840
|
+
return renderBaselineDiff(host, view, diff);
|
|
8841
|
+
}
|
|
8842
|
+
}
|
|
8843
|
+
}
|
|
8844
|
+
function renderBaselineDiff(host, view, diff) {
|
|
8845
|
+
return b2`
|
|
8846
|
+
${view.source === void 0 ? A : b2`<p class="doorstop-muted doorstop-changes-source">matched via ${view.source === "review-commit" ? "review commit" : "history walk"}</p>`}
|
|
8847
|
+
${diff.text === void 0 ? b2`<p class="doorstop-muted doorstop-changes-notice">Text changed — too large to render a line diff.</p>` : b2`<div class="doorstop-diff-lines" role="list" aria-label="Text changes since review">
|
|
8848
|
+
${diff.text.map((line) => renderDiffLine(host, line))}
|
|
8849
|
+
</div>`}
|
|
8850
|
+
${diff.ref === void 0 ? A : renderFieldChange(host, "ref", diff.ref.before, diff.ref.after)}
|
|
8851
|
+
${diff.references === void 0 ? A : renderFieldChange(
|
|
8852
|
+
host,
|
|
8853
|
+
"references",
|
|
8854
|
+
referenceListText(diff.references.before),
|
|
8855
|
+
referenceListText(diff.references.after)
|
|
8856
|
+
)}
|
|
8857
|
+
${diff.linksAdded.length === 0 && diff.linksRemoved.length === 0 ? A : b2`<div class="doorstop-field-change">
|
|
8858
|
+
<span class="doorstop-field-name">links</span>
|
|
8859
|
+
${diff.linksAdded.map((uid) => b2`<code class="doorstop-field-chip doorstop-field-chip-after">+ ${uid}</code>`)}
|
|
8860
|
+
${diff.linksRemoved.map((uid) => b2`<code class="doorstop-field-chip doorstop-field-chip-before">− ${uid}</code>`)}
|
|
8861
|
+
</div>`}
|
|
8862
|
+
${diff.extended.map((change) => renderFieldChange(host, change.name, change.before, change.after))}
|
|
8863
|
+
`;
|
|
8864
|
+
}
|
|
8865
|
+
function renderDiffLine(host, line) {
|
|
8866
|
+
const mark = line.kind === "added" ? "+" : line.kind === "removed" ? "\u2212" : "";
|
|
8867
|
+
return b2`
|
|
8868
|
+
<div class=${`doorstop-diff-line is-${line.kind}`} role="listitem">
|
|
8869
|
+
<span class="doorstop-diff-mark">${mark}</span>
|
|
8870
|
+
<span class="doorstop-diff-text">${line.text === "" ? "\xA0" : line.text}</span>
|
|
8871
|
+
</div>
|
|
8872
|
+
`;
|
|
8873
|
+
}
|
|
8874
|
+
function renderFieldChange(host, label, before, after) {
|
|
8875
|
+
return b2`
|
|
8876
|
+
<div class="doorstop-field-change">
|
|
8877
|
+
<span class="doorstop-field-name">${label}</span>
|
|
8878
|
+
<code class="doorstop-field-chip doorstop-field-chip-before">${diffValueText(before)}</code>
|
|
8879
|
+
<span class="doorstop-field-arrow">→</span>
|
|
8880
|
+
<code class="doorstop-field-chip doorstop-field-chip-after">${diffValueText(after)}</code>
|
|
8881
|
+
</div>
|
|
8882
|
+
`;
|
|
8883
|
+
}
|
|
8884
|
+
function onChangesToggle(host, event) {
|
|
8885
|
+
const details = event.currentTarget;
|
|
8886
|
+
if (!(details instanceof HTMLDetailsElement) || !details.open) return;
|
|
8887
|
+
const item = host.selectedItem();
|
|
8888
|
+
if (item === void 0) return;
|
|
8889
|
+
void host.controller?.requestBaseline(item);
|
|
8890
|
+
}
|
|
8891
|
+
function unlinkLink(host, item, target) {
|
|
8892
|
+
const controller = host.controller;
|
|
8893
|
+
if (controller === void 0 || controller.runInProgress !== void 0) return;
|
|
8894
|
+
host.runDoorstop(
|
|
8895
|
+
"unlink",
|
|
8896
|
+
`Doorstop: unlink ${item.uid}`,
|
|
8897
|
+
{ op: "unlink", uid: item.uid, target },
|
|
8898
|
+
`doorstop unlink ${item.uid} ${target}`,
|
|
8899
|
+
false
|
|
8900
|
+
);
|
|
8901
|
+
}
|
|
8902
|
+
|
|
8903
|
+
// src/browser/sections/item-list.ts
|
|
8904
|
+
var DEFAULT_STATE_KEY = "normative";
|
|
8905
|
+
function renderListFilters(host, result) {
|
|
8906
|
+
return b2`
|
|
8907
|
+
<section class="doorstop-list-filters">
|
|
8908
|
+
<div class="doorstop-docs" role="list" aria-label="Doorstop documents">
|
|
8909
|
+
${renderDocumentChip(host, void 0, result)}
|
|
8910
|
+
${result.index.documents.map((document2) => renderDocumentChip(host, document2, result))}
|
|
8911
|
+
</div>
|
|
8912
|
+
<select class="doorstop-state-filter" aria-label="Filter by state" @change=${(event) => onStateFilterChange(host, event)}>
|
|
8913
|
+
<option value="" .selected=${host.stateFilter === void 0}>All states</option>
|
|
8914
|
+
${Object.entries(STATE_CHIP_LABELS).map(
|
|
8915
|
+
([key, label]) => b2`<option value=${key} .selected=${host.stateFilter === key}>${label}</option>`
|
|
8916
|
+
)}
|
|
8917
|
+
</select>
|
|
8918
|
+
<input
|
|
8919
|
+
class="doorstop-search"
|
|
8920
|
+
type="search"
|
|
8921
|
+
aria-label="Search items"
|
|
8922
|
+
placeholder="Search UID or text"
|
|
8923
|
+
.value=${host.search}
|
|
8924
|
+
@input=${(event) => onSearchInput(host, event)}
|
|
8925
|
+
/>
|
|
8926
|
+
</section>
|
|
8927
|
+
`;
|
|
8928
|
+
}
|
|
8929
|
+
function renderDocumentChip(host, document2, result) {
|
|
8930
|
+
const prefix = document2?.prefix ?? ALL_DOCUMENTS_LABEL;
|
|
8931
|
+
const count = document2 === void 0 ? result.index.items.length : result.index.items.filter((item) => item.documentPrefix === document2.prefix).length;
|
|
8932
|
+
const dots = document2 === void 0 ? [] : documentStateDots(document2, result.index.items);
|
|
8933
|
+
const selected = document2 === void 0 ? host.selectedDocumentPrefix === void 0 || host.selectedDocumentPrefix === "" : host.selectedDocumentPrefix === document2.prefix;
|
|
8934
|
+
return b2`
|
|
8935
|
+
<button
|
|
8936
|
+
type="button"
|
|
8937
|
+
role="listitem"
|
|
8938
|
+
class=${e7({ "doorstop-doc-chip": true, "is-selected": selected })}
|
|
8939
|
+
data-prefix=${document2?.prefix ?? ""}
|
|
8940
|
+
@click=${() => {
|
|
8941
|
+
host.controller?.selectDocument(document2?.prefix ?? "");
|
|
8942
|
+
}}
|
|
8943
|
+
>
|
|
8944
|
+
${document2?.parentPrefix === void 0 ? A : b2`<span class="doorstop-doc-arrow">← ${document2.parentPrefix}</span>`}
|
|
8945
|
+
<span class="doorstop-doc-prefix">${prefix}</span>
|
|
8946
|
+
<span class="doorstop-doc-count">${String(count)}</span>
|
|
8947
|
+
${dots.map((dot) => b2`<span class=${`doorstop-dot doorstop-dot-${dot}`} title=${dotTitle(dot)}></span>`)}
|
|
8948
|
+
</button>
|
|
8949
|
+
`;
|
|
8950
|
+
}
|
|
8951
|
+
function renderViewer(host) {
|
|
8952
|
+
const result = host.result;
|
|
8953
|
+
if (result === void 0) {
|
|
8954
|
+
return b2`<p class="doorstop-muted doorstop-standalone">${host.loading ? "Loading workspace\u2026" : "Run Refresh to scan for Doorstop documents."}</p>`;
|
|
8955
|
+
}
|
|
8956
|
+
if (host.view === "findings") {
|
|
8957
|
+
return renderFindingsView(host, result);
|
|
8958
|
+
}
|
|
8959
|
+
return b2`
|
|
8960
|
+
${renderListFilters(host, result)}
|
|
8961
|
+
${result.index.documents.length === 0 ? b2`
|
|
8962
|
+
${renderDiagnostics(host, result)}
|
|
8963
|
+
<section class="doorstop-empty">
|
|
8964
|
+
<p>${EMPTY_WORKSPACE_MESSAGE}</p>
|
|
8965
|
+
${host.context === void 0 ? A : b2`
|
|
8966
|
+
<button
|
|
8967
|
+
type="button"
|
|
8968
|
+
class="doorstop-setup"
|
|
8969
|
+
@click=${() => {
|
|
8970
|
+
host.insertPrompt(setupProjectPrompt());
|
|
8971
|
+
}}
|
|
8972
|
+
>Ask agent to set up the project</button>
|
|
8973
|
+
`}
|
|
8974
|
+
</section>
|
|
8975
|
+
` : b2`
|
|
8976
|
+
<section class="doorstop-split">
|
|
8977
|
+
<section class="doorstop-list">
|
|
8978
|
+
${renderDiagnostics(host, result)}
|
|
8979
|
+
${renderItemList(host, result)}
|
|
8980
|
+
</section>
|
|
8981
|
+
<section class="doorstop-detail-pane">${renderDetail(host, result)}</section>
|
|
8982
|
+
</section>
|
|
8983
|
+
`}
|
|
8984
|
+
`;
|
|
8985
|
+
}
|
|
8986
|
+
function renderItemList(host, result) {
|
|
8987
|
+
if (result.index.items.length === 0) {
|
|
8988
|
+
return b2`<p class="doorstop-muted doorstop-standalone">No Doorstop items found (item files may be binary or truncated — see the diagnostics above).</p>`;
|
|
8989
|
+
}
|
|
8990
|
+
const items = filteredItems(result.index, host.selectedDocumentPrefix, host.stateFilter, host.search);
|
|
8991
|
+
if (items.length === 0) {
|
|
8992
|
+
return b2`<p class="doorstop-muted doorstop-standalone">No items match the current document, state, or search filters.</p>`;
|
|
8993
|
+
}
|
|
8994
|
+
const gitFiles = host.readyGitFiles();
|
|
8995
|
+
const attributeKeys = doorstopShowAdditionalAttribute(result);
|
|
8996
|
+
return b2`
|
|
8997
|
+
<div
|
|
8998
|
+
class=${e7({ "doorstop-items": true, "has-item-attrs": attributeKeys.length > 0 })}
|
|
8999
|
+
role="list"
|
|
9000
|
+
aria-label="Doorstop items"
|
|
9001
|
+
>
|
|
9002
|
+
${c5(items, (item) => item.uid, (item) => renderItemRow(host, item, gitFiles, attributeKeys))}
|
|
9003
|
+
</div>
|
|
9004
|
+
`;
|
|
9005
|
+
}
|
|
9006
|
+
function renderItemRow(host, item, gitFiles, attributeKeys) {
|
|
9007
|
+
const selected = host.selectedUid === item.uid;
|
|
9008
|
+
const gitState = itemGitState(gitFiles?.get(item.path));
|
|
9009
|
+
const shownAttributes = shownAttributeRows(item, attributeKeys);
|
|
9010
|
+
const textBody = attributeKeys.includes("text") ? itemTextRow(item) : void 0;
|
|
9011
|
+
const stageable = itemStageable(gitFiles?.get(item.path));
|
|
9012
|
+
const unstageable = itemUnstageable(gitFiles?.get(item.path));
|
|
9013
|
+
return b2`
|
|
9014
|
+
<button
|
|
9015
|
+
type="button"
|
|
9016
|
+
role="listitem"
|
|
9017
|
+
class=${e7({ "doorstop-item-row": true, "is-selected": selected })}
|
|
9018
|
+
data-uid=${item.uid}
|
|
9019
|
+
@click=${() => {
|
|
9020
|
+
host.controller?.selectUid(item.uid);
|
|
9021
|
+
}}
|
|
9022
|
+
>
|
|
9023
|
+
<span class="doorstop-item-level">${item.level}</span>
|
|
9024
|
+
<code class="doorstop-item-uid">${item.uid}</code>
|
|
9025
|
+
<span class="doorstop-item-summary">${itemExcerpt(item)}</span>
|
|
9026
|
+
${shownAttributes.length === 0 ? A : b2`<span class="doorstop-item-attrs">${shownAttributes.map((row) => `${row.key}: ${row.text}`).join(" \xB7 ")}</span>`}
|
|
9027
|
+
<span class="doorstop-item-chips">
|
|
9028
|
+
${item.stateKeys.filter((key) => key !== DEFAULT_STATE_KEY).map((key) => renderStateChip(key))}
|
|
9029
|
+
${gitState === "clean" ? A : b2`<span class=${`doorstop-chip doorstop-chip-${gitChipKind(gitState)}`}>${GIT_CHIP_LABELS[gitState]}</span>`}
|
|
9030
|
+
${unstageable && host.runInProgress === void 0 ? b2`<span
|
|
9031
|
+
class="doorstop-item-add"
|
|
9032
|
+
aria-hidden="true"
|
|
9033
|
+
title=${`git reset ${item.path}`}
|
|
9034
|
+
@click=${(event) => {
|
|
9035
|
+
event.stopPropagation();
|
|
9036
|
+
host.unstageItem(item);
|
|
9037
|
+
}}
|
|
9038
|
+
>${gitUnstageIconSvg}</span>` : stageable && host.runInProgress === void 0 ? b2`<span
|
|
9039
|
+
class="doorstop-item-add"
|
|
9040
|
+
aria-hidden="true"
|
|
9041
|
+
title=${`git add ${item.path}`}
|
|
9042
|
+
@click=${(event) => {
|
|
9043
|
+
event.stopPropagation();
|
|
9044
|
+
host.stageItem(item);
|
|
9045
|
+
}}
|
|
9046
|
+
>${gitStageIconSvg}</span>` : A}
|
|
9047
|
+
${host.context === void 0 ? A : b2`<span
|
|
9048
|
+
class="doorstop-item-insert"
|
|
9049
|
+
aria-hidden="true"
|
|
9050
|
+
title=${`Insert ${item.uid} into the prompt`}
|
|
9051
|
+
@click=${(event) => {
|
|
9052
|
+
event.stopPropagation();
|
|
9053
|
+
host.insertPrompt(item.uid);
|
|
9054
|
+
}}
|
|
9055
|
+
>${insertPromptIconSvg}</span>`}
|
|
9056
|
+
</span>
|
|
9057
|
+
${textBody === void 0 ? A : b2`<span class="doorstop-item-text">${textBody}</span>`}
|
|
9058
|
+
</button>
|
|
9059
|
+
`;
|
|
9060
|
+
}
|
|
9061
|
+
function renderStateChip(key) {
|
|
9062
|
+
const kind = stateChipKind(key);
|
|
9063
|
+
return b2`<span class=${`doorstop-chip doorstop-chip-${kind}`}>${STATE_CHIP_LABELS[key]}</span>`;
|
|
9064
|
+
}
|
|
9065
|
+
function renderDiagnostics(host, result) {
|
|
9066
|
+
if (result.index.diagnostics.length === 0) return A;
|
|
9067
|
+
return b2`
|
|
9068
|
+
<section class="doorstop-diagnostics" aria-label="Workspace diagnostics">
|
|
9069
|
+
${result.index.diagnostics.map((diagnostic) => {
|
|
9070
|
+
const kind = diagnostic.severity === "error" ? "doorstop-error" : "doorstop-warning";
|
|
9071
|
+
return b2`
|
|
9072
|
+
<div class=${`doorstop-diagnostic ${kind}`}>
|
|
9073
|
+
<span class="doorstop-severity">${diagnostic.severity}</span>
|
|
9074
|
+
<span class="doorstop-diagnostic-copy">
|
|
9075
|
+
${diagnostic.path === void 0 ? A : b2`<code>${diagnostic.path}</code>`}
|
|
9076
|
+
${diagnostic.message}
|
|
9077
|
+
</span>
|
|
9078
|
+
</div>
|
|
9079
|
+
`;
|
|
9080
|
+
})}
|
|
9081
|
+
</section>
|
|
9082
|
+
`;
|
|
9083
|
+
}
|
|
9084
|
+
function renderFindingsView(host, result) {
|
|
9085
|
+
const rows = findingsViewRows(result.index);
|
|
9086
|
+
const counts = findingsViewCounts(rows);
|
|
9087
|
+
return b2`
|
|
9088
|
+
<section class="doorstop-findings-view" aria-label="Doorstop findings">
|
|
9089
|
+
<header class="doorstop-findings-head">
|
|
9090
|
+
<span class="doorstop-findings-counts" role="status" aria-label="Finding counts">${findingsCountText(counts)}</span>
|
|
9091
|
+
<span class="doorstop-findings-note doorstop-muted">${FINDINGS_PLUGIN_LOCAL_NOTE}</span>
|
|
9092
|
+
</header>
|
|
9093
|
+
${rows.length === 0 ? b2`
|
|
9094
|
+
<section class="doorstop-empty">
|
|
9095
|
+
<p>${FINDINGS_EMPTY_MESSAGE}</p>
|
|
9096
|
+
<p class="doorstop-muted">${FINDINGS_EMPTY_HINT}</p>
|
|
9097
|
+
</section>
|
|
9098
|
+
` : b2`<div class="doorstop-findings-list" role="list" aria-label="Validation findings">
|
|
9099
|
+
${rows.map((row) => renderFindingRow(host, row, result.index))}
|
|
9100
|
+
</div>`}
|
|
9101
|
+
</section>
|
|
9102
|
+
`;
|
|
9103
|
+
}
|
|
9104
|
+
function renderFindingRow(host, row, index) {
|
|
9105
|
+
const kind = row.severity === "error" ? "doorstop-error" : row.severity === "warning" ? "doorstop-warning" : "doorstop-info";
|
|
9106
|
+
const uid = row.uid;
|
|
9107
|
+
const navigable = uid !== void 0 && index.byUid.has(uid);
|
|
9108
|
+
return b2`
|
|
9109
|
+
<div class=${`doorstop-finding-row ${kind}`} role="listitem">
|
|
9110
|
+
<span class="doorstop-severity">${row.severity}</span>
|
|
9111
|
+
${uid === void 0 ? A : navigable ? b2`<button type="button" class="doorstop-finding-uid" data-uid=${uid} title=${`Show ${uid} in the item list`} @click=${() => {
|
|
9112
|
+
selectFindingTarget(host, uid);
|
|
9113
|
+
}}><code>${uid}</code></button>` : b2`<code class="doorstop-finding-uid">${uid}</code>`}
|
|
9114
|
+
${row.path === void 0 ? A : b2`<code class="doorstop-finding-path">${row.path}</code>`}
|
|
9115
|
+
<span class="doorstop-finding-message">${row.message}</span>
|
|
9116
|
+
</div>
|
|
9117
|
+
`;
|
|
9118
|
+
}
|
|
9119
|
+
function selectFindingTarget(host, uid) {
|
|
9120
|
+
const controller = host.controller;
|
|
9121
|
+
if (controller === void 0) return;
|
|
9122
|
+
controller.selectDocument("");
|
|
9123
|
+
controller.setStateFilter(void 0);
|
|
9124
|
+
controller.setSearch("");
|
|
9125
|
+
controller.selectUid(uid);
|
|
9126
|
+
host.view = "items";
|
|
9127
|
+
}
|
|
9128
|
+
function onStateFilterChange(host, event) {
|
|
9129
|
+
const value = event.target.value;
|
|
9130
|
+
host.controller?.setStateFilter(value === "" ? void 0 : value);
|
|
9131
|
+
}
|
|
9132
|
+
function onSearchInput(host, event) {
|
|
9133
|
+
host.controller?.setSearch(event.target.value);
|
|
9134
|
+
}
|
|
9135
|
+
|
|
9136
|
+
// src/browser/sections/item-action-palette.ts
|
|
9137
|
+
function renderItemActionPalette(host) {
|
|
9138
|
+
if (host.view !== "items") return A;
|
|
9139
|
+
const result = host.result;
|
|
9140
|
+
if (result === void 0 || result.index.documents.length === 0) return A;
|
|
9141
|
+
const item = host.selectedItem();
|
|
9142
|
+
if (item === void 0) {
|
|
9143
|
+
return b2`
|
|
9144
|
+
<section class="doorstop-action-palette" aria-label="Item actions">
|
|
9145
|
+
<span class="doorstop-muted doorstop-palette-placeholder">Select an item…</span>
|
|
9146
|
+
</section>
|
|
9147
|
+
`;
|
|
9148
|
+
}
|
|
9149
|
+
const suspects = suspectParentItems(item, result.index);
|
|
9150
|
+
return b2`
|
|
9151
|
+
<section class="doorstop-action-palette" aria-label="Item actions">
|
|
9152
|
+
${renderActionRow(host, item, result.index, suspects)}
|
|
9153
|
+
</section>
|
|
9154
|
+
`;
|
|
9155
|
+
}
|
|
9156
|
+
function renderActionRow(host, item, index, suspects) {
|
|
9157
|
+
const reviewed = item.stateKeys.includes("reviewed");
|
|
9158
|
+
const suspectUids = suspects.length > 0 ? suspects.map((parent) => parent.uid) : [];
|
|
9159
|
+
const gitStatus = host.readyGitStatus();
|
|
9160
|
+
const gitFile = gitStatus === void 0 ? void 0 : gitStatusFileFor(gitStatus, item.path);
|
|
9161
|
+
const stageable = itemStageable(gitFile);
|
|
9162
|
+
const unstageable = itemUnstageable(gitFile);
|
|
9163
|
+
return b2`
|
|
9164
|
+
<button
|
|
9165
|
+
type="button"
|
|
9166
|
+
class="doorstop-review"
|
|
9167
|
+
?disabled=${reviewed || host.runInProgress !== void 0}
|
|
9168
|
+
title=${reviewed ? `${item.uid} is already reviewed against its current fingerprint` : `Mark ${item.uid} reviewed`}
|
|
9169
|
+
@click=${() => {
|
|
9170
|
+
reviewItem(host, item);
|
|
9171
|
+
}}
|
|
9172
|
+
>Approve</button>
|
|
9173
|
+
<button
|
|
9174
|
+
type="button"
|
|
9175
|
+
class="doorstop-clear"
|
|
9176
|
+
?disabled=${suspects.length === 0 || host.runInProgress !== void 0}
|
|
9177
|
+
title=${suspects.length === 0 ? `No suspect links to clear` : `Re-record the parent fingerprints of ${item.uid}`}
|
|
9178
|
+
@click=${() => {
|
|
9179
|
+
clearSuspects(host, item, suspectUids);
|
|
9180
|
+
}}
|
|
9181
|
+
>Clear suspect links</button>
|
|
9182
|
+
${gitStatus === void 0 ? A : b2`<button
|
|
9183
|
+
type="button"
|
|
9184
|
+
class="doorstop-item-stage"
|
|
9185
|
+
?disabled=${host.runInProgress !== void 0 || !stageable}
|
|
9186
|
+
title=${stageable ? `Stage ${item.path}` : `${item.uid} has no unstaged changes \u2014 commit next`}
|
|
9187
|
+
@click=${() => {
|
|
9188
|
+
host.stageItem(item);
|
|
9189
|
+
}}
|
|
9190
|
+
>${gitStageIconSvg}Stage</button>
|
|
9191
|
+
<button
|
|
9192
|
+
type="button"
|
|
9193
|
+
class="doorstop-item-unstage"
|
|
9194
|
+
?disabled=${host.runInProgress !== void 0 || !unstageable}
|
|
9195
|
+
title=${unstageable ? `Unstage ${item.path} (git reset)` : `${item.uid} has nothing staged`}
|
|
9196
|
+
@click=${() => {
|
|
9197
|
+
host.unstageItem(item);
|
|
9198
|
+
}}
|
|
9199
|
+
>${gitUnstageIconSvg}Unstage</button>`}
|
|
9200
|
+
<div class="doorstop-op">
|
|
9201
|
+
<input
|
|
9202
|
+
type="text"
|
|
9203
|
+
class="doorstop-target-input"
|
|
9204
|
+
data-op="link"
|
|
9205
|
+
placeholder="parent UID"
|
|
9206
|
+
${n5(host.linkInputRef)}
|
|
9207
|
+
@keydown=${(event) => onTargetKeydown(host, event)}
|
|
9208
|
+
/>
|
|
9209
|
+
<button type="button" class="doorstop-link" title=${`doorstop link ${item.uid} <target>`} ?disabled=${host.runInProgress !== void 0} @click=${() => {
|
|
9210
|
+
runTargetOp(host, "link", host.linkInputRef, item);
|
|
9211
|
+
}}>Link</button>
|
|
9212
|
+
</div>
|
|
9213
|
+
${host.targetError === void 0 ? A : b2`<span class="doorstop-op-error" role="alert">${host.targetError}</span>`}
|
|
9214
|
+
${renderAskMenu(host, item, index, suspects)}
|
|
9215
|
+
`;
|
|
9216
|
+
}
|
|
9217
|
+
function renderAskMenu(host, item, index, suspects) {
|
|
9218
|
+
const children = index.childrenByUid.get(item.uid) ?? [];
|
|
9219
|
+
const childPrefix = firstChildDocumentPrefix(index, item);
|
|
9220
|
+
return b2`
|
|
9221
|
+
<div class="doorstop-menu">
|
|
9222
|
+
<button
|
|
9223
|
+
type="button"
|
|
9224
|
+
class="doorstop-menu-toggle"
|
|
9225
|
+
aria-expanded=${host.askMenuOpen ? "true" : "false"}
|
|
9226
|
+
@click=${() => onAskMenuToggle(host)}
|
|
9227
|
+
>Ask agent</button>
|
|
9228
|
+
${host.askMenuOpen ? b2`
|
|
9229
|
+
<div class="doorstop-menu-items" role="menu" aria-label="Ask the agent about ${item.uid}">
|
|
9230
|
+
<button type="button" role="menuitem" class="doorstop-menu-item doorstop-explain" @click=${() => {
|
|
9231
|
+
host.insertPrompt(explainItemPrompt(item));
|
|
9232
|
+
}}>Explain</button>
|
|
9233
|
+
<button
|
|
9234
|
+
type="button"
|
|
9235
|
+
role="menuitem"
|
|
9236
|
+
class="doorstop-menu-item doorstop-fix-suspects"
|
|
9237
|
+
?disabled=${suspects.length === 0}
|
|
9238
|
+
title=${suspects.length === 0 ? "No suspect links \u2014 nothing to fix" : `Fix the suspect links of ${item.uid}`}
|
|
9239
|
+
@click=${() => {
|
|
9240
|
+
host.insertPrompt(fixSuspectLinksPrompt(item, suspects));
|
|
9241
|
+
}}
|
|
9242
|
+
>Fix suspect links</button>
|
|
9243
|
+
<button
|
|
9244
|
+
type="button"
|
|
9245
|
+
role="menuitem"
|
|
9246
|
+
class="doorstop-menu-item doorstop-draft-child"
|
|
9247
|
+
?disabled=${childPrefix === void 0}
|
|
9248
|
+
title=${childPrefix === void 0 ? "No child document to add to" : `Draft a child item in the ${childPrefix} document`}
|
|
9249
|
+
@click=${() => {
|
|
9250
|
+
if (childPrefix !== void 0) host.insertPrompt(draftChildRequirementPrompt(item, childPrefix));
|
|
9251
|
+
}}
|
|
9252
|
+
>Draft child requirement</button>
|
|
9253
|
+
<button type="button" role="menuitem" class="doorstop-menu-item doorstop-review-readiness" @click=${() => {
|
|
9254
|
+
host.insertPrompt(reviewReadinessPrompt(item, children));
|
|
9255
|
+
}}>Review readiness</button>
|
|
9256
|
+
<button
|
|
9257
|
+
type="button"
|
|
9258
|
+
role="menuitem"
|
|
9259
|
+
class="doorstop-menu-item doorstop-start-review-session"
|
|
9260
|
+
title=${`Start an independent review session for ${item.uid} (inserts /opendoor-review)`}
|
|
9261
|
+
@click=${() => {
|
|
9262
|
+
insertReviewCommand(host, item);
|
|
9263
|
+
}}
|
|
9264
|
+
>Start review session</button>
|
|
9265
|
+
</div>
|
|
9266
|
+
` : A}
|
|
9267
|
+
</div>
|
|
9268
|
+
`;
|
|
9269
|
+
}
|
|
9270
|
+
function insertReviewCommand(host, item) {
|
|
9271
|
+
if (host.context === void 0) return;
|
|
9272
|
+
host.insertPrompt(composeReviewCommand(item));
|
|
9273
|
+
host.controller?.commitAskReview(item.uid);
|
|
9274
|
+
}
|
|
9275
|
+
function reviewItem(host, item) {
|
|
9276
|
+
const commit = doorstopCommitAfterReview(host.result);
|
|
9277
|
+
host.runDoorstop(
|
|
9278
|
+
"review",
|
|
9279
|
+
`Doorstop: review ${item.uid}`,
|
|
9280
|
+
commit ? { op: "review", uid: item.uid, commit: true } : { op: "review", uid: item.uid },
|
|
9281
|
+
`doorstop review ${item.uid}`,
|
|
9282
|
+
false
|
|
9283
|
+
);
|
|
9284
|
+
}
|
|
9285
|
+
function clearSuspects(host, item, suspectUids) {
|
|
9286
|
+
if (suspectUids.length === 0) return;
|
|
9287
|
+
host.runDoorstop(
|
|
9288
|
+
"clear",
|
|
9289
|
+
"Doorstop: clear suspect links",
|
|
9290
|
+
{ op: "clear", uid: item.uid, parents: suspectUids },
|
|
9291
|
+
`doorstop clear ${item.uid} ${suspectUids.join(" ")}`,
|
|
9292
|
+
false
|
|
9293
|
+
);
|
|
9294
|
+
}
|
|
9295
|
+
function runTargetOp(host, op, inputRef, item) {
|
|
9296
|
+
const input = inputRef.value;
|
|
9297
|
+
const target = input?.value.trim() ?? "";
|
|
9298
|
+
if (target === "") {
|
|
9299
|
+
host.targetError = `Enter a link target UID (e.g. ${item.documentPrefix}0001)`;
|
|
9300
|
+
input?.focus();
|
|
9301
|
+
return;
|
|
9302
|
+
}
|
|
9303
|
+
if (!isValidTargetUid(target)) {
|
|
9304
|
+
host.targetError = `Invalid link target "${target}" \u2014 expected a Doorstop UID like ${item.documentPrefix}0001 (no spaces, quotes, or special characters)`;
|
|
9305
|
+
input?.focus();
|
|
9306
|
+
return;
|
|
9307
|
+
}
|
|
9308
|
+
host.targetError = void 0;
|
|
9309
|
+
if (input !== void 0) input.value = "";
|
|
9310
|
+
host.runDoorstop(
|
|
9311
|
+
op,
|
|
9312
|
+
`Doorstop: ${op} ${item.uid}`,
|
|
9313
|
+
{ op, uid: item.uid, target },
|
|
9314
|
+
`doorstop ${op} ${item.uid} ${target}`,
|
|
9315
|
+
false
|
|
9316
|
+
);
|
|
9317
|
+
}
|
|
9318
|
+
function onTargetKeydown(host, event) {
|
|
9319
|
+
const input = event.target;
|
|
9320
|
+
if (input.getAttribute("data-op") !== "link") return;
|
|
9321
|
+
if (event.key === "Enter") {
|
|
9322
|
+
event.preventDefault();
|
|
9323
|
+
const item = host.selectedItem();
|
|
9324
|
+
if (item !== void 0) runTargetOp(host, "link", host.linkInputRef, item);
|
|
9325
|
+
} else if (event.key === "Escape") {
|
|
9326
|
+
input.value = "";
|
|
8007
9327
|
}
|
|
8008
|
-
return `${item.uid} has suspect links to ${andJoin(parents.map((parent) => parent.uid))}: the recorded fingerprints no longer match because those parents changed after ${item.uid} was last reviewed. Read \`${item.path}\` and the changed parents (${parentItems}), compare the texts, and propose updated text for ${item.uid} that still traces correctly to every changed parent. Then run \`doorstop clear ${item.uid}\` in the workspace root to re-record the parents' current fingerprints, and run \`doorstop\` to confirm no suspect link remains.`;
|
|
8009
9328
|
}
|
|
8010
|
-
function
|
|
8011
|
-
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
|
|
9329
|
+
function onAskMenuToggle(host) {
|
|
9330
|
+
host.askMenuOpen = !host.askMenuOpen;
|
|
9331
|
+
}
|
|
9332
|
+
|
|
9333
|
+
// src/browser/sections/status-bar.ts
|
|
9334
|
+
function renderStatusBar(host) {
|
|
9335
|
+
const lastRun = host.lastRun;
|
|
9336
|
+
if (lastRun === void 0) return A;
|
|
9337
|
+
const statusLabel = lastRun.status === "killed" ? "killed (timeout)" : lastRun.status;
|
|
9338
|
+
const detailParts = [lastRun.title, `${String(lastRun.durationMs)} ms`];
|
|
9339
|
+
if (lastRun.exitCode !== null && (lastRun.status === "failed" || lastRun.status === "killed")) {
|
|
9340
|
+
detailParts.push(`exit ${String(lastRun.exitCode)}`);
|
|
8015
9341
|
}
|
|
8016
|
-
|
|
9342
|
+
if (lastRun.signal !== null) detailParts.push(lastRun.signal);
|
|
9343
|
+
return b2`
|
|
9344
|
+
<section class="doorstop-status-bar" aria-label="Last run">
|
|
9345
|
+
<div class="doorstop-status-bar-row">
|
|
9346
|
+
<span class=${`doorstop-last-run-status is-${lastRun.status}`}>${statusLabel}</span>
|
|
9347
|
+
<span class="doorstop-last-run-meta">${detailParts.join(" \xB7 ")}</span>
|
|
9348
|
+
<button
|
|
9349
|
+
type="button"
|
|
9350
|
+
class="doorstop-last-run-dismiss"
|
|
9351
|
+
title="Dismiss the last run output"
|
|
9352
|
+
@click=${() => onDismissRun(host)}
|
|
9353
|
+
>Dismiss</button>
|
|
9354
|
+
</div>
|
|
9355
|
+
${host.statusExpanded ? renderStatusBarBody(host, lastRun) : A}
|
|
9356
|
+
</section>
|
|
9357
|
+
`;
|
|
8017
9358
|
}
|
|
8018
|
-
function
|
|
8019
|
-
|
|
8020
|
-
|
|
9359
|
+
function renderStatusBarBody(host, lastRun) {
|
|
9360
|
+
return b2`
|
|
9361
|
+
<div class="doorstop-last-run">
|
|
9362
|
+
${lastRun.commit === void 0 ? A : b2`<p class="doorstop-last-run-commit doorstop-muted">${commitOutcomeText(lastRun.op, lastRun.commit)}</p>`}
|
|
9363
|
+
${lastRun.status === "error" ? b2`<pre class="doorstop-last-run-pre">${lastRun.errorMessage ?? ""}</pre>` : A}
|
|
9364
|
+
${lastRun.stdout === "" ? A : b2`
|
|
9365
|
+
<pre class="doorstop-last-run-pre">${lastRun.stdout}</pre>
|
|
9366
|
+
${lastRun.stdoutTruncated ? b2`<p class="doorstop-last-run-notice">stdout truncated by the host stream limit (2 MiB) — output not fully captured</p>` : A}
|
|
9367
|
+
`}
|
|
9368
|
+
${lastRun.stderr === "" ? A : b2`
|
|
9369
|
+
<pre class="doorstop-last-run-pre">${lastRun.stderr}</pre>
|
|
9370
|
+
${lastRun.stderrTruncated ? b2`<p class="doorstop-last-run-notice">stderr truncated by the host stream limit (2 MiB) — output not fully captured</p>` : A}
|
|
9371
|
+
`}
|
|
9372
|
+
${lastRun.stdout === "" && lastRun.stderr === "" && lastRun.status !== "error" ? b2`<p class="doorstop-last-run-notice">No output captured.</p>` : A}
|
|
9373
|
+
</div>
|
|
9374
|
+
`;
|
|
8021
9375
|
}
|
|
8022
|
-
function
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
const childLinks = andJoin(
|
|
8026
|
-
children.map((child) => `${child.uid} (\`${child.path}\`)`)
|
|
8027
|
-
);
|
|
8028
|
-
const facts = [
|
|
8029
|
-
item.text.trim() === "" ? "its text is empty (that alone blocks review)" : "its text is filled in",
|
|
8030
|
-
item.links.length === 0 ? "it declares no parent links" : `its recorded parent links (${parents}) are not suspect or missing a fingerprint`,
|
|
8031
|
-
children.length === 0 ? "its child links are left to the `doorstop` run below" : `its child links are declared (${childLinks})`,
|
|
8032
|
-
references.length === 0 ? "it has no file references to verify" : `its file references exist (${references.map((path) => `\`${path}\``).join(", ")})`
|
|
8033
|
-
];
|
|
8034
|
-
return `Check whether ${item.uid} is ready for \`doorstop review ${item.uid}\`. Read \`${item.path}\` and verify that ${facts.join("; ")}. Then run \`doorstop\` in the workspace root and weigh any WARNING or ERROR it reports about ${item.uid} (for example a requirement that should link to ${item.uid} but does not). Report a verdict, and if ${item.uid} is not ready, the exact fixes to make first.`;
|
|
9376
|
+
function onDismissRun(host) {
|
|
9377
|
+
host.statusExpanded = false;
|
|
9378
|
+
host.controller?.dismissRun();
|
|
8035
9379
|
}
|
|
8036
9380
|
|
|
8037
|
-
// src/doorstop-panel-element.ts
|
|
9381
|
+
// src/browser/doorstop-panel-element.ts
|
|
8038
9382
|
var bodyElementTag = "pi-web-opendoor-panel-body";
|
|
8039
9383
|
function defineDoorstopPanelElements() {
|
|
8040
9384
|
defineDoorstopPanelBodyElement();
|
|
@@ -8054,90 +9398,10 @@ function defineDoorstopPanelBodyElement() {
|
|
|
8054
9398
|
this.confirmSkipped = false;
|
|
8055
9399
|
this.view = "items";
|
|
8056
9400
|
this.statusExpanded = false;
|
|
8057
|
-
|
|
8058
|
-
this.linkInputRef =
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
this.onDismissRun = () => {
|
|
8062
|
-
this.statusExpanded = false;
|
|
8063
|
-
this.controller?.dismissRun();
|
|
8064
|
-
};
|
|
8065
|
-
this.onRefreshClick = () => {
|
|
8066
|
-
void this.controller?.invalidate();
|
|
8067
|
-
};
|
|
8068
|
-
this.onValidateClick = () => {
|
|
8069
|
-
this.runDoorstop("validate", "Doorstop: validate", { op: "validate" }, "doorstop", true);
|
|
8070
|
-
};
|
|
8071
|
-
this.onPublishClick = () => {
|
|
8072
|
-
const target = doorstopPublishTarget(this.result);
|
|
8073
|
-
if (typeof window.confirm === "function") {
|
|
8074
|
-
if (!window.confirm(`Publish the Doorstop tree as HTML to ${target} in the workspace terminal?`)) return;
|
|
8075
|
-
this.confirmSkipped = false;
|
|
8076
|
-
} else {
|
|
8077
|
-
this.confirmSkipped = true;
|
|
8078
|
-
}
|
|
8079
|
-
this.runDoorstop(
|
|
8080
|
-
"publish",
|
|
8081
|
-
"Doorstop: publish",
|
|
8082
|
-
{ op: "publish", target },
|
|
8083
|
-
doorstopPublishCommand(this.result, target),
|
|
8084
|
-
false
|
|
8085
|
-
);
|
|
8086
|
-
};
|
|
8087
|
-
this.onGitCommitInput = (event) => {
|
|
8088
|
-
this.gitCommitMessage = event.target.value;
|
|
8089
|
-
};
|
|
8090
|
-
this.onGitStageClick = () => {
|
|
8091
|
-
const controller = this.controller;
|
|
8092
|
-
if (controller === void 0) return;
|
|
8093
|
-
if (controller.runInProgress !== void 0) return;
|
|
8094
|
-
const result = this.result;
|
|
8095
|
-
if (result === void 0) {
|
|
8096
|
-
this.gitActionError = "The workspace is not loaded \u2014 refresh first";
|
|
8097
|
-
return;
|
|
8098
|
-
}
|
|
8099
|
-
const paths = doorstopPaths(result);
|
|
8100
|
-
if (paths.length === 0) {
|
|
8101
|
-
this.gitActionError = "Nothing to stage \u2014 the workspace has no Doorstop-managed files";
|
|
8102
|
-
return;
|
|
8103
|
-
}
|
|
8104
|
-
this.gitActionError = void 0;
|
|
8105
|
-
void controller.runGitStage(paths);
|
|
8106
|
-
};
|
|
8107
|
-
this.onGitCommitClick = () => {
|
|
8108
|
-
this.gitCommitSubmit();
|
|
8109
|
-
};
|
|
8110
|
-
this.onGitCommitKeydown = (event) => {
|
|
8111
|
-
const input = this.gitCommitInputRef.value;
|
|
8112
|
-
if (input === void 0) return;
|
|
8113
|
-
if (event.key === "Enter") {
|
|
8114
|
-
event.preventDefault();
|
|
8115
|
-
this.gitCommitSubmit();
|
|
8116
|
-
} else if (event.key === "Escape") {
|
|
8117
|
-
input.value = "";
|
|
8118
|
-
this.gitCommitMessage = "";
|
|
8119
|
-
this.gitActionError = void 0;
|
|
8120
|
-
}
|
|
8121
|
-
};
|
|
8122
|
-
this.onStateFilterChange = (event) => {
|
|
8123
|
-
const value = event.target.value;
|
|
8124
|
-
this.controller?.setStateFilter(value === "" ? void 0 : value);
|
|
8125
|
-
};
|
|
8126
|
-
this.onSearchInput = (event) => {
|
|
8127
|
-
this.controller?.setSearch(event.target.value);
|
|
8128
|
-
};
|
|
8129
|
-
this.onTargetKeydown = (event) => {
|
|
8130
|
-
const input = event.target;
|
|
8131
|
-
const op = input.getAttribute("data-op");
|
|
8132
|
-
if (op !== "unlink" && op !== "link") return;
|
|
8133
|
-
if (event.key === "Enter") {
|
|
8134
|
-
event.preventDefault();
|
|
8135
|
-
const item = this.selectedItem();
|
|
8136
|
-
if (item !== void 0) this.runTargetOp(op, op === "unlink" ? this.unlinkInputRef : this.linkInputRef, item);
|
|
8137
|
-
} else if (event.key === "Escape") {
|
|
8138
|
-
input.value = "";
|
|
8139
|
-
}
|
|
8140
|
-
};
|
|
9401
|
+
/** section-API: the palette's free-text Link target input. */
|
|
9402
|
+
this.linkInputRef = e5();
|
|
9403
|
+
/** section-API: the toolbar's commit-message input. */
|
|
9404
|
+
this.gitCommitInputRef = e5();
|
|
8141
9405
|
this.onDocumentClick = (event) => {
|
|
8142
9406
|
if (!this.askMenuOpen) return;
|
|
8143
9407
|
const menuRoot = this.shadowRoot?.querySelector(".doorstop-menu");
|
|
@@ -8156,16 +9420,6 @@ function defineDoorstopPanelBodyElement() {
|
|
|
8156
9420
|
this.askMenuOpen = false;
|
|
8157
9421
|
}
|
|
8158
9422
|
};
|
|
8159
|
-
this.onAskMenuToggle = () => {
|
|
8160
|
-
this.askMenuOpen = !this.askMenuOpen;
|
|
8161
|
-
};
|
|
8162
|
-
this.onChangesToggle = (event) => {
|
|
8163
|
-
const details = event.currentTarget;
|
|
8164
|
-
if (!(details instanceof HTMLDetailsElement) || !details.open) return;
|
|
8165
|
-
const item = this.selectedItem();
|
|
8166
|
-
if (item === void 0) return;
|
|
8167
|
-
void this.controller?.requestBaseline(item);
|
|
8168
|
-
};
|
|
8169
9423
|
}
|
|
8170
9424
|
static {
|
|
8171
9425
|
this.styles = panelStyles;
|
|
@@ -8211,9 +9465,8 @@ function defineDoorstopPanelBodyElement() {
|
|
|
8211
9465
|
* expand handler alone would strand the section on "Loading baseline…"
|
|
8212
9466
|
* with no fetch in flight. `requestBaseline` installs its "loading"
|
|
8213
9467
|
* view synchronously, so this cannot loop. (A selection switch needs
|
|
8214
|
-
* no help here —
|
|
8215
|
-
*
|
|
8216
|
-
* `toggle`.)
|
|
9468
|
+
* no help here — the section keys the node by UID, so Lit recreates it
|
|
9469
|
+
* closed and the user's expand fires a real `toggle`.)
|
|
8217
9470
|
*/
|
|
8218
9471
|
updated() {
|
|
8219
9472
|
this.ensureGitStatus();
|
|
@@ -8241,318 +9494,28 @@ function defineDoorstopPanelBodyElement() {
|
|
|
8241
9494
|
}
|
|
8242
9495
|
render() {
|
|
8243
9496
|
return b2`
|
|
8244
|
-
${
|
|
9497
|
+
${renderProjectActions(this)}
|
|
8245
9498
|
${this.error === void 0 ? A : b2`<div class="doorstop-error" role="alert">${this.error}</div>`}
|
|
8246
|
-
<section class="doorstop-viewer">${
|
|
8247
|
-
${
|
|
8248
|
-
${
|
|
8249
|
-
`;
|
|
8250
|
-
}
|
|
8251
|
-
// --- project actions (region 1) -----------------------------------------------
|
|
8252
|
-
renderProjectActions() {
|
|
8253
|
-
const publishTarget = doorstopPublishTarget(this.result);
|
|
8254
|
-
const projectPath = this.context?.workspace.path ?? "";
|
|
8255
|
-
const projectLabel = projectPath === "" ? "Doorstop" : projectPath.split("/").filter(Boolean).pop() ?? projectPath;
|
|
8256
|
-
return b2`
|
|
8257
|
-
<section class="doorstop-project-actions">
|
|
8258
|
-
<strong class="doorstop-title" title=${projectPath === "" ? A : projectPath}>${doorstopIconSvg}${projectLabel}</strong>
|
|
8259
|
-
${this.renderViewToggle()}
|
|
8260
|
-
<div class="doorstop-toolbar-actions">
|
|
8261
|
-
${this.stale ? b2`<button type="button" class="doorstop-stale" title="Doorstop ran or files changed behind the panel — click to rescan" @click=${this.onRefreshClick}>stale — refresh</button>` : A}
|
|
8262
|
-
${this.confirmSkipped ? b2`<span class="doorstop-muted doorstop-confirm-skipped" title="No confirmation dialog is available in this environment — publishing proceeded without one">confirmation skipped — publishing</span>` : A}
|
|
8263
|
-
<button type="button" class="doorstop-refresh" title="Re-read the workspace" @click=${this.onRefreshClick}>${refreshIconSvg}Refresh</button>
|
|
8264
|
-
<button type="button" class="doorstop-validate" title="Run \`doorstop\` in the workspace (terminal when unpaired)" ?disabled=${this.runInProgress !== void 0} @click=${this.onValidateClick}>${validateIconSvg}Run validation</button>
|
|
8265
|
-
<button type="button" class="doorstop-publish" title=${`Publish the tree to ${publishTarget}`} ?disabled=${this.runInProgress !== void 0} @click=${this.onPublishClick}>${publishIconSvg}Publish HTML</button>
|
|
8266
|
-
${this.renderGitActions()}
|
|
8267
|
-
</div>
|
|
8268
|
-
</section>
|
|
8269
|
-
`;
|
|
8270
|
-
}
|
|
8271
|
-
/**
|
|
8272
|
-
* The commit INPUT is disabled while a run is in flight (like the
|
|
8273
|
-
* buttons) so a typed-but-never-submitted message cannot be lost when
|
|
8274
|
-
* the run's invalidate lands mid-typing.
|
|
8275
|
-
*/
|
|
8276
|
-
renderGitActions() {
|
|
8277
|
-
if (!this.backendActive()) return A;
|
|
8278
|
-
const hasDocuments = (this.result?.index.documents.length ?? 0) > 0;
|
|
8279
|
-
return b2`
|
|
8280
|
-
${this.renderGitStatus()}
|
|
8281
|
-
<button
|
|
8282
|
-
type="button"
|
|
8283
|
-
class="doorstop-git-stage"
|
|
8284
|
-
title="Stage all Doorstop-managed files (requirements, documents, configs)"
|
|
8285
|
-
?disabled=${this.runInProgress !== void 0 || !hasDocuments}
|
|
8286
|
-
@click=${this.onGitStageClick}
|
|
8287
|
-
>${gitStageIconSvg}Stage all</button>
|
|
8288
|
-
<div class="doorstop-git-commit">
|
|
8289
|
-
<input
|
|
8290
|
-
type="text"
|
|
8291
|
-
class="doorstop-git-commit-input"
|
|
8292
|
-
placeholder="Commit message"
|
|
8293
|
-
aria-label="Git commit message"
|
|
8294
|
-
?disabled=${this.runInProgress !== void 0}
|
|
8295
|
-
${n5(this.gitCommitInputRef)}
|
|
8296
|
-
@input=${this.onGitCommitInput}
|
|
8297
|
-
@keydown=${this.onGitCommitKeydown}
|
|
8298
|
-
/>
|
|
8299
|
-
<button
|
|
8300
|
-
type="button"
|
|
8301
|
-
class="doorstop-git-commit-button"
|
|
8302
|
-
title="Commit the staged index with this message"
|
|
8303
|
-
?disabled=${this.runInProgress !== void 0 || this.gitCommitMessage.trim() === ""}
|
|
8304
|
-
@click=${this.onGitCommitClick}
|
|
8305
|
-
>${gitCommitIconSvg}Commit</button>
|
|
8306
|
-
${this.gitActionError === void 0 ? A : b2`<span class="doorstop-op-error" role="alert">${this.gitActionError}</span>`}
|
|
8307
|
-
</div>
|
|
8308
|
-
`;
|
|
8309
|
-
}
|
|
8310
|
-
renderGitStatus() {
|
|
8311
|
-
const view = this.gitStatusView;
|
|
8312
|
-
if (view === void 0) return A;
|
|
8313
|
-
const refresh = () => {
|
|
8314
|
-
void this.controller?.requestGitStatus();
|
|
8315
|
-
};
|
|
8316
|
-
switch (view.state) {
|
|
8317
|
-
case "loading":
|
|
8318
|
-
return b2`<button
|
|
8319
|
-
type="button"
|
|
8320
|
-
class="doorstop-git-status"
|
|
8321
|
-
title="Refreshing git status…"
|
|
8322
|
-
aria-busy=${this.gitStatusInFlight}
|
|
8323
|
-
@click=${refresh}
|
|
8324
|
-
><span class="doorstop-git-status-text">⎇ …</span></button>`;
|
|
8325
|
-
case "no-git":
|
|
8326
|
-
return b2`<button type="button" class="doorstop-git-status" title="Not a git repository — click to re-check" aria-busy=${this.gitStatusInFlight} @click=${refresh}><span class="doorstop-git-status-text">no git</span></button>`;
|
|
8327
|
-
case "error":
|
|
8328
|
-
return b2`<button
|
|
8329
|
-
type="button"
|
|
8330
|
-
class="doorstop-git-status doorstop-git-status-error"
|
|
8331
|
-
title=${`Git status unavailable \u2014 ${view.errorMessage ?? "request failed"} \u2014 click to retry`}
|
|
8332
|
-
aria-busy=${this.gitStatusInFlight}
|
|
8333
|
-
@click=${refresh}
|
|
8334
|
-
><span class="doorstop-git-status-text">git status error — retry</span></button>`;
|
|
8335
|
-
case "ready":
|
|
8336
|
-
return b2`<button type="button" class="doorstop-git-status" title="Git status — click to refresh" aria-busy=${this.gitStatusInFlight} @click=${refresh}><span class="doorstop-git-status-text">${gitStatusText(view.response)}</span></button>`;
|
|
8337
|
-
}
|
|
8338
|
-
}
|
|
8339
|
-
// --- list filters (above the item list) ---------------------------------------
|
|
8340
|
-
renderListFilters(result) {
|
|
8341
|
-
return b2`
|
|
8342
|
-
<section class="doorstop-list-filters">
|
|
8343
|
-
<div class="doorstop-docs" role="list" aria-label="Doorstop documents">
|
|
8344
|
-
${this.renderDocumentChip(void 0, result)}
|
|
8345
|
-
${result.index.documents.map((document2) => this.renderDocumentChip(document2, result))}
|
|
8346
|
-
</div>
|
|
8347
|
-
<select class="doorstop-state-filter" aria-label="Filter by state" @change=${this.onStateFilterChange}>
|
|
8348
|
-
<option value="" .selected=${this.stateFilter === void 0}>All states</option>
|
|
8349
|
-
${Object.entries(STATE_CHIP_LABELS).map(
|
|
8350
|
-
([key, label]) => b2`<option value=${key} .selected=${this.stateFilter === key}>${label}</option>`
|
|
8351
|
-
)}
|
|
8352
|
-
</select>
|
|
8353
|
-
<input
|
|
8354
|
-
class="doorstop-search"
|
|
8355
|
-
type="search"
|
|
8356
|
-
aria-label="Search items"
|
|
8357
|
-
placeholder="Search UID or text"
|
|
8358
|
-
.value=${this.search}
|
|
8359
|
-
@input=${this.onSearchInput}
|
|
8360
|
-
/>
|
|
8361
|
-
</section>
|
|
8362
|
-
`;
|
|
8363
|
-
}
|
|
8364
|
-
/**
|
|
8365
|
-
* Expansion is CONTENT-DRIVEN (see `willUpdate`); there is no manual
|
|
8366
|
-
* expand/collapse toggle.
|
|
8367
|
-
*/
|
|
8368
|
-
renderStatusBar() {
|
|
8369
|
-
const lastRun = this.lastRun;
|
|
8370
|
-
if (lastRun === void 0) return A;
|
|
8371
|
-
const statusLabel = lastRun.status === "killed" ? "killed (timeout)" : lastRun.status;
|
|
8372
|
-
const detailParts = [lastRun.title, `${String(lastRun.durationMs)} ms`];
|
|
8373
|
-
if (lastRun.exitCode !== null && (lastRun.status === "failed" || lastRun.status === "killed")) {
|
|
8374
|
-
detailParts.push(`exit ${String(lastRun.exitCode)}`);
|
|
8375
|
-
}
|
|
8376
|
-
if (lastRun.signal !== null) detailParts.push(lastRun.signal);
|
|
8377
|
-
return b2`
|
|
8378
|
-
<section class="doorstop-status-bar" aria-label="Last run">
|
|
8379
|
-
<div class="doorstop-status-bar-row">
|
|
8380
|
-
<span class=${`doorstop-last-run-status is-${lastRun.status}`}>${statusLabel}</span>
|
|
8381
|
-
<span class="doorstop-last-run-meta">${detailParts.join(" \xB7 ")}</span>
|
|
8382
|
-
<button
|
|
8383
|
-
type="button"
|
|
8384
|
-
class="doorstop-last-run-dismiss"
|
|
8385
|
-
title="Dismiss the last run output"
|
|
8386
|
-
@click=${this.onDismissRun}
|
|
8387
|
-
>Dismiss</button>
|
|
8388
|
-
</div>
|
|
8389
|
-
${this.statusExpanded ? this.renderStatusBarBody(lastRun) : A}
|
|
8390
|
-
</section>
|
|
8391
|
-
`;
|
|
8392
|
-
}
|
|
8393
|
-
renderStatusBarBody(lastRun) {
|
|
8394
|
-
return b2`
|
|
8395
|
-
<div class="doorstop-last-run">
|
|
8396
|
-
${lastRun.commit === void 0 ? A : b2`<p class="doorstop-last-run-commit doorstop-muted">${commitOutcomeText(lastRun.op, lastRun.commit)}</p>`}
|
|
8397
|
-
${lastRun.status === "error" ? b2`<pre class="doorstop-last-run-pre">${lastRun.errorMessage ?? ""}</pre>` : A}
|
|
8398
|
-
${lastRun.stdout === "" ? A : b2`
|
|
8399
|
-
<pre class="doorstop-last-run-pre">${lastRun.stdout}</pre>
|
|
8400
|
-
${lastRun.stdoutTruncated ? b2`<p class="doorstop-last-run-notice">stdout truncated by the host stream limit (2 MiB) — output not fully captured</p>` : A}
|
|
8401
|
-
`}
|
|
8402
|
-
${lastRun.stderr === "" ? A : b2`
|
|
8403
|
-
<pre class="doorstop-last-run-pre">${lastRun.stderr}</pre>
|
|
8404
|
-
${lastRun.stderrTruncated ? b2`<p class="doorstop-last-run-notice">stderr truncated by the host stream limit (2 MiB) — output not fully captured</p>` : A}
|
|
8405
|
-
`}
|
|
8406
|
-
${lastRun.stdout === "" && lastRun.stderr === "" && lastRun.status !== "error" ? b2`<p class="doorstop-last-run-notice">No output captured.</p>` : A}
|
|
8407
|
-
</div>
|
|
8408
|
-
`;
|
|
8409
|
-
}
|
|
8410
|
-
renderViewToggle() {
|
|
8411
|
-
return b2`
|
|
8412
|
-
<div class="doorstop-view-toggle" role="tablist" aria-label="Requirements panel view">
|
|
8413
|
-
<button
|
|
8414
|
-
type="button"
|
|
8415
|
-
role="tab"
|
|
8416
|
-
class=${e5({
|
|
8417
|
-
"doorstop-view-tab": true,
|
|
8418
|
-
"doorstop-view-items": true,
|
|
8419
|
-
"is-selected": this.view === "items"
|
|
8420
|
-
})}
|
|
8421
|
-
aria-selected=${this.view === "items" ? "true" : "false"}
|
|
8422
|
-
title="Item list and detail"
|
|
8423
|
-
@click=${() => {
|
|
8424
|
-
this.view = "items";
|
|
8425
|
-
}}
|
|
8426
|
-
>Items</button>
|
|
8427
|
-
<button
|
|
8428
|
-
type="button"
|
|
8429
|
-
role="tab"
|
|
8430
|
-
class=${e5({
|
|
8431
|
-
"doorstop-view-tab": true,
|
|
8432
|
-
"doorstop-view-findings": true,
|
|
8433
|
-
"is-selected": this.view === "findings"
|
|
8434
|
-
})}
|
|
8435
|
-
aria-selected=${this.view === "findings" ? "true" : "false"}
|
|
8436
|
-
title="Validation findings"
|
|
8437
|
-
@click=${() => {
|
|
8438
|
-
this.view = "findings";
|
|
8439
|
-
}}
|
|
8440
|
-
>Findings</button>
|
|
8441
|
-
</div>
|
|
8442
|
-
`;
|
|
8443
|
-
}
|
|
8444
|
-
renderDocumentChip(document2, result) {
|
|
8445
|
-
const prefix = document2?.prefix ?? ALL_DOCUMENTS_LABEL;
|
|
8446
|
-
const count = document2 === void 0 ? result.index.items.length : result.index.items.filter((item) => item.documentPrefix === document2.prefix).length;
|
|
8447
|
-
const dots = document2 === void 0 ? [] : documentStateDots(document2, result.index.items);
|
|
8448
|
-
const selected = document2 === void 0 ? this.selectedDocumentPrefix === void 0 || this.selectedDocumentPrefix === "" : this.selectedDocumentPrefix === document2.prefix;
|
|
8449
|
-
return b2`
|
|
8450
|
-
<button
|
|
8451
|
-
type="button"
|
|
8452
|
-
role="listitem"
|
|
8453
|
-
class=${e5({ "doorstop-doc-chip": true, "is-selected": selected })}
|
|
8454
|
-
data-prefix=${document2?.prefix ?? ""}
|
|
8455
|
-
@click=${() => {
|
|
8456
|
-
this.controller?.selectDocument(document2?.prefix ?? "");
|
|
8457
|
-
}}
|
|
8458
|
-
>
|
|
8459
|
-
${document2?.parentPrefix === void 0 ? A : b2`<span class="doorstop-doc-arrow">← ${document2.parentPrefix}</span>`}
|
|
8460
|
-
<span class="doorstop-doc-prefix">${prefix}</span>
|
|
8461
|
-
<span class="doorstop-doc-count">${String(count)}</span>
|
|
8462
|
-
${dots.map((dot) => b2`<span class=${`doorstop-dot doorstop-dot-${dot}`} title=${dotTitle(dot)}></span>`)}
|
|
8463
|
-
</button>
|
|
8464
|
-
`;
|
|
8465
|
-
}
|
|
8466
|
-
// --- item list ------------------------------------------------------------------
|
|
8467
|
-
renderViewer() {
|
|
8468
|
-
const result = this.result;
|
|
8469
|
-
if (result === void 0) {
|
|
8470
|
-
return b2`<p class="doorstop-muted doorstop-standalone">${this.loading ? "Loading workspace\u2026" : "Run Refresh to scan for Doorstop documents."}</p>`;
|
|
8471
|
-
}
|
|
8472
|
-
if (this.view === "findings") {
|
|
8473
|
-
return this.renderFindingsView(result);
|
|
8474
|
-
}
|
|
8475
|
-
return b2`
|
|
8476
|
-
${this.renderListFilters(result)}
|
|
8477
|
-
${result.index.documents.length === 0 ? b2`
|
|
8478
|
-
${this.renderDiagnostics(result)}
|
|
8479
|
-
<section class="doorstop-empty"><p>${EMPTY_WORKSPACE_MESSAGE}</p></section>
|
|
8480
|
-
` : b2`
|
|
8481
|
-
<section class="doorstop-split">
|
|
8482
|
-
<section class="doorstop-list">
|
|
8483
|
-
${this.renderDiagnostics(result)}
|
|
8484
|
-
${this.renderItemList(result)}
|
|
8485
|
-
</section>
|
|
8486
|
-
<section class="doorstop-detail-pane">${this.renderDetail(result)}</section>
|
|
8487
|
-
</section>
|
|
8488
|
-
`}
|
|
8489
|
-
`;
|
|
8490
|
-
}
|
|
8491
|
-
renderFindingsView(result) {
|
|
8492
|
-
const rows = findingsViewRows(result.index);
|
|
8493
|
-
const counts = findingsViewCounts(rows);
|
|
8494
|
-
return b2`
|
|
8495
|
-
<section class="doorstop-findings-view" aria-label="Doorstop findings">
|
|
8496
|
-
<header class="doorstop-findings-head">
|
|
8497
|
-
<span class="doorstop-findings-counts" role="status" aria-label="Finding counts">${findingsCountText(counts)}</span>
|
|
8498
|
-
<span class="doorstop-findings-note doorstop-muted">${FINDINGS_PLUGIN_LOCAL_NOTE}</span>
|
|
8499
|
-
</header>
|
|
8500
|
-
${rows.length === 0 ? b2`
|
|
8501
|
-
<section class="doorstop-empty">
|
|
8502
|
-
<p>${FINDINGS_EMPTY_MESSAGE}</p>
|
|
8503
|
-
<p class="doorstop-muted">${FINDINGS_EMPTY_HINT}</p>
|
|
8504
|
-
</section>
|
|
8505
|
-
` : b2`<div class="doorstop-findings-list" role="list" aria-label="Validation findings">
|
|
8506
|
-
${rows.map((row) => this.renderFindingRow(row, result.index))}
|
|
8507
|
-
</div>`}
|
|
8508
|
-
</section>
|
|
8509
|
-
`;
|
|
8510
|
-
}
|
|
8511
|
-
renderFindingRow(row, index) {
|
|
8512
|
-
const kind = row.severity === "error" ? "doorstop-error" : row.severity === "warning" ? "doorstop-warning" : "doorstop-info";
|
|
8513
|
-
const uid = row.uid;
|
|
8514
|
-
const navigable = uid !== void 0 && index.byUid.has(uid);
|
|
8515
|
-
return b2`
|
|
8516
|
-
<div class=${`doorstop-finding-row ${kind}`} role="listitem">
|
|
8517
|
-
<span class="doorstop-severity">${row.severity}</span>
|
|
8518
|
-
${uid === void 0 ? A : navigable ? b2`<button type="button" class="doorstop-finding-uid" data-uid=${uid} title=${`Show ${uid} in the item list`} @click=${() => {
|
|
8519
|
-
this.selectFindingTarget(uid);
|
|
8520
|
-
}}><code>${uid}</code></button>` : b2`<code class="doorstop-finding-uid">${uid}</code>`}
|
|
8521
|
-
${row.path === void 0 ? A : b2`<code class="doorstop-finding-path">${row.path}</code>`}
|
|
8522
|
-
<span class="doorstop-finding-message">${row.message}</span>
|
|
8523
|
-
</div>
|
|
9499
|
+
<section class="doorstop-viewer">${renderViewer(this)}</section>
|
|
9500
|
+
${renderItemActionPalette(this)}
|
|
9501
|
+
${renderStatusBar(this)}
|
|
8524
9502
|
`;
|
|
8525
9503
|
}
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
return
|
|
8538
|
-
<section class="doorstop-diagnostics" aria-label="Workspace diagnostics">
|
|
8539
|
-
${result.index.diagnostics.map((diagnostic) => {
|
|
8540
|
-
const kind = diagnostic.severity === "error" ? "doorstop-error" : "doorstop-warning";
|
|
8541
|
-
return b2`
|
|
8542
|
-
<div class=${`doorstop-diagnostic ${kind}`}>
|
|
8543
|
-
<span class="doorstop-severity">${diagnostic.severity}</span>
|
|
8544
|
-
<span class="doorstop-diagnostic-copy">
|
|
8545
|
-
${diagnostic.path === void 0 ? A : b2`<code>${diagnostic.path}</code>`}
|
|
8546
|
-
${diagnostic.message}
|
|
8547
|
-
</span>
|
|
8548
|
-
</div>
|
|
8549
|
-
`;
|
|
8550
|
-
})}
|
|
8551
|
-
</section>
|
|
8552
|
-
`;
|
|
9504
|
+
/** section-API: the baseline fetch and the review→commit pipeline live
|
|
9505
|
+
* server-side, so this gates the sections that need them. The v4 peer
|
|
9506
|
+
* is the exact revision-paired path to this package's own active server
|
|
9507
|
+
* entry; v4 also removed the provider `capabilities.request` field, so
|
|
9508
|
+
* `peer.request` presence is the paired-backend gate (a legal
|
|
9509
|
+
* openChannel-only peer is NOT paired for these request-shaped
|
|
9510
|
+
* sections). The opendoor provider ownership check is kept for the
|
|
9511
|
+
* ownership-based information display (behavior-preserving Phase A;
|
|
9512
|
+
* Phase C revisits it). */
|
|
9513
|
+
backendActive() {
|
|
9514
|
+
const context = this.context;
|
|
9515
|
+
return context?.peer?.request !== void 0 && context.workspace.provider?.pluginId === OPENDOOR_PLUGIN_ID;
|
|
8553
9516
|
}
|
|
8554
|
-
/**
|
|
8555
|
-
* ready view, and an UNTRUNCATED `files` list. `undefined` when
|
|
9517
|
+
/** section-API: the status response usable for PER-ITEM git UI: a paired
|
|
9518
|
+
* install, a ready view, and an UNTRUNCATED `files` list. `undefined` when
|
|
8556
9519
|
* unpaired / loading / no-git / error / truncated, so the item list and
|
|
8557
9520
|
* action row render no chips, no per-row add, and no svg inside the row
|
|
8558
9521
|
* (the XSS assertion's structural dependency). The strip reads
|
|
@@ -8565,448 +9528,24 @@ function defineDoorstopPanelBodyElement() {
|
|
|
8565
9528
|
if (gitStatusFilesTruncated(view.response)) return void 0;
|
|
8566
9529
|
return view.response;
|
|
8567
9530
|
}
|
|
8568
|
-
/**
|
|
8569
|
-
* git UI is unavailable (see {@link readyGitStatus}). */
|
|
9531
|
+
/** section-API: the path→git-file map for one list render; `undefined`
|
|
9532
|
+
* when per-item git UI is unavailable (see {@link readyGitStatus}). */
|
|
8570
9533
|
readyGitFiles() {
|
|
8571
9534
|
const response = this.readyGitStatus();
|
|
8572
9535
|
return response === void 0 ? void 0 : gitStatusFilesByPath(response);
|
|
8573
9536
|
}
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
}
|
|
8578
|
-
const items = filteredItems(result.index, this.selectedDocumentPrefix, this.stateFilter, this.search);
|
|
8579
|
-
if (items.length === 0) {
|
|
8580
|
-
return b2`<p class="doorstop-muted doorstop-standalone">No items match the current document, state, or search filters.</p>`;
|
|
8581
|
-
}
|
|
8582
|
-
const gitFiles = this.readyGitFiles();
|
|
8583
|
-
return b2`
|
|
8584
|
-
<div class="doorstop-items" role="list" aria-label="Doorstop items">
|
|
8585
|
-
${c5(items, (item) => item.uid, (item) => this.renderItemRow(item, gitFiles))}
|
|
8586
|
-
</div>
|
|
8587
|
-
`;
|
|
8588
|
-
}
|
|
8589
|
-
renderItemRow(item, gitFiles) {
|
|
8590
|
-
const selected = this.selectedUid === item.uid;
|
|
8591
|
-
const gitState = itemGitState(gitFiles?.get(item.path));
|
|
8592
|
-
const stageable = itemStageable(gitFiles?.get(item.path));
|
|
8593
|
-
const unstageable = itemUnstageable(gitFiles?.get(item.path));
|
|
8594
|
-
return b2`
|
|
8595
|
-
<button
|
|
8596
|
-
type="button"
|
|
8597
|
-
role="listitem"
|
|
8598
|
-
class=${e5({ "doorstop-item-row": true, "is-selected": selected })}
|
|
8599
|
-
data-uid=${item.uid}
|
|
8600
|
-
@click=${() => {
|
|
8601
|
-
this.controller?.selectUid(item.uid);
|
|
8602
|
-
}}
|
|
8603
|
-
>
|
|
8604
|
-
<span class="doorstop-item-level">${item.level}</span>
|
|
8605
|
-
<code class="doorstop-item-uid">${item.uid}</code>
|
|
8606
|
-
<span class="doorstop-item-summary">${itemExcerpt(item)}</span>
|
|
8607
|
-
<span class="doorstop-item-chips">
|
|
8608
|
-
${item.stateKeys.map((key) => this.renderStateChip(key))}
|
|
8609
|
-
${gitState === "clean" ? A : b2`<span class=${`doorstop-chip doorstop-chip-${gitChipKind(gitState)}`}>${GIT_CHIP_LABELS[gitState]}</span>`}
|
|
8610
|
-
${unstageable && this.runInProgress === void 0 ? b2`<span
|
|
8611
|
-
class="doorstop-item-add"
|
|
8612
|
-
aria-hidden="true"
|
|
8613
|
-
title=${`git reset ${item.path}`}
|
|
8614
|
-
@click=${(event) => {
|
|
8615
|
-
event.stopPropagation();
|
|
8616
|
-
this.unstageItem(item);
|
|
8617
|
-
}}
|
|
8618
|
-
>${gitUnstageIconSvg}</span>` : stageable && this.runInProgress === void 0 ? b2`<span
|
|
8619
|
-
class="doorstop-item-add"
|
|
8620
|
-
aria-hidden="true"
|
|
8621
|
-
title=${`git add ${item.path}`}
|
|
8622
|
-
@click=${(event) => {
|
|
8623
|
-
event.stopPropagation();
|
|
8624
|
-
this.stageItem(item);
|
|
8625
|
-
}}
|
|
8626
|
-
>${gitStageIconSvg}</span>` : A}
|
|
8627
|
-
</span>
|
|
8628
|
-
</button>
|
|
8629
|
-
`;
|
|
8630
|
-
}
|
|
8631
|
-
renderStateChip(key) {
|
|
8632
|
-
const kind = stateChipKind(key);
|
|
8633
|
-
return b2`<span class=${`doorstop-chip doorstop-chip-${kind}`}>${STATE_CHIP_LABELS[key]}</span>`;
|
|
8634
|
-
}
|
|
8635
|
-
/** The baseline fetch and the review→commit pipeline live server-side,
|
|
8636
|
-
* so this gates the sections that need them. */
|
|
8637
|
-
backendActive() {
|
|
8638
|
-
const context = this.context;
|
|
8639
|
-
return context?.backend !== void 0 && context.workspace.provider?.pluginId === OPENDOOR_PLUGIN_ID && context.workspace.provider?.capabilities.request !== false;
|
|
8640
|
-
}
|
|
8641
|
-
/**
|
|
8642
|
-
* The `<details>` is wrapped in `keyed(item.uid, …)` so a selection
|
|
8643
|
-
* switch RE-CREATES the node (fresh, collapsed, with a real `toggle` on
|
|
8644
|
-
* expand) instead of Lit reusing the old item's open node — a reused
|
|
8645
|
-
* node fires no `toggle`, which would strand the new item on
|
|
8646
|
-
* "Loading baseline…". {@link updated} covers the remaining reuse
|
|
8647
|
-
* hole (same item re-rendered after its cache key changed).
|
|
8648
|
-
*/
|
|
8649
|
-
renderChangesSinceReview(item) {
|
|
8650
|
-
if (!item.stateKeys.includes("unreviewed") || item.reviewed === null) return A;
|
|
8651
|
-
if (!this.backendActive()) return A;
|
|
8652
|
-
const view = this.controller?.baselineViewFor(item);
|
|
8653
|
-
return i7(
|
|
8654
|
-
item.uid,
|
|
8655
|
-
b2`
|
|
8656
|
-
<details class="doorstop-changes" @toggle=${this.onChangesToggle}>
|
|
8657
|
-
<summary class="doorstop-changes-summary">Changes since review</summary>
|
|
8658
|
-
${view === void 0 || view.state === "loading" ? b2`<p class="doorstop-muted doorstop-changes-notice">Loading baseline…</p>` : this.renderBaselineView(view)}
|
|
8659
|
-
</details>
|
|
8660
|
-
`
|
|
8661
|
-
);
|
|
8662
|
-
}
|
|
8663
|
-
renderBaselineView(view) {
|
|
8664
|
-
switch (view.state) {
|
|
8665
|
-
case "loading":
|
|
8666
|
-
return b2`<p class="doorstop-muted doorstop-changes-notice">Loading baseline…</p>`;
|
|
8667
|
-
case "no-git":
|
|
8668
|
-
return b2`<p class="doorstop-muted doorstop-changes-notice">No git history — previous version unavailable</p>`;
|
|
8669
|
-
case "no-match":
|
|
8670
|
-
return b2`<p class="doorstop-muted doorstop-changes-notice">Could not locate the reviewed version (history may have been rewritten)</p>`;
|
|
8671
|
-
case "error":
|
|
8672
|
-
return b2`<p class="doorstop-muted doorstop-changes-notice">Baseline unavailable — ${view.errorMessage ?? "request failed"}</p>`;
|
|
8673
|
-
case "ready": {
|
|
8674
|
-
const diff = view.diff;
|
|
8675
|
-
if (diff === void 0) return b2`<p class="doorstop-muted doorstop-changes-notice">No changes found.</p>`;
|
|
8676
|
-
return this.renderBaselineDiff(view, diff);
|
|
8677
|
-
}
|
|
8678
|
-
}
|
|
8679
|
-
}
|
|
8680
|
-
renderBaselineDiff(view, diff) {
|
|
8681
|
-
return b2`
|
|
8682
|
-
${view.source === void 0 ? A : b2`<p class="doorstop-muted doorstop-changes-source">matched via ${view.source === "review-commit" ? "review commit" : "history walk"}</p>`}
|
|
8683
|
-
${diff.text === void 0 ? b2`<p class="doorstop-muted doorstop-changes-notice">Text changed — too large to render a line diff.</p>` : b2`<div class="doorstop-diff-lines" role="list" aria-label="Text changes since review">
|
|
8684
|
-
${diff.text.map((line) => this.renderDiffLine(line))}
|
|
8685
|
-
</div>`}
|
|
8686
|
-
${diff.ref === void 0 ? A : this.renderFieldChange("ref", diff.ref.before, diff.ref.after)}
|
|
8687
|
-
${diff.references === void 0 ? A : this.renderFieldChange(
|
|
8688
|
-
"references",
|
|
8689
|
-
referenceListText(diff.references.before),
|
|
8690
|
-
referenceListText(diff.references.after)
|
|
8691
|
-
)}
|
|
8692
|
-
${diff.linksAdded.length === 0 && diff.linksRemoved.length === 0 ? A : b2`<div class="doorstop-field-change">
|
|
8693
|
-
<span class="doorstop-field-name">links</span>
|
|
8694
|
-
${diff.linksAdded.map((uid) => b2`<code class="doorstop-field-chip doorstop-field-chip-after">+ ${uid}</code>`)}
|
|
8695
|
-
${diff.linksRemoved.map((uid) => b2`<code class="doorstop-field-chip doorstop-field-chip-before">− ${uid}</code>`)}
|
|
8696
|
-
</div>`}
|
|
8697
|
-
${diff.extended.map((change) => this.renderFieldChange(change.name, change.before, change.after))}
|
|
8698
|
-
`;
|
|
8699
|
-
}
|
|
8700
|
-
renderDiffLine(line) {
|
|
8701
|
-
const mark = line.kind === "added" ? "+" : line.kind === "removed" ? "\u2212" : "";
|
|
8702
|
-
return b2`
|
|
8703
|
-
<div class=${`doorstop-diff-line is-${line.kind}`} role="listitem">
|
|
8704
|
-
<span class="doorstop-diff-mark">${mark}</span>
|
|
8705
|
-
<span class="doorstop-diff-text">${line.text === "" ? "\xA0" : line.text}</span>
|
|
8706
|
-
</div>
|
|
8707
|
-
`;
|
|
8708
|
-
}
|
|
8709
|
-
renderFieldChange(label, before, after) {
|
|
8710
|
-
return b2`
|
|
8711
|
-
<div class="doorstop-field-change">
|
|
8712
|
-
<span class="doorstop-field-name">${label}</span>
|
|
8713
|
-
<code class="doorstop-field-chip doorstop-field-chip-before">${diffValueText(before)}</code>
|
|
8714
|
-
<span class="doorstop-field-arrow">→</span>
|
|
8715
|
-
<code class="doorstop-field-chip doorstop-field-chip-after">${diffValueText(after)}</code>
|
|
8716
|
-
</div>
|
|
8717
|
-
`;
|
|
8718
|
-
}
|
|
8719
|
-
// --- detail pane ------------------------------------------------------------------
|
|
8720
|
-
// v1 deliberately renders the item text as escaped text — no markdown,
|
|
8721
|
-
// injection-safe by construction.
|
|
8722
|
-
renderDetail(result) {
|
|
8723
|
-
const selectedUid = this.selectedUid;
|
|
8724
|
-
if (selectedUid === void 0) {
|
|
8725
|
-
return b2`<p class="doorstop-muted doorstop-standalone">Select an item in the list to inspect its details.</p>`;
|
|
8726
|
-
}
|
|
8727
|
-
const item = result.index.byUid.get(selectedUid);
|
|
8728
|
-
if (item === void 0) {
|
|
8729
|
-
return b2`<p class="doorstop-muted doorstop-standalone">This item is no longer available — refresh the panel.</p>`;
|
|
8730
|
-
}
|
|
8731
|
-
const children = result.index.childrenByUid.get(item.uid) ?? [];
|
|
8732
|
-
const findings = result.index.findings.filter((finding) => finding.uid === item.uid);
|
|
8733
|
-
return b2`
|
|
8734
|
-
<section class="doorstop-detail" aria-label="Item details">
|
|
8735
|
-
<div class="doorstop-detail-head">
|
|
8736
|
-
<code class="doorstop-detail-uid">${item.uid}</code>
|
|
8737
|
-
<span class="doorstop-level">level ${item.level}</span>
|
|
8738
|
-
${item.header === void 0 ? A : b2`<h3>${item.header}</h3>`}
|
|
8739
|
-
</div>
|
|
8740
|
-
${this.renderFlags(item)}
|
|
8741
|
-
<div class="doorstop-state-chip-row">
|
|
8742
|
-
${item.stateKeys.map((key) => this.renderStateChip(key))}
|
|
8743
|
-
</div>
|
|
8744
|
-
<h4 class="doorstop-section-title">Text</h4>
|
|
8745
|
-
<p class=${item.text === "" ? "doorstop-text doorstop-text-empty" : "doorstop-text"}>${item.text === "" ? "empty" : item.text}</p>
|
|
8746
|
-
${this.renderChangesSinceReview(item)}
|
|
8747
|
-
<h4 class="doorstop-section-title">Parent links</h4>
|
|
8748
|
-
${this.renderLinksOut(item, result.index)}
|
|
8749
|
-
<h4 class="doorstop-section-title">Child links</h4>
|
|
8750
|
-
${this.renderLinksIn(item, result.index, children)}
|
|
8751
|
-
<h4 class="doorstop-section-title">References</h4>
|
|
8752
|
-
${this.renderReferences(item, result.index)}
|
|
8753
|
-
${Object.entries(item.attributes).length === 0 ? A : b2`
|
|
8754
|
-
<h4 class="doorstop-section-title">Extended attributes</h4>
|
|
8755
|
-
<dl class="doorstop-attributes">
|
|
8756
|
-
${Object.entries(item.attributes).map(
|
|
8757
|
-
([key, value]) => b2`<div class="doorstop-attribute"><dt>${key}</dt><dd><code>${jsonishText(value)}</code></dd></div>`
|
|
8758
|
-
)}
|
|
8759
|
-
</dl>
|
|
8760
|
-
`}
|
|
8761
|
-
<h4 class="doorstop-section-title">Findings</h4>
|
|
8762
|
-
${findings.length === 0 ? b2`<p class="doorstop-muted">No local findings for this item.</p>` : b2`<div class="doorstop-findings" aria-label="Findings for this item">
|
|
8763
|
-
${findings.map((finding) => {
|
|
8764
|
-
const kind = finding.severity === "error" ? "doorstop-error" : finding.severity === "warning" ? "doorstop-warning" : "doorstop-info";
|
|
8765
|
-
return b2`<div class=${`doorstop-finding ${kind}`}><span class="doorstop-finding-severity">${finding.severity}</span><span>${finding.message}</span></div>`;
|
|
8766
|
-
})}
|
|
8767
|
-
</div>`}
|
|
8768
|
-
</section>
|
|
8769
|
-
`;
|
|
8770
|
-
}
|
|
8771
|
-
// --- item action palette (region 4) --------------------------------------------
|
|
8772
|
-
renderItemActionPalette() {
|
|
8773
|
-
if (this.view !== "items") return A;
|
|
8774
|
-
const result = this.result;
|
|
8775
|
-
if (result === void 0 || result.index.documents.length === 0) return A;
|
|
8776
|
-
const item = this.selectedItem();
|
|
8777
|
-
if (item === void 0) {
|
|
8778
|
-
return b2`
|
|
8779
|
-
<section class="doorstop-action-palette" aria-label="Item actions">
|
|
8780
|
-
<span class="doorstop-muted doorstop-palette-placeholder">Select an item…</span>
|
|
8781
|
-
</section>
|
|
8782
|
-
`;
|
|
8783
|
-
}
|
|
8784
|
-
const suspects = suspectParentItems(item, result.index);
|
|
8785
|
-
return b2`
|
|
8786
|
-
<section class="doorstop-action-palette" aria-label="Item actions">
|
|
8787
|
-
${this.renderActionRow(item, result.index, suspects)}
|
|
8788
|
-
</section>
|
|
8789
|
-
`;
|
|
8790
|
-
}
|
|
8791
|
-
renderFlags(item) {
|
|
8792
|
-
return b2`
|
|
8793
|
-
<div class="doorstop-flags" aria-label="Item flags">
|
|
8794
|
-
<span class=${e5({ "doorstop-flag": true, "is-inactive": !item.active })}>
|
|
8795
|
-
${item.active ? "active" : "inactive"}
|
|
8796
|
-
</span>
|
|
8797
|
-
<span class="doorstop-flag">${item.normative ? "normative" : "non-normative"}</span>
|
|
8798
|
-
<span class="doorstop-flag">${item.derived ? "derived" : "non-derived"}</span>
|
|
8799
|
-
</div>
|
|
8800
|
-
`;
|
|
8801
|
-
}
|
|
8802
|
-
/** `computeItemStamp(parent, config, false)` is exactly the
|
|
8803
|
-
* link-record stamp the state chain compares against. */
|
|
8804
|
-
renderLinksOut(item, index) {
|
|
8805
|
-
if (item.links.length === 0) return b2`<p class="doorstop-muted">No parent links.</p>`;
|
|
8806
|
-
return b2`
|
|
8807
|
-
<div class="doorstop-links" aria-label="Parent links">
|
|
8808
|
-
${item.links.map((link) => this.renderLinkOut(item, link, index))}
|
|
8809
|
-
</div>
|
|
8810
|
-
`;
|
|
8811
|
-
}
|
|
8812
|
-
renderLinkOut(item, link, index) {
|
|
8813
|
-
const remove = b2`
|
|
8814
|
-
<span
|
|
8815
|
-
class="doorstop-link-remove"
|
|
8816
|
-
aria-hidden="true"
|
|
8817
|
-
title=${`doorstop unlink ${item.uid} ${link.uid}`}
|
|
8818
|
-
@click=${(event) => {
|
|
8819
|
-
event.stopPropagation();
|
|
8820
|
-
this.unlinkLink(item, link.uid);
|
|
8821
|
-
}}
|
|
8822
|
-
>${trashIconSvg}</span>
|
|
8823
|
-
`;
|
|
8824
|
-
const target = index.byUid.get(link.uid);
|
|
8825
|
-
if (target === void 0) {
|
|
8826
|
-
return b2`
|
|
8827
|
-
<div class="doorstop-link-row">
|
|
8828
|
-
<code>${link.uid}</code>
|
|
8829
|
-
<span class="doorstop-chip doorstop-chip-danger">unknown</span>
|
|
8830
|
-
<span class="doorstop-fingerprint">not in the index — ${link.fingerprint === null ? "no recorded fingerprint" : `recorded ${shortFingerprint(link.fingerprint)}`}</span>
|
|
8831
|
-
${remove}
|
|
8832
|
-
</div>
|
|
8833
|
-
`;
|
|
8834
|
-
}
|
|
8835
|
-
const current = computeItemStamp(target, documentConfigFor(index, target), false);
|
|
8836
|
-
const suspect = link.fingerprint !== null && link.fingerprint !== current;
|
|
8837
|
-
return b2`
|
|
8838
|
-
<button
|
|
8839
|
-
type="button"
|
|
8840
|
-
class="doorstop-link-row"
|
|
8841
|
-
data-uid=${link.uid}
|
|
8842
|
-
title=${`Open ${link.uid}`}
|
|
8843
|
-
@click=${() => {
|
|
8844
|
-
this.controller?.selectUid(link.uid);
|
|
8845
|
-
}}
|
|
8846
|
-
>
|
|
8847
|
-
<code>${link.uid}</code>
|
|
8848
|
-
<span class=${suspect ? "doorstop-chip doorstop-chip-danger" : "doorstop-chip doorstop-chip-ok"}>${suspect ? "suspect" : "ok"}</span>
|
|
8849
|
-
<span class="doorstop-fingerprint">recorded ${shortFingerprint(link.fingerprint)} · current ${shortFingerprint(current)}</span>
|
|
8850
|
-
${remove}
|
|
8851
|
-
</button>
|
|
8852
|
-
`;
|
|
8853
|
-
}
|
|
8854
|
-
renderLinksIn(item, index, children) {
|
|
8855
|
-
if (children.length === 0) {
|
|
8856
|
-
return b2`<p class="doorstop-muted">No child links.</p>`;
|
|
8857
|
-
}
|
|
8858
|
-
return b2`
|
|
8859
|
-
<div class="doorstop-links" aria-label="Child links">
|
|
8860
|
-
${children.map(
|
|
8861
|
-
(child) => b2`<button type="button" class="doorstop-link-row" data-uid=${child.uid} title=${`Open ${child.uid}`} @click=${() => {
|
|
8862
|
-
this.controller?.selectUid(child.uid);
|
|
8863
|
-
}}>
|
|
8864
|
-
<code>${child.uid}</code>
|
|
8865
|
-
<span class="doorstop-muted">${child.level}</span>
|
|
8866
|
-
</button>`
|
|
8867
|
-
)}
|
|
8868
|
-
</div>
|
|
8869
|
-
`;
|
|
8870
|
-
}
|
|
8871
|
-
renderReferences(item, index) {
|
|
8872
|
-
const paths = [];
|
|
8873
|
-
if (item.ref !== "") paths.push(item.ref);
|
|
8874
|
-
if (item.references !== void 0) {
|
|
8875
|
-
for (const reference of item.references) paths.push(reference.path);
|
|
8876
|
-
}
|
|
8877
|
-
if (paths.length === 0) return b2`<p class="doorstop-muted">No file references.</p>`;
|
|
8878
|
-
return b2`
|
|
8879
|
-
<div class="doorstop-links" aria-label="File references">
|
|
8880
|
-
${paths.map((path) => {
|
|
8881
|
-
const found = index.knownFilePaths.has(path);
|
|
8882
|
-
return b2`
|
|
8883
|
-
<div class="doorstop-link-row">
|
|
8884
|
-
<code>${path}</code>
|
|
8885
|
-
${found ? A : b2`<span class="doorstop-chip doorstop-chip-danger">not found</span>`}
|
|
8886
|
-
</div>
|
|
8887
|
-
`;
|
|
8888
|
-
})}
|
|
8889
|
-
</div>
|
|
8890
|
-
`;
|
|
8891
|
-
}
|
|
8892
|
-
// --- action row ------------------------------------------------------------------
|
|
8893
|
-
renderActionRow(item, index, suspects) {
|
|
8894
|
-
const reviewed = item.stateKeys.includes("reviewed");
|
|
8895
|
-
const suspectUids = suspects.length > 0 ? suspects.map((parent) => parent.uid) : [];
|
|
8896
|
-
const gitStatus = this.readyGitStatus();
|
|
8897
|
-
const gitFile = gitStatus === void 0 ? void 0 : gitStatusFileFor(gitStatus, item.path);
|
|
8898
|
-
const stageable = itemStageable(gitFile);
|
|
8899
|
-
const unstageable = itemUnstageable(gitFile);
|
|
8900
|
-
return b2`
|
|
8901
|
-
<button
|
|
8902
|
-
type="button"
|
|
8903
|
-
class="doorstop-review"
|
|
8904
|
-
?disabled=${reviewed || this.runInProgress !== void 0}
|
|
8905
|
-
title=${reviewed ? `${item.uid} is already reviewed against its current fingerprint` : `Mark ${item.uid} reviewed`}
|
|
8906
|
-
@click=${() => {
|
|
8907
|
-
this.reviewItem(item);
|
|
8908
|
-
}}
|
|
8909
|
-
>Approve</button>
|
|
8910
|
-
<button
|
|
8911
|
-
type="button"
|
|
8912
|
-
class="doorstop-clear"
|
|
8913
|
-
?disabled=${suspects.length === 0 || this.runInProgress !== void 0}
|
|
8914
|
-
title=${suspects.length === 0 ? `No suspect links to clear` : `Re-record the parent fingerprints of ${item.uid}`}
|
|
8915
|
-
@click=${() => {
|
|
8916
|
-
this.clearSuspects(item, suspectUids);
|
|
8917
|
-
}}
|
|
8918
|
-
>Clear suspect links</button>
|
|
8919
|
-
${gitStatus === void 0 ? A : b2`<button
|
|
8920
|
-
type="button"
|
|
8921
|
-
class="doorstop-item-stage"
|
|
8922
|
-
?disabled=${this.runInProgress !== void 0 || !stageable}
|
|
8923
|
-
title=${stageable ? `Stage ${item.path}` : `${item.uid} has no unstaged changes \u2014 commit next`}
|
|
8924
|
-
@click=${() => {
|
|
8925
|
-
this.stageItem(item);
|
|
8926
|
-
}}
|
|
8927
|
-
>${gitStageIconSvg}Stage</button>
|
|
8928
|
-
<button
|
|
8929
|
-
type="button"
|
|
8930
|
-
class="doorstop-item-unstage"
|
|
8931
|
-
?disabled=${this.runInProgress !== void 0 || !unstageable}
|
|
8932
|
-
title=${unstageable ? `Unstage ${item.path} (git reset)` : `${item.uid} has nothing staged`}
|
|
8933
|
-
@click=${() => {
|
|
8934
|
-
this.unstageItem(item);
|
|
8935
|
-
}}
|
|
8936
|
-
>${gitUnstageIconSvg}Unstage</button>`}
|
|
8937
|
-
<div class="doorstop-op">
|
|
8938
|
-
<input
|
|
8939
|
-
type="text"
|
|
8940
|
-
class="doorstop-target-input"
|
|
8941
|
-
data-op="link"
|
|
8942
|
-
placeholder="parent UID"
|
|
8943
|
-
${n5(this.linkInputRef)}
|
|
8944
|
-
@keydown=${this.onTargetKeydown}
|
|
8945
|
-
/>
|
|
8946
|
-
<button type="button" class="doorstop-link" title=${`doorstop link ${item.uid} <target>`} ?disabled=${this.runInProgress !== void 0} @click=${() => {
|
|
8947
|
-
this.runTargetOp("link", this.linkInputRef, item);
|
|
8948
|
-
}}>Link</button>
|
|
8949
|
-
</div>
|
|
8950
|
-
${this.targetError === void 0 ? A : b2`<span class="doorstop-op-error" role="alert">${this.targetError}</span>`}
|
|
8951
|
-
${this.renderAskMenu(item, index, suspects)}
|
|
8952
|
-
`;
|
|
8953
|
-
}
|
|
8954
|
-
renderAskMenu(item, index, suspects) {
|
|
8955
|
-
const children = index.childrenByUid.get(item.uid) ?? [];
|
|
8956
|
-
const childPrefix = firstChildDocumentPrefix(index, item);
|
|
8957
|
-
return b2`
|
|
8958
|
-
<div class="doorstop-menu">
|
|
8959
|
-
<button
|
|
8960
|
-
type="button"
|
|
8961
|
-
class="doorstop-menu-toggle"
|
|
8962
|
-
aria-expanded=${this.askMenuOpen ? "true" : "false"}
|
|
8963
|
-
@click=${this.onAskMenuToggle}
|
|
8964
|
-
>Ask agent</button>
|
|
8965
|
-
${this.askMenuOpen ? b2`
|
|
8966
|
-
<div class="doorstop-menu-items" role="menu" aria-label="Ask the agent about ${item.uid}">
|
|
8967
|
-
<button type="button" role="menuitem" class="doorstop-menu-item doorstop-explain" @click=${() => {
|
|
8968
|
-
this.insertPrompt(explainItemPrompt(item));
|
|
8969
|
-
}}>Explain</button>
|
|
8970
|
-
<button
|
|
8971
|
-
type="button"
|
|
8972
|
-
role="menuitem"
|
|
8973
|
-
class="doorstop-menu-item doorstop-fix-suspects"
|
|
8974
|
-
?disabled=${suspects.length === 0}
|
|
8975
|
-
title=${suspects.length === 0 ? "No suspect links \u2014 nothing to fix" : `Fix the suspect links of ${item.uid}`}
|
|
8976
|
-
@click=${() => {
|
|
8977
|
-
this.insertPrompt(fixSuspectLinksPrompt(item, suspects));
|
|
8978
|
-
}}
|
|
8979
|
-
>Fix suspect links</button>
|
|
8980
|
-
<button
|
|
8981
|
-
type="button"
|
|
8982
|
-
role="menuitem"
|
|
8983
|
-
class="doorstop-menu-item doorstop-draft-child"
|
|
8984
|
-
?disabled=${childPrefix === void 0}
|
|
8985
|
-
title=${childPrefix === void 0 ? "No child document to add to" : `Draft a child item in the ${childPrefix} document`}
|
|
8986
|
-
@click=${() => {
|
|
8987
|
-
if (childPrefix !== void 0) this.insertPrompt(draftChildRequirementPrompt(item, childPrefix));
|
|
8988
|
-
}}
|
|
8989
|
-
>Draft child requirement</button>
|
|
8990
|
-
<button type="button" role="menuitem" class="doorstop-menu-item doorstop-review-readiness" @click=${() => {
|
|
8991
|
-
this.insertPrompt(reviewReadinessPrompt(item, children));
|
|
8992
|
-
}}>Review readiness</button>
|
|
8993
|
-
</div>
|
|
8994
|
-
` : A}
|
|
8995
|
-
</div>
|
|
8996
|
-
`;
|
|
8997
|
-
}
|
|
8998
|
-
/** Stage one item's own file — the same `doorstop.git-stage` operation
|
|
8999
|
-
* with `[item.path]`; the shared run dispatch narrates the uid. Shared
|
|
9000
|
-
* by the row's `git add` span and the palette's Stage button. */
|
|
9537
|
+
/** section-API: stage one item's own file — the same `doorstop.git-stage`
|
|
9538
|
+
* operation with `[item.path]`; the shared run dispatch narrates the uid.
|
|
9539
|
+
* Shared by the row's `git add` span and the palette's Stage button. */
|
|
9001
9540
|
stageItem(item) {
|
|
9002
9541
|
const controller = this.controller;
|
|
9003
9542
|
if (controller === void 0 || controller.runInProgress !== void 0) return;
|
|
9004
9543
|
void controller.runGitStage([item.path], `Git: stage ${item.uid}`);
|
|
9005
9544
|
}
|
|
9006
|
-
/**
|
|
9007
|
-
* `[item.path]`; the shared run dispatch narrates the uid.
|
|
9008
|
-
* the row's minus span and the palette's Unstage button. The
|
|
9009
|
-
* mirrors {@link stageItem}: a run already in flight must never be
|
|
9545
|
+
/** section-API: unstage one item's own file — the `doorstop.git-unstage`
|
|
9546
|
+
* inverse with `[item.path]`; the shared run dispatch narrates the uid.
|
|
9547
|
+
* Shared by the row's minus span and the palette's Unstage button. The
|
|
9548
|
+
* guard mirrors {@link stageItem}: a run already in flight must never be
|
|
9010
9549
|
* overlapped (the disabled states cover pointer clicks; this covers
|
|
9011
9550
|
* every call path). */
|
|
9012
9551
|
unstageItem(item) {
|
|
@@ -9014,103 +9553,7 @@ function defineDoorstopPanelBodyElement() {
|
|
|
9014
9553
|
if (controller === void 0 || controller.runInProgress !== void 0) return;
|
|
9015
9554
|
void controller.runGitUnstage([item.path], `Git: unstage ${item.uid}`);
|
|
9016
9555
|
}
|
|
9017
|
-
/**
|
|
9018
|
-
* free-text Unlink button dispatches, but with the target sourced from
|
|
9019
|
-
* the index (`link.uid`) instead of an input. The guard mirrors
|
|
9020
|
-
* {@link stageItem}: a run already in flight must never be overlapped.
|
|
9021
|
-
* There is no `targetError` interaction: an index-sourced UID cannot
|
|
9022
|
-
* fail the free-text validation, and `parseDoorstopRunRequest`
|
|
9023
|
-
* re-validates server-side. */
|
|
9024
|
-
unlinkLink(item, target) {
|
|
9025
|
-
const controller = this.controller;
|
|
9026
|
-
if (controller === void 0 || controller.runInProgress !== void 0) return;
|
|
9027
|
-
this.runDoorstop(
|
|
9028
|
-
"unlink",
|
|
9029
|
-
`Doorstop: unlink ${item.uid}`,
|
|
9030
|
-
{ op: "unlink", uid: item.uid, target },
|
|
9031
|
-
`doorstop unlink ${item.uid} ${target}`,
|
|
9032
|
-
false
|
|
9033
|
-
);
|
|
9034
|
-
}
|
|
9035
|
-
/**
|
|
9036
|
-
* The browser must never send an empty commit message: the server
|
|
9037
|
-
* rejects it, and git with an empty `-m` would hang the exec until the
|
|
9038
|
-
* deadline. The input is cleared only on SUCCESS so a `failed` commit
|
|
9039
|
-
* (hook stderr, missing identity) keeps the message for a corrected
|
|
9040
|
-
* retry.
|
|
9041
|
-
*/
|
|
9042
|
-
gitCommitSubmit() {
|
|
9043
|
-
const controller = this.controller;
|
|
9044
|
-
if (controller === void 0) return;
|
|
9045
|
-
if (controller.runInProgress !== void 0) return;
|
|
9046
|
-
const input = this.gitCommitInputRef.value;
|
|
9047
|
-
const message = input?.value.trim() ?? "";
|
|
9048
|
-
if (message === "") {
|
|
9049
|
-
this.gitActionError = "Enter a commit message";
|
|
9050
|
-
input?.focus();
|
|
9051
|
-
return;
|
|
9052
|
-
}
|
|
9053
|
-
this.gitActionError = void 0;
|
|
9054
|
-
void controller.runGitCommit(message).then(() => {
|
|
9055
|
-
const lastRun = controller.lastRun;
|
|
9056
|
-
if (lastRun?.op === "git-commit" && lastRun.status === "ok" && input !== void 0) {
|
|
9057
|
-
input.value = "";
|
|
9058
|
-
this.gitCommitMessage = "";
|
|
9059
|
-
}
|
|
9060
|
-
});
|
|
9061
|
-
}
|
|
9062
|
-
reviewItem(item) {
|
|
9063
|
-
const commit = doorstopCommitAfterReview(this.result);
|
|
9064
|
-
this.runDoorstop(
|
|
9065
|
-
"review",
|
|
9066
|
-
`Doorstop: review ${item.uid}`,
|
|
9067
|
-
commit ? { op: "review", uid: item.uid, commit: true } : { op: "review", uid: item.uid },
|
|
9068
|
-
`doorstop review ${item.uid}`,
|
|
9069
|
-
false
|
|
9070
|
-
);
|
|
9071
|
-
}
|
|
9072
|
-
clearSuspects(item, suspectUids) {
|
|
9073
|
-
if (suspectUids.length === 0) return;
|
|
9074
|
-
this.runDoorstop(
|
|
9075
|
-
"clear",
|
|
9076
|
-
"Doorstop: clear suspect links",
|
|
9077
|
-
{ op: "clear", uid: item.uid, parents: suspectUids },
|
|
9078
|
-
`doorstop clear ${item.uid} ${suspectUids.join(" ")}`,
|
|
9079
|
-
false
|
|
9080
|
-
);
|
|
9081
|
-
}
|
|
9082
|
-
editItem(item) {
|
|
9083
|
-
this.runDoorstop(
|
|
9084
|
-
"edit",
|
|
9085
|
-
`Doorstop: edit ${item.uid}`,
|
|
9086
|
-
{ op: "edit", uid: item.uid },
|
|
9087
|
-
`doorstop edit ${item.uid}`,
|
|
9088
|
-
false
|
|
9089
|
-
);
|
|
9090
|
-
}
|
|
9091
|
-
runTargetOp(op, inputRef, item) {
|
|
9092
|
-
const input = inputRef.value;
|
|
9093
|
-
const target = input?.value.trim() ?? "";
|
|
9094
|
-
if (target === "") {
|
|
9095
|
-
this.targetError = `Enter a ${op} target UID (e.g. ${item.documentPrefix}0001)`;
|
|
9096
|
-
input?.focus();
|
|
9097
|
-
return;
|
|
9098
|
-
}
|
|
9099
|
-
if (!isValidTargetUid(target)) {
|
|
9100
|
-
this.targetError = `Invalid ${op} target "${target}" \u2014 expected a Doorstop UID like ${item.documentPrefix}0001 (no spaces, quotes, or special characters)`;
|
|
9101
|
-
input?.focus();
|
|
9102
|
-
return;
|
|
9103
|
-
}
|
|
9104
|
-
this.targetError = void 0;
|
|
9105
|
-
if (input !== void 0) input.value = "";
|
|
9106
|
-
this.runDoorstop(
|
|
9107
|
-
op,
|
|
9108
|
-
`Doorstop: ${op} ${item.uid}`,
|
|
9109
|
-
{ op, uid: item.uid, target },
|
|
9110
|
-
`doorstop ${op} ${item.uid} ${target}`,
|
|
9111
|
-
false
|
|
9112
|
-
);
|
|
9113
|
-
}
|
|
9556
|
+
/** section-API: insert an Ask-agent prompt and close the menu. */
|
|
9114
9557
|
insertPrompt(text) {
|
|
9115
9558
|
const context = this.context;
|
|
9116
9559
|
if (context === void 0) return;
|
|
@@ -9121,9 +9564,9 @@ function defineDoorstopPanelBodyElement() {
|
|
|
9121
9564
|
/**
|
|
9122
9565
|
* The single dispatch surface of every panel action, with TWO paths:
|
|
9123
9566
|
*
|
|
9124
|
-
* -
|
|
9125
|
-
* with an active
|
|
9126
|
-
* `context.
|
|
9567
|
+
* - PEER path when the workspace is owned by the opendoor provider
|
|
9568
|
+
* with an active peer: the STRUCTURED `input` goes through
|
|
9569
|
+
* `context.peer.request("doorstop.run", …)` — argv is built
|
|
9127
9570
|
* server-side, so the browser never shell-quotes on this path. A
|
|
9128
9571
|
* rejected request commits `status: "error"` and does NOT
|
|
9129
9572
|
* invalidate (the run wrote nothing to the workspace).
|
|
@@ -9139,8 +9582,8 @@ function defineDoorstopPanelBodyElement() {
|
|
|
9139
9582
|
const controller = this.controller;
|
|
9140
9583
|
if (context === void 0 || controller === void 0) return;
|
|
9141
9584
|
if (controller.runInProgress !== void 0) return;
|
|
9142
|
-
if (context.
|
|
9143
|
-
void this.runDoorstopBackend(op, title, input, context.
|
|
9585
|
+
if (context.peer?.request !== void 0 && context.workspace.provider?.pluginId === OPENDOOR_PLUGIN_ID) {
|
|
9586
|
+
void this.runDoorstopBackend(op, title, input, context.peer);
|
|
9144
9587
|
return;
|
|
9145
9588
|
}
|
|
9146
9589
|
const terminal = context.terminal;
|
|
@@ -9154,13 +9597,15 @@ function defineDoorstopPanelBodyElement() {
|
|
|
9154
9597
|
}).catch(() => {
|
|
9155
9598
|
});
|
|
9156
9599
|
}
|
|
9157
|
-
async runDoorstopBackend(op, title, input,
|
|
9600
|
+
async runDoorstopBackend(op, title, input, peer) {
|
|
9158
9601
|
const controller = this.controller;
|
|
9159
9602
|
if (controller === void 0) return;
|
|
9603
|
+
const request = peer.request;
|
|
9604
|
+
if (request === void 0) return;
|
|
9160
9605
|
controller.beginRun(title);
|
|
9161
9606
|
const startedAt = Date.now();
|
|
9162
9607
|
try {
|
|
9163
|
-
const response = await
|
|
9608
|
+
const response = await request(DOORSTOP_RUN_OPERATION, input);
|
|
9164
9609
|
const parsed = parseDoorstopRunResponse(response);
|
|
9165
9610
|
const status = parsed.signal !== null ? "killed" : parsed.exitCode === 0 ? "ok" : "failed";
|
|
9166
9611
|
controller.commitRun({
|
|
@@ -9200,12 +9645,21 @@ function defineDoorstopPanelBodyElement() {
|
|
|
9200
9645
|
controller.endRun();
|
|
9201
9646
|
}
|
|
9202
9647
|
}
|
|
9648
|
+
/** section-API: the currently selected item, resolved against the
|
|
9649
|
+
* loaded index. */
|
|
9203
9650
|
selectedItem() {
|
|
9204
9651
|
const result = this.result;
|
|
9205
9652
|
const uid = this.selectedUid;
|
|
9206
9653
|
if (result === void 0 || uid === void 0) return void 0;
|
|
9207
9654
|
return result.index.byUid.get(uid);
|
|
9208
9655
|
}
|
|
9656
|
+
/** section-API: the shared state chip (item rows + detail head). The
|
|
9657
|
+
* renderer is a free function in `sections/item-list.ts`; this delegate
|
|
9658
|
+
* lets detail-pane reach it through the host, keeping the runtime import
|
|
9659
|
+
* graph one-way (coordinator → sections). */
|
|
9660
|
+
renderStateChip(key) {
|
|
9661
|
+
return renderStateChip(key);
|
|
9662
|
+
}
|
|
9209
9663
|
}
|
|
9210
9664
|
__decorateClass([
|
|
9211
9665
|
n6({ attribute: false })
|
|
@@ -9285,18 +9739,19 @@ function defineCustomElementOnce(tag, define) {
|
|
|
9285
9739
|
define();
|
|
9286
9740
|
}
|
|
9287
9741
|
|
|
9288
|
-
// src/doorstop-contributions.ts
|
|
9289
|
-
var
|
|
9742
|
+
// src/browser/doorstop-contributions.ts
|
|
9743
|
+
var DOORSTOP_PANEL_LOCAL_ID2 = "workspace.doorstop";
|
|
9290
9744
|
var DOORSTOP_LABEL_STATE_LIMIT = 8;
|
|
9291
9745
|
function createOpendoorBrowserContributions(runtimePluginId, _html, _svg) {
|
|
9292
|
-
const panelId = `${runtimePluginId}:${
|
|
9746
|
+
const panelId = `${runtimePluginId}:${DOORSTOP_PANEL_LOCAL_ID2}`;
|
|
9293
9747
|
const registry = new DoorstopWorkspaceRegistry();
|
|
9294
9748
|
const labelCache = new DoorstopLabelCache();
|
|
9295
9749
|
defineDoorstopPanelElements();
|
|
9296
9750
|
return {
|
|
9297
9751
|
actions: createDoorstopActions(panelId),
|
|
9298
9752
|
workspacePanels: [createDoorstopPanel(registry)],
|
|
9299
|
-
workspaceLabels: [createDoorstopLabel(labelCache)]
|
|
9753
|
+
workspaceLabels: [createDoorstopLabel(labelCache)],
|
|
9754
|
+
sessionLabels: [createDoorstopSessionLabel(runtimePluginId)]
|
|
9300
9755
|
};
|
|
9301
9756
|
}
|
|
9302
9757
|
function createDoorstopActions(panelId) {
|
|
@@ -9315,7 +9770,7 @@ function createDoorstopActions(panelId) {
|
|
|
9315
9770
|
// Enabled unconditionally: the Requirements panel is useful for every
|
|
9316
9771
|
// workspace (paired or unpaired — it needs no owned provider).
|
|
9317
9772
|
run: (context) => {
|
|
9318
|
-
context.
|
|
9773
|
+
context.selectWorkspaceTool(panelId);
|
|
9319
9774
|
}
|
|
9320
9775
|
},
|
|
9321
9776
|
{
|
|
@@ -9336,7 +9791,7 @@ function createDoorstopActions(panelId) {
|
|
|
9336
9791
|
}
|
|
9337
9792
|
function createDoorstopPanel(registry) {
|
|
9338
9793
|
return {
|
|
9339
|
-
id:
|
|
9794
|
+
id: DOORSTOP_PANEL_LOCAL_ID2,
|
|
9340
9795
|
title: "Requirements",
|
|
9341
9796
|
// Door/archive icon (currentColor): a filing cabinet with the doorstop
|
|
9342
9797
|
// checklist mark — the panel title icon, created with this module's own
|
|
@@ -9348,18 +9803,53 @@ function createDoorstopPanel(registry) {
|
|
|
9348
9803
|
<path d="m9 12 2 2 4-4"></path>
|
|
9349
9804
|
</svg>
|
|
9350
9805
|
`,
|
|
9351
|
-
order:
|
|
9806
|
+
// Plan Phase C step C3 follow-up — order 5: the host's chat-file-link
|
|
9807
|
+
// dispatch (resolveWorkspaceFileOpen) opens the FIRST ACCEPTING panel,
|
|
9808
|
+
// iterating panels by `order` ascending, and the bundled Files panel is
|
|
9809
|
+
// order 10 and answers `{ file: path }` for EVERY path unconditionally —
|
|
9810
|
+
// at order 60 this panel's `fileOpenQuery` was never consulted at all.
|
|
9811
|
+
// Sitting below 10 is the only host-supported way to claim doorstop item
|
|
9812
|
+
// files for the Requirements panel; the accepted consequence is that the
|
|
9813
|
+
// Requirements tab precedes Files in the workspace tab bar.
|
|
9814
|
+
order: 5,
|
|
9352
9815
|
// The panel is always visible (paired or unpaired): when the workspace
|
|
9353
9816
|
// is opendoor-owned the run actions use the backend, otherwise the
|
|
9354
9817
|
// terminal fallback keeps every action working; the discovery-based
|
|
9355
9818
|
// empty state carries the "no documents" story (feature spec §7.1).
|
|
9356
9819
|
visible: () => true,
|
|
9357
|
-
|
|
9820
|
+
// Plan Phase C step C1 — typed invalidation (browser API v4): declare the
|
|
9821
|
+
// fixed workspace resources whose automatic invalidations this panel
|
|
9822
|
+
// receives. With `workspace.files` declared, the host calls onInvalidate
|
|
9823
|
+
// not only for the manual Refresh action (refreshWorkspacePanels) but
|
|
9824
|
+
// also when workspace files change (`reason: "mutation"` — e.g. the
|
|
9825
|
+
// review-commit pipeline's commits landing, or any other plugin's file
|
|
9826
|
+
// writes) and when agent work finishes in the workspace (`reason:
|
|
9827
|
+
// "agent-activity"` — an agent editing doorstop documents refreshes the
|
|
9828
|
+
// panel without a manual Refresh). The reason is forwarded to the
|
|
9829
|
+
// registry, which rescans unconditionally (see
|
|
9830
|
+
// DoorstopWorkspaceRegistry.invalidate).
|
|
9831
|
+
invalidationResources: ["workspace.files"],
|
|
9832
|
+
onInvalidate: (context, invalidation) => registry.invalidate(context, invalidation),
|
|
9833
|
+
// Plan Phase C step C3 — chat file links: when the host resolves a
|
|
9834
|
+
// Markdown link to a workspace-relative path, this hook answers with the
|
|
9835
|
+
// panel's navigation query IF the panel owns the path (its last landed
|
|
9836
|
+
// discovery indexed it as an item file), so the chat opens the item IN
|
|
9837
|
+
// the Requirements panel instead of the Files fallback. Synchronous and
|
|
9838
|
+
// side-effect-free: it answers purely from the registry's recorded index
|
|
9839
|
+
// (never loads, never renders); unknown paths and pre-first-load panels
|
|
9840
|
+
// return undefined (the host asks the next panel / serves Files).
|
|
9841
|
+
fileOpenQuery: (context, path) => registry.fileOpenQuery(context, path),
|
|
9358
9842
|
render: (context) => renderDoorstopPanel(registry, context)
|
|
9359
9843
|
};
|
|
9360
9844
|
}
|
|
9361
9845
|
function renderDoorstopPanel(registry, context) {
|
|
9362
9846
|
const controller = registry.for(context);
|
|
9847
|
+
if (context.navigation !== void 0) {
|
|
9848
|
+
controller.applyNavigationQuery(readPanelNavigationQuery(context.navigation));
|
|
9849
|
+
}
|
|
9850
|
+
if (controller.result !== void 0) {
|
|
9851
|
+
registry.recordFileOpenIndex(context, controller.result);
|
|
9852
|
+
}
|
|
9363
9853
|
return b2`
|
|
9364
9854
|
<pi-web-opendoor-panel-body
|
|
9365
9855
|
.controller=${controller}
|
|
@@ -9458,12 +9948,12 @@ function doorstopLabelKey(context) {
|
|
|
9458
9948
|
return JSON.stringify([context.machine.id, context.workspace.projectId, context.workspace.id]);
|
|
9459
9949
|
}
|
|
9460
9950
|
|
|
9461
|
-
// src/pi-web-plugin.ts
|
|
9951
|
+
// src/browser/pi-web-plugin.ts
|
|
9462
9952
|
var plugin = {
|
|
9463
|
-
apiVersion:
|
|
9953
|
+
apiVersion: 4,
|
|
9464
9954
|
name: "Opendoor",
|
|
9465
|
-
activate: ({ runtimePluginId, html, svg
|
|
9466
|
-
contributions: createOpendoorBrowserContributions(runtimePluginId, html,
|
|
9955
|
+
activate: ({ runtimePluginId, html, svg }) => ({
|
|
9956
|
+
contributions: createOpendoorBrowserContributions(runtimePluginId, html, svg)
|
|
9467
9957
|
})
|
|
9468
9958
|
};
|
|
9469
9959
|
var pi_web_plugin_default = plugin;
|
|
@@ -9484,7 +9974,6 @@ lit-html/lit-html.js:
|
|
|
9484
9974
|
lit-element/lit-element.js:
|
|
9485
9975
|
lit-html/directive.js:
|
|
9486
9976
|
lit-html/async-directive.js:
|
|
9487
|
-
lit-html/directives/repeat.js:
|
|
9488
9977
|
@lit/reactive-element/decorators/custom-element.js:
|
|
9489
9978
|
@lit/reactive-element/decorators/property.js:
|
|
9490
9979
|
@lit/reactive-element/decorators/state.js:
|
|
@@ -9494,6 +9983,7 @@ lit-html/directives/repeat.js:
|
|
|
9494
9983
|
@lit/reactive-element/decorators/query-all.js:
|
|
9495
9984
|
@lit/reactive-element/decorators/query-async.js:
|
|
9496
9985
|
@lit/reactive-element/decorators/query-assigned-nodes.js:
|
|
9986
|
+
lit-html/directives/repeat.js:
|
|
9497
9987
|
(**
|
|
9498
9988
|
* @license
|
|
9499
9989
|
* Copyright 2017 Google LLC
|
|
@@ -9507,13 +9997,6 @@ lit-html/is-server.js:
|
|
|
9507
9997
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
9508
9998
|
*)
|
|
9509
9999
|
|
|
9510
|
-
lit-html/directives/class-map.js:
|
|
9511
|
-
(**
|
|
9512
|
-
* @license
|
|
9513
|
-
* Copyright 2018 Google LLC
|
|
9514
|
-
* SPDX-License-Identifier: BSD-3-Clause
|
|
9515
|
-
*)
|
|
9516
|
-
|
|
9517
10000
|
lit-html/directive-helpers.js:
|
|
9518
10001
|
lit-html/directives/ref.js:
|
|
9519
10002
|
(**
|
|
@@ -9522,11 +10005,18 @@ lit-html/directives/ref.js:
|
|
|
9522
10005
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
9523
10006
|
*)
|
|
9524
10007
|
|
|
9525
|
-
lit-html/directives/keyed.js:
|
|
9526
10008
|
@lit/reactive-element/decorators/query-assigned-elements.js:
|
|
10009
|
+
lit-html/directives/keyed.js:
|
|
9527
10010
|
(**
|
|
9528
10011
|
* @license
|
|
9529
10012
|
* Copyright 2021 Google LLC
|
|
9530
10013
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
9531
10014
|
*)
|
|
10015
|
+
|
|
10016
|
+
lit-html/directives/class-map.js:
|
|
10017
|
+
(**
|
|
10018
|
+
* @license
|
|
10019
|
+
* Copyright 2018 Google LLC
|
|
10020
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
10021
|
+
*)
|
|
9532
10022
|
*/
|