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
6 changes: 6 additions & 0 deletions .changeset/neutral-deleted-line-numbers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"kilo-code": patch
"@kilocode/kilo-ui": patch
---

Keep deleted diff bars visible without coloring deleted line numbers.
10 changes: 8 additions & 2 deletions packages/kilo-ui/src/pierre/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import { createDefaultOptions } from "./index"

describe("Pierre diff options", () => {
test("keeps changed identifiers intact in unified and split diffs", () => {
expect(createDefaultOptions("unified").lineDiffType).toBe("word-alt")
expect(createDefaultOptions("split").lineDiffType).toBe("word-alt")
expect(createDefaultOptions("unified")).toMatchObject({
diffIndicators: "bars",
lineDiffType: "word-alt",
})
expect(createDefaultOptions("split")).toMatchObject({
diffIndicators: "bars",
lineDiffType: "word-alt",
})
})
})
12 changes: 7 additions & 5 deletions packages/kilo-ui/src/pierre/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ export interface DiffHandle {
// existing symbols. Word-alt keeps those logical additions visually intact.
export const LINE_DIFF_TYPE = "word-alt" as const

// Pierre 1.1 treats its changed-line override properties as tint targets. Apply
// Kilo semantic surfaces at the computed row level so host diff colors stay final.
// Keep Kilo semantic surfaces at the computed row level. Pierre's dedicated
// number override keeps deletion bars red without tinting line-number text.
const css = `
:host {
--diffs-fg-number-deletion-override: var(--diffs-fg-number-override, var(--diffs-fg));
--diffs-bg-deletion-override: var(--surface-diff-delete-base, var(--diffs-deletion-base));
}
[data-indicators='bars'] [data-column-number][data-line-type='change-deletion'] {
--diffs-deletion-base: var(--surface-diff-delete-strong, #ff6762);
}
Expand All @@ -36,9 +40,6 @@ const css = `
--diffs-computed-diff-line-bg: var(--surface-diff-delete-base, var(--diffs-bg-deletion));
--diffs-computed-selected-line-bg: var(--surface-diff-delete-base, var(--diffs-bg-deletion));
}
[data-diff] [data-column-number][data-line-type='change-deletion'] {
color: inherit;
}
[data-diff][data-background] [data-column-number][data-line-type='change-deletion'] {
--diffs-computed-diff-line-bg: var(--surface-diff-delete-weaker, var(--diffs-bg-deletion-number));
--diffs-computed-selected-line-bg: var(--surface-diff-delete-weaker, var(--diffs-bg-deletion-number));
Expand All @@ -49,6 +50,7 @@ export function createDefaultOptions<T>(style: FileDiffOptions<T>["diffStyle"])
const opts = defaults<T>(style)
return {
...opts,
diffIndicators: "bars" as const,
lineDiffType: LINE_DIFF_TYPE,
unsafeCSS: `${opts.unsafeCSS}\n${css}`,
}
Expand Down
93 changes: 57 additions & 36 deletions packages/kilo-ui/tests/diff-indicators.spec.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,64 @@
import { expect, test } from "@playwright/test"
import { expect, test, type Page } from "@playwright/test"
import { VSCODE_THEMES } from "../src/stories/vscode-themes"

for (const scheme of ["light", "dark"]) {
async function check(page: Page, width: number, globals: string) {
await page.setViewportSize({ width, height: 720 })
await page.goto(`/iframe.html?id=components-diff--default&viewMode=story&globals=${globals}`)

const diff = page.locator("[data-diff]").first()
await expect(diff).toBeVisible()
await expect(diff).toHaveAttribute("data-indicators", "bars")
if (width < 640) await expect(diff).toHaveAttribute("data-disable-line-numbers", "")
if (width > 640) await expect(diff).not.toHaveAttribute("data-disable-line-numbers", "")

for (const type of ["deletion", "addition"]) {
const gutter = page.locator(`[data-column-number][data-line-type='change-${type}']`).first()
await expect(gutter).toBeVisible()
await expect.poll(() => gutter.evaluate((element) => getComputedStyle(element, "::before").width)).toBe("4px")
await expect.poll(() => gutter.evaluate((element) => getComputedStyle(element, "::before").opacity)).toBe("1")
await expect.poll(() => gutter.evaluate((element) => getComputedStyle(element, "::before").height)).not.toBe("0px")
await expect.poll(() => gutter.evaluate((element) => getComputedStyle(element, "::before").content)).toBe('""')
if (type === "deletion") {
await expect
.poll(() => gutter.evaluate((element) => getComputedStyle(element, "::before").backgroundImage))
.not.toBe("none")
const context = page.locator("[data-column-number][data-line-type='context']").first()
await expect(context).toBeVisible()
await expect
.poll(() =>
gutter.evaluate((deletion) => {
const root = deletion.getRootNode() as ShadowRoot
const context = root.querySelector("[data-column-number][data-line-type='context']")
if (!deletion || !context) return false
const contextStyle = getComputedStyle(context)
const color = contextStyle.color || getComputedStyle((context.getRootNode() as ShadowRoot).host).color
return Boolean(color) && getComputedStyle(deletion).color === color
}),
)
.toBe(true)
await expect
.poll(() =>
gutter.evaluate((deletion) => {
return getComputedStyle(deletion).getPropertyValue("--diffs-fg-number-deletion-override").trim()
}),
)
.not.toBe("")
}
}
}

for (const scheme of ["light", "dark"] as const) {
for (const width of [420, 1000]) {
test(`diff bars in ${scheme} theme at ${width}px`, async ({ page }) => {
await page.setViewportSize({ width, height: 720 })
await page.goto(`/iframe.html?id=components-diff--default&viewMode=story&globals=colorScheme:${scheme}`)

const diff = page.locator("[data-diff]").first()
await expect(diff).toBeVisible()
if (width < 640) await expect(diff).toHaveAttribute("data-disable-line-numbers", "")
if (width > 640) await expect(diff).not.toHaveAttribute("data-disable-line-numbers", "")
await check(page, width, `colorScheme:${scheme}`)
})
}
}

for (const type of ["deletion", "addition"]) {
const gutter = page.locator(`[data-column-number][data-line-type='change-${type}']`).first()
await expect(gutter).toBeVisible()
await expect.poll(() => gutter.evaluate((element) => getComputedStyle(element, "::before").width)).toBe("4px")
const bar = await gutter.evaluate((element) => {
const style = getComputedStyle(element, "::before")
return {
width: parseFloat(style.width),
opacity: style.opacity,
height: parseFloat(style.height),
content: style.content,
base: getComputedStyle(element).getPropertyValue("--diffs-deletion-base").trim(),
}
})
expect(bar.width).toBe(4)
expect(bar.opacity).toBe("1")
expect(bar.height).toBeGreaterThan(0)
expect(bar.content).toBe('""')
if (type === "deletion") expect(bar.base).not.toBe(scheme === "dark" ? "#ff6762" : "#ff2e3f")
if (type === "deletion") {
const context = page.locator("[data-column-number][data-line-type='context']").first()
await expect(context).toBeVisible()
expect(await gutter.evaluate((element) => getComputedStyle(element).color)).toBe(
await context.evaluate((element) => getComputedStyle(element).color),
)
}
}
for (const [theme] of Object.entries(VSCODE_THEMES)) {
for (const width of [420, 1000]) {
test(`neutral deleted numbers in VS Code ${theme} at ${width}px`, async ({ page }) => {
await check(page, width, `theme:kilo-vscode;vscodeTheme:${theme}`)
})
}
}
Loading