[codex] Fix collapsed starred project indentation (#9215)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The board sidebar is a high-frequency navigation surface for companies, projects, and work queues. > - Starred projects render as child rows under Projects in the expanded sidebar. > - The collapsed rail should align every nav icon in the same rail column. > - The starred-project child indent was still applied in the collapsed rail, which pushed the project glyph out of alignment. > - This pull request keeps the expanded hierarchy indent while removing it only for the collapsed rail. > - The benefit is a cleaner collapsed sidebar without changing expanded sidebar hierarchy. ## Linked Issues or Issue Description No public GitHub issue exists. ## What happened? In the collapsed sidebar rail, starred project rows kept the expanded child indentation. That pushed the project glyph out of alignment with the rest of the collapsed sidebar icons. ## Expected behavior Collapsed starred project icons should align with the other sidebar rail icons while the expanded sidebar should keep the child-row indentation under Projects. ## Steps to reproduce 1. Open Paperclip with at least one starred project. 2. Collapse the sidebar into rail mode. 3. Compare the starred project glyph position with the other collapsed sidebar glyphs. ## Paperclip version or commit Reproduced on the PR base before this branch; fixed on commite57d14343dwith CI cleanup on commit12aafe8959. ## Deployment mode Local dev (`pnpm dev`). ## What Changed - Applies the starred-project left padding only when the sidebar is not in rail mode. - Adds a regression test covering expanded and collapsed starred-project rendering. - Makes the heartbeat worktree suppression test cleanup tolerate late heartbeat run events before deleting heartbeat runs. ## Verification - Passed: `pnpm exec vitest run ui/src/components/SidebarStarredProjects.test.tsx` - Passed: `pnpm exec vitest run server/src/__tests__/heartbeat-worktree-suppression.test.ts` - Passed: PR #9215 latest-head GitHub checks on commit `12aafe8959351826038baf0c1e401fb44913c67c` - Passed: Greptile 5/5 with no inline comments or unresolved review threads on commit `12aafe8959351826038baf0c1e401fb44913c67c` - Known existing baseline failure: `pnpm check:token-gates` reports violations in `ui/src/components/ActivityCharts.tsx` and `ui/src/components/IssueRecoveryActionCard.tsx`, which this PR does not touch. ## Risks Low risk. The UI change adjusts one conditional class on starred project links and preserves the expanded sidebar layout. The server test change is cleanup-only and does not alter production behavior. ## Model Used OpenAI GPT-5 Codex coding agent with local command execution and repository editing tools. ## 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 searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
555391fed7
commit
5d0de3499d
|
|
@ -36,19 +36,36 @@ describeEmbeddedPostgres("heartbeat worktree suppression", () => {
|
|||
let db!: ReturnType<typeof createDb>;
|
||||
let tempDb: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>> | null = null;
|
||||
|
||||
function isHeartbeatRunEventFkError(error: unknown) {
|
||||
const message = error instanceof Error ? `${error.message} ${String(error.cause ?? "")}` : String(error);
|
||||
return message.includes("heartbeat_run_events_run_id_heartbeat_runs_id_fk");
|
||||
}
|
||||
|
||||
async function deleteHeartbeatRunsWithEvents() {
|
||||
for (let attempt = 0; attempt < 5; attempt += 1) {
|
||||
await db.delete(heartbeatRunEvents);
|
||||
try {
|
||||
await db.delete(heartbeatRuns);
|
||||
return;
|
||||
} catch (error) {
|
||||
if (!isHeartbeatRunEventFkError(error) || attempt === 4) throw error;
|
||||
await new Promise((resolve) => setTimeout(resolve, 25));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
tempDb = await startEmbeddedPostgresTestDatabase("heartbeat-worktree-suppression-");
|
||||
db = createDb(tempDb.connectionString);
|
||||
}, 20_000);
|
||||
|
||||
afterEach(async () => {
|
||||
await db.delete(heartbeatRunEvents);
|
||||
await db.delete(issueComments);
|
||||
await db.delete(issueDocuments);
|
||||
await db.delete(documentRevisions);
|
||||
await db.delete(documents);
|
||||
await db.delete(activityLog);
|
||||
await db.delete(heartbeatRuns);
|
||||
await deleteHeartbeatRunsWithEvents();
|
||||
await db.delete(agentWakeupRequests);
|
||||
await db.delete(issues);
|
||||
await db.delete(agentRuntimeState);
|
||||
|
|
|
|||
|
|
@ -112,6 +112,10 @@ function projectLinkLabels(container: HTMLElement) {
|
|||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function projectLink(container: HTMLElement, projectRef: string) {
|
||||
return container.querySelector(`a[href="/projects/${projectRef}/issues"]`) as HTMLAnchorElement | null;
|
||||
}
|
||||
|
||||
describe("SidebarStarredProjects", () => {
|
||||
let container: HTMLDivElement;
|
||||
let root: ReturnType<typeof createRoot> | null;
|
||||
|
|
@ -176,6 +180,30 @@ describe("SidebarStarredProjects", () => {
|
|||
expect(document.body.querySelector('button[aria-label="Unstar Bravo"]')).not.toBeNull();
|
||||
});
|
||||
|
||||
it("keeps starred projects indented only outside the collapsed rail", async () => {
|
||||
mockProjectsApi.list.mockResolvedValue([
|
||||
makeProject({ id: "project-a", name: "Alpha", urlKey: "alpha" }),
|
||||
]);
|
||||
memberships = { ...memberships, starredProjectIds: ["project-a"] };
|
||||
|
||||
await render();
|
||||
|
||||
expect(projectLink(container, "alpha")?.className).toContain("pl-8");
|
||||
|
||||
await act(async () => root?.unmount());
|
||||
root = null;
|
||||
container.innerHTML = "";
|
||||
queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } } });
|
||||
mockSidebarState.collapsed = true;
|
||||
|
||||
await render();
|
||||
|
||||
const railProjectLink = projectLink(container, "alpha");
|
||||
expect(railProjectLink?.className).not.toContain("pl-8");
|
||||
const nameSpan = Array.from(container.querySelectorAll("span")).find((el) => el.textContent === "Alpha");
|
||||
expect(nameSpan?.className).toContain("w-0");
|
||||
});
|
||||
|
||||
it("renders nothing when no projects are starred", async () => {
|
||||
mockProjectsApi.list.mockResolvedValue([makeProject({ id: "project-a", name: "Alpha" })]);
|
||||
|
||||
|
|
|
|||
|
|
@ -121,7 +121,8 @@ export function SidebarStarredProjects() {
|
|||
if (isMobile) setSidebarOpen(false);
|
||||
}}
|
||||
className={cn(
|
||||
"flex min-w-0 flex-1 items-center gap-2.5 px-3 py-1.5 pl-8 pointer-coarse:py-1 pr-8 text-(length:--text-compact) font-medium transition-colors",
|
||||
"flex min-w-0 flex-1 items-center gap-2.5 px-3 py-1.5 pointer-coarse:py-1 pr-8 text-(length:--text-compact) font-medium transition-colors",
|
||||
!rail && "pl-8",
|
||||
isActive
|
||||
? "bg-accent text-foreground"
|
||||
: "text-foreground/80 hover:bg-accent/50 hover:text-foreground",
|
||||
|
|
|
|||
Loading…
Reference in New Issue