From c21bfca22008cb6891450284a352e30e0a1f07c4 Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Tue, 18 Aug 2026 18:42:33 +0200 Subject: [PATCH] fix(editor): the workspace panel stacks its actions, and the toolbar stops growing a hole MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two places where the editor bar spent width on nothing. The file explorer put its label and its buttons on one line: WORKSPACE next to new/open/save, and every board section's name next to rename / new file / new folder plus the status dot. Between them those set a floor under the panel's width, and the board name — the one thing a user reads there — got whatever was left ("M5 Cardput..." in a 150px pane). Both headers now stack: the label owns its row, the actions sit above (board) or below (workspace), and the status dot rides in the actions row, where it costs the name nothing. The actions row keeps its height reserved whether or not the buttons are showing, so hovering a section never shoves the files below it down. Default pane width 150 -> 124px; the freed px go to the code editor. The toolbar's center slot is a flexible spacer that pins the right icons to the far right, and nothing renders into it (no caller passes centerSlot). So whenever the bar had width to spare — closing the docked AI chat is exactly that — the spacer ate it: 191px of black between Reset and Libraries on a 1600px window. Empty, it no longer grows, so the editor controls stay packed and the surplus collects at the one boundary that already carries a divider, in front of the canvas controls (which belong over the canvas pane). --- .../src/components/editor/EditorToolbar.css | 9 + .../src/components/editor/FileExplorer.css | 63 +++-- .../src/components/editor/FileExplorer.tsx | 266 ++++++++++-------- frontend/src/pages/EditorPage.tsx | 10 +- 4 files changed, 205 insertions(+), 143 deletions(-) diff --git a/frontend/src/components/editor/EditorToolbar.css b/frontend/src/components/editor/EditorToolbar.css index 06342577..f9cdbe7f 100644 --- a/frontend/src/components/editor/EditorToolbar.css +++ b/frontend/src/components/editor/EditorToolbar.css @@ -47,6 +47,15 @@ overflow: hidden; } +/* Empty (nothing renders here today), it must NOT grow: a spacer that eats + the surplus width turns it into a black hole between the Run group and + Libraries whenever the bar gets roomier — closing the AI chat is exactly + that. Packed, the surplus collects at ONE place instead: the boundary in + front of the canvas controls, which already carries a divider. */ +.toolbar-center-slot:empty { + flex: 0 0 0; +} + /* ── Divider ─────────────────────────────────────── */ .tb-divider { width: 1px; diff --git a/frontend/src/components/editor/FileExplorer.css b/frontend/src/components/editor/FileExplorer.css index e2507fe2..7815d169 100644 --- a/frontend/src/components/editor/FileExplorer.css +++ b/frontend/src/components/editor/FileExplorer.css @@ -9,13 +9,18 @@ font-family: 'Segoe UI', sans-serif; } +/* Two rows, not one: the title on top, the three actions under it. Side by + side they set the pane's minimum width between them (a truncated "WORK..." + next to buttons that must stay reachable); stacked, the panel is free to be + narrow and the code editor takes the px that frees up. Vertical space is + what this column has to spare. */ .file-explorer-header { display: flex; - align-items: center; - justify-content: space-between; - gap: 4px; - padding: 0 4px 0 6px; - height: 46px; + flex-direction: column; + align-items: stretch; + justify-content: center; + gap: 3px; + padding: 6px 6px 6px 8px; color: #9d9d9d; font-size: 10px; font-weight: 600; @@ -24,10 +29,7 @@ flex-shrink: 0; } -/* The title gives way before the actions do: at narrow pane widths the - three buttons must stay reachable, the word can truncate. */ .file-explorer-title { - flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; @@ -38,7 +40,7 @@ display: flex; align-items: center; gap: 2px; - flex-shrink: 0; + margin-left: -2px; /* optical: line the first glyph up with the title */ } .file-explorer-new-btn { @@ -230,11 +232,15 @@ margin-bottom: 2px; } +/* A board section header is two rows: its actions, then its name. The name + row then spans the full pane width, so "M5 Cardputer ADV" reads as a name + instead of an ellipsis squeezed between three buttons and a status dot. */ .fe-board-header { display: flex; - align-items: center; - gap: 4px; - padding: 3px 4px 3px 2px; + flex-direction: column; + align-items: stretch; + gap: 1px; + padding: 2px 4px 3px 2px; cursor: pointer; color: #aaa; border-radius: 3px; @@ -243,6 +249,29 @@ position: relative; } +.fe-board-name-row { + display: flex; + align-items: center; + gap: 4px; + min-width: 0; +} + +/* The actions keep their row reserved whether or not they are showing: a row + that appears on hover would shove every file below it down by its height + under the pointer. */ +.fe-board-actions-row { + display: flex; + align-items: center; + gap: 1px; + min-height: 16px; + padding-left: 2px; +} + +/* Pushes the buttons to the right end and leaves the status dot at the left. */ +.fe-board-actions-spacer { + flex: 1 1 auto; +} + .fe-board-header:hover { background: rgba(255, 255, 255, 0.06); color: #ddd; @@ -306,13 +335,13 @@ border: none; color: #9d9d9d; cursor: pointer; - padding: 1px; + padding: 0; border-radius: 3px; display: flex; align-items: center; justify-content: center; - width: 18px; - height: 18px; + width: 16px; + height: 16px; flex-shrink: 0; opacity: 0; transition: opacity 0.1s; @@ -324,8 +353,8 @@ } .fe-board-new-btn svg { - width: 16px; - height: 16px; + width: 14px; + height: 14px; } .fe-board-new-btn:hover { diff --git a/frontend/src/components/editor/FileExplorer.tsx b/frontend/src/components/editor/FileExplorer.tsx index 1191bfa6..c417b8cb 100644 --- a/frontend/src/components/editor/FileExplorer.tsx +++ b/frontend/src/components/editor/FileExplorer.tsx @@ -773,6 +773,11 @@ export const FileExplorer: React.FC = ({ onSaveClick, onNewCl return (
+ {/* Stacked, not side by side: the title owns the first line and the + actions the second. Side by side, the three buttons and the word + set the pane's minimum width between them — and every px the + explorer does not need goes to the code editor. Vertical room is + what this panel has to spare. */}
{t('editor.fileExplorer.workspace')}
@@ -834,90 +839,101 @@ export const FileExplorer: React.FC = ({ onSaveClick, onNewCl }} title={`${boardDisplayName(board)} — ${t('editor.fileExplorer.clickToEdit')}`} > - - - - {BOARD_ICON[board.boardKind] ?? PRO_FALLBACK_ICON} - - - {renamingSection?.id === board.id && renamingSection.kind === 'board' ? ( - setSectionRenameValue(e.target.value)} - onBlur={commitSectionRename} - onKeyDown={(e) => { - if (e.key === 'Enter') commitSectionRename(); - if (e.key === 'Escape') cancelSectionRename(); - }} - onClick={(e) => e.stopPropagation()} - /> - ) : ( + {/* Actions ride ABOVE the name, not beside it: sharing the + line, three buttons plus the status dot left a board name + like "M5 Cardputer ADV" a stub of the row and set a floor + under the pane's width. */} +
+ {/* The run/compile dot rides here too: on the name row it + cost the name another 11px, and it is not hover-gated, + so this row is never empty. */} { + className="fe-status-dot" + style={{ background: statusColor }} + title={ + board.running + ? t('editor.fileExplorer.status.running') + : board.compiledProgram + ? t('editor.fileExplorer.status.compiled') + : t('editor.fileExplorer.status.idle') + } + /> + + + + +
+ +
+ + + + {BOARD_ICON[board.boardKind] ?? PRO_FALLBACK_ICON} - )} - + {renamingSection?.id === board.id && renamingSection.kind === 'board' ? ( + setSectionRenameValue(e.target.value)} + onBlur={commitSectionRename} + onKeyDown={(e) => { + if (e.key === 'Enter') commitSectionRename(); + if (e.key === 'Escape') cancelSectionRename(); + }} + onClick={(e) => e.stopPropagation()} + /> + ) : ( + { + e.stopPropagation(); + startBoardRename(board); + }} + title="Double-click to rename" + > + {boardDisplayName(board)} + + )} - {/* Rename + new-file buttons — visible on hover */} - - - +
{/* Files under this board, as a folder tree */} @@ -1159,59 +1175,65 @@ export const FileExplorer: React.FC = ({ onSaveClick, onNewCl }} title={`${chipName} — ${t('editor.fileExplorer.clickToEdit')}`} > - + {/* Same two-row shape as a board section: the rename button + sits above, the chip's name gets the full row. */} +
+ {!(renamingSection?.id === chip.id && renamingSection.kind === 'chip') && ( + + )} +
- - - - - {renamingSection?.id === chip.id && renamingSection.kind === 'chip' ? ( - setSectionRenameValue(e.target.value)} - onBlur={commitSectionRename} - onKeyDown={(e) => { - if (e.key === 'Enter') commitSectionRename(); - if (e.key === 'Escape') cancelSectionRename(); - }} - onClick={(e) => e.stopPropagation()} - /> - ) : ( - { - e.stopPropagation(); - startChipRename(chip.id, chipName); - }} - title="Double-click to rename" - > - {chipName} - - )} - - {!(renamingSection?.id === chip.id && renamingSection.kind === 'chip') && ( +
- )} + + + + + + {renamingSection?.id === chip.id && renamingSection.kind === 'chip' ? ( + setSectionRenameValue(e.target.value)} + onBlur={commitSectionRename} + onKeyDown={(e) => { + if (e.key === 'Enter') commitSectionRename(); + if (e.key === 'Escape') cancelSectionRename(); + }} + onClick={(e) => e.stopPropagation()} + /> + ) : ( + { + e.stopPropagation(); + startChipRename(chip.id, chipName); + }} + title="Double-click to rename" + > + {chipName} + + )} +
{isOpen && ( diff --git a/frontend/src/pages/EditorPage.tsx b/frontend/src/pages/EditorPage.tsx index 76e9f277..bb097c16 100644 --- a/frontend/src/pages/EditorPage.tsx +++ b/frontend/src/pages/EditorPage.tsx @@ -45,10 +45,12 @@ const BOTTOM_PANEL_DEFAULT = 200; const EXPLORER_MIN = 100; const EXPLORER_MAX = 500; -// Narrow by default: the explorer rows are compact (see FileExplorer.css), -// so 150px fits a board header with its actions and typical file names, -// and every px saved here goes to the code editor. -const EXPLORER_DEFAULT = 150; +// Narrow by default: the explorer rows are compact and both headers stack +// their actions ABOVE their label instead of beside it (see +// FileExplorer.css), so nothing has to share a line — 124px fits a board +// name and typical file names, and every px saved here goes to the code +// editor. +const EXPLORER_DEFAULT = 124; // Once per full page load: the pristine-visit starter dialog must not pop // again when the user closes it and later navigates away from and back to