From 2b7c9cd3ebedb85b918948976feb1a6fcbd53e7c Mon Sep 17 00:00:00 2001 From: Greg Pomerantz Date: Tue, 18 Aug 2026 08:32:19 -0400 Subject: [PATCH] Collapse editor top bar to one row (back + filename) The cut/copy/paste icons were dead placeholders (no handlers); clipboard actions live in the floating selection menu, so the second icon row is removed. The bar goes from two rows at 52dp to one row at 32dp, giving the editor 20dp more height. - filename_test.go now finds the label by type (back icon precedes it) - menu tracking tests updated for the new editorY (reg.Y = 10+32) - scroll_cursor e2e simulation updated to match - On-device verified: back button works, floating menu places above with room and flips below on the first lines, handles unaffected. --- doc/development_plan.md | 12 +++++++++++ internal/editor/filename_test.go | 22 ++++++++++++++------ internal/editor/selection_menu_track_test.go | 10 ++++----- internal/editor/state.go | 15 ++++++------- internal/test/e2e/scroll_cursor_test.go | 8 +++---- 5 files changed, 43 insertions(+), 24 deletions(-) diff --git a/doc/development_plan.md b/doc/development_plan.md index 063789e..5ca6dd9 100644 --- a/doc/development_plan.md +++ b/doc/development_plan.md @@ -934,3 +934,15 @@ grabbable from the uncovered top strip of their boxes. Regressions: `internal/editor/selection_menu_track_test.go` (tracking + above-placement arithmetic + first-line flip). + +## 16. Top bar: single row (2026-08-18) + +The dead cut/copy/paste icon row was removed from the editor top bar +(clipboard actions live exclusively in the floating selection menu, the +native pattern). The bar is now one 32dp row — back icon + filename — +down from two rows at 52dp, giving the editor 20dp more height. The +menu's "above first" placement (section 15) is unchanged; with the +shorter bar, selections on the first lines flip below because there is +no room above, and the menu may overlap the top bar when clamped high — +both are the native toolbar's behaviour (transient, dismissed by +tapping elsewhere). diff --git a/internal/editor/filename_test.go b/internal/editor/filename_test.go index 16b951c..5bbc67e 100644 --- a/internal/editor/filename_test.go +++ b/internal/editor/filename_test.go @@ -21,9 +21,14 @@ func TestEditorLayout_Filename(t *testing.T) { // Find top bar (assuming it's the first container element) topBar := elements[0].(ui.Container) - // Find filename label (assuming it's the first label in the top bar) - filenameLabel := topBar.Children[0].(ui.Label) - + // Find the filename label in the top bar (the back icon precedes it). + var filenameLabel ui.Label + for _, c := range topBar.Children { + if l, ok := c.(ui.Label); ok { + filenameLabel = l + break + } + } if filenameLabel.Text != expectedFilename { t.Errorf("expected filename %q, got %q", expectedFilename, filenameLabel.Text) } @@ -44,9 +49,14 @@ func TestEditorLayout_DefaultFilename(t *testing.T) { // Find top bar (assuming it's the first container element) topBar := elements[0].(ui.Container) - // Find filename label (assuming it's the first label in the top bar) - filenameLabel := topBar.Children[0].(ui.Label) - + // Find the filename label in the top bar (the back icon precedes it). + var filenameLabel ui.Label + for _, c := range topBar.Children { + if l, ok := c.(ui.Label); ok { + filenameLabel = l + break + } + } expectedDefault := "untitled.txt" if filenameLabel.Text != expectedDefault { t.Errorf("expected default filename %q, got %q", expectedDefault, filenameLabel.Text) diff --git a/internal/editor/selection_menu_track_test.go b/internal/editor/selection_menu_track_test.go index d119c21..dae8ec5 100644 --- a/internal/editor/selection_menu_track_test.go +++ b/internal/editor/selection_menu_track_test.go @@ -70,8 +70,8 @@ func TestSelectionMenu_FollowsTextAcrossScroll(t *testing.T) { } y0 := e.MenuRect.Y // The menu sits ABOVE the anchor's line: lineTop(line 5) = reg.Y + 5*lh - // (reg.Y = 10+52, the anchor's visual line index is 5). - wantY0 := ui.Dp(62) + ui.Dp(5*lh) - menuH - 8 + // (reg.Y = 10+32, the anchor's visual line index is 5). + wantY0 := ui.Dp(42) + ui.Dp(5*lh) - menuH - 8 if !dpeq(y0, wantY0) { t.Fatalf("initial menu Y = %v, want %v (above the anchor line)", y0, wantY0) } @@ -150,9 +150,9 @@ func TestSelectionMenu_FlipsBelowOnFirstLine(t *testing.T) { if !e.MenuVisible { t.Fatal("menu not shown") } - // Line 0 top = reg.Y = 62; above would be 62-52-8 = 2 < 8, so the menu - // flips below: 62 + lh + 8. - wantY := ui.Dp(62) + EffectiveLineHeight() + 8 + // Line 0 top = reg.Y = 42; above would be 42-52-8 = -18 < 8, so the menu + // flips below: 42 + lh + 8. + wantY := ui.Dp(42) + EffectiveLineHeight() + 8 if !dpeq(e.MenuRect.Y, wantY) { t.Fatalf("menu Y = %v, want %v (flipped below the first line)", e.MenuRect.Y, wantY) } diff --git a/internal/editor/state.go b/internal/editor/state.go index 8054fc8..c4a8435 100644 --- a/internal/editor/state.go +++ b/internal/editor/state.go @@ -1601,11 +1601,13 @@ func markDirty() { func EditorLayout(screenWidth, screenHeight ui.Dp, wordWrap bool) []ui.Element { margin := ui.Dp(10) - // --- Top bar: filename on row 1, icons on row 2 --- + // --- Top bar: one row, back icon + filename. Cut/copy/paste live in the + // floating selection menu (the native Android pattern); the dead icon row + // is gone, saving 20dp of editor height. --- statusBarRegion := ui.Region{ X: margin, Y: margin, W: screenWidth - margin*2, - H: ui.Dp(52), + H: ui.Dp(32), } statusBarW := statusBarRegion.W filename := TheState.Editor.Filename @@ -1616,14 +1618,9 @@ func EditorLayout(screenWidth, screenHeight ui.Dp, wordWrap bool) []ui.Element { statusBarRegion, ui.Color{R: 230, G: 230, B: 230, A: 255}, []ui.Element{ - // Row 1: filename - ui.NewLabel(filename, 14, ui.Region{X: 0, Y: ui.Dp(2), W: statusBarW, H: ui.Dp(20)}, ui.AlignStart, "", nil), - // Row 2: back, cut, copy, paste icons - ui.NewIcon("back", ui.Region{X: ui.Dp(0), Y: ui.Dp(28), W: ui.IconSize, H: ui.IconSize}, 0, + ui.NewIcon("back", ui.Region{X: ui.Dp(0), Y: ui.Dp(4), W: ui.IconSize, H: ui.IconSize}, 0, []ui.Interaction{{Gesture: ui.Tap, Handler: GoToBrowser}}), - ui.NewIcon("cut", ui.Region{X: ui.Dp(48), Y: ui.Dp(28), W: ui.IconSize, H: ui.IconSize}, 0, nil), - ui.NewIcon("copy", ui.Region{X: ui.Dp(96), Y: ui.Dp(28), W: ui.IconSize, H: ui.IconSize}, 0, nil), - ui.NewIcon("paste", ui.Region{X: ui.Dp(144), Y: ui.Dp(28), W: ui.IconSize, H: ui.IconSize}, 0, nil), + ui.NewLabel(filename, 14, ui.Region{X: ui.Dp(32), Y: ui.Dp(6), W: statusBarW - ui.Dp(32), H: ui.Dp(20)}, ui.AlignStart, "", nil), }, ) diff --git a/internal/test/e2e/scroll_cursor_test.go b/internal/test/e2e/scroll_cursor_test.go index 7435fc9..75e4471 100644 --- a/internal/test/e2e/scroll_cursor_test.go +++ b/internal/test/e2e/scroll_cursor_test.go @@ -85,13 +85,13 @@ func TestEditorClickToMoveCursorWithScroll(t *testing.T) { Handler: func(data any) { if pt, ok := data.(ui.Point); ok { // Manually simulate the offset correction that EditorLayout does - // editorRegion.Y is margin(10) + statusBarH(52) = 62. - // Let's set pt.Y to 10 + 62 = 72. - localY := float64(pt.Y) - 62.0 + float64(editor.TheState.ScrollOffset) + // editorRegion.Y is margin(10) + statusBarH(32) = 42. + // Let's set pt.Y to 10 + 42 = 52. + localY := float64(pt.Y) - 42.0 + float64(editor.TheState.ScrollOffset) editor.SetCursorFromPoint(float64(pt.X), localY) } }, - Data: ui.Point{X: 10, Y: 72}, + Data: ui.Point{X: 10, Y: 52}, }, })