the universal sandbox runtime for agents and humans. pocketenv.io
sandbox openclaw agent claude-code vercel-sandbox deno-sandbox cloudflare-sandbox atproto sprites daytona
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add context menu and refine project row UI

Add a reusable ContextMenu component and export. Integrate it into
the project table row, refactor the action buttons into a centered
flex layout and adjust button/loading/terminal visuals and behavior.
Update Navbar FAQs link to external docs and remove the Snapshots link
from the sidebar. Constrain the Projects table container height and make
the Sandbox page full-height with vertical overflow hidden.

+152 -60
+110
apps/web/src/components/contextmenu/ContextMenu.tsx
··· 1 + import { useEffect, useRef, useState } from "react"; 2 + 3 + function ContextMenu() { 4 + const dropdownRef = useRef<HTMLDivElement>(null); 5 + const [open, setOpen] = useState(false); 6 + 7 + useEffect(() => { 8 + if (!open) return; 9 + function handleClickOutside(event: MouseEvent) { 10 + if ( 11 + dropdownRef.current && 12 + !dropdownRef.current.contains(event.target as Node) 13 + ) { 14 + setOpen(false); 15 + } 16 + } 17 + document.addEventListener("mousedown", handleClickOutside); 18 + return () => { 19 + document.removeEventListener("mousedown", handleClickOutside); 20 + }; 21 + }, [open]); 22 + 23 + const onOpenContextMenu = (e: React.MouseEvent) => { 24 + e.stopPropagation(); 25 + setOpen(!open); 26 + }; 27 + 28 + const onAddFile = (e: React.MouseEvent) => { 29 + e.stopPropagation(); 30 + setOpen(false); 31 + }; 32 + 33 + const onAddSecret = (e: React.MouseEvent) => { 34 + e.stopPropagation(); 35 + setOpen(false); 36 + }; 37 + 38 + const onAddEnvironmentVariable = (e: React.MouseEvent) => { 39 + e.stopPropagation(); 40 + setOpen(false); 41 + }; 42 + 43 + const onAddVolume = (e: React.MouseEvent) => { 44 + e.stopPropagation(); 45 + setOpen(false); 46 + }; 47 + 48 + const onDelete = (e: React.MouseEvent) => { 49 + e.stopPropagation(); 50 + setOpen(false); 51 + }; 52 + 53 + return ( 54 + <> 55 + <div 56 + ref={dropdownRef} 57 + className={`dropdown relative inline-flex items-center [--auto-close:inside] [--offset:8] [--placement:bottom-end] ${ 58 + open ? "open" : "" 59 + }`} 60 + > 61 + <button 62 + type="button" 63 + aria-haspopup="menu" 64 + aria-expanded={open ? "true" : "false"} 65 + aria-label="Dropdown" 66 + className="dropdown-toggle btn btn-circle btn-text btn-sm bg-transparent outline-0 flex items-center" 67 + onClick={onOpenContextMenu} 68 + > 69 + <span className="icon-[tabler--dots-vertical] size-5 hover:text-white block mx-auto"></span> 70 + </button> 71 + <ul 72 + className={`dropdown-menu dropdown-open:opacity-100 absolute right-0 top-full mt-2 min-w-60 z-50 ${ 73 + open ? "" : "hidden" 74 + }`} 75 + role="menu" 76 + aria-orientation="vertical" 77 + aria-labelledby="dropdown-avatar" 78 + > 79 + <li> 80 + <div className="dropdown-item" onClick={onAddFile}> 81 + Add File 82 + </div> 83 + </li> 84 + <li> 85 + <div className="dropdown-item" onClick={onAddSecret}> 86 + Add Secret 87 + </div> 88 + </li> 89 + <li> 90 + <div className="dropdown-item" onClick={onAddEnvironmentVariable}> 91 + Add Environment Variable 92 + </div> 93 + </li> 94 + <li> 95 + <div className="dropdown-item" onClick={onAddVolume}> 96 + Add Volume 97 + </div> 98 + </li> 99 + <li> 100 + <div className="dropdown-item" onClick={onDelete}> 101 + Delete 102 + </div> 103 + </li> 104 + </ul> 105 + </div> 106 + </> 107 + ); 108 + } 109 + 110 + export default ContextMenu;
+3
apps/web/src/components/contextmenu/index.tsx
··· 1 + import ContextMenu from "./ContextMenu"; 2 + 3 + export default ContextMenu;
+4 -7
apps/web/src/components/navbar/Navbar.tsx
··· 168 168 </Link> 169 169 </li> 170 170 <li> 171 - <Link className="dropdown-item" to="/settings"> 172 - <span className="icon-[tabler--settings]"></span> 173 - Settings 174 - </Link> 175 - </li> 176 - <li> 177 - <a className="dropdown-item" href="/faqs"> 171 + <a 172 + className="dropdown-item" 173 + href="https://docs.pocketenv.io/faqs" 174 + > 178 175 <span className="icon-[tabler--help-triangle]"></span> 179 176 FAQs 180 177 </a>
-16
apps/web/src/components/sidebar/Sidebar.tsx
··· 73 73 </li> 74 74 <li> 75 75 <Link 76 - to="/snapshots" 77 - className={`${ 78 - isActive("/snapshots") 79 - ? "active bg-white/7 text-[#00e8c6]! font-semibold rounded-full" 80 - : "rounded-full hover:text-white" 81 - } ${isCollapsed ? "justify-center px-2" : ""}`} 82 - title={isCollapsed ? "Snapshots" : undefined} 83 - > 84 - <span 85 - className={`icon-[tabler--device-floppy] size-6 ${isCollapsed ? "" : "mr-2"}`} 86 - ></span> 87 - {!isCollapsed && "Snapshots"} 88 - </Link> 89 - </li> 90 - <li> 91 - <Link 92 76 to="/volumes" 93 77 className={`${ 94 78 isActive("/volumes")
+30 -35
apps/web/src/pages/projects/Project/Project.tsx
··· 11 11 import { useAtomValue } from "jotai"; 12 12 import { profileAtom } from "../../../atoms/profile"; 13 13 import TerminalModal from "./TerminalModal"; 14 + import ContextMenu from "../../../components/contextmenu"; 14 15 15 16 export type ProjectProps = { 16 17 sandbox: Sandbox; ··· 57 58 }); 58 59 }; 59 60 60 - const onOpenContextMenu = (e: React.MouseEvent) => { 61 - e.stopPropagation(); 62 - }; 63 - 64 61 return ( 65 62 <tr className="cursor-pointer" onClick={onOpenProject}> 66 63 <td>{sandbox.name}</td> ··· 81 78 </span> 82 79 </td> 83 80 <td>{dayjs(sandbox.createdAt).format("M/D/YYYY, h:mm:ss A")}</td> 84 - <td> 85 - {!displayLoading && sandbox.status === "RUNNING" && ( 81 + <td className="align-middle"> 82 + <div className="flex items-center justify-center p-1"> 83 + {!displayLoading && sandbox.status === "RUNNING" && ( 84 + <button 85 + className="btn btn-circle btn-text btn-sm bg-transparent outline-0" 86 + onClick={onStop} 87 + > 88 + <span className="icon-[tabler--player-stop] size-5 hover:text-white"></span> 89 + </button> 90 + )} 91 + {!displayLoading && sandbox.status !== "RUNNING" && ( 92 + <button 93 + className="btn btn-circle btn-text btn-sm bg-transparent outline-0" 94 + onClick={onPlay} 95 + > 96 + <span className="icon-[tabler--player-play] size-5 hover:text-white"></span> 97 + </button> 98 + )} 99 + {displayLoading && ( 100 + <span className="loading loading-spinner loading-sm btn-text mr-[10px]"></span> 101 + )} 86 102 <button 87 - className="btn btn-circle btn-text btn-sm bg-transparent outline-0" 88 - onClick={onStop} 103 + className={`btn btn-circle btn-text btn-sm bg-transparent outline-0 ${sandbox.status !== "RUNNING" ? "opacity-50" : ""}`} 104 + onClick={onOpenTerminal} 89 105 > 90 - <span className="icon-[tabler--player-stop] size-5 hover:text-white"></span> 91 - </button> 92 - )} 93 - {!displayLoading && sandbox.status !== "RUNNING" && ( 94 - <button 95 - className="btn btn-circle btn-text btn-sm bg-transparent outline-0" 96 - onClick={onPlay} 97 - > 98 - <span className="icon-[tabler--player-play] size-5 hover:text-white"></span> 106 + <span 107 + className={`icon-[mingcute--terminal-fill] size-5 ${sandbox.status !== "RUNNING" ? "" : "hover:text-white"}`} 108 + ></span> 99 109 </button> 100 - )} 101 - {displayLoading && ( 102 - <span className="loading loading-spinner loading-sm btn-text mr-[10px]"></span> 103 - )} 104 - <button 105 - className={`btn btn-circle btn-text btn-sm bg-transparent outline-0 ${sandbox.status !== "RUNNING" ? "opacity-50" : ""}`} 106 - onClick={onOpenTerminal} 107 - > 108 - <span 109 - className={`icon-[mingcute--terminal-fill] size-5 ${sandbox.status !== "RUNNING" ? "" : "hover:text-white"}`} 110 - ></span> 111 - </button> 112 - <button 113 - className="btn btn-circle btn-text btn-sm bg-transparent outline-0" 114 - onClick={onOpenContextMenu} 115 - > 116 - <span className="icon-[tabler--dots-vertical] size-5 hover:text-white"></span> 117 - </button> 110 + <ContextMenu /> 111 + </div> 112 + 118 113 <TerminalModal 119 114 title={sandbox.name} 120 115 isOpen={modalOpen}
+4 -1
apps/web/src/pages/projects/Projects.tsx
··· 10 10 return ( 11 11 <Main> 12 12 <div> 13 - <div className="w-full overflow-x-auto"> 13 + <div 14 + className="w-full overflow-x-auto" 15 + style={{ height: "calc(100vh - 80px)" }} 16 + > 14 17 <table className="table"> 15 18 <thead> 16 19 <tr>
+1 -1
apps/web/src/pages/sandbox/Sandbox.tsx
··· 74 74 75 75 return ( 76 76 <> 77 - <div className="flex flex-col min-h-screen bg-base-100"> 77 + <div className="flex flex-col min-h-screen bg-base-100 h-screen overflow-y-hidden"> 78 78 <Navbar withLogo title="" project={data?.sandbox?.name} /> 79 79 {data?.sandbox && !isLoading && ( 80 80 <>