1- import { useState } from "react" ;
1+ import { useRef , useState } from "react" ;
22import { XIcon , WarningIcon , CheckCircleIcon , CaretRightIcon } from "@phosphor-icons/react" ;
33import { copyTextToClipboard } from "../utils/clipboard" ;
4+ import { useDialogBehavior } from "./ui/useDialogBehavior" ;
45
56export interface LintFinding {
67 severity : "error" | "warning" ;
@@ -12,16 +13,28 @@ export interface LintFinding {
1213export function LintModal ( {
1314 findings,
1415 projectId,
16+ projectDir,
17+ title = "HyperFrame Lint Results" ,
18+ promptIntro = "Fix these HyperFrames lint issues" ,
1519 onClose,
1620} : {
1721 findings : LintFinding [ ] ;
1822 projectId : string ;
23+ /** Real on-disk project directory for the agent prompt (not the browser URL). */
24+ projectDir ?: string | null ;
25+ /** Header subtitle — parameterize so console errors don't masquerade as lint results. */
26+ title ?: string ;
27+ /** First line of the copied agent prompt. */
28+ promptIntro ?: string ;
1929 onClose : ( ) => void ;
2030} ) {
2131 const errors = findings . filter ( ( f ) => f . severity === "error" ) ;
2232 const warnings = findings . filter ( ( f ) => f . severity === "warning" ) ;
2333 const hasIssues = findings . length > 0 ;
2434 const [ copied , setCopied ] = useState ( false ) ;
35+ const [ copyFailed , setCopyFailed ] = useState ( false ) ;
36+ const containerRef = useRef < HTMLDivElement > ( null ) ;
37+ const { requestClose } = useDialogBehavior ( { open : true , onClose, containerRef } ) ;
2538
2639 const handleCopyToAgent = async ( ) => {
2740 const lines = findings . map ( ( f ) => {
@@ -30,21 +43,31 @@ export function LintModal({
3043 if ( f . fixHint ) line += `\n Fix: ${ f . fixHint } ` ;
3144 return line ;
3245 } ) ;
33- const text = `Fix these HyperFrames lint issues for project "${ projectId } ":\n\nProject path: ${ window . location . href } \n\n${ lines . join ( "\n\n" ) } ` ;
46+ const pathLine = projectDir ? `Project path: ${ projectDir } \n\n` : "" ;
47+ const text = `${ promptIntro } for project "${ projectId } ":\n\n${ pathLine } ${ lines . join ( "\n\n" ) } ` ;
3448 const copiedText = await copyTextToClipboard ( text ) ;
3549 if ( copiedText ) {
3650 setCopied ( true ) ;
51+ setCopyFailed ( false ) ;
3752 setTimeout ( ( ) => setCopied ( false ) , 2000 ) ;
53+ } else {
54+ setCopyFailed ( true ) ;
55+ setTimeout ( ( ) => setCopyFailed ( false ) , 3000 ) ;
3856 }
3957 } ;
4058
4159 return (
4260 < div
43- className = "fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm"
44- onClick = { onClose }
61+ className = "hf-backdrop-in fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm"
62+ onClick = { requestClose }
4563 >
4664 < div
47- className = "bg-neutral-950 border border-neutral-800 rounded-xl shadow-2xl w-full max-w-xl max-h-[80vh] flex flex-col overflow-hidden"
65+ ref = { containerRef }
66+ role = "dialog"
67+ aria-modal = "true"
68+ aria-label = { title }
69+ tabIndex = { - 1 }
70+ className = "bg-neutral-950 border border-neutral-800 rounded-xl shadow-2xl w-full max-w-xl max-h-[80vh] flex flex-col overflow-hidden outline-none"
4871 onClick = { ( e ) => e . stopPropagation ( ) }
4972 >
5073 { /* Header */ }
@@ -65,12 +88,13 @@ export function LintModal({
6588 ? `${ errors . length } error${ errors . length !== 1 ? "s" : "" } , ${ warnings . length } warning${ warnings . length !== 1 ? "s" : "" } `
6689 : "All checks passed" }
6790 </ h2 >
68- < p className = "text-xs text-neutral-500" > HyperFrame Lint Results </ p >
91+ < p className = "text-xs text-neutral-500" > { title } </ p >
6992 </ div >
7093 </ div >
7194 < button
7295 onClick = { onClose }
73- className = "p-1.5 rounded-lg text-neutral-500 hover:text-neutral-200 hover:bg-neutral-800 transition-colors"
96+ aria-label = "Close"
97+ className = "p-1.5 rounded-lg text-neutral-500 hover:text-neutral-200 hover:bg-neutral-800 transition-colors active:scale-[0.98]"
7498 >
7599 < XIcon size = { 16 } />
76100 </ button >
@@ -81,13 +105,19 @@ export function LintModal({
81105 < div className = "flex items-center justify-end px-5 py-2 border-b border-neutral-800/50" >
82106 < button
83107 onClick = { handleCopyToAgent }
84- className = { `px-3 py-1 text-xs font-medium rounded-lg transition-colors ${
108+ className = { `px-3 py-1 text-xs font-medium rounded-lg transition-colors active:scale-[0.98] ${
85109 copied
86110 ? "bg-green-600 text-white"
87- : "bg-studio-accent hover:bg-studio-accent/80 text-white"
111+ : copyFailed
112+ ? "bg-red-600 text-white"
113+ : "bg-studio-accent hover:bg-studio-accent/80 text-white"
88114 } `}
89115 >
90- { copied ? "Copied!" : "Copy to Agent" }
116+ { copied
117+ ? "Copied!"
118+ : copyFailed
119+ ? "Copy failed — check permissions"
120+ : "Copy to Agent" }
91121 </ button >
92122 </ div >
93123 ) }
0 commit comments