mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-09 16:02:36 +01:00
## Thinking Path > - Paperclip helps people manage AI agents and their work. > - Task conversations and documents use the shared Markdown renderer. > - GitHub repository, pull request, and issue links currently use the same icon. > - Long pasted URLs make these references hard to scan in a conversation. > - This change identifies each resource from its URL and shows a compact label with a distinct icon. > - The exact destination and custom link text stay intact. Connected status data keeps its existing behavior. ## Linked Issues or Issue Description **What existing behavior does this improve?** Inline GitHub links in rendered Markdown. **Current behavior** Repositories, pull requests, and issues all use the GitHub mark. Pasted resource links show the full URL. **Proposed behavior** Pull requests use the pull request icon. Issues use the issue icon. Repositories and other GitHub pages keep the brand mark. Pasted resource URLs show `owner/repository#number` or `owner/repository`. Custom labels keep their text and formatting. **Reason and benefit** Readers can identify GitHub resource types and scan references in a narrow conversation. Full URLs, query strings, and comment anchors stay available through the link destination. Link rendering makes no remote metadata requests. **Breaking changes** The visible text of a bare GitHub resource URL becomes compact. The destination does not change. No API, database, or connection changes are required. Searches found no duplicate work for GitHub inline resource presentation. Related work: [same-origin Markdown routing](https://github.com/paperclipai/paperclip/pull/17864). This change preserves that route handling. ## What Changed - Add a pure GitHub URL classifier with exact host checks and resource labels. - Move ordinary URL presentation into `MarkdownUrlLink`. Keep internal references, workspace links, custom resolvers, and connected metadata in their existing order. - Add separate icons and accessible resource names. Preserve authored Markdown titles and formatted labels. - Add Playground, Narrow, and Streaming Storybook examples that mount `MarkdownBody`. - Add parser and renderer tests, eight Chromium browser cases, and the browser command in the development guide. - Consider Open Graph loading. Use the existing connected metadata path for remote status and titles. Add no arbitrary page fetches. ## Verification - Focused Vitest coverage: 129 passing tests across nine files for GitHub parsing, Markdown rendering, connected metadata, document rendering, and workspace-file rendering. - `pnpm check:token-gates`. - `pnpm --filter @paperclipai/ui typecheck`. - `pnpm --filter @paperclipai/ui build`. - `pnpm build-storybook`. - Storybook catalog generation/check and 30 catalog/map tests. - Site-route regression: Stars, Education, and enterprise pages keep general GitHub branding and their full URL text. Three parser cases failed before the fix; parser and renderer cases now pass. - Streaming regression: three resource cases failed before the fix and pass after it. The fix preserves the generated word span and custom formatted labels. - Chromium: `STORYBOOK_TEST_BASE_URL=http://localhost:6106 pnpm exec playwright test inline-links.spec.ts --config=tests/storybook-visual/playwright.config.ts --project=stories`. - Browser scope: eight passing cases for ordinary/streaming modes, dark/light at desktop and 390px mobile, completed play assertions, exact comment destinations, safe external attributes, keyboard focus, wrapping, and no GitHub requests. Screenshots are saved as task artifacts; no review images are committed. - The first browser attempt in the resumed environment could not launch Chromium because its executable was missing. The browser was installed, and all eight cases then passed. - The browser cases use synthetic text and one connected metadata fixture. They do not prove provider authentication or server persistence. No visual baseline comparison is claimed; the repository baseline manifest is unpublished. - Initial typecheck found two unsupported story assertion options and an unbuilt SDK dependency in the fresh checkout. The options were removed and the SDK was built before the successful retry. - `pnpm verify` returned 2 because the new browser spec requires broad CI coverage. Focused tests and the browser suite supply local evidence. Required CI supplies broad coverage. ## Risks - Compact labels hide URL detail from the visible line. The exact href and default tooltip preserve that detail. - GitHub site routes can change. Unknown paths retain the general GitHub treatment. Lookalike hosts and credential-bearing URLs do not get GitHub resource treatment. - Connected external-object status and title rendering keeps priority over URL-derived presentation. ## Model Used OpenAI Codex, GPT-6 (`gpt-6` family), with reasoning, tool use, browser checks, and code execution. The exact deployment ID and context window are not exposed in this run. ## 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 - [ ] 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>
45 lines
2.6 KiB
TypeScript
45 lines
2.6 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
for (const theme of ["dark", "light"]) {
|
|
for (const mobile of [false, true]) {
|
|
for (const streaming of [false, true]) {
|
|
test(`GitHub inline links [${theme}, ${mobile ? "mobile" : "desktop"}, ${streaming ? "streaming" : "ordinary"}]`, async ({ page }, testInfo) => {
|
|
if (mobile) await page.setViewportSize({ width: 390, height: 844 });
|
|
const errors: string[] = [];
|
|
const providerRequests: string[] = [];
|
|
page.on("pageerror", (error) => errors.push(error.message));
|
|
page.on("request", (request) => {
|
|
if (new URL(request.url()).hostname === "github.com") providerRequests.push(request.url());
|
|
});
|
|
const id = `tasks-inline-links--${streaming ? "streaming" : mobile ? "narrow" : "playground"}`;
|
|
await page.goto(`/iframe.html?id=${id}&viewMode=story&globals=theme:${theme}`);
|
|
await expect.poll(() => page.evaluate(() => {
|
|
const channel = (window as unknown as {
|
|
__STORYBOOK_ADDONS_CHANNEL__?: { data: Record<string, Array<{ status?: string }>> };
|
|
}).__STORYBOOK_ADDONS_CHANNEL__;
|
|
return channel?.data.storyFinished?.[0]?.status;
|
|
})).toBe("success");
|
|
const main = page.getByRole("main");
|
|
const pr = main.getByRole("link", { name: "GitHub pull request: acme/web#241", exact: true });
|
|
await expect(pr).toHaveAttribute("rel", "noopener noreferrer");
|
|
if (streaming) {
|
|
await expect(pr.locator(".conversation-stream-word")).toHaveText("acme/web#241");
|
|
}
|
|
const comment = main.getByRole("link", { name: "GitHub pull request: Jump to the review comment" });
|
|
await expect(comment).toHaveAttribute("href", "https://github.com/acme/web/pull/241/files?diff=split#discussion_r123");
|
|
await expect(comment).toHaveAttribute("title", "Review comment on the exact diff");
|
|
await pr.focus();
|
|
await expect(pr).toBeFocused();
|
|
await pr.blur();
|
|
const settings = main.getByRole("link", { name: "company settings" });
|
|
await expect(settings).not.toHaveAttribute("target");
|
|
await expect(main.locator("a[data-github-link] .lucide-external-link")).toHaveCount(0);
|
|
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
|
|
expect(errors).toEqual([]);
|
|
expect(providerRequests).toEqual([]);
|
|
await page.screenshot({ path: testInfo.outputPath(`inline-links-${theme}-${mobile ? "mobile" : "desktop"}-${streaming ? "streaming" : "ordinary"}.png`), fullPage: true });
|
|
});
|
|
}
|
|
}
|
|
}
|