Skip to content

Control bar#2820

Merged
abose merged 19 commits intomainfrom
ai
Apr 19, 2026
Merged

Control bar#2820
abose merged 19 commits intomainfrom
ai

Conversation

@abose
Copy link
Copy Markdown
Member

@abose abose commented Apr 18, 2026

No description provided.

abose added 19 commits April 18, 2026 14:47
A new 30px control bar sits between the sidebar and the editor area
with editor collapse, edit/save actions, file navigation, sidebar
toggle, and a vertical filename label. The sidebar-toggle button has
been moved from the titlebar into the control bar permanently. The
collapse toggle expands live preview to fill the editor area (opening
it first if not already open), and honors the user's last live
preview width on restore. Dragging the plugin-toolbar resizer while
collapsed exits the collapsed state so normal resize resumes.

The NAVIGATE_SHOW_IN_FILE_TREE command now also expands the sidebar
when it is hidden so reveal-in-tree works regardless of entry point
(control-bar click, context menu, keyboard shortcut).
The control bar collapse toggle now presents as a feather icon with
title "Switch to Visual Edit" while the editor is visible, and flips
to the code icon with title "Switch to Code Editor" while the editor
is collapsed into design/preview mode.
…mode

While the editor is collapsed, window resize events triggered two
visible glitches: (1) the sidebar shrunk by ~1px per resize in a slow
drift, because Resizer.updateResizeLimits recomputes sideBarMaxSize
from sibling widths (which equals ~sidebarWidth once #main-toolbar is
a full-width sibling) and clamps only when data-maxsize is a
percentage; (2) #main-toolbar flickered as WorkspaceManager's
_clampPluginPanelWidth capped it below our desired width before our
handler reset it each frame.

Fix both by pinning data-maxsize to "1000%" during collapse so the
sidebar clamp is short-circuited, and by setting main-toolbar
geometry with !important so the workspace clamp can't override it.
Also add an applyingCollapsedLayout reentry guard on the
EVENT_WORKSPACE_UPDATE_LAYOUT listener so our own recomputeLayout
calls don't re-enter.
…design mode

The sidebar's right-resizer handle now visually sits to the right of
#centralControlBar (instead of between the sidebar and the control
bar), using a CSS transform so Resizer.repositionResizer can keep its
own internal left offset. The same shift applies when the sidebar is
hidden and the handle is reparented to .main-view.

In design mode (editor collapsed), the main-toolbar's own left
resizer is hidden — previously dragging it exited design mode. Now
the single sidebar resizer acts as the sidebar↔live-preview splitter
so the split can be adjusted without leaving the mode.

Bumped sidebar data-minsize from 0 to 30 so drag can't auto-collapse
the sidebar below the control-bar width; the dedicated sidebar-toggle
button still fully hides it.
…ss toggles

Introduce a CSS `max-width: 70vw` cap on the sidebar while in design
mode so a drag can't push the sidebar past the cap — the cap is
enforced during the drag itself instead of snapping back after the
fact, and there are no width writes inside the Resizer's own frame
that would trip "ResizeObserver loop completed with undelivered
notifications" warnings.

When exiting design mode the max-width constraint is removed; the
sidebar's style.width may still hold the larger value the Resizer
wrote during the capped drag, so the sidebar would snap back to
that stale width. Before flipping the body class, pin style.width
to the currently rendered width (reading via offsetWidth to force
a synchronous reflow) and resync the Resizer handle so the sidebar
keeps the size the user sees through collapse/expand cycles.

Also switch _syncLeftPositions / _applyCollapsedLayout /
_restoreExpandedLayout to read sidebar.offsetWidth directly rather
than jQuery's outerWidth — the latter returned the uncapped style
value in some frames mid-drag which left the control bar and
main-toolbar positioned at stale offsets.
Clicking toolbar-go-live while in design mode hid the live preview
panel but left the editor collapsed — producing a dead zone where
the panel used to be. Now listen for EVENT_WORKSPACE_PANEL_HIDDEN
on the live-preview panel and exit design mode so the editor comes
back in its place.

_restoreExpandedLayout accepts a skipToolbarRestore flag for this
path — WorkspaceManager._hidePluginSidePanel has already sized
#main-toolbar back to the icon-bar width when it fired the hide
event, so our usual pre-collapse live-preview width restore would
re-open the same dead zone we're trying to avoid.
… mode

Capping the sidebar at 70vw worked on typical displays but became
too generous on very large screens — the live-preview panel could
shrink to a useless sliver. Change the design-mode sidebar cap to
`calc(100vw - 230px)` so it always reserves at least 200px for the
live preview plus the 30px control bar, regardless of viewport size.
In design mode the sidebar's right-resizer doubles as the
sidebar↔live-preview splitter (the main-toolbar's own left-resizer
is hidden). Listeners that watch #main-toolbar for
panelResizeStart/Update/End — most notably the live-preview resize
ruler / media-query ruler in lpedit-helper — weren't firing when
the sidebar was dragged, so the ruler stayed hidden.

Forward those three events from #sidebar to #main-toolbar while
collapsed so the ruler (and any similar listener) reacts to the
sidebar drag just like a main-toolbar drag in normal mode.
In design mode #main-toolbar's geometry is pinned with !important,
so callers of WorkspaceManager.setPluginPanelWidth(w) had no way to
programmatically size the live-preview panel. Wrap that API at CCB
init time: while collapsed, translate the requested live-preview
content width into a sidebar width (window - toolbar - CCB) and
drive the normal sidebar sync pipeline. In normal mode the call
falls through to the original implementation unchanged.
Register VIEW_TOGGLE_DESIGN_MODE (view.toggleDesignMode) at
CentralControlBar module load so extensions and keybindings can
trigger the design-mode toggle without reaching into the module
directly. The command's checked state mirrors the body class so
the menu shows a tick when design mode is active.

Add a File menu item directly below Live Preview, wired from
Phoenix-live-preview (where the live-preview menu group is built)
so the anchor exists at insert time. Default shortcut Ctrl-F11 is
wired via src/base-config/keyboard.json.
Expose WorkspaceManager.isInDesignMode() / setDesignMode() and a
new EVENT_WORKSPACE_DESIGN_MODE_CHANGE so other modules can react
to the full-live-preview layout without reaching into the control
bar. CentralControlBar mirrors its editorCollapsed flag into the
workspace manager on every toggle.

NoDistractions now checks isInDesignMode(): in design mode
entering/exiting only toggles the sidebar (the main toolbar and
panels are part of the editing surface and hiding them would
blank the live preview area). In the normal editor layout the
previous behavior — hide sidebar + main toolbar + panels — still
applies.
The central control bar now sits between #sidebar and .content, so the
"should update the .content left offset" SidebarTabs spec — which used
to assert sidebar.width — must add #centralControlBar.outerWidth() to
match the new layout. Computed from the live CCB width so the test
stays correct regardless of control-bar width changes.

Add test/control-bar-tests-todo.md with the full mainview: suite we
plan to write for the control bar + design-mode feature set. Serves
as a running checklist; we'll land the actual tests in follow-ups.
… open, or git panel

Each of these surfaces mounts UI in a region that design mode has
collapsed away (the editor chrome or the bottom panel area), so
clicking into them while in design mode produced a broken layout.
Call the VIEW_TOGGLE_DESIGN_MODE command to exit first; the normal
command flow then renders correctly.

Each site includes a TODO noting the long-term desire: float the
panel / picker on top of the live preview so users can use these
features without leaving design mode.
Adds a new section 11b to control-bar-tests-todo.md tracking the
auto-exit behavior wired into the tools panel (app-drawer-button),
Find in Files, Quick Open, and the git toolbar icon — plus future
follow-ups for each (floating variants that would avoid the need
to exit design mode).
Quick Open's standard ModalBar mounts above #editor-holder, which is
collapsed in design mode — invoking the picker there would show no
UI. Add a ModalBar-compatible floating variant: when
WorkspaceManager.isInDesignMode() is true, showDialog creates a
centered overlay with a compact search bar instead. The picker stays
non-modal: no backdrop dim or blur so the live preview stays fully
visible and file switching never feels jarring.

- _createFloatingQuickOpenBar implements the bits of ModalBar that
  QuickOpen uses (on("close", ...), close(), prepareClose, getRoot)
  and returns a resolved jQuery Deferred from close() so callers
  chain .done() safely.
- Dropdown (appended to <body> by QuickSearchField) is anchored to
  the floating bar via the $positionEl option and bumped above the
  overlay's stacking so its rendering isn't dimmed.
- verticalAdjust is computed as (bar.bottom - input.bottom) so the
  dropdown sits flush with the bar instead of floating detached.
- Escape handling runs in capture phase on both the overlay and the
  document so one keypress dismisses both dropdown and picker at
  once instead of requiring two.
- setSearchFieldValue guards against a torn-down searchField so
  re-entering the command after a stale close doesn't throw.
- QuickOpen's own "exit design mode first" branch is removed — the
  floating picker replaces that workaround entirely.
…iframe-click dismiss

Move the file-type classification helpers (getExtension, isPDF,
isSVG, isImage, isHTMLFile, isMarkdownFile, isPreviewableFile,
isServerRenderedFile) from Phoenix-live-preview/utils.js into a
shared src/utils/FileTypeUtils.js module so non-live-preview code
can depend on them. The live-preview utils now re-exports those
helpers and keeps only its iframe-specific pieces
(LIVE_PREVIEW_IFRAME_ID, MDVIEWR_IFRAME_ID,
focusActiveEditorIfFocusInLivePreview).

Quick Open's floating (design-mode) picker now restricts results
to files that actually make sense to open into the live preview —
HTML, SVG, Markdown, PDF — plus server-rendered sources (PHP,
ASP, JSP, Python, Ruby/ERB, ColdFusion). Everything else is hidden
because opening it would just collapse design mode.

Also dismiss the floating picker when focus moves into the live
preview iframe: those clicks never surface as mousedown / focusin
on the parent document, so they used to leave the picker stranded.
Listen for window blur and close once focus settles outside the
bar / results dropdown.
When the md viewer iframe forwards an unhandled shortcut to Phoenix,
it re-focuses its own viewer-content 100ms later so commands like
Save return focus to the editor. That timer also fired for shortcuts
that open parent UIs (Quick Open, Find in Files) — pulling focus out
of the picker and making the dropdown vanish on the first keystroke.

Send the set of "skip refocus" key strings from the parent to the
iframe at runtime. MarkdownSync reads the bindings for the relevant
commands from KeyBindingManager and posts MDVIEWR_SKIP_REFOCUS_KEYS
on iframe ready and whenever a binding changes. The iframe stores
the set and builds the same canonical key string KBM uses from each
keydown to decide whether to skip the refocus. Adding more shortcuts
is now just appending a command id to SKIP_REFOCUS_COMMANDS in
MarkdownSync — no hardcoded keys in the iframe.

Also remove the QuickOpen design-mode exit branch added earlier:
the floating picker variant replaces that workaround entirely.
Rename the collapse-editor toggle's expanded-state title from
"Switch to Visual Edit" to "Switch to Design Mode" so it matches
the rest of the feature's naming (command id, menu item, body
class, WorkspaceManager API all already use "design mode").

Also localize the control bar's hardcoded English titles:
- Switch to Design Mode / Switch to Code Editor get new string
  keys (CCB_SWITCH_TO_DESIGN_MODE, CCB_SWITCH_TO_CODE_EDITOR).
- Sidebar toggle, Undo, Redo, Save reuse existing
  CMD_TOGGLE_SIDEBAR / CMD_UNDO / CMD_REDO / CMD_FILE_SAVE.
- Search / Back / Forward were already localized via
  NavigationProvider.updateTooltips.

HTML keeps the English titles as fallback; the JS init overwrites
them with the localized strings so the user's locale shows on
first render.
The old "should remember scroll positions" spec called
CodeInspection.scrollToProblem(40) and asserted the resulting
scrollTop was non-zero. Both sides of that flaked across
environments:
- scrollToProblem uses Element.scrollIntoView, which picks a
  scrollable ancestor that isn't always the .table-container we
  asserted on.
- The problems panel's height depends on the window size / user
  prefs; on wide windows the table fits all rows and no scroll is
  possible, so scrollTop stays 0 regardless of how we invoke it.

Replace the assertion with the round-trip invariant: set
scrollTop(200) on the table, switch away, switch back, and wait
for scrollTop to land back at 200. The remember-scroll feature's
entire contract is this round-trip; verifying it makes the test
robust to panel height, platform, and headless runners.

Also add an awaitsFor gate for row 40 before the scroll is seeded
so the test doesn't race against async lint population.
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
D Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@abose abose merged commit 7eff822 into main Apr 19, 2026
12 of 21 checks passed
@abose abose deleted the ai branch April 19, 2026 05:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant