@@ -77,6 +77,7 @@ pub fn render(frame: &mut Frame, app: &App) {
7777 InputMode :: Generate => render_generator ( frame, app, body) ,
7878 InputMode :: Form => render_form ( frame, app, body) ,
7979 InputMode :: ConfirmDelete => render_confirm ( frame, app, body) ,
80+ InputMode :: About => render_about ( frame, body) ,
8081 InputMode :: Normal | InputMode :: Search | InputMode :: Command | InputMode :: Unlock => { }
8182 }
8283 render_status_bar ( frame, app, status_bar) ;
@@ -310,6 +311,49 @@ const fn onoff(b: bool) -> &'static str {
310311 if b { "on" } else { "off" }
311312}
312313
314+ /// Centered read-only About overlay (Standard §13.2), drawn over the browser.
315+ /// Renders from `crate::PKG_VERSION` + `crate::ATTRIBUTION` so it can't drift
316+ /// from `vault-tui --version`.
317+ fn render_about ( frame : & mut Frame , area : Rect ) {
318+ let amber = hex ( steelbore:: MOLTEN_AMBER ) ;
319+ let steel = hex ( steelbore:: STEEL_BLUE ) ;
320+ let info = hex ( steelbore:: INFO ) ;
321+ let mut lines = vec ! [
322+ Line :: from( "" ) ,
323+ Line :: from( Span :: styled(
324+ format!( "Vault v{}" , crate :: PKG_VERSION ) ,
325+ Style :: default ( ) . fg( amber) . add_modifier( Modifier :: BOLD ) ,
326+ ) ) ,
327+ Line :: from( "" ) ,
328+ ] ;
329+ lines. extend (
330+ crate :: ATTRIBUTION
331+ . lines ( )
332+ . map ( |l| Line :: from ( Span :: styled ( l. to_owned ( ) , Style :: default ( ) . fg ( info) ) ) ) ,
333+ ) ;
334+ lines. push ( Line :: from ( "" ) ) ;
335+ lines. push ( Line :: from ( Span :: styled (
336+ "Esc close" ,
337+ Style :: default ( ) . fg ( steel) . add_modifier ( Modifier :: ITALIC ) ,
338+ ) ) ) ;
339+ let block = Block :: default ( )
340+ . borders ( Borders :: ALL )
341+ . border_style ( Style :: default ( ) . fg ( amber) )
342+ . title ( " About " )
343+ . style ( Style :: default ( ) . bg ( hex ( steelbore:: VOID_NAVY ) ) ) ;
344+ // Wider/taller than the generator overlay so the long §13.2 lines fit
345+ // without clipping the URL or hint.
346+ let overlay = centered ( area, 80 , 60 ) ;
347+ frame. render_widget ( ratatui:: widgets:: Clear , overlay) ;
348+ frame. render_widget (
349+ Paragraph :: new ( lines)
350+ . alignment ( Alignment :: Center )
351+ . wrap ( Wrap { trim : true } )
352+ . block ( block) ,
353+ overlay,
354+ ) ;
355+ }
356+
313357/// Centered add/edit form overlay, drawn over the browser.
314358fn render_form ( frame : & mut Frame , app : & App , area : Rect ) {
315359 let Some ( form) = app. form . as_ref ( ) else {
@@ -450,7 +494,8 @@ fn render_status_bar(frame: &mut Frame, app: &App, area: Rect) {
450494 | InputMode :: Generate
451495 | InputMode :: Form
452496 | InputMode :: ConfirmDelete
453- | InputMode :: Unlock => None ,
497+ | InputMode :: Unlock
498+ | InputMode :: About => None ,
454499 } ;
455500 if let Some ( input) = editing {
456501 spans. push ( Span :: styled (
@@ -770,4 +815,28 @@ mod tests {
770815 assert ! ( text. contains( & pw) , "generated password missing:\n {text}" ) ;
771816 assert ! ( text. contains( "Length 20" ) , "options line missing:\n {text}" ) ;
772817 }
818+
819+ #[ test]
820+ fn about_overlay_renders_attribution ( ) {
821+ let mut app = App :: browsing ( status ( ) , vec ! [ login_entry( ) ] ) ;
822+ app. open_about ( ) ;
823+ let text = draw ( & app) ;
824+ assert ! ( text. contains( "About" ) , "overlay title missing:\n {text}" ) ;
825+ assert ! (
826+ text. contains( & format!( "v{}" , crate :: PKG_VERSION ) ) ,
827+ "version missing:\n {text}"
828+ ) ;
829+ assert ! (
830+ text. contains( "Mohamed Hammad" ) ,
831+ "maintainer missing:\n {text}"
832+ ) ;
833+ assert ! (
834+ text. contains( "GPL-3.0-or-later" ) ,
835+ "license missing:\n {text}"
836+ ) ;
837+ assert ! (
838+ text. contains( "SpacecraftSoftware.org" ) ,
839+ "URL missing:\n {text}"
840+ ) ;
841+ }
773842}
0 commit comments