mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-19 10:36:26 +02:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The board UI is where operators create issues, inspect issue-thread decisions, write markdown, and move between issue workflow surfaces. > - A broad recovered branch mixed these operator workflow polish fixes with unrelated backend, pipeline, plugin, eval, and work-product changes. > - Reviewers need the operator UI workflow fixes in a small PR that can be understood without pulling in the rest of that recovery branch. > - This pull request extracts only the issue workflow UI slice: interaction cards, markdown editing fallback behavior, create-issue work mode shortcuts, file-viewer URL handling, and issue navigation scroll behavior. > - The benefit is a lower-risk review path for operator workflow fixes while keeping unrelated execution-workspace and server work out of this PR. ## Linked Issues or Issue Description - Refs #8866, the closed broad source PR that this focused slice was extracted from. - Related: #8228 is an open UI polish PR, but its file list does not overlap this branch. - Related: #4090 is older merged operator workflow polish context. No public GitHub issue exists for this exact extracted slice. The problem is that several small operator issue-workflow fixes were bundled inside a broad recovery branch, making them hard to review and ship independently. ## What Changed - Removed interaction continuation wake-policy labels from issue-thread interaction card headers and updated the card test/story copy accordingly. - Added a markdown editor error boundary so rich editor render crashes fall back to a raw textarea instead of breaking the compose surface. - Made the create-issue work-mode shortcut accept ctrl-period and iOS hardware-keyboard command-period-as-Escape behavior without dismissing the dialog. - Fixed file-viewer URL navigation to compare against the live browser search string when router state is stale. - Restored scroll reset when navigating from an issue detail route back to the issue index, while preserving browser-history restoration behavior. ## Screenshots Before removing the continuation wake-policy badge from interaction card headers:  After removing the wake-policy badge:  ## Verification - `pnpm exec vitest run ui/src/components/IssueThreadInteractionCard.test.tsx ui/src/components/MarkdownEditor.test.tsx ui/src/components/NewIssueDialog.test.tsx ui/src/context/FileViewerContext.test.ts ui/src/lib/navigation-scroll.test.ts` — 5 files, 102 tests passed. - `pnpm --filter @paperclipai/ui typecheck` — passed. - `git diff --check` — passed. ## Risks Low risk. The PR is limited to UI workflow components/helpers and their tests. Main behavioral risks are keyboard shortcut edge cases across browsers and fallback editor rendering, both covered by focused tests. ## Model Used OpenAI Codex, GPT-5-based coding agent (`gpt-5` family; exact served variant and context window are not exposed in this runtime), with terminal, Git, GitHub, and test execution 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>
189 lines
5.4 KiB
TypeScript
189 lines
5.4 KiB
TypeScript
// @vitest-environment jsdom
|
|
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import {
|
|
applyMainContentScrollTop,
|
|
NavigationScrollMemory,
|
|
resetNavigationScroll,
|
|
SIDEBAR_SCROLL_RESET_STATE,
|
|
shouldResetScrollOnNavigation,
|
|
} from "./navigation-scroll";
|
|
|
|
describe("navigation-scroll", () => {
|
|
it("resets scroll only for flagged sidebar navigation", () => {
|
|
expect(
|
|
shouldResetScrollOnNavigation({
|
|
previousPathname: "/issues",
|
|
pathname: "/dashboard",
|
|
navigationType: "PUSH",
|
|
state: SIDEBAR_SCROLL_RESET_STATE,
|
|
}),
|
|
).toBe(true);
|
|
|
|
expect(
|
|
shouldResetScrollOnNavigation({
|
|
previousPathname: "/issues",
|
|
pathname: "/dashboard",
|
|
navigationType: "PUSH",
|
|
state: null,
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("preserves scroll restoration for browser history navigation even for sidebar entries", () => {
|
|
expect(
|
|
shouldResetScrollOnNavigation({
|
|
previousPathname: "/issues",
|
|
pathname: "/dashboard",
|
|
navigationType: "POP",
|
|
state: SIDEBAR_SCROLL_RESET_STATE,
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("resets scroll when navigating into the top-level issues page", () => {
|
|
expect(
|
|
shouldResetScrollOnNavigation({
|
|
previousPathname: "/issues/PAP-1389",
|
|
pathname: "/issues",
|
|
navigationType: "PUSH",
|
|
state: null,
|
|
}),
|
|
).toBe(true);
|
|
|
|
expect(
|
|
shouldResetScrollOnNavigation({
|
|
previousPathname: "/PAP/issues/PAP-1389",
|
|
pathname: "/PAP/issues",
|
|
navigationType: "REPLACE",
|
|
state: null,
|
|
}),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("does not reset issues page scroll on browser history restoration", () => {
|
|
expect(
|
|
shouldResetScrollOnNavigation({
|
|
previousPathname: "/issues/PAP-1389",
|
|
pathname: "/issues",
|
|
navigationType: "POP",
|
|
state: null,
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("resets scroll when navigating directly between issue detail routes", () => {
|
|
expect(
|
|
shouldResetScrollOnNavigation({
|
|
previousPathname: "/issues/PAP-1389",
|
|
pathname: "/issues/PAP-1346",
|
|
navigationType: "PUSH",
|
|
state: null,
|
|
}),
|
|
).toBe(true);
|
|
|
|
expect(
|
|
shouldResetScrollOnNavigation({
|
|
previousPathname: "/PAP/issues/PAP-1389",
|
|
pathname: "/PAP/issues/PAP-1346",
|
|
navigationType: "REPLACE",
|
|
state: null,
|
|
}),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("does not treat non-detail issue routes as issue-to-issue navigation", () => {
|
|
expect(
|
|
shouldResetScrollOnNavigation({
|
|
previousPathname: "/projects/project-1/issues/all",
|
|
pathname: "/issues/PAP-1346",
|
|
navigationType: "PUSH",
|
|
state: null,
|
|
}),
|
|
).toBe(false);
|
|
|
|
expect(
|
|
shouldResetScrollOnNavigation({
|
|
previousPathname: "/issues/PAP-1389",
|
|
pathname: "/projects/project-1/issues/all",
|
|
navigationType: "PUSH",
|
|
state: null,
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("does not reset scroll on the initial render or when the pathname is unchanged", () => {
|
|
expect(
|
|
shouldResetScrollOnNavigation({
|
|
previousPathname: null,
|
|
pathname: "/dashboard",
|
|
navigationType: "PUSH",
|
|
state: SIDEBAR_SCROLL_RESET_STATE,
|
|
}),
|
|
).toBe(false);
|
|
|
|
expect(
|
|
shouldResetScrollOnNavigation({
|
|
previousPathname: "/dashboard",
|
|
pathname: "/dashboard",
|
|
navigationType: "REPLACE",
|
|
state: SIDEBAR_SCROLL_RESET_STATE,
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("resets both the main content container and page scroll state", () => {
|
|
const main = document.createElement("main");
|
|
main.scrollTop = 180;
|
|
main.scrollLeft = 14;
|
|
main.scrollTo = vi.fn();
|
|
document.body.appendChild(main);
|
|
|
|
document.documentElement.scrollTop = 240;
|
|
document.documentElement.scrollLeft = 9;
|
|
document.body.scrollTop = 120;
|
|
document.body.scrollLeft = 7;
|
|
const windowScrollTo = vi.spyOn(window, "scrollTo").mockImplementation(() => {});
|
|
|
|
resetNavigationScroll(main);
|
|
|
|
expect(main.scrollTo).toHaveBeenCalledWith({ top: 0, left: 0, behavior: "auto" });
|
|
expect(main.scrollTop).toBe(0);
|
|
expect(main.scrollLeft).toBe(0);
|
|
expect(document.documentElement.scrollTop).toBe(0);
|
|
expect(document.documentElement.scrollLeft).toBe(0);
|
|
expect(document.body.scrollTop).toBe(0);
|
|
expect(document.body.scrollLeft).toBe(0);
|
|
expect(windowScrollTo).toHaveBeenCalledWith({ top: 0, left: 0, behavior: "auto" });
|
|
});
|
|
|
|
it("remembers and recalls scroll offsets per history key", () => {
|
|
const memory = new NavigationScrollMemory();
|
|
expect(memory.recall("missing")).toBe(0);
|
|
|
|
memory.remember("inbox", 640);
|
|
memory.remember("issue", 1820);
|
|
expect(memory.recall("inbox")).toBe(640);
|
|
expect(memory.recall("issue")).toBe(1820);
|
|
|
|
memory.remember("inbox", 700);
|
|
expect(memory.recall("inbox")).toBe(700);
|
|
|
|
memory.remember("inbox", -50);
|
|
expect(memory.recall("inbox")).toBe(0);
|
|
});
|
|
|
|
it("restores a remembered scroll offset onto the main content element", () => {
|
|
const main = document.createElement("main");
|
|
main.scrollTo = vi.fn();
|
|
|
|
applyMainContentScrollTop(main, 540);
|
|
|
|
expect(main.scrollTo).toHaveBeenCalledWith({ top: 540, left: 0, behavior: "auto" });
|
|
expect(main.scrollTop).toBe(540);
|
|
expect(main.scrollLeft).toBe(0);
|
|
|
|
expect(() => applyMainContentScrollTop(null, 540)).not.toThrow();
|
|
});
|
|
});
|