@@ -129,6 +129,11 @@ export class OpenClawTerminalPanel extends LitElement {
129129 @property ( { type : Boolean } ) available = false ;
130130 /** Active Control UI color mode, mirrored into the terminal theme. */
131131 @property ( { attribute : false } ) themeMode : "dark" | "light" = "dark" ;
132+ /**
133+ * Terminal-only document mode (`?view=terminal`), used by the mobile apps'
134+ * WebViews: fills the viewport, always open while available, no dock chrome.
135+ */
136+ @property ( { type : Boolean } ) fullscreen = false ;
132137
133138 @state ( ) private open = false ;
134139 @state ( ) private dock : TerminalDock = "bottom" ;
@@ -159,15 +164,21 @@ export class OpenClawTerminalPanel extends LitElement {
159164
160165 override connectedCallback ( ) : void {
161166 super . connectedCallback ( ) ;
162- const layout = loadLayout ( ) ;
163- this . dock = layout . dock ;
164- this . height = layout . height ;
165- this . width = layout . width ;
166- // Only restore the open state when the surface is actually available.
167- this . open = layout . open && this . available ;
168- window . addEventListener ( "keydown" , this . onGlobalKeyDown ) ;
169- window . addEventListener ( TOGGLE_EVENT , this . onToggleRequest ) ;
170- window . addEventListener ( "resize" , this . onViewportResize ) ;
167+ if ( ! this . fullscreen ) {
168+ const layout = loadLayout ( ) ;
169+ this . dock = layout . dock ;
170+ this . height = layout . height ;
171+ this . width = layout . width ;
172+ // Only restore the open state when the surface is actually available.
173+ this . open = layout . open && this . available ;
174+ window . addEventListener ( "keydown" , this . onGlobalKeyDown ) ;
175+ window . addEventListener ( TOGGLE_EVENT , this . onToggleRequest ) ;
176+ window . addEventListener ( "resize" , this . onViewportResize ) ;
177+ } else {
178+ // Fullscreen documents have no toggle/dock chrome; the panel is simply
179+ // open whenever the terminal surface is available.
180+ this . open = this . available ;
181+ }
171182 if ( this . open ) {
172183 void this . restoreSessions ( ) ;
173184 }
@@ -196,9 +207,10 @@ export class OpenClawTerminalPanel extends LitElement {
196207 // open preference, or the reconnect path would never auto-reopen.
197208 this . open = false ;
198209 this . disposeAllTabs ( ) ;
199- } else if ( ! this . open && loadLayout ( ) . open ) {
210+ } else if ( ! this . open && ( this . fullscreen || loadLayout ( ) . open ) ) {
200211 // Hello arrived after mount (or a reconnect); restore the persisted
201- // open state and reattach persisted sessions where possible.
212+ // open state (fullscreen documents are always open while available)
213+ // and reattach persisted sessions where possible.
202214 this . open = true ;
203215 void this . restoreSessions ( ) ;
204216 }
@@ -241,6 +253,10 @@ export class OpenClawTerminalPanel extends LitElement {
241253 * content simply shrinks to make room, so this reads as a real dock.
242254 */
243255 private syncLayoutReservation ( ) : void {
256+ if ( this . fullscreen ) {
257+ // No shell content to reserve space for in a terminal-only document.
258+ return ;
259+ }
244260 const root = document . documentElement . style ;
245261 const bottom =
246262 this . available && this . open && this . dock === "bottom" ? `${ this . height } px` : "0px" ;
@@ -533,7 +549,10 @@ export class OpenClawTerminalPanel extends LitElement {
533549 this . activeId = this . tabs . at ( - 1 ) ?. id ?? null ;
534550 }
535551 this . persistLiveSessions ( ) ;
536- if ( this . tabs . length === 0 ) {
552+ // Fullscreen documents (mobile WebViews) have no toggle to reopen a closed
553+ // panel, so closing the last tab keeps the panel with an empty tab strip
554+ // (the "+" button stays reachable) instead of leaving a dead blank page.
555+ if ( this . tabs . length === 0 && ! this . fullscreen ) {
537556 this . closePanel ( ) ;
538557 }
539558 }
@@ -650,15 +669,22 @@ export class OpenClawTerminalPanel extends LitElement {
650669 if ( ! this . available || ! this . open ) {
651670 return nothing ;
652671 }
653- const style = this . dock === "bottom" ? `height:${ this . height } px` : `width:${ this . width } px` ;
672+ const mode = this . fullscreen ? "fullscreen" : this . dock ;
673+ const style = this . fullscreen
674+ ? nothing
675+ : this . dock === "bottom"
676+ ? `height:${ this . height } px`
677+ : `width:${ this . width } px` ;
654678 return html `
655- <section class= "tp tp--${ this . dock } " style = ${ style } aria- label= ${ t ( "terminal.title" ) } >
656- <div
657- class= "tp-resizer tp-resizer--${ this . dock } "
658- @pointerdown = ${ ( e : PointerEvent ) => this . startResize ( e ) }
659- role= "separator"
660- aria-label = ${ t ( "terminal.resize" ) }
661- > </ div>
679+ <section class= "tp tp--${ mode } " style = ${ style } aria- label= ${ t ( "terminal.title" ) } >
680+ ${ this . fullscreen
681+ ? nothing
682+ : html `<div
683+ class= "tp-resizer tp-resizer--${ this . dock } "
684+ @pointerdown = ${ ( e : PointerEvent ) => this . startResize ( e ) }
685+ role= "separator"
686+ aria-label = ${ t ( "terminal.resize" ) }
687+ > </ div> ` }
662688 <header class= "tp-header" >
663689 <div class= "tp-tabs" role = "tablis t">
664690 ${ this . tabs . map (
@@ -704,35 +730,37 @@ export class OpenClawTerminalPanel extends LitElement {
704730 ${ PLUS_GLYPH }
705731 </ butto n>
706732 </ div>
707- <div class= "tp-actions" >
708- <butto n
709- class= "tp-icon ${ this . dock === "bottom" ? "is-active" : "" } "
710- type = "butto n"
711- title= ${ t ( "terminal.dockBottom" ) }
712- aria- label= ${ t ( "terminal.dockBottom" ) }
713- @click = ${ ( ) => this . setDock ( "bottom" ) }
714- >
715- ${ DOCK_BOTTOM_GLYPH }
716- </ butto n>
717- <butto n
718- class= "tp-icon ${ this . dock === "right" ? "is-active" : "" } "
719- type = "butto n"
720- title= ${ t ( "terminal.dockRight" ) }
721- aria- label= ${ t ( "terminal.dockRight" ) }
722- @click = ${ ( ) => this . setDock ( "right" ) }
723- >
724- ${ DOCK_RIGHT_GLYPH }
725- </ butto n>
726- <butto n
727- class= "tp-icon"
728- type = "butto n"
729- title= ${ t ( "terminal.hide" ) }
730- aria- label= ${ t ( "terminal.hide" ) }
731- @click = ${ ( ) => this . closePanel ( ) }
732- >
733- ${ CLOSE_GLYPH }
734- </ butto n>
735- </ div>
733+ ${ this . fullscreen
734+ ? nothing
735+ : html `<div class= "tp-actions" >
736+ <butto n
737+ class= "tp-icon ${ this . dock === "bottom" ? "is-active" : "" } "
738+ type = "butto n"
739+ title= ${ t ( "terminal.dockBottom" ) }
740+ aria- label= ${ t ( "terminal.dockBottom" ) }
741+ @click = ${ ( ) => this . setDock ( "bottom" ) }
742+ >
743+ ${ DOCK_BOTTOM_GLYPH }
744+ </ butto n>
745+ <butto n
746+ class= "tp-icon ${ this . dock === "right" ? "is-active" : "" } "
747+ type = "butto n"
748+ title= ${ t ( "terminal.dockRight" ) }
749+ aria- label= ${ t ( "terminal.dockRight" ) }
750+ @click = ${ ( ) => this . setDock ( "right" ) }
751+ >
752+ ${ DOCK_RIGHT_GLYPH }
753+ </ butto n>
754+ <butto n
755+ class= "tp-icon"
756+ type = "butto n"
757+ title= ${ t ( "terminal.hide" ) }
758+ aria- label= ${ t ( "terminal.hide" ) }
759+ @click = ${ ( ) => this . closePanel ( ) }
760+ >
761+ ${ CLOSE_GLYPH }
762+ </ butto n>
763+ </ div> ` }
736764 </ header>
737765 ${ this . errorText
738766 ? html `<div class= "tp-error" role = "alert"> ${ this . errorText } </ div> `
@@ -782,6 +810,10 @@ export class OpenClawTerminalPanel extends LitElement {
782810 bottom : 0 ;
783811 border-left : 1px solid var (--border , # 262b34 );
784812 }
813+ /* Terminal-only document (mobile WebViews): fill the viewport, no seams. */
814+ .tp--fullscreen {
815+ inset : 0 ;
816+ }
785817 .tp-resizer {
786818 position : absolute;
787819 z-index : 2 ;
0 commit comments