const { useState, useEffect, useCallback } = React;
// --- Design Tokens ---
const C = {
bg: "#08080f",
bgCard: "#0d0d1a",
bgHover: "#0f0f1e",
bgInput: "#050508",
border: "#1a1a28",
borderLight: "#2a2a3a",
text: "#ddd8cc",
textDim: "#888",
textMuted: "#7c7c8c",
textFaint: "#555",
accent: "#4a9eff",
green: "#34d399",
orange: "#f59e0b",
red: "#e01e5a",
purple: "#a78bfa",
};
const font = { mono: "'DM Mono', 'Courier New', monospace", heading: "'Syne', sans-serif" };
// --- API ---
let onAuthFail = () => {};
let onServerError = () => {};
async function handleResponse(r) {
if (r.status === 401) { onAuthFail(); throw new Error("Not authenticated"); }
const data = await r.json();
if (r.status === 500 && data.error_id) { onServerError(data); throw new Error(data.error); }
return data;
}
const api = {
get: (path) => fetch(`/api${path}`, { credentials: "same-origin" }).then(handleResponse),
post: (path, body) => fetch(`/api${path}`, {
method: "POST", credentials: "same-origin",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
}).then(handleResponse),
patch: (path, body) => fetch(`/api${path}`, {
method: "PATCH", credentials: "same-origin",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
}).then(handleResponse),
del: (path) => fetch(`/api${path}`, {
method: "DELETE", credentials: "same-origin",
}).then(r => r.json()),
};
// --- Global refs (set by App, used by deep components) ---
let globalNav = () => {};
let globalEnvLookup = {}; // name -> id
// --- Components ---
function StatusBadge({ status }) {
const colors = { open: C.orange, resolved: C.green, stale: C.textFaint };
const color = colors[status] || C.textDim;
return (
{status}
);
}
function EnvBadge({ env, envId, clickable = true }) {
if (!env) return null;
const resolvedId = envId || globalEnvLookup[env];
const handleClick = (e) => {
if (!clickable || !resolvedId) return;
e.stopPropagation();
globalNav("env-show", resolvedId);
};
return (
{env}
);
}
function StatTile({ label, value, color, onClick, delta }) {
const len = String(value).length;
const sz = len > 10 ? "0.85rem" : len > 4 ? "1rem" : "1.25rem";
const deltaNum = delta != null ? Number(delta) : null;
const deltaStr = deltaNum != null && deltaNum !== 0
? (deltaNum > 0 ? `+${deltaNum.toLocaleString()}` : deltaNum.toLocaleString())
: null;
const deltaColor = deltaNum > 0 ? C.green : deltaNum < 0 ? C.red : C.textFaint;
return (
{ if (onClick) e.currentTarget.style.borderColor = C.accent + "44"; }}
onMouseLeave={e => { if (onClick) e.currentTarget.style.borderColor = C.border; }}>
{label}
{value}
{deltaStr &&
{deltaStr}
}
);
}
function Btn({ children, onClick, variant = "primary", disabled, style: extra }) {
const styles = {
primary: { background: C.accent, color: "#000" },
ghost: { background: "transparent", color: C.textDim, border: `1px solid ${C.borderLight}` },
danger: { background: C.red, color: "#fff" },
success: { background: C.green, color: "#000" },
};
return (
{children}
);
}
function Input({ label, value, onChange, placeholder, type = "text", style: extra }) {
return (
{label && {label} }
onChange(e.target.value)}
placeholder={placeholder}
style={{
background: C.bgInput, color: C.text, border: `1px solid ${C.border}`,
borderRadius: "3px", padding: "0.5rem", fontFamily: font.mono,
fontSize: "1rem", letterSpacing: "0.04em", outline: "none",
width: "100%", boxSizing: "border-box",
...extra,
}}
/>
);
}
function Select({ label, value, onChange, options, placeholder }) {
return (
{label && {label} }
onChange(e.target.value)} style={{
background: C.bgInput, color: C.text, border: `1px solid ${C.border}`,
borderRadius: "3px", padding: "0.5rem", fontFamily: font.mono,
fontSize: "1rem", letterSpacing: "0.04em", outline: "none",
width: "100%", boxSizing: "border-box",
}}>
{placeholder && {placeholder} }
{options.map(o => typeof o === "string"
? {o}
: {o.label}
)}
);
}
// --- Pages ---
function Dashboard({ user, investigations, environments, onNav }) {
const [tab, setTab] = useState("investigations");
const open = investigations.filter(i => i.status === "open").length;
const resolved = investigations.filter(i => i.status === "resolved").length;
const recent = investigations.slice(0, 5);
return (
Watson
StoreConnect Investigation Platform
{/* Dashboard tabs */}
{[{key: "investigations", label: "Investigations"}, {key: "environments", label: "Environments"}].map(t => (
setTab(t.key)}
style={{ fontSize: "0.8rem", padding: "0.4rem 0.8rem" }}>{t.label}
))}
{tab === "investigations" && (<>
onNav("list")} />
onNav("list")} />
onNav("list")} />
setTab("environments")} />
onNav("new")}>New Investigation
onNav("list")}>All Investigations
>)}
{tab === "environments" && (<>
{environments.length} environment(s)
onNav("env-new")} style={{ fontSize: "0.75rem" }}>Add Environment
{environments.map(env => (
onNav("env-show", env.id)}
style={{ background: C.bgCard, border: `1px solid ${C.border}`, borderRadius: "5px",
padding: "0.7rem", cursor: "pointer", transition: "border-color 0.15s" }}
onMouseEnter={e => e.currentTarget.style.borderColor = C.accent + "44"}
onMouseLeave={e => e.currentTarget.style.borderColor = C.border}>
{env.name}
App: {env.rails_app || env.db_app} · DB: {env.db_app}
{env.org_id &&
Org: {env.org_id}
}
))}
>)}
{recent.length > 0 && (
Recent
{recent.map((inv) => (
onNav("show", inv.id)}
style={{
padding: "0.7rem", cursor: "pointer",
background: C.bgCard, border: `1px solid ${C.border}`, borderRadius: "5px",
transition: "border-color 0.15s",
}}
onMouseEnter={e => e.currentTarget.style.borderColor = C.accent + "44"}
onMouseLeave={e => e.currentTarget.style.borderColor = C.border}
>
{timeAgo(inv.updated_at)}
{inv.title}
{inv.entity_type} {inv.entity_id?.slice(0, 8)}
{inv.case_number && <>{" "}· {inv.case_number}>}
))}
)}
);
}
function InvestigationList({ investigations, onNav }) {
const [filter, setFilter] = useState("all");
const filtered = filter === "all" ? investigations : investigations.filter(i => i.status === filter);
return (
Investigations
onNav("new")}>New
{["all", "open", "resolved", "stale"].map(s => (
setFilter(s)}
style={{ fontSize: "0.75rem", padding: "0.3rem 0.6rem" }}>
{s.charAt(0).toUpperCase() + s.slice(1)}
))}
{filtered.length === 0 && (
No investigations found.
)}
{filtered.map((inv) => (
onNav("show", inv.id)}
style={{
padding: "0.7rem", cursor: "pointer",
background: C.bgCard, border: `1px solid ${C.border}`, borderRadius: "5px",
transition: "border-color 0.15s",
}}
onMouseEnter={e => e.currentTarget.style.borderColor = C.accent + "44"}
onMouseLeave={e => e.currentTarget.style.borderColor = C.border}
>
{inv.case_number && {inv.case_number} }
{timeAgo(inv.updated_at)}
{inv.title}
{inv.entity_type} {inv.entity_id?.slice(0, 8)}
{inv.user && <>{" "}· {inv.user.name}>}
))}
);
}
function InvestigationShow({ id, onNav, environments }) {
const [inv, setInv] = useState(null);
const [loading, setLoading] = useState(true);
const [editing, setEditing] = useState(false);
const [editForm, setEditForm] = useState({});
const [chatMsg, setChatMsg] = useState("");
const [chatLoading, setChatLoading] = useState(false);
const [toolPanel, setToolPanel] = useState(null); // "lookup"|"audit"|"timeline"|"shipments"
const [toolForm, setToolForm] = useState({});
const [toolLoading, setToolLoading] = useState(false);
const [connectCase, setConnectCase] = useState(false);
const [caseInput, setCaseInput] = useState("");
const [connectLoading, setConnectLoading] = useState(false);
const [bugForm, setBugForm] = useState(null); // null = hidden
const [bugLoading, setBugLoading] = useState(false);
const reload = () => api.get(`/investigations/${id}`).then(setInv);
useEffect(() => {
setLoading(true);
reload().then(() => setLoading(false));
}, [id]);
const saveEdit = async () => {
await api.patch(`/investigations/${id}`, editForm);
setEditing(false);
reload();
};
const startEdit = () => {
setEditForm({ status: inv.status, findings: inv.findings || "", resolution: inv.resolution || "",
case_number: inv.case_number || "", case_url: inv.case_url || "", description: inv.description || "" });
setEditing(true);
};
const [chatError, setChatError] = useState(null);
const sendChat = async () => {
if (!chatMsg.trim()) return;
const msg = chatMsg; setChatMsg(""); setChatLoading(true); setChatError(null);
try {
const result = await api.post(`/investigations/${id}/chat_sync`, { message: msg });
if (result.error) { setChatError(result.error); }
else { reload(); }
} catch (e) {
setChatError(e.message || "Failed to send message");
}
setChatLoading(false);
};
const runTool = async () => {
setToolLoading(true);
try {
await api.post(`/investigations/${id}/run_tool`, { tool: toolPanel, tool_params: toolForm });
reload();
setToolPanel(null); setToolForm({});
} catch (e) { /* ignore */ }
setToolLoading(false);
};
const doConnectCase = async () => {
if (!caseInput.trim()) return;
setConnectLoading(true);
try {
const result = await api.post(`/investigations/${id}/connect_case`, { platform: "salesforce", case_number: caseInput });
if (result.errors) { alert(result.errors.join(", ")); }
else { setInv(result); setConnectCase(false); setCaseInput(""); }
} catch (e) { /* ignore */ }
setConnectLoading(false);
};
const logBug = async () => {
if (!bugForm?.title?.trim()) return;
setBugLoading(true);
try {
const result = await api.post(`/investigations/${id}/log_bug`, bugForm);
if (result.error) { alert(result.error); }
else { setBugForm(null); reload(); }
} catch (e) { /* ignore */ }
setBugLoading(false);
};
if (loading) return Loading...
;
if (!inv) return Not found
;
const chatEntries = (inv.entries || []).filter(e => e.entry_type === "chat_message");
const otherEntries = (inv.entries || []).filter(e => e.entry_type !== "chat_message");
return (
{inv.title}
{inv.case_number && (
<>{inv.case_url
? {inv.case_number}
: {inv.case_number}
}{" "}·{" "}>
)}
{inv.entity_type} {inv.entity_id} · by {inv.user?.name} · {timeAgo(inv.created_at)}
setEditing(false) : startEdit}>
{editing ? "Cancel" : "Edit"}
onNav("list")}>Back
{editing && (
)}
{/* Connect Case */}
{!inv.case_number && !connectCase && (
setConnectCase(true)} style={{ fontSize: "0.7rem" }}>Connect Case
)}
{connectCase && (
Connect Salesforce Case
{connectLoading ? "..." : "Connect"}
setConnectCase(false)}>Cancel
Pulls ticket description into investigation context.
)}
{/* Description */}
{inv.description && (
Description
{inv.description}
)}
{/* Run Tool inline */}
Run Tool
{["lookup","audit","timeline","shipments"].map(t => (
{ setToolPanel(toolPanel === t ? null : t); setToolForm({}); }}
style={{ fontSize: "0.75rem", padding: "0.35rem 0.6rem" }}>{t}
))}
{toolPanel === "lookup" && (
setToolForm({...toolForm, table: v})}
options={["order","order_item","payment","payment_item","cart","cart_item","cart_item_tax","contact","shipment","cart_fulfillment","cart_fulfillment_item","fulfillment_item","line_item","register_shift","payment_gateway_txn"]} placeholder="Select table" />
setToolForm({...toolForm, sc_id: v})} placeholder="abc123..." />
setToolForm({...toolForm, ref: v})} placeholder="AMRT..." />
{toolLoading ? "..." : "Run"}
)}
{toolPanel === "audit" && (
setToolForm({...toolForm, payment_sc_id: v})} />
setToolForm({...toolForm, order_sc_id: v})} />
setToolForm({...toolForm, from: v})} />
{toolLoading ? "..." : "Run"}
)}
{toolPanel === "timeline" && (
setToolForm({...toolForm, table: v})}
options={["order","order_item","payment","payment_item","cart","cart_item","contact","shipment","cart_fulfillment"]} placeholder="Auto (order/cart)" />
setToolForm({...toolForm, sc_id: v})} placeholder="sc_id..." />
setToolForm({...toolForm, from: v})} />
setToolForm({...toolForm, to: v})} />
{toolLoading ? "..." : "Run"}
)}
{toolPanel === "shipments" && (
setToolForm({...toolForm, order_sc_id: v})} />
{toolLoading ? "..." : "Run"}
)}
{/* Actions: Log Bug */}
Actions
setBugForm(bugForm ? null : { title: inv.title || "", product: "POS", description: inv.findings || inv.description || "", create_ticket: true })}
style={{ fontSize: "0.75rem", padding: "0.35rem 0.6rem" }}>Log Bug
{bugForm && (
)}
{/* App Instance */}
{(inv.db_app || inv.rails_app || inv.org_id) && (
App Instance
{inv.db_app && <>
DB App {inv.db_app} >}
{inv.rails_app && <>Rails App {inv.rails_app} >}
{inv.org_id && <>Org ID {inv.org_id} >}
)}
{inv.findings && (
)}
{inv.resolution && (
Resolution
{inv.resolution}
)}
{otherEntries.length > 0 && (() => {
const latest = otherEntries[otherEntries.length - 1];
const history = otherEntries.slice(0, -1).reverse();
return (<>
{/* Latest entry — always expanded */}
Latest: {latest.entry_type}
{latest.actor} · {new Date(latest.created_at).toLocaleString()}
{/* History — collapsed by default */}
{history.length > 0 && (
History ({history.length} entries)
tap to expand
{history.map(e => (
{e.entry_type} · {e.actor}
{timeAgo(e.created_at)}
))}
)}
>);
})()}
);
}
const POLICY_STYLE = {
gated_open: { label: "Open", color: C.green, hint: "Client-gated" },
gated_closed: { label: "Closed", color: C.red, hint: "Client-gated" },
legacy: { label: "Legacy", color: C.orange, hint: "Not client-gated" },
unknown: { label: "Unknown", color: C.textFaint, hint: "Could not check" },
};
function PolicyBadge({ policy }) {
const st = POLICY_STYLE[policy] || POLICY_STYLE.unknown;
return (
{st.label}
);
}
const fmtWhen = (iso) => iso ? iso.replace("T", " ").replace(/:\d\d(\.\d+)?Z$/, " UTC") : "-";
// One-line notice that a tool result was read under a given support-access
// policy. Shown wherever tenant logs are rendered so a legacy read is never silent.
function AccessNotice({ access }) {
if (!access) return null;
const st = POLICY_STYLE[access.policy] || POLICY_STYLE.unknown;
return (
{access.logs_note || access.reason}
);
}
// Support-access panel for an environment: package version, gem version, the
// client's login-access grant window and the resulting policy for logs and DB.
function SupportAccessCard({ envId }) {
const [access, setAccess] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const load = (refresh) => {
setLoading(true); setError(null);
api.get(`/environments/${envId}/support_access${refresh ? "?refresh=1" : ""}`)
.then(data => { if (data.error) setError(data.error); else setAccess(data); setLoading(false); })
.catch(e => { setError(e.message); setLoading(false); });
};
useEffect(() => { load(false); }, [envId]);
return (
Support Access
{access &&
}
load(true)} disabled={loading} style={{ fontSize: "0.7rem", padding: "0.2rem 0.5rem" }}>
{loading ? "Checking..." : "Refresh"}
{error && }
{access && (<>
License
{access.license_name || "-"}{access.subscriber_org_name ? ` · ${access.subscriber_org_name}` : ""}
Package
{access.package_version || "-"}{access.package_version_name ? ` (${access.package_version_name})` : ""}
{access.package_updated_at ? · updated {fmtWhen(access.package_updated_at)} : null}
{!access.grant_capable && access.package_version ? · below {access.min_grant_package_version} : null}
Gem {access.gem_version || "-"}
Grant
{!access.grant_fields_available ? License grant fields not installed yet (SYS-10070)
: access.granted_to ? `${fmtWhen(access.granted_from)} → ${fmtWhen(access.granted_to)}`
: none live }
{access.reason}
Logs {access.logs_note}
Database {access.db_note}
{access.error && {access.error}
}
>)}
);
}
function EnvironmentDetail({ id, onNav, onUpdated }) {
const cacheKey = `watson_telemetry_v2_${id}`;
const cached = (() => { try { return JSON.parse(localStorage.getItem(cacheKey)); } catch { return null; } })();
const [env, setEnv] = useState(null);
const [telemetry, setTelemetry] = useState(cached?.data || null);
const [loading, setLoading] = useState(true);
const [tLoading, setTLoading] = useState(false);
const [editing, setEditing] = useState(false);
const [editForm, setEditForm] = useState({});
const [hcAction, setHcAction] = useState(null);
const [prevTelemetry, setPrevTelemetry] = useState(null);
const telemetryRef = React.useRef(cached?.data || null);
const loadEnv = () => api.get(`/environments/${id}`).then(setEnv);
const loadTelemetry = () => {
setTLoading(true);
api.get(`/environments/${id}/telemetry`).then(data => {
if (telemetryRef.current) setPrevTelemetry(telemetryRef.current);
telemetryRef.current = data;
setTelemetry(data);
setTLoading(false);
// Cache to localStorage
try { localStorage.setItem(cacheKey, JSON.stringify({ data, cachedAt: new Date().toISOString() })); } catch {}
}).catch(() => setTLoading(false));
};
useEffect(() => { loadEnv().then(() => setLoading(false)); loadTelemetry(); }, [id]);
// Auto-refresh every 30s
useEffect(() => {
const timer = setInterval(loadTelemetry, 30000);
return () => clearInterval(timer);
}, [id]);
const saveEdit = async () => {
await api.patch(`/environments/${id}`, editForm);
setEditing(false); loadEnv(); onUpdated();
};
const doHcAction = async (action) => {
setHcAction(action);
await api.post(`/environments/${id}/hc_${action}`, {});
setHcAction(null); loadTelemetry();
};
if (loading) return Loading...
;
if (!env) return Not found
;
return (
{env.name}
{ setEditForm(env); setEditing(!editing); }}>{editing ? "Cancel" : "Edit"}
onNav("dashboard")}>Back
{editing && (
setEditForm({...editForm, name: v})} />
setEditForm({...editForm, rails_app: v})} placeholder="m-f-3c62d74e-v21" />
setEditForm({...editForm, db_app: v})} placeholder="m-f-3c62d74e" />
setEditForm({...editForm, org_id: v})} placeholder="00d5i000007ezuheag" />
Save
)}
{/* Config */}
Configuration
App {env.rails_app || "-"}
Database {env.db_app}
Org ID {env.org_id || "-"}
{env.schema && <>Schema {env.schema} >}
{/* Support access: package version, grant window, policy */}
{/* Telemetry */}
Telemetry
{tLoading ? "Loading..." : "Refresh"}
{!telemetry && tLoading && Loading telemetry...
}
{telemetry && (<>
{/* Refresh timestamp */}
{(() => {
const refreshedAt = telemetry.refreshed_at ? new Date(telemetry.refreshed_at) : null;
const ageMs = refreshedAt ? Date.now() - refreshedAt.getTime() : Infinity;
const isStale = ageMs > 3 * 60 * 1000; // >3 minutes
return (
Last refreshed: {refreshedAt ? refreshedAt.toLocaleTimeString() : "—"}
{tLoading && (refreshing...) }
{isStale && !tLoading && (
stale
)}
auto-refresh 30s
);
})()}
{(() => {
const hc = telemetry.hc_status || "";
const hcLabel = hc.includes("POLLING") ? "Polling" : hc.includes("IDLE") ? "Idle" : hc.includes("PAUSED") ? "Paused" : hc.includes("Error") ? "Error" : "Active";
const hcColor = hc.includes("POLLING") || hc.includes("IDLE") ? C.green : hc.includes("PAUSED") ? C.orange : C.red;
const d = (key) => prevTelemetry ? (telemetry[key] ?? 0) - (prevTelemetry[key] ?? 0) : null;
const ce = telemetry.change_events || {};
const pce = prevTelemetry?.change_events || {};
const ced = (k) => prevTelemetry?.change_events ? (ce[k] ?? 0) - (pce[k] ?? 0) : null;
return (<>
1000 ? C.red : (ce.unprocessed ?? 0) > 100 ? C.orange : C.green} delta={ced("unprocessed")} />
10000 ? C.orange : C.green} delta={ced("processed_pending_archive")} />
100 ? C.orange : C.textDim} delta={ced("pending_deletes")} />
0 ? C.red : C.green} delta={ced("failed")} />
0 ? C.orange : C.green} delta={d("upstream_backlog")} />
10 ? C.orange : C.green} delta={d("downstream_queue")} />
0 ? C.red : C.green} delta={d("failed_events")} />
0 ? C.orange : C.green} delta={d("missing_sfids")} />
0 ? C.orange : C.textDim} />
>);
})()}
{/* HC Actions */}
doHcAction("pause")} disabled={!!hcAction}
style={{ fontSize: "0.75rem", color: C.orange }}>{hcAction === "pause" ? "Pausing..." : "Pause HC"}
doHcAction("resume")} disabled={!!hcAction}
style={{ fontSize: "0.75rem", color: C.green }}>{hcAction === "resume" ? "Resuming..." : "Resume HC"}
{/* HC raw status */}
HC Detail
{telemetry.hc_status}
{/* Trigger log breakdown by state */}
{telemetry.trigger_log?.length > 0 && (
Trigger Log by State
)}
{/* Trigger log backlog by table — shows which tables consume HC bandwidth */}
{telemetry.trigger_log_by_table?.length > 0 && (
Trigger Log Backlog by Table ({telemetry.trigger_log_by_table.reduce((s, r) => s + parseInt(r.count || 0), 0).toLocaleString()} pending)
)}
{/* Dynos */}
{telemetry.dynos && !telemetry.dynos.includes("Error") && (
Dynos
{telemetry.dynos}
)}
{/* Release */}
{telemetry.recent_release && (
{telemetry.recent_release}
)}
>)}
);
}
function NewEnvironment({ onNav, onCreated }) {
const [form, setForm] = useState({ name: "", db_app: "", rails_app: "", org_id: "" });
const [saving, setSaving] = useState(false);
const [error, setError] = useState(null);
const handleSubmit = async () => {
if (!form.name.trim() || !form.db_app.trim()) { setError("Name and Database App are required"); return; }
setSaving(true); setError(null);
try {
const result = await api.post("/environments", form);
if (result.errors) { setError(result.errors.join(", ")); setSaving(false); return; }
onCreated();
onNav("env-show", result.id);
} catch (e) { setError(e.message); setSaving(false); }
};
return (
Add Environment
{error &&
}
);
}
function NewInvestigation({ environments, onNav, onCreated }) {
const [form, setForm] = useState({ title: "", entity_type: "", entity_id: "", environment: "", case_number: "", case_url: "", description: "" });
const [saving, setSaving] = useState(false);
const [error, setError] = useState(null);
const handleSubmit = async () => {
if (!form.title.trim()) { setError("Title is required"); return; }
setSaving(true); setError(null);
try {
const result = await api.post("/investigations", form);
if (result.errors) { setError(result.errors.join(", ")); setSaving(false); return; }
onCreated();
onNav("show", result.id);
} catch (e) { setError(e.message); setSaving(false); }
};
return (
New Investigation
{error &&
}
setForm({ ...form, title: v })}
placeholder="Describe the issue" />
setForm({ ...form, environment: v })}
options={environments.map(e => ({ value: e.name, label: `${e.name} (${e.db_app})` }))}
placeholder="Default (amart)" />
setForm({ ...form, case_number: v })} placeholder="WEB-8502" />
setForm({ ...form, case_url: v })} placeholder="https://..." />
Entity (optional)
setForm({ ...form, entity_type: v })}
options={[{ value: "order", label: "Order" }, { value: "cart", label: "Cart" },
{ value: "payment", label: "Payment" }, { value: "contact", label: "Contact" }]}
placeholder="Any" />
setForm({ ...form, entity_id: v })} placeholder="abc123..." />
setForm({ ...form, entity_ref: v })} placeholder="AMRT..." />
Description
{saving ? "Creating..." : "Create"}
onNav("dashboard")}>Cancel
);
}
// --- Tool Panels ---
function Card({ children, style: extra }) {
return {children}
;
}
function SectionLabel({ children }) {
return {children}
;
}
function DataTable({ rows, columns }) {
if (!rows || rows.length === 0) return (no results)
;
const cols = columns || Object.keys(rows[0]);
const isMobile = typeof window !== "undefined" && window.innerWidth < 640;
if (isMobile) {
// Card layout for mobile
return (
{rows.map((row, i) => (
{cols.map(c => (
{c}
{String(row[c] ?? "")}
))}
))}
);
}
// Desktop table
return (
{cols.map(c => {c} )}
{rows.map((row, i) => (
{cols.map(c => {String(row[c] ?? "")} )}
))}
);
}
function AddToInvestigation({ entryType, content, investigations }) {
const [open, setOpen] = useState(false);
const [saving, setSaving] = useState(false);
const [saved, setSaved] = useState(false);
const save = async (invId) => {
setSaving(true);
await api.post(`/investigations/${invId}/entries`, { entry_type: entryType, content });
setSaving(false); setSaved(true); setOpen(false);
setTimeout(() => setSaved(false), 3000);
};
if (saved) return Saved to investigation ;
return (
setOpen(!open)} style={{ fontSize: "0.7rem", padding: "0.2rem 0.5rem" }}>
+ Add to Investigation
{open && (
{investigations.length === 0 &&
No open investigations
}
{investigations.map(inv => (
save(inv.id)} style={{
padding: "0.3rem 0.5rem", cursor: "pointer", borderRadius: "3px",
fontSize: "0.75rem", transition: "background 0.1s",
}}
onMouseEnter={e => e.currentTarget.style.background = C.bgHover}
onMouseLeave={e => e.currentTarget.style.background = "transparent"}>
{inv.title}
{inv.environment && ({inv.environment}) }
))}
)}
);
}
function ChatPanel({ chatEntries, chatMsg, setChatMsg, chatLoading, chatError, sendChat }) {
const [claudeConnected, setClaudeConnected] = useState(null);
useEffect(() => {
api.get("/integrations/claude/status")
.then(data => setClaudeConnected(data.connected))
.catch(() => setClaudeConnected(false));
}, []);
const isAuthError = chatError && (chatError.includes("api_key") || chatError.includes("authentication") || chatError.includes("401") || chatError.includes("Not authenticated"));
const needsSetup = claudeConnected === false || isAuthError;
const errorMsg = isAuthError
? null // handled by needsSetup banner
: (chatError || null);
return (
Chat with Watson
{claudeConnected === true && }
{claudeConnected === false && }
{needsSetup && (
Claude is not connected.{" "}
globalNav("integrations")} style={{ textDecoration: "underline", cursor: "pointer", fontWeight: 500 }}>
Go to Setup
{" "}
to add your API key, or{" "}
get a key from Anthropic .
)}
{errorMsg && (
{errorMsg}
)}
{chatLoading && (
Watson is thinking...
)}
{chatEntries.length === 0 && !chatLoading && !errorMsg && (
No messages yet. Ask Watson about this investigation.
)}
{chatEntries.map(e => (
{e.content?.role === "user" ? "You" : "Watson"}
{e.content?.text}
))}
setChatMsg(e.target.value)}
onKeyDown={e => e.key === "Enter" && !e.shiftKey && sendChat()}
placeholder={claudeConnected === false ? "Connect Claude first..." : "Ask Watson..."}
disabled={chatLoading || claudeConnected === false}
style={{
flex: 1, background: C.bgInput, color: C.text, border: `1px solid ${C.border}`,
borderRadius: "3px", padding: "0.5rem", fontFamily: font.mono,
fontSize: "1rem", outline: "none",
opacity: claudeConnected === false ? 0.5 : 1,
}} />
{chatLoading ? "..." : "Send"}
);
}
// Renders a key-value detail view for a single record
function RecordDetail({ record, label }) {
if (!record || typeof record !== "object") return null;
const entries = Object.entries(record).filter(([, v]) => v != null && v !== "");
if (entries.length === 0) return null;
return (
{label &&
{label}
}
{entries.map(([k, v]) => [
{k}
,
{String(v)}
,
])}
);
}
// Renders a list of records as a responsive table or card list
function RecordList({ records, label }) {
if (!records || !Array.isArray(records) || records.length === 0) return null;
const cols = Object.keys(records[0]).slice(0, 8); // Limit visible columns
return (
{label &&
{label} ({records.length})
}
{cols.map(c => {c} )}
{records.map((row, i) => (
{cols.map(c => {String(row[c] ?? "")} )}
))}
);
}
// Renders a tool result entry with grouped records
function ToolResultView({ entry }) {
const content = entry.content;
if (!content) return (empty)
;
// Note entries
if (entry.entry_type === "note") {
if (content.action === "connected_case") {
return Connected Salesforce case {content.case_number}
;
}
if (content.action === "logged_bug") {
return (
);
}
return {typeof content === "object" ? JSON.stringify(content, null, 2) : content} ;
}
// Tool results (lookup, audit, timeline, shipments)
const result = content.result || content;
const params = content.params;
const tool = content.tool || entry.entry_type;
// Lookup result: grouped by record type
if (result.results && typeof result.results === "object" && !Array.isArray(result.results)) {
return (
{params &&
{Object.entries(params).filter(([,v]) => v).map(([k,v]) => `${k}: ${v}`).join(" · ")}
}
{Object.entries(result.results).map(([key, data]) => {
if (Array.isArray(data)) {
return
;
} else if (data && typeof data === "object") {
return
;
}
return null;
})}
);
}
// Audit result: table of results
if (result.results && Array.isArray(result.results)) {
return (
{result.total != null &&
{result.total} records, {result.issues || 0} issues
}
);
}
// Timeline result
if (result.entity && result.log_entries) {
const groups = result.grouped_timeline || [];
return (
{result.log_entries?.length > 0 && (
Timeline ({result.log_entries.length} events)
{groups.length > 0 ? groups.map((g, gi) => (
{g.time} ({g.count})
{g.entries.map((e, i) => (
{e.timestamp?.split("T")[1]?.slice(0,8)} {e.program}
{e.message?.slice(0, 200)}
))}
)) : result.log_entries.map((e, i) => (
{e.timestamp}
{e.message?.slice(0, 200)}
))}
)}
{result.related && Object.entries(result.related).map(([name, rows]) =>
rows?.length > 0 &&
)}
);
}
// Fallback: pretty-print JSON
return
{typeof content === "object" ? JSON.stringify(content, null, 2) : String(content)}
;
}
function ShareUrl({ url }) {
const [copied, setCopied] = useState(false);
const copy = () => {
navigator.clipboard?.writeText(url).then(() => { setCopied(true); setTimeout(() => setCopied(false), 2000); });
};
return (
Share:
{url}
{copied ? "Copied" : "Copy"}
);
}
function ErrorBox({ error }) {
if (!error) return null;
return {error}
;
}
function ToolPage({ title, children }) {
return (
{title}
{children}
);
}
function LookupPage({ environments, investigations }) {
const [form, setForm] = useState({ sc_id: "", table: "", ref: "", env: "", with_related: "0" });
const [result, setResult] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const run = async () => {
setLoading(true); setError(null); setResult(null);
try {
const r = await api.post("/lookup", form);
if (r.error) { setError(r.error); } else { setResult(r); }
} catch (e) { setError(e.message); }
setLoading(false);
};
return (
setForm({...form, env: v})}
options={environments.map(e => ({value: e.name, label: e.name}))} placeholder="Default" />
setForm({...form, sc_id: v})} placeholder="07c07f7a..." />
setForm({...form, table: v})}
options={["order","order_item","payment","payment_item","cart","cart_item","cart_item_tax","contact","shipment","cart_fulfillment","cart_fulfillment_item","fulfillment_item","line_item","register_shift","payment_gateway_txn"]} placeholder="Select table" />
setForm({...form, ref: v})} placeholder="AMRT..." />
{loading ? "Looking up..." : "Lookup"}
setForm({...form, with_related: e.target.checked ? "1" : "0"})} style={{ accentColor: C.accent }} /> Related records
{result && (
)}
{result && Object.entries(result.results).map(([key, data]) => (
{key}
{Array.isArray(data) ? : (
{Object.entries(data).map(([k, v]) => [
{k} ,
{String(v ?? "")}
])}
)}
))}
);
}
function AuditPage({ environments, investigations }) {
const [form, setForm] = useState({ payment_sc_id: "", order_sc_id: "", from: "", to: "", method: "all", env: "" });
const [result, setResult] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const run = async () => {
setLoading(true); setError(null); setResult(null);
try {
const r = await api.post("/audit", form);
if (r.error) { setError(r.error); } else { setResult(r); }
} catch (e) { setError(e.message); }
setLoading(false);
};
return (
setForm({...form, env: v})}
options={environments.map(e => ({value: e.name, label: e.name}))} placeholder="Default" />
setForm({...form, payment_sc_id: v})} />
setForm({...form, order_sc_id: v})} />
setForm({...form, from: v})} />
setForm({...form, to: v})} />
setForm({...form, method: v})}
options={[{value: "all", label: "All"}, {value: "linkly", label: "Linkly"}, {value: "vii", label: "VII"}]} />
{loading ? "Auditing..." : "Run Audit"}
{result && (
Results ({result.total} payments, {result.issues} issues)
i.status === "open")} />
)}
);
}
function TimelinePage({ environments, investigations }) {
const [form, setForm] = useState({ sc_id: "", table: "", from: "", to: "", env: "", with_related: "1" });
const [result, setResult] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const run = async () => {
setLoading(true); setError(null); setResult(null);
try {
const r = await api.post("/timeline", form);
if (r.error) { setError(r.error); } else { setResult(r); }
} catch (e) { setError(e.message); }
setLoading(false);
};
return (
setForm({...form, env: v})}
options={environments.map(e => ({value: e.name, label: e.name}))} placeholder="Default" />
setForm({...form, table: v})}
options={["order","order_item","payment","cart","cart_item","contact","shipment","cart_fulfillment"]} placeholder="Auto (order/cart)" />
setForm({...form, sc_id: v})} placeholder="sc_id..." />
setForm({...form, from: v})} />
setForm({...form, to: v})} />
{loading ? "Building..." : "Build Timeline"}
{result && (
<>
{result.entity_type} {result.sc_id?.slice(0,8)}
i.status === "open")} />
{/* Grouped timeline */}
Timeline ({result.log_entries?.length || 0} events)
{(result.grouped_timeline || []).length > 0 ? (
{result.grouped_timeline.map((group, gi) => (
{group.time}
{group.count} event{group.count > 1 ? "s" : ""}
{group.entries.map((e, i) => (
{e.timestamp?.split("T")[1]?.slice(0,8)}
{e.program}
{e._source_type && ({e._source_type}) }
{e.message?.slice(0, 300)}
))}
))}
) : (
No log entries found. {!result.log_entries?.length && "Try specifying a date range."}
)}
{/* Related records */}
{Object.entries(result.related || {}).map(([name, rows]) => rows?.length > 0 && (
{name} ({rows.length})
))}
>
)}
);
}
function ShipmentsPage({ environments, investigations }) {
const [form, setForm] = useState({ order_sc_id: "", missing: "0", stuck: "0", from: "", env: "" });
const [result, setResult] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const run = async () => {
setLoading(true); setError(null); setResult(null);
try {
const r = await api.post("/shipments", form);
if (r.error) { setError(r.error); } else { setResult(r); }
} catch (e) { setError(e.message); }
setLoading(false);
};
return (
{result && (
Results ({result.total})
i.status === "open")} />
)}
);
}
function IntegrationCard({ int, onUpdated }) {
const [editing, setEditing] = useState(false);
const [value, setValue] = useState("");
const [saving, setSaving] = useState(false);
const save = async () => {
setSaving(true);
await api.patch(`/integrations/${int.id}`, { value });
setSaving(false); setEditing(false); setValue("");
onUpdated();
};
const disconnect = async () => {
setSaving(true);
await api.patch(`/integrations/${int.id}`, { value: "" });
setSaving(false); setEditing(false);
onUpdated();
};
const cfg = int.config;
const maskedValue = cfg?.has_value && cfg?.secret ? "••••••••••••" : null;
return (
{int.name}
{int.managed && (
managed
)}
{cfg?.source === "user" && (
user key
)}
{cfg?.source === "env" && (
env
)}
{int.connected ? "Connected" : "Not Connected"}
{int.description}
{int.detail && {int.detail}
}
{int.actions?.length > 0 && (
{int.actions.map(a => (
{a}
))}
)}
{/* The chat runs on the user's own Claude subscription, never an API key */}
{int.id === "claude" && !int.connected && !editing && (
On your own machine, in a terminal with Claude Code installed, run{" "}
claude setup-token, sign in to your Claude
account, then paste the token it prints here. Watson runs the assistant on your subscription; the token is
stored encrypted and never shown again.
)}
{cfg && !editing && (
setEditing(true)} style={{ fontSize: "0.7rem", padding: "0.25rem 0.6rem" }}>
{int.connected ? "Update Key" : "Connect"}
{cfg.has_value && cfg.source === "user" && (
Disconnect
)}
)}
{cfg && editing && (
{maskedValue && (
Current: {maskedValue}
)}
)}
);
}
// The OAuth grants this user has approved: Claude Code, the watson CLI, any
// other MCP client. Watson issued each a token after the Salesforce sign-in;
// revoking here ends it immediately.
function ConnectedAppsCard() {
const [tokens, setTokens] = useState(null);
const load = () => api.get("/tokens").then(setTokens).catch(() => setTokens([]));
useEffect(() => { load(); }, []);
const revoke = async (t) => {
await api.del(`/tokens/${t.id}`);
load();
};
const when = (iso) => iso ? new Date(iso).toLocaleString() : "never";
const origin = window.location.origin;
const cmd = `claude mcp add --transport http watson ${origin}/mcp`;
return (
Connected apps
Tools signed in as you through Watson. Each one runs Watson's investigation tools with your Salesforce access; revoke any you no longer use.
{tokens === null ? (
Loading...
) : tokens.length === 0 ? (
None yet.
) : (
App Connected Last used Expires
{tokens.map(t => (
{t.name}
{when(t.created_at)}
{when(t.last_used_at)}
{when(t.refresh_expires_at)}
revoke(t)} style={{ fontSize: "0.7rem", padding: "0.2rem 0.5rem", color: C.red }}>Revoke
))}
)}
Use Watson from your terminal
In Claude Code, add the Watson MCP server, then run
/mcp and choose Watson to sign in with Salesforce:
{cmd}
With the Watson repo checked out,
bin/watson login signs the CLI in the same way; every
watson command then runs through this instance.
);
}
function IntegrationsPage() {
const [integrations, setIntegrations] = useState([]);
const [loading, setLoading] = useState(true);
const load = () => api.get("/integrations").then(data => { setIntegrations(data); setLoading(false); }).catch(() => setLoading(false));
useEffect(() => { load(); }, []);
if (loading) return Loading...
;
return (
{integrations.map(int => (
))}
);
}
// --- Nav ---
function Nav({ page, user, onNav, onLogout }) {
const [menuOpen, setMenuOpen] = useState(false);
const links = [
{ key: "dashboard", label: "Dashboard" },
{ key: "list", label: "Investigations" },
{ key: "lookup", label: "Lookup" },
{ key: "audit", label: "Audit" },
{ key: "timeline", label: "Timeline" },
{ key: "shipments", label: "Shipments" },
{ key: "integrations", label: "Setup" },
];
const navigate = (key) => { onNav(key); setMenuOpen(false); };
return (
navigate("dashboard")} style={{
fontFamily: font.heading, fontSize: "0.85rem", fontWeight: 800,
color: C.accent, cursor: "pointer", letterSpacing: "-0.01em",
}}>W
setMenuOpen(!menuOpen)} style={{
cursor: "pointer", padding: "0.3rem", display: "flex", flexDirection: "column",
gap: "4px", width: "22px",
}}>
{menuOpen && (
{links.map(l => (
navigate(l.key)} style={{
fontSize: "0.85rem", letterSpacing: "0.06em", textTransform: "uppercase",
color: page === l.key ? C.text : C.textDim, cursor: "pointer", padding: "0.45rem 0",
fontWeight: page === l.key ? 500 : 400, transition: "color 0.15s",
}}>{l.label}
))}
{user?.name}
{ onLogout(); setMenuOpen(false); }} style={{ fontSize: "0.75rem", color: C.textFaint, cursor: "pointer" }}>Sign out
)}
);
}
// --- Helpers ---
function timeAgo(dateStr) {
if (!dateStr) return "";
const seconds = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1000);
if (seconds < 60) return "just now";
const minutes = Math.floor(seconds / 60);
if (minutes < 60) return `${minutes}m ago`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}h ago`;
const days = Math.floor(hours / 24);
return `${days}d ago`;
}
// --- Login ---
function ServerErrorPage({ error, onBack }) {
const [showDetail, setShowDetail] = useState(false);
return (
{/* Animated Watson brain SVG */}
{[[60,35],[78,45],[82,65],[70,80],[50,80],[38,65],[42,45],[60,60]].map(([cx,cy], i) =>
)}
Watson is on it
Something went wrong, but it's already being investigated.
{error?.error_class ? `${error.error_class}: ` : ""}{error?.error || "An unexpected error occurred"}
{error?.error_id && (
Error ID: {error.error_id}
)}
setShowDetail(!showDetail)} style={{
background: "none", border: `1px solid ${C.borderLight}`, color: C.textMuted,
fontFamily: font.mono, fontSize: "0.7rem", padding: "0.2rem 0.6rem", borderRadius: "3px",
cursor: "pointer", marginTop: "0.5rem", letterSpacing: "0.06em",
}}>{showDetail ? "Hide detail" : "Show full detail"}
{showDetail && error?.trace && (
{Array.isArray(error.trace) ? error.trace.join("\n") : error.trace}
)}
This error has been automatically reported and logged for investigation.
Back to Watson
);
}
function LoginPage({ onLogin, herokuOAuth, salesforceOAuth, csrfToken }) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(() => {
try { return new URLSearchParams(window.location.search).get("login_error"); } catch { return null; }
});
const devLogin = async () => {
setLoading(true); setError(null);
try {
const r = await api.post("/dev_login", {});
if (r.authenticated) { onLogin(r.user); }
else { setError("Login failed"); }
} catch (e) { setError(e.message); }
setLoading(false);
};
// OmniAuth only accepts a POST carrying the session's CSRF token, which
// /api/auth_status hands out. Submit a hidden form.
const oauthLogin = (provider) => {
const form = document.createElement("form");
form.method = "POST";
form.action = `/auth/${provider}`;
if (csrfToken) {
const input = document.createElement("input");
input.type = "hidden"; input.name = "authenticity_token"; input.value = csrfToken;
form.appendChild(input);
}
document.body.appendChild(form);
form.submit();
};
const herokuLogin = () => oauthLogin("heroku");
const salesforceLogin = () => oauthLogin("salesforce");
return (
Watson
StoreConnect Investigation Platform
{error &&
}
{salesforceOAuth && (
<>
Sign in with Salesforce
Your StoreConnect org user, with the support permission set.
>
)}
{herokuOAuth && (
<>
Sign in with Heroku
>
)}
{loading ? "Signing in..." : (herokuOAuth || salesforceOAuth) ? "Dev Login (skip OAuth)" : "Sign In"}
);
}
// --- App ---
function App() {
const [authed, setAuthed] = useState(null); // null = checking, true/false
// Restore page from URL hash on load (lazy initializer — runs once)
const [page, setPage] = useState(() => {
const hash = window.location.hash.slice(1);
return hash ? hash.split("/")[0] || "dashboard" : "dashboard";
});
const [pageId, setPageId] = useState(() => {
const hash = window.location.hash.slice(1);
return hash ? hash.split("/")[1] || null : null;
});
const [user, setUser] = useState(null);
const [investigations, setInvestigations] = useState([]);
const [environments, setEnvironments] = useState([]);
const [serverError, setServerError] = useState(null);
const [herokuOAuth, setHerokuOAuth] = useState(false);
const [salesforceOAuth, setSalesforceOAuth] = useState(false);
const [csrfToken, setCsrfToken] = useState(null);
// Wire up global handlers
onAuthFail = () => { setAuthed(false); setUser(null); };
onServerError = (err) => { setServerError(err); };
const load = useCallback(() => {
api.get("/investigations").then(setInvestigations).catch(() => {});
api.get("/environments").then(data => {
setEnvironments(data);
globalEnvLookup = {};
data.forEach(e => { globalEnvLookup[e.name] = e.id; });
}).catch(() => {});
}, []);
// Check auth on mount
useEffect(() => {
fetch("/api/auth_status", { credentials: "same-origin" })
.then(r => r.json())
.then(data => {
setHerokuOAuth(!!data.heroku_oauth);
setSalesforceOAuth(!!data.salesforce_oauth);
setCsrfToken(data.csrf_token || null);
if (data.authenticated) {
setAuthed(true); setUser(data.user); load();
} else {
setAuthed(false);
}
})
.catch(() => setAuthed(false));
}, []);
const onNav = useCallback((p, id) => {
setPage(p); setPageId(id || null);
const hash = id ? `${p}/${id}` : p;
window.location.hash = hash === "dashboard" ? "" : hash;
window.scrollTo(0, 0);
}, []);
globalNav = onNav;
// Handle browser back/forward
useEffect(() => {
const handleHash = () => {
const hash = window.location.hash.slice(1);
if (hash) {
const [p, id] = hash.split("/");
setPage(p || "dashboard"); setPageId(id || null);
} else {
setPage("dashboard"); setPageId(null);
}
};
window.addEventListener("hashchange", handleHash);
return () => window.removeEventListener("hashchange", handleHash);
}, []);
const handleLogin = (u) => {
setAuthed(true); setUser(u); load();
};
const handleLogout = async () => {
await api.del("/logout");
setAuthed(false); setUser(null);
};
// --- All hooks above this line --- early returns below ---
// Still checking auth
if (authed === null) return
Loading...
;
// Server error
if (serverError) return setServerError(null)} />;
// Not authed — show login
if (!authed) return ;
let content;
switch (page) {
case "dashboard":
content = ;
break;
case "list":
content = ;
break;
case "show":
content = ;
break;
case "new":
content = ;
break;
case "env-show":
content = ;
break;
case "env-new":
content = ;
break;
case "lookup":
content = ;
break;
case "audit":
content = ;
break;
case "timeline":
content = ;
break;
case "shipments":
content = ;
break;
case "integrations":
content = ;
break;
default:
content = ;
}
return (
{content}
);
}
ReactDOM.createRoot(document.getElementById("root")).render( );