navigator.clipboard.writeText(JSON.stringify(diagnostics, null, 2))
this.setState({ copied: true })
setTimeout(() => this.setState({ copied: false }), 2000)
(frontend/src/components/ErrorBoundary.tsx:85-87)
Three issues in three lines, in the one component that must never fail.
1. The promise is not awaited and has no .catch. navigator.clipboard.writeText rejects when the document is not focused, when the Permissions API denies clipboard-write, or in any non-secure context. navigator.clipboard is undefined entirely on plain HTTP origins other than localhost — which is precisely the deployment shape README.md:255-258 describes for this RC (an internal host, one frontend process). On such a host this line throws TypeError: Cannot read properties of undefined (reading 'writeText') synchronously, inside a click handler rendered by the error boundary.
Trigger: hit any app error while browsing FAROS over http://<internal-host>:5176, then click "Copy Diagnostics".
Observed: nothing is copied, and the button still flips to the "copied!" state (step 2 below runs regardless). The user believes they have the diagnostics on their clipboard and pastes stale content into the bug report.
Expected: either it copies, or it tells the user it could not.
2. copied: true is set unconditionally, before the write is known to have succeeded. It should be set in the .then.
3. setTimeout with no stored handle, firing setState 2 seconds later on a component that may have been unmounted by handleReset — which navigates the whole document at ErrorBoundary.tsx:51.
Suggested:
handleCopyDiagnostics = async () => {
const text = JSON.stringify(diagnostics, null, 2)
try {
if (!navigator.clipboard) throw new Error('clipboard unavailable')
await navigator.clipboard.writeText(text)
this.setState({ copied: true })
this.copyTimer = window.setTimeout(() => this.setState({ copied: false }), 2000)
} catch {
this.setState({ copyFailed: true }) // render a selectable <textarea> fallback
}
}
componentWillUnmount() { window.clearTimeout(this.copyTimer) }
The <textarea> fallback is the part I would push hardest for. This button exists so that users can hand you a stack trace; on the exact deployment topology this project targets, it currently cannot.
Separately, and more debatable: handleReset at ErrorBoundary.tsx:44-52 clears the error state and then does window.location.href = '/', which is a full document reload that discards the React tree, the QueryClient cache from main.tsx:8, and every lazy chunk. Since the state reset immediately preceding it is thrown away by the navigation, one of the two is redundant. If the intent is "recover in place", use navigate('/') and drop the assignment; if the intent is "nuke everything", drop the setState and say so in a comment.
(
frontend/src/components/ErrorBoundary.tsx:85-87)Three issues in three lines, in the one component that must never fail.
1. The promise is not awaited and has no
.catch.navigator.clipboard.writeTextrejects when the document is not focused, when the Permissions API denies clipboard-write, or in any non-secure context.navigator.clipboardisundefinedentirely on plain HTTP origins other thanlocalhost— which is precisely the deployment shapeREADME.md:255-258describes for this RC (an internal host, one frontend process). On such a host this line throwsTypeError: Cannot read properties of undefined (reading 'writeText')synchronously, inside a click handler rendered by the error boundary.Trigger: hit any app error while browsing FAROS over
http://<internal-host>:5176, then click "Copy Diagnostics".Observed: nothing is copied, and the button still flips to the "copied!" state (step 2 below runs regardless). The user believes they have the diagnostics on their clipboard and pastes stale content into the bug report.
Expected: either it copies, or it tells the user it could not.
2.
copied: trueis set unconditionally, before the write is known to have succeeded. It should be set in the.then.3.
setTimeoutwith no stored handle, firingsetState2 seconds later on a component that may have been unmounted byhandleReset— which navigates the whole document atErrorBoundary.tsx:51.Suggested:
The
<textarea>fallback is the part I would push hardest for. This button exists so that users can hand you a stack trace; on the exact deployment topology this project targets, it currently cannot.Separately, and more debatable:
handleResetatErrorBoundary.tsx:44-52clears the error state and then doeswindow.location.href = '/', which is a full document reload that discards the React tree, theQueryClientcache frommain.tsx:8, and every lazy chunk. Since the state reset immediately preceding it is thrown away by the navigation, one of the two is redundant. If the intent is "recover in place", usenavigate('/')and drop the assignment; if the intent is "nuke everything", drop thesetStateand say so in a comment.