feat(sources): removing a source deletes its files and index entries behind a confirmation modal
This commit is contained in:
+223
-45
@@ -66,24 +66,59 @@
|
||||
* branch): a running scan re-enters the processing state + poll
|
||||
* (a reload mid-scan re-attaches — no second upload), a terminal
|
||||
* run re-renders its result line / error banner.
|
||||
* • remove — a row's Remove button asks window.confirm first
|
||||
* (removal prunes the documents only on the NEXT sync — the
|
||||
* confirm says so). Cancel → nothing; ok → the row button
|
||||
* disables, DELETE /api/git-sources/{id}, loadSources(). A
|
||||
* failure shows a per-row role="alert" error and re-enables the
|
||||
* button. Env-fallback rows (id null — the list comes from
|
||||
* BOR_GIT_SOURCES, not the table) carry no Remove: nothing is
|
||||
* stored to remove — they show a "from .env" tag instead.
|
||||
* • remove — a row's Remove button opens the page-local
|
||||
* confirmation modal (#remove-confirm-dialog, a real
|
||||
* role="alertdialog" — the native confirm() retired, phase 69):
|
||||
* it
|
||||
* names the source (#remove-confirm-source, textContent ONLY —
|
||||
* URLs may embed user:pass@ credentials, phase 32) and states
|
||||
* the full-removal policy — the row, the source's indexed
|
||||
* documents (chunks + embeddings), and, for git clones and
|
||||
* uploaded archives, the files on the server's disk, all removed
|
||||
* immediately by the server-side DELETE. Cancel is the safe
|
||||
* default: focus lands on Cancel at open; Escape, the Cancel
|
||||
* button, and the dim backdrop all close as CANCEL (no request —
|
||||
* focus returns to the row's Remove button). "Remove source" runs
|
||||
* the §7.4 in-flight lifecycle IN the modal: both buttons
|
||||
* disable + the confirm relabels "Removing…" while the DELETE is
|
||||
* out — the in-flight window covers the whole server-side
|
||||
* cleanup (DB prune → file removal → best-effort overview
|
||||
* refresh; a slow LLM refresh is expected, not a stuck button —
|
||||
* the same "wait for the terminal state" pattern as the
|
||||
* Sync/Upload processing states). Navigating away mid-removal is
|
||||
* not recommended: the row + index commit first, so the KB stays
|
||||
* consistent; a rare interrupted file step leaves an inert orphan
|
||||
* dir (no row → never imported again). 204 → close (focus
|
||||
* return), loadSources(), then announce — the removal
|
||||
* confirmation is the LAST announcement, so the reload's "N
|
||||
* sources listed." cannot overwrite it (the announcer is the
|
||||
* screen-reader confirmation for the destructive action);
|
||||
* non-2xx → the in-modal role="alert" line (the server detail,
|
||||
* apiDetail) + both
|
||||
* buttons re-enabled + the confirm relabeled "Remove source" (the
|
||||
* dialog stays open — the fix is one retry, not a re-search for
|
||||
* the row); network failure → the fixed "is the app reachable?"
|
||||
* line, same restore. Env-fallback rows (id null — the list
|
||||
* comes from BOR_GIT_SOURCES, not the table) carry no Remove:
|
||||
* nothing is stored to remove — they show a "from .env" tag
|
||||
* instead.
|
||||
* • announce(msg) — #git-sources-announcer (role=status,
|
||||
* aria-live=polite): the screen-reader confirmation for loads,
|
||||
* adds, and removals.
|
||||
*
|
||||
* Scope boundary (phase locked decisions): adding a git repo or
|
||||
* removing a source does NOT clone, import, or prune — the sync
|
||||
* service (server-side) performs that; the page's hint box says so.
|
||||
* The phase-49 upload is the exception: it unpacks and scans the
|
||||
* single source in place (the phase-64 background task — 202 +
|
||||
* status endpoint), and its counts render as the result line.
|
||||
* Scope boundary (phase locked decisions): adding a git repo does
|
||||
* NOT clone — the sync service (server-side) does that. Removing a
|
||||
* source, however, performs the FULL cleanup server-side (phase 69):
|
||||
* the row, the source's indexed documents (chunks + embeddings), and
|
||||
* — for git clones and uploaded archives — the app-managed files on
|
||||
* disk (foreign local directories are never touched), all in one
|
||||
* action; the confirmation modal states exactly that, and the
|
||||
* page's hint box matches. The Sync button still mirrors the
|
||||
* remaining sources (upstream file churn is pruned on that run).
|
||||
* The phase-49 upload is the other in-place exception: it unpacks
|
||||
* and scans the single source in place (the phase-64 background task
|
||||
* — 202 + status endpoint), and its counts render as the result
|
||||
* line.
|
||||
*
|
||||
* The shared header module loads through this script's own relative
|
||||
* import ("./header.js") — a hoisted import evaluated before this body
|
||||
@@ -116,6 +151,15 @@ const tbody = document.querySelector("#git-sources-tbody");
|
||||
const emptyEl = document.querySelector("#git-sources-empty");
|
||||
const envNote = document.querySelector("#git-sources-env-note");
|
||||
const announcer = document.querySelector("#git-sources-announcer");
|
||||
/* Phase 69: the remove confirmation modal (the native confirm()
|
||||
retired) — static markup in git-sources.html; this module owns the
|
||||
open / cancel / confirm lifecycle. */
|
||||
const removeDialog = document.querySelector("#remove-confirm-dialog");
|
||||
const removeBackdrop = document.querySelector(".remove-confirm-backdrop");
|
||||
const removeSourceEl = document.querySelector("#remove-confirm-source");
|
||||
const removeError = document.querySelector("#remove-confirm-error");
|
||||
const removeCancelBtn = document.querySelector("#remove-confirm-cancel");
|
||||
const removeRemoveBtn = document.querySelector("#remove-confirm-remove");
|
||||
|
||||
/* Polite live region: the screen-reader confirmation for loads, adds,
|
||||
and removals (the phase-15 announcer pattern). */
|
||||
@@ -248,12 +292,12 @@ function makeRow(s) {
|
||||
btn.className = "git-source-remove";
|
||||
btn.setAttribute("aria-label", `Remove ${kindLabel} source: ${value}`);
|
||||
btn.innerHTML = REMOVE_ICON + "<span>Remove</span>";
|
||||
const rowError = document.createElement("span");
|
||||
rowError.className = "git-source-row-error";
|
||||
rowError.setAttribute("role", "alert");
|
||||
rowError.hidden = true;
|
||||
btn.addEventListener("click", () => removeSource(s, btn, rowError, kindLabel));
|
||||
actTd.append(btn, rowError);
|
||||
// Phase 69: opens the confirmation modal (the native confirm()
|
||||
// retired) — the modal names the source and states the
|
||||
// full-removal policy; the per-row error span is retired (the
|
||||
// modal carries the in-flight error line).
|
||||
btn.addEventListener("click", () => openRemoveConfirm(s, btn));
|
||||
actTd.appendChild(btn);
|
||||
} else {
|
||||
const tag = document.createElement("span");
|
||||
tag.className = "git-source-env-tag";
|
||||
@@ -264,35 +308,169 @@ function makeRow(s) {
|
||||
return tr;
|
||||
}
|
||||
|
||||
/* ---------- remove (DELETE /api/git-sources/{id}) ----------
|
||||
* Removal does not prune anything immediately — the next sync does
|
||||
* (phase scope boundary), so the confirm says exactly that. Cancel →
|
||||
* nothing; a failed delete → per-row role="alert" error + re-enabled
|
||||
* button (never a stuck row); success → the list reloads. */
|
||||
async function removeSource(s, btn, rowError, kindLabel) {
|
||||
const ok = window.confirm(
|
||||
`Remove this ${kindLabel} source from the list? Its documents stay indexed until the next sync prunes them.`,
|
||||
);
|
||||
if (!ok) return;
|
||||
btn.disabled = true; // one delete per click
|
||||
rowError.hidden = true;
|
||||
try {
|
||||
const r = await fetch(`/api/git-sources/${encodeURIComponent(s.id)}`, { method: "DELETE" });
|
||||
if (!r.ok) {
|
||||
rowError.textContent = await apiDetail(r, `Could not remove the ${kindLabel} source — try again.`);
|
||||
rowError.hidden = false;
|
||||
btn.disabled = false;
|
||||
return;
|
||||
/* ---------- remove (DELETE /api/git-sources/{id}) — the confirmation modal ----------
|
||||
* A row's Remove button opens the page-local alertdialog
|
||||
* (#remove-confirm-dialog — the native confirm() retired, phase 69)
|
||||
* via openRemoveConfirm(s, triggerBtn): #remove-confirm-source shows the
|
||||
* row's value (textContent ONLY — the same `value` expression
|
||||
* makeRow uses: s.path ?? s.url for local rows, s.url for git — URLs
|
||||
* may embed user:pass@ credentials, phase 32), the error line clears,
|
||||
* and focus lands on Cancel (the safe default for a destructive
|
||||
* action). Escape / Cancel / the dim backdrop close as CANCEL: no
|
||||
* request, focus returns to the row's Remove button.
|
||||
*
|
||||
* "Remove source" (confirmRemove) runs the §7.4 never-stale
|
||||
* lifecycle IN the modal: both buttons disable and the confirm
|
||||
* relabels "Removing…" while the DELETE is out — the in-flight
|
||||
* window covers the whole server-side cleanup (DB prune → file
|
||||
* removal → best-effort overview refresh), so a slow LLM refresh is
|
||||
* expected, not a stuck button. Navigating away mid-removal is not
|
||||
* recommended: the row + index commit first, so the KB stays
|
||||
* consistent; a rare interrupted file step leaves an inert orphan
|
||||
* dir (no row → never imported again). 204 → close (focus return),
|
||||
* loadSources(), then announce — the removal confirmation is the
|
||||
* LAST announcement (the reload's "N sources listed." must not
|
||||
* overwrite it — the announcer is the screen-reader confirmation for
|
||||
* the destructive action); non-2xx → the in-modal role="alert" line
|
||||
* (the server detail, apiDetail 422-shape-aware) + both buttons
|
||||
* re-enabled + the confirm relabeled "Remove source" (the dialog
|
||||
* stays open — the fix is one retry, not a re-search for the row);
|
||||
* network failure → the fixed reachable? line, same restore. */
|
||||
let removeTriggerBtn = null; // the row's Remove button — focus returns here on close
|
||||
let removeInFlight = false; // §7.4: a DELETE is out (both buttons disabled)
|
||||
let removingId = null; // the row id of the open/in-flight removal
|
||||
|
||||
function openRemoveConfirm(s, triggerBtn) {
|
||||
if (!removeDialog || !removeSourceEl) return; // defensive — the markup ships with the page
|
||||
if (removeInFlight) return; // one removal at a time
|
||||
const isLocal = s.kind === "local";
|
||||
// The same `value` expression makeRow uses — textContent ONLY.
|
||||
removeSourceEl.textContent = isLocal ? s.path ?? s.url : s.url;
|
||||
if (removeError) {
|
||||
removeError.textContent = "";
|
||||
removeError.hidden = true; // a new attempt starts clean
|
||||
}
|
||||
removingId = s.id;
|
||||
removeInFlight = false;
|
||||
removeTriggerBtn = triggerBtn; // recorded for the focus return on close
|
||||
removeDialog.hidden = false;
|
||||
document.addEventListener("keydown", onRemoveDialogKeydown);
|
||||
// Cancel is the safe default for a destructive action — focus
|
||||
// lands on it (visibly: the global 3px :focus-visible outline).
|
||||
if (removeCancelBtn) removeCancelBtn.focus();
|
||||
}
|
||||
|
||||
/* Any close (cancel, success): hide the dialog, clear the error
|
||||
line, reset the buttons, detach the keydown handling, and return
|
||||
focus to the row's Remove button (WCAG 2.1). */
|
||||
function closeRemoveConfirm() {
|
||||
if (!removeDialog) return;
|
||||
removeDialog.hidden = true;
|
||||
removeInFlight = false;
|
||||
removingId = null;
|
||||
if (removeError) {
|
||||
removeError.textContent = "";
|
||||
removeError.hidden = true;
|
||||
}
|
||||
if (removeCancelBtn) removeCancelBtn.disabled = false;
|
||||
if (removeRemoveBtn) {
|
||||
removeRemoveBtn.disabled = false;
|
||||
removeRemoveBtn.textContent = "Remove source";
|
||||
}
|
||||
document.removeEventListener("keydown", onRemoveDialogKeydown);
|
||||
const trigger = removeTriggerBtn;
|
||||
removeTriggerBtn = null;
|
||||
if (trigger) trigger.focus(); // focus returns to the row's Remove button
|
||||
}
|
||||
|
||||
/* Escape / Cancel / backdrop all close as cancel — NO request. A
|
||||
cancel is a no-op while a DELETE is in flight (no half-cancel of
|
||||
an in-progress server-side removal; the buttons are disabled
|
||||
anyway, the Escape/backdrop paths need this guard). */
|
||||
function cancelRemoveConfirm() {
|
||||
if (removeInFlight) return;
|
||||
closeRemoveConfirm();
|
||||
}
|
||||
|
||||
/* While open (attached in openRemoveConfirm, detached in
|
||||
closeRemoveConfirm): Escape cancels; Tab/Shift+Tab cycle between
|
||||
the modal's two buttons (the only focusable elements — aria-modal
|
||||
is honored for keyboard users, not just screen readers). */
|
||||
function onRemoveDialogKeydown(e) {
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
cancelRemoveConfirm();
|
||||
return;
|
||||
}
|
||||
if (e.key === "Tab" && removeCancelBtn && removeRemoveBtn) {
|
||||
const leaving = e.shiftKey ? removeCancelBtn : removeRemoveBtn;
|
||||
const wrapTo = e.shiftKey ? removeRemoveBtn : removeCancelBtn;
|
||||
if (document.activeElement === leaving) {
|
||||
e.preventDefault();
|
||||
wrapTo.focus();
|
||||
}
|
||||
announce("Source removed.");
|
||||
await loadSources(); // 204: the server confirmed — the list re-renders
|
||||
} catch {
|
||||
rowError.textContent = `Could not remove the ${kindLabel} source — is the app reachable?`;
|
||||
rowError.hidden = false;
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmRemove() {
|
||||
if (!removingId || removeInFlight) return; // one request at a time
|
||||
removeInFlight = true;
|
||||
if (removeError) {
|
||||
removeError.textContent = "";
|
||||
removeError.hidden = true;
|
||||
}
|
||||
if (removeCancelBtn) removeCancelBtn.disabled = true;
|
||||
if (removeRemoveBtn) {
|
||||
removeRemoveBtn.disabled = true;
|
||||
removeRemoveBtn.textContent = "Removing…"; // §7.4 in-flight label
|
||||
}
|
||||
try {
|
||||
const r = await fetch(`/api/git-sources/${encodeURIComponent(removingId)}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
if (r.ok) {
|
||||
// 204: the server confirmed the total removal (row + index +
|
||||
// app-managed files).
|
||||
closeRemoveConfirm(); // focus returns to the row's Remove button
|
||||
await loadSources(); // the row leaves the table
|
||||
// The removal confirmation is the LAST announcement: the
|
||||
// reload's "N sources listed." must not overwrite it (the
|
||||
// announcer is the screen-reader confirmation for the
|
||||
// destructive action — phase 69 task 03's E2E pins the success
|
||||
// line on the announcer after a real removal).
|
||||
announce("Source removed — its files and index entries were cleaned up.");
|
||||
return;
|
||||
}
|
||||
// non-2xx: the in-modal role="alert" line (the server detail) —
|
||||
// the dialog STAYS open: the fix is one retry, not a re-search
|
||||
// for the row.
|
||||
if (removeError) {
|
||||
removeError.textContent = await apiDetail(r, "Could not remove the source — try again.");
|
||||
removeError.hidden = false;
|
||||
}
|
||||
} catch {
|
||||
if (removeError) {
|
||||
removeError.textContent = "Could not remove the source — is the app reachable?";
|
||||
removeError.hidden = false;
|
||||
}
|
||||
} finally {
|
||||
// Never stale (PLAN §7.4): the failure paths re-enable BOTH
|
||||
// buttons + relabel the confirm; the success path already closed
|
||||
// the dialog (which resets them) — the restore is a no-op there.
|
||||
removeInFlight = false;
|
||||
if (removeCancelBtn) removeCancelBtn.disabled = false;
|
||||
if (removeRemoveBtn) {
|
||||
removeRemoveBtn.disabled = false;
|
||||
removeRemoveBtn.textContent = "Remove source";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The modal's own buttons (static markup — wired once). */
|
||||
if (removeCancelBtn) removeCancelBtn.addEventListener("click", cancelRemoveConfirm);
|
||||
if (removeBackdrop) removeBackdrop.addEventListener("click", cancelRemoveConfirm);
|
||||
if (removeRemoveBtn) removeRemoveBtn.addEventListener("click", confirmRemove);
|
||||
|
||||
/* ---------- add (POST /api/git-sources) — the git form ----------
|
||||
* wireAddForm gives the form the §7.4 never-stale lifecycle: while
|
||||
* the request is out the button disables + relabels "Adding…" and
|
||||
|
||||
+141
-13
@@ -1915,6 +1915,147 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
/* ---------- Remove confirmation modal (phase 69, task 02) ----------
|
||||
/git-sources.html: the in-app confirmation that replaces the
|
||||
native confirm() — the total-removal warning (the row, the
|
||||
source's indexed documents, and — for git clones and uploaded
|
||||
archives — the files on the server's disk). The .doc-modal overlay contract:
|
||||
a fixed full-viewport dim backdrop + a centered panel (z-index
|
||||
1000, above the sticky header (20) + skip-link (100); NO blur —
|
||||
the phase-08 no-blur perf anchor), scaled to a compact dialog:
|
||||
the 46rem chat-column width or the viewport, whichever is
|
||||
narrower. Phase-08 tokens only; system fonts; no CDN.
|
||||
|
||||
AA pairs: title/copy are --ink on --surface (13.8:1); the source
|
||||
value is --ink on --bg (16.7:1); the error line is the err pair
|
||||
(err-ink on err-bg 9.3:1, the err-line border); the destructive
|
||||
button rides the err token family — the .tuning-delete /
|
||||
.steering-delete hover convention used as the resting state
|
||||
(err-ink on err-bg 9.3:1; the hover inverts to dark --bg on
|
||||
--err-line, 5.2:1 — both AA); Cancel is the ghost ink-soft family
|
||||
(5.1:1 on --surface). :focus-visible via the global 3px outline
|
||||
rule (no local suppression — focus lands on Cancel at open and is
|
||||
visible); both buttons >=44px; no animation (reduced-motion safe
|
||||
by construction). */
|
||||
.remove-confirm {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1000;
|
||||
display: flex; /* the panel is the only in-flow child — margin: auto centers it */
|
||||
}
|
||||
/* Explicit (the global [hidden] rule already wins — the documented,
|
||||
testable contract for the skeleton). */
|
||||
.remove-confirm[hidden] { display: none; }
|
||||
|
||||
.remove-confirm-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
/* --bg at 82% — the doc-modal dim, no backdrop-filter (no-blur). */
|
||||
background: rgba(15, 10, 10, 0.82);
|
||||
}
|
||||
|
||||
.remove-confirm-panel {
|
||||
/* position:relative lifts the panel above the fixed backdrop
|
||||
(positioned elements paint over in-flow siblings otherwise). */
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin: auto;
|
||||
width: min(46rem, calc(100vw - 2rem));
|
||||
padding: 1.5rem;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.remove-confirm-title {
|
||||
margin: 0 0 0.75rem;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.3;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
/* The source's value (git URL or local path) — mono, wrapped (a long
|
||||
URL must not overflow the panel), --ink on --bg (16.7:1). */
|
||||
.remove-confirm-source {
|
||||
display: block;
|
||||
margin: 0 0 0.75rem;
|
||||
padding: 0.5rem 0.65rem;
|
||||
font-family: var(--mono);
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
color: var(--ink);
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-sm);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.remove-confirm-copy {
|
||||
margin: 0;
|
||||
color: var(--ink); /* 13.8:1 on --surface */
|
||||
}
|
||||
|
||||
/* The in-modal failure line (role=alert): the err pair (err-ink on
|
||||
err-bg 9.3:1, the err-line border) — the .git-source-error banner
|
||||
language, boxed. */
|
||||
.remove-confirm-error {
|
||||
margin: 0.75rem 0 0;
|
||||
padding: 0.5rem 0.65rem;
|
||||
color: var(--err-ink);
|
||||
background: var(--err-bg);
|
||||
border: 1px solid var(--err-line);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.remove-confirm-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.6rem;
|
||||
margin-top: 1.1rem;
|
||||
}
|
||||
|
||||
/* The two modal buttons: >=44px targets, the house 3px :focus-visible
|
||||
via the global outline rule (no local override). */
|
||||
.remove-confirm-btn {
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
padding: 0.55rem 1.1rem;
|
||||
border-radius: var(--radius-sm);
|
||||
font: inherit;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
.remove-confirm-btn:disabled { opacity: 0.5; cursor: wait; }
|
||||
|
||||
/* Cancel — the ghost ink-soft family (5.1:1 on --surface), like the
|
||||
row's Remove / .tuning-edit; the brand pair on hover (6.9:1). */
|
||||
.remove-confirm-cancel {
|
||||
border: 1px solid var(--line);
|
||||
background: transparent;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.remove-confirm-cancel:hover:not(:disabled) {
|
||||
background: var(--brand-soft);
|
||||
color: var(--brand-ink);
|
||||
}
|
||||
|
||||
/* "Remove source" — the destructive button on the err token family
|
||||
(the .tuning-delete / .steering-delete convention): err-ink on
|
||||
err-bg 9.3:1, the err-line border; the hover inverts to dark --bg
|
||||
on --err-line (5.2:1 — AA). */
|
||||
.remove-confirm-remove {
|
||||
border: 1px solid var(--err-line);
|
||||
background: var(--err-bg);
|
||||
color: var(--err-ink);
|
||||
}
|
||||
.remove-confirm-remove:hover:not(:disabled) {
|
||||
background: var(--err-line);
|
||||
color: var(--bg);
|
||||
}
|
||||
|
||||
/* The list: the Sources page's table pattern — full width in the
|
||||
72rem frame, surface card, horizontally scrollable wrapper (the
|
||||
URL column never wraps or ellipsizes: long URLs, credentials
|
||||
@@ -1998,19 +2139,6 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
.git-source-remove:hover:not(:disabled) { background: var(--err-bg); color: var(--err-ink); border-color: var(--err-line); }
|
||||
.git-source-remove:disabled { opacity: 0.5; cursor: wait; }
|
||||
|
||||
/* Per-row delete failure (role=alert): the err pair, inline after the
|
||||
(re-enabled) button. */
|
||||
.git-source-row-error {
|
||||
margin-left: 0.6rem;
|
||||
background: var(--err-bg);
|
||||
color: var(--err-ink);
|
||||
border: 1px solid var(--err-line);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.3rem 0.6rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Env-fallback rows carry no Remove (nothing is stored to remove) —
|
||||
the tag says where the row comes from (brand pair, 6.9:1). */
|
||||
.git-source-env-tag {
|
||||
|
||||
@@ -229,19 +229,67 @@
|
||||
active list comes from. -->
|
||||
<p class="git-sources-empty" id="git-sources-empty" hidden>No sources stored yet.</p>
|
||||
|
||||
<!-- Scope boundary (phase locked decision): adding/removing a
|
||||
source does NOT clone or prune — the Sync button performs
|
||||
that; the phase-49 upload is the exception (it unpacks and
|
||||
scans in place, and a same-name re-upload replaces the
|
||||
source in place). -->
|
||||
<!-- Phase 69 (owner request 2026-09-02): removal is a TOTAL
|
||||
removal — the row, the source's indexed documents, and —
|
||||
for git clones and uploaded archives — the files on the
|
||||
server's disk, all immediately (the confirmation modal
|
||||
below spells it out; foreign local directories are never
|
||||
touched). Adding still does not clone — the Sync button
|
||||
mirrors the remaining sources (upstream file churn is
|
||||
pruned on that run); the phase-49 upload is the
|
||||
in-place exception (it unpacks and scans, and a
|
||||
same-name re-upload replaces the source in place). -->
|
||||
<p class="git-source-hint" id="git-sources-hint" role="note">
|
||||
Removing a source is a total removal, done immediately: its
|
||||
entry, its indexed documents, and — for git clones and
|
||||
uploaded archives — its files on the server's disk (the
|
||||
confirmation modal spells out exactly what will be deleted;
|
||||
files in your own local directories are never touched).
|
||||
Uploads unpack and scan immediately — re-uploading the same
|
||||
filename replaces that source in place (no new folder, no
|
||||
duplicate row). The Sync button still imports the git
|
||||
checkouts and local directories together (files removed from
|
||||
a source are pruned) — removing a source prunes its documents
|
||||
from the index on the next sync.
|
||||
duplicate row). The Sync button still mirrors the remaining
|
||||
sources (files removed upstream are pruned on that run).
|
||||
</p>
|
||||
|
||||
<!-- Phase 69 (owner request 2026-09-02): the remove
|
||||
confirmation — a real in-app alertdialog (the native
|
||||
confirm() retired): a row's Remove button opens it
|
||||
(git-sources.js).
|
||||
It names the source (#remove-confirm-source — ALWAYS
|
||||
populated via textContent: URLs may embed user:pass@
|
||||
credentials, the phase-32 masking discipline) and states
|
||||
the full-removal policy. Focus lands on Cancel (the safe
|
||||
default for a destructive action); Escape, the Cancel
|
||||
button, and the dim backdrop all close as cancel (no
|
||||
request — focus returns to the row's Remove button); only
|
||||
"Remove source" sends the DELETE, in the §7.4 "Removing…"
|
||||
in-flight state. The .doc-modal overlay contract: a fixed
|
||||
full-viewport dim backdrop + a centered panel (no blur).
|
||||
Static markup so the E2E suite gets stable selectors (the
|
||||
#git-sources-hint / gate convention). -->
|
||||
<div class="remove-confirm" id="remove-confirm-dialog" role="alertdialog"
|
||||
aria-modal="true" aria-labelledby="remove-confirm-title"
|
||||
aria-describedby="remove-confirm-copy" hidden>
|
||||
<div class="remove-confirm-backdrop" aria-hidden="true"></div>
|
||||
<div class="remove-confirm-panel">
|
||||
<h2 class="remove-confirm-title" id="remove-confirm-title">Remove this source?</h2>
|
||||
<code class="remove-confirm-source" id="remove-confirm-source"></code>
|
||||
<p class="remove-confirm-copy" id="remove-confirm-copy">
|
||||
This permanently removes the source entry, all of its
|
||||
indexed documents from the knowledge base, and — for git
|
||||
clones and uploaded archives — the files on the server's
|
||||
disk. Files in your own local directories are never
|
||||
touched. This cannot be undone.
|
||||
</p>
|
||||
<p class="remove-confirm-error" id="remove-confirm-error" role="alert" hidden></p>
|
||||
<div class="remove-confirm-actions">
|
||||
<button type="button" class="remove-confirm-btn remove-confirm-cancel"
|
||||
id="remove-confirm-cancel">Cancel</button>
|
||||
<button type="button" class="remove-confirm-btn remove-confirm-remove"
|
||||
id="remove-confirm-remove">Remove source</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Polite live region: the screen-reader confirmation for list
|
||||
loads, adds, and removals (git-sources.js owns the text). -->
|
||||
|
||||
Reference in New Issue
Block a user