Skip to content

Commit 5cc2a2e

Browse files
authored
Merge pull request #51 from tyulyukov/marcode/port-wco-and-markdown-file-links
feat: port Window Controls Overlay + markdown file link UX (upstream pingdotgg#1969, pingdotgg#1956)
2 parents 8d43271 + f63c772 commit 5cc2a2e

19 files changed

Lines changed: 796 additions & 48 deletions

apps/desktop/src/main.ts

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ declare const __EMBEDDED_MARCODE_JIRA_TOKEN_PROXY_URL__: string;
1010
import {
1111
app,
1212
BrowserWindow,
13+
type BrowserWindowConstructorOptions,
1314
clipboard,
1415
dialog,
1516
ipcMain,
@@ -154,6 +155,16 @@ function normalizeContextMenuItems(source: readonly ContextMenuItem[]): ContextM
154155
return normalizedItems;
155156
}
156157

158+
const TITLEBAR_HEIGHT = 40;
159+
const TITLEBAR_COLOR = "#01000000"; // #00000000 does not work correctly on Linux
160+
const TITLEBAR_LIGHT_SYMBOL_COLOR = "#1f2937";
161+
const TITLEBAR_DARK_SYMBOL_COLOR = "#f8fafc";
162+
163+
type WindowTitleBarOptions = Pick<
164+
BrowserWindowConstructorOptions,
165+
"titleBarOverlay" | "titleBarStyle" | "trafficLightPosition"
166+
>;
167+
157168
type DesktopUpdateErrorContext = DesktopUpdateState["errorContext"];
158169
type LinuxDesktopNamedApp = Electron.App & {
159170
setDesktopName?: (desktopName: string) => void;
@@ -1716,6 +1727,46 @@ function saveWindowState(window: BrowserWindow): void {
17161727
writeWindowState(WINDOW_STATE_PATH, lastWindowState);
17171728
}
17181729

1730+
function getWindowTitleBarOptions(): WindowTitleBarOptions {
1731+
if (process.platform === "darwin") {
1732+
return {
1733+
titleBarStyle: "hiddenInset",
1734+
trafficLightPosition: { x: 16, y: 18 },
1735+
};
1736+
}
1737+
1738+
return {
1739+
titleBarStyle: "hidden",
1740+
titleBarOverlay: {
1741+
color: TITLEBAR_COLOR,
1742+
height: TITLEBAR_HEIGHT,
1743+
symbolColor: nativeTheme.shouldUseDarkColors
1744+
? TITLEBAR_DARK_SYMBOL_COLOR
1745+
: TITLEBAR_LIGHT_SYMBOL_COLOR,
1746+
},
1747+
};
1748+
}
1749+
1750+
function syncWindowAppearance(window: BrowserWindow): void {
1751+
if (window.isDestroyed()) {
1752+
return;
1753+
}
1754+
1755+
window.setBackgroundColor(getInitialWindowBackgroundColor());
1756+
const { titleBarOverlay } = getWindowTitleBarOptions();
1757+
if (typeof titleBarOverlay === "object") {
1758+
window.setTitleBarOverlay(titleBarOverlay);
1759+
}
1760+
}
1761+
1762+
function syncAllWindowAppearance(): void {
1763+
for (const window of BrowserWindow.getAllWindows()) {
1764+
syncWindowAppearance(window);
1765+
}
1766+
}
1767+
1768+
nativeTheme.on("updated", syncAllWindowAppearance);
1769+
17191770
function createWindow(options?: { deferLoad?: boolean }): BrowserWindow {
17201771
const restoredBounds = resolveWindowBounds(lastWindowState);
17211772

@@ -1728,8 +1779,7 @@ function createWindow(options?: { deferLoad?: boolean }): BrowserWindow {
17281779
backgroundColor: getInitialWindowBackgroundColor(),
17291780
...getIconOption(),
17301781
title: APP_DISPLAY_NAME,
1731-
titleBarStyle: "hiddenInset",
1732-
trafficLightPosition: { x: 16, y: 18 },
1782+
...getWindowTitleBarOptions(),
17331783
webPreferences: {
17341784
preload: Path.join(__dirname, "preload.js"),
17351785
contextIsolation: true,

apps/web/src/components/ChatMarkdown.browser.tsx

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ describe("ChatMarkdown", () => {
6363
);
6464

6565
try {
66-
const link = page.getByRole("link", { name: "PermissionRule.ts:1" });
66+
const link = page.getByRole("link", { name: "PermissionRule.ts · L1" });
6767
await expect.element(link).toBeInTheDocument();
6868
await expect.element(link).toHaveAttribute("href", `${filePath}#L1`);
6969

@@ -76,4 +76,66 @@ describe("ChatMarkdown", () => {
7676
await screen.unmount();
7777
}
7878
});
79+
80+
it("shows column information inline when present", async () => {
81+
const filePath =
82+
"/Users/yashsingh/p/sco/claude-code-extract/src/utils/permissions/PermissionRule.ts";
83+
const screen = await render(
84+
<ChatMarkdown text={`[PermissionRule.ts](file://${filePath}#L1C7)`} cwd="/repo/project" />,
85+
);
86+
87+
try {
88+
const link = page.getByRole("link", { name: "PermissionRule.ts · L1:C7" });
89+
await expect.element(link).toBeInTheDocument();
90+
await expect.element(link).toHaveAttribute("href", `${filePath}#L1C7`);
91+
92+
await link.click();
93+
94+
await vi.waitFor(() => {
95+
expect(openInPreferredEditorMock).toHaveBeenCalledWith(
96+
expect.anything(),
97+
`${filePath}:1:7`,
98+
);
99+
});
100+
} finally {
101+
await screen.unmount();
102+
}
103+
});
104+
105+
it("disambiguates duplicate file basenames inline", async () => {
106+
const firstPath = "/Users/yashsingh/p/t3code/apps/web/src/components/chat/MessagesTimeline.tsx";
107+
const secondPath = "/Users/yashsingh/p/t3code/apps/web/src/components/MessagesTimeline.tsx";
108+
const screen = await render(
109+
<ChatMarkdown
110+
text={`See [MessagesTimeline.tsx](file://${firstPath}) and [MessagesTimeline.tsx](file://${secondPath}).`}
111+
cwd="/repo/project"
112+
/>,
113+
);
114+
115+
try {
116+
await expect
117+
.element(page.getByRole("link", { name: "MessagesTimeline.tsx · components/chat" }))
118+
.toBeInTheDocument();
119+
await expect
120+
.element(page.getByRole("link", { name: "MessagesTimeline.tsx · src/components" }))
121+
.toBeInTheDocument();
122+
} finally {
123+
await screen.unmount();
124+
}
125+
});
126+
127+
it("keeps normal web links unchanged", async () => {
128+
const screen = await render(
129+
<ChatMarkdown text="[OpenAI](https://openai.com/docs)" cwd="/repo/project" />,
130+
);
131+
132+
try {
133+
const link = page.getByRole("link", { name: "OpenAI" });
134+
await expect.element(link).toBeInTheDocument();
135+
await expect.element(link).toHaveAttribute("href", "https://openai.com/docs");
136+
await expect.element(link).toHaveAttribute("target", "_blank");
137+
} finally {
138+
await screen.unmount();
139+
}
140+
});
79141
});

0 commit comments

Comments
 (0)