Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions apps/web/src/components/chat/AgentsPanel.browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1540,6 +1540,41 @@ describe("AgentsPanel", () => {
}
});

it("gives an inactive tab distinct resting and hover surfaces", async () => {
const mounted = await render(
<main style={{ boxSizing: "border-box", height: 640, width: 330 }}>
<ChatRightPanel
openTabs={["sourceControl", "agents"]}
availableTabs={["sourceControl", "diff", "agents"]}
activeTab="sourceControl"
onSelectTab={vi.fn()}
onCloseTab={vi.fn()}
>
<div />
</ChatRightPanel>
</main>,
);

try {
await expect.element(page.getByRole("tab", { name: "Agents" })).toBeVisible();
const panel = document.querySelector("[data-chat-right-panel='true']") as HTMLElement;
const inactiveTab = document.querySelector("[data-right-panel-tab='agents']") as HTMLElement;
const panelBackground = getComputedStyle(panel).backgroundColor;
const restingBackground = getComputedStyle(inactiveTab).backgroundColor;

expect(restingBackground).not.toBe(panelBackground);

await page.getByRole("tab", { name: "Agents" }).hover();
await vi.waitFor(() => {
if (getComputedStyle(inactiveTab).backgroundColor === restingBackground) {
throw new Error("The inactive tab never gained its separate hover surface.");
}
});
} finally {
await mounted.unmount();
}
});

it("parks the + at the strip's left edge while the launcher is showing", async () => {
const mounted = await render(
<main style={{ boxSizing: "border-box", height: 640, width: 330 }}>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/chat/RightPanelTabStrip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ function TabStripItem({
"pr-3.5 pl-1.5",
active
? "bg-background text-foreground"
: "text-muted-foreground/80 hover:bg-accent hover:text-foreground",
: "bg-muted/50 text-muted-foreground/80 hover:bg-accent hover:text-foreground",
)}
{...(measuring
? {}
Expand Down
4 changes: 2 additions & 2 deletions scripts/generate-release-notes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,14 @@ it("uses GitHub PR attribution, filters release-preparation noise, and keeps dir
[
"## What's Changed",
"",
"- Add prompt stashing by @will in https://github.com/Threadlines/threadlines/pull/93",
"- Add prompt stashing by [@will](https://github.com/will) in https://github.com/Threadlines/threadlines/pull/93",
"",
"### Direct changes",
"",
"- [`cccccccc`](https://github.com/Threadlines/threadlines/commit/cccccccccccccccccccccccccccccccccccccccc) Keep updater diagnostics visible",
"",
"## New Contributors",
"* @will made their first contribution in https://github.com/Threadlines/threadlines/pull/93",
"* [@will](https://github.com/will) made their first contribution in https://github.com/Threadlines/threadlines/pull/93",
"",
"**Full Changelog**: https://github.com/Threadlines/threadlines/compare/v0.3.1-nightly.20260731.204...v0.3.1-nightly.20260731.205",
"",
Expand Down
24 changes: 22 additions & 2 deletions scripts/generate-release-notes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,26 @@ interface ParsedGitHubGeneratedNotes {
readonly fullChangelogLine: string | undefined;
}

function githubProfileLink(login: string): string {
return `[@${login}](https://github.com/${login})`;
}

/** GitHub gives bare @mentions a viewer-specific highlight. Keep attribution
* clickable without that highlight by rendering the generated author patterns
* as ordinary profile links. Limit this to GitHub's own templates so an @ in a
* pull request title or email-like text is not rewritten. */
function linkGitHubGeneratedAttribution(line: string): string {
return line
.replace(
/\bby @([a-z\d](?:[a-z\d-]{0,37}[a-z\d])?)(?=\s+in\s+)/gi,
(_match, login: string) => `by ${githubProfileLink(login)}`,
)
.replace(
/^([-*]\s+)@([a-z\d](?:[a-z\d-]{0,37}[a-z\d])?)(?=\s+made their first contribution\b)/i,
(_match, bullet: string, login: string) => `${bullet}${githubProfileLink(login)}`,
);
}

function parseGitHubGeneratedNotes(body: string): ParsedGitHubGeneratedNotes {
const lines = body.replaceAll("\r\n", "\n").split("\n");
const newContributorsStart = lines.findIndex((line) => /^##\s+New Contributors\s*$/i.test(line));
Expand All @@ -272,7 +292,7 @@ function parseGitHubGeneratedNotes(body: string): ParsedGitHubGeneratedNotes {
if (pullRequestNumber) filteredPullRequestNumbers.add(pullRequestNumber);
return false;
})
.map((line) => line.trim().replace(/^\*\s+/, "- "));
.map((line) => linkGitHubGeneratedAttribution(line.trim().replace(/^\*\s+/, "- ")));

const newContributorSection =
newContributorsStart === -1
Expand All @@ -286,7 +306,7 @@ function parseGitHubGeneratedNotes(body: string): ParsedGitHubGeneratedNotes {
const pullRequestNumber = /\/pull\/(\d+)/i.exec(line)?.[1];
return !pullRequestNumber || !filteredPullRequestNumbers.has(pullRequestNumber);
})
.map((line) => line.trimEnd())
.map((line) => linkGitHubGeneratedAttribution(line.trimEnd()))
.filter((line, index, section) => {
if (line.length > 0) return true;
return index > 0 && index < section.length - 1;
Expand Down
Loading