Repository navigation
Add Home workspace webview #6788
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| import{c as h,j as e,i as s,b as m}from"./vendor.mjs";import{i as d}from"./installWebviewStyles.mjs";const p=":root{color-scheme:light dark;--home-bg: light-dark(#f6f7f9, #111214);--home-fg: light-dark(#15171a, #f3f4f5);--home-muted: light-dark(#626973, #a9b0ba);--home-border: light-dark(rgb(0 0 0 / 12%), rgb(255 255 255 / 14%));--home-card: light-dark(rgb(255 255 255 / 82%), rgb(255 255 255 / 7%));--home-card-hover: light-dark(#fff, rgb(255 255 255 / 11%));--home-shadow: light-dark(rgb(20 26 35 / 10%), rgb(0 0 0 / 20%));--home-blue: #0a84ff;--home-green: #25a244;--home-violet: #9b5cff;--home-orange: #ff9500;background:transparent;color:var(--home-fg);font-family:system-ui,-apple-system,BlinkMacSystemFont,SF Pro Text,Helvetica Neue,Arial,sans-serif;font-size:13px;line-height:1.4}html,body,#root{width:100%;height:100%;margin:0;overflow:hidden;background:transparent}*{box-sizing:border-box}button{font:inherit}.home-shell{min-height:100%;padding:34px 32px;display:grid;align-content:start;gap:26px;background:linear-gradient(135deg,color-mix(in lab,var(--home-blue) 10%,transparent),transparent 42%),var(--home-bg);color:var(--home-fg)}.home-header{display:flex;align-items:center;gap:14px;min-width:0}.home-mark,.home-action-icon{display:grid;place-items:center;flex:0 0 auto}.home-mark{width:48px;height:48px;border-radius:8px;color:#fff;background:linear-gradient(135deg,#0a84ff,#14b8a6);box-shadow:0 10px 24px var(--home-shadow)}.home-mark svg{width:25px;height:25px}.home-heading{min-width:0}.home-heading h1{margin:0;font-size:28px;line-height:1.05;font-weight:720;letter-spacing:0}.home-heading p{margin:5px 0 0;color:var(--home-muted);font-size:13px;font-weight:500}.home-actions{width:min(100%,920px);display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px}.home-action{min-height:78px;display:flex;align-items:center;gap:12px;padding:14px;border:1px solid var(--home-border);border-radius:8px;background:var(--home-card);color:inherit;text-align:left;cursor:default;box-shadow:0 1px #ffffff1f inset}.home-action:hover{background:var(--home-card-hover);border-color:color-mix(in lab,var(--home-fg) 24%,transparent)}.home-action:focus-visible{outline:2px solid color-mix(in lab,var(--home-blue) 80%,white);outline-offset:2px}.home-action:disabled{opacity:.62}.home-action-icon{width:36px;height:36px;border-radius:8px}.home-action-icon svg{width:20px;height:20px}.home-action-icon-newWorkspace{color:var(--home-green);background:color-mix(in lab,var(--home-green) 16%,transparent)}.home-action-icon-newBrowser{color:var(--home-blue);background:color-mix(in lab,var(--home-blue) 16%,transparent)}.home-action-icon-commandPalette{color:var(--home-violet);background:color-mix(in lab,var(--home-violet) 16%,transparent)}.home-action-icon-settings{color:var(--home-orange);background:color-mix(in lab,var(--home-orange) 16%,transparent)}.home-action svg{fill:none;stroke:currentColor;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round}.home-action-copy{display:grid;min-width:0;gap:2px}.home-action-title{font-size:14px;font-weight:680;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.home-action-subtitle{color:var(--home-muted);font-size:12px;font-weight:500}.home-error{margin:0;width:min(100%,920px);color:light-dark(#b42318,#ffb4ab);font-size:12px}@media(max-width:560px){.home-shell{padding:22px 18px}.home-actions{grid-template-columns:minmax(0,1fr)}}",x=[{id:"newWorkspace",title:"New Workspace",subtitle:"Start a terminal workspace",icon:"terminal"},{id:"newBrowser",title:"Browser",subtitle:"Open a browser workspace",icon:"browser"},{id:"commandPalette",title:"Command Palette",subtitle:"Run commands and switch workspaces",icon:"command"},{id:"settings",title:"Settings",subtitle:"Open preferences",icon:"settings"}];function g(){const t=window.webkit?.messageHandlers?.cmuxHome;return t&&typeof t.postMessage=="function"?t:null}async function f(t){const a=g();if(!a)throw new Error("Home bridge is unavailable.");const i=await a.postMessage({action:t});if(!i?.ok)throw new Error(i?.error?.code??"action_failed")}function u(){const[t,a]=s.useState(null),[i,o]=s.useState(null);async function c(r){a(r),o(null);try{await f(r)}catch(n){o(n instanceof Error?n.message:"Action failed.")}finally{a(null)}}return e.jsxs("main",{className:"home-shell","aria-label":"Home",children:[e.jsxs("section",{className:"home-header",children:[e.jsx("div",{className:"home-mark","aria-hidden":"true",children:e.jsx(l,{name:"home"})}),e.jsxs("div",{className:"home-heading",children:[e.jsx("h1",{children:"Home"}),e.jsx("p",{children:"Choose a workspace action."})]})]}),e.jsx("section",{className:"home-actions","aria-label":"Workspace actions",children:x.map(r=>e.jsxs("button",{type:"button",className:"home-action",disabled:t!=null,onClick:()=>{c(r.id)},children:[e.jsx("span",{className:`home-action-icon home-action-icon-${r.id}`,"aria-hidden":"true",children:e.jsx(l,{name:r.icon})}),e.jsxs("span",{className:"home-action-copy",children:[e.jsx("span",{className:"home-action-title",children:r.title}),e.jsx("span",{className:"home-action-subtitle",children:r.subtitle})]})]},r.id))}),i?e.jsx("p",{className:"home-error",role:"status",children:i}):null]})}function l(t){const a=m.c(5),{name:i}=t;switch(i){case"browser":{let o;return a[0]===Symbol.for("react.memo_cache_sentinel")?(o=e.jsxs("svg",{viewBox:"0 0 20 20",focusable:"false",children:[e.jsx("circle",{cx:"10",cy:"10",r:"7"}),e.jsx("path",{d:"M3.5 10h13"}),e.jsx("path",{d:"M10 3a10 10 0 0 1 0 14"}),e.jsx("path",{d:"M10 3a10 10 0 0 0 0 14"})]}),a[0]=o):o=a[0],o}case"command":{let o;return a[1]===Symbol.for("react.memo_cache_sentinel")?(o=e.jsx("svg",{viewBox:"0 0 20 20",focusable:"false",children:e.jsx("path",{d:"M7 7H5.5a2.5 2.5 0 1 1 2.5-2.5V7h4V4.5A2.5 2.5 0 1 1 14.5 7H13v4h1.5A2.5 2.5 0 1 1 12 13.5V12H8v1.5A2.5 2.5 0 1 1 5.5 11H7V7Z"})}),a[1]=o):o=a[1],o}case"settings":{let o;return a[2]===Symbol.for("react.memo_cache_sentinel")?(o=e.jsxs("svg",{viewBox:"0 0 20 20",focusable:"false",children:[e.jsx("circle",{cx:"10",cy:"10",r:"2.5"}),e.jsx("path",{d:"M10 2.8v2"}),e.jsx("path",{d:"M10 15.2v2"}),e.jsx("path",{d:"m4.9 4.9 1.4 1.4"}),e.jsx("path",{d:"m13.7 13.7 1.4 1.4"}),e.jsx("path",{d:"M2.8 10h2"}),e.jsx("path",{d:"M15.2 10h2"}),e.jsx("path",{d:"m4.9 15.1 1.4-1.4"}),e.jsx("path",{d:"m13.7 6.3 1.4-1.4"})]}),a[2]=o):o=a[2],o}case"terminal":{let o;return a[3]===Symbol.for("react.memo_cache_sentinel")?(o=e.jsxs("svg",{viewBox:"0 0 20 20",focusable:"false",children:[e.jsx("rect",{x:"3",y:"4",width:"14",height:"12",rx:"2"}),e.jsx("path",{d:"m6 8 2.5 2L6 12"}),e.jsx("path",{d:"M10 12h4"})]}),a[3]=o):o=a[3],o}default:{let o;return a[4]===Symbol.for("react.memo_cache_sentinel")?(o=e.jsxs("svg",{viewBox:"0 0 20 20",focusable:"false",children:[e.jsx("path",{d:"M3.5 9.5 10 4l6.5 5.5"}),e.jsx("path",{d:"M5 8.5V16h10V8.5"})]}),a[4]=o):o=a[4],o}}}function v(t){d("home",p),document.title="Home",h.createRoot(t).render(e.jsx(u,{}))}export{v as mountHomeSurface}; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| import{e as u,f as a,g as i,d,O as f}from"./vendor.mjs";function l(){const t=globalThis.crypto;if(typeof t?.randomUUID=="function")return t.randomUUID();const o=t?.getRandomValues?.bind(t);if(typeof o=="function"){const e=new Uint8Array(16);return o(e),e[6]=e[6]&15|64,e[8]=e[8]&63|128,[...e].map((r,s)=>{const n=r.toString(16).padStart(2,"0");return[4,6,8,10].includes(s)?`-${n}`:n}).join("")}return`cmux-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}function p(t){const o=u({component:f,notFoundComponent:t}),e=a({getParentRoute:()=>o,path:"/",component:t}),r=a({getParentRoute:()=>o,path:"/diff",component:t}),s=a({getParentRoute:()=>o,path:"/cmux-diff-viewer",component:t}),n=a({getParentRoute:()=>o,path:"/agent-session",component:t}),c=o.addChildren([e,r,s,n]);return i({history:d(),routeTree:c})}function R(t,o){const e=document.createElement("style");e.dataset.cmuxWebviewStyle=t,e.textContent=o,document.head.append(e)}export{p as c,R as i,l as m}; | ||
| function a(t,n){const e=document.createElement("style");e.dataset.cmuxWebviewStyle=t,e.textContent=n,document.head.append(e)}export{a as i}; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| import{e as i,f as n,g as d,d as f,O as R}from"./vendor.mjs";function p(){const t=globalThis.crypto;if(typeof t?.randomUUID=="function")return t.randomUUID();const e=t?.getRandomValues?.bind(t);if(typeof e=="function"){const o=new Uint8Array(16);return e(o),o[6]=o[6]&15|64,o[8]=o[8]&63|128,[...o].map((a,c)=>{const r=a.toString(16).padStart(2,"0");return[4,6,8,10].includes(c)?`-${r}`:r}).join("")}return`cmux-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}function g(t){const e=i({component:R,notFoundComponent:t}),o=n({getParentRoute:()=>e,path:"/",component:t}),a=n({getParentRoute:()=>e,path:"/diff",component:t}),c=n({getParentRoute:()=>e,path:"/cmux-diff-viewer",component:t}),r=n({getParentRoute:()=>e,path:"/agent-session",component:t}),s=n({getParentRoute:()=>e,path:"/home",component:t}),u=e.addChildren([o,a,c,r,s]);return d({history:f(),routeTree:u})}export{g as c,p as m}; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| <!doctype html> | ||
| <html lang="en" data-cmux-webview-kind="home"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>Home</title> | ||
| </head> | ||
| <body data-cmux-webview-kind="home"> | ||
| <main id="root"></main> | ||
| <script type="module" src="./main.mjs"></script> | ||
| </body> | ||
| </html> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| import{_ as n}from"./chunks/vendor.mjs";function o(){return document.documentElement.dataset.cmuxWebviewKind==="agent-session"||document.body.dataset.cmuxWebviewKind==="agent-session"||document.getElementById("cmux-agent-session-config")?"agent-session":"diff"}const t=document.getElementById("root");if(!t)throw new Error("Missing cmux webview root");o()==="agent-session"?n(()=>import("./chunks/agentSessionSurface.mjs"),[]).then(e=>{e.mountAgentSessionSurface(t)}):n(()=>import("./chunks/diffSurface.mjs"),[]).then(e=>{e.mountDiffSurface(t)}); | ||
| import{_ as o}from"./chunks/vendor.mjs";function n(){return document.documentElement.dataset.cmuxWebviewKind==="home"||document.body.dataset.cmuxWebviewKind==="home"?"home":document.documentElement.dataset.cmuxWebviewKind==="agent-session"||document.body.dataset.cmuxWebviewKind==="agent-session"||document.getElementById("cmux-agent-session-config")?"agent-session":"diff"}const t=document.getElementById("root");if(!t)throw new Error("Missing cmux webview root");n()==="agent-session"?o(()=>import("./chunks/agentSessionSurface.mjs"),[]).then(e=>{e.mountAgentSessionSurface(t)}):n()==="home"?o(()=>import("./chunks/homeSurface.mjs"),[]).then(e=>{e.mountHomeSurface(t)}):o(()=>import("./chunks/diffSurface.mjs"),[]).then(e=>{e.mountDiffSurface(t)}); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6637,6 +6637,105 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent | |
| return false | ||
| } | ||
|
|
||
| @discardableResult | ||
| func showHomeInActiveMainWindow(preferredWindow: NSWindow? = nil) -> Bool { | ||
| func showHome(_ context: MainWindowContext) -> Bool { | ||
| guard let window = resolvedWindow(for: context) else { | ||
| discardOrphanedMainWindowContext(context) | ||
| return false | ||
| } | ||
| guard BrowserAvailabilitySettings.isEnabled(), | ||
| let homeURL = Self.homeWebViewURL() else { | ||
| NSSound.beep() | ||
| return false | ||
| } | ||
| setActiveMainWindow(window) | ||
| context.sidebarSelectionState.selection = .tabs | ||
| ensureHomeWorkspace(in: context.tabManager, homeURL: homeURL) | ||
| return true | ||
| } | ||
|
|
||
| if let preferredWindow, | ||
| let preferredContext = contextForMainTerminalWindow(preferredWindow), | ||
| showHome(preferredContext) { | ||
| return true | ||
| } | ||
| if let keyWindow = shortcutRoutingKeyWindow, | ||
| let keyContext = contextForMainTerminalWindow(keyWindow), | ||
| showHome(keyContext) { | ||
| return true | ||
| } | ||
| if let mainWindow = NSApp.mainWindow, | ||
| let mainContext = contextForMainTerminalWindow(mainWindow), | ||
| showHome(mainContext) { | ||
| return true | ||
| } | ||
| if let activeManager = tabManager, | ||
| let activeContext = mainWindowContexts.values.first(where: { $0.tabManager === activeManager }), | ||
| showHome(activeContext) { | ||
| return true | ||
| } | ||
| for fallbackContext in Array(mainWindowContexts.values) where showHome(fallbackContext) { | ||
| return true | ||
| } | ||
| return false | ||
| } | ||
|
|
||
| private static func homeWebViewURL() -> URL? { | ||
| CmuxBundledWebViewURLSchemeHandler.homeURL() | ||
| } | ||
|
|
||
| @discardableResult | ||
| func ensureHomeWorkspace(in tabManager: TabManager, homeURL: URL) -> Workspace { | ||
| let title = String(localized: "home.workspace.title", defaultValue: "Home") | ||
| let workspace = tabManager.tabs.first { workspace in | ||
| workspace.customTitle == title || workspace.title == title | ||
| } ?? tabManager.addWorkspace( | ||
|
Comment on lines
+6691
to
+6693
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When any existing workspace has the localized display title “Home” (for example a user-renamed project or a browser page whose title is Home), this treats that user workspace as the dashboard and the following code then pins/reorders it and navigates or adds a browser surface. That can overwrite the user's current workspace instead of opening the Home dashboard; match a stable marker such as the bundled Home URL or dedicated metadata rather than the mutable display title. Useful? React with 👍 / 👎. |
||
| title: title, | ||
|
Comment on lines
+6689
to
+6694
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift Use a stable Home workspace identifier instead of the localized title. This lookup can hijack any user workspace renamed to the localized Home title, and it will also miss the existing Home workspace after a locale change, creating or mutating the wrong tab before Lines 6707-6712 pin/reorder it. Home needs a non-user-editable identifier/flag persisted with the workspace, not a title comparison. As per path instructions, "flag deriving the value from a window/pane/terminal title, name, or process-argv heuristic" for correctness-critical detection/identity. 🤖 Prompt for AI AgentsSource: Path instructions |
||
| workingDirectory: FileManager.default.homeDirectoryForCurrentUser.path, | ||
| initialSurface: .browser, | ||
| initialBrowserURL: homeURL, | ||
| initialBrowserOmnibarVisible: false, | ||
| initialBrowserTransparentBackground: true, | ||
| initialBrowserBypassRemoteProxy: true, | ||
| inheritWorkingDirectory: false, | ||
| select: false, | ||
| autoWelcomeIfNeeded: false, | ||
| autoRefreshMetadata: false | ||
| ) | ||
| workspace.setCustomTitle(title) | ||
| if !workspace.isPinned { | ||
| tabManager.setPinned(workspace, pinned: true) | ||
| } | ||
| _ = tabManager.reorderWorkspace(tabId: workspace.id, toIndex: 0) | ||
| ensureHomeBrowserSurface(in: workspace, homeURL: homeURL) | ||
| tabManager.selectedTabId = workspace.id | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Fresh evidence in this revision is that Home now selects its pinned workspace here by assigning Useful? React with 👍 / 👎. |
||
| return workspace | ||
| } | ||
|
|
||
| private func ensureHomeBrowserSurface(in workspace: Workspace, homeURL: URL) { | ||
| let browserPanel = workspace.panels.values | ||
| .compactMap { $0 as? BrowserPanel } | ||
| .first ?? workspace.bonsplitController.allPaneIds.first.flatMap { paneId in | ||
| workspace.newBrowserSurface( | ||
| inPane: paneId, | ||
| url: homeURL, | ||
| focus: true, | ||
| creationPolicy: .automationPreload, | ||
| omnibarVisible: false, | ||
| transparentBackground: true, | ||
| bypassRemoteProxy: true | ||
| ) | ||
| } | ||
| guard let browserPanel else { return } | ||
| browserPanel.setOmnibarVisible(false) | ||
| let currentURL = browserPanel.currentURLForTabDuplication ?? browserPanel.webView.url | ||
| if currentURL?.absoluteString != homeURL.absoluteString { | ||
| browserPanel.navigate(to: homeURL) | ||
| } | ||
| workspace.focusPanel(browserPanel.id) | ||
| } | ||
|
|
||
| @discardableResult | ||
| func toggleRightSidebarInActiveMainWindow(preferredWindow: NSWindow? = nil) -> Bool { | ||
| guard let context = preferredRegisteredMainWindowContext(preferredWindow: preferredWindow) else { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Remove the beep from this shared helper.
ContentView.showHome()and the minimal-titlebar callers already beep when this returnsfalse, so this path produces two alert sounds when Home cannot open.Suggested fix
guard BrowserAvailabilitySettings.isEnabled(), let homeURL = Self.homeWebViewURL() else { - NSSound.beep() return false }📝 Committable suggestion
🤖 Prompt for AI Agents