169169 align-items : flex-start;
170170 }
171171 }
172+
173+ /* Phase 1 Feature Panel */
174+ .feature-panel {
175+ background : rgba (255 , 255 , 255 , 0.1 );
176+ backdrop-filter : blur (10px );
177+ padding : 15px ;
178+ border-radius : 10px ;
179+ color : white;
180+ margin-top : 20px ;
181+ }
182+
183+ .feature-panel h3 {
184+ margin-bottom : 15px ;
185+ font-size : 1.2rem ;
186+ }
187+
188+ .button-grid {
189+ display : grid;
190+ grid-template-columns : repeat (auto-fit, minmax (200px , 1fr ));
191+ gap : 10px ;
192+ margin-bottom : 15px ;
193+ }
194+
195+ .test-button {
196+ background : rgba (76 , 175 , 80 , 0.8 );
197+ border : none;
198+ padding : 12px 20px ;
199+ border-radius : 5px ;
200+ color : white;
201+ cursor : pointer;
202+ font-size : 14px ;
203+ font-weight : 500 ;
204+ transition : all 0.2s ;
205+ }
206+
207+ .test-button : hover {
208+ background : rgba (76 , 175 , 80 , 1 );
209+ transform : translateY (-2px );
210+ box-shadow : 0 4px 12px rgba (0 , 0 , 0 , 0.3 );
211+ }
212+
213+ .test-button : active {
214+ transform : translateY (0 );
215+ }
216+
217+ .event-log {
218+ background : rgba (0 , 0 , 0 , 0.3 );
219+ border-radius : 5px ;
220+ padding : 10px ;
221+ max-height : 150px ;
222+ overflow-y : auto;
223+ font-family : 'Monaco' , 'Menlo' , monospace;
224+ font-size : 12px ;
225+ }
226+
227+ .event-log .event {
228+ padding : 4px 0 ;
229+ border-bottom : 1px solid rgba (255 , 255 , 255 , 0.1 );
230+ }
231+
232+ .event-log .event : last-child {
233+ border-bottom : none;
234+ }
235+
236+ .event-log .event-type {
237+ color : # 4caf50 ;
238+ font-weight : bold;
239+ }
240+
241+ .event-log .event-data {
242+ color : # e0e0e0 ;
243+ }
244+
245+ .clear-log {
246+ background : rgba (244 , 67 , 54 , 0.8 );
247+ border : none;
248+ padding : 8px 16px ;
249+ border-radius : 5px ;
250+ color : white;
251+ cursor : pointer;
252+ font-size : 12px ;
253+ margin-top : 10px ;
254+ }
255+
256+ .clear-log : hover {
257+ background : rgba (244 , 67 , 54 , 1 );
258+ }
172259 </ style >
173260 </ head >
174261 < body >
@@ -193,21 +280,25 @@ <h1>🖥️ Ghostty Terminal</h1>
193280 < div id ="terminal-container "> </ div >
194281 </ div >
195282
196- < div class ="info-box ">
197- < h3 > 📚 Available Commands</ h3 >
198- < ul >
199- < li > < code > ls</ code > , < code > ls -la</ code > - List directory contents</ li >
200- < li >
201- < code > cd <path></ code > - Change directory (supports < code > ~</ code > ,
202- < code > /</ code > , < code > ..</ code > )
203- </ li >
204- < li > < code > pwd</ code > - Print working directory</ li >
205- < li > < code > cat <file></ code > - Display file contents</ li >
206- < li > < code > grep</ code > , < code > find</ code > , < code > tree</ code > - Search and explore</ li >
207- < li > < code > whoami</ code > , < code > hostname</ code > , < code > date</ code > - System info</ li >
208- < li > < code > clear</ code > - Clear screen</ li >
209- < li > < code > exit</ code > - Close connection</ li >
210- </ ul >
283+ < div class ="feature-panel ">
284+ < h3 > 🎯 Phase 1 Features - xterm.js API Parity</ h3 >
285+ < div class ="button-grid ">
286+ < button class ="test-button " id ="btn-paste "> 📋 Test paste()</ button >
287+ < button class ="test-button " id ="btn-blur "> 👁️ Test blur()</ button >
288+ < button class ="test-button " id ="btn-input "> ⌨️ Test input()</ button >
289+ < button class ="test-button " id ="btn-select "> 🔤 Test select()</ button >
290+ < button class ="test-button " id ="btn-selectLines "> 📄 Test selectLines()</ button >
291+ < button class ="test-button " id ="btn-getSelectionPos "> 📍 Get Selection Position</ button >
292+ < button class ="test-button " id ="btn-setTitle "> 🏷️ Set Title (OSC)</ button >
293+ < button class ="test-button " id ="btn-customHandler "> 🔧 Toggle Custom Handler</ button >
294+ </ div >
295+ < div >
296+ < strong style ="display: block; margin-bottom: 8px; "> Event Log (onKey, onTitleChange):</ strong >
297+ < div class ="event-log " id ="event-log ">
298+ < div style ="opacity: 0.5; text-align: center; "> Events will appear here...</ div >
299+ </ div >
300+ < button class ="clear-log " id ="btn-clear-log "> Clear Log</ button >
301+ </ div >
211302 </ div >
212303
213304 < div class ="warning ">
@@ -532,6 +623,129 @@ <h3>📚 Available Commands</h3>
532623 // - Auto-copy to clipboard on selection
533624 // - Ctrl+C / Cmd+C to copy (via browser context menu)
534625
626+ // =========================================================================
627+ // Phase 1 Features - Event Logging & Handlers
628+ // =========================================================================
629+
630+ let customHandlerEnabled = false ;
631+
632+ function logEvent ( type , data ) {
633+ const eventLog = document . getElementById ( 'event-log' ) ;
634+ const eventDiv = document . createElement ( 'div' ) ;
635+ eventDiv . className = 'event' ;
636+
637+ const timestamp = new Date ( ) . toLocaleTimeString ( ) ;
638+ const dataStr = typeof data === 'object' ? JSON . stringify ( data ) : String ( data ) ;
639+
640+ eventDiv . innerHTML = `
641+ <span style="opacity: 0.6;">[${ timestamp } ]</span>
642+ <span class="event-type">${ type } :</span>
643+ <span class="event-data">${ dataStr } </span>
644+ ` ;
645+
646+ eventLog . appendChild ( eventDiv ) ;
647+ eventLog . scrollTop = eventLog . scrollHeight ;
648+
649+ // Keep only last 50 events
650+ while ( eventLog . children . length > 50 ) {
651+ eventLog . removeChild ( eventLog . firstChild ) ;
652+ }
653+ }
654+
655+ function setupPhase1Events ( ) {
656+ // Event: onKey - fires on every keypress
657+ term . onKey ( ( e ) => {
658+ logEvent ( 'onKey' , `key="${ e . key } " ctrl=${ e . domEvent . ctrlKey } alt=${ e . domEvent . altKey } ` ) ;
659+ } ) ;
660+
661+ // Event: onTitleChange - fires when terminal title changes via OSC sequences
662+ term . onTitleChange ( ( title ) => {
663+ logEvent ( 'onTitleChange' , title ) ;
664+ document . title = `${ title } - Ghostty Terminal` ;
665+ } ) ;
666+
667+ // Button handlers
668+ document . getElementById ( 'btn-paste' ) . addEventListener ( 'click' , ( ) => {
669+ term . paste ( 'Pasted text from paste() method!\n' ) ;
670+ logEvent ( 'Action' , 'Called paste()' ) ;
671+ } ) ;
672+
673+ document . getElementById ( 'btn-blur' ) . addEventListener ( 'click' , ( ) => {
674+ term . blur ( ) ;
675+ logEvent ( 'Action' , 'Called blur() - terminal lost focus' ) ;
676+ } ) ;
677+
678+ document . getElementById ( 'btn-input' ) . addEventListener ( 'click' , ( ) => {
679+ term . input ( 'echo "Input from input() method"\n' , true ) ;
680+ logEvent ( 'Action' , 'Called input() with wasUserInput=true' ) ;
681+ } ) ;
682+
683+ document . getElementById ( 'btn-select' ) . addEventListener ( 'click' , ( ) => {
684+ // Select 20 characters starting at column 0, row 0
685+ // This selects from the current cursor position backwards 20 chars
686+ const cursor = term . wasmTerm . getCursor ( ) ;
687+ const startRow = Math . max ( 0 , cursor . y - 1 ) ;
688+ term . select ( 0 , startRow , 30 ) ;
689+ logEvent ( 'Action' , `Called select(0, ${ startRow } , 30) - selects 30 chars from row ${ startRow } ` ) ;
690+ } ) ;
691+
692+ document . getElementById ( 'btn-selectLines' ) . addEventListener ( 'click' , ( ) => {
693+ // Select last 3 visible lines
694+ const cursor = term . wasmTerm . getCursor ( ) ;
695+ const endRow = cursor . y ;
696+ const startRow = Math . max ( 0 , endRow - 2 ) ;
697+ term . selectLines ( startRow , endRow ) ;
698+ logEvent ( 'Action' , `Called selectLines(${ startRow } , ${ endRow } ) - selects 3 lines` ) ;
699+ } ) ;
700+
701+ document . getElementById ( 'btn-getSelectionPos' ) . addEventListener ( 'click' , ( ) => {
702+ const pos = term . getSelectionPosition ( ) ;
703+ if ( pos ) {
704+ logEvent ( 'getSelectionPosition' ,
705+ `start=(${ pos . start . x } ,${ pos . start . y } ) end=(${ pos . end . x } ,${ pos . end . y } )` ) ;
706+ } else {
707+ logEvent ( 'getSelectionPosition' , 'No selection' ) ;
708+ }
709+ } ) ;
710+
711+ document . getElementById ( 'btn-setTitle' ) . addEventListener ( 'click' , ( ) => {
712+ // Send OSC 2 sequence to change title
713+ const newTitle = `Ghostty Terminal ${ new Date ( ) . toLocaleTimeString ( ) } ` ;
714+ term . write ( `\x1b]2;${ newTitle } \x07` ) ;
715+ logEvent ( 'Action' , `Sent OSC 2 sequence with title: ${ newTitle } ` ) ;
716+ } ) ;
717+
718+ document . getElementById ( 'btn-customHandler' ) . addEventListener ( 'click' , ( ) => {
719+ customHandlerEnabled = ! customHandlerEnabled ;
720+
721+ if ( customHandlerEnabled ) {
722+ // Custom handler that blocks Ctrl+K
723+ term . attachCustomKeyEventHandler ( ( event ) => {
724+ if ( event . ctrlKey && event . key === 'k' ) {
725+ logEvent ( 'CustomHandler' , 'Blocked Ctrl+K' ) ;
726+ return true ; // Block default handling
727+ }
728+ return false ; // Allow default handling
729+ } ) ;
730+ logEvent ( 'Action' , 'Custom handler enabled (blocks Ctrl+K)' ) ;
731+ document . getElementById ( 'btn-customHandler' ) . style . background = 'rgba(33, 150, 243, 0.8)' ;
732+ } else {
733+ term . attachCustomKeyEventHandler ( undefined ) ;
734+ logEvent ( 'Action' , 'Custom handler disabled' ) ;
735+ document . getElementById ( 'btn-customHandler' ) . style . background = 'rgba(76, 175, 80, 0.8)' ;
736+ }
737+ } ) ;
738+
739+ document . getElementById ( 'btn-clear-log' ) . addEventListener ( 'click' , ( ) => {
740+ document . getElementById ( 'event-log' ) . innerHTML =
741+ '<div style="opacity: 0.5; text-align: center;">Log cleared</div>' ;
742+ } ) ;
743+
744+ // Expose terminal to console for debugging
745+ window . term = term ;
746+ console . log ( 'Terminal exposed to window.term for debugging' ) ;
747+ }
748+
535749 // =========================================================================
536750 // Initialization
537751 // =========================================================================
@@ -603,6 +817,9 @@ <h3>📚 Available Commands</h3>
603817 // Text selection is now built-in - no need to enable it!
604818 // You can use term.getSelection(), term.selectAll(), etc.
605819
820+ // Phase 1: Hook up new event listeners
821+ setupPhase1Events ( ) ;
822+
606823 // Connect to WebSocket server
607824 connect ( ) ;
608825 } catch ( error ) {
0 commit comments