fix(dashboard): restore shift-click suggestion submit

Default domain was retired slug "custodian", so POST /technical-debt/
returned 422 and the modal failed after open. Use infotech, resolve
apiBase like config.js, and surface API error detail in the toast.
This commit is contained in:
tegwick 2026-07-22 01:22:22 +02:00
parent 224584d379
commit c363549388
3 changed files with 116 additions and 7 deletions

View file

@ -12,10 +12,59 @@
* Submissions are stored as technical-debt items with debt_type="dashboard-improvement".
*
* Interaction:
* - Hold Shift cursor changes to crosshair across the entire page
* - Hold Shift 1s cursor changes to copy/highlight mode (shift-wait-click)
* - Shift+click any element (except form controls) opens suggestion modal
*/
/** Domain slug for dashboard-improvement TD records (state-hub lives under infotech). */
export const DEFAULT_IMPROVEMENT_DOMAIN = "infotech";
const DEFAULT_API = "http://127.0.0.1:8000";
const API_STORAGE_KEY = "stateHubApiBase";
/**
* Resolve API base when init is called without an explicit apiBase.
* Mirrors dashboard/src/components/config.js so the modal works when the
* dashboard is not on 127.0.0.1 (remote host, tunnel, storage override).
*/
export function resolveImprovementApiBase({
location = globalThis.location,
storage = globalThis.localStorage,
} = {}) {
const clean = (value) => {
if (typeof value !== "string") return null;
const cleaned = value.trim().replace(/\/+$/, "");
return cleaned || null;
};
try {
if (location?.href) {
const url = new URL(location.href);
for (const name of ["api_base", "apiBase"]) {
const q = clean(url.searchParams.get(name));
if (q) return q;
}
}
} catch { /* ignore */ }
const fromGlobal = clean(globalThis.STATE_HUB_API_BASE);
if (fromGlobal) return fromGlobal;
try {
const fromStorage = clean(storage?.getItem?.(API_STORAGE_KEY));
if (fromStorage) return fromStorage;
} catch { /* ignore */ }
try {
if (location?.href) {
const url = new URL(location.href);
if (!["http:", "https:"].includes(url.protocol)) return DEFAULT_API;
if (url.hostname === "::1" || url.hostname === "[::1]") return DEFAULT_API;
url.port = globalThis.STATE_HUB_API_PORT || "8000";
url.pathname = "";
url.search = "";
url.hash = "";
return url.origin;
}
} catch { /* ignore */ }
return DEFAULT_API;
}
const _STYLE_ID = "improvement-modal-styles";
function _ensureStyles() {
@ -239,10 +288,13 @@ let _initialized = false;
* Safe to call multiple times only the first call takes effect.
*
* @param {object} opts
* @param {string} opts.apiBase State Hub API base URL (default: "http://127.0.0.1:8000")
* @param {string} opts.domain Domain slug for the TD record (default: "custodian")
* @param {string} [opts.apiBase] State Hub API base URL (resolved like config.js if omitted)
* @param {string} [opts.domain] Domain slug for the TD record (default: "infotech")
*/
export function initImprovementModal({ apiBase = "http://127.0.0.1:8000", domain = "custodian" } = {}) {
export function initImprovementModal({
apiBase = resolveImprovementApiBase(),
domain = DEFAULT_IMPROVEMENT_DOMAIN,
} = {}) {
if (_initialized) return;
_initialized = true;
_ensureStyles();
@ -391,7 +443,10 @@ export function initImprovementModal({ apiBase = "http://127.0.0.1:8000", domain
try {
const r = await fetch(`${apiBase}/technical-debt/`, {
method: "POST",
headers: { "Content-Type": "application/json" },
headers: {
"Content-Type": "application/json",
"X-StateHub-Component": "state-hub.dashboard.improvement-modal",
},
body: JSON.stringify(payload),
});
if (r.ok) {
@ -400,7 +455,13 @@ export function initImprovementModal({ apiBase = "http://127.0.0.1:8000", domain
} else {
submitBtn.disabled = false;
submitBtn.textContent = "Submit suggestion";
_toast(`⚠ Submission failed (HTTP ${r.status})`);
let detail = `HTTP ${r.status}`;
try {
const body = await r.json();
if (typeof body?.detail === "string") detail = body.detail;
else if (body?.detail != null) detail = JSON.stringify(body.detail);
} catch { /* keep status-only detail */ }
_toast(`⚠ Submission failed (${detail})`);
}
} catch {
submitBtn.disabled = false;