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