2026-03-10 10:04:08 -05:00
|
|
|
import { useCallback, useEffect, useMemo, useState, useRef } from "react";
|
2026-03-26 13:00:25 -05:00
|
|
|
import { Link, useParams, useNavigate, useLocation, Navigate } from "@/lib/router";
|
2026-02-20 10:32:32 -06:00
|
|
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
[codex] Improve workspace navigation and runtime UI (#4089)
## Thinking Path
> - Paperclip agents do real work in project and execution workspaces.
> - Operators need workspace state to be visible, navigable, and
copyable without digging through raw run logs.
> - The branch included related workspace cards, navigation, runtime
controls, stale-service handling, and issue-property visibility.
> - These changes share the workspace UI and runtime-control surfaces
and can stand alone from unrelated access/profile work.
> - This pull request groups the workspace experience changes into one
standalone branch.
> - The benefit is a clearer workspace overview, better metadata copy
flows, and more accurate runtime service controls.
## What Changed
- Polished project workspace summary cards and made workspace metadata
copyable.
- Added a workspace navigation overview and extracted reusable project
workspace content.
- Squared and polished the execution workspace configuration page.
- Fixed stale workspace command matching and hid stopped stale services
in runtime controls.
- Showed live workspace service context in issue properties.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run
ui/src/components/ProjectWorkspaceSummaryCard.test.tsx
ui/src/lib/project-workspaces-tab.test.ts
ui/src/components/Sidebar.test.tsx
ui/src/components/WorkspaceRuntimeControls.test.tsx
ui/src/components/IssueProperties.test.tsx`
- `pnpm exec vitest run packages/shared/src/workspace-commands.test.ts
--config /dev/null` because the root Vitest project config does not
currently include `packages/shared` tests.
- Split integration check: merged after runtime/governance,
dev-infra/backups, and access/profiles with no merge conflicts.
- Confirmed this branch does not include `pnpm-lock.yaml`.
## Risks
- Medium risk: touches workspace navigation, runtime controls, and issue
property rendering.
- Visual layout changes may need browser QA, especially around smaller
screens and dense workspace metadata.
- No database migrations are included.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5.4 tool-enabled coding model, agentic
code-editing/runtime with local shell and GitHub CLI access; exact
context window and reasoning mode are not exposed by the Paperclip
harness.
## 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)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] 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-20 06:14:32 -05:00
|
|
|
import { PROJECT_COLORS, isUuidLike, type BudgetPolicySummary } from "@paperclipai/shared";
|
2026-03-14 22:00:12 -05:00
|
|
|
import { budgetsApi } from "../api/budgets";
|
2026-03-26 13:00:25 -05:00
|
|
|
import { executionWorkspacesApi } from "../api/execution-workspaces";
|
|
|
|
|
import { instanceSettingsApi } from "../api/instanceSettings";
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
import { projectsApi } from "../api/projects";
|
|
|
|
|
import { issuesApi } from "../api/issues";
|
2026-02-23 09:56:31 -06:00
|
|
|
import { agentsApi } from "../api/agents";
|
|
|
|
|
import { heartbeatsApi } from "../api/heartbeats";
|
Add MarkdownEditor component, asset image upload, and rich description editing
Introduce MarkdownEditor built on @mdxeditor/editor with headings,
lists, links, quotes, image upload with drag-and-drop, and themed CSS
integration. Add asset image upload API (routes, service, storage) and
wire image upload into InlineEditor multiline mode, NewIssueDialog,
NewProjectDialog, GoalDetail, IssueDetail, and ProjectDetail
description fields. Tighten prompt template editor styling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:50:45 -06:00
|
|
|
import { assetsApi } from "../api/assets";
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
import { usePanel } from "../context/PanelContext";
|
|
|
|
|
import { useCompany } from "../context/CompanyContext";
|
[codex] Harden execution reliability and heartbeat tooling (#3679)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Reliable execution depends on heartbeat routing, issue lifecycle
semantics, telemetry, and a fast enough local verification loop to keep
regressions visible
> - The remaining commits on this branch were mostly server/runtime
correctness fixes plus test and documentation follow-ups in that area
> - Those changes are logically separate from the UI-focused
issue-detail and workspace/navigation branches even when they touch
overlapping issue APIs
> - This pull request groups the execution reliability, heartbeat,
telemetry, and tooling changes into one standalone branch
> - The benefit is a focused review of the control-plane correctness
work, including the follow-up fix that restored the implicit
comment-reopen helpers after branch splitting
## What Changed
- Hardened issue/heartbeat execution behavior, including self-review
stage skipping, deferred mention wakes during active execution, stranded
execution recovery, active-run scoping, assignee resolution, and
blocked-to-todo wake resumption
- Reduced noisy polling/logging overhead by trimming issue run payloads,
compacting persisted run logs, silencing high-volume request logs, and
capping heartbeat-run queries in dashboard/inbox surfaces
- Expanded telemetry and status semantics with adapter/model fields on
task completion plus clearer status guidance in docs/onboarding material
- Updated test infrastructure and verification defaults with faster
route-test module isolation, cheaper default `pnpm test`, e2e isolation
from local state, and repo verification follow-ups
- Included docs/release housekeeping from the branch and added a small
follow-up commit restoring the implicit comment-reopen helpers that were
dropped during branch reconstruction
## Verification
- `pnpm vitest run
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/__tests__/issue-telemetry-routes.test.ts`
- `pnpm vitest run server/src/__tests__/http-log-policy.test.ts
server/src/__tests__/heartbeat-run-log.test.ts
server/src/__tests__/health.test.ts`
- `server/src/__tests__/activity-service.test.ts`,
`server/src/__tests__/heartbeat-comment-wake-batching.test.ts`, and
`server/src/__tests__/heartbeat-process-recovery.test.ts` were attempted
on this host but the embedded Postgres harness reported
init-script/data-dir problems and skipped or failed to start, so they
are noted as environment-limited
## Risks
- Medium: this branch changes core issue/heartbeat routing and
reopen/wakeup behavior, so regressions would affect agent execution flow
rather than isolated UI polish
- Because it also updates verification infrastructure, reviewers should
pay attention to whether the new tests are asserting the right failure
modes and not just reshaping harness behavior
## 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 13:34:52 -05:00
|
|
|
import { useToastActions } from "../context/ToastContext";
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
import { useBreadcrumbs } from "../context/BreadcrumbContext";
|
2026-02-17 12:24:48 -06:00
|
|
|
import { queryKeys } from "../lib/queryKeys";
|
2026-03-10 10:04:08 -05:00
|
|
|
import { ProjectProperties, type ProjectConfigFieldKey, type ProjectFieldSaveState } from "../components/ProjectProperties";
|
2026-02-20 10:32:32 -06:00
|
|
|
import { InlineEditor } from "../components/InlineEditor";
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
import { StatusBadge } from "../components/StatusBadge";
|
2026-03-14 22:00:12 -05:00
|
|
|
import { BudgetPolicyCard } from "../components/BudgetPolicyCard";
|
2026-02-23 09:56:31 -06:00
|
|
|
import { IssuesList } from "../components/IssuesList";
|
2026-03-02 16:44:03 -06:00
|
|
|
import { PageSkeleton } from "../components/PageSkeleton";
|
2026-03-10 09:08:20 -05:00
|
|
|
import { PageTabBar } from "../components/PageTabBar";
|
[codex] Improve workspace navigation and runtime UI (#4089)
## Thinking Path
> - Paperclip agents do real work in project and execution workspaces.
> - Operators need workspace state to be visible, navigable, and
copyable without digging through raw run logs.
> - The branch included related workspace cards, navigation, runtime
controls, stale-service handling, and issue-property visibility.
> - These changes share the workspace UI and runtime-control surfaces
and can stand alone from unrelated access/profile work.
> - This pull request groups the workspace experience changes into one
standalone branch.
> - The benefit is a clearer workspace overview, better metadata copy
flows, and more accurate runtime service controls.
## What Changed
- Polished project workspace summary cards and made workspace metadata
copyable.
- Added a workspace navigation overview and extracted reusable project
workspace content.
- Squared and polished the execution workspace configuration page.
- Fixed stale workspace command matching and hid stopped stale services
in runtime controls.
- Showed live workspace service context in issue properties.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run
ui/src/components/ProjectWorkspaceSummaryCard.test.tsx
ui/src/lib/project-workspaces-tab.test.ts
ui/src/components/Sidebar.test.tsx
ui/src/components/WorkspaceRuntimeControls.test.tsx
ui/src/components/IssueProperties.test.tsx`
- `pnpm exec vitest run packages/shared/src/workspace-commands.test.ts
--config /dev/null` because the root Vitest project config does not
currently include `packages/shared` tests.
- Split integration check: merged after runtime/governance,
dev-infra/backups, and access/profiles with no merge conflicts.
- Confirmed this branch does not include `pnpm-lock.yaml`.
## Risks
- Medium risk: touches workspace navigation, runtime controls, and issue
property rendering.
- Visual layout changes may need browser QA, especially around smaller
screens and dense workspace metadata.
- No database migrations are included.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex, GPT-5.4 tool-enabled coding model, agentic
code-editing/runtime with local shell and GitHub CLI access; exact
context window and reasoning mode are not exposed by the Paperclip
harness.
## 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)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] 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-20 06:14:32 -05:00
|
|
|
import { ProjectWorkspacesContent } from "../components/ProjectWorkspacesContent";
|
2026-03-26 13:00:25 -05:00
|
|
|
import { buildProjectWorkspaceSummaries } from "../lib/project-workspaces-tab";
|
[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 { projectRouteRef } from "../lib/utils";
|
2026-03-28 16:15:20 -05:00
|
|
|
import { Button } from "@/components/ui/button";
|
2026-03-10 09:08:20 -05:00
|
|
|
import { Tabs } from "@/components/ui/tabs";
|
2026-03-13 23:03:51 -05:00
|
|
|
import { PluginLauncherOutlet } from "@/plugins/launchers";
|
|
|
|
|
import { PluginSlotMount, PluginSlotOutlet, usePluginSlots } from "@/plugins/slots";
|
2026-02-23 09:56:31 -06:00
|
|
|
|
|
|
|
|
/* ── Top-level tab types ── */
|
|
|
|
|
|
2026-03-26 13:00:25 -05:00
|
|
|
type ProjectBaseTab = "overview" | "list" | "workspaces" | "configuration" | "budget";
|
2026-03-13 16:22:34 -05:00
|
|
|
type ProjectPluginTab = `plugin:${string}`;
|
|
|
|
|
type ProjectTab = ProjectBaseTab | ProjectPluginTab;
|
|
|
|
|
|
|
|
|
|
function isProjectPluginTab(value: string | null): value is ProjectPluginTab {
|
|
|
|
|
return typeof value === "string" && value.startsWith("plugin:");
|
|
|
|
|
}
|
2026-02-23 09:56:31 -06:00
|
|
|
|
|
|
|
|
function resolveProjectTab(pathname: string, projectId: string): ProjectTab | null {
|
2026-03-02 16:44:03 -06:00
|
|
|
const segments = pathname.split("/").filter(Boolean);
|
|
|
|
|
const projectsIdx = segments.indexOf("projects");
|
|
|
|
|
if (projectsIdx === -1 || segments[projectsIdx + 1] !== projectId) return null;
|
|
|
|
|
const tab = segments[projectsIdx + 2];
|
|
|
|
|
if (tab === "overview") return "overview";
|
2026-03-10 09:08:20 -05:00
|
|
|
if (tab === "configuration") return "configuration";
|
2026-03-16 08:12:38 -05:00
|
|
|
if (tab === "budget") return "budget";
|
2026-03-02 16:44:03 -06:00
|
|
|
if (tab === "issues") return "list";
|
2026-03-26 13:00:25 -05:00
|
|
|
if (tab === "workspaces") return "workspaces";
|
2026-02-23 09:56:31 -06:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ── Overview tab content ── */
|
|
|
|
|
|
|
|
|
|
function OverviewContent({
|
|
|
|
|
project,
|
|
|
|
|
onUpdate,
|
|
|
|
|
imageUploadHandler,
|
|
|
|
|
}: {
|
|
|
|
|
project: { description: string | null; status: string; targetDate: string | null };
|
|
|
|
|
onUpdate: (data: Record<string, unknown>) => void;
|
|
|
|
|
imageUploadHandler?: (file: File) => Promise<string>;
|
|
|
|
|
}) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
<InlineEditor
|
|
|
|
|
value={project.description ?? ""}
|
|
|
|
|
onSave={(description) => onUpdate({ description })}
|
fix: allow to remove project description (#2338)
fixes https://github.com/paperclipai/paperclip/issues/2336
## Thinking Path
<!--
Required. Trace your reasoning from the top of the project down to this
specific change. Start with what Paperclip is, then narrow through the
subsystem, the problem, and why this PR exists. Use blockquote style.
Aim for 5–8 steps. See CONTRIBUTING.md for full examples.
-->
- Paperclip allows to manage projects
- During the project creation you can optionally enter a description
- In the project overview or configuration you can edit the description
- However, you cannot remove the description
- The user should be able to remove the project description because it's
an optional property
- This pull request fixes the frontend bug that prevented the user to
remove/clear the project description
## What Changed
<!-- Bullet list of concrete changes. One bullet per logical unit. -->
- project description can be cleared in "project configuration" and
"project overview"
## Verification
<!--
How can a reviewer confirm this works? Include test commands, manual
steps, or both. For UI changes, include before/after screenshots.
-->
In project configuration or project overview:
- In the description field remove/clear the text
## Risks
<!--
What could go wrong? Mention migration safety, breaking changes,
behavioral shifts, or "Low risk" if genuinely minor.
-->
- none
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] 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
2026-04-06 22:18:38 +02:00
|
|
|
nullable
|
2026-02-23 09:56:31 -06:00
|
|
|
as="p"
|
|
|
|
|
className="text-sm text-muted-foreground"
|
|
|
|
|
placeholder="Add a description..."
|
|
|
|
|
multiline
|
|
|
|
|
imageUploadHandler={imageUploadHandler}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm">
|
|
|
|
|
<div>
|
|
|
|
|
<span className="text-muted-foreground">Status</span>
|
|
|
|
|
<div className="mt-1">
|
|
|
|
|
<StatusBadge status={project.status} />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{project.targetDate && (
|
|
|
|
|
<div>
|
|
|
|
|
<span className="text-muted-foreground">Target Date</span>
|
|
|
|
|
<p>{project.targetDate}</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ── Color picker popover ── */
|
|
|
|
|
|
|
|
|
|
function ColorPicker({
|
|
|
|
|
currentColor,
|
|
|
|
|
onSelect,
|
|
|
|
|
}: {
|
|
|
|
|
currentColor: string;
|
|
|
|
|
onSelect: (color: string) => void;
|
|
|
|
|
}) {
|
|
|
|
|
const [open, setOpen] = useState(false);
|
|
|
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!open) return;
|
|
|
|
|
function handleClick(e: MouseEvent) {
|
|
|
|
|
if (ref.current && !ref.current.contains(e.target as Node)) {
|
|
|
|
|
setOpen(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
document.addEventListener("mousedown", handleClick);
|
|
|
|
|
return () => document.removeEventListener("mousedown", handleClick);
|
|
|
|
|
}, [open]);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="relative" ref={ref}>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setOpen(!open)}
|
2026-03-02 16:44:03 -06:00
|
|
|
className="shrink-0 h-5 w-5 rounded-md cursor-pointer hover:ring-2 hover:ring-foreground/20 transition-[box-shadow]"
|
2026-02-23 09:56:31 -06:00
|
|
|
style={{ backgroundColor: currentColor }}
|
|
|
|
|
aria-label="Change project color"
|
|
|
|
|
/>
|
|
|
|
|
{open && (
|
2026-02-23 14:41:21 -06:00
|
|
|
<div className="absolute top-full left-0 mt-2 p-2 bg-popover border border-border rounded-lg shadow-lg z-50 w-max">
|
2026-02-23 09:56:31 -06:00
|
|
|
<div className="grid grid-cols-5 gap-1.5">
|
|
|
|
|
{PROJECT_COLORS.map((color) => (
|
|
|
|
|
<button
|
|
|
|
|
key={color}
|
|
|
|
|
onClick={() => {
|
|
|
|
|
onSelect(color);
|
|
|
|
|
setOpen(false);
|
|
|
|
|
}}
|
2026-03-02 16:44:03 -06:00
|
|
|
className={`h-6 w-6 rounded-md cursor-pointer transition-[transform,box-shadow] duration-150 hover:scale-110 ${
|
2026-02-23 09:56:31 -06:00
|
|
|
color === currentColor
|
|
|
|
|
? "ring-2 ring-foreground ring-offset-1 ring-offset-background"
|
|
|
|
|
: "hover:ring-2 hover:ring-foreground/30"
|
|
|
|
|
}`}
|
|
|
|
|
style={{ backgroundColor: color }}
|
|
|
|
|
aria-label={`Select color ${color}`}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ── List (issues) tab content ── */
|
|
|
|
|
|
2026-03-02 16:44:03 -06:00
|
|
|
function ProjectIssuesList({ projectId, companyId }: { projectId: string; companyId: string }) {
|
2026-02-23 09:56:31 -06:00
|
|
|
const queryClient = useQueryClient();
|
|
|
|
|
|
|
|
|
|
const { data: agents } = useQuery({
|
2026-03-02 16:44:03 -06:00
|
|
|
queryKey: queryKeys.agents.list(companyId),
|
|
|
|
|
queryFn: () => agentsApi.list(companyId),
|
|
|
|
|
enabled: !!companyId,
|
2026-02-23 09:56:31 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const { data: liveRuns } = useQuery({
|
2026-03-02 16:44:03 -06:00
|
|
|
queryKey: queryKeys.liveRuns(companyId),
|
|
|
|
|
queryFn: () => heartbeatsApi.liveRunsForCompany(companyId),
|
|
|
|
|
enabled: !!companyId,
|
2026-02-23 09:56:31 -06:00
|
|
|
refetchInterval: 5000,
|
|
|
|
|
});
|
2026-04-07 16:45:57 -05:00
|
|
|
const { data: projects } = useQuery({
|
|
|
|
|
queryKey: queryKeys.projects.list(companyId),
|
|
|
|
|
queryFn: () => projectsApi.list(companyId),
|
|
|
|
|
enabled: !!companyId,
|
|
|
|
|
});
|
2026-02-23 09:56:31 -06:00
|
|
|
|
|
|
|
|
const liveIssueIds = useMemo(() => {
|
|
|
|
|
const ids = new Set<string>();
|
|
|
|
|
for (const run of liveRuns ?? []) {
|
|
|
|
|
if (run.issueId) ids.add(run.issueId);
|
|
|
|
|
}
|
|
|
|
|
return ids;
|
|
|
|
|
}, [liveRuns]);
|
|
|
|
|
|
|
|
|
|
const { data: issues, isLoading, error } = useQuery({
|
2026-03-02 16:44:03 -06:00
|
|
|
queryKey: queryKeys.issues.listByProject(companyId, projectId),
|
|
|
|
|
queryFn: () => issuesApi.list(companyId, { projectId }),
|
|
|
|
|
enabled: !!companyId,
|
2026-02-23 09:56:31 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const updateIssue = useMutation({
|
|
|
|
|
mutationFn: ({ id, data }: { id: string; data: Record<string, unknown> }) =>
|
|
|
|
|
issuesApi.update(id, data),
|
|
|
|
|
onSuccess: () => {
|
2026-03-02 16:44:03 -06:00
|
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.issues.listByProject(companyId, projectId) });
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.issues.list(companyId) });
|
2026-02-23 09:56:31 -06:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<IssuesList
|
|
|
|
|
issues={issues ?? []}
|
|
|
|
|
isLoading={isLoading}
|
|
|
|
|
error={error as Error | null}
|
|
|
|
|
agents={agents}
|
2026-04-07 16:45:57 -05:00
|
|
|
projects={projects}
|
2026-02-23 09:56:31 -06:00
|
|
|
liveIssueIds={liveIssueIds}
|
|
|
|
|
projectId={projectId}
|
[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
|
|
|
viewStateKey="paperclip:project-issues-view"
|
2026-02-23 09:56:31 -06:00
|
|
|
onUpdateIssue={(id, data) => updateIssue.mutate({ id, data })}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ── Main project page ── */
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
|
|
|
|
|
export function ProjectDetail() {
|
2026-03-02 16:44:03 -06:00
|
|
|
const { companyPrefix, projectId, filter } = useParams<{
|
|
|
|
|
companyPrefix?: string;
|
|
|
|
|
projectId: string;
|
|
|
|
|
filter?: string;
|
|
|
|
|
}>();
|
|
|
|
|
const { companies, selectedCompanyId, setSelectedCompanyId } = useCompany();
|
2026-03-10 09:08:20 -05:00
|
|
|
const { closePanel } = usePanel();
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
const { setBreadcrumbs } = useBreadcrumbs();
|
[codex] Harden execution reliability and heartbeat tooling (#3679)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Reliable execution depends on heartbeat routing, issue lifecycle
semantics, telemetry, and a fast enough local verification loop to keep
regressions visible
> - The remaining commits on this branch were mostly server/runtime
correctness fixes plus test and documentation follow-ups in that area
> - Those changes are logically separate from the UI-focused
issue-detail and workspace/navigation branches even when they touch
overlapping issue APIs
> - This pull request groups the execution reliability, heartbeat,
telemetry, and tooling changes into one standalone branch
> - The benefit is a focused review of the control-plane correctness
work, including the follow-up fix that restored the implicit
comment-reopen helpers after branch splitting
## What Changed
- Hardened issue/heartbeat execution behavior, including self-review
stage skipping, deferred mention wakes during active execution, stranded
execution recovery, active-run scoping, assignee resolution, and
blocked-to-todo wake resumption
- Reduced noisy polling/logging overhead by trimming issue run payloads,
compacting persisted run logs, silencing high-volume request logs, and
capping heartbeat-run queries in dashboard/inbox surfaces
- Expanded telemetry and status semantics with adapter/model fields on
task completion plus clearer status guidance in docs/onboarding material
- Updated test infrastructure and verification defaults with faster
route-test module isolation, cheaper default `pnpm test`, e2e isolation
from local state, and repo verification follow-ups
- Included docs/release housekeeping from the branch and added a small
follow-up commit restoring the implicit comment-reopen helpers that were
dropped during branch reconstruction
## Verification
- `pnpm vitest run
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/__tests__/issue-telemetry-routes.test.ts`
- `pnpm vitest run server/src/__tests__/http-log-policy.test.ts
server/src/__tests__/heartbeat-run-log.test.ts
server/src/__tests__/health.test.ts`
- `server/src/__tests__/activity-service.test.ts`,
`server/src/__tests__/heartbeat-comment-wake-batching.test.ts`, and
`server/src/__tests__/heartbeat-process-recovery.test.ts` were attempted
on this host but the embedded Postgres harness reported
init-script/data-dir problems and skipped or failed to start, so they
are noted as environment-limited
## Risks
- Medium: this branch changes core issue/heartbeat routing and
reopen/wakeup behavior, so regressions would affect agent execution flow
rather than isolated UI polish
- Because it also updates verification infrastructure, reviewers should
pay attention to whether the new tests are asserting the right failure
modes and not just reshaping harness behavior
## 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 13:34:52 -05:00
|
|
|
const { pushToast } = useToastActions();
|
2026-02-20 10:32:32 -06:00
|
|
|
const queryClient = useQueryClient();
|
|
|
|
|
const navigate = useNavigate();
|
2026-02-23 09:56:31 -06:00
|
|
|
const location = useLocation();
|
2026-03-10 10:04:08 -05:00
|
|
|
const [fieldSaveStates, setFieldSaveStates] = useState<Partial<Record<ProjectConfigFieldKey, ProjectFieldSaveState>>>({});
|
|
|
|
|
const fieldSaveRequestIds = useRef<Partial<Record<ProjectConfigFieldKey, number>>>({});
|
|
|
|
|
const fieldSaveTimers = useRef<Partial<Record<ProjectConfigFieldKey, ReturnType<typeof setTimeout>>>>({});
|
2026-03-02 16:44:03 -06:00
|
|
|
const routeProjectRef = projectId ?? "";
|
|
|
|
|
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;
|
|
|
|
|
const canFetchProject = routeProjectRef.length > 0 && (isUuidLike(routeProjectRef) || Boolean(lookupCompanyId));
|
2026-03-13 16:22:34 -05:00
|
|
|
const activeRouteTab = routeProjectRef ? resolveProjectTab(location.pathname, routeProjectRef) : null;
|
|
|
|
|
const pluginTabFromSearch = useMemo(() => {
|
|
|
|
|
const tab = new URLSearchParams(location.search).get("tab");
|
|
|
|
|
return isProjectPluginTab(tab) ? tab : null;
|
|
|
|
|
}, [location.search]);
|
|
|
|
|
const activeTab = activeRouteTab ?? pluginTabFromSearch;
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
|
2026-02-17 12:24:48 -06:00
|
|
|
const { data: project, isLoading, error } = useQuery({
|
2026-03-02 16:44:03 -06:00
|
|
|
queryKey: [...queryKeys.projects.detail(routeProjectRef), lookupCompanyId ?? null],
|
|
|
|
|
queryFn: () => projectsApi.get(routeProjectRef, lookupCompanyId),
|
|
|
|
|
enabled: canFetchProject,
|
2026-02-17 12:24:48 -06:00
|
|
|
});
|
2026-03-02 16:44:03 -06:00
|
|
|
const canonicalProjectRef = project ? projectRouteRef(project) : routeProjectRef;
|
|
|
|
|
const projectLookupRef = project?.id ?? routeProjectRef;
|
|
|
|
|
const resolvedCompanyId = project?.companyId ?? selectedCompanyId;
|
2026-03-26 13:00:25 -05:00
|
|
|
const experimentalSettingsQuery = useQuery({
|
|
|
|
|
queryKey: queryKeys.instance.experimentalSettings,
|
|
|
|
|
queryFn: () => instanceSettingsApi.getExperimental(),
|
|
|
|
|
});
|
2026-03-13 16:22:34 -05:00
|
|
|
const {
|
|
|
|
|
slots: pluginDetailSlots,
|
|
|
|
|
isLoading: pluginDetailSlotsLoading,
|
|
|
|
|
} = usePluginSlots({
|
|
|
|
|
slotTypes: ["detailTab"],
|
|
|
|
|
entityType: "project",
|
|
|
|
|
companyId: resolvedCompanyId,
|
|
|
|
|
enabled: !!resolvedCompanyId,
|
|
|
|
|
});
|
|
|
|
|
const pluginTabItems = useMemo(
|
|
|
|
|
() => pluginDetailSlots.map((slot) => ({
|
|
|
|
|
value: `plugin:${slot.pluginKey}:${slot.id}` as ProjectPluginTab,
|
|
|
|
|
label: slot.displayName,
|
|
|
|
|
slot,
|
|
|
|
|
})),
|
|
|
|
|
[pluginDetailSlots],
|
|
|
|
|
);
|
|
|
|
|
const activePluginTab = pluginTabItems.find((item) => item.value === activeTab) ?? null;
|
2026-03-26 13:00:25 -05:00
|
|
|
const isolatedWorkspacesEnabled = experimentalSettingsQuery.data?.enableIsolatedWorkspaces === true;
|
|
|
|
|
const workspaceTabProjectId = project?.id ?? null;
|
|
|
|
|
const { data: workspaceTabIssues = [], isLoading: isWorkspaceTabIssuesLoading, error: workspaceTabIssuesError } = useQuery({
|
|
|
|
|
queryKey: workspaceTabProjectId && resolvedCompanyId
|
|
|
|
|
? queryKeys.issues.listByProject(resolvedCompanyId, workspaceTabProjectId)
|
|
|
|
|
: ["issues", "__workspace-tab__", "disabled"],
|
|
|
|
|
queryFn: () => issuesApi.list(resolvedCompanyId!, { projectId: workspaceTabProjectId! }),
|
|
|
|
|
enabled: Boolean(resolvedCompanyId && workspaceTabProjectId && isolatedWorkspacesEnabled),
|
|
|
|
|
});
|
|
|
|
|
const {
|
|
|
|
|
data: workspaceTabExecutionWorkspaces = [],
|
|
|
|
|
isLoading: isWorkspaceTabExecutionWorkspacesLoading,
|
|
|
|
|
error: workspaceTabExecutionWorkspacesError,
|
|
|
|
|
} = useQuery({
|
|
|
|
|
queryKey: workspaceTabProjectId && resolvedCompanyId
|
|
|
|
|
? queryKeys.executionWorkspaces.list(resolvedCompanyId, { projectId: workspaceTabProjectId })
|
|
|
|
|
: ["execution-workspaces", "__workspace-tab__", "disabled"],
|
|
|
|
|
queryFn: () => executionWorkspacesApi.list(resolvedCompanyId!, { projectId: workspaceTabProjectId! }),
|
|
|
|
|
enabled: Boolean(resolvedCompanyId && workspaceTabProjectId && isolatedWorkspacesEnabled),
|
|
|
|
|
});
|
|
|
|
|
const workspaceSummaries = useMemo(() => {
|
|
|
|
|
if (!project || !isolatedWorkspacesEnabled) return [];
|
|
|
|
|
return buildProjectWorkspaceSummaries({
|
|
|
|
|
project,
|
|
|
|
|
issues: workspaceTabIssues,
|
|
|
|
|
executionWorkspaces: workspaceTabExecutionWorkspaces,
|
|
|
|
|
});
|
|
|
|
|
}, [project, isolatedWorkspacesEnabled, workspaceTabIssues, workspaceTabExecutionWorkspaces]);
|
|
|
|
|
const showWorkspacesTab = isolatedWorkspacesEnabled && workspaceSummaries.length > 0;
|
|
|
|
|
const workspaceTabDecisionLoaded =
|
|
|
|
|
experimentalSettingsQuery.isFetched &&
|
|
|
|
|
(!isolatedWorkspacesEnabled || (!isWorkspaceTabIssuesLoading && !isWorkspaceTabExecutionWorkspacesLoading));
|
|
|
|
|
const workspaceTabError = (workspaceTabIssuesError ?? workspaceTabExecutionWorkspacesError) as Error | null;
|
2026-03-02 16:44:03 -06:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!project?.companyId || project.companyId === selectedCompanyId) return;
|
|
|
|
|
setSelectedCompanyId(project.companyId, { source: "route_sync" });
|
|
|
|
|
}, [project?.companyId, selectedCompanyId, setSelectedCompanyId]);
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
|
2026-02-20 10:32:32 -06:00
|
|
|
const invalidateProject = () => {
|
2026-03-02 16:44:03 -06:00
|
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.projects.detail(routeProjectRef) });
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.projects.detail(projectLookupRef) });
|
|
|
|
|
if (resolvedCompanyId) {
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.projects.list(resolvedCompanyId) });
|
2026-02-20 10:32:32 -06:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateProject = useMutation({
|
2026-03-02 16:44:03 -06:00
|
|
|
mutationFn: (data: Record<string, unknown>) =>
|
|
|
|
|
projectsApi.update(projectLookupRef, data, resolvedCompanyId ?? lookupCompanyId),
|
2026-02-20 10:32:32 -06:00
|
|
|
onSuccess: invalidateProject,
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-14 17:47:53 -05:00
|
|
|
const archiveProject = useMutation({
|
|
|
|
|
mutationFn: (archived: boolean) =>
|
|
|
|
|
projectsApi.update(
|
|
|
|
|
projectLookupRef,
|
|
|
|
|
{ archivedAt: archived ? new Date().toISOString() : null },
|
|
|
|
|
resolvedCompanyId ?? lookupCompanyId,
|
|
|
|
|
),
|
2026-03-16 07:59:19 -05:00
|
|
|
onSuccess: (updatedProject, archived) => {
|
2026-03-14 17:47:53 -05:00
|
|
|
invalidateProject();
|
2026-03-16 07:59:19 -05:00
|
|
|
const name = updatedProject?.name ?? project?.name ?? "Project";
|
2026-03-14 17:47:53 -05:00
|
|
|
if (archived) {
|
2026-03-16 07:59:19 -05:00
|
|
|
pushToast({ title: `"${name}" has been archived`, tone: "success" });
|
2026-03-16 18:46:26 -05:00
|
|
|
navigate("/dashboard");
|
|
|
|
|
} else {
|
2026-03-16 07:59:19 -05:00
|
|
|
pushToast({ title: `"${name}" has been unarchived`, tone: "success" });
|
2026-03-14 17:47:53 -05:00
|
|
|
}
|
|
|
|
|
},
|
2026-03-16 18:46:26 -05:00
|
|
|
onError: (_, archived) => {
|
|
|
|
|
pushToast({
|
|
|
|
|
title: archived ? "Failed to archive project" : "Failed to unarchive project",
|
|
|
|
|
tone: "error",
|
|
|
|
|
});
|
|
|
|
|
},
|
2026-03-14 17:47:53 -05:00
|
|
|
});
|
|
|
|
|
|
Add MarkdownEditor component, asset image upload, and rich description editing
Introduce MarkdownEditor built on @mdxeditor/editor with headings,
lists, links, quotes, image upload with drag-and-drop, and themed CSS
integration. Add asset image upload API (routes, service, storage) and
wire image upload into InlineEditor multiline mode, NewIssueDialog,
NewProjectDialog, GoalDetail, IssueDetail, and ProjectDetail
description fields. Tighten prompt template editor styling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:50:45 -06:00
|
|
|
const uploadImage = useMutation({
|
|
|
|
|
mutationFn: async (file: File) => {
|
2026-03-02 16:44:03 -06:00
|
|
|
if (!resolvedCompanyId) throw new Error("No company selected");
|
|
|
|
|
return assetsApi.uploadImage(resolvedCompanyId, file, `projects/${projectLookupRef || "draft"}`);
|
Add MarkdownEditor component, asset image upload, and rich description editing
Introduce MarkdownEditor built on @mdxeditor/editor with headings,
lists, links, quotes, image upload with drag-and-drop, and themed CSS
integration. Add asset image upload API (routes, service, storage) and
wire image upload into InlineEditor multiline mode, NewIssueDialog,
NewProjectDialog, GoalDetail, IssueDetail, and ProjectDetail
description fields. Tighten prompt template editor styling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:50:45 -06:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-14 22:00:12 -05:00
|
|
|
const { data: budgetOverview } = useQuery({
|
|
|
|
|
queryKey: queryKeys.budgets.overview(resolvedCompanyId ?? "__none__"),
|
|
|
|
|
queryFn: () => budgetsApi.overview(resolvedCompanyId!),
|
|
|
|
|
enabled: !!resolvedCompanyId,
|
|
|
|
|
refetchInterval: 30_000,
|
|
|
|
|
staleTime: 5_000,
|
|
|
|
|
});
|
|
|
|
|
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
useEffect(() => {
|
|
|
|
|
setBreadcrumbs([
|
|
|
|
|
{ label: "Projects", href: "/projects" },
|
2026-03-02 16:44:03 -06:00
|
|
|
{ label: project?.name ?? routeProjectRef ?? "Project" },
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
]);
|
2026-03-02 16:44:03 -06:00
|
|
|
}, [setBreadcrumbs, project, routeProjectRef]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!project) return;
|
|
|
|
|
if (routeProjectRef === canonicalProjectRef) return;
|
2026-03-13 16:22:34 -05:00
|
|
|
if (isProjectPluginTab(activeTab)) {
|
|
|
|
|
navigate(`/projects/${canonicalProjectRef}?tab=${encodeURIComponent(activeTab)}`, { replace: true });
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-03-02 16:44:03 -06:00
|
|
|
if (activeTab === "overview") {
|
|
|
|
|
navigate(`/projects/${canonicalProjectRef}/overview`, { replace: true });
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-03-10 09:08:20 -05:00
|
|
|
if (activeTab === "configuration") {
|
|
|
|
|
navigate(`/projects/${canonicalProjectRef}/configuration`, { replace: true });
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-03-16 08:12:38 -05:00
|
|
|
if (activeTab === "budget") {
|
|
|
|
|
navigate(`/projects/${canonicalProjectRef}/budget`, { replace: true });
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-03-26 13:00:25 -05:00
|
|
|
if (activeTab === "workspaces") {
|
|
|
|
|
navigate(`/projects/${canonicalProjectRef}/workspaces`, { replace: true });
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-03-02 16:44:03 -06:00
|
|
|
if (activeTab === "list") {
|
|
|
|
|
if (filter) {
|
|
|
|
|
navigate(`/projects/${canonicalProjectRef}/issues/${filter}`, { replace: true });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
navigate(`/projects/${canonicalProjectRef}/issues`, { replace: true });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
navigate(`/projects/${canonicalProjectRef}`, { replace: true });
|
|
|
|
|
}, [project, routeProjectRef, canonicalProjectRef, activeTab, filter, navigate]);
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-03-10 09:08:20 -05:00
|
|
|
closePanel();
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
return () => closePanel();
|
2026-03-10 09:08:20 -05:00
|
|
|
}, [closePanel]);
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
|
2026-03-10 10:04:08 -05:00
|
|
|
useEffect(() => {
|
|
|
|
|
return () => {
|
|
|
|
|
Object.values(fieldSaveTimers.current).forEach((timer) => {
|
|
|
|
|
if (timer) clearTimeout(timer);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const setFieldState = useCallback((field: ProjectConfigFieldKey, state: ProjectFieldSaveState) => {
|
|
|
|
|
setFieldSaveStates((current) => ({ ...current, [field]: state }));
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const scheduleFieldReset = useCallback((field: ProjectConfigFieldKey, delayMs: number) => {
|
|
|
|
|
const existing = fieldSaveTimers.current[field];
|
|
|
|
|
if (existing) clearTimeout(existing);
|
|
|
|
|
fieldSaveTimers.current[field] = setTimeout(() => {
|
|
|
|
|
setFieldSaveStates((current) => {
|
|
|
|
|
const next = { ...current };
|
|
|
|
|
delete next[field];
|
|
|
|
|
return next;
|
|
|
|
|
});
|
|
|
|
|
delete fieldSaveTimers.current[field];
|
|
|
|
|
}, delayMs);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const updateProjectField = useCallback(async (field: ProjectConfigFieldKey, data: Record<string, unknown>) => {
|
|
|
|
|
const requestId = (fieldSaveRequestIds.current[field] ?? 0) + 1;
|
|
|
|
|
fieldSaveRequestIds.current[field] = requestId;
|
|
|
|
|
setFieldState(field, "saving");
|
|
|
|
|
try {
|
|
|
|
|
await projectsApi.update(projectLookupRef, data, resolvedCompanyId ?? lookupCompanyId);
|
|
|
|
|
invalidateProject();
|
|
|
|
|
if (fieldSaveRequestIds.current[field] !== requestId) return;
|
|
|
|
|
setFieldState(field, "saved");
|
|
|
|
|
scheduleFieldReset(field, 1800);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (fieldSaveRequestIds.current[field] !== requestId) return;
|
|
|
|
|
setFieldState(field, "error");
|
|
|
|
|
scheduleFieldReset(field, 3000);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}, [invalidateProject, lookupCompanyId, projectLookupRef, resolvedCompanyId, scheduleFieldReset, setFieldState]);
|
|
|
|
|
|
2026-03-14 22:00:12 -05:00
|
|
|
const projectBudgetSummary = useMemo(() => {
|
|
|
|
|
const matched = budgetOverview?.policies.find(
|
|
|
|
|
(policy) => policy.scopeType === "project" && policy.scopeId === (project?.id ?? routeProjectRef),
|
|
|
|
|
);
|
|
|
|
|
if (matched) return matched;
|
|
|
|
|
return {
|
|
|
|
|
policyId: "",
|
|
|
|
|
companyId: resolvedCompanyId ?? "",
|
|
|
|
|
scopeType: "project",
|
|
|
|
|
scopeId: project?.id ?? routeProjectRef,
|
|
|
|
|
scopeName: project?.name ?? "Project",
|
|
|
|
|
metric: "billed_cents",
|
|
|
|
|
windowKind: "lifetime",
|
|
|
|
|
amount: 0,
|
|
|
|
|
observedAmount: 0,
|
|
|
|
|
remainingAmount: 0,
|
|
|
|
|
utilizationPercent: 0,
|
|
|
|
|
warnPercent: 80,
|
|
|
|
|
hardStopEnabled: true,
|
|
|
|
|
notifyEnabled: true,
|
|
|
|
|
isActive: false,
|
|
|
|
|
status: "ok",
|
|
|
|
|
paused: Boolean(project?.pausedAt),
|
|
|
|
|
pauseReason: project?.pauseReason ?? null,
|
|
|
|
|
windowStart: new Date(),
|
|
|
|
|
windowEnd: new Date(),
|
|
|
|
|
} satisfies BudgetPolicySummary;
|
|
|
|
|
}, [budgetOverview?.policies, project, resolvedCompanyId, routeProjectRef]);
|
|
|
|
|
|
|
|
|
|
const budgetMutation = useMutation({
|
|
|
|
|
mutationFn: (amount: number) =>
|
|
|
|
|
budgetsApi.upsertPolicy(resolvedCompanyId!, {
|
|
|
|
|
scopeType: "project",
|
|
|
|
|
scopeId: project?.id ?? routeProjectRef,
|
|
|
|
|
amount,
|
|
|
|
|
windowKind: "lifetime",
|
|
|
|
|
}),
|
|
|
|
|
onSuccess: () => {
|
|
|
|
|
if (!resolvedCompanyId) return;
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.budgets.overview(resolvedCompanyId) });
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.projects.detail(routeProjectRef) });
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.projects.detail(projectLookupRef) });
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.projects.list(resolvedCompanyId) });
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.dashboard(resolvedCompanyId) });
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-13 16:22:34 -05:00
|
|
|
if (pluginTabFromSearch && !pluginDetailSlotsLoading && !activePluginTab) {
|
|
|
|
|
return <Navigate to={`/projects/${canonicalProjectRef}/issues`} replace />;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 13:00:25 -05:00
|
|
|
if (activeTab === "workspaces" && workspaceTabDecisionLoaded && !showWorkspacesTab) {
|
|
|
|
|
return <Navigate to={`/projects/${canonicalProjectRef}/issues`} replace />;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-16 07:46:46 -05:00
|
|
|
// Redirect bare /projects/:id to cached tab or default /issues
|
2026-03-02 16:44:03 -06:00
|
|
|
if (routeProjectRef && activeTab === null) {
|
2026-03-16 07:46:46 -05:00
|
|
|
let cachedTab: string | null = null;
|
|
|
|
|
if (project?.id) {
|
|
|
|
|
try { cachedTab = localStorage.getItem(`paperclip:project-tab:${project.id}`); } catch {}
|
|
|
|
|
}
|
|
|
|
|
if (cachedTab === "overview") {
|
|
|
|
|
return <Navigate to={`/projects/${canonicalProjectRef}/overview`} replace />;
|
|
|
|
|
}
|
|
|
|
|
if (cachedTab === "configuration") {
|
|
|
|
|
return <Navigate to={`/projects/${canonicalProjectRef}/configuration`} replace />;
|
|
|
|
|
}
|
2026-03-17 09:21:44 -05:00
|
|
|
if (cachedTab === "budget") {
|
|
|
|
|
return <Navigate to={`/projects/${canonicalProjectRef}/budget`} replace />;
|
|
|
|
|
}
|
2026-03-26 13:00:25 -05:00
|
|
|
if (cachedTab === "workspaces" && workspaceTabDecisionLoaded && showWorkspacesTab) {
|
|
|
|
|
return <Navigate to={`/projects/${canonicalProjectRef}/workspaces`} replace />;
|
|
|
|
|
}
|
|
|
|
|
if (cachedTab === "workspaces" && !workspaceTabDecisionLoaded) {
|
|
|
|
|
return <PageSkeleton variant="detail" />;
|
|
|
|
|
}
|
2026-03-16 07:46:46 -05:00
|
|
|
if (isProjectPluginTab(cachedTab)) {
|
|
|
|
|
return <Navigate to={`/projects/${canonicalProjectRef}?tab=${encodeURIComponent(cachedTab)}`} replace />;
|
|
|
|
|
}
|
2026-03-02 16:44:03 -06:00
|
|
|
return <Navigate to={`/projects/${canonicalProjectRef}/issues`} replace />;
|
2026-02-23 09:56:31 -06:00
|
|
|
}
|
|
|
|
|
|
2026-03-02 16:44:03 -06:00
|
|
|
if (isLoading) return <PageSkeleton variant="detail" />;
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
if (error) return <p className="text-sm text-destructive">{error.message}</p>;
|
|
|
|
|
if (!project) return null;
|
|
|
|
|
|
2026-02-23 09:56:31 -06:00
|
|
|
const handleTabChange = (tab: ProjectTab) => {
|
2026-03-16 07:46:46 -05:00
|
|
|
// Cache the active tab per project
|
|
|
|
|
if (project?.id) {
|
|
|
|
|
try { localStorage.setItem(`paperclip:project-tab:${project.id}`, tab); } catch {}
|
|
|
|
|
}
|
2026-03-13 16:22:34 -05:00
|
|
|
if (isProjectPluginTab(tab)) {
|
|
|
|
|
navigate(`/projects/${canonicalProjectRef}?tab=${encodeURIComponent(tab)}`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-02-23 09:56:31 -06:00
|
|
|
if (tab === "overview") {
|
2026-03-02 16:44:03 -06:00
|
|
|
navigate(`/projects/${canonicalProjectRef}/overview`);
|
2026-03-26 13:00:25 -05:00
|
|
|
} else if (tab === "workspaces") {
|
|
|
|
|
navigate(`/projects/${canonicalProjectRef}/workspaces`);
|
2026-03-16 08:12:38 -05:00
|
|
|
} else if (tab === "budget") {
|
|
|
|
|
navigate(`/projects/${canonicalProjectRef}/budget`);
|
2026-03-10 09:08:20 -05:00
|
|
|
} else if (tab === "configuration") {
|
|
|
|
|
navigate(`/projects/${canonicalProjectRef}/configuration`);
|
2026-02-23 09:56:31 -06:00
|
|
|
} else {
|
2026-03-02 16:44:03 -06:00
|
|
|
navigate(`/projects/${canonicalProjectRef}/issues`);
|
2026-02-23 09:56:31 -06:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
return (
|
|
|
|
|
<div className="space-y-6">
|
2026-02-23 14:41:21 -06:00
|
|
|
<div className="flex items-start gap-3">
|
|
|
|
|
<div className="h-7 flex items-center">
|
|
|
|
|
<ColorPicker
|
|
|
|
|
currentColor={project.color ?? "#6366f1"}
|
|
|
|
|
onSelect={(color) => updateProject.mutate({ color })}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2026-03-16 08:12:38 -05:00
|
|
|
<div className="min-w-0 space-y-2">
|
|
|
|
|
<InlineEditor
|
|
|
|
|
value={project.name}
|
|
|
|
|
onSave={(name) => updateProject.mutate({ name })}
|
|
|
|
|
as="h2"
|
|
|
|
|
className="text-xl font-bold"
|
|
|
|
|
/>
|
|
|
|
|
{project.pauseReason === "budget" ? (
|
|
|
|
|
<div className="inline-flex items-center gap-2 rounded-full border border-red-500/30 bg-red-500/10 px-3 py-1 text-[11px] font-medium uppercase tracking-[0.18em] text-red-200">
|
|
|
|
|
<span className="h-2 w-2 rounded-full bg-red-400" />
|
|
|
|
|
Paused by budget hard stop
|
|
|
|
|
</div>
|
|
|
|
|
) : null}
|
|
|
|
|
</div>
|
2026-02-23 09:56:31 -06:00
|
|
|
</div>
|
2026-02-20 10:32:32 -06:00
|
|
|
|
2026-03-13 23:03:51 -05:00
|
|
|
<PluginSlotOutlet
|
|
|
|
|
slotTypes={["toolbarButton", "contextMenuItem"]}
|
|
|
|
|
entityType="project"
|
|
|
|
|
context={{
|
|
|
|
|
companyId: resolvedCompanyId ?? null,
|
|
|
|
|
companyPrefix: companyPrefix ?? null,
|
|
|
|
|
projectId: project.id,
|
|
|
|
|
projectRef: canonicalProjectRef,
|
|
|
|
|
entityId: project.id,
|
|
|
|
|
entityType: "project",
|
|
|
|
|
}}
|
|
|
|
|
className="flex flex-wrap gap-2"
|
|
|
|
|
itemClassName="inline-flex"
|
|
|
|
|
missingBehavior="placeholder"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<PluginLauncherOutlet
|
|
|
|
|
placementZones={["toolbarButton"]}
|
|
|
|
|
entityType="project"
|
|
|
|
|
context={{
|
|
|
|
|
companyId: resolvedCompanyId ?? null,
|
|
|
|
|
companyPrefix: companyPrefix ?? null,
|
|
|
|
|
projectId: project.id,
|
|
|
|
|
projectRef: canonicalProjectRef,
|
|
|
|
|
entityId: project.id,
|
|
|
|
|
entityType: "project",
|
|
|
|
|
}}
|
|
|
|
|
className="flex flex-wrap gap-2"
|
|
|
|
|
itemClassName="inline-flex"
|
|
|
|
|
/>
|
|
|
|
|
|
2026-03-10 09:08:20 -05:00
|
|
|
<Tabs value={activeTab ?? "list"} onValueChange={(value) => handleTabChange(value as ProjectTab)}>
|
|
|
|
|
<PageTabBar
|
|
|
|
|
items={[
|
2026-03-16 07:46:46 -05:00
|
|
|
{ value: "list", label: "Issues" },
|
2026-03-10 09:08:20 -05:00
|
|
|
{ value: "overview", label: "Overview" },
|
2026-03-26 13:00:25 -05:00
|
|
|
...(showWorkspacesTab ? [{ value: "workspaces", label: "Workspaces" }] : []),
|
2026-03-10 09:08:20 -05:00
|
|
|
{ value: "configuration", label: "Configuration" },
|
2026-03-16 08:12:38 -05:00
|
|
|
{ value: "budget", label: "Budget" },
|
2026-03-13 16:22:34 -05:00
|
|
|
...pluginTabItems.map((item) => ({
|
|
|
|
|
value: item.value,
|
|
|
|
|
label: item.label,
|
|
|
|
|
})),
|
2026-03-10 09:08:20 -05:00
|
|
|
]}
|
2026-03-10 10:04:08 -05:00
|
|
|
align="start"
|
2026-03-10 09:08:20 -05:00
|
|
|
value={activeTab ?? "list"}
|
|
|
|
|
onValueChange={(value) => handleTabChange(value as ProjectTab)}
|
|
|
|
|
/>
|
|
|
|
|
</Tabs>
|
2026-02-23 09:56:31 -06:00
|
|
|
|
|
|
|
|
{activeTab === "overview" && (
|
|
|
|
|
<OverviewContent
|
|
|
|
|
project={project}
|
|
|
|
|
onUpdate={(data) => updateProject.mutate(data)}
|
Add MarkdownEditor component, asset image upload, and rich description editing
Introduce MarkdownEditor built on @mdxeditor/editor with headings,
lists, links, quotes, image upload with drag-and-drop, and themed CSS
integration. Add asset image upload API (routes, service, storage) and
wire image upload into InlineEditor multiline mode, NewIssueDialog,
NewProjectDialog, GoalDetail, IssueDetail, and ProjectDetail
description fields. Tighten prompt template editor styling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:50:45 -06:00
|
|
|
imageUploadHandler={async (file) => {
|
|
|
|
|
const asset = await uploadImage.mutateAsync(file);
|
|
|
|
|
return asset.contentPath;
|
|
|
|
|
}}
|
2026-02-20 10:32:32 -06:00
|
|
|
/>
|
2026-02-23 09:56:31 -06:00
|
|
|
)}
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
|
2026-03-02 16:44:03 -06:00
|
|
|
{activeTab === "list" && project?.id && resolvedCompanyId && (
|
|
|
|
|
<ProjectIssuesList projectId={project.id} companyId={resolvedCompanyId} />
|
2026-02-23 09:56:31 -06:00
|
|
|
)}
|
2026-03-05 18:57:48 -06:00
|
|
|
|
2026-03-26 13:00:25 -05:00
|
|
|
{activeTab === "workspaces" ? (
|
|
|
|
|
workspaceTabDecisionLoaded ? (
|
|
|
|
|
workspaceTabError ? (
|
|
|
|
|
<p className="text-sm text-destructive">{workspaceTabError.message}</p>
|
|
|
|
|
) : (
|
2026-03-28 16:15:20 -05:00
|
|
|
<ProjectWorkspacesContent
|
|
|
|
|
companyId={resolvedCompanyId!}
|
|
|
|
|
projectId={project.id}
|
|
|
|
|
projectRef={canonicalProjectRef}
|
|
|
|
|
summaries={workspaceSummaries}
|
|
|
|
|
/>
|
2026-03-26 13:00:25 -05:00
|
|
|
)
|
|
|
|
|
) : (
|
|
|
|
|
<p className="text-sm text-muted-foreground">Loading workspaces...</p>
|
|
|
|
|
)
|
|
|
|
|
) : null}
|
|
|
|
|
|
2026-03-10 09:08:20 -05:00
|
|
|
{activeTab === "configuration" && (
|
2026-03-10 10:04:08 -05:00
|
|
|
<div className="max-w-4xl">
|
|
|
|
|
<ProjectProperties
|
|
|
|
|
project={project}
|
|
|
|
|
onUpdate={(data) => updateProject.mutate(data)}
|
|
|
|
|
onFieldUpdate={updateProjectField}
|
|
|
|
|
getFieldSaveState={(field) => fieldSaveStates[field] ?? "idle"}
|
2026-03-14 17:47:53 -05:00
|
|
|
onArchive={(archived) => archiveProject.mutate(archived)}
|
|
|
|
|
archivePending={archiveProject.isPending}
|
2026-03-10 10:04:08 -05:00
|
|
|
/>
|
|
|
|
|
</div>
|
2026-03-10 09:08:20 -05:00
|
|
|
)}
|
2026-03-13 16:22:34 -05:00
|
|
|
|
2026-03-16 08:12:38 -05:00
|
|
|
{activeTab === "budget" && resolvedCompanyId ? (
|
|
|
|
|
<div className="max-w-3xl">
|
|
|
|
|
<BudgetPolicyCard
|
|
|
|
|
summary={projectBudgetSummary}
|
|
|
|
|
variant="plain"
|
|
|
|
|
isSaving={budgetMutation.isPending}
|
|
|
|
|
onSave={(amount) => budgetMutation.mutate(amount)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
) : null}
|
|
|
|
|
|
2026-03-13 16:22:34 -05:00
|
|
|
{activePluginTab && (
|
|
|
|
|
<PluginSlotMount
|
|
|
|
|
slot={activePluginTab.slot}
|
|
|
|
|
context={{
|
|
|
|
|
companyId: resolvedCompanyId,
|
|
|
|
|
companyPrefix: companyPrefix ?? null,
|
|
|
|
|
projectId: project.id,
|
|
|
|
|
projectRef: canonicalProjectRef,
|
|
|
|
|
entityId: project.id,
|
|
|
|
|
entityType: "project",
|
|
|
|
|
}}
|
|
|
|
|
missingBehavior="placeholder"
|
|
|
|
|
/>
|
|
|
|
|
)}
|
Add detail pages, property panels, and restyle list pages
New pages: AgentDetail, GoalDetail, IssueDetail, ProjectDetail, Inbox,
MyIssues. New feature components: AgentProperties, GoalProperties,
IssueProperties, ProjectProperties, GoalTree, NewIssueDialog. Add
heartbeats API client. Restyle all list pages (Agents, Issues, Goals,
Projects, Dashboard, Costs, Activity, Org) with EntityRow, FilterBar,
and improved layouts. Add routing for detail views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:57:06 -06:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|