import { basename } from 'path' import { toString as qrToString } from 'qrcode' import * as React from 'react' import { useEffect, useState } from 'react' import { getOriginalCwd } from '../bootstrap/state.js' import { buildActiveFooterText, buildIdleFooterText, FAILED_FOOTER_TEXT, getBridgeStatus, } from '../bridge/bridgeStatusUtil.js' import { BRIDGE_FAILED_INDICATOR, BRIDGE_READY_INDICATOR, } from '../constants/figures.js' import { useRegisterOverlay } from '../context/overlayContext.js' // eslint-disable-next-line custom-rules/prefer-use-keybindings -- raw 'd' key for disconnect, not a configurable keybinding action import { Box, Text, useInput } from '@anthropic/ink' import { useKeybindings } from '../keybindings/useKeybinding.js' import { useAppState, useSetAppState } from '../state/AppState.js' import { saveGlobalConfig } from '../utils/config.js' import { getBranch } from '../utils/git.js' import { Dialog } from '@anthropic/ink' type Props = { onDone: () => void } export function BridgeDialog({ onDone }: Props): React.ReactNode { useRegisterOverlay('bridge-dialog') const connected = useAppState(s => s.replBridgeConnected) const sessionActive = useAppState(s => s.replBridgeSessionActive) const reconnecting = useAppState(s => s.replBridgeReconnecting) const connectUrl = useAppState(s => s.replBridgeConnectUrl) const sessionUrl = useAppState(s => s.replBridgeSessionUrl) const error = useAppState(s => s.replBridgeError) const explicit = useAppState(s => s.replBridgeExplicit) const environmentId = useAppState(s => s.replBridgeEnvironmentId) const sessionId = useAppState(s => s.replBridgeSessionId) const verbose = useAppState(s => s.verbose) const setAppState = useSetAppState() const [showQR, setShowQR] = useState(false) const [qrText, setQrText] = useState('') const [branchName, setBranchName] = useState('') const repoName = basename(getOriginalCwd()) // Fetch branch name on mount useEffect(() => { getBranch() .then(setBranchName) .catch(() => {}) }, []) // The URL to display/QR: session URL when connected, connect URL when ready const displayUrl = sessionActive ? sessionUrl : connectUrl // Generate QR code when URL changes or QR is toggled on useEffect(() => { if (!showQR || !displayUrl) { setQrText('') return } qrToString(displayUrl, { type: 'utf8', errorCorrectionLevel: 'L', small: true, }) .then(setQrText) .catch(() => setQrText('')) }, [showQR, displayUrl]) useKeybindings( { 'confirm:yes': onDone, 'confirm:toggle': () => { setShowQR(prev => !prev) }, }, { context: 'Confirmation' }, ) useInput(input => { if (input === 'd') { // Persist opt-out only for CLI-flag/command-activated bridge. // Config-driven and GB-auto-connect users get session-only disconnect // — writing false would silently undo a Settings choice or opt a // GB-rollout user out permanently. if (explicit) { saveGlobalConfig(current => { if (current.remoteControlAtStartup === false) return current return { ...current, remoteControlAtStartup: false } }) } setAppState(prev => { if (!prev.replBridgeEnabled) return prev return { ...prev, replBridgeEnabled: false } }) onDone() } }) const { label: statusLabel, color: statusColor } = getBridgeStatus({ error, connected, sessionActive, reconnecting, }) const indicator = error ? BRIDGE_FAILED_INDICATOR : BRIDGE_READY_INDICATOR const qrLines = qrText ? qrText.split('\n').filter(l => l.length > 0) : [] // Build suffix with repo and branch (matches standalone bridge format) const contextParts: string[] = [] if (repoName) contextParts.push(repoName) if (branchName) contextParts.push(branchName) const contextSuffix = contextParts.length > 0 ? ' \u00b7 ' + contextParts.join(' \u00b7 ') : '' // Footer text matches standalone bridge const footerText = error ? FAILED_FOOTER_TEXT : displayUrl ? sessionActive ? buildActiveFooterText(displayUrl) : buildIdleFooterText(displayUrl) : undefined return ( {indicator} {statusLabel} {contextSuffix} {error && {error}} {verbose && environmentId && ( Environment: {environmentId} )} {verbose && sessionId && Session: {sessionId}} {showQR && qrLines.length > 0 && ( {qrLines.map((line, i) => ( {line} ))} )} {footerText && {footerText}} d to disconnect · space for QR code · Enter/Esc to close ) }