Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions codec_dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand All @@ -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));
}
Expand Down Expand Up @@ -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)}
}
Expand Down
47 changes: 47 additions & 0 deletions packaging/macos/launcher/codec_launcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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: "")
Expand Down