From f6849020d10079a15755be9206cf7927764df96b Mon Sep 17 00:00:00 2001 From: vickeykumar Date: Fri, 9 Oct 2026 08:49:52 +0530 Subject: [PATCH] Genie pinned: stop the resize loop that closed the editor's right-click menu While the panel was docked the widget fired a resize event on every frame: setDockWidth runs on every window resize and asked for another one each time. The editor and the terminal refitted constantly, and the right-click menu of the editor, which dismisses itself on a resize, could not stay open. A resize is now asked for only when the dock comes, goes or changes width. Also: a size the floating panel was dragged to no longer applies in the dock, and its corner handle is hidden there; the usage line is asked for again after signing in or out; Maximize is hidden in the phone app bar, which has no room for it (the command palette keeps it). Co-Authored-By: Claude Opus 5.5 --- docs/lld/06-frontend.md | 2 +- src/resources/chat-widget/src/index.ts | 23 ++++++++++++++++++++--- src/resources/chat-widget/src/widget.css | 8 ++++++++ src/resources/css/ui-refresh.css | 3 +++ 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/docs/lld/06-frontend.md b/docs/lld/06-frontend.md index 4ce88e9d..42a06d1b 100644 --- a/docs/lld/06-frontend.md +++ b/docs/lld/06-frontend.md @@ -60,7 +60,7 @@ Since T20 the page script is split into feature files under `src/js/src/page/`, What the parts do, together: - **Layout.** Split panes (split.js), Maximize (`ToggleFunction`, Esc restores; an icon button, the first one in the app bar, before Files), Genie pinned beside the IDE (below), Stacked or Side by side (`ToggleRotateEditor`), Hide/Show editor (`ToggleEditor` + `syncEditorLayoutUI`), and the phone layout (`applyMobileLayout`). -- **Genie pinned beside the IDE** (`chat-widget/src/index.ts` "pinned beside the IDE", `02-layout.js`, `#genie-dock` in `index.html`). A pin button in the panel's header (between Peer chat and ×) docks the panel as a column at the right of the workspace row, after the editor/terminal split, like a side bar. The panel element is moved into `#genie-dock` (class `is-docked`, which drops the floating position, size and shadow); a grip on the dock's left edge resizes it (drag, arrow keys, double click for the default 380px; 300px to 680px and at most 60% of the row; `--genie-dock-w`, remembered in `localStorage` `genie-dock-width`). The pin itself is `localStorage` `genie-pinned`. While pinned, the widget fires `genie-pin` ({pinned}) on `window` and `02-layout.js` stacks the editor over the terminal (`SetEditorDirection`; a hidden editor stays hidden); unpinning puts the layout back unless the user chose one meanwhile. × hides a pinned panel and keeps the pin; Ask Genie and Ctrl+K bring it back docked. The dock is inside `#ide-shell`, so Maximize keeps it. Below 1100px or on a phone the pin button is hidden and the panel floats as before (the choice is kept, and the panel docks again when the window is wide enough). +- **Genie pinned beside the IDE** (`chat-widget/src/index.ts` "pinned beside the IDE", `02-layout.js`, `#genie-dock` in `index.html`). A pin button in the panel's header (between Peer chat and ×) docks the panel as a column at the right of the workspace row, after the editor/terminal split, like a side bar. The panel element is moved into `#genie-dock` (class `is-docked`, which drops the floating position, size and shadow); a grip on the dock's left edge resizes it (drag, arrow keys, double click for the default 380px; 300px to 680px and at most 60% of the row; `--genie-dock-w`, remembered in `localStorage` `genie-dock-width`). The pin itself is `localStorage` `genie-pinned`. While pinned, the widget fires `genie-pin` ({pinned}) on `window` and `02-layout.js` stacks the editor over the terminal (`SetEditorDirection`; a hidden editor stays hidden); unpinning puts the layout back unless the user chose one meanwhile. × hides a pinned panel and keeps the pin; Ask Genie and Ctrl+K bring it back docked. The dock is inside `#ide-shell`, so Maximize keeps it. Below 1100px or on a phone the pin button is hidden and the panel floats as before (the choice is kept, and the panel docks again when the window is wide enough). The widget tells the page to refit (a `resize` event, at most one a frame) only when the dock comes, goes or changes width: `setDockWidth` runs on every window resize, and firing one from there each time is a loop that refits the editor on every frame and closes the editor's right-click menu, which dismisses itself on a resize. A size the floating panel was dragged to does not apply in the dock (`!important`), and its corner handle is hidden there. At 800px and below the Maximize button is hidden (the phone app bar has no room); the command palette still has it. - **Editor.** Ace setup, theme persistence, language mode from `data-editor`, `updateEditorContent`, download and upload. `defineOpenreplAceTheme()` registers the default "OpenREPL Dark" Ace theme (`ace/theme/openrepl_dark`, the mockup's colours); every other Ace theme stays in `#select-theme`. The terminal uses the same 14px size as the editor (set in `js/src/xterm.ts`). - **Language switch.** `actionOnchange` fetches `/demo?q=` and renders the demo animation, usage table and links (LLD 04). - **Run and debug.** `CompileandRun`, `RunandDebug` dispatch `optionrun` and `optiondebug`. `ToggleReconnect` reconnects. diff --git a/src/resources/chat-widget/src/index.ts b/src/resources/chat-widget/src/index.ts index 0a43059f..d8d3176a 100644 --- a/src/resources/chat-widget/src/index.ts +++ b/src/resources/chat-widget/src/index.ts @@ -813,6 +813,7 @@ let pinnedPref = (() => { })(); let pinTold = false; // what the page was last told let resizePending = false; +let dockWidthSet = 0; // the width the dock was last given function dockEl(): HTMLElement | null { return document.getElementById("genie-dock"); @@ -847,6 +848,7 @@ function announcePin() { // Puts the panel where it belongs: in the dock when pinned, else on the page. function placePanel() { const dock = dockEl(); + const was = document.body.classList.contains("genie-docked"); if (dock && wantDock()) { dock.hidden = false; dock.appendChild(containerElement); @@ -858,7 +860,8 @@ function placePanel() { if (dock) dock.hidden = true; document.body.appendChild(containerElement); } - requestResize(); + // the IDE has more or less room only when the dock came or went + if (was !== document.body.classList.contains("genie-docked")) requestResize(); } function refreshPin() { @@ -900,6 +903,13 @@ function setDockWidth(w: number, save: boolean = true): number { // (a row that has no width yet, as at start-up, limits nothing) const max = total > 0 ? Math.max(DOCK_MIN_W, Math.min(680, Math.floor(total * 0.6))) : 680; const width = Math.round(Math.min(max, Math.max(DOCK_MIN_W, w))); + // Only a width that changed is news to the page. This runs on every resize of + // the window (onViewportChange), and telling the page to resize from here each + // time would be a resize that causes the next one, for ever: the editor and the + // terminal would refit on every frame, and the right-click menu of the editor, + // which closes on a resize, could not stay open. + const changed = width !== dockWidthSet; + dockWidthSet = width; document.documentElement.style.setProperty("--genie-dock-w", width + "px"); const grip = document.getElementById("genie-dock-grip"); if (grip) { @@ -914,7 +924,7 @@ function setDockWidth(w: number, save: boolean = true): number { // not remembered } } - requestResize(); + if (changed) requestResize(); return width; } @@ -1234,7 +1244,13 @@ function setMode(agent: boolean) { renderUsage(); // the line counts tasks in Agent mode } +let signedSeen: boolean | null = null; + function refreshMode() { + // signing in or out changes what is left: ask again (the page marks it on the body) + const signed = document.body.classList.contains("is-signed-in"); + if (signedSeen !== null && signed !== signedSeen) fetchUsage(); + signedSeen = signed; const box = document.getElementById("chat-widget__mode"); if (!box) return; const a = agentAvailability(); @@ -1565,10 +1581,11 @@ function close() { document.body.classList.remove("genie-open"); // a pinned panel that is closed leaves its place empty: the IDE has the room back const dock = dockEl(); + const wasDocked = document.body.classList.contains("genie-docked"); if (dock) dock.hidden = true; containerElement.classList.remove("is-docked"); document.body.classList.remove("genie-docked"); - requestResize(); + if (wasDocked) requestResize(); renderActivity(); } diff --git a/src/resources/chat-widget/src/widget.css b/src/resources/chat-widget/src/widget.css index 22742912..14a7310a 100644 --- a/src/resources/chat-widget/src/widget.css +++ b/src/resources/chat-widget/src/widget.css @@ -171,6 +171,14 @@ box-shadow: none; animation: none; } +/* A size the floating panel was dragged to (inline, from its corner handle) + does not apply in the dock, and the handle is not there: the dock's own grip + resizes it. */ +#chat-widget__container.is-docked { + width: 100% !important; + height: 100% !important; +} +#chat-widget__container.is-docked .chat-widget__resizer { display: none; } /* the header has no room for the "Reads editor + terminal" note beside the pin */ #chat-widget__container.is-docked #chat-widget__context { display: none; } diff --git a/src/resources/css/ui-refresh.css b/src/resources/css/ui-refresh.css index d67939fb..9c90b42c 100644 --- a/src/resources/css/ui-refresh.css +++ b/src/resources/css/ui-refresh.css @@ -2274,6 +2274,9 @@ body.files-collapsed .files-panel { display: none; } .app-bar .share-btn, .app-bar .forkbtn { height: 44px; min-width: 44px; } .app-bar .ide-btn--icon { width: 44px; } + /* row 1 has room for files, language and run only; Maximize stays in the + command palette on a phone */ + .app-bar #togglescreen-button { display: none; } .select-wrap--lang { flex: 1; } .select-wrap--lang select { width: 100%; min-width: 0; height: 44px; font-size: 16px; } .run-split { order: 3; margin-left: 0; }