forked from molecule-ai/molecule-core
Follow-up to the quality-fixes-pass2 code review. ## Go: direct unit tests for PR #5 extracted helpers (~47 new tests) a2a_proxy_test.go: - resolveAgentURL: cache hit, cache-miss DB hit, not-found, null-URL, docker-rewrite guard - dispatchA2A: build error, canvas timeout, agent timeout, success - handleA2ADispatchError: context deadline, generic error, build error - maybeMarkContainerDead: nil-provisioner, runtime=external short-circuits - logA2AFailure, logA2ASuccess: activity_logs row content + status delegation_test.go: - bindDelegateRequest: valid / malformed / bad-UUID - lookupIdempotentDelegation: no-key / no-match / failed-row-deleted / existing-pending - insertDelegationRow: insertOK / insertHandledByIdempotent / insertTrackingUnavailable - insertDelegationOutcome: zero-value is insertOutcomeUnknown sentinel discovery_test.go: - discoverWorkspacePeer: online / not-found / access-denied + 2 edges - writeExternalWorkspaceURL: 3 cases - discoverHostPeer: smoke test documents the unreachable-by-design path activity_test.go: - parseSessionSearchParams: defaults + custom limit/offset/q - buildSessionSearchQuery: no-filters + with-query shapes - scanSessionSearchRows: empty / single / multiple rows Package coverage: 56.1% → 57.6%. Every helper extracted in PR #5 is now at or near 100% line coverage (see PR notes for the 4 remaining gaps, all blocked on provisioner interface mockability). ## Defensive enum zero-value fix insertDelegationOutcome now starts with insertOutcomeUnknown=0 as a sentinel so an un-initialized variable can't silently read as "success". insertOK, insertHandledByIdempotent, insertTrackingUnavailable shift to 1/2/3. No caller changes needed. ## Canvas: ConfirmDialog.singleButton test (5 cases) canvas/src/components/__tests__/ConfirmDialog.test.tsx covers: - default render (both buttons) - singleButton hides Cancel - singleButton: Escape still fires onCancel - singleButton: backdrop-click still fires onCancel - singleButton: onConfirm fires on click vitest total: 352 → 357, all passing. ## Docstring clarity ConfirmDialog.tsx: expanded singleButton prop comment to explicitly instruct callers to pass the same handler for onConfirm/onCancel when using it as an info toast (matches TemplatePalette usage). ## ErrorBoundary clipboard observability .catch(() => {}) silently swallowed rejections. Now: .catch((e) => console.warn("clipboard write failed:", e)) so permission-denied / insecure-context failures surface in the console. ## Verification - go build ./... clean - go vet ./... clean - go test -race ./internal/... — all pass - canvas npm run build — clean - canvas npm test -- --run — 357/357 pass - tests/e2e/test_api.sh — 46/62 pass; all 16 failures are pre-existing (token-auth enforcement + stale test workspaces + missing Docker network). None involve handlers touched in PR #5. - Manual: platform + canvas running locally, title=Molecule AI, /workspaces returns [], /health returns ok. Identified + killed a stale Next.js server from the old Starfire-AgentTeam repo that was serving the old brand on IPv4 port 3000. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
107 lines
3.6 KiB
TypeScript
107 lines
3.6 KiB
TypeScript
"use client";
|
|
|
|
import React from "react";
|
|
|
|
interface ErrorBoundaryProps {
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
interface ErrorBoundaryState {
|
|
hasError: boolean;
|
|
error: Error | null;
|
|
}
|
|
|
|
export class ErrorBoundary extends React.Component<
|
|
ErrorBoundaryProps,
|
|
ErrorBoundaryState
|
|
> {
|
|
constructor(props: ErrorBoundaryProps) {
|
|
super(props);
|
|
this.state = { hasError: false, error: null };
|
|
}
|
|
|
|
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
|
return { hasError: true, error };
|
|
}
|
|
|
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
|
|
console.error("ErrorBoundary caught an error:", error, errorInfo.componentStack);
|
|
}
|
|
|
|
handleReload = () => {
|
|
window.location.reload();
|
|
};
|
|
|
|
handleReport = () => {
|
|
const errorDetails = {
|
|
message: this.state.error?.message ?? "Unknown error",
|
|
stack: this.state.error?.stack ?? "N/A",
|
|
timestamp: new Date().toISOString(),
|
|
url: window.location.href,
|
|
};
|
|
// Log the full report to console for collection by monitoring tools
|
|
console.error("Error Report:", JSON.stringify(errorDetails, null, 2));
|
|
// Copy error info to clipboard for manual reporting (button click is its
|
|
// own affordance — no native alert needed). On clipboard failure the
|
|
// console.error above still surfaces the report.
|
|
void navigator.clipboard?.writeText(JSON.stringify(errorDetails, null, 2))
|
|
.catch((e) => console.warn("clipboard write failed:", e));
|
|
};
|
|
|
|
render() {
|
|
if (this.state.hasError) {
|
|
return (
|
|
<div className="fixed inset-0 flex items-center justify-center bg-zinc-950 z-50">
|
|
<div className="max-w-md rounded-2xl border border-red-500/30 bg-zinc-900/90 px-8 py-8 text-center shadow-2xl shadow-black/40">
|
|
<div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-full bg-red-500/10 border border-red-500/30">
|
|
<svg
|
|
width="24"
|
|
height="24"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="#ef4444"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
>
|
|
<circle cx="12" cy="12" r="10" />
|
|
<line x1="12" y1="8" x2="12" y2="12" />
|
|
<line x1="12" y1="16" x2="12.01" y2="16" />
|
|
</svg>
|
|
</div>
|
|
<h2 className="text-lg font-semibold text-zinc-100 mb-2">
|
|
Something went wrong
|
|
</h2>
|
|
<p className="text-sm text-zinc-400 mb-1">
|
|
An unexpected error occurred while rendering the application.
|
|
</p>
|
|
<p className="text-xs text-red-400/80 mb-6 font-mono break-all">
|
|
{this.state.error?.message ?? "Unknown error"}
|
|
</p>
|
|
<div className="flex items-center justify-center gap-3">
|
|
<button
|
|
onClick={this.handleReload}
|
|
className="rounded-lg bg-blue-600 hover:bg-blue-500 px-5 py-2 text-sm font-medium text-white transition-colors"
|
|
>
|
|
Reload
|
|
</button>
|
|
<a
|
|
href="#report"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
this.handleReport();
|
|
}}
|
|
className="rounded-lg border border-zinc-700 hover:border-zinc-600 px-5 py-2 text-sm font-medium text-zinc-300 hover:text-zinc-100 transition-colors"
|
|
>
|
|
Report
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return this.props.children;
|
|
}
|
|
}
|