Recent activity
Redacted Console audit
Refresh
@@ -518,6 +571,11 @@ button.danger { color: #ffd7cf; background: transparent; border: 1px solid #7043
.setup-review ul { display: grid; gap: .35rem; margin: .8rem 0 1rem; padding-left: 1.25rem; color: var(--muted); }
.setup-review .form-action { justify-content: flex-start; flex-wrap: wrap; gap: .6rem; }
.summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: 1px; background: var(--line); border: 1px solid var(--line); }
+#workspace-task-navigation { display: flex; flex-wrap: wrap; gap: .55rem; margin: 0 0 1rem; }
+#workspace-task-navigation a { display: inline-flex; min-height: 2.75rem; align-items: center; padding: .55rem .85rem; color: var(--ink); background: var(--panel-raised); border: 1px solid var(--line); border-radius: .28rem; text-decoration: none; font-weight: 700; }
+#workspace-task-navigation a:hover { border-color: var(--key); }
+#workspace-task-navigation a:focus-visible { outline: 2px solid var(--key); outline-offset: 2px; }
+#connection-overview, #connection-accounts, #connection-authentication, #connection-client-setup, #connection-audit { scroll-margin-top: 1rem; }
.summary article { display: flex; min-height: 9rem; flex-direction: column; gap: .45rem; padding: 1.25rem; background: var(--panel); }
.summary strong { font: 500 1.5rem/1.15 Georgia, serif; }
.summary span { color: var(--muted); font-size: .8rem; line-height: 1.45; }
@@ -536,15 +594,21 @@ button.danger { color: #ffd7cf; background: transparent; border: 1px solid #7043
.configuration-card p { margin: .25rem 0 0; font-size: .82rem; }
.configuration-card .configuration-meta { font: .73rem/1.5 ui-monospace, monospace; }
.configuration-profiles { display: flex; flex-wrap: wrap; gap: .4rem; margin: .75rem 0 0; padding: 0; list-style: none; }
-.configuration-profiles li { padding: .28rem .5rem; color: var(--ink); background: var(--ground); border: 1px solid var(--line); font: .72rem/1.35 ui-monospace, monospace; }
+.configuration-profiles li { display: flex; flex-wrap: wrap; align-items: center; gap: .45rem; padding: .28rem .5rem; color: var(--ink); background: var(--ground); border: 1px solid var(--line); font: .72rem/1.35 ui-monospace, monospace; }
.configuration-profiles .configuration-default { border-color: var(--key); }
+.configuration-profiles button { min-height: 2.75rem; padding: 0 .65rem; }
.configuration-card .configuration-switch { margin-top: .7rem; color: var(--ink); }
-.configuration-card button { min-height: 2.4rem; font-size: .78rem; }
+.configuration-card .configuration-switch-technical { color: var(--muted); font-size: .75rem; }
+.configuration-card button { min-height: 2.75rem; font-size: .78rem; }
.provider-authentication { border-left: .2rem solid var(--safe); padding-left: 1.2rem; background: linear-gradient(90deg, rgb(117 201 154 / 7%), transparent 50%); }
.provider-authentication .section-heading { margin-bottom: 0; }
.setup-completion { border-left: .2rem solid var(--key); padding-left: 1.2rem; background: linear-gradient(90deg, rgb(239 180 77 / 7%), transparent 50%); }
-.setup-completion-copy { display: grid; gap: .75rem; max-width: 58rem; }
-.setup-completion-copy p { margin: 0; }
+.setup-completion-copy { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .85rem; max-width: 68rem; }
+.setup-completion-copy article { padding: 1rem; border: 1px solid var(--line); background: var(--panel); }
+.setup-completion-copy h3 { margin: 0 0 .75rem; }
+.setup-completion-copy p { margin: .55rem 0 0; }
+.setup-completion-client { grid-column: 1 / -1; }
+.setup-completion-client textarea { margin-bottom: .65rem; }
.profile-readiness { border-left: .2rem solid var(--key); padding-left: 1.2rem; background: linear-gradient(90deg, rgb(239 180 77 / 7%), transparent 50%); }
.profile-readiness .input-row { max-width: 42rem; }
.profile-inventory-list { display: grid; gap: .65rem; }
@@ -563,7 +627,7 @@ summary { cursor: pointer; font-weight: 700; }
.status { position: sticky; bottom: 1rem; min-height: 1.5rem; width: fit-content; max-width: 100%; margin: 1rem 0 0; padding: .7rem 1rem; color: var(--ink); background: #222923; border: 1px solid var(--line); box-shadow: 0 .7rem 2rem rgb(0 0 0 / 35%); }
.status:empty { visibility: hidden; }
@media (max-width: 850px) { .mode-grid, .summary, .configuration-catalog { grid-template-columns: repeat(2, 1fr); } .section-heading, .split { grid-template-columns: 1fr; gap: 1rem; } }
-@media (max-width: 620px) { .gate, .form-grid, .mode-grid, .summary, .configuration-catalog { grid-template-columns: 1fr; } .wide { grid-column: 1; } .masthead { flex-direction: column; } .input-row, .connection, .configuration-card { align-items: stretch; flex-direction: column; grid-template-columns: 1fr; } .connection-actions { justify-content: flex-start; } .audit-list li { grid-template-columns: 1fr; gap: .2rem; } }
+@media (max-width: 620px) { .gate, .form-grid, .mode-grid, .summary, .configuration-catalog, .setup-completion-copy { grid-template-columns: 1fr; } .wide, .setup-completion-client { grid-column: 1; } .masthead { flex-direction: column; } .input-row, .connection, .configuration-card { align-items: stretch; flex-direction: column; grid-template-columns: 1fr; } .connection-actions { justify-content: flex-start; } .audit-list li { grid-template-columns: 1fr; gap: .2rem; } }
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { scroll-behavior: auto !important; transition: none !important; animation: none !important; } }
`;
@@ -583,6 +647,10 @@ const script = `(() => {
const configurationCatalog = byId("configuration-catalog");
const configurationCatalogSummary = byId("configuration-catalog-summary");
const configurationCatalogAttention = byId("configuration-catalog-attention");
+ const configurationCatalogRejectedGuidance = byId("configuration-catalog-rejected-guidance");
+ const catalogClientSelect = byId("catalog-client-select");
+ let catalogSwitchCopies = [];
+ let catalogSwitchButtons = [];
const setUpAnotherMcp = byId("set-up-another-mcp");
const setupWizardView = byId("setup-wizard-view");
const setupWizardStep = byId("setup-wizard-step");
@@ -594,10 +662,19 @@ const script = `(() => {
const setupWizardContinue = byId("setup-wizard-continue");
const setupDraftActions = byId("setup-draft-actions");
const setupCompletionView = byId("setup-completion-view");
+ const setupCompletionCreated = byId("setup-completion-created");
const setupCompletionVerification = byId("setup-completion-verification");
const setupCompletionNextAction = byId("setup-completion-next-action");
const setupCompletionEnvironment = byId("setup-completion-environment");
const setupCompletionHandoff = byId("setup-completion-handoff");
+ const setupCompletionClientSelect = byId("setup-completion-client-select");
+ const setupCompletionGenerateEntry = byId("setup-completion-generate-entry");
+ const setupCompletionClientTarget = byId("setup-completion-client-target");
+ const setupCompletionClientJson = byId("setup-completion-client-json");
+ const setupCompletionClientGuidance = byId("setup-completion-client-guidance");
+ const setupCompletionCopyJson = byId("setup-completion-copy-json");
+ const setupCompletionSecondAccount = byId("setup-completion-second-account");
+ const setupCompletionSwitch = byId("setup-completion-switch");
const providerAuthenticationView = byId("provider-authentication-view");
const providerAuthenticationCopy = byId("provider-authentication-copy");
const providerAccountEditor = byId("provider-account-editor");
@@ -630,6 +707,11 @@ const script = `(() => {
let profileReadinessTargets = [];
let profileReadinessGeneration = 0;
let setupCompletion = undefined;
+ let pendingSetupCompletion = undefined;
+ let setupCompletionGeneration = 0;
+ let setupRefreshGeneration = 0;
+ let authenticationEpoch = 0;
+ let sessionRecoveryGeneration = 0;
let pendingPresetRequest = undefined;
let presetReviewGeneration = 0;
let presetCreateInFlight = false;
@@ -637,21 +719,36 @@ const script = `(() => {
let returningSetupVisible = false;
let setupWizardSource = "connector";
let profileSwitchingFromMcp = false;
+ const staleAuthenticationRequestName = "MiftahStaleAuthenticationRequest";
function message(text) {
- if (status) status.textContent = text;
+ if (status && typeof text === "string") status.textContent = text;
+ }
+
+ function staleAuthenticationRequestError() {
+ const error = new Error("This response belongs to an earlier Console session.");
+ error.name = staleAuthenticationRequestName;
+ return error;
+ }
+
+ function isStaleAuthenticationRequest(error) {
+ return error instanceof Error && error.name === staleAuthenticationRequestName;
}
function errorMessage(error) {
+ if (isStaleAuthenticationRequest(error)) return undefined;
return error instanceof Error ? error.message : "The Console request failed.";
}
function restoreUnlock(recoveryMessage) {
+ authenticationEpoch += 1;
+ sessionRecoveryGeneration += 1;
csrfToken = "";
returningSetupVisible = false;
- setupCompletion = undefined;
+ clearSetupCompletion();
activeSetupDraft = undefined;
renderSetupDraftControls();
+ presetCreateInFlight = false;
clearPresetReview();
if (setupCompletionView) setupCompletionView.hidden = true;
if (dashboardView) dashboardView.hidden = true;
@@ -706,6 +803,7 @@ const script = `(() => {
}
async function api(path, options) {
+ const requestAuthenticationEpoch = authenticationEpoch;
const request = options || {};
const headers = { "Accept": "application/json" };
if (request.body !== undefined) headers["Content-Type"] = "application/json";
@@ -718,12 +816,14 @@ const script = `(() => {
body: request.body === undefined ? undefined : JSON.stringify(request.body)
});
} catch {
+ if (requestAuthenticationEpoch !== authenticationEpoch) throw staleAuthenticationRequestError();
const recovery = "This Console process is no longer reachable. Run \`miftah dashboard\` in the terminal for a new URL and one-time code.";
restoreUnlock(recovery);
throw new Error(recovery);
}
let payload;
try { payload = await response.json(); } catch { payload = undefined; }
+ if (requestAuthenticationEpoch !== authenticationEpoch) throw staleAuthenticationRequestError();
if (!response.ok) {
if (response.status === 401) {
const code = payload && payload.error && typeof payload.error.code === "string" ? payload.error.code : "";
@@ -740,16 +840,21 @@ const script = `(() => {
}
async function resumeSession() {
+ const resumeAuthenticationEpoch = authenticationEpoch;
+ let resumeMessageEpoch = resumeAuthenticationEpoch;
message("Checking this browser session…");
try {
const resumed = record(await api("/api/v1/session"));
+ if (authenticationEpoch !== resumeAuthenticationEpoch) return;
if (typeof resumed.csrfToken !== "string" || resumed.csrfToken.length < 32) {
throw new Error("Miftah did not return a valid session proof.");
}
+ authenticationEpoch += 1;
+ resumeMessageEpoch = authenticationEpoch;
csrfToken = resumed.csrfToken;
- await refresh();
+ await refreshAfterAuthentication();
} catch (error) {
- message(errorMessage(error));
+ if (authenticationEpoch === resumeMessageEpoch) message(errorMessage(error));
}
}
@@ -889,8 +994,42 @@ const script = `(() => {
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
}
+ function completionFromSetupResult(result, fallback = {}) {
+ const setupResult = record(result);
+ const completion = record(setupResult.completion);
+ const safeFallback = record(fallback);
+ const name = typeof setupResult.name === "string" ? setupResult.name : typeof safeFallback.name === "string" ? safeFallback.name : "";
+ const defaultProfile = typeof setupResult.defaultProfile === "string"
+ ? setupResult.defaultProfile
+ : typeof setupResult.profile === "string"
+ ? setupResult.profile
+ : typeof safeFallback.defaultProfile === "string" ? safeFallback.defaultProfile : "";
+ const profileCount = Number.isSafeInteger(setupResult.profileCount) && setupResult.profileCount > 0
+ ? setupResult.profileCount
+ : 1;
+ return { ...completion, setup: { name, defaultProfile, profileCount } };
+ }
+
+ function selectedSetupCompletionClient() {
+ const selected = setupCompletionClientSelect instanceof HTMLSelectElement
+ ? setupCompletionClientSelect.value
+ : "claude-desktop";
+ return ["claude-desktop", "claude-code", "cursor", "vscode"].includes(selected)
+ ? selected
+ : "claude-desktop";
+ }
+
+ function renderSetupCompletionSwitch(setup) {
+ if (!setupCompletionSwitch) return;
+ const client = selectedSetupCompletionClient();
+ setupCompletionSwitch.textContent =
+ "After adding another account, return to Your MCP connections and copy its switch request for " +
+ catalogClientDisplayName(client) + ". Paste it into that chat; Console does not switch the running client session.";
+ }
+
function renderSetupCompletion(value) {
const completion = record(value);
+ const setup = record(completion.setup);
const verification = record(completion.verification);
const environment = record(completion.environment);
const handoff = record(completion.clientHandoff);
@@ -899,25 +1038,126 @@ const script = `(() => {
const environmentMessage = typeof environment.message === "string" ? environment.message : "";
const environmentNextAction = typeof environment.nextAction === "string" ? environment.nextAction : "";
const handoffMessage = typeof handoff.message === "string" ? handoff.message : "";
- if (!verificationMessage && !environmentMessage && !handoffMessage) {
+ const setupName = typeof setup.name === "string" ? setup.name : "";
+ const defaultProfile = typeof setup.defaultProfile === "string" ? setup.defaultProfile : "";
+ const profileCount = Number.isSafeInteger(setup.profileCount) ? setup.profileCount : 0;
+ if (!setupName && !verificationMessage && !environmentMessage && !handoffMessage) {
if (setupCompletionView) setupCompletionView.hidden = true;
return;
}
- if (setupCompletionVerification) setupCompletionVerification.textContent = verificationMessage;
+ if (setupCompletionCreated) {
+ setupCompletionCreated.textContent = setupName
+ ? "Created Miftah connection '" + setupName + "' with " + profileCount + " named account" + (profileCount === 1 ? "" : "s") +
+ (defaultProfile ? "; default for new sessions: '" + defaultProfile + "'." : ".")
+ : "The Miftah connection was created.";
+ }
+ const verificationState = typeof verification.state === "string" ? verification.state : "manual";
+ if (setupCompletionVerification) {
+ setupCompletionVerification.textContent = "Verification (" + verificationState + "): " + verificationMessage;
+ }
if (setupCompletionNextAction) {
- setupCompletionNextAction.textContent = nextAction;
- setupCompletionNextAction.hidden = !nextAction;
+ const safeAction = nextAction || (verificationState === "available"
+ ? "Open Manage connection, choose Authentication, then run the reviewed safe check."
+ : verificationState === "not-declared"
+ ? "No automatic check is available. After client setup, use one provider-documented read-only action for manual verification."
+ : "Complete the provider authorization or reported readiness step before treating the account as verified.");
+ setupCompletionNextAction.textContent = safeAction;
+ setupCompletionNextAction.hidden = !safeAction;
}
if (setupCompletionEnvironment) {
- setupCompletionEnvironment.textContent = [environmentMessage, environmentNextAction].filter(Boolean).join(" ");
+ const environmentState = typeof environment.state === "string" ? environment.state : "not-reported";
+ setupCompletionEnvironment.textContent = "Secret readiness (" + environmentState + "): " +
+ [environmentMessage, environmentNextAction].filter(Boolean).join(" ");
setupCompletionEnvironment.hidden = !environmentMessage && !environmentNextAction;
}
if (setupCompletionHandoff) setupCompletionHandoff.textContent = handoffMessage;
- if (setupCompletionView) setupCompletionView.hidden = false;
+ if (setupCompletionSecondAccount) {
+ setupCompletionSecondAccount.textContent =
+ "Open Manage connection, then choose Accounts or Authentication. Miftah shows only the supported way to add another named account for this connection.";
+ }
+ renderSetupCompletionSwitch(setup);
+ if (setupCompletionView) {
+ setupCompletionView.hidden = false;
+ if (typeof setupCompletionView.scrollIntoView === "function") {
+ setupCompletionView.scrollIntoView({ block: "start" });
+ }
+ }
+ if (setupCompletionClientSelect instanceof HTMLSelectElement) {
+ setupCompletionClientSelect.focus({ preventScroll: true });
+ }
+ }
+
+ function replaceSetupCompletion(value) {
+ setupCompletionGeneration += 1;
+ setupCompletion = value;
+ if (setupCompletionGenerateEntry instanceof HTMLButtonElement) {
+ setupCompletionGenerateEntry.disabled = false;
+ }
+ renderSetupCompletion(setupCompletion);
+ }
+
+ function setupCompletionRequestIsCurrent(generation, client) {
+ return generation === setupCompletionGeneration && client === selectedSetupCompletionClient();
+ }
+
+ function restorePendingSetupCompletion() {
+ if (pendingSetupCompletion === undefined) return;
+ const completion = pendingSetupCompletion;
+ pendingSetupCompletion = undefined;
+ replaceSetupCompletion(completion);
+ }
+
+ function resetAuthenticatedActionControls() {
+ if (saveSetupDraft instanceof HTMLButtonElement) saveSetupDraft.disabled = false;
+ if (resumeSetupDraft instanceof HTMLButtonElement) resumeSetupDraft.disabled = false;
+ if (runProfileReadiness instanceof HTMLButtonElement) runProfileReadiness.disabled = false;
+ }
+
+ async function refreshAfterAuthentication() {
+ const refreshAuthenticationEpoch = authenticationEpoch;
+ const recoveryGeneration = sessionRecoveryGeneration;
+ try {
+ await refresh();
+ if (authenticationEpoch === refreshAuthenticationEpoch && sessionRecoveryGeneration === recoveryGeneration) {
+ resetAuthenticatedActionControls();
+ }
+ } finally {
+ if (authenticationEpoch === refreshAuthenticationEpoch && sessionRecoveryGeneration === recoveryGeneration) {
+ restorePendingSetupCompletion();
+ }
+ }
+ }
+
+ async function refreshAfterSetup(completion) {
+ const setupAuthenticationEpoch = authenticationEpoch;
+ const refreshGeneration = ++setupRefreshGeneration;
+ pendingSetupCompletion = completion;
+ try {
+ await refresh();
+ } catch (error) {
+ if (authenticationEpoch !== setupAuthenticationEpoch || refreshGeneration !== setupRefreshGeneration) {
+ if (isStaleAuthenticationRequest(error)) throw error;
+ throw staleAuthenticationRequestError();
+ }
+ throw error;
+ } finally {
+ if (authenticationEpoch === setupAuthenticationEpoch && refreshGeneration === setupRefreshGeneration) {
+ if (unlockView instanceof HTMLElement && !unlockView.hidden) {
+ pendingSetupCompletion = completion;
+ } else {
+ pendingSetupCompletion = undefined;
+ replaceSetupCompletion(completion);
+ }
+ }
+ }
}
function clearSetupCompletion() {
- setupCompletion = undefined;
+ replaceSetupCompletion(undefined);
+ if (setupCompletionClientTarget) setupCompletionClientTarget.textContent = "";
+ if (setupCompletionClientJson instanceof HTMLTextAreaElement) setupCompletionClientJson.value = "";
+ if (setupCompletionClientGuidance) setupCompletionClientGuidance.textContent = "";
+ if (setupCompletionCopyJson instanceof HTMLButtonElement) setupCompletionCopyJson.disabled = true;
renderSetupCompletion(undefined);
}
@@ -948,6 +1188,38 @@ const script = `(() => {
};
}
+ function selectedCatalogClient() {
+ const supported = ["claude-desktop", "claude-code", "cursor", "vscode"];
+ const selected = catalogClientSelect instanceof HTMLSelectElement ? catalogClientSelect.value : "";
+ return supported.includes(selected) ? selected : "claude-desktop";
+ }
+
+ function catalogClientDisplayName(client) {
+ return {
+ "claude-desktop": "Claude Desktop",
+ "claude-code": "Claude Code",
+ cursor: "Cursor",
+ vscode: "VS Code"
+ }[client] || "your MCP client";
+ }
+
+ function profileSwitchRequest(client, profile) {
+ return "In " + catalogClientDisplayName(client) + ", send this message: Use the Miftah account named " + profile + " for this chat.";
+ }
+
+ function updateCatalogSwitchCopy() {
+ const client = selectedCatalogClient();
+ const clientName = catalogClientDisplayName(client);
+ catalogSwitchCopies.forEach((copy) => {
+ copy.textContent = "Choose an account below, then paste the copied request into " + clientName + ". This changes this chat, not the durable default.";
+ });
+ catalogSwitchButtons.forEach((button) => {
+ if (!(button instanceof HTMLButtonElement)) return;
+ const profile = button.dataset.copyProfileSwitch || "";
+ button.setAttribute("aria-label", "Copy switch request for " + profile + " in " + clientName);
+ });
+ }
+
function renderConfigurationCatalog(metadata) {
const catalog = catalogConfigurations(metadata);
const hasCatalogState = catalog.discoveredCount > 0 || catalog.readyCount > 0 || catalog.attentionCount > 0;
@@ -956,13 +1228,16 @@ const script = `(() => {
setUpAnotherMcp.hidden = catalog.discoveryState !== "ready" || catalog.configurations.length === 0;
}
if (configurationCatalogSummary) {
- const files = catalog.discoveredCount === 1 ? "configuration file found" : "configuration files found";
+ const connections = catalog.discoveredCount === 1 ? "MCP connection found" : "MCP connections found";
const attention = catalog.attentionCount === 1 ? "needs attention" : "need attention";
configurationCatalogSummary.textContent =
- catalog.discoveredCount + " " + files + " · " +
+ catalog.discoveredCount + " " + connections + " · " +
catalog.readyCount + " ready · " +
catalog.attentionCount + " " + attention;
}
+ if (configurationCatalogRejectedGuidance) {
+ configurationCatalogRejectedGuidance.hidden = catalog.attentionCount === 0;
+ }
if (configurationCatalogAttention) {
configurationCatalogAttention.replaceChildren();
const labels = {
@@ -982,6 +1257,8 @@ const script = `(() => {
}
if (!configurationCatalog) return catalog;
configurationCatalog.replaceChildren();
+ catalogSwitchCopies = [];
+ catalogSwitchButtons = [];
catalog.configurations.forEach((configuration) => {
const id = typeof configuration.id === "string" ? configuration.id : "";
const card = document.createElement("article");
@@ -1005,8 +1282,28 @@ const script = `(() => {
profileList.setAttribute("aria-label", visibleProfileCount + " named account profile" + (visibleProfileCount === 1 ? "" : "s"));
profileNames.forEach((profile) => {
const item = document.createElement("li");
- item.textContent = profile + (profile === defaultProfile ? " · default" : "");
+ const profileName = document.createElement("span");
+ profileName.textContent = profile + (profile === defaultProfile ? " · default" : "");
+ item.append(profileName);
if (profile === defaultProfile) item.className = "configuration-default";
+ if (configuration.profileSwitchingFromMcp === true) {
+ const copySwitchRequest = document.createElement("button");
+ copySwitchRequest.type = "button";
+ copySwitchRequest.className = "secondary";
+ copySwitchRequest.dataset.copyProfileSwitch = profile;
+ copySwitchRequest.textContent = "Copy switch request";
+ copySwitchRequest.addEventListener("click", async () => {
+ const client = selectedCatalogClient();
+ try {
+ await navigator.clipboard.writeText(profileSwitchRequest(client, profile));
+ message("Account-switch request copied for " + catalogClientDisplayName(client) + ". Paste it into the chat where Miftah is connected.");
+ } catch {
+ message("Clipboard access was unavailable. Copy this request manually: " + profileSwitchRequest(client, profile));
+ }
+ });
+ catalogSwitchButtons.push(copySwitchRequest);
+ item.append(copySwitchRequest);
+ }
profileList.append(item);
});
const ownership = document.createElement("p");
@@ -1020,9 +1317,16 @@ const script = `(() => {
const switching = document.createElement("p");
switching.className = "configuration-switch";
switching.textContent = configuration.profileSwitchingFromMcp === true
- ? "Live account switch: use miftah_use_profile in your MCP client."
+ ? "Choose an account below, then paste the copied request into your MCP client. This changes this chat, not the durable default."
: "Live account switch: off. Open a new connection to use another default.";
- details.append(title, summary, profileList, switching, ownership);
+ if (configuration.profileSwitchingFromMcp === true) switching.dataset.catalogSwitchCopy = "true";
+ if (configuration.profileSwitchingFromMcp === true) catalogSwitchCopies.push(switching);
+ const switchingTechnical = document.createElement("p");
+ switchingTechnical.className = "configuration-switch-technical";
+ switchingTechnical.textContent = configuration.profileSwitchingFromMcp === true
+ ? "Technical detail: the client can call miftah_use_profile. Console does not switch a running client session."
+ : "Changing the durable default affects only new client connections.";
+ details.append(title, summary, profileList, switching, switchingTechnical, ownership);
const button = document.createElement("button");
button.type = "button";
button.dataset.configuration = id;
@@ -1033,9 +1337,14 @@ const script = `(() => {
card.append(details, button);
configurationCatalog.append(card);
});
+ updateCatalogSwitchCopy();
return catalog;
}
+ if (catalogClientSelect instanceof HTMLSelectElement) {
+ catalogClientSelect.addEventListener("change", updateCatalogSwitchCopy);
+ }
+
function renderProviderAuthentication(value) {
const authentication = record(value);
const providerAdapter = authentication.mode === "provider-adapter";
@@ -1723,10 +2032,11 @@ const script = `(() => {
}
async function refresh() {
+ const refreshAuthenticationEpoch = authenticationEpoch;
clearSetupCompletion();
- const metadata = record(await api("/api/v1/config"));
if (unlockView) unlockView.hidden = true;
if (dashboardView) dashboardView.hidden = false;
+ const metadata = record(await api("/api/v1/config"));
const catalog = renderConfigurationCatalog(metadata);
if (metadata.initialized !== true) {
renderProviderAuthentication(undefined);
@@ -1750,7 +2060,7 @@ const script = `(() => {
returningSetupVisible = false;
if (setupWizardView) setupWizardView.hidden = true;
if (workspaceView) workspaceView.hidden = true;
- message("Miftah found configuration files, but none passed every trust and validation check. Review the safe reason summary, correct the expected files, then refresh.");
+ message("Miftah found MCP connections, but none passed every trust and validation check. Review the safe reason summary, correct the expected files, then refresh.");
return;
}
if (catalog.discoveryState === "unavailable") {
@@ -1785,12 +2095,12 @@ const script = `(() => {
if (profileSwitchingState) profileSwitchingState.textContent = profileSwitchingFromMcp ? "Available" : "Off";
if (profileSwitchingCopy) {
profileSwitchingCopy.textContent = profileSwitchingFromMcp
- ? "Use miftah_use_profile in your MCP client to switch this active session."
+ ? "Return to Your MCP connections and copy the request for the named account you want in this chat."
: "Open a new connection after changing the durable default.";
}
if (activeProfileGuidance) {
activeProfileGuidance.textContent = profileSwitchingFromMcp
- ? "Active vs durable: changing the default affects new connections. Existing sessions keep their account until you use miftah_use_profile in the MCP client."
+ ? "Active vs durable: changing the default affects new connections. For this chat, use a profile's Copy switch request action above. Technical detail: the client calls miftah_use_profile; Console does not switch a running client."
: "Active vs durable: changing the default affects new connections. Existing sessions keep their account until you reconnect.";
}
const profileMetadata = Array.isArray(metadata.profiles) ? metadata.profiles.map(record) : [];
@@ -1807,11 +2117,18 @@ const script = `(() => {
setOptions(byId("manual-connection-profile"), profiles);
setOptions(byId("manual-connection-upstream"), upstreams);
renderProfileReadiness(metadata.authentication, configuredDefaultProfile);
- const results = await Promise.all([
+ const recoveryGeneration = sessionRecoveryGeneration;
+ const settledResults = await Promise.allSettled([
api("/api/v1/health"),
api("/api/v1/connections"),
api("/api/v1/audit?limit=50")
]);
+ if (authenticationEpoch !== refreshAuthenticationEpoch || sessionRecoveryGeneration !== recoveryGeneration) {
+ throw staleAuthenticationRequestError();
+ }
+ const failedResult = settledResults.find((result) => result.status === "rejected");
+ if (failedResult && failedResult.status === "rejected") throw failedResult.reason;
+ const results = settledResults.map((result) => result.status === "fulfilled" ? result.value : undefined);
const health = record(results[0]);
const audit = record(health.audit);
const auditState = byId("audit-state");
@@ -1819,13 +2136,15 @@ const script = `(() => {
renderConnections(results[1], metadata.authentication);
renderAudit(results[2]);
message(profileSwitchingFromMcp
- ? "Console data refreshed. Durable changes apply to new connections; use miftah_use_profile for this active MCP session."
+ ? "Console data refreshed. Durable changes apply to new connections; use a Copy switch request action above for this chat."
: "Console data refreshed. Open a new MCP connection before expecting durable changes to be active.");
}
if (unlockForm instanceof HTMLFormElement && bootstrapInput instanceof HTMLInputElement) {
unlockForm.addEventListener("submit", async (event) => {
event.preventDefault();
+ const bootstrapAuthenticationEpoch = authenticationEpoch;
+ let bootstrapMessageEpoch = bootstrapAuthenticationEpoch;
message("Opening the local Console…");
try {
const response = await fetch("/api/v1/sessions", {
@@ -1835,15 +2154,20 @@ const script = `(() => {
});
bootstrapInput.value = "";
const payload = await response.json();
+ if (authenticationEpoch !== bootstrapAuthenticationEpoch) return;
if (!response.ok) throw bootstrapResponseError(response, payload);
if (!payload || !payload.data || typeof payload.data.csrfToken !== "string") {
throw new Error("Miftah did not return a valid session proof.");
}
+ authenticationEpoch += 1;
+ bootstrapMessageEpoch = authenticationEpoch;
csrfToken = payload.data.csrfToken;
- await refresh();
+ await refreshAfterAuthentication();
} catch (error) {
- message(errorMessage(error));
- bootstrapInput.focus();
+ if (authenticationEpoch === bootstrapMessageEpoch) {
+ message(errorMessage(error));
+ if (!(unlockView instanceof HTMLElement) || !unlockView.hidden) bootstrapInput.focus();
+ }
}
});
}
@@ -1854,6 +2178,7 @@ const script = `(() => {
if (!(button instanceof HTMLButtonElement)) return;
const configurationId = button.dataset.configuration || "";
if (!configurationId) return;
+ const actionAuthenticationEpoch = authenticationEpoch;
button.disabled = true;
message("Opening the selected Miftah configuration…");
try {
@@ -1864,7 +2189,9 @@ const script = `(() => {
});
await refresh();
} catch (error) { message(errorMessage(error)); }
- finally { button.disabled = false; }
+ finally {
+ if (authenticationEpoch === actionAuthenticationEpoch) button.disabled = false;
+ }
});
}
@@ -1903,21 +2230,17 @@ const script = `(() => {
resource: String(data.get("resource") || "").trim()
}
}));
- const completion = record(result.completion);
+ const completion = completionFromSetupResult(result, {
+ name: String(data.get("name") || "").trim(),
+ defaultProfile: String(data.get("profile") || "").trim()
+ });
onboardingForm.reset();
returningSetupVisible = false;
- await refresh();
- setupCompletion = completion;
- renderSetupCompletion(setupCompletion);
+ await refreshAfterSetup(completion);
} catch (error) { message(errorMessage(error)); }
});
}
- const nativeOAuthSetupLink = byId("native-oauth-setup-link");
- if (nativeOAuthSetupLink instanceof HTMLButtonElement) {
- nativeOAuthSetupLink.addEventListener("click", () => selectSetupSource("browser-sign-in"));
- }
-
if (setupSourceChoice instanceof HTMLElement) {
setupSourceChoice.addEventListener("change", (event) => {
const input = event.target;
@@ -1978,6 +2301,7 @@ const script = `(() => {
if (saveSetupDraft instanceof HTMLButtonElement) {
saveSetupDraft.addEventListener("click", async () => {
if (saveSetupDraft.disabled) return;
+ const actionAuthenticationEpoch = authenticationEpoch;
saveSetupDraft.disabled = true;
message("Saving only the connector choice…");
try {
@@ -1985,13 +2309,16 @@ const script = `(() => {
restoreSetupDraft(draft);
message("Saved the configuration name and connector choice. Re-enter every connection detail when you continue.");
} catch (error) { message(errorMessage(error)); }
- finally { saveSetupDraft.disabled = false; }
+ finally {
+ if (authenticationEpoch === actionAuthenticationEpoch) saveSetupDraft.disabled = false;
+ }
});
}
if (resumeSetupDraft instanceof HTMLButtonElement) {
resumeSetupDraft.addEventListener("click", async () => {
if (resumeSetupDraft.disabled) return;
+ const actionAuthenticationEpoch = authenticationEpoch;
resumeSetupDraft.disabled = true;
message("Loading the saved connector choice…");
try {
@@ -2005,13 +2332,16 @@ const script = `(() => {
restoreSetupDraft(draft);
message("Restored only the configuration name and connector choice. Re-enter every connection value before reviewing.");
} catch (error) { message(errorMessage(error)); }
- finally { resumeSetupDraft.disabled = false; }
+ finally {
+ if (authenticationEpoch === actionAuthenticationEpoch) resumeSetupDraft.disabled = false;
+ }
});
}
if (discardSetupDraft instanceof HTMLButtonElement) {
discardSetupDraft.addEventListener("click", async () => {
if (activeSetupDraft === undefined || discardSetupDraft.disabled) return;
+ const actionAuthenticationEpoch = authenticationEpoch;
discardSetupDraft.disabled = true;
message("Discarding the saved connector choice…");
try {
@@ -2020,7 +2350,9 @@ const script = `(() => {
renderSetupDraftControls();
message("Discarded the saved connector choice. The current form stays open and is not saved.");
} catch (error) { message(errorMessage(error)); }
- finally { discardSetupDraft.disabled = false; }
+ finally {
+ if (authenticationEpoch === actionAuthenticationEpoch) discardSetupDraft.disabled = false;
+ }
});
}
@@ -2034,12 +2366,13 @@ const script = `(() => {
return;
}
if (presetCreateInFlight || presetCreateReviewed.disabled) return;
+ const actionAuthenticationEpoch = authenticationEpoch;
message("Creating the reviewed Miftah configuration…");
presetCreateInFlight = true;
setPresetReviewActionsDisabled(true);
try {
const result = record(await api("/api/v1/onboarding/preset", { method: "POST", body: request }));
- const completion = record(result.completion);
+ const completion = completionFromSetupResult(result);
clearPresetReview();
activeSetupDraft = undefined;
renderSetupDraftControls();
@@ -2048,13 +2381,13 @@ const script = `(() => {
updatePresetFields();
}
returningSetupVisible = false;
- await refresh();
- setupCompletion = completion;
- renderSetupCompletion(setupCompletion);
+ await refreshAfterSetup(completion);
} catch (error) { message(errorMessage(error)); }
finally {
- presetCreateInFlight = false;
- if (pendingPresetRequest !== undefined) setPresetReviewActionsDisabled(false);
+ if (authenticationEpoch === actionAuthenticationEpoch) {
+ presetCreateInFlight = false;
+ if (pendingPresetRequest !== undefined) setPresetReviewActionsDisabled(false);
+ }
}
});
}
@@ -2076,6 +2409,7 @@ const script = `(() => {
if (clientEntryOnboardingForm instanceof HTMLFormElement) {
clientEntryOnboardingForm.addEventListener("submit", async (event) => {
event.preventDefault();
+ const actionAuthenticationEpoch = authenticationEpoch;
const documentInput = clientEntryOnboardingForm.querySelector("textarea[name='document']");
message("Importing one selected MCP entry…");
try {
@@ -2088,15 +2422,15 @@ const script = `(() => {
document: String(data.get("document") || "")
}
}));
- const completion = record(result.completion);
+ const completion = completionFromSetupResult(result);
clientEntryOnboardingForm.reset();
returningSetupVisible = false;
- await refresh();
- setupCompletion = completion;
- renderSetupCompletion(setupCompletion);
+ await refreshAfterSetup(completion);
} catch (error) { message(errorMessage(error)); }
finally {
- if (documentInput instanceof HTMLTextAreaElement) documentInput.value = "";
+ if (authenticationEpoch === actionAuthenticationEpoch && documentInput instanceof HTMLTextAreaElement) {
+ documentInput.value = "";
+ }
}
});
}
@@ -2218,6 +2552,7 @@ const script = `(() => {
const reference = button.dataset.connection || "";
const action = button.dataset.action || "";
if (!reference || !action) return;
+ const actionAuthenticationEpoch = authenticationEpoch;
button.disabled = true;
message(action === "credential" ? "Removing the exact local vault credential…" : "Running " + action + "…");
try {
@@ -2227,7 +2562,9 @@ const script = `(() => {
});
await refresh();
} catch (error) { message(errorMessage(error)); }
- finally { button.disabled = false; }
+ finally {
+ if (authenticationEpoch === actionAuthenticationEpoch) button.disabled = false;
+ }
});
}
@@ -2252,6 +2589,7 @@ const script = `(() => {
}
clearProfileReadinessResult();
const readinessGeneration = profileReadinessGeneration;
+ const actionAuthenticationEpoch = authenticationEpoch;
const selectedProfile = profile.value;
const selectedUpstream = upstream.value;
runProfileReadiness.disabled = true;
@@ -2273,7 +2611,9 @@ const script = `(() => {
} catch (error) {
if (readinessGeneration === profileReadinessGeneration) message(errorMessage(error));
}
- finally { runProfileReadiness.disabled = false; }
+ finally {
+ if (authenticationEpoch === actionAuthenticationEpoch) runProfileReadiness.disabled = false;
+ }
});
}
@@ -2283,6 +2623,7 @@ const script = `(() => {
const profile = byId("default-profile-selection");
const result = byId("default-profile-result");
if (!(profile instanceof HTMLSelectElement) || !profile.value) return;
+ const actionAuthenticationEpoch = authenticationEpoch;
const selectedProfile = profile.value;
setDefaultProfile.disabled = true;
message("Saving the durable default profile…");
@@ -2297,11 +2638,13 @@ const script = `(() => {
if (result) result.textContent = publicResult;
await refresh();
message(publicResult + (profileSwitchingFromMcp
- ? " New connections will use it; use miftah_use_profile to switch this active MCP session."
+ ? " New connections will use it; use a Copy switch request action above for this chat."
: " Open a new MCP connection to use it.") +
" If you are using the configuration catalog, select this configuration again before another Console change.");
} catch (error) { message(errorMessage(error)); }
- finally { setDefaultProfile.disabled = false; }
+ finally {
+ if (authenticationEpoch === actionAuthenticationEpoch) setDefaultProfile.disabled = false;
+ }
});
}
@@ -2312,6 +2655,7 @@ const script = `(() => {
const input = byId("profile-description-input");
const result = byId("profile-description-result");
if (!(profile instanceof HTMLSelectElement) || !profile.value || !(input instanceof HTMLInputElement)) return;
+ const actionAuthenticationEpoch = authenticationEpoch;
const selectedProfile = profile.value;
const description = input.value.trim();
if (!clearDescription && !description) {
@@ -2337,8 +2681,10 @@ const script = `(() => {
message(publicResult + " Existing MCP clients need a restart; if you are using the configuration catalog, select this configuration again before another Console change.");
} catch (error) { message(errorMessage(error)); }
finally {
- if (setProfileDescription instanceof HTMLButtonElement) setProfileDescription.disabled = false;
- if (clearProfileDescription instanceof HTMLButtonElement) clearProfileDescription.disabled = false;
+ if (authenticationEpoch === actionAuthenticationEpoch) {
+ if (setProfileDescription instanceof HTMLButtonElement) setProfileDescription.disabled = false;
+ if (clearProfileDescription instanceof HTMLButtonElement) clearProfileDescription.disabled = false;
+ }
}
}
@@ -2368,6 +2714,7 @@ const script = `(() => {
input.focus();
return;
}
+ const actionAuthenticationEpoch = authenticationEpoch;
renameProfile.disabled = true;
message("Renaming the selected account in Miftah configuration…");
try {
@@ -2384,7 +2731,9 @@ const script = `(() => {
} catch (error) { message(errorMessage(error)); }
finally {
const editor = byId("profile-rename-editor");
- if (editor instanceof HTMLElement && !editor.hidden) renameProfile.disabled = false;
+ if (authenticationEpoch === actionAuthenticationEpoch && editor instanceof HTMLElement && !editor.hidden) {
+ renameProfile.disabled = false;
+ }
}
});
}
@@ -2403,6 +2752,7 @@ const script = `(() => {
}
const selectedProfile = profile.value;
const replacementProfile = replacement.value;
+ const actionAuthenticationEpoch = authenticationEpoch;
removeProfile.disabled = true;
message("Removing the selected account from Miftah configuration…");
try {
@@ -2419,7 +2769,70 @@ const script = `(() => {
} catch (error) { message(errorMessage(error)); }
finally {
const editor = byId("profile-removal-editor");
- if (editor instanceof HTMLElement && !editor.hidden) removeProfile.disabled = false;
+ if (authenticationEpoch === actionAuthenticationEpoch && editor instanceof HTMLElement && !editor.hidden) {
+ removeProfile.disabled = false;
+ }
+ }
+ });
+ }
+
+ if (setupCompletionClientSelect instanceof HTMLSelectElement) {
+ setupCompletionClientSelect.addEventListener("change", () => {
+ setupCompletionGeneration += 1;
+ renderSetupCompletionSwitch(record(record(setupCompletion).setup));
+ if (setupCompletionClientTarget) setupCompletionClientTarget.textContent = "";
+ if (setupCompletionClientJson instanceof HTMLTextAreaElement) setupCompletionClientJson.value = "";
+ if (setupCompletionClientGuidance) setupCompletionClientGuidance.textContent = "";
+ if (setupCompletionHandoff) setupCompletionHandoff.textContent = "";
+ if (setupCompletionCopyJson instanceof HTMLButtonElement) setupCompletionCopyJson.disabled = true;
+ if (setupCompletionGenerateEntry instanceof HTMLButtonElement) setupCompletionGenerateEntry.disabled = false;
+ });
+ }
+
+ if (setupCompletionGenerateEntry instanceof HTMLButtonElement) {
+ setupCompletionGenerateEntry.addEventListener("click", async () => {
+ if (setupCompletionGenerateEntry.disabled) return;
+ setupCompletionGenerateEntry.disabled = true;
+ const client = selectedSetupCompletionClient();
+ const generation = setupCompletionGeneration;
+ try {
+ const snippets = await api("/api/v1/client-snippets?client=" + encodeURIComponent(client));
+ if (!setupCompletionRequestIsCurrent(generation, client)) return;
+ const snippet = Array.isArray(snippets) ? record(snippets[0]) : {};
+ const target = record(snippet.target);
+ const json = typeof snippet.json === "string" ? snippet.json : "";
+ const guidance = typeof snippet.guidance === "string" ? snippet.guidance : "";
+ if (!json || typeof target.label !== "string") throw new Error("A client entry was not available for the selected client.");
+ if (setupCompletionClientTarget) {
+ setupCompletionClientTarget.textContent = "Merge this one entry into " + target.label + ". Miftah did not edit that file.";
+ }
+ if (setupCompletionClientJson instanceof HTMLTextAreaElement) setupCompletionClientJson.value = json;
+ if (setupCompletionClientGuidance) setupCompletionClientGuidance.textContent = guidance;
+ if (setupCompletionCopyJson instanceof HTMLButtonElement) setupCompletionCopyJson.disabled = false;
+ if (setupCompletionHandoff) {
+ setupCompletionHandoff.textContent =
+ "Review and merge the entry, then restart or reconnect " + catalogClientDisplayName(client) +
+ ". A generated entry does not prove that a credential works or belongs to the intended account.";
+ }
+ message("Generated the copy-only client entry for " + catalogClientDisplayName(client) + ". Review it before merging.");
+ } catch (error) {
+ if (setupCompletionRequestIsCurrent(generation, client)) message(errorMessage(error));
+ } finally {
+ if (setupCompletionRequestIsCurrent(generation, client)) setupCompletionGenerateEntry.disabled = false;
+ }
+ });
+ }
+
+ if (setupCompletionCopyJson instanceof HTMLButtonElement) {
+ setupCompletionCopyJson.addEventListener("click", async () => {
+ if (!(setupCompletionClientJson instanceof HTMLTextAreaElement) || !setupCompletionClientJson.value) return;
+ try {
+ await navigator.clipboard.writeText(setupCompletionClientJson.value);
+ message("Client JSON copied. Miftah did not modify any client file.");
+ } catch {
+ setupCompletionClientJson.focus();
+ setupCompletionClientJson.select();
+ message("Clipboard access was unavailable. The client JSON is selected for manual copy.");
}
});
}
diff --git a/tests/console-server.test.ts b/tests/console-server.test.ts
index c7c304f..6d0b87b 100644
--- a/tests/console-server.test.ts
+++ b/tests/console-server.test.ts
@@ -238,6 +238,586 @@ async function submitPresetFormWithStaleValue(
return JSON.parse(request.body) as Record
;
}
+async function supersedeSetupCompletionClientRequests(javascript: string): Promise<{
+ readonly success: { readonly json: string; readonly status: string };
+ readonly failure: { readonly json: string; readonly status: string };
+}> {
+ type Listener = () => void | Promise;
+ type PendingResponse = {
+ readonly resolve: (response: {
+ readonly ok: boolean;
+ readonly status: number;
+ readonly json: () => Promise>;
+ }) => void;
+ };
+ class FakeElement {
+ readonly listeners = new Map();
+ hidden = false;
+ textContent = "";
+
+ addEventListener(name: string, listener: Listener): void {
+ this.listeners.set(name, listener);
+ }
+ }
+ class FakeButton extends FakeElement {
+ disabled = false;
+ }
+ class FakeSelect extends FakeElement {
+ constructor(public value: string) {
+ super();
+ }
+
+ focus(): void {}
+ }
+ class FakeTextArea extends FakeElement {
+ value = "";
+ }
+
+ const status = new FakeElement();
+ const completionView = new FakeElement();
+ const select = new FakeSelect("claude-desktop");
+ const generate = new FakeButton();
+ const target = new FakeElement();
+ const json = new FakeTextArea();
+ const guidance = new FakeElement();
+ const copy = new FakeButton();
+ const handoff = new FakeElement();
+ const switching = new FakeElement();
+ const pending: PendingResponse[] = [];
+ runInNewContext(javascript, {
+ document: {
+ getElementById(id: string): unknown {
+ if (id === "status") return status;
+ if (id === "setup-completion-view") return completionView;
+ if (id === "setup-completion-client-select") return select;
+ if (id === "setup-completion-generate-entry") return generate;
+ if (id === "setup-completion-client-target") return target;
+ if (id === "setup-completion-client-json") return json;
+ if (id === "setup-completion-client-guidance") return guidance;
+ if (id === "setup-completion-copy-json") return copy;
+ if (id === "setup-completion-handoff") return handoff;
+ if (id === "setup-completion-switch") return switching;
+ return undefined;
+ },
+ querySelectorAll: () => []
+ },
+ HTMLFormElement: class {},
+ HTMLSelectElement: FakeSelect,
+ HTMLElement: FakeElement,
+ HTMLInputElement: class {},
+ HTMLButtonElement: FakeButton,
+ HTMLTextAreaElement: FakeTextArea,
+ Element: FakeElement,
+ navigator: { clipboard: { writeText: async () => undefined } },
+ fetch: async () => new Promise((resolve) => pending.push({ resolve }))
+ });
+
+ const click = generate.listeners.get("click");
+ const change = select.listeners.get("change");
+ if (click === undefined || change === undefined) throw new Error("Expected setup completion client handlers.");
+
+ const successRequest = click();
+ select.value = "cursor";
+ await change();
+ select.value = "claude-desktop";
+ await change();
+ status.textContent = "Current setup is ready.";
+ pending[0]?.resolve({
+ ok: true,
+ status: 200,
+ json: async () => ({
+ data: [{
+ target: { label: "Claude Desktop settings" },
+ json: "{\"mcpServers\":{\"stale\":{}}}",
+ guidance: "Stale guidance"
+ }]
+ })
+ });
+ await successRequest;
+ const success = { json: json.value, status: status.textContent };
+
+ const failureRequest = click();
+ select.value = "cursor";
+ await change();
+ select.value = "claude-desktop";
+ await change();
+ status.textContent = "Newer setup is ready.";
+ pending[1]?.resolve({
+ ok: false,
+ status: 500,
+ json: async () => ({ error: { code: "snippet_failed", message: "Stale request failed." } })
+ });
+ await failureRequest;
+ return { success, failure: { json: json.value, status: status.textContent } };
+}
+
+async function recoverSetupCompletionAfterUnlock(
+ javascript: string,
+ refreshFailure: "none" | "config" | "late" | "racing-recovery" | "overlapping-reauth" | "setup-origin-overlap" | "current-setup-failure" | "control-recovery" = "none"
+): Promise<{
+ readonly locked: { readonly completionHidden: boolean; readonly focus: string };
+ readonly afterRefreshFailure: { readonly completionHidden: boolean; readonly focus: string };
+ readonly afterReauthentication: { readonly completionHidden: boolean; readonly focus: string };
+ readonly recovered: { readonly completionHidden: boolean; readonly focus: string };
+ readonly statusPreserved: boolean;
+ readonly setupRefreshErrorName?: string;
+ readonly setupRefreshErrorMessage?: string;
+ readonly persistentActionsEnabled?: boolean;
+}> {
+ class FakeElement {
+ hidden = false;
+ textContent = "";
+
+ scrollIntoView(): void {}
+ }
+ class FakeInput extends FakeElement {
+ focus(): void {
+ focused = "bootstrap";
+ }
+ }
+ class FakeSelect extends FakeElement {
+ value = "claude-desktop";
+
+ addEventListener(): void {}
+
+ focus(): void {
+ focused = "client";
+ }
+ }
+ class FakeButton extends FakeElement {
+ disabled = false;
+
+ addEventListener(): void {}
+ }
+ class FakeTextArea extends FakeElement {
+ value = "";
+ }
+
+ let focused = "";
+ let locked = refreshFailure !== "setup-origin-overlap" && refreshFailure !== "current-setup-failure";
+ let activeRefreshFailure = refreshFailure;
+ let releaseDelayedRecovery: (() => void) | undefined;
+ const delayedRecovery = new Promise((resolve) => {
+ releaseDelayedRecovery = resolve;
+ });
+ let markOverlapRecoveryRequested: (() => void) | undefined;
+ const overlapRecoveryRequested = new Promise((resolve) => {
+ markOverlapRecoveryRequested = resolve;
+ });
+ let releaseFirstOverlapRecovery: (() => void) | undefined;
+ const firstOverlapRecovery = new Promise((resolve) => {
+ releaseFirstOverlapRecovery = resolve;
+ });
+ const unlock = new FakeElement();
+ unlock.hidden = true;
+ const dashboard = new FakeElement();
+ const status = new FakeElement();
+ const completion = new FakeElement();
+ completion.hidden = true;
+ const bootstrap = new FakeInput();
+ const client = new FakeSelect();
+ const generate = new FakeButton();
+ const clientJson = new FakeTextArea();
+ const copy = new FakeButton();
+ copy.disabled = true;
+ const saveDraft = new FakeButton();
+ const resumeDraft = new FakeButton();
+ const runReadiness = new FakeButton();
+ const sandbox: Record = {
+ document: {
+ getElementById(id: string): unknown {
+ if (id === "status") return status;
+ if (id === "unlock-view") return unlock;
+ if (id === "dashboard-view") return dashboard;
+ if (id === "bootstrap") return bootstrap;
+ if (id === "setup-completion-view") return completion;
+ if (id === "setup-completion-client-select") return client;
+ if (id === "setup-completion-generate-entry") return generate;
+ if (id === "setup-completion-client-json") return clientJson;
+ if (id === "setup-completion-copy-json") return copy;
+ if (id === "save-setup-draft") return saveDraft;
+ if (id === "resume-setup-draft") return resumeDraft;
+ if (id === "run-profile-readiness") return runReadiness;
+ return undefined;
+ },
+ querySelectorAll: () => []
+ },
+ HTMLFormElement: class {},
+ HTMLSelectElement: FakeSelect,
+ HTMLElement: FakeElement,
+ HTMLInputElement: FakeInput,
+ HTMLButtonElement: FakeButton,
+ HTMLTextAreaElement: FakeTextArea,
+ Element: FakeElement,
+ navigator: { clipboard: { writeText: async () => undefined } },
+ fetch: async (path: unknown) => {
+ const requestPath = String(path);
+ if (locked) {
+ return {
+ ok: false,
+ status: 401,
+ json: async () => ({ error: { code: "session_expired", message: "Session expired." } })
+ };
+ }
+ if (requestPath === "/api/v1/session") {
+ return {
+ ok: true,
+ status: 200,
+ json: async () => ({ data: { csrfToken: "x".repeat(32) } })
+ };
+ }
+ if (activeRefreshFailure === "config" && requestPath === "/api/v1/config") {
+ return {
+ ok: false,
+ status: 500,
+ json: async () => ({ error: { code: "config_failed", message: "Configuration refresh failed." } })
+ };
+ }
+ if (activeRefreshFailure === "current-setup-failure" && requestPath === "/api/v1/config") {
+ return {
+ ok: false,
+ status: 500,
+ json: async () => ({ error: { code: "config_failed", message: "Configuration refresh failed." } })
+ };
+ }
+ if (activeRefreshFailure === "overlapping-reauth" && requestPath === "/api/v1/health") {
+ markOverlapRecoveryRequested?.();
+ await firstOverlapRecovery;
+ return {
+ ok: false,
+ status: 401,
+ json: async () => ({ error: { code: "session_expired", message: "Session expired." } })
+ };
+ }
+ if (activeRefreshFailure === "overlapping-reauth" && requestPath === "/api/v1/connections") {
+ await delayedRecovery;
+ return {
+ ok: false,
+ status: 401,
+ json: async () => ({ error: { code: "session_expired", message: "Session expired." } })
+ };
+ }
+ if (activeRefreshFailure === "overlapping-reauth" && requestPath === "/api/v1/audit?limit=50") {
+ return { ok: true, status: 200, json: async () => ({ data: [] }) };
+ }
+ if (activeRefreshFailure === "setup-origin-overlap" && requestPath === "/api/v1/health") {
+ markOverlapRecoveryRequested?.();
+ return {
+ ok: false,
+ status: 401,
+ json: async () => ({ error: { code: "session_expired", message: "Session expired." } })
+ };
+ }
+ if (activeRefreshFailure === "setup-origin-overlap" && requestPath === "/api/v1/connections") {
+ await delayedRecovery;
+ return { ok: true, status: 200, json: async () => ({ data: [] }) };
+ }
+ if (activeRefreshFailure === "setup-origin-overlap" && requestPath === "/api/v1/audit?limit=50") {
+ return { ok: true, status: 200, json: async () => ({ data: [] }) };
+ }
+ if (activeRefreshFailure === "racing-recovery" && requestPath === "/api/v1/connections") {
+ await new Promise((resolve) => setTimeout(resolve, 10));
+ return {
+ ok: false,
+ status: 401,
+ json: async () => ({ error: { code: "session_expired", message: "Session expired." } })
+ };
+ }
+ if ((activeRefreshFailure === "late" || activeRefreshFailure === "racing-recovery") && requestPath !== "/api/v1/config") {
+ return requestPath === "/api/v1/health"
+ ? {
+ ok: false,
+ status: 500,
+ json: async () => ({ error: { code: "health_failed", message: "Health refresh failed." } })
+ }
+ : { ok: true, status: 200, json: async () => ({ data: [] }) };
+ }
+ return {
+ ok: true,
+ status: 200,
+ json: async () => ({
+ data: activeRefreshFailure !== "none"
+ ? {
+ initialized: true,
+ name: "analytics",
+ version: "1",
+ defaultProfile: "default",
+ profiles: [{ name: "default" }],
+ upstreams: []
+ }
+ : {
+ initialized: false,
+ catalog: {
+ configurations: [{ name: "analytics", profileCount: 1 }],
+ readyCount: 1,
+ attentionCount: 0
+ }
+ }
+ })
+ };
+ }
+ };
+ const instrumented = javascript.replace(
+ "\n})();",
+ "\nglobalThis.__miftahTest = { refreshAfterSetup, resumeSession };\n})();"
+ );
+ runInNewContext(instrumented, sandbox);
+ const harness = sandbox.__miftahTest as {
+ readonly refreshAfterSetup: (completion: Record) => Promise;
+ readonly resumeSession: () => Promise;
+ };
+ const waitForUnlock = async (): Promise => {
+ for (let attempt = 0; attempt < 100; attempt += 1) {
+ if (!unlock.hidden) return;
+ await new Promise((resolve) => setTimeout(resolve, 0));
+ }
+ throw new Error("Expected session recovery to reveal the unlock view.");
+ };
+ let setupRefreshErrorName = "";
+ let setupRefreshErrorMessage = "";
+ const setupRefresh = harness.refreshAfterSetup({ setup: { name: "analytics", defaultProfile: "default", profileCount: 1 } })
+ .catch((error: unknown) => {
+ const failure = typeof error === "object" && error !== null
+ ? error as { readonly name?: unknown; readonly message?: unknown }
+ : {};
+ setupRefreshErrorName = typeof failure.name === "string" ? failure.name : "NonError";
+ setupRefreshErrorMessage = typeof failure.message === "string" ? failure.message : String(error);
+ });
+ if (refreshFailure === "setup-origin-overlap") {
+ await overlapRecoveryRequested;
+ await waitForUnlock();
+ const lockedState = { completionHidden: completion.hidden, focus: focused };
+ const afterRefreshFailure = { completionHidden: completion.hidden, focus: focused };
+ activeRefreshFailure = "none";
+ await harness.resumeSession();
+ const afterReauthentication = { completionHidden: completion.hidden, focus: focused };
+ const reauthenticatedStatus = status.textContent;
+ releaseDelayedRecovery?.();
+ await setupRefresh;
+ return {
+ locked: lockedState,
+ afterRefreshFailure,
+ afterReauthentication,
+ recovered: { completionHidden: completion.hidden, focus: focused },
+ statusPreserved: status.textContent === reauthenticatedStatus,
+ setupRefreshErrorName
+ };
+ }
+ await setupRefresh;
+ if (refreshFailure === "current-setup-failure") {
+ const state = { completionHidden: completion.hidden, focus: focused };
+ return {
+ locked: state,
+ afterRefreshFailure: state,
+ afterReauthentication: state,
+ recovered: state,
+ statusPreserved: true,
+ setupRefreshErrorMessage
+ };
+ }
+ const lockedState = { completionHidden: completion.hidden, focus: focused };
+ if (refreshFailure === "control-recovery") {
+ saveDraft.disabled = true;
+ resumeDraft.disabled = true;
+ runReadiness.disabled = true;
+ locked = false;
+ await harness.resumeSession();
+ const state = { completionHidden: completion.hidden, focus: focused };
+ return {
+ locked: lockedState,
+ afterRefreshFailure: state,
+ afterReauthentication: state,
+ recovered: state,
+ statusPreserved: true,
+ persistentActionsEnabled: !saveDraft.disabled && !resumeDraft.disabled && !runReadiness.disabled
+ };
+ }
+ locked = false;
+ let afterRefreshFailure: { readonly completionHidden: boolean; readonly focus: string };
+ let afterReauthentication: { readonly completionHidden: boolean; readonly focus: string };
+ if (refreshFailure === "overlapping-reauth") {
+ const staleResume = harness.resumeSession();
+ await overlapRecoveryRequested;
+ releaseFirstOverlapRecovery?.();
+ await waitForUnlock();
+ afterRefreshFailure = { completionHidden: completion.hidden, focus: focused };
+ activeRefreshFailure = "none";
+ await harness.resumeSession();
+ afterReauthentication = { completionHidden: completion.hidden, focus: focused };
+ releaseDelayedRecovery?.();
+ await staleResume;
+ } else {
+ await harness.resumeSession();
+ afterRefreshFailure = { completionHidden: completion.hidden, focus: focused };
+ if (refreshFailure === "racing-recovery") {
+ activeRefreshFailure = "none";
+ await harness.resumeSession();
+ }
+ afterReauthentication = { completionHidden: completion.hidden, focus: focused };
+ }
+ const reauthenticatedStatus = status.textContent;
+ return {
+ locked: lockedState,
+ afterRefreshFailure,
+ afterReauthentication,
+ recovered: { completionHidden: completion.hidden, focus: focused },
+ statusPreserved: status.textContent === reauthenticatedStatus
+ };
+}
+
+async function settleStaleConfigurationSelectionAfterReauthentication(
+ javascript: string,
+ outcome: "success" | "failure"
+): Promise<{
+ readonly before: { readonly completionHidden: boolean; readonly focus: string; readonly status: string };
+ readonly after: { readonly completionHidden: boolean; readonly focus: string; readonly status: string };
+}> {
+ type Response = {
+ readonly ok: boolean;
+ readonly status: number;
+ readonly json: () => Promise>;
+ };
+ class FakeElement {
+ hidden = false;
+ textContent = "";
+
+ scrollIntoView(): void {}
+ }
+ class FakeInput extends FakeElement {
+ focus(): void {
+ focused = "bootstrap";
+ }
+ }
+ class FakeSelect extends FakeElement {
+ value = "claude-desktop";
+
+ addEventListener(): void {}
+
+ focus(): void {
+ focused = "client";
+ }
+ }
+ class FakeButton extends FakeElement {
+ disabled = false;
+
+ addEventListener(): void {}
+ }
+ class FakeTextArea extends FakeElement {
+ value = "";
+ }
+
+ let focused = "";
+ let locked = true;
+ let releaseSelection: ((response: Response) => void) | undefined;
+ const status = new FakeElement();
+ const unlock = new FakeElement();
+ unlock.hidden = true;
+ const dashboard = new FakeElement();
+ const completion = new FakeElement();
+ completion.hidden = true;
+ const bootstrap = new FakeInput();
+ const client = new FakeSelect();
+ const generate = new FakeButton();
+ const clientJson = new FakeTextArea();
+ const copy = new FakeButton();
+ copy.disabled = true;
+ const sandbox: Record = {
+ document: {
+ getElementById(id: string): unknown {
+ if (id === "status") return status;
+ if (id === "unlock-view") return unlock;
+ if (id === "dashboard-view") return dashboard;
+ if (id === "bootstrap") return bootstrap;
+ if (id === "setup-completion-view") return completion;
+ if (id === "setup-completion-client-select") return client;
+ if (id === "setup-completion-generate-entry") return generate;
+ if (id === "setup-completion-client-json") return clientJson;
+ if (id === "setup-completion-copy-json") return copy;
+ return undefined;
+ },
+ querySelectorAll: () => []
+ },
+ HTMLFormElement: class {},
+ HTMLSelectElement: FakeSelect,
+ HTMLElement: FakeElement,
+ HTMLInputElement: FakeInput,
+ HTMLButtonElement: FakeButton,
+ HTMLTextAreaElement: FakeTextArea,
+ Element: FakeElement,
+ navigator: { clipboard: { writeText: async () => undefined } },
+ fetch: async (path: unknown): Promise => {
+ const requestPath = String(path);
+ if (requestPath.includes("/configurations/analytics/select")) {
+ return await new Promise((resolve) => {
+ releaseSelection = resolve;
+ });
+ }
+ if (locked) {
+ return {
+ ok: false,
+ status: 401,
+ json: async () => ({ error: { code: "session_expired", message: "Session expired." } })
+ };
+ }
+ if (requestPath === "/api/v1/session") {
+ return {
+ ok: true,
+ status: 200,
+ json: async () => ({ data: { csrfToken: "x".repeat(32) } })
+ };
+ }
+ return {
+ ok: true,
+ status: 200,
+ json: async () => ({
+ data: {
+ initialized: false,
+ catalog: {
+ configurations: [{ name: "analytics", profileCount: 1 }],
+ readyCount: 1,
+ attentionCount: 0
+ }
+ }
+ })
+ };
+ }
+ };
+ const instrumented = javascript.replace(
+ "\n})();",
+ "\nglobalThis.__miftahTest = { api, errorMessage, message, refresh, refreshAfterSetup, resumeSession };\n})();"
+ );
+ runInNewContext(instrumented, sandbox);
+ const harness = sandbox.__miftahTest as {
+ readonly api: (path: string, options?: Record) => Promise;
+ readonly errorMessage: (error: unknown) => string;
+ readonly message: (text: string) => void;
+ readonly refresh: () => Promise;
+ readonly refreshAfterSetup: (completion: Record) => Promise;
+ readonly resumeSession: () => Promise;
+ };
+
+ await harness.refreshAfterSetup({ setup: { name: "analytics", defaultProfile: "default", profileCount: 1 } })
+ .catch(() => undefined);
+ locked = false;
+ const staleSelection = harness.api("/api/v1/configurations/analytics/select", { method: "POST", body: {} })
+ .then(async () => await harness.refresh())
+ .catch((error) => harness.message(harness.errorMessage(error)));
+ await harness.resumeSession();
+ const before = { completionHidden: completion.hidden, focus: focused, status: status.textContent };
+ releaseSelection?.(outcome === "success"
+ ? { ok: true, status: 200, json: async () => ({ data: {} }) }
+ : {
+ ok: false,
+ status: 500,
+ json: async () => ({ error: { code: "selection_failed", message: "Stale selection failed." } })
+ });
+ await staleSelection;
+ return {
+ before,
+ after: { completionHidden: completion.hidden, focus: focused, status: status.textContent }
+ };
+}
+
async function reviewThenCreatePresetForm(
javascript: string,
reviewOptions: {
@@ -808,7 +1388,7 @@ async function resumeMissingSetupDraft(javascript: string): Promise<{
readonly controlsUpdated: number;
readonly message: string;
}> {
- const start = javascript.indexOf("if (resumeSetupDraft instanceof HTMLButtonElement)");
+ const start = javascript.indexOf("\n if (resumeSetupDraft instanceof HTMLButtonElement)") + 1;
const end = javascript.indexOf("\n\n if (discardSetupDraft instanceof HTMLButtonElement)", start);
if (start < 0 || end < 0) throw new Error("Expected the saved setup-draft resume action.");
@@ -824,7 +1404,7 @@ async function resumeMissingSetupDraft(javascript: string): Promise<{
let restored = false;
let controlsUpdated = 0;
let status = "";
- runInNewContext(`let activeSetupDraft = { revision: 1 };\n${javascript.slice(start, end)}`, {
+ runInNewContext(`let activeSetupDraft = { revision: 1 };\nlet authenticationEpoch = 0;\n${javascript.slice(start, end)}`, {
resumeSetupDraft: button,
HTMLButtonElement: FakeButton,
api: async () => null,
@@ -853,7 +1433,7 @@ async function saveSetupDraftOnlyOnce(javascript: string): Promise<{
readonly disabledWhilePending: boolean;
readonly enabledAfterCompletion: boolean;
}> {
- const start = javascript.indexOf("if (saveSetupDraft instanceof HTMLButtonElement)");
+ const start = javascript.indexOf("\n if (saveSetupDraft instanceof HTMLButtonElement)") + 1;
const end = javascript.indexOf("\n\n if (resumeSetupDraft instanceof HTMLButtonElement)", start);
if (start < 0 || end < 0) throw new Error("Expected the saved setup-draft save action.");
@@ -872,6 +1452,7 @@ async function saveSetupDraftOnlyOnce(javascript: string): Promise<{
let releaseRequest: () => void = () => undefined;
const request = new Promise((resolve) => { releaseRequest = resolve; });
runInNewContext(javascript.slice(start, end), {
+ authenticationEpoch: 0,
saveSetupDraft: button,
HTMLButtonElement: FakeButton,
message(): void {},
@@ -1666,6 +2247,318 @@ describe("local Console control server", () => {
}
});
+ it("reports MCP connections and hides rejected-file help until the catalog needs attention", async () => {
+ const server = await startConsoleServer(await writeConfig(), {
+ bootstrapCredential: "test-only-bootstrap-credential"
+ });
+
+ try {
+ const page = await fetch(server.url);
+ expect(page.status).toBe(200);
+ const html = await page.text();
+ expect(html).toMatch(/]* hidden>/u);
+
+ const script = await fetch(new URL("/app.js", server.url));
+ expect(script.status).toBe(200);
+ const javascript = await script.text();
+ expect(javascript).toContain('catalog.discoveredCount === 1 ? "MCP connection found" : "MCP connections found"');
+ expect(javascript).not.toContain("configuration files found");
+ expect(javascript).toContain("configurationCatalogRejectedGuidance.hidden = catalog.attentionCount === 0;");
+ } finally {
+ await server.close();
+ }
+ });
+
+ it("offers client-specific copyable account-switch requests without claiming Console controls the session", async () => {
+ const server = await startConsoleServer(await writeConfig(), {
+ bootstrapCredential: "test-only-bootstrap-credential"
+ });
+
+ try {
+ const page = await fetch(server.url);
+ expect(page.status).toBe(200);
+ const html = await page.text();
+ expect(html).toContain('id="catalog-client-select"');
+ expect(html).toContain('Claude Desktop ');
+ expect(html).toContain('Claude Code ');
+ expect(html).toContain('Cursor ');
+ expect(html).toContain('VS Code ');
+
+ const script = await fetch(new URL("/app.js", server.url));
+ expect(script.status).toBe(200);
+ const javascript = await script.text();
+ expect(javascript).toContain("function profileSwitchRequest(client, profile)");
+ expect(javascript).toContain("Use the Miftah account named");
+ expect(javascript).toContain("Copy switch request");
+ expect(javascript).toContain("navigator.clipboard.writeText(profileSwitchRequest(client, profile))");
+ expect(javascript).toContain("Console does not switch a running client session.");
+ expect(javascript).not.toContain("Live account switch: use miftah_use_profile in your MCP client.");
+ } finally {
+ await server.close();
+ }
+ });
+
+ it("groups connection management behind accessible task navigation", async () => {
+ const server = await startConsoleServer(await writeConfig(), {
+ bootstrapCredential: "test-only-bootstrap-credential"
+ });
+
+ try {
+ const page = await fetch(server.url);
+ expect(page.status).toBe(200);
+ const html = await page.text();
+ expect(html).toContain('');
+ expect(html).toContain('href="#connection-overview">Overview');
+ expect(html).toContain('href="#connection-accounts">Accounts');
+ expect(html).toContain('href="#connection-authentication">Authentication');
+ expect(html).toContain('href="#connection-client-setup">Client setup');
+ expect(html).toContain('href="#connection-audit">Audit');
+ expect(html).toContain('id="connection-overview"');
+ expect(html).toContain('id="connection-accounts"');
+ expect(html).toContain('id="connection-authentication"');
+ expect(html).toContain('id="connection-client-setup"');
+ expect(html).toContain('id="connection-audit"');
+
+ const accounts = html.slice(
+ html.indexOf('id="connection-accounts"'),
+ html.indexOf('id="connection-authentication"')
+ );
+ expect(accounts).toContain('id="confirm-profile-removal"');
+ expect(accounts).toContain('id="remove-profile"');
+ } finally {
+ await server.close();
+ }
+ });
+
+ it("keeps the selected setup form ahead of optional authentication theory", async () => {
+ const server = await startConsoleServer(await writeConfig(), {
+ bootstrapCredential: "test-only-bootstrap-credential"
+ });
+
+ try {
+ const page = await fetch(server.url);
+ expect(page.status).toBe(200);
+ const html = await page.text();
+ const wizard = html.indexOf('id="setup-wizard-view"');
+ const connectorForm = html.indexOf('id="preset-onboarding-view"');
+ const importForm = html.indexOf('id="client-entry-onboarding-view"');
+ const browserSignInForm = html.indexOf('id="onboarding-view"');
+ const authenticationReference = html.indexOf('id="authentication-guide"');
+
+ expect(wizard).toBeGreaterThan(-1);
+ expect(connectorForm).toBeGreaterThan(wizard);
+ expect(importForm).toBeGreaterThan(connectorForm);
+ expect(browserSignInForm).toBeGreaterThan(importForm);
+ expect(authenticationReference).toBeGreaterThan(browserSignInForm);
+ expect(html).toContain("How authentication works ");
+ expect(html).not.toContain('');
+ expect(html).toContain("Connection type");
+ expect(html).toContain('');
+ expect(html).toContain('');
+ expect(html).not.toContain("Known connector");
+ expect(html).not.toContain('id="native-oauth-setup-link"');
+ } finally {
+ await server.close();
+ }
+ });
+
+ it("finishes setup with a client-specific install, verification, second-account, and switching handoff", async () => {
+ const server = await startConsoleServer(await writeConfig(), {
+ bootstrapCredential: "test-only-bootstrap-credential"
+ });
+
+ try {
+ const page = await fetch(server.url);
+ expect(page.status).toBe(200);
+ const html = await page.text();
+ expect(html).toContain('id="setup-completion-created"');
+ expect(html).toContain('id="setup-completion-client-select"');
+ expect(html).toContain('id="setup-completion-generate-entry"');
+ expect(html).toContain('id="setup-completion-client-target"');
+ expect(html).toContain('id="setup-completion-client-json"');
+ expect(html).toContain('id="setup-completion-copy-json"');
+ expect(html).toContain('id="setup-completion-readiness"');
+ expect(html).toContain('id="setup-completion-second-account"');
+ expect(html).toContain('id="setup-completion-switch"');
+
+ const script = await fetch(new URL("/app.js", server.url));
+ expect(script.status).toBe(200);
+ const javascript = await script.text();
+ expect(javascript).toContain("function completionFromSetupResult(result");
+ expect(javascript).toContain("Created Miftah connection");
+ expect(javascript).toContain('/api/v1/client-snippets?client=');
+ expect(javascript).toContain("target.label");
+ expect(javascript).toContain("restart or reconnect");
+ expect(javascript).toContain("Open Manage connection");
+ expect(javascript).toContain("copy its switch request");
+ expect(javascript).toContain("A generated entry does not prove that a credential works");
+ expect(javascript).toContain("async function refreshAfterSetup(completion)");
+ expect(javascript.match(/await refreshAfterSetup\(completion\);/gu)).toHaveLength(3);
+ expect(javascript).toContain("setupCompletionView.scrollIntoView");
+ expect(javascript).toContain("setupCompletionClientSelect.focus");
+ expect(javascript).toContain('if (setupCompletionHandoff) setupCompletionHandoff.textContent = "";');
+ expect(javascript).toContain("function setupCompletionRequestIsCurrent(generation, client)");
+ } finally {
+ await server.close();
+ }
+ });
+
+ it("ignores superseded setup-completion client responses after the client choice changes", async () => {
+ const server = await startConsoleServer(await writeConfig(), {
+ bootstrapCredential: "test-only-bootstrap-credential"
+ });
+
+ try {
+ const script = await fetch(new URL("/app.js", server.url));
+ expect(script.status).toBe(200);
+ const javascript = await script.text();
+ await expect(supersedeSetupCompletionClientRequests(javascript)).resolves.toEqual({
+ success: { json: "", status: "Current setup is ready." },
+ failure: { json: "", status: "Newer setup is ready." }
+ });
+ } finally {
+ await server.close();
+ }
+ });
+
+ it("keeps setup completion behind unlock recovery and restores it after reauthentication", async () => {
+ const server = await startConsoleServer(await writeConfig(), {
+ bootstrapCredential: "test-only-bootstrap-credential"
+ });
+
+ try {
+ const script = await fetch(new URL("/app.js", server.url));
+ expect(script.status).toBe(200);
+ const javascript = await script.text();
+ await expect(recoverSetupCompletionAfterUnlock(javascript)).resolves.toEqual({
+ locked: { completionHidden: true, focus: "bootstrap" },
+ afterRefreshFailure: { completionHidden: false, focus: "client" },
+ afterReauthentication: { completionHidden: false, focus: "client" },
+ recovered: { completionHidden: false, focus: "client" },
+ statusPreserved: true
+ });
+ await expect(recoverSetupCompletionAfterUnlock(javascript, "current-setup-failure")).resolves.toEqual({
+ locked: { completionHidden: false, focus: "client" },
+ afterRefreshFailure: { completionHidden: false, focus: "client" },
+ afterReauthentication: { completionHidden: false, focus: "client" },
+ recovered: { completionHidden: false, focus: "client" },
+ statusPreserved: true,
+ setupRefreshErrorMessage: "Configuration refresh failed."
+ });
+ await expect(recoverSetupCompletionAfterUnlock(javascript, "control-recovery")).resolves.toEqual({
+ locked: { completionHidden: true, focus: "bootstrap" },
+ afterRefreshFailure: { completionHidden: false, focus: "client" },
+ afterReauthentication: { completionHidden: false, focus: "client" },
+ recovered: { completionHidden: false, focus: "client" },
+ statusPreserved: true,
+ persistentActionsEnabled: true
+ });
+ await expect(recoverSetupCompletionAfterUnlock(javascript, "late")).resolves.toEqual({
+ locked: { completionHidden: true, focus: "bootstrap" },
+ afterRefreshFailure: { completionHidden: false, focus: "client" },
+ afterReauthentication: { completionHidden: false, focus: "client" },
+ recovered: { completionHidden: false, focus: "client" },
+ statusPreserved: true
+ });
+ await expect(recoverSetupCompletionAfterUnlock(javascript, "config")).resolves.toEqual({
+ locked: { completionHidden: true, focus: "bootstrap" },
+ afterRefreshFailure: { completionHidden: false, focus: "client" },
+ afterReauthentication: { completionHidden: false, focus: "client" },
+ recovered: { completionHidden: false, focus: "client" },
+ statusPreserved: true
+ });
+ await expect(recoverSetupCompletionAfterUnlock(javascript, "racing-recovery")).resolves.toEqual({
+ locked: { completionHidden: true, focus: "bootstrap" },
+ afterRefreshFailure: { completionHidden: true, focus: "bootstrap" },
+ afterReauthentication: { completionHidden: false, focus: "client" },
+ recovered: { completionHidden: false, focus: "client" },
+ statusPreserved: true
+ });
+ await expect(recoverSetupCompletionAfterUnlock(javascript, "overlapping-reauth")).resolves.toEqual({
+ locked: { completionHidden: true, focus: "bootstrap" },
+ afterRefreshFailure: { completionHidden: true, focus: "bootstrap" },
+ afterReauthentication: { completionHidden: false, focus: "client" },
+ recovered: { completionHidden: false, focus: "client" },
+ statusPreserved: true
+ });
+ await expect(recoverSetupCompletionAfterUnlock(javascript, "setup-origin-overlap")).resolves.toEqual({
+ locked: { completionHidden: true, focus: "bootstrap" },
+ afterRefreshFailure: { completionHidden: true, focus: "bootstrap" },
+ afterReauthentication: { completionHidden: false, focus: "client" },
+ recovered: { completionHidden: false, focus: "client" },
+ statusPreserved: true,
+ setupRefreshErrorName: "MiftahStaleAuthenticationRequest"
+ });
+ } finally {
+ await server.close();
+ }
+ });
+
+ it("ignores stale configuration-selection success after reauthentication", async () => {
+ const server = await startConsoleServer(await writeConfig(), {
+ bootstrapCredential: "test-only-bootstrap-credential"
+ });
+
+ try {
+ const script = await fetch(new URL("/app.js", server.url));
+ expect(script.status).toBe(200);
+ const javascript = await script.text();
+ const state = await settleStaleConfigurationSelectionAfterReauthentication(javascript, "success");
+ expect(state.before.completionHidden).toBe(false);
+ expect(state.before.focus).toBe("client");
+ expect(state.after).toEqual(state.before);
+ } finally {
+ await server.close();
+ }
+ });
+
+ it("ignores stale configuration-selection failure after reauthentication", async () => {
+ const server = await startConsoleServer(await writeConfig(), {
+ bootstrapCredential: "test-only-bootstrap-credential"
+ });
+
+ try {
+ const script = await fetch(new URL("/app.js", server.url));
+ expect(script.status).toBe(200);
+ const javascript = await script.text();
+ const state = await settleStaleConfigurationSelectionAfterReauthentication(javascript, "failure");
+ expect(state.before.completionHidden).toBe(false);
+ expect(state.before.focus).toBe("client");
+ expect(state.after).toEqual(state.before);
+ } finally {
+ await server.close();
+ }
+ });
+
+ it("keeps new Console actions keyboard-visible, announced, target-sized, and responsive", async () => {
+ const server = await startConsoleServer(await writeConfig(), {
+ bootstrapCredential: "test-only-bootstrap-credential"
+ });
+
+ try {
+ const page = await fetch(server.url);
+ expect(page.status).toBe(200);
+ const html = await page.text();
+ expect(html).toContain('id="setup-completion-readiness" role="status" aria-live="polite" aria-atomic="true"');
+ expect(html).toContain('aria-label="Connection tasks"');
+
+ const stylesheet = await fetch(new URL("/app.css", server.url));
+ expect(stylesheet.status).toBe(200);
+ const css = await stylesheet.text();
+ expect(css).toContain("button:focus-visible, summary:focus-visible");
+ expect(css).toContain("#workspace-task-navigation a:focus-visible");
+ expect(css).toContain(".configuration-profiles button { min-height: 2.75rem;");
+ expect(css).toContain(".setup-completion-copy { grid-template-columns: 1fr;");
+
+ const script = await fetch(new URL("/app.js", server.url));
+ expect(script.status).toBe(200);
+ const javascript = await script.text();
+ expect(javascript).toContain('button.setAttribute("aria-label", "Copy switch request for " + profile + " in " + clientName)');
+ } finally {
+ await server.close();
+ }
+ });
+
it("serves a navigation-safe local dashboard shell without exposing bootstrap credentials", async () => {
const server = await startConsoleServer(await writeConfig(), {
bootstrapCredential: "test-only-bootstrap-credential"
@@ -1709,7 +2602,6 @@ describe("local Console control server", () => {
expect(html).not.toContain('aria-pressed=');
expect(html).toContain("Local executable + argument array");
expect(html).toContain("Remote HTTPS MCP endpoint");
- expect(html).toContain('id="native-oauth-setup-link"');
expect(html).toContain("Remote MCP with browser sign-in");
expect(html).toContain("Check sign-in and create profile");
expect(html).toContain("Miftah checks this exact HTTPS endpoint for supported browser sign-in before it creates the configuration.");
@@ -1857,7 +2749,6 @@ describe("local Console control server", () => {
expect(discoveryUnavailableBranch).toContain("hideSetupWizardPaths()");
expect(discoveryUnavailableBranch).toContain("returningSetupVisible = false");
expect(javascript).toContain("Choose a short lowercase name");
- expect(javascript).toContain("native-oauth-setup-link");
expect(javascript).toContain("/api/v1/connections/discover");
expect(javascript).toContain("/api/v1/profiles/native-oauth/discover");
expect(javascript).toContain("/api/v1/profiles/provider-account");
@@ -1889,10 +2780,10 @@ describe("local Console control server", () => {
});
expect(javascript).toContain("if (resumeSetupDraft.disabled) return;");
expect(javascript).toContain("resumeSetupDraft.disabled = true;");
- expect(javascript).toContain("finally { resumeSetupDraft.disabled = false; }");
+ expect(javascript).toContain("if (authenticationEpoch === actionAuthenticationEpoch) resumeSetupDraft.disabled = false;");
expect(javascript).toContain("if (activeSetupDraft === undefined || discardSetupDraft.disabled) return;");
expect(javascript).toContain("discardSetupDraft.disabled = true;");
- expect(javascript).toContain("finally { discardSetupDraft.disabled = false; }");
+ expect(javascript).toContain("if (authenticationEpoch === actionAuthenticationEpoch) discardSetupDraft.disabled = false;");
expect(javascript).toContain("renderSetupCompletion");
expect(javascript).toContain("completion.environment");
expect(javascript).toContain("setupCompletionEnvironment");
@@ -1904,13 +2795,17 @@ describe("local Console control server", () => {
expect(refreshBody.indexOf("clearSetupCompletion();")).toBeLessThan(
refreshBody.indexOf('api("/api/v1/config")')
);
- const completionAssignments = [...javascript.matchAll(/setupCompletion = completion;/gu)];
- expect(completionAssignments).toHaveLength(3);
- for (const assignment of completionAssignments) {
- const assignmentIndex = assignment.index;
- expect(javascript.slice(assignmentIndex - 80, assignmentIndex)).toContain("await refresh();");
- }
- expect(javascript).toContain("configuration files found");
+ const completionAssignments = [...javascript.matchAll(/setupCompletion = value;/gu)];
+ expect(completionAssignments).toHaveLength(1);
+ const refreshAfterSetup = javascript.slice(
+ javascript.indexOf("async function refreshAfterSetup(completion)"),
+ javascript.indexOf("function clearSetupCompletion()")
+ );
+ expect(refreshAfterSetup.indexOf("await refresh();")).toBeLessThan(
+ refreshAfterSetup.indexOf("replaceSetupCompletion(completion);")
+ );
+ expect(refreshAfterSetup).toContain("finally");
+ expect(javascript).toContain("MCP connections found");
expect(javascript).toContain("need attention");
expect(javascript).toContain('"file-permissions": "private file permission"');
expect(javascript).not.toContain('"file-permissions": "private file permissions"');
diff --git a/tests/release-version.test.ts b/tests/release-version.test.ts
index 0a354de..1b800af 100644
--- a/tests/release-version.test.ts
+++ b/tests/release-version.test.ts
@@ -1,7 +1,7 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
-const releaseVersion = "0.5.5";
+const releaseVersion = "0.5.6";
function readRepositoryFile(path: string): string {
return readFileSync(new URL(`../${path}`, import.meta.url), "utf8");
@@ -21,15 +21,15 @@ function releaseNotes(changelog: string, version: string): string {
return changelog.slice(match.index, end < 0 ? undefined : end);
}
-describe("v0.5.5 release artifacts", () => {
+describe("v0.5.6 release artifacts", () => {
it.each([
{
name: "a non-zero-padded date",
- changelog: "## [0.5.5] - 2026-7-31\n\n### Changed\n"
+ changelog: "## [0.5.6] - 2026-7-31\n\n### Fixed\n"
},
{
name: "a heading that does not start its line",
- changelog: "Release candidate: ## [0.5.5] - 2026-07-31\n\n### Changed\n"
+ changelog: "Release candidate: ## [0.5.6] - 2026-07-31\n\n### Fixed\n"
}
])("rejects $name", ({ changelog }) => {
expect(() => releaseNotes(changelog, releaseVersion)).toThrow(
@@ -71,24 +71,22 @@ describe("v0.5.5 release artifacts", () => {
}
});
- it("documents the task-first Console while retaining the experimental package status", () => {
+ it("documents the actionable Console patch while retaining the experimental package status", () => {
const changelog = readRepositoryFile("CHANGELOG.md");
const notes = releaseNotes(changelog, releaseVersion);
expect(notes).toContain("Miftah remains experimental and pre-1.0");
- expect(notes).toContain("### Changed");
- const changedStart = notes.indexOf("### Changed");
- const changedEnd = notes.indexOf("\n### ", changedStart + "### Changed".length);
- const changedNotes = notes.slice(changedStart, changedEnd < 0 ? undefined : changedEnd);
- for (const issue of [202, 319]) {
- expect(changedNotes).toContain(`[#${issue}](https://github.com/mohanagy/miftah/issues/${issue})`);
+ expect(notes).toContain("### Fixed");
+ const fixedStart = notes.indexOf("### Fixed");
+ const fixedEnd = notes.indexOf("\n### ", fixedStart + "### Fixed".length);
+ const fixedNotes = notes.slice(fixedStart, fixedEnd < 0 ? undefined : fixedEnd);
+ for (const issue of [325, 327]) {
+ expect(fixedNotes).toContain(`[#${issue}](https://github.com/mohanagy/miftah/issues/${issue})`);
}
- expect(notes).toMatch(/task-first/iu);
- expect(notes).toMatch(
- /named account profiles, durable default, and whether live in-session switching through `miftah_use_profile` is available/iu
- );
- expect(notes).toContain("collapsed behind **How authentication works**");
- expect(notes).toContain("external validation remains incomplete under #25, #88, and #202");
+ expect(notes).toMatch(/setup completion/iu);
+ expect(notes).toMatch(/stale.*session.*responses/iu);
+ expect(notes).toMatch(/reauthentication/iu);
+ expect(notes).toContain("external validation remains incomplete under #25, #88, #202, and #290");
const readme = readRepositoryFile("README.md");
const featureGuide = readRepositoryFile("docs/whats-new-in-0.5.md");