2026-03-28 09:51:58 -05:00
import { useEffect , useMemo , useState } from "react" ;
import { Link , useNavigate , useParams } from "@/lib/router" ;
import { useMutation , useQuery , useQueryClient } from "@tanstack/react-query" ;
2026-03-28 11:31:38 -05:00
import { isUuidLike , type ProjectWorkspace } from "@paperclipai/shared" ;
2026-03-28 09:51:58 -05:00
import { ArrowLeft , Check , ExternalLink , Loader2 , Sparkles } from "lucide-react" ;
import { Button } from "@/components/ui/button" ;
import { Separator } from "@/components/ui/separator" ;
import { ChoosePathButton } from "../components/PathInstructionsModal" ;
import { projectsApi } from "../api/projects" ;
[codex] Improve workspace runtime and navigation ergonomics (#3680)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - That operator experience depends not just on issue chat, but also on
how workspaces, inbox groups, and navigation state behave over
long-running sessions
> - The current branch included a separate cluster of workspace-runtime
controls, inbox grouping, sidebar ordering, and worktree lifecycle fixes
> - Those changes cross server, shared contracts, database state, and UI
navigation, but they still form one coherent operator workflow area
> - This pull request isolates the workspace/runtime and navigation
ergonomics work into one standalone branch
> - The benefit is better workspace recovery and navigation persistence
without forcing reviewers through the unrelated issue-detail/chat work
## What Changed
- Improved execution workspace and project workspace controls, request
wiring, layout, and JSON editor ergonomics
- Hardened linked worktree reuse/startup behavior and documented the
`worktree repair` flow for recovering linked worktrees safely
- Added inbox workspace grouping, mobile collapse, archive undo,
keyboard navigation, shared group-header styling, and persisted
collapsed-group behavior
- Added persistent sidebar order preferences with the supporting DB
migration, shared/server contracts, routes, services, hooks, and UI
integration
- Scoped issue-list preferences by context and added targeted UI/server
tests for workspace controls, inbox behavior, sidebar preferences, and
worktree validation
## Verification
- `pnpm vitest run
server/src/__tests__/sidebar-preferences-routes.test.ts
ui/src/pages/Inbox.test.tsx
ui/src/components/ProjectWorkspaceSummaryCard.test.tsx
ui/src/components/WorkspaceRuntimeControls.test.tsx
ui/src/api/workspace-runtime-control.test.ts`
- `server/src/__tests__/workspace-runtime.test.ts` was attempted, but
the embedded Postgres suite self-skipped/hung on this host after
reporting an init-script issue, so it is not counted as a local pass
here
## Risks
- Medium: this branch includes migration-backed preference storage plus
worktree/runtime behavior, so merge review should pay attention to state
persistence and worktree recovery semantics
- The sidebar preference migration is standalone, but it should still be
watched for conflicts if another migration lands first
## Model Used
- OpenAI Codex coding agent (GPT-5-class runtime in Codex CLI; exact
deployed model ID is not exposed in this environment), reasoning
enabled, tool use and local code execution enabled
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-14 12:57:11 -05:00
import {
buildWorkspaceRuntimeControlSections ,
WorkspaceRuntimeControls ,
type WorkspaceRuntimeControlRequest ,
} from "../components/WorkspaceRuntimeControls" ;
2026-03-28 09:51:58 -05:00
import { useBreadcrumbs } from "../context/BreadcrumbContext" ;
import { useCompany } from "../context/CompanyContext" ;
import { queryKeys } from "../lib/queryKeys" ;
import { projectRouteRef , projectWorkspaceUrl } from "../lib/utils" ;
type WorkspaceFormState = {
name : string ;
sourceType : ProjectWorkspaceSourceType ;
cwd : string ;
repoUrl : string ;
repoRef : string ;
defaultRef : string ;
visibility : ProjectWorkspaceVisibility ;
setupCommand : string ;
cleanupCommand : string ;
remoteProvider : string ;
remoteWorkspaceRef : string ;
sharedWorkspaceKey : string ;
2026-03-28 16:46:43 -05:00
runtimeConfig : string ;
2026-03-28 09:51:58 -05:00
} ;
type ProjectWorkspaceSourceType = ProjectWorkspace [ "sourceType" ] ;
type ProjectWorkspaceVisibility = ProjectWorkspace [ "visibility" ] ;
const SOURCE_TYPE_OPTIONS : Array < { value : ProjectWorkspaceSourceType ; label : string ; description : string } > = [
{ value : "local_path" , label : "Local git checkout" , description : "A local path Paperclip can use directly." } ,
{ value : "non_git_path" , label : "Local non-git path" , description : "A local folder without git semantics." } ,
{ value : "git_repo" , label : "Remote git repo" , description : "A repo URL with optional refs and local checkout." } ,
{ value : "remote_managed" , label : "Remote-managed workspace" , description : "A hosted workspace tracked by external reference." } ,
] ;
const VISIBILITY_OPTIONS : Array < { value : ProjectWorkspaceVisibility ; label : string } > = [
{ value : "default" , label : "Default" } ,
{ value : "advanced" , label : "Advanced" } ,
] ;
function isSafeExternalUrl ( value : string | null | undefined ) {
if ( ! value ) return false ;
try {
const parsed = new URL ( value ) ;
return parsed . protocol === "http:" || parsed . protocol === "https:" ;
} catch {
return false ;
}
}
function isAbsolutePath ( value : string ) {
return value . startsWith ( "/" ) || /^[A-Za-z]:[\\/]/ . test ( value ) ;
}
function readText ( value : string | null | undefined ) {
return value ? ? "" ;
}
2026-03-28 16:46:43 -05:00
function formatJson ( value : Record < string , unknown > | null | undefined ) {
if ( ! value || Object . keys ( value ) . length === 0 ) return "" ;
return JSON . stringify ( value , null , 2 ) ;
}
2026-03-28 09:51:58 -05:00
function formStateFromWorkspace ( workspace : ProjectWorkspace ) : WorkspaceFormState {
return {
name : workspace.name ,
sourceType : workspace.sourceType ,
cwd : readText ( workspace . cwd ) ,
repoUrl : readText ( workspace . repoUrl ) ,
repoRef : readText ( workspace . repoRef ) ,
defaultRef : readText ( workspace . defaultRef ) ,
visibility : workspace.visibility ,
setupCommand : readText ( workspace . setupCommand ) ,
cleanupCommand : readText ( workspace . cleanupCommand ) ,
remoteProvider : readText ( workspace . remoteProvider ) ,
remoteWorkspaceRef : readText ( workspace . remoteWorkspaceRef ) ,
sharedWorkspaceKey : readText ( workspace . sharedWorkspaceKey ) ,
2026-03-28 16:46:43 -05:00
runtimeConfig : formatJson ( workspace . runtimeConfig ? . workspaceRuntime ) ,
2026-03-28 09:51:58 -05:00
} ;
}
function normalizeText ( value : string ) {
const trimmed = value . trim ( ) ;
return trimmed . length > 0 ? trimmed : null ;
}
2026-03-28 16:46:43 -05:00
function parseRuntimeConfigJson ( value : string ) {
const trimmed = value . trim ( ) ;
if ( ! trimmed ) return { ok : true as const , value : null as Record < string , unknown > | null } ;
try {
const parsed = JSON . parse ( trimmed ) ;
if ( ! parsed || typeof parsed !== "object" || Array . isArray ( parsed ) ) {
return {
ok : false as const ,
[codex] Improve workspace runtime and navigation ergonomics (#3680)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - That operator experience depends not just on issue chat, but also on
how workspaces, inbox groups, and navigation state behave over
long-running sessions
> - The current branch included a separate cluster of workspace-runtime
controls, inbox grouping, sidebar ordering, and worktree lifecycle fixes
> - Those changes cross server, shared contracts, database state, and UI
navigation, but they still form one coherent operator workflow area
> - This pull request isolates the workspace/runtime and navigation
ergonomics work into one standalone branch
> - The benefit is better workspace recovery and navigation persistence
without forcing reviewers through the unrelated issue-detail/chat work
## What Changed
- Improved execution workspace and project workspace controls, request
wiring, layout, and JSON editor ergonomics
- Hardened linked worktree reuse/startup behavior and documented the
`worktree repair` flow for recovering linked worktrees safely
- Added inbox workspace grouping, mobile collapse, archive undo,
keyboard navigation, shared group-header styling, and persisted
collapsed-group behavior
- Added persistent sidebar order preferences with the supporting DB
migration, shared/server contracts, routes, services, hooks, and UI
integration
- Scoped issue-list preferences by context and added targeted UI/server
tests for workspace controls, inbox behavior, sidebar preferences, and
worktree validation
## Verification
- `pnpm vitest run
server/src/__tests__/sidebar-preferences-routes.test.ts
ui/src/pages/Inbox.test.tsx
ui/src/components/ProjectWorkspaceSummaryCard.test.tsx
ui/src/components/WorkspaceRuntimeControls.test.tsx
ui/src/api/workspace-runtime-control.test.ts`
- `server/src/__tests__/workspace-runtime.test.ts` was attempted, but
the embedded Postgres suite self-skipped/hung on this host after
reporting an init-script issue, so it is not counted as a local pass
here
## Risks
- Medium: this branch includes migration-backed preference storage plus
worktree/runtime behavior, so merge review should pay attention to state
persistence and worktree recovery semantics
- The sidebar preference migration is standalone, but it should still be
watched for conflicts if another migration lands first
## Model Used
- OpenAI Codex coding agent (GPT-5-class runtime in Codex CLI; exact
deployed model ID is not exposed in this environment), reasoning
enabled, tool use and local code execution enabled
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-14 12:57:11 -05:00
error : "Workspace commands JSON must be a JSON object." ,
2026-03-28 16:46:43 -05:00
} ;
}
return { ok : true as const , value : parsed as Record < string , unknown > } ;
} catch ( error ) {
return {
ok : false as const ,
error : error instanceof Error ? error . message : "Invalid JSON." ,
} ;
}
}
2026-03-28 09:51:58 -05:00
function buildWorkspacePatch ( initialState : WorkspaceFormState , nextState : WorkspaceFormState ) {
const patch : Record < string , unknown > = { } ;
const maybeAssign = ( key : keyof WorkspaceFormState , transform ? : ( value : string ) = > unknown ) = > {
const initialValue = initialState [ key ] ;
const nextValue = nextState [ key ] ;
if ( initialValue === nextValue ) return ;
patch [ key ] = transform ? transform ( nextValue ) : nextValue ;
} ;
maybeAssign ( "name" , normalizeText ) ;
maybeAssign ( "sourceType" ) ;
maybeAssign ( "cwd" , normalizeText ) ;
maybeAssign ( "repoUrl" , normalizeText ) ;
maybeAssign ( "repoRef" , normalizeText ) ;
maybeAssign ( "defaultRef" , normalizeText ) ;
maybeAssign ( "visibility" ) ;
maybeAssign ( "setupCommand" , normalizeText ) ;
maybeAssign ( "cleanupCommand" , normalizeText ) ;
maybeAssign ( "remoteProvider" , normalizeText ) ;
maybeAssign ( "remoteWorkspaceRef" , normalizeText ) ;
maybeAssign ( "sharedWorkspaceKey" , normalizeText ) ;
2026-03-28 16:46:43 -05:00
if ( initialState . runtimeConfig !== nextState . runtimeConfig ) {
const parsed = parseRuntimeConfigJson ( nextState . runtimeConfig ) ;
if ( ! parsed . ok ) throw new Error ( parsed . error ) ;
patch . runtimeConfig = {
workspaceRuntime : parsed.value ,
} ;
}
2026-03-28 09:51:58 -05:00
return patch ;
}
function validateWorkspaceForm ( form : WorkspaceFormState ) {
const cwd = normalizeText ( form . cwd ) ;
const repoUrl = normalizeText ( form . repoUrl ) ;
const remoteWorkspaceRef = normalizeText ( form . remoteWorkspaceRef ) ;
if ( form . sourceType === "remote_managed" ) {
if ( ! remoteWorkspaceRef && ! repoUrl ) {
return "Remote-managed workspaces require a remote workspace ref or repo URL." ;
}
} else if ( ! cwd && ! repoUrl ) {
return "Workspace requires at least one local path or repo URL." ;
}
if ( cwd && ( form . sourceType === "local_path" || form . sourceType === "non_git_path" ) && ! isAbsolutePath ( cwd ) ) {
return "Local workspace path must be absolute." ;
}
if ( repoUrl ) {
try {
new URL ( repoUrl ) ;
} catch {
return "Repo URL must be a valid URL." ;
}
}
2026-03-28 16:46:43 -05:00
const runtimeConfig = parseRuntimeConfigJson ( form . runtimeConfig ) ;
if ( ! runtimeConfig . ok ) {
return runtimeConfig . error ;
}
2026-03-28 09:51:58 -05:00
return null ;
}
function Field ( {
label ,
hint ,
children ,
} : {
label : string ;
hint? : string ;
children : React.ReactNode ;
} ) {
return (
< label className = "space-y-1.5" >
2026-03-28 19:28:11 -05:00
< div className = "flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between sm:gap-3" >
2026-03-28 09:51:58 -05:00
< span className = "text-xs font-medium uppercase tracking-[0.16em] text-muted-foreground" > { label } < / span >
2026-03-28 19:28:11 -05:00
{ hint ? < span className = "text-[11px] leading-relaxed text-muted-foreground sm:text-right" > { hint } < / span > : null }
2026-03-28 09:51:58 -05:00
< / div >
{ children }
< / label >
) ;
}
function DetailRow ( { label , children } : { label : string ; children : React.ReactNode } ) {
return (
2026-03-28 19:28:11 -05:00
< div className = "flex flex-col gap-1.5 py-1.5 sm:flex-row sm:items-start sm:gap-3" >
< div className = "shrink-0 text-xs text-muted-foreground sm:w-28" > { label } < / div >
2026-03-28 09:51:58 -05:00
< div className = "min-w-0 flex-1 text-sm" > { children } < / div >
< / div >
) ;
}
export function ProjectWorkspaceDetail() {
const { companyPrefix , projectId , workspaceId } = useParams < {
companyPrefix? : string ;
projectId : string ;
workspaceId : string ;
} > ( ) ;
const { companies , selectedCompanyId , setSelectedCompanyId } = useCompany ( ) ;
const { setBreadcrumbs } = useBreadcrumbs ( ) ;
const navigate = useNavigate ( ) ;
const queryClient = useQueryClient ( ) ;
const [ form , setForm ] = useState < WorkspaceFormState | null > ( null ) ;
const [ errorMessage , setErrorMessage ] = useState < string | null > ( null ) ;
2026-03-28 16:46:43 -05:00
const [ runtimeActionMessage , setRuntimeActionMessage ] = useState < string | null > ( null ) ;
2026-03-28 09:51:58 -05:00
const routeProjectRef = projectId ? ? "" ;
const routeWorkspaceId = workspaceId ? ? "" ;
const routeCompanyId = useMemo ( ( ) = > {
if ( ! companyPrefix ) return null ;
const requestedPrefix = companyPrefix . toUpperCase ( ) ;
return companies . find ( ( company ) = > company . issuePrefix . toUpperCase ( ) === requestedPrefix ) ? . id ? ? null ;
} , [ companies , companyPrefix ] ) ;
const lookupCompanyId = routeCompanyId ? ? selectedCompanyId ? ? undefined ;
2026-03-28 11:31:38 -05:00
const canFetchProject = routeProjectRef . length > 0 && ( isUuidLike ( routeProjectRef ) || Boolean ( lookupCompanyId ) ) ;
2026-03-28 09:51:58 -05:00
const projectQuery = useQuery ( {
queryKey : [ . . . queryKeys . projects . detail ( routeProjectRef ) , lookupCompanyId ? ? null ] ,
queryFn : ( ) = > projectsApi . get ( routeProjectRef , lookupCompanyId ) ,
2026-03-28 11:31:38 -05:00
enabled : canFetchProject ,
2026-03-28 09:51:58 -05:00
} ) ;
const project = projectQuery . data ? ? null ;
const workspace = useMemo (
( ) = > project ? . workspaces . find ( ( item ) = > item . id === routeWorkspaceId ) ? ? null ,
[ project , routeWorkspaceId ] ,
) ;
const canonicalProjectRef = project ? projectRouteRef ( project ) : routeProjectRef ;
const initialState = useMemo ( ( ) = > ( workspace ? formStateFromWorkspace ( workspace ) : null ) , [ workspace ] ) ;
const isDirty = Boolean ( form && initialState && JSON . stringify ( form ) !== JSON . stringify ( initialState ) ) ;
useEffect ( ( ) = > {
if ( ! project ? . companyId || project . companyId === selectedCompanyId ) return ;
setSelectedCompanyId ( project . companyId , { source : "route_sync" } ) ;
} , [ project ? . companyId , selectedCompanyId , setSelectedCompanyId ] ) ;
useEffect ( ( ) = > {
if ( ! workspace ) return ;
setForm ( formStateFromWorkspace ( workspace ) ) ;
setErrorMessage ( null ) ;
} , [ workspace ] ) ;
useEffect ( ( ) = > {
if ( ! project ) return ;
setBreadcrumbs ( [
{ label : "Projects" , href : "/projects" } ,
{ label : project.name , href : ` /projects/ ${ canonicalProjectRef } ` } ,
{ label : "Workspaces" , href : ` /projects/ ${ canonicalProjectRef } /workspaces ` } ,
{ label : workspace?.name ? ? routeWorkspaceId } ,
] ) ;
} , [ setBreadcrumbs , project , canonicalProjectRef , workspace ? . name , routeWorkspaceId ] ) ;
useEffect ( ( ) = > {
if ( ! project ) return ;
if ( routeProjectRef === canonicalProjectRef ) return ;
navigate ( projectWorkspaceUrl ( project , routeWorkspaceId ) , { replace : true } ) ;
} , [ project , routeProjectRef , canonicalProjectRef , routeWorkspaceId , navigate ] ) ;
const invalidateProject = ( ) = > {
if ( ! project ) return ;
queryClient . invalidateQueries ( { queryKey : queryKeys.projects.detail ( project . id ) } ) ;
queryClient . invalidateQueries ( { queryKey : queryKeys.projects.detail ( project . urlKey ) } ) ;
if ( lookupCompanyId ) {
queryClient . invalidateQueries ( { queryKey : queryKeys.projects.list ( lookupCompanyId ) } ) ;
}
} ;
const updateWorkspace = useMutation ( {
mutationFn : ( patch : Record < string , unknown > ) = >
projectsApi . updateWorkspace ( project ! . id , routeWorkspaceId , patch , lookupCompanyId ) ,
onSuccess : ( ) = > {
invalidateProject ( ) ;
setErrorMessage ( null ) ;
} ,
onError : ( error ) = > {
setErrorMessage ( error instanceof Error ? error . message : "Failed to save workspace." ) ;
} ,
} ) ;
const setPrimaryWorkspace = useMutation ( {
mutationFn : ( ) = > projectsApi . updateWorkspace ( project ! . id , routeWorkspaceId , { isPrimary : true } , lookupCompanyId ) ,
onSuccess : ( ) = > {
invalidateProject ( ) ;
setErrorMessage ( null ) ;
} ,
onError : ( error ) = > {
setErrorMessage ( error instanceof Error ? error . message : "Failed to update workspace." ) ;
} ,
} ) ;
2026-03-28 16:46:43 -05:00
const controlRuntimeServices = useMutation ( {
[codex] Improve workspace runtime and navigation ergonomics (#3680)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - That operator experience depends not just on issue chat, but also on
how workspaces, inbox groups, and navigation state behave over
long-running sessions
> - The current branch included a separate cluster of workspace-runtime
controls, inbox grouping, sidebar ordering, and worktree lifecycle fixes
> - Those changes cross server, shared contracts, database state, and UI
navigation, but they still form one coherent operator workflow area
> - This pull request isolates the workspace/runtime and navigation
ergonomics work into one standalone branch
> - The benefit is better workspace recovery and navigation persistence
without forcing reviewers through the unrelated issue-detail/chat work
## What Changed
- Improved execution workspace and project workspace controls, request
wiring, layout, and JSON editor ergonomics
- Hardened linked worktree reuse/startup behavior and documented the
`worktree repair` flow for recovering linked worktrees safely
- Added inbox workspace grouping, mobile collapse, archive undo,
keyboard navigation, shared group-header styling, and persisted
collapsed-group behavior
- Added persistent sidebar order preferences with the supporting DB
migration, shared/server contracts, routes, services, hooks, and UI
integration
- Scoped issue-list preferences by context and added targeted UI/server
tests for workspace controls, inbox behavior, sidebar preferences, and
worktree validation
## Verification
- `pnpm vitest run
server/src/__tests__/sidebar-preferences-routes.test.ts
ui/src/pages/Inbox.test.tsx
ui/src/components/ProjectWorkspaceSummaryCard.test.tsx
ui/src/components/WorkspaceRuntimeControls.test.tsx
ui/src/api/workspace-runtime-control.test.ts`
- `server/src/__tests__/workspace-runtime.test.ts` was attempted, but
the embedded Postgres suite self-skipped/hung on this host after
reporting an init-script issue, so it is not counted as a local pass
here
## Risks
- Medium: this branch includes migration-backed preference storage plus
worktree/runtime behavior, so merge review should pay attention to state
persistence and worktree recovery semantics
- The sidebar preference migration is standalone, but it should still be
watched for conflicts if another migration lands first
## Model Used
- OpenAI Codex coding agent (GPT-5-class runtime in Codex CLI; exact
deployed model ID is not exposed in this environment), reasoning
enabled, tool use and local code execution enabled
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-14 12:57:11 -05:00
mutationFn : ( request : WorkspaceRuntimeControlRequest ) = >
projectsApi . controlWorkspaceCommands ( project ! . id , routeWorkspaceId , request . action , lookupCompanyId , request ) ,
onSuccess : ( result , request ) = > {
2026-03-28 16:46:43 -05:00
invalidateProject ( ) ;
setErrorMessage ( null ) ;
setRuntimeActionMessage (
[codex] Improve workspace runtime and navigation ergonomics (#3680)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - That operator experience depends not just on issue chat, but also on
how workspaces, inbox groups, and navigation state behave over
long-running sessions
> - The current branch included a separate cluster of workspace-runtime
controls, inbox grouping, sidebar ordering, and worktree lifecycle fixes
> - Those changes cross server, shared contracts, database state, and UI
navigation, but they still form one coherent operator workflow area
> - This pull request isolates the workspace/runtime and navigation
ergonomics work into one standalone branch
> - The benefit is better workspace recovery and navigation persistence
without forcing reviewers through the unrelated issue-detail/chat work
## What Changed
- Improved execution workspace and project workspace controls, request
wiring, layout, and JSON editor ergonomics
- Hardened linked worktree reuse/startup behavior and documented the
`worktree repair` flow for recovering linked worktrees safely
- Added inbox workspace grouping, mobile collapse, archive undo,
keyboard navigation, shared group-header styling, and persisted
collapsed-group behavior
- Added persistent sidebar order preferences with the supporting DB
migration, shared/server contracts, routes, services, hooks, and UI
integration
- Scoped issue-list preferences by context and added targeted UI/server
tests for workspace controls, inbox behavior, sidebar preferences, and
worktree validation
## Verification
- `pnpm vitest run
server/src/__tests__/sidebar-preferences-routes.test.ts
ui/src/pages/Inbox.test.tsx
ui/src/components/ProjectWorkspaceSummaryCard.test.tsx
ui/src/components/WorkspaceRuntimeControls.test.tsx
ui/src/api/workspace-runtime-control.test.ts`
- `server/src/__tests__/workspace-runtime.test.ts` was attempted, but
the embedded Postgres suite self-skipped/hung on this host after
reporting an init-script issue, so it is not counted as a local pass
here
## Risks
- Medium: this branch includes migration-backed preference storage plus
worktree/runtime behavior, so merge review should pay attention to state
persistence and worktree recovery semantics
- The sidebar preference migration is standalone, but it should still be
watched for conflicts if another migration lands first
## Model Used
- OpenAI Codex coding agent (GPT-5-class runtime in Codex CLI; exact
deployed model ID is not exposed in this environment), reasoning
enabled, tool use and local code execution enabled
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-14 12:57:11 -05:00
request . action === "run"
? "Workspace job completed."
: request . action === "stop"
? "Workspace service stopped."
: request . action === "restart"
? "Workspace service restarted."
: "Workspace service started." ,
2026-03-28 16:46:43 -05:00
) ;
} ,
onError : ( error ) = > {
setRuntimeActionMessage ( null ) ;
[codex] Improve workspace runtime and navigation ergonomics (#3680)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - That operator experience depends not just on issue chat, but also on
how workspaces, inbox groups, and navigation state behave over
long-running sessions
> - The current branch included a separate cluster of workspace-runtime
controls, inbox grouping, sidebar ordering, and worktree lifecycle fixes
> - Those changes cross server, shared contracts, database state, and UI
navigation, but they still form one coherent operator workflow area
> - This pull request isolates the workspace/runtime and navigation
ergonomics work into one standalone branch
> - The benefit is better workspace recovery and navigation persistence
without forcing reviewers through the unrelated issue-detail/chat work
## What Changed
- Improved execution workspace and project workspace controls, request
wiring, layout, and JSON editor ergonomics
- Hardened linked worktree reuse/startup behavior and documented the
`worktree repair` flow for recovering linked worktrees safely
- Added inbox workspace grouping, mobile collapse, archive undo,
keyboard navigation, shared group-header styling, and persisted
collapsed-group behavior
- Added persistent sidebar order preferences with the supporting DB
migration, shared/server contracts, routes, services, hooks, and UI
integration
- Scoped issue-list preferences by context and added targeted UI/server
tests for workspace controls, inbox behavior, sidebar preferences, and
worktree validation
## Verification
- `pnpm vitest run
server/src/__tests__/sidebar-preferences-routes.test.ts
ui/src/pages/Inbox.test.tsx
ui/src/components/ProjectWorkspaceSummaryCard.test.tsx
ui/src/components/WorkspaceRuntimeControls.test.tsx
ui/src/api/workspace-runtime-control.test.ts`
- `server/src/__tests__/workspace-runtime.test.ts` was attempted, but
the embedded Postgres suite self-skipped/hung on this host after
reporting an init-script issue, so it is not counted as a local pass
here
## Risks
- Medium: this branch includes migration-backed preference storage plus
worktree/runtime behavior, so merge review should pay attention to state
persistence and worktree recovery semantics
- The sidebar preference migration is standalone, but it should still be
watched for conflicts if another migration lands first
## Model Used
- OpenAI Codex coding agent (GPT-5-class runtime in Codex CLI; exact
deployed model ID is not exposed in this environment), reasoning
enabled, tool use and local code execution enabled
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-14 12:57:11 -05:00
setErrorMessage ( error instanceof Error ? error . message : "Failed to control workspace commands." ) ;
2026-03-28 16:46:43 -05:00
} ,
} ) ;
2026-03-28 09:51:58 -05:00
if ( projectQuery . isLoading ) return < p className = "text-sm text-muted-foreground" > Loading workspace … < / p > ;
if ( projectQuery . error ) {
return (
< p className = "text-sm text-destructive" >
{ projectQuery . error instanceof Error ? projectQuery . error . message : "Failed to load workspace" }
< / p >
) ;
}
if ( ! project || ! workspace || ! form || ! initialState ) {
return < p className = "text-sm text-muted-foreground" > Workspace not found for this project . < / p > ;
}
[codex] Improve workspace runtime and navigation ergonomics (#3680)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - That operator experience depends not just on issue chat, but also on
how workspaces, inbox groups, and navigation state behave over
long-running sessions
> - The current branch included a separate cluster of workspace-runtime
controls, inbox grouping, sidebar ordering, and worktree lifecycle fixes
> - Those changes cross server, shared contracts, database state, and UI
navigation, but they still form one coherent operator workflow area
> - This pull request isolates the workspace/runtime and navigation
ergonomics work into one standalone branch
> - The benefit is better workspace recovery and navigation persistence
without forcing reviewers through the unrelated issue-detail/chat work
## What Changed
- Improved execution workspace and project workspace controls, request
wiring, layout, and JSON editor ergonomics
- Hardened linked worktree reuse/startup behavior and documented the
`worktree repair` flow for recovering linked worktrees safely
- Added inbox workspace grouping, mobile collapse, archive undo,
keyboard navigation, shared group-header styling, and persisted
collapsed-group behavior
- Added persistent sidebar order preferences with the supporting DB
migration, shared/server contracts, routes, services, hooks, and UI
integration
- Scoped issue-list preferences by context and added targeted UI/server
tests for workspace controls, inbox behavior, sidebar preferences, and
worktree validation
## Verification
- `pnpm vitest run
server/src/__tests__/sidebar-preferences-routes.test.ts
ui/src/pages/Inbox.test.tsx
ui/src/components/ProjectWorkspaceSummaryCard.test.tsx
ui/src/components/WorkspaceRuntimeControls.test.tsx
ui/src/api/workspace-runtime-control.test.ts`
- `server/src/__tests__/workspace-runtime.test.ts` was attempted, but
the embedded Postgres suite self-skipped/hung on this host after
reporting an init-script issue, so it is not counted as a local pass
here
## Risks
- Medium: this branch includes migration-backed preference storage plus
worktree/runtime behavior, so merge review should pay attention to state
persistence and worktree recovery semantics
- The sidebar preference migration is standalone, but it should still be
watched for conflicts if another migration lands first
## Model Used
- OpenAI Codex coding agent (GPT-5-class runtime in Codex CLI; exact
deployed model ID is not exposed in this environment), reasoning
enabled, tool use and local code execution enabled
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-14 12:57:11 -05:00
const canRunWorkspaceCommands = Boolean ( workspace . cwd ) ;
const canStartRuntimeServices = Boolean ( workspace . runtimeConfig ? . workspaceRuntime ) && canRunWorkspaceCommands ;
const runtimeControlSections = buildWorkspaceRuntimeControlSections ( {
runtimeConfig : workspace.runtimeConfig?.workspaceRuntime ? ? null ,
runtimeServices : workspace.runtimeServices ? ? [ ] ,
canStartServices : canStartRuntimeServices ,
canRunJobs : canRunWorkspaceCommands ,
} ) ;
const pendingRuntimeAction = controlRuntimeServices . isPending ? controlRuntimeServices . variables ? ? null : null ;
2026-03-28 09:51:58 -05:00
const saveChanges = ( ) = > {
const validationError = validateWorkspaceForm ( form ) ;
if ( validationError ) {
setErrorMessage ( validationError ) ;
return ;
}
const patch = buildWorkspacePatch ( initialState , form ) ;
if ( Object . keys ( patch ) . length === 0 ) return ;
updateWorkspace . mutate ( patch ) ;
} ;
const sourceTypeDescription = SOURCE_TYPE_OPTIONS . find ( ( option ) = > option . value === form . sourceType ) ? . description ? ? null ;
return (
< div className = "mx-auto max-w-5xl space-y-6" >
< div className = "flex flex-wrap items-center gap-3" >
< Button variant = "ghost" size = "sm" asChild >
< Link to = { ` /projects/ ${ canonicalProjectRef } /workspaces ` } >
< ArrowLeft className = "mr-1 h-4 w-4" / >
Back to workspaces
< / Link >
< / Button >
< div className = "inline-flex items-center rounded-full border border-border bg-background px-2.5 py-1 text-xs text-muted-foreground" >
{ workspace . isPrimary ? "Primary workspace" : "Secondary workspace" }
< / div >
< / div >
< div className = "grid gap-6 lg:grid-cols-[minmax(0,1.4fr)_minmax(18rem,0.9fr)]" >
< div className = "space-y-6" >
< div className = "rounded-2xl border border-border bg-card p-5" >
2026-03-28 19:28:11 -05:00
< div className = "flex flex-col gap-4 sm:flex-row sm:flex-wrap sm:items-start sm:justify-between" >
2026-03-28 09:51:58 -05:00
< div className = "space-y-2" >
< div className = "text-xs font-medium uppercase tracking-[0.16em] text-muted-foreground" >
Project workspace
< / div >
< h1 className = "text-2xl font-semibold" > { workspace . name } < / h1 >
< p className = "max-w-2xl text-sm text-muted-foreground" >
Configure the concrete workspace Paperclip attaches to this project . These values drive per - workspace
2026-03-28 16:46:43 -05:00
checkout behavior , default runtime services for child execution workspaces , and let you override setup
or cleanup commands when one workspace needs special handling .
2026-03-28 09:51:58 -05:00
< / p >
< / div >
{ ! workspace . isPrimary ? (
< Button
variant = "outline"
2026-03-28 19:28:11 -05:00
className = "w-full sm:w-auto"
2026-03-28 09:51:58 -05:00
disabled = { setPrimaryWorkspace . isPending }
onClick = { ( ) = > setPrimaryWorkspace . mutate ( ) }
>
{ setPrimaryWorkspace . isPending
? < Loader2 className = "mr-2 h-4 w-4 animate-spin" / >
: < Check className = "mr-2 h-4 w-4" / > }
Make primary
< / Button >
) : (
2026-03-28 19:28:11 -05:00
< div className = "inline-flex items-center gap-2 rounded-xl border border-emerald-500/25 bg-emerald-500/10 px-3 py-2 text-sm text-emerald-700 dark:text-emerald-300 sm:max-w-sm" >
2026-03-28 09:51:58 -05:00
< Sparkles className = "h-4 w-4" / >
This is the project ’ s primary codebase workspace .
< / div >
) }
< / div >
< Separator className = "my-5" / >
< div className = "grid gap-4 md:grid-cols-2" >
< Field label = "Workspace name" >
< input
className = "w-full rounded-lg border border-border bg-background px-3 py-2 text-sm outline-none"
value = { form . name }
onChange = { ( event ) = > setForm ( ( current ) = > current ? { . . . current , name : event.target.value } : current ) }
placeholder = "Workspace name"
/ >
< / Field >
< Field label = "Visibility" >
< select
className = "w-full rounded-lg border border-border bg-background px-3 py-2 text-sm outline-none"
value = { form . visibility }
onChange = { ( event ) = >
setForm ( ( current ) = > current ? { . . . current , visibility : event.target.value as ProjectWorkspaceVisibility } : current )
}
>
{ VISIBILITY_OPTIONS . map ( ( option ) = > (
< option key = { option . value } value = { option . value } > { option . label } < / option >
) ) }
< / select >
< / Field >
< / div >
< div className = "mt-4 grid gap-4" >
< Field label = "Source type" hint = { sourceTypeDescription ? ? undefined } >
< select
className = "w-full rounded-lg border border-border bg-background px-3 py-2 text-sm outline-none"
value = { form . sourceType }
onChange = { ( event ) = >
setForm ( ( current ) = > current ? { . . . current , sourceType : event.target.value as ProjectWorkspaceSourceType } : current )
}
>
{ SOURCE_TYPE_OPTIONS . map ( ( option ) = > (
< option key = { option . value } value = { option . value } > { option . label } < / option >
) ) }
< / select >
< / Field >
< div className = "grid gap-4 md:grid-cols-[minmax(0,1fr)_auto]" >
< Field label = "Local path" >
< input
className = "w-full rounded-lg border border-border bg-background px-3 py-2 font-mono text-sm outline-none"
value = { form . cwd }
onChange = { ( event ) = > setForm ( ( current ) = > current ? { . . . current , cwd : event.target.value } : current ) }
placeholder = "/absolute/path/to/workspace"
/ >
< / Field >
< div className = "flex items-end" >
< ChoosePathButton / >
< / div >
< / div >
< div className = "grid gap-4 md:grid-cols-2" >
< Field label = "Repo URL" >
< input
className = "w-full rounded-lg border border-border bg-background px-3 py-2 text-sm outline-none"
value = { form . repoUrl }
onChange = { ( event ) = > setForm ( ( current ) = > current ? { . . . current , repoUrl : event.target.value } : current ) }
placeholder = "https://github.com/org/repo"
/ >
< / Field >
< Field label = "Repo ref" >
< input
className = "w-full rounded-lg border border-border bg-background px-3 py-2 font-mono text-sm outline-none"
value = { form . repoRef }
onChange = { ( event ) = > setForm ( ( current ) = > current ? { . . . current , repoRef : event.target.value } : current ) }
placeholder = "origin/main"
/ >
< / Field >
< / div >
< div className = "grid gap-4 md:grid-cols-2" >
< Field label = "Default ref" >
< input
className = "w-full rounded-lg border border-border bg-background px-3 py-2 font-mono text-sm outline-none"
value = { form . defaultRef }
onChange = { ( event ) = > setForm ( ( current ) = > current ? { . . . current , defaultRef : event.target.value } : current ) }
placeholder = "origin/main"
/ >
< / Field >
< Field label = "Shared workspace key" >
< input
className = "w-full rounded-lg border border-border bg-background px-3 py-2 font-mono text-sm outline-none"
value = { form . sharedWorkspaceKey }
onChange = { ( event ) = > setForm ( ( current ) = > current ? { . . . current , sharedWorkspaceKey : event.target.value } : current ) }
placeholder = "frontend"
/ >
< / Field >
< / div >
< div className = "grid gap-4 md:grid-cols-2" >
< Field label = "Remote provider" >
< input
className = "w-full rounded-lg border border-border bg-background px-3 py-2 text-sm outline-none"
value = { form . remoteProvider }
onChange = { ( event ) = > setForm ( ( current ) = > current ? { . . . current , remoteProvider : event.target.value } : current ) }
placeholder = "codespaces"
/ >
< / Field >
< Field label = "Remote workspace ref" >
< input
className = "w-full rounded-lg border border-border bg-background px-3 py-2 font-mono text-sm outline-none"
value = { form . remoteWorkspaceRef }
onChange = { ( event ) = > setForm ( ( current ) = > current ? { . . . current , remoteWorkspaceRef : event.target.value } : current ) }
placeholder = "workspace-123"
/ >
< / Field >
< / div >
< div className = "grid gap-4 md:grid-cols-2" >
< Field label = "Setup command" hint = "Runs when this workspace needs custom bootstrap" >
< textarea
className = "min-h-28 w-full rounded-lg border border-border bg-background px-3 py-2 font-mono text-sm outline-none"
value = { form . setupCommand }
onChange = { ( event ) = > setForm ( ( current ) = > current ? { . . . current , setupCommand : event.target.value } : current ) }
placeholder = "pnpm install && pnpm dev"
/ >
< / Field >
< Field label = "Cleanup command" hint = "Runs before project-level execution workspace teardown" >
< textarea
className = "min-h-28 w-full rounded-lg border border-border bg-background px-3 py-2 font-mono text-sm outline-none"
value = { form . cleanupCommand }
onChange = { ( event ) = > setForm ( ( current ) = > current ? { . . . current , cleanupCommand : event.target.value } : current ) }
placeholder = "pkill -f vite || true"
/ >
< / Field >
< / div >
2026-03-28 16:46:43 -05:00
[codex] Improve workspace runtime and navigation ergonomics (#3680)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - That operator experience depends not just on issue chat, but also on
how workspaces, inbox groups, and navigation state behave over
long-running sessions
> - The current branch included a separate cluster of workspace-runtime
controls, inbox grouping, sidebar ordering, and worktree lifecycle fixes
> - Those changes cross server, shared contracts, database state, and UI
navigation, but they still form one coherent operator workflow area
> - This pull request isolates the workspace/runtime and navigation
ergonomics work into one standalone branch
> - The benefit is better workspace recovery and navigation persistence
without forcing reviewers through the unrelated issue-detail/chat work
## What Changed
- Improved execution workspace and project workspace controls, request
wiring, layout, and JSON editor ergonomics
- Hardened linked worktree reuse/startup behavior and documented the
`worktree repair` flow for recovering linked worktrees safely
- Added inbox workspace grouping, mobile collapse, archive undo,
keyboard navigation, shared group-header styling, and persisted
collapsed-group behavior
- Added persistent sidebar order preferences with the supporting DB
migration, shared/server contracts, routes, services, hooks, and UI
integration
- Scoped issue-list preferences by context and added targeted UI/server
tests for workspace controls, inbox behavior, sidebar preferences, and
worktree validation
## Verification
- `pnpm vitest run
server/src/__tests__/sidebar-preferences-routes.test.ts
ui/src/pages/Inbox.test.tsx
ui/src/components/ProjectWorkspaceSummaryCard.test.tsx
ui/src/components/WorkspaceRuntimeControls.test.tsx
ui/src/api/workspace-runtime-control.test.ts`
- `server/src/__tests__/workspace-runtime.test.ts` was attempted, but
the embedded Postgres suite self-skipped/hung on this host after
reporting an init-script issue, so it is not counted as a local pass
here
## Risks
- Medium: this branch includes migration-backed preference storage plus
worktree/runtime behavior, so merge review should pay attention to state
persistence and worktree recovery semantics
- The sidebar preference migration is standalone, but it should still be
watched for conflicts if another migration lands first
## Model Used
- OpenAI Codex coding agent (GPT-5-class runtime in Codex CLI; exact
deployed model ID is not exposed in this environment), reasoning
enabled, tool use and local code execution enabled
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-14 12:57:11 -05:00
< details className = "rounded-xl border border-dashed border-border/70 bg-muted/20 px-3 py-3" >
< summary className = "cursor-pointer text-sm font-medium" > Advanced runtime JSON < / summary >
< p className = "mt-2 text-sm text-muted-foreground" >
Paperclip derives Services and Jobs from this JSON . Prefer editing named commands first ; use raw JSON for advanced lifecycle , port , readiness , or environment settings .
< / p >
< div className = "mt-3" >
< Field label = "Workspace commands JSON" hint = "Execution workspaces inherit this config unless they override it. Legacy `services` arrays still work, but `commands` supports both services and jobs." >
< textarea
className = "min-h-96 w-full rounded-lg border border-border bg-background px-3 py-2 font-mono text-sm outline-none"
value = { form . runtimeConfig }
onChange = { ( event ) = > setForm ( ( current ) = > current ? { . . . current , runtimeConfig : event.target.value } : current ) }
placeholder = { "{\n \"commands\": [\n {\n \"id\": \"web\",\n \"name\": \"web\",\n \"kind\": \"service\",\n \"command\": \"pnpm dev\",\n \"cwd\": \".\",\n \"port\": { \"type\": \"auto\" },\n \"readiness\": {\n \"type\": \"http\",\n \"urlTemplate\": \"http://127.0.0.1:${port}\"\n },\n \"expose\": {\n \"type\": \"url\",\n \"urlTemplate\": \"http://127.0.0.1:${port}\"\n },\n \"lifecycle\": \"shared\",\n \"reuseScope\": \"project_workspace\"\n },\n {\n \"id\": \"db-migrate\",\n \"name\": \"db:migrate\",\n \"kind\": \"job\",\n \"command\": \"pnpm db:migrate\",\n \"cwd\": \".\"\n }\n ]\n}" }
/ >
< / Field >
< / div >
< / details >
2026-03-28 09:51:58 -05:00
< / div >
2026-03-28 19:28:11 -05:00
< div className = "mt-5 flex flex-col items-stretch gap-3 sm:flex-row sm:flex-wrap sm:items-center" >
< Button className = "w-full sm:w-auto" disabled = { ! isDirty || updateWorkspace . isPending } onClick = { saveChanges } >
2026-03-28 09:51:58 -05:00
{ updateWorkspace . isPending ? < Loader2 className = "mr-2 h-4 w-4 animate-spin" / > : null }
Save changes
< / Button >
< Button
variant = "outline"
2026-03-28 19:28:11 -05:00
className = "w-full sm:w-auto"
2026-03-28 09:51:58 -05:00
disabled = { ! isDirty || updateWorkspace . isPending }
onClick = { ( ) = > {
setForm ( initialState ) ;
setErrorMessage ( null ) ;
} }
>
Reset
< / Button >
{ errorMessage ? < p className = "text-sm text-destructive" > { errorMessage } < / p > : null }
2026-03-28 16:46:43 -05:00
{ ! errorMessage && runtimeActionMessage ? < p className = "text-sm text-muted-foreground" > { runtimeActionMessage } < / p > : null }
2026-03-28 09:51:58 -05:00
{ ! errorMessage && ! isDirty ? < p className = "text-sm text-muted-foreground" > No unsaved changes . < / p > : null }
< / div >
< / div >
< / div >
< div className = "space-y-6" >
< div className = "rounded-2xl border border-border bg-card p-5" >
< div className = "space-y-1" >
< div className = "text-xs font-medium uppercase tracking-[0.16em] text-muted-foreground" > Workspace facts < / div >
< h2 className = "text-lg font-semibold" > Current state < / h2 >
< / div >
< Separator className = "my-4" / >
< DetailRow label = "Project" >
< Link to = { ` /projects/ ${ canonicalProjectRef } ` } className = "hover:underline" > { project . name } < / Link >
< / DetailRow >
< DetailRow label = "Workspace ID" >
< span className = "break-all font-mono text-xs" > { workspace . id } < / span >
< / DetailRow >
< DetailRow label = "Local path" >
< span className = "break-all font-mono text-xs" > { workspace . cwd ? ? "None" } < / span >
< / DetailRow >
< DetailRow label = "Repo" >
{ workspace . repoUrl && isSafeExternalUrl ( workspace . repoUrl ) ? (
< a href = { workspace . repoUrl } target = "_blank" rel = "noreferrer" className = "inline-flex items-center gap-1 hover:underline" >
{ workspace . repoUrl }
< ExternalLink className = "h-3 w-3" / >
< / a >
) : workspace . repoUrl ? (
< span className = "break-all font-mono text-xs" > { workspace . repoUrl } < / span >
) : "None" }
< / DetailRow >
< DetailRow label = "Default ref" > { workspace . defaultRef ? ? "None" } < / DetailRow >
< DetailRow label = "Updated" > { new Date ( workspace . updatedAt ) . toLocaleString ( ) } < / DetailRow >
< / div >
< div className = "rounded-2xl border border-border bg-card p-5" >
2026-03-28 19:28:11 -05:00
< div className = "flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between" >
2026-03-28 16:46:43 -05:00
< div className = "space-y-1" >
[codex] Improve workspace runtime and navigation ergonomics (#3680)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - That operator experience depends not just on issue chat, but also on
how workspaces, inbox groups, and navigation state behave over
long-running sessions
> - The current branch included a separate cluster of workspace-runtime
controls, inbox grouping, sidebar ordering, and worktree lifecycle fixes
> - Those changes cross server, shared contracts, database state, and UI
navigation, but they still form one coherent operator workflow area
> - This pull request isolates the workspace/runtime and navigation
ergonomics work into one standalone branch
> - The benefit is better workspace recovery and navigation persistence
without forcing reviewers through the unrelated issue-detail/chat work
## What Changed
- Improved execution workspace and project workspace controls, request
wiring, layout, and JSON editor ergonomics
- Hardened linked worktree reuse/startup behavior and documented the
`worktree repair` flow for recovering linked worktrees safely
- Added inbox workspace grouping, mobile collapse, archive undo,
keyboard navigation, shared group-header styling, and persisted
collapsed-group behavior
- Added persistent sidebar order preferences with the supporting DB
migration, shared/server contracts, routes, services, hooks, and UI
integration
- Scoped issue-list preferences by context and added targeted UI/server
tests for workspace controls, inbox behavior, sidebar preferences, and
worktree validation
## Verification
- `pnpm vitest run
server/src/__tests__/sidebar-preferences-routes.test.ts
ui/src/pages/Inbox.test.tsx
ui/src/components/ProjectWorkspaceSummaryCard.test.tsx
ui/src/components/WorkspaceRuntimeControls.test.tsx
ui/src/api/workspace-runtime-control.test.ts`
- `server/src/__tests__/workspace-runtime.test.ts` was attempted, but
the embedded Postgres suite self-skipped/hung on this host after
reporting an init-script issue, so it is not counted as a local pass
here
## Risks
- Medium: this branch includes migration-backed preference storage plus
worktree/runtime behavior, so merge review should pay attention to state
persistence and worktree recovery semantics
- The sidebar preference migration is standalone, but it should still be
watched for conflicts if another migration lands first
## Model Used
- OpenAI Codex coding agent (GPT-5-class runtime in Codex CLI; exact
deployed model ID is not exposed in this environment), reasoning
enabled, tool use and local code execution enabled
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-14 12:57:11 -05:00
< div className = "text-xs font-medium uppercase tracking-[0.16em] text-muted-foreground" > Workspace commands < / div >
< h2 className = "text-lg font-semibold" > Services and jobs < / h2 >
2026-03-28 16:46:43 -05:00
< p className = "text-sm text-muted-foreground" >
[codex] Improve workspace runtime and navigation ergonomics (#3680)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - That operator experience depends not just on issue chat, but also on
how workspaces, inbox groups, and navigation state behave over
long-running sessions
> - The current branch included a separate cluster of workspace-runtime
controls, inbox grouping, sidebar ordering, and worktree lifecycle fixes
> - Those changes cross server, shared contracts, database state, and UI
navigation, but they still form one coherent operator workflow area
> - This pull request isolates the workspace/runtime and navigation
ergonomics work into one standalone branch
> - The benefit is better workspace recovery and navigation persistence
without forcing reviewers through the unrelated issue-detail/chat work
## What Changed
- Improved execution workspace and project workspace controls, request
wiring, layout, and JSON editor ergonomics
- Hardened linked worktree reuse/startup behavior and documented the
`worktree repair` flow for recovering linked worktrees safely
- Added inbox workspace grouping, mobile collapse, archive undo,
keyboard navigation, shared group-header styling, and persisted
collapsed-group behavior
- Added persistent sidebar order preferences with the supporting DB
migration, shared/server contracts, routes, services, hooks, and UI
integration
- Scoped issue-list preferences by context and added targeted UI/server
tests for workspace controls, inbox behavior, sidebar preferences, and
worktree validation
## Verification
- `pnpm vitest run
server/src/__tests__/sidebar-preferences-routes.test.ts
ui/src/pages/Inbox.test.tsx
ui/src/components/ProjectWorkspaceSummaryCard.test.tsx
ui/src/components/WorkspaceRuntimeControls.test.tsx
ui/src/api/workspace-runtime-control.test.ts`
- `server/src/__tests__/workspace-runtime.test.ts` was attempted, but
the embedded Postgres suite self-skipped/hung on this host after
reporting an init-script issue, so it is not counted as a local pass
here
## Risks
- Medium: this branch includes migration-backed preference storage plus
worktree/runtime behavior, so merge review should pay attention to state
persistence and worktree recovery semantics
- The sidebar preference migration is standalone, but it should still be
watched for conflicts if another migration lands first
## Model Used
- OpenAI Codex coding agent (GPT-5-class runtime in Codex CLI; exact
deployed model ID is not exposed in this environment), reasoning
enabled, tool use and local code execution enabled
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-14 12:57:11 -05:00
Long - running services stay supervised here , while one - shot jobs run on demand against this workspace . Execution workspaces inherit this config unless they override it .
2026-03-28 16:46:43 -05:00
< / p >
< / div >
2026-03-28 09:51:58 -05:00
< / div >
[codex] Improve workspace runtime and navigation ergonomics (#3680)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - That operator experience depends not just on issue chat, but also on
how workspaces, inbox groups, and navigation state behave over
long-running sessions
> - The current branch included a separate cluster of workspace-runtime
controls, inbox grouping, sidebar ordering, and worktree lifecycle fixes
> - Those changes cross server, shared contracts, database state, and UI
navigation, but they still form one coherent operator workflow area
> - This pull request isolates the workspace/runtime and navigation
ergonomics work into one standalone branch
> - The benefit is better workspace recovery and navigation persistence
without forcing reviewers through the unrelated issue-detail/chat work
## What Changed
- Improved execution workspace and project workspace controls, request
wiring, layout, and JSON editor ergonomics
- Hardened linked worktree reuse/startup behavior and documented the
`worktree repair` flow for recovering linked worktrees safely
- Added inbox workspace grouping, mobile collapse, archive undo,
keyboard navigation, shared group-header styling, and persisted
collapsed-group behavior
- Added persistent sidebar order preferences with the supporting DB
migration, shared/server contracts, routes, services, hooks, and UI
integration
- Scoped issue-list preferences by context and added targeted UI/server
tests for workspace controls, inbox behavior, sidebar preferences, and
worktree validation
## Verification
- `pnpm vitest run
server/src/__tests__/sidebar-preferences-routes.test.ts
ui/src/pages/Inbox.test.tsx
ui/src/components/ProjectWorkspaceSummaryCard.test.tsx
ui/src/components/WorkspaceRuntimeControls.test.tsx
ui/src/api/workspace-runtime-control.test.ts`
- `server/src/__tests__/workspace-runtime.test.ts` was attempted, but
the embedded Postgres suite self-skipped/hung on this host after
reporting an init-script issue, so it is not counted as a local pass
here
## Risks
- Medium: this branch includes migration-backed preference storage plus
worktree/runtime behavior, so merge review should pay attention to state
persistence and worktree recovery semantics
- The sidebar preference migration is standalone, but it should still be
watched for conflicts if another migration lands first
## Model Used
- OpenAI Codex coding agent (GPT-5-class runtime in Codex CLI; exact
deployed model ID is not exposed in this environment), reasoning
enabled, tool use and local code execution enabled
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-14 12:57:11 -05:00
< WorkspaceRuntimeControls
className = "mt-4"
sections = { runtimeControlSections }
isPending = { controlRuntimeServices . isPending }
pendingRequest = { pendingRuntimeAction }
serviceEmptyMessage = {
workspace . runtimeConfig ? . workspaceRuntime
? "No services have been started for this workspace yet."
: "No workspace command config is defined for this workspace yet."
}
jobEmptyMessage = "No one-shot jobs are configured for this workspace yet."
disabledHint = "Project workspaces need a working directory before local commands can run, and services also need runtime config."
onAction = { ( request ) = > controlRuntimeServices . mutate ( request ) }
/ >
2026-03-28 09:51:58 -05:00
< / div >
< / div >
< / div >
< / div >
) ;
}