Hive is an Electron app (React 19 + Zustand + Vite) for managing git worktrees with AI coding sessions. It already has a --headless mode with a GraphQL server (GraphQL Yoga, port 8443, API key auth, WebSocket subscriptions).
Goal: Serve the React frontend as a web app from the headless server, accessible via Chrome from any machine. The Electron app continues using IPC (unchanged). Web mode uses GraphQL as transport.
Approach: Transport adapter pattern -- React calls the same window.* API shape. In Electron, preload bridge handles it. In web mode, adapters translate to GraphQL.
Decisions:
- HTTP mode (
--insecure) for now. TLS via reverse proxy later. - Vite dev server with proxy to headless server (no CORS needed).
- Full feature parity -- all 17 adapter modules, all GraphQL schema gaps.
Goal: Make the headless server capable of serving static web files and validating auth from a browser.
-
Create
src/server/static-handler.ts- Export
createStaticHandler(webRoot: string)that returns a(req: IncomingMessage, res: ServerResponse) => booleanhandler - Resolve request paths against
webRootwith path traversal prevention (..escaping) - Serve files with correct
Content-Typebased on extension (.js,.css,.html,.svg,.png,.woff2,.ico,.json,.map) - Cache headers:
Cache-Control: public, max-age=31536000, immutablefor/assets/*(hashed filenames),Cache-Control: no-cacheforindex.html - SPA fallback: any path that doesn't match a file and isn't
/graphqlor/api/*servesindex.html - Return
trueif request was handled,falseto pass through
- Export
-
Create
src/server/plugins/auth-endpoint.ts- Export
handleAuthEndpoint(req, res, getKeyHash, bruteForce)function - Match only
POST /api/auth/validate - Parse JSON body for
{ apiKey: string } - Call existing
verifyApiKey()fromsrc/server/plugins/auth.ts - Respect
BruteForceTracker-- checkisBlocked(), callrecordFailure()/recordSuccess() - Return
{ valid: true }with 200, or{ valid: false, error: string }with 401/429 - Return
falsefor non-matching requests to pass through
- Export
-
Modify
src/server/config.ts- Add
webRoot: string | nullfield toHeadlessConfig(default:null) - When
null, runtime resolves topath.join(__dirname, 'web')if that directory containsindex.html
- Add
-
Modify
src/server/index.ts- Add
webRoot?: stringtoServerOptionsinterface - Change server handler from
createHttpServer(yoga)tocreateHttpServer(requestHandler)whererequestHandlerroutes:POST /api/auth/validate-> auth endpoint handler- Path starts with
/graphql->yoga(req, res) - Everything else -> static handler (if webRoot configured) or yoga fallback
- Same change for
createHttpsServerpath - If
webRootis not set or has noindex.html, behavior is identical to current (no regression)
- Add
-
Modify
src/server/headless-bootstrap.ts- Pass resolved
webRoottostartGraphQLServer()options - Log whether web UI is being served:
"Web UI available at http://..."or"Web UI not found, API-only mode"
- Pass resolved
hive --headless --insecure --port 8443starts successfully- Placing a test
index.htmlin the expected web directory causes it to be served athttp://localhost:8443/ POST http://localhost:8443/api/auth/validatewith correct API key returns{ valid: true }POST http://localhost:8443/api/auth/validatewith wrong key returns 401/graphqlendpoint continues to work exactly as before- Brute force protection works on the auth endpoint
- No changes to Electron app behavior (IPC unaffected)
Goal: Add all missing GraphQL operations that have IPC equivalents, so the web adapter layer has complete coverage.
-
Worktree extensions -- modify
src/server/schema/types/project.graphqlandsrc/server/schema/schema.graphql- Extend
Worktreetype to include missing fields:attachments,pinned,context,github_pr_number,github_pr_url(these exist in the DB schema andindex.d.tsbut are absent from the GraphQL type) - Extend
CreateFromBranchInputto includeprNumberandnameHintfields (used by PR checkout flow) - Add
AttachmentInputinput type andAttachmenttype - Add mutations:
worktreeAddAttachment,worktreeRemoveAttachment,worktreeAttachPR,worktreeDetachPR,worktreeSetPinned - Add queries:
pinnedWorktrees,recentlyActiveWorktrees(cutoffMs: Int!) - Add resolvers in
src/server/resolvers/mutation/db.resolvers.tsandquery/db.resolvers.ts - Backing DB methods already exist in
database.ts
- Extend
-
Connection extensions -- modify schema and resolvers
- Add mutation:
connectionSetPinned - Add queries:
pinnedConnections,connectionOpenInTerminal(or mark as server-side-only) - Add resolvers
- Add mutation:
-
Project sorting and detection -- modify schema and resolvers
- Add query:
projectIdsSortedByLastMessage - Add queries:
projectFindXcworkspace(projectPath),projectIsAndroidProject(projectPath) - Backing:
database.ts+ filesystem checks fromproject-handlers.ts
- Add query:
-
Git operations -- modify
src/server/schema/types/git.graphql- Add types:
GitBranchDiffFile,GitBranchDiffFilesResult,GitBranchFileDiffResult,GitFileContentBase64Result,GitPRStateResult,PRReviewComment,PRReviewUser,GitPRReviewCommentsResult - Add queries:
gitBranchDiffFiles,gitBranchFileDiff,gitFileContentBase64,gitRefContentBase64,gitPRState,gitPRReviewComments - Add resolvers in
src/server/resolvers/query/git.resolvers.ts - Backing:
git-service.tsmethods (getBranchDiffFiles,getBranchFileDiff) andgit-file-handlers.tsPR logic
- Add types:
-
Kanban board -- create new schema and resolver files
- Create
src/server/schema/types/kanban.graphqlwithKanbanTicket,KanbanTicketColumnenum,TicketFollowupMessage, and input types - Add to
schema.graphql: queries (kanbanTicket,kanbanTicketsByProject,kanbanTicketsBySession,kanbanFollowupsByTicket) and mutations (kanbanCreateTicket,kanbanUpdateTicket,kanbanDeleteTicket,kanbanArchiveTicket,kanbanArchiveAllDone,kanbanUnarchiveTicket,kanbanMoveTicket,kanbanReorderTicket,kanbanToggleSimpleMode,kanbanCreateFollowup) - Create
src/server/resolvers/query/kanban.resolvers.ts - Create
src/server/resolvers/mutation/kanban.resolvers.ts - Backing:
database.tslines 1614-1792
- Create
-
Usage tracking -- create resolver
- Add queries:
usageFetch,usageFetchOpenaireturningUsageResulttype - Create
src/server/resolvers/query/usage.resolvers.ts - Backing:
usage-service.ts,openai-usage-service.ts
- Add queries:
-
File operations -- extend existing
- Add query:
fileReadImageAsBase64(filePath)returning base64 string + mimeType - Add resolver in
src/server/resolvers/query/file.resolvers.ts
- Add query:
-
OpenCode extensions -- modify
src/server/schema/types/opencode.graphql- Add mutation:
opencodeCommandApprovalReply(input)-- needed for command filter approval UI - Verify all IPC channels in
opencode-handlers.tshave GraphQL equivalents
- Add mutation:
-
Merge all new resolvers
- Update
src/server/resolvers/index.tsto import and merge kanban + usage resolvers
- Update
- All new GraphQL operations are callable via the
/graphqlendpoint - Each new query/mutation returns the expected data shape matching the IPC handler equivalents
- Existing GraphQL operations are unaffected (no regressions)
- Schema validates without errors
- TypeScript compiles cleanly
Goal: Create the adapter infrastructure and implement the four most critical adapters (db, opencode, git, file-tree) that cover ~80% of frontend functionality.
-
Create
src/renderer/src/transport/detect.ts- Export
detectTransportMode(): 'electron' | 'web' - Check
window.db !== undefined-- if true, preload ran = Electron. Otherwise = web. - Simple, synchronous, no dependencies
- Export
-
Create
src/renderer/src/transport/types.ts- Re-export Window sub-interfaces as named types:
DbApi = Window['db'],GitOpsApi = Window['gitOps'], etc. - References existing
src/preload/index.d.tsdeclarations (no duplication)
- Re-export Window sub-interfaces as named types:
-
Create
src/renderer/src/transport/graphql/client.tsinitGraphQLClient({ httpUrl, wsUrl, apiKey })-- configures fetch URL andgraphql-wsclientgraphqlQuery<T>(query, variables?)-- fetch-based query/mutation execution with Bearer auth header, error extractiongraphqlSubscribe<T>(query, variables, onData, onError?)-- returns() => voidcleanup function usinggraphql-wsclient- No Apollo/urql -- Zustand already manages state. Uses existing
graphql-wsdependency.
-
Create
src/renderer/src/transport/graphql/auth.tsgetWebAuth(): { serverUrl, apiKey } | null-- reads from localStorage, with URL param override (?server=...&key=...)saveWebAuth(config)-- persists to localStorageclearWebAuth()-- removes from localStorage- On URL param detection: save, then clean URL with
history.replaceState
-
Create
src/renderer/src/transport/stubs/electron-only.tsnotAvailableInWeb(name)-- returns async function that logs warning and returns{ success: false }noopSubscription()-- returns() => {}cleanup functionnoopAsync()-- returnsasync () => {}
-
Create
src/renderer/src/transport/graphql/adapters/db.ts- Implement full
DbApiinterface usinggraphqlQuery() - Covers:
setting.*(get, set, delete, getAll),project.*(create, get, getByPath, getAll, update, delete, touch, reorder, sortByLastMessage),worktree.*(full CRUD + archive, touch, updateModel, appendSessionTitle, addAttachment, removeAttachment, attachPR, detachPR, setPinned, getPinned),session.*(full CRUD + getByWorktree, getByProject, getActiveByWorktree, getByConnection, search, getDraft, updateDraft),space.*(list, create, update, delete, assignProject, removeProject, getProjectIds, getAllAssignments, reorder),sessionMessage.list,sessionActivity.list - Utility methods:
schemaVersion-> existing GraphQL querydbSchemaVersion,tableExistsandgetIndexes-> stubs returning safe defaults (these are debug/utility methods) - Map GraphQL camelCase responses to snake_case DB row format expected by renderer (or handle in resolvers)
- Implement full
-
Create
src/renderer/src/transport/graphql/adapters/opencode-ops.ts- Implement
OpenCodeOpsApiinterface - Request-response:
connect,reconnect,disconnect,prompt,abort,messages,sessionInfo,setModel,models,modelInfo,undo,redo,renameSession,fork,commands,capabilities,questionReply,questionReject,planApprove,planReject,permissionReply,permissionList,command,commandApprovalReply - Critical subscription:
onStream->graphqlSubscribe('opencodeStream(sessionIds)', ...)-- this is the AI streaming backbone onQuestion,onPermissionevents -- map to appropriate GraphQL subscriptions or poll
- Implement
-
Create
src/renderer/src/transport/graphql/adapters/git-ops.ts- Implement
GitOpsApiinterface - Request-response: all staging/unstaging, commit, push, pull, merge, diff, branch operations, PR listing/state/comments, file content, branch diff, watch/unwatch
- Subscriptions:
onStatusChanged->gitStatusChanged,onBranchChanged->gitBranchChanged - Electron-only stubs:
openInEditor,showInFinder-> no-op stubs (can't launch desktop apps from browser)
- Implement
-
Create
src/renderer/src/transport/graphql/adapters/file-tree-ops.ts- Implement
FileTreeOpsApiinterface - Methods:
scan,scanFlat,loadChildren,watch,unwatch - Subscription:
onChange->fileTreeChange(worktreePath)GraphQL subscription
- Implement
-
Create
src/renderer/src/transport/index.ts(partial -- bootstrap without all adapters)installTransport(): detect mode -> if electron, return -> if web, check auth -> init client -> install core adapters onwindow.*- Returns
{ mode: 'electron' | 'web', needsAuth: boolean } - For this session, only installs the 4 core adapters; remaining are added in Session 4
detect.tscorrectly identifies Electron vs web environments- GraphQL client can make authenticated queries and subscriptions against the headless server
- The 4 core adapters pass type checking against the
Windowinterface inindex.d.ts installTransport()in web mode populateswindow.db,window.opencodeOps,window.gitOps,window.fileTreeOpswith working GraphQL adapters- In Electron mode,
installTransport()is a no-op (zero changes to existing behavior) - Manual test: loading the renderer in a browser (without Electron) and calling
window.db.project.getAll()returns data from the headless server
Goal: Implement all 13 remaining adapter modules for full feature parity.
-
Create
src/renderer/src/transport/graphql/adapters/terminal-ops.ts- PTY operations:
create,write,resize,destroy,getConfigvia GraphQL mutations/queries - Subscriptions:
onData(worktreeId)->terminalData(worktreeId),onExit(worktreeId)->terminalExit(worktreeId) - All Ghostty methods (
init,isAvailable,shutdown,createSurface,destroySurface,setFocus,pasteText,keyEvent,mouseButton,mousePos,mouseScroll,setFrame,setSize) -> stubs returning{ success: false }or no-op.isAvailablereturnsfalse.
- PTY operations:
-
Create
src/renderer/src/transport/graphql/adapters/worktree-ops.ts- Methods:
create,delete,sync,duplicate,createFromBranch,renameBranch,exists,hasCommits,getContext,updateContext - Branch methods:
getBranches-> GraphQL querygitBranches,branchExists-> GraphQL querygitBranchExists openInTerminal,openInEditor-> stubs (no OS-level process launch from browser)- Subscription:
onBranchRenamed->worktreeBranchRenamedGraphQL subscription
- Methods:
-
Create
src/renderer/src/transport/graphql/adapters/project-ops.ts- Methods:
validateProject,detectLanguage,loadLanguageIcons,findXcworkspace,isAndroidProject,getProjectIconPath,isGitRepository openDirectoryDialog-> web alternative: return empty/null (UI will show text input instead of native dialog)showInFolder,openPath-> stubs (log warning)copyToClipboard->navigator.clipboard.writeText()readFromClipboard->navigator.clipboard.readText()pickProjectIcon-> stub (web file upload handled differently)removeProjectIcon-> GraphQL mutationprojectRemoveIconinitRepository-> GraphQL mutationprojectInitRepository
- Methods:
-
Create
src/renderer/src/transport/graphql/adapters/connection-ops.ts- Methods:
create,delete,rename,get,getAll,getPinned,addMember,removeMember,removeWorktreeFromAll,setPinned openInTerminal,openInEditor-> stubs
- Methods:
-
Create
src/renderer/src/transport/graphql/adapters/system-ops.tsgetLogDir-> GraphQL querysystemLogDirgetAppVersion-> GraphQL querysystemAppVersiongetAppPaths-> GraphQL querysystemAppPathsdetectAgentSdks-> GraphQL querysystemDetectAgentSdksgetPlatform->navigator.userAgentparsing (use existingsrc/renderer/src/lib/platform.tslogic)isPackaged-> returnfalseisLogMode-> returnfalsequitApp-> no-opopenInApp-> stub for Cursor/Ghostty/Android Studio; for URLs usewindow.open()openInChrome->window.open(url, '_blank')installServerToPath,uninstallServerFromPath-> stubsonNewSessionShortcut->document.addEventListener('keydown', ...)for Cmd/Ctrl+TonCloseSessionShortcut-> keyboard listener for Cmd/Ctrl+W (withpreventDefault)onFileSearchShortcut-> keyboard listener for Cmd/Ctrl+DonWindowFocused->document.addEventListener('visibilitychange')+window.addEventListener('focus')onEditPaste-> no-op (browser handles paste natively)onNotificationNavigate-> no-op stub (no native OS notification deep-links in browser)updateMenuState,onMenuAction-> no-op stubs (no native app menu in browser)
-
Create
src/renderer/src/transport/graphql/adapters/settings-ops.tsdetectEditors-> GraphQL querydetectedEditorsdetectTerminals-> GraphQL querydetectedTerminalsopenWithEditor,openWithTerminal-> stubs (can't launch desktop apps from browser)onSettingsUpdated-> no-op subscription stub (settings changes are local in web mode)
-
Create
src/renderer/src/transport/graphql/adapters/file-ops.tsreadFile-> GraphQL queryfileReadreadImageAsBase64-> GraphQL queryfileReadImageAsBase64readPrompt-> GraphQL queryfileReadPromptwriteFile-> GraphQL mutationfileWritegetPathForFile-> returnfile.name(browser has no real path access viawebUtils)- Note: verify exact interface in
index.d.ts-- only implement methods that exist in the type definition
-
Create
src/renderer/src/transport/graphql/adapters/script-ops.ts- Methods:
runSetup,runProject,kill,runArchivevia GraphQL mutations getPort-> GraphQL queryscriptPortonOutput/offOutput->scriptOutput(worktreeId, channel)GraphQL subscription with internal tracking of active subscriptions per channel
- Methods:
-
Create
src/renderer/src/transport/graphql/adapters/logging-ops.tscreateResponseLog-> GraphQL mutationcreateResponseLogappendResponseLog-> GraphQL mutationappendResponseLog
-
Create
src/renderer/src/transport/graphql/adapters/updater-ops.ts- All methods stubbed:
check->{ available: false },download-> no-op,install-> no-op,setChannel-> no-op getVersion-> GraphQL querysystemAppVersion- All event subscriptions (
onAvailable,onProgress, etc.) ->noopSubscription()
- All methods stubbed:
-
Create
src/renderer/src/transport/graphql/adapters/usage-ops.tsfetch-> GraphQL queryusageFetchfetchOpenai-> GraphQL queryusageFetchOpenai
-
Create
src/renderer/src/transport/graphql/adapters/analytics-ops.tsisEnabled-> returnfalsesetEnabled-> no-optrack-> no-op (or POST to a lightweight endpoint if analytics desired in web mode)
-
Create
src/renderer/src/transport/graphql/adapters/kanban.ts- Full CRUD:
ticket.create,ticket.get,ticket.getByProject,ticket.update,ticket.delete,ticket.archive,ticket.archiveAllDone,ticket.unarchive,ticket.move,ticket.reorder,ticket.getBySession followup.create,followup.getByTicketsimpleMode.toggle- All via GraphQL queries/mutations
- Full CRUD:
-
Update
src/renderer/src/transport/index.ts- Import and install all 17 adapters in
installTransport()for web mode - Complete the full
window.*assignment
- Import and install all 17 adapters in
- All 17 adapter modules compile against the
Windowinterface types fromindex.d.ts installTransport()populates everywindow.*API module- Keyboard shortcuts (Cmd+T, Cmd+D, Cmd+W) work via browser event listeners in web mode
- Clipboard operations use
navigator.clipboardAPI in web mode - All Ghostty methods safely return stubs without errors
- Electron mode is completely unaffected
Goal: Create the web login experience, modify the app entry point, and set up the build pipeline.
-
Create
src/renderer/src/components/WebAuthScreen.tsx- Clean login UI using existing shadcn/ui components and Tailwind
- Fields: Server URL (defaults to
window.location.originwhen served from headless), API Key (password input) - "Connect" button: POST to
/api/auth/validate-> on success,saveWebAuth()and reload - Error states: invalid key, server unreachable, rate limited (429)
- "Disconnect" action accessible from web mode to clear auth and return to login
- Responsive layout that works on various screen sizes
-
Modify
src/renderer/src/main.tsx- Import
installTransportfrom./transport - Call
installTransport()beforeReactDOM.createRoot - Conditionally render
<WebAuthScreen />whenneedsAuthis true, otherwise render<App /> - Minimal change (3-5 lines added)
- Import
-
Create
vite.config.web.ts- Use same React and Tailwind plugins as the renderer section in
electron.vite.config.ts - Set
roottosrc/renderer - Output to
dist/web/ base: '/'- Dev server proxy configuration:
'/graphql' -> 'http://localhost:8443' (with ws: true for WebSocket) '/api' -> 'http://localhost:8443' - Resolve aliases matching existing config (
@shared,@renderer, etc.)
- Use same React and Tailwind plugins as the renderer section in
-
Update
package.json- Add
"dev:web": "vite --config vite.config.web.ts"-- starts Vite dev server with proxy - Add
"build:web": "vite build --config vite.config.web.ts"-- production build todist/web/ - Add
"build:headless": "pnpm build:web && cp -r dist/web out/web"-- copies web build alongside server output
- Add
-
Update
src/server/headless-bootstrap.ts- Auto-resolve
webRoot: checkpath.join(__dirname, '..', '..', 'out', 'web')forindex.html - Pass to
startGraphQLServer() - Console output:
"Web UI: http://{bind}:{port}/"when web root found, or"Web UI: not bundled (API-only mode)"otherwise
- Auto-resolve
pnpm dev:webstarts Vite dev server on port 5173 with hot reload- Navigating to
http://localhost:5173shows the login screen - After entering API key, the full Hive UI loads with data from the headless server
pnpm build:webproduces a production build indist/web/pnpm build:headlesscreates a complete bundle with web assets- Running
hive --headless --insecurewith bundled web assets serves the UI athttp://localhost:8443/ - Electron app (
pnpm dev) works identically to before -- no regressions
Goal: Polish the web experience by hiding Electron-only features and perform end-to-end testing.
-
Create
src/renderer/src/hooks/useIsWebMode.ts- Export
useIsWebMode()hook that returnsboolean - Backed by
detectTransportMode()fromtransport/detect.ts - Memoized -- detection only runs once
- Export
-
Conditional UI hiding -- modify components that render Electron-only features:
- "Open in Cursor" / "Open in Ghostty" / "Open in Android Studio" buttons -> hide when
isWebMode - "Show in Finder" / "Reveal in Explorer" -> change label to "Copy Path" and copy to clipboard instead
- Native file picker trigger -> show a text input with path entry instead
- Auto-updater notification/banner -> hide when
isWebMode - Ghostty terminal option in terminal selector -> hide when
isWebMode(only show xterm.js) - "Install CLI" / "Uninstall CLI" options -> hide when
isWebMode - "Quit App" menu item -> hide when
isWebMode
- "Open in Cursor" / "Open in Ghostty" / "Open in Android Studio" buttons -> hide when
-
Web-specific additions:
- Add "Disconnect" button in settings/header for web mode (calls
clearWebAuth()+ reload) - Show connection status indicator (connected to
server:port) - Handle GraphQL client disconnect/reconnect gracefully (show reconnecting UI)
- Add "Disconnect" button in settings/header for web mode (calls
-
End-to-end testing checklist:
- Projects: create, list, update, delete, reorder
- Worktrees: create, delete, sync, duplicate, archive
- Sessions: create, connect, stream AI responses, abort, rename, disconnect
- Git: view file statuses, stage/unstage, commit, push, pull, view diffs, branch switching
- Terminal: create xterm.js terminal, type commands, see output, resize, destroy
- File tree: scan directory, watch for changes, navigate files
- Kanban: create tickets, move between columns, archive
- Keyboard shortcuts: Cmd/Ctrl+T (new session), Cmd/Ctrl+D (file search), Cmd/Ctrl+W (close session)
- Clipboard: copy/paste text works
- Settings: detect editors/terminals, change settings
- Connections: create multiplexed worktree connections
- Scripts: run setup/project scripts, see output
- Auth: login, disconnect, re-login, brute force lockout after 5 attempts
- Electron regression: all above features still work in the Electron app via IPC
-
Fix any issues found during testing
- All Electron-only UI elements are hidden in web mode without affecting Electron mode
- "Disconnect" and connection status are visible in web mode
- All 14 end-to-end test scenarios pass in web mode (Chrome)
- All 14 end-to-end test scenarios pass in Electron mode (regression check)
- No TypeScript compilation errors
- No console errors in either mode
| File | Session |
|---|---|
src/server/static-handler.ts |
1 |
src/server/plugins/auth-endpoint.ts |
1 |
src/server/schema/types/kanban.graphql |
2 |
src/server/resolvers/query/kanban.resolvers.ts |
2 |
src/server/resolvers/mutation/kanban.resolvers.ts |
2 |
src/server/resolvers/query/usage.resolvers.ts |
2 |
src/renderer/src/transport/detect.ts |
3 |
src/renderer/src/transport/types.ts |
3 |
src/renderer/src/transport/index.ts |
3 |
src/renderer/src/transport/graphql/client.ts |
3 |
src/renderer/src/transport/graphql/auth.ts |
3 |
src/renderer/src/transport/stubs/electron-only.ts |
3 |
src/renderer/src/transport/graphql/adapters/db.ts |
3 |
src/renderer/src/transport/graphql/adapters/opencode-ops.ts |
3 |
src/renderer/src/transport/graphql/adapters/git-ops.ts |
3 |
src/renderer/src/transport/graphql/adapters/file-tree-ops.ts |
3 |
src/renderer/src/transport/graphql/adapters/terminal-ops.ts |
4 |
src/renderer/src/transport/graphql/adapters/worktree-ops.ts |
4 |
src/renderer/src/transport/graphql/adapters/project-ops.ts |
4 |
src/renderer/src/transport/graphql/adapters/connection-ops.ts |
4 |
src/renderer/src/transport/graphql/adapters/system-ops.ts |
4 |
src/renderer/src/transport/graphql/adapters/settings-ops.ts |
4 |
src/renderer/src/transport/graphql/adapters/file-ops.ts |
4 |
src/renderer/src/transport/graphql/adapters/script-ops.ts |
4 |
src/renderer/src/transport/graphql/adapters/logging-ops.ts |
4 |
src/renderer/src/transport/graphql/adapters/updater-ops.ts |
4 |
src/renderer/src/transport/graphql/adapters/usage-ops.ts |
4 |
src/renderer/src/transport/graphql/adapters/analytics-ops.ts |
4 |
src/renderer/src/transport/graphql/adapters/kanban.ts |
4 |
src/renderer/src/components/WebAuthScreen.tsx |
5 |
vite.config.web.ts |
5 |
src/renderer/src/hooks/useIsWebMode.ts |
6 |
| File | Session | Change |
|---|---|---|
src/server/config.ts |
1 | Add webRoot field |
src/server/index.ts |
1 | Request routing (static + auth + yoga) |
src/server/headless-bootstrap.ts |
1, 5 | Pass webRoot, log web UI status |
src/server/schema/schema.graphql |
2 | Add new Query/Mutation entries |
src/server/schema/types/project.graphql |
2 | Worktree type fields, attachment/pinning types |
src/server/schema/types/git.graphql |
2 | Branch diff, base64, PR types |
src/server/schema/types/opencode.graphql |
2 | Command approval reply mutation |
src/server/schema/types/results.graphql |
2 | New result types |
src/server/resolvers/index.ts |
2 | Merge new resolvers |
src/server/resolvers/query/git.resolvers.ts |
2 | Branch diff, PR resolvers |
src/server/resolvers/query/db.resolvers.ts |
2 | Sorting/pinning/recently-active queries |
src/server/resolvers/mutation/db.resolvers.ts |
2 | Attachment/pinning mutations |
src/renderer/src/main.tsx |
5 | 3-5 lines: import transport, conditional render |
package.json |
5 | Add dev:web, build:web, build:headless scripts |
| Various UI components | 6 | Conditional hiding with useIsWebMode() |
OpenCode SDK unavailable in headless: The headless bootstrap stubs OpenCode. Only Claude Code and Codex work in headless mode. This is an existing limitation, not introduced by this change.
Terminal write is fire-and-forget: IPC uses ipcRenderer.send (no response). The GraphQL terminalWrite mutation returns a boolean. The adapter ignores the return value.
webUtils.getPathForFile: Electron's file path utility doesn't exist in browsers. The adapter returns file.name instead. Drag-drop file upload may need a web-specific flow.
Network security: Using HTTP mode (--insecure) means traffic is unencrypted. Fine for localhost/LAN use. For remote access over the internet, add a reverse proxy (nginx/caddy) with a valid TLS cert later.