Float the editor search when no file is open (#7575)

## What

The super-search work pinned the editor's `WorkbenchBar` visible on
every view except My Files, even with no file open. That left an empty
workbench showing a fully painted bar whose only live control was the
search — download / close / print / save were all disabled, because
those actions only make sense with a file open.

This stops forcing the bar. When nothing is open, only the global search
floats (unpainted, centered), mirroring how the Processor already works.
When a file **is** open, the `WorkbenchBar` renders exactly as before.

Also fixes a smaller Processor issue: its floating search strip was
shorter than the sidebar logo row, so the search sat higher than the
brand. Its height now matches the logo row (51px) so they line up.

## Changes

- **`Workbench.tsx`** — render the `WorkbenchBar` only when a file is
open (or a custom view supplies content); otherwise render the new
floating search. My Files and `hideTopControls` custom views are
unchanged.
- **`WorkbenchFloatingSearch.tsx` / `.css`** (new) — the editor's
`SuperSearch` floated in an unpainted strip, mirroring
`PortalSearchBar`. Its vertical band matches the bar's so opening a file
swaps in the bar without a shift.
- **`PortalSearchBar.css`** — strip height matched to
`.portal-sidebar__logo` (51px) so the Processor search aligns with the
logo.

The notification bell is intentionally out of scope — it ships in a
separate PR.

## Before / after

(ignore the bell icon in the after that’s not live yet)

<img width="2056" height="1077" alt="Screenshot 2026-08-20 at 2 28
17 AM"
src="https://github.com/user-attachments/assets/144f5216-4784-42b2-8c09-afda43577ad0"
/>
<img width="2056" height="1071" alt="Screenshot 2026-08-20 at 2 28
31 AM"
src="https://github.com/user-attachments/assets/63af9303-af8a-48dd-b113-485169fb4924"
/>

- **Editor, no file:** painted bar with disabled buttons → just a
floating search.
- **Editor, file open:** unchanged.
- **Processor:** search now vertically aligned with the logo.

## Testing

- `task frontend:check` — lint (incl. colour linters) + typecheck +
tests (247 files / 2137 tests) all pass.
- Processor alignment verified in Storybook (`Portal/Shell/AppShell`):
logo row, search strip, and search pill share the same vertical center.
- Editor float not verified in-browser (local backend is behind a login
gate); covered by types/tests and reuses the verified Processor pattern.
This commit is contained in:
EthanHealy01
2026-08-20 09:33:26 +00:00
committed by GitHub
parent a744102cb6
commit f7ed1822c7
4 changed files with 94 additions and 36 deletions
@@ -18,6 +18,7 @@ import { useCookieConsent } from "@app/hooks/useCookieConsent";
import styles from "@app/components/layout/Workbench.module.css";
import WorkbenchBar from "@app/components/shared/WorkbenchBar";
import WorkbenchFloatingSearch from "@app/components/shared/WorkbenchFloatingSearch";
import LandingPage from "@app/components/shared/LandingPage";
import DismissAllErrorsButton from "@app/components/shared/DismissAllErrorsButton";
import { ChatFAB } from "@app/components/chat/ChatFAB";
@@ -77,6 +78,22 @@ export default function Workbench() {
const [viewerToolbarCollapsed, setViewerToolbarCollapsed] = useState(false);
const showReopenTab = currentView === "viewer" && viewerToolbarCollapsed;
// The WorkbenchBar carries file-scoped actions, so it only shows once a file
// is open or a custom view supplies content; otherwise the search floats.
const activeCustomView = customWorkbenchViews.find(
(v) => v.workbenchId === currentView,
);
const topControlsAvailable =
currentView !== "myFiles" && !activeCustomView?.hideTopControls;
const hasWorkbenchContent =
hasFiles ||
fileIds.length > 0 ||
!isBaseWorkbench(currentView) ||
// Shared signing drives the viewer from the sidebar with no file in context.
(currentView === "viewer" && !!signingOverlay?.file);
const showWorkbenchBar = topControlsAvailable && hasWorkbenchContent;
const showFloatingSearch = topControlsAvailable && !hasWorkbenchContent;
const handlePreviewClose = () => {
setPreviewFile(null);
const previousMode = sessionStorage.getItem("previousMode");
@@ -231,11 +248,7 @@ export default function Workbench() {
data-tour="workbench"
style={{ backgroundColor: "var(--c-bg)", minWidth: 0 }}
>
{/* Workbench Bar — always visible outside My Files (it hosts the
global search), even with no files loaded. */}
{currentView !== "myFiles" &&
!customWorkbenchViews.find((v) => v.workbenchId === currentView)
?.hideTopControls && (
{showWorkbenchBar && (
<div className={styles.workbenchBarShell}>
<div className={styles.workbenchBarWrapper}>
<div className={styles.workbenchBarInner}>
@@ -248,8 +261,7 @@ export default function Workbench() {
/>
</div>
</div>
{/* Reopen tab: a little handle hanging off the bar's bottom-right
while the viewer tool row is retracted. */}
{/* Reopen tab for the retracted viewer tool row. */}
{showReopenTab && (
<Button
type="button"
@@ -259,13 +271,12 @@ export default function Workbench() {
aria-expanded={false}
aria-label={t("workbenchBar.showToolbar", "Show toolbar")}
title={t("workbenchBar.showToolbar", "Show toolbar")}
leftSection={
<KeyboardArrowDownIcon sx={{ fontSize: "1rem" }} />
}
leftSection={<KeyboardArrowDownIcon sx={{ fontSize: "1rem" }} />}
/>
)}
</div>
)}
{showFloatingSearch && <WorkbenchFloatingSearch />}
{/* Dismiss All Errors Button */}
<DismissAllErrorsButton />
@@ -0,0 +1,31 @@
/* Unpainted floating search for the empty workbench. Height + top margin match
the WorkbenchBar's band so opening a file swaps it in without a shift. */
.workbench-floating-search {
display: flex;
align-items: center;
justify-content: center;
min-height: 38px;
margin-top: var(--nav-gutter);
padding: 0 1rem;
flex-shrink: 0;
}
.workbench-floating-search .super-search {
flex: 0 1 24rem;
width: min(100%, 24rem);
max-width: 24rem;
}
.workbench-floating-search .super-search input {
background-color: transparent;
padding-top: 4px;
padding-bottom: 4px;
font-size: 12.5px;
}
[data-mantine-color-scheme="dark"]
.workbench-floating-search
.super-search
input {
background-color: transparent;
}
@@ -0,0 +1,15 @@
import SuperSearch from "@app/components/shared/superSearch/SuperSearch";
import { useEditorSearchScopes } from "@app/hooks/useSuperSearch";
import "@app/components/shared/WorkbenchFloatingSearch.css";
// The editor's global search, floated while no file is open (mirrors the
// processor's PortalSearchBar). Renders only when the WorkbenchBar doesn't, so
// reusing the default input id is safe.
export default function WorkbenchFloatingSearch() {
const scopes = useEditorSearchScopes();
return (
<div className="workbench-floating-search">
<SuperSearch scopes={scopes} />
</div>
);
}
@@ -1,10 +1,11 @@
/* Slim strip at the top of the main column hosting the shared search bar.
Deliberately unpainted: only the input itself shows, on the page ground. */
/* Unpainted strip at the top of the main column. Height matches the sidebar's
logo row (.portal-sidebar__logo, 51px) so the search lines up with the brand. */
.portal-searchbar {
display: flex;
align-items: center;
justify-content: center;
padding: 0.22rem 1rem;
min-height: 3.1875rem;
padding: 0 1rem;
flex-shrink: 0;
}