From e356b352770c6776b6f5e6825fee51e84e0a14f1 Mon Sep 17 00:00:00 2001 From: Mickael Farina Date: Tue, 15 Sep 2026 12:05:11 +0200 Subject: [PATCH 1/2] fix(dashboard): long chat replies scroll fully on desktop + native app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1612bf0 made the composer position:fixed at the viewport bottom on EVERY size to solve a mobile gesture-bar problem, and had .content reserve room for it with padding-bottom: calc(var(--input-h) + 16px). On the desktop window (Chrome tab and the native WKWebView, both loading :8090/) that reservation did not hold at the end of the scroll range in WebKit, so the last ~60px of any reply longer than the viewport sat behind the composer and was unreachable — you had to zoom out to read the end of a reply. Scope the fixed-composer treatment to the touch/mobile media query, where it is needed, and let the composer be a normal flex child on desktop again (its pre-1612bf0 behaviour): .content flex:1 then sizes to the leftover space and scrolls the whole reply into view. Mobile layout unchanged. Co-Authored-By: Claude Opus 4.8 --- codec_dashboard.html | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/codec_dashboard.html b/codec_dashboard.html index 11771680..3ef923ae 100644 --- a/codec_dashboard.html +++ b/codec_dashboard.html @@ -165,7 +165,7 @@ .tab-badge.hidden { display: none; } /* ── Content ── */ -.content { flex: 1; min-height: 0; overflow-y: auto; padding-bottom: calc(var(--input-h,76px) + 16px); -webkit-overflow-scrolling: touch; } +.content { flex: 1; min-height: 0; overflow-y: auto; padding-bottom: 16px; -webkit-overflow-scrolling: touch; } .panel { display: none; padding: 14px; } .panel.active { display: block; } #panel-chat { position: relative; } @@ -179,7 +179,13 @@ /* ── Input Area (matches Deep Chat) ── */ .input-area { - position: fixed; left: 0; right: 0; bottom: 0; z-index: 9997; + /* Desktop/native app: a normal flex child at the bottom of .app, so the + scroll area (.content) sizes to the space left over and a long reply + scrolls fully into view. It only goes position:fixed on phones (see the + touch media query) where the docked nav + gesture bars need it. + Regression note: 1612bf0 made it fixed on EVERY size, which hid the last + ~60px of every reply behind the composer in the desktop window. */ + flex-shrink: 0; padding: 8px 12px; border-top: 1px solid var(--border); background: var(--surface); padding-bottom: calc(8px + var(--safe-bottom)); } @@ -614,7 +620,7 @@ .text-size-group button.sel{background:var(--accent);border-color:var(--accent);color:#fff} @media (max-width: 768px), (max-height: 600px) and (pointer: coarse){ - .input-area{bottom:var(--nav-h,56px);padding-bottom:8px} + .input-area{position:fixed;left:0;right:0;bottom:var(--nav-h,56px);z-index:9997;padding-bottom:8px} .page-nav{padding-bottom:calc(6px + env(safe-area-inset-bottom,0px))} .content{padding-bottom:calc(var(--nav-h,56px) + var(--input-h,64px) + 12px)} } From 22f7a148db84d614e4c5506629bc03a45fde4adf Mon Sep 17 00:00:00 2001 From: Mickael Farina Date: Tue, 15 Sep 2026 12:05:11 +0200 Subject: [PATCH 2/2] fix(macos-app): Cmd-C/X/V/A/Z work inside the app window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The native app never installed an NSApp.mainMenu, only a status-bar item menu. Without a main-menu Edit menu carrying the standard clipboard key-equivalents, macOS had nowhere to dispatch Cmd-V (etc.) so paste silently failed in the dashboard's WKWebView — it only worked in a real browser because the browser ships its own Edit menu. Install a minimal main menu (App + Edit) at launch. The Edit items use the first-responder selectors (paste:/copy:/cut:/selectAll:/undo:/redo:) with a nil target, so the keystroke walks the responder chain to the focused WKWebView and the clipboard works in-app exactly as in a browser. Co-Authored-By: Claude Opus 4.8 --- packaging/macos/launcher/codec_launcher.swift | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/packaging/macos/launcher/codec_launcher.swift b/packaging/macos/launcher/codec_launcher.swift index f2543baa..a32b617b 100644 --- a/packaging/macos/launcher/codec_launcher.swift +++ b/packaging/macos/launcher/codec_launcher.swift @@ -140,6 +140,13 @@ final class Launcher: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ notification: Notification) { log("CODEC launched from \(bundleURL.path)") + // Without a main menu carrying an Edit menu, macOS has nowhere to + // dispatch Cmd-C/X/V/A/Z, so paste (etc.) silently failed inside the + // dashboard's WKWebView — the shortcuts only worked in a real browser + // because the browser ships its own Edit menu. The status-bar item's + // menu is NOT the main menu and does not supply these key-equivalents. + installMainMenu() + let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) if let button = item.button { button.image = NSImage(systemSymbolName: "waveform", accessibilityDescription: "CODEC") @@ -161,6 +168,46 @@ final class Launcher: NSObject, NSApplicationDelegate { } } + /// Install the app's main menu. The only functional requirement is a + /// standard Edit menu — its items use the first-responder selectors + /// (`paste:`, `copy:`, ...) with a nil target, so the keystroke walks the + /// responder chain to the focused WKWebView and the clipboard works inside + /// the app window exactly as it does in a browser. + private func installMainMenu() { + let mainMenu = NSMenu() + + // App menu. macOS always treats the first submenu as the app menu; it + // owns Cmd-Q, Cmd-H and friends. + let appItem = NSMenuItem() + mainMenu.addItem(appItem) + let appMenu = NSMenu() + appItem.submenu = appMenu + appMenu.addItem(withTitle: "Hide CODEC", action: #selector(NSApplication.hide(_:)), keyEquivalent: "h") + let hideOthers = appMenu.addItem(withTitle: "Hide Others", + action: #selector(NSApplication.hideOtherApplications(_:)), + keyEquivalent: "h") + hideOthers.keyEquivalentModifierMask = [.command, .option] + appMenu.addItem(withTitle: "Show All", action: #selector(NSApplication.unhideAllApplications(_:)), keyEquivalent: "") + appMenu.addItem(.separator()) + appMenu.addItem(withTitle: "Quit CODEC", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q") + + // Edit menu — the reason this method exists. + let editItem = NSMenuItem() + mainMenu.addItem(editItem) + let editMenu = NSMenu(title: "Edit") + editItem.submenu = editMenu + editMenu.addItem(withTitle: "Undo", action: Selector(("undo:")), keyEquivalent: "z") + let redo = editMenu.addItem(withTitle: "Redo", action: Selector(("redo:")), keyEquivalent: "z") + redo.keyEquivalentModifierMask = [.command, .shift] + editMenu.addItem(.separator()) + editMenu.addItem(withTitle: "Cut", action: #selector(NSText.cut(_:)), keyEquivalent: "x") + editMenu.addItem(withTitle: "Copy", action: #selector(NSText.copy(_:)), keyEquivalent: "c") + editMenu.addItem(withTitle: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v") + editMenu.addItem(withTitle: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a") + + NSApp.mainMenu = mainMenu + } + private func rebuildMenu(status: String, enabled: Bool) { let menu = NSMenu() menu.addItem(withTitle: status, action: nil, keyEquivalent: "")