From 3b92274c53ace894a61f3690e121117fd37b3756 Mon Sep 17 00:00:00 2001 From: KevinDerWicketProfi <8469847+KevinDerWicketProfi@users.noreply.github.com> Date: Thu, 20 Aug 2026 12:37:53 +0900 Subject: [PATCH] feat(web): add copyable run deep links --- web/src/App.tsx | 42 ++++++++++++++++++++++++++++++++++++++++++ web/src/styles.css | 14 +++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/web/src/App.tsx b/web/src/App.tsx index ab85468..1ab614f 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -4,6 +4,7 @@ import { AlertTriangle, BarChart3, CheckCircle2, + Copy, Database, Headphones, ListChecks, @@ -461,6 +462,10 @@ export function App() { const [quickDemoError, setQuickDemoError] = useState(null) const [quickDemoTargetRms, setQuickDemoTargetRms] = useState(1600) const [quickDemoScenario, setQuickDemoScenario] = useState('rtp-gap') + const [copyLinkFeedback, setCopyLinkFeedback] = useState<{ + kind: 'success' | 'error' + message: string + } | null>(null) const stageDetailRef = useRef(null) const timelineQuery = useQuery({ @@ -620,6 +625,25 @@ export function App() { }) } + async function copyDeepLink() { + const url = new URL(window.location.href) + url.search = '' + url.searchParams.set('run_id', runId) + if (compareRunId) { + url.searchParams.set('compare_run_id', compareRunId) + } + + try { + await navigator.clipboard.writeText(url.toString()) + setCopyLinkFeedback({ kind: 'success', message: 'Run link copied.' }) + } catch { + setCopyLinkFeedback({ + kind: 'error', + message: 'Could not copy the run link. Check clipboard permissions and try again.', + }) + } + } + async function loadExamplePayload() { setExamplePayloadPending(true) setAsyncRunError(null) @@ -766,6 +790,24 @@ export function App() { > + + {copyLinkFeedback ? ( + + {copyLinkFeedback.message} + + ) : null} diff --git a/web/src/styles.css b/web/src/styles.css index 76de059..02ce7d8 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -134,7 +134,7 @@ a { .runForm { display: grid; - grid-template-columns: minmax(130px, 190px) minmax(200px, 330px) minmax(200px, 330px) auto auto; + grid-template-columns: minmax(130px, 190px) minmax(200px, 330px) minmax(200px, 330px) auto auto auto; align-items: end; gap: 10px; } @@ -157,6 +157,18 @@ a { background: #ffffff; } +.copyLinkFeedback { + grid-column: 1 / -1; + color: #33665b; + font-size: 12px; + font-weight: 700; + text-align: right; +} + +.copyLinkFeedback.error { + color: #8e2f35; +} + .summaryGrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));