Keep TUI sidebar selections visible.
This commit is contained in:
@@ -21,7 +21,7 @@ The project is under active development. Core blogging workflows are broadly ava
|
||||
- Project-scoped typed domain events synchronize desktop views and cached runtime settings with shared-engine and CLI mutations even when Preferences is closed; persisted CLI notifications are consumed once, and the selected UI language is shared through settings.
|
||||
- Headless `bds-cli` automation for rebuild/repair/render, with live terminal progress through metadata comparison, site validation, and page rendering; publishing and Git sync; post/media/gallery creation; effective shared settings with secret-presence redaction; projects; utility Lua tasks; JSON I/O; airplane-mode AI routing; and package-linked CLI installation from Settings → Data or `bds-cli install`.
|
||||
- Local MCP automation over stdio or a localhost-only stateless HTTP endpoint, with project resources, read/search/count tools, uniquely identified inert write proposals, clean duplicate-pending rejection, explicit desktop approval, and opt-in Claude Code/Copilot configuration.
|
||||
- A fully localized Ratatui terminal workspace, available locally through `bds-cli tui`/`BDS_MODE=tui` and remotely through authenticated SSH shell sessions, with shared post/template/script editing and publishing, project/search/command overlays, settings, tags, Git, reports, task progress, live multi-client locale updates, and airplane-mode AI gating.
|
||||
- A fully localized Ratatui terminal workspace, available locally through `bds-cli tui`/`BDS_MODE=tui` and remotely through authenticated SSH shell sessions, with selection-following scrollable sidebars, shared post/template/script editing and publishing, project/search/command overlays, settings, tags, Git, reports, task progress, live multi-client locale updates, and airplane-mode AI gating.
|
||||
- `bds-cli server` hosting the shared application engines over a loopback-by-default, public-key-only SSH service, with restrictive private key material, live authorization updates, terminal-session transport, CLI-change synchronization, ordered domain/task events, and native desktop remote-project selection.
|
||||
- bDS2-compatible Markdown/Liquid rendering with built-in macros rendered from bundled Liquid templates in isolated scopes (customizable with `macros/*` partial-template slugs), project-description homepage headings, category-controlled list title visibility, descriptive category archive titles, canonical multilingual and flat page routes for every configured blog language, recursive menus, calendar archives, feeds, a root hreflang sitemap, Pagefind, shared cached multicore full-site rendering, change-aware and forced full renders from the native Blog menu/CLI/TUI, and fast route/mtime-based incremental validation whose targeted repair refreshes affected aggregate pages through cancellable section task groups with bDS2-style per-URL progress.
|
||||
- Navigable generated-route preview in the app or system browser, with draft database overlays and published filesystem content.
|
||||
|
||||
@@ -25,7 +25,9 @@ use ratatui::layout::Size;
|
||||
use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
use ratatui::text::{Line, Span, Text};
|
||||
use ratatui::widgets::{Block, Borders, Clear, Paragraph, Widget, Wrap};
|
||||
use ratatui::widgets::{
|
||||
Block, Borders, Clear, List, ListItem, ListState, Paragraph, StatefulWidget, Widget, Wrap,
|
||||
};
|
||||
use ratatui_image::picker::Picker;
|
||||
use ratatui_image::protocol::Protocol;
|
||||
use ratatui_image::{Image as TerminalImage, Resize};
|
||||
@@ -3302,14 +3304,8 @@ impl TuiApp {
|
||||
let rows = self
|
||||
.items
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, item)| {
|
||||
let style = if index == self.selected_index && self.focus == Focus::Sidebar {
|
||||
Style::default()
|
||||
.bg(Color::Rgb(54, 61, 78))
|
||||
.fg(Color::White)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else if matches!(item, SidebarItem::Header(_)) {
|
||||
.map(|item| {
|
||||
let style = if matches!(item, SidebarItem::Header(_)) {
|
||||
Style::default()
|
||||
.fg(Color::Rgb(126, 137, 160))
|
||||
.add_modifier(Modifier::BOLD)
|
||||
@@ -3321,10 +3317,27 @@ impl TuiApp {
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
Line::styled(format!("{prefix}{}", item.label(self.locale)), style)
|
||||
ListItem::new(Line::styled(
|
||||
format!("{prefix}{}", item.label(self.locale)),
|
||||
style,
|
||||
))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Paragraph::new(rows).render(inner, buffer);
|
||||
let highlight = if self.focus == Focus::Sidebar {
|
||||
Style::default()
|
||||
.bg(Color::Rgb(54, 61, 78))
|
||||
.fg(Color::White)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default()
|
||||
};
|
||||
let mut state = ListState::default().with_selected(Some(self.selected_index));
|
||||
StatefulWidget::render(
|
||||
List::new(rows).highlight_style(highlight),
|
||||
inner,
|
||||
buffer,
|
||||
&mut state,
|
||||
);
|
||||
}
|
||||
|
||||
fn render_main(&self, area: Rect, buffer: &mut Buffer) {
|
||||
@@ -4198,6 +4211,31 @@ mod tests {
|
||||
assert_eq!(app.view, TuiView::Settings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sidebar_scroll_follows_j_and_k_selection() {
|
||||
let fixture = Fixture::new();
|
||||
let mut app = fixture.app(false);
|
||||
app.items = (0..20)
|
||||
.map(|index| SidebarItem::Post(index.to_string(), format!("Post {index:02}")))
|
||||
.collect();
|
||||
app.selected_index = 0;
|
||||
|
||||
for _ in 0..12 {
|
||||
app.handle_input(TuiInput::plain(TuiKey::Char('j')))
|
||||
.unwrap();
|
||||
}
|
||||
let scrolled_down = rendered_text(&app, 50, 10);
|
||||
assert!(scrolled_down.contains("Post 12"));
|
||||
assert!(!scrolled_down.contains("Post 00"));
|
||||
|
||||
for _ in 0..12 {
|
||||
app.handle_input(TuiInput::plain(TuiKey::Char('k')))
|
||||
.unwrap();
|
||||
}
|
||||
let scrolled_up = rendered_text(&app, 50, 10);
|
||||
assert!(scrolled_up.contains("Post 00"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn posts_templates_and_scripts_edit_validate_save_and_publish_through_shared_engines() {
|
||||
let fixture = Fixture::new();
|
||||
|
||||
@@ -53,6 +53,9 @@ surface TuiSurface {
|
||||
-- viewport width, keeps the cursor's visual row visible while
|
||||
-- scrolling, and highlights the cursor's source line. There is
|
||||
-- no line-number gutter.
|
||||
|
||||
@guarantee SidebarSelectionVisibility
|
||||
-- The sidebar viewport follows the selected entry.
|
||||
}
|
||||
|
||||
-- ─── Sidebar navigation ─────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user