Skip to content

Allow opening in the side panel as well as the popup (re-target of #134) - #137

Merged
forgetso merged 1 commit into
masterfrom
help-rules-popup-refresh
Sep 21, 2026
Merged

forgetso merged 1 commit into
masterfrom
help-rules-popup-refresh

Conversation

@forgetso

Copy link
Copy Markdown
Owner

Re-opens the side panel work for master. Closes #121.

Important

#134 was merged into the wrong base and never reached master. It targeted help-rules-popup-refresh, but that branch had already been squash-merged into master 47 minutes earlier as #133 (7ec3d0b). Merging #134 therefore added the side panel to a branch nothing was pulling from.

git log --oneline origin/master..origin/help-rules-popup-refresh
67e8bdf Allow opening in the side panel as well as the popup (#134)
9ddd25d Refresh the help and rules pages, ...

This PR is the same branch targeted at master. The diff is the side panel alone — #133's content is already in master via the squash, so it does not reappear here.

The content is unchanged from #134 and was reviewed there. Summary:

One UI, two surfaces

The popup closes the moment anything else takes focus, so clicking into the page to check a replacement dismisses it and the terms have to be typed again. Settings offers Open in: Popup | Side panel, and the header carries a button to switch there and then.

Both surfaces load the same assets/popup.html, popup.js and style.css — no second copy of the UI. background/surface.ts registers the panel with ?surface=sidepanel, and that query string is the only thing popup.ts keys its layout off: the panel fills its width, pins the action buttons to the bottom, and drops the history list's height cap. Measured at 320, 400 and 560px: no overflow, footer pinned.

Making the toolbar button reach it

Clearing default_popup is the part that matters — while a popup is registered Chrome opens it and never fires action.onClicked, so openPanelOnActionClick has nothing to act on. Neither that nor the panel behaviour survives a browser restart, so both are reapplied on onStartup.

side_panel.default_path stays a plain path: a manifest Chrome rejects stops the extension loading at all, and --load-extension installs nothing in this environment (the unmodified manifest fails identically), so that could not be verified. The query is applied through sidePanel.setOptions() instead, which always runs before the panel can be opened.

Header

Six icons and the title could not share 320px, so the header is split by what the icons do — help and about stay on the title line, and history, settings, saved rules and the side panel toggle form a full-bleed tab strip sharing the width equally:

🔍 Search and Replace                    ?  i
─────────────────────────────────────────────
      🕐         ⚙         💾         ▯
                 ▔▔▔▔

The strip costs ~26px, which matters because Chrome scrolls the entire popup past 600px; body padding drops to 12px and the history cap to 330px. Across all ten locales the tallest is Russian at 593.

This exposed a rule that had never worked: #header form set flex-wrap: nowrap, but the markup carried bootstrap's .flex-wrap, whose !important beat it.

Also

  • chrome.sidePanel needs Chrome 114; the preference is hidden where the API is missing rather than setting minimum_chrome_version and cutting older browsers off from updates.
  • The toggle-popup command had no handler, so Ctrl+Shift+S had never done anything. It now opens whichever surface is configured.
  • Default stays popup — the issue asks for a choice, not a replacement.

Checks

npm run checks passes — typecheck, lint, format, 643 tests, build. 16 cover surface.ts, including the Chrome-113 path and the browser refusing the call.

Worth a smoke test on an unpacked build: manifest loads, panel opens from the button and from Ctrl+Shift+S, and setOptions lands before first use.

🤖 Generated with Claude Code

* Allow opening in the side panel as well as the popup (#121)

The popup closes the moment anything else takes focus, so clicking into the
page to check a replacement dismisses it and the terms have to be typed again.
Settings now offers "Open in: Popup | Side panel".

Both surfaces load the same assets/popup.html, popup.js and style.css — there
is no second copy of the UI. background/surface.ts registers the side panel
with ?surface=sidepanel, and that query string is the only thing popup.ts keys
its layout off: the panel fills the width it is given, pins the action buttons
to the bottom of the window, and drops the history list's height cap, which
only exists to keep the popup inside Chrome's 600px.

Clearing default_popup is the part that makes the toolbar button work: while a
popup is registered Chrome opens it and never fires action.onClicked, so
openPanelOnActionClick has nothing to act on. Neither that nor the panel
behaviour survives a browser restart, so both are reapplied on startup as well
as on install and whenever the preference changes.

side_panel.default_path in the manifest stays a plain path. A manifest Chrome
rejects stops the extension loading at all, and --load-extension does not work
in this environment to prove a query string there is accepted — the unmodified
manifest fails to install identically — so the query is applied through
sidePanel.setOptions instead, which runs before the panel can ever be opened.

The panel outlives a tab, unlike the popup, so it recounts when the user
switches tab or navigates; a count left over from a tab they have since moved
away from is worse than no count.

Also:
  - chrome.sidePanel needs Chrome 114. Rather than setting
    minimum_chrome_version and cutting older browsers off from updates, the
    preference is hidden where the API is missing.
  - The toggle-popup command had no handler, so Ctrl+Shift+S has never done
    anything. It now opens whichever surface is configured.
  - Switching to the side panel from inside the popup opens the panel there and
    then, rather than leaving the user to work out they must click the toolbar
    button again.

Documented in the help page, and translated into all ten locales.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Add a side panel open/close button to the header

The preference under the cog is for choosing a default; this is for switching
surface there and then. In the popup the icon opens the side panel and closes
the popup behind it; in the panel it closes the panel. There is no
sidePanel.close(), so the panel closes its own window, which is also how the
popup dismisses itself once the panel has taken over.

The window id is looked up on load rather than in the click handler:
sidePanel.open has to be called while the user gesture is still in effect, and
awaiting windows.getCurrent() inside the handler risks spending it.

The sixth icon exposed a rule that has never worked. #header form set
flex-wrap: nowrap, but the markup carried bootstrap's .flex-wrap, whose
!important beat it — with five icons they happened to fit, so nobody noticed.
The class is gone, the form no longer shrinks, and the gap is 4px, so all six
icons and the full title fit across 320px.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Move the header icons onto their own row

Six icons and the title were fighting over 320px: the icons ended up crowded
together and the title was trimmed to "Search and Repl...". The icons now sit
on a row of their own beneath the title, with the full width to spread across.

That row costs about 27px, which took every section back over the 600px at
which Chrome scrolls the whole popup, so the header padding drops to py-2, the
gap above the icons is 6px, and the history list cap comes down to 340px.
Measured across all ten locales: the tallest is now Russian at exactly 600.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Group the header icons instead of putting them on their own row

A row of six evenly spaced glyphs under the title read as undifferentiated.
They are back on the title row, in two groups with a divider between them: the
two that only tell you something (help, about) and the four that do something
(history, settings, saved rules, side panel).

Fitting all six and the untruncated title across 320px needed the width back:
the icons are 16px rather than 18px, the gap within a group is 3px, and the
title span loses a redundant .mx-1 that doubled up with the flex gap. Title
renders in full at 320px.

Back on one row, every section has room again: the tallest is Russian at 575
against Chrome's 600px cap, so the history list cap returns to 380px.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Split the header into a title line and a tab strip

Help and about only ever open documentation; history, settings, saved rules and
the side panel toggle change what you are looking at. The first pair now sits
on the title line, and the other four form a full-bleed tab strip beneath it,
sharing the width equally. The underline marking the open panel spans the whole
tab rather than just the glyph.

The strip costs about 26px, so the body panel's vertical padding drops to 12px
and the history list cap to 330px. Measured across all ten locales: the tallest
is Russian at 593 against Chrome's 600px cap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@forgetso
forgetso force-pushed the help-rules-popup-refresh branch from 67e8bdf to 97026bc Compare September 21, 2026 22:21
@forgetso
forgetso merged commit db779c6 into master Sep 21, 2026
5 checks passed
@forgetso forgetso mentioned this pull request Sep 21, 2026
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.

Allow opening in sidebar or popup

1 participant