fix: name the auto-approve commands by scope and repair CI failures

Restore packages/ui/src/components/markdown.css. Upstream moved markdown
styling into the new session-ui package and deleted this file, but Kilo still
ships packages/ui's markdown component to the VS Code webview, so the rules
went missing while their consumer stayed. The copy button lost
position: absolute and opacity: 0, so it rendered below every code block
instead of in its corner, and showed the copy and check icons at once because
nothing hid the check state.

The palette showed "Enable auto-approve permissions" next to "Enable
auto-approve mode", which reads as the same command twice. They are not: one
lasts for the TUI run and is what --auto and --yolo seed, the other saves a
rule to global config. Both now say which they are and carry a desc, which
the palette already renders. The saved one is enforced server side, so its
desc says it covers every client. No behaviour change, and the command names
are untouched so the slash aliases still work.

Tests no longer depend on the developer's credential store. Credential
imports Global.data/auth.json on startup, and the catalog, plugin and
models-dev layers built Credential.node without overriding Global, so results
changed depending on whether the machine was logged in. They now use a temp
data directory, as the integration suite already did. That suite in turn set
KILO_AUTH_CONTENT at module scope; bun shares one process across test files,
so it leaked into credential.test.ts, whose auth.json cases then read "{}"
and found no credentials. Scoped with beforeAll/afterAll.

macOS-only failures, all merge gaps in Kilo tests:

- sandbox/state.test.ts runs a script in a subprocess; the LayerNode
  migration put AppNodeBuilder.build in it without adding the import
- sandbox/session-tools.test.ts mocked MCP without clients(), which
  upstream's resource tools now call
- sandbox/session.test.ts built SessionV2 without binding SessionExecution

Generated files: schema.gen.ts was formatted by an older prettier than the
one main's dependency bump resolves, so the generator no longer reproduced it
and the migration check called the schema stale. packages/client was missing
Pty.sessionID and the compaction include field. Both regenerated.

Also corrected the listener comment: it claimed application services still
come from AppRuntime, but the SessionV2 and MoveSession builds sit inside
KiloListener's Layer.fresh boundary and self-provide their subtrees, which is
deliberate since both bind this listener's own LocationServiceMap.
This commit is contained in:
Johnny Amancio
2026-07-30 21:23:50 +02:00
parent a08d57da55
commit 76d06fd8ba
14 changed files with 413 additions and 26 deletions
+6
View File
@@ -1090,6 +1090,7 @@ export type SessionsHistoryOutput = {
readonly reason: "auto" | "manual"
readonly text: string
readonly recent: string
readonly include?: string | null
}
}
| {
@@ -1548,6 +1549,7 @@ export type SessionsEventsOutput =
readonly reason: "auto" | "manual"
readonly text: string
readonly recent: string
readonly include?: string | undefined
}
}
| {
@@ -2561,6 +2563,7 @@ export type PtysListOutput = {
readonly status: "running" | "exited"
readonly pid: number
readonly exitCode?: number
readonly sessionID?: string | null
}>
}
@@ -2620,6 +2623,7 @@ export type PtysCreateOutput = {
readonly status: "running" | "exited"
readonly pid: number
readonly exitCode?: number
readonly sessionID?: string | null
}
}
@@ -2645,6 +2649,7 @@ export type PtysGetOutput = {
readonly status: "running" | "exited"
readonly pid: number
readonly exitCode?: number
readonly sessionID?: string | null
}
}
@@ -2675,6 +2680,7 @@ export type PtysUpdateOutput = {
readonly status: "running" | "exited"
readonly pid: number
readonly exitCode?: number
readonly sessionID?: string | null
}
}
+24 -8
View File
@@ -238,16 +238,32 @@ export default {
`)
yield* tx.run(`CREATE UNIQUE INDEX \`event_aggregate_seq_idx\` ON \`event\` (\`aggregate_id\`,\`seq\`);`)
yield* tx.run(`CREATE INDEX \`event_aggregate_type_seq_idx\` ON \`event\` (\`aggregate_id\`,\`type\`,\`seq\`);`)
yield* tx.run(`CREATE UNIQUE INDEX \`permission_project_action_resource_idx\` ON \`permission\` (\`project_id\`,\`action\`,\`resource\`);`)
yield* tx.run(`CREATE INDEX \`message_session_time_created_id_idx\` ON \`message\` (\`session_id\`,\`time_created\`,\`id\`);`)
yield* tx.run(
`CREATE UNIQUE INDEX \`permission_project_action_resource_idx\` ON \`permission\` (\`project_id\`,\`action\`,\`resource\`);`,
)
yield* tx.run(
`CREATE INDEX \`message_session_time_created_id_idx\` ON \`message\` (\`session_id\`,\`time_created\`,\`id\`);`,
)
yield* tx.run(`CREATE INDEX \`part_message_id_id_idx\` ON \`part\` (\`message_id\`,\`id\`);`)
yield* tx.run(`CREATE INDEX \`part_session_idx\` ON \`part\` (\`session_id\`);`)
yield* tx.run(`CREATE INDEX \`session_input_session_pending_delivery_seq_idx\` ON \`session_input\` (\`session_id\`,\`promoted_seq\`,\`delivery\`,\`admitted_seq\`);`)
yield* tx.run(`CREATE UNIQUE INDEX \`session_input_session_admitted_seq_idx\` ON \`session_input\` (\`session_id\`,\`admitted_seq\`);`)
yield* tx.run(`CREATE UNIQUE INDEX \`session_input_session_promoted_seq_idx\` ON \`session_input\` (\`session_id\`,\`promoted_seq\`);`)
yield* tx.run(`CREATE UNIQUE INDEX \`session_message_session_seq_idx\` ON \`session_message\` (\`session_id\`,\`seq\`);`)
yield* tx.run(`CREATE INDEX \`session_message_session_type_seq_idx\` ON \`session_message\` (\`session_id\`,\`type\`,\`seq\`);`)
yield* tx.run(`CREATE INDEX \`session_message_session_time_created_id_idx\` ON \`session_message\` (\`session_id\`,\`time_created\`,\`id\`);`)
yield* tx.run(
`CREATE INDEX \`session_input_session_pending_delivery_seq_idx\` ON \`session_input\` (\`session_id\`,\`promoted_seq\`,\`delivery\`,\`admitted_seq\`);`,
)
yield* tx.run(
`CREATE UNIQUE INDEX \`session_input_session_admitted_seq_idx\` ON \`session_input\` (\`session_id\`,\`admitted_seq\`);`,
)
yield* tx.run(
`CREATE UNIQUE INDEX \`session_input_session_promoted_seq_idx\` ON \`session_input\` (\`session_id\`,\`promoted_seq\`);`,
)
yield* tx.run(
`CREATE UNIQUE INDEX \`session_message_session_seq_idx\` ON \`session_message\` (\`session_id\`,\`seq\`);`,
)
yield* tx.run(
`CREATE INDEX \`session_message_session_type_seq_idx\` ON \`session_message\` (\`session_id\`,\`type\`,\`seq\`);`,
)
yield* tx.run(
`CREATE INDEX \`session_message_session_time_created_id_idx\` ON \`session_message\` (\`session_id\`,\`time_created\`,\`id\`);`,
)
yield* tx.run(`CREATE INDEX \`session_message_time_created_idx\` ON \`session_message\` (\`time_created\`);`)
yield* tx.run(`CREATE INDEX \`session_project_idx\` ON \`session\` (\`project_id\`);`)
yield* tx.run(`CREATE INDEX \`session_workspace_idx\` ON \`session\` (\`workspace_id\`);`)
+27 -3
View File
@@ -1,8 +1,12 @@
import { describe, expect } from "bun:test"
import { afterAll, describe, expect } from "bun:test"
import { Effect, Fiber, Layer, Stream } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { Integration } from "@opencode-ai/core/integration"
import fs from "node:fs"
import os from "node:os"
import path from "node:path"
import { Credential } from "@opencode-ai/core/credential"
import { Global } from "@opencode-ai/core/global"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { EventV2 } from "@opencode-ai/core/event"
@@ -23,9 +27,25 @@ const locationLayer = Layer.succeed(
Location.Service,
Location.Service.of(location({ directory: AbsolutePath.make("test") })),
)
// kilocode_change - Credential imports Global.data/auth.json on startup, so without this the suite
// reads the developer's real credential store and its results depend on whether they are logged in.
const dataDirs: string[] = []
// Each Layer.fresh below rebuilds Credential, which re-imports data/auth.json, so a shared directory
// would carry credentials from one test into the next. Give every layer its own.
const globalLayer = () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "kilo-catalog-test-"))
dataDirs.push(dir)
return Global.layerWith({ data: dir })
}
afterAll(() => {
for (const dir of dataDirs) fs.rmSync(dir, { recursive: true, force: true })
})
const catalogLayer = AppNodeBuilder.build(
LayerNode.group([Catalog.node, EventV2.node, Credential.node, Integration.node, Policy.node]),
[[Location.node, locationLayer]],
[
[Location.node, locationLayer],
[Global.node, globalLayer()], // kilocode_change
],
)
const it = testEffect(catalogLayer)
@@ -48,7 +68,10 @@ describe("CatalogV2", () => {
it.effect("derives availability from active credentials without changing provider state", () => {
const integrationID = Integration.ID.make("test")
const localCatalogLayer = Layer.fresh(
AppNodeBuilder.build(LayerNode.group([Catalog.node, Credential.node]), [[Location.node, locationLayer]]),
AppNodeBuilder.build(LayerNode.group([Catalog.node, Credential.node]), [
[Location.node, locationLayer],
[Global.node, globalLayer()], // kilocode_change
]),
)
return Effect.gen(function* () {
@@ -79,6 +102,7 @@ describe("CatalogV2", () => {
const localCatalogLayer = Layer.fresh(
AppNodeBuilder.build(LayerNode.group([Catalog.node, Credential.node, Integration.node]), [
[Location.node, locationLayer],
[Global.node, globalLayer()], // kilocode_change
]),
)
+12 -2
View File
@@ -1,4 +1,4 @@
import { describe, expect } from "bun:test"
import { afterAll, beforeAll, describe, expect } from "bun:test"
import { Duration, Effect, Exit, Fiber, Scope, Stream } from "effect"
import * as TestClock from "effect/testing/TestClock"
import { Credential } from "@opencode-ai/core/credential"
@@ -16,8 +16,18 @@ import { testEffect } from "./lib/effect"
// them on every startup. testEffect rebuilds the layer per test, so without isolation one test's
// credentials reappear in the next as "Imported" (and the real developer store gets written to).
// KILO_AUTH_CONTENT is Kilo's process-local credential mode: it skips the auth.json dual-write.
process.env.KILO_AUTH_CONTENT ??= "{}"
// Set it around this file only. bun shares one process across test files, so setting it at module
// scope leaks into credential.test.ts, whose auth.json cases then read "{}" and see no credentials.
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "kilo-integration-test-"))
let previous: string | undefined
beforeAll(() => {
previous = process.env.KILO_AUTH_CONTENT
process.env.KILO_AUTH_CONTENT ??= "{}"
})
afterAll(() => {
if (previous === undefined) delete process.env.KILO_AUTH_CONTENT
else process.env.KILO_AUTH_CONTENT = previous
})
const it = testEffect(
AppNodeBuilder.build(LayerNode.group([Integration.node, Credential.node, EventV2.node]), [
[Global.node, Global.layerWith({ data: directory })],
+9
View File
@@ -15,9 +15,17 @@ import { Npm } from "@opencode-ai/core/npm"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Reference } from "@opencode-ai/core/reference"
import { SkillV2 } from "@opencode-ai/core/skill"
import { Global } from "@opencode-ai/core/global"
import { Effect, Layer } from "effect"
import fs from "node:fs"
import os from "node:os"
import path from "node:path"
import { tempLocationLayer } from "../fixture/location"
// kilocode_change - Credential imports Global.data/auth.json on startup, so without this the suite
// reads the developer's real credential store and its results depend on whether they are logged in.
const globalLayer = Global.layerWith({ data: fs.mkdtempSync(path.join(os.tmpdir(), "kilo-plugin-test-")) })
const npmLayer = Layer.succeed(
Npm.Service,
Npm.Service.of({
@@ -48,5 +56,6 @@ export const PluginTestLayer = AppNodeBuilder.build(
[
[Location.node, tempLocationLayer],
[Npm.node, npmLayer],
[Global.node, globalLayer], // kilocode_change
],
)
+10 -1
View File
@@ -1,5 +1,5 @@
import path from "path"
import { describe, expect } from "bun:test"
import { afterAll, describe, expect } from "bun:test"
import { Effect, Layer } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { Integration } from "@opencode-ai/core/integration"
@@ -14,6 +14,9 @@ import { ModelsDevPlugin } from "@opencode-ai/core/plugin/models-dev"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { location } from "../fixture/location"
import { Global } from "@opencode-ai/core/global"
import fs from "node:fs"
import os from "node:os"
import { testEffect } from "../lib/effect"
import { catalogHost, host, integrationHost } from "./host"
@@ -21,8 +24,14 @@ const locationLayer = Layer.succeed(
Location.Service,
Location.Service.of(location({ directory: AbsolutePath.make(import.meta.dir) })),
)
// kilocode_change - Catalog pulls Credential, which imports Global.data/auth.json on startup, so
// without this the suite reads the developer's real credential store.
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "kilo-modelsdev-test-"))
const globalLayer = Global.layerWith({ data: dataDir })
afterAll(() => fs.rmSync(dataDir, { recursive: true, force: true }))
const layer = AppNodeBuilder.build(LayerNode.group([Catalog.node, Integration.node, EventV2.node]), [
[Location.node, locationLayer],
[Global.node, globalLayer], // kilocode_change
])
const it = testEffect(layer)
@@ -279,10 +279,12 @@ export function init() {
name: "permission.allow_everything",
get title() {
return isAllowEverything(sync.data.config.permission)
? "Disable auto-approve mode"
: "Enable auto-approve mode"
? "Disable saved auto-approve"
: "Enable saved auto-approve"
},
desc: "Toggle auto-approve for all permission prompts, saved to global config",
// kilocode_change - the saved rule is server side, so it also stops VS Code, JetBrains and
// headless runs from prompting
desc: "Toggle auto-approve for all permission prompts, saved to global config and shared with every client",
category: "System",
slashName: "auto-approve",
slashAliases: ["autoapprove", "approve-all", "approveall"],
@@ -357,7 +357,13 @@ export function createListenerRoutes(corsOptions?: CorsOptions) {
provideKiloListenerRoutes(corsOptions),
// Upstream's v2 ServerApi groups declare location/session middleware and services that must be
// satisfied when the layer is built, not at request time, so the listener needs the same chain
// createRoutes uses. Application services still come from AppRuntime.
// createRoutes uses.
//
// These builds sit inside KiloListener's Layer.fresh boundary, so each one self-provides its own
// dependency subtree rather than resolving AppRuntime's. That is deliberate: SessionV2 is bound
// to this listener's LocationServiceMap and to SessionExecutionLocal, so it cannot be the
// process-wide instance. Everything the graph does not rebind (the nodes listed in AppLayer)
// still comes from AppRuntime, and the scope teardown releases the rest.
Layer.provide(sessionLocationLayer),
Layer.provide(locationLayer),
Layer.provide(PtyEnvironment.layer),
@@ -112,6 +112,7 @@ const plugin = Layer.mock(Plugin.Service)({
})
const mcp = Layer.mock(MCP.Service)({
tools: () => Effect.succeed({}),
clients: () => Effect.succeed({}), // kilocode_change - upstream's MCP resource tools probe the clients
})
const lsp = Layer.mock(LSP.Service)({
touchFile: () => Effect.void,
@@ -7,6 +7,7 @@ import { Deferred, Effect, Exit, Layer } from "effect"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { Database } from "@opencode-ai/core/database/database"
import { SessionV2 } from "@opencode-ai/core/session"
import { SessionExecution } from "@opencode-ai/core/session/execution"
import { BackgroundJob } from "@/background/job"
import { Bus } from "@/bus"
import { Config } from "@/config/config"
@@ -37,7 +38,7 @@ const it = testEffect(
Layer.provide(AppNodeBuilder.build(BackgroundJob.node)),
Layer.provide(AppNodeBuilder.build(Database.node)),
Layer.provide(AppNodeBuilder.build(EventV2Bridge.node)),
Layer.provide(AppNodeBuilder.build(SessionV2.node)),
Layer.provide(AppNodeBuilder.build(SessionV2.node, [[SessionExecution.node, SessionExecution.noopLayer]])), // kilocode_change
),
AppNodeBuilder.build(BackgroundJob.node),
Bus.layer,
@@ -138,7 +139,9 @@ describe("sandbox session cleanup", () => {
if (!status.available) return
const token = SandboxInheritance.issue({ sessionID: source.id, directory: dir, count: 1 })
const child = yield* provideInstance(worktree)(sessions.create({ title: "sandbox-child", sandboxInheritanceToken: token }))
const child = yield* provideInstance(worktree)(
sessions.create({ title: "sandbox-child", sandboxInheritanceToken: token }),
)
expect((yield* provideInstance(worktree)(SandboxPolicy.status(child.id))).enabled).toBe(true)
yield* provideInstance(dir)(SandboxPolicy.toggle(source.id))
@@ -19,7 +19,12 @@ import { TestInstance } from "../../fixture/fixture"
import { testEffect } from "../../lib/effect"
const it = testEffect(
Layer.mergeAll(Bus.layer, AppNodeBuilder.build(Config.node), AppNodeBuilder.build(CrossSpawnSpawner.node), AppNodeBuilder.build(Database.node)),
Layer.mergeAll(
Bus.layer,
AppNodeBuilder.build(Config.node),
AppNodeBuilder.build(CrossSpawnSpawner.node),
AppNodeBuilder.build(Database.node),
),
)
const linux = process.platform === "linux" ? test : test.skip
const posix = process.platform === "win32" ? test.skip : test
@@ -37,6 +42,7 @@ test("restores the session snapshot after a backend restart", async () => {
'import { Effect, Layer } from "effect"',
'import { Config } from "@/config/config"',
'import { Database } from "@opencode-ai/core/database/database"',
'import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"',
'import { InstanceRef } from "@/effect/instance-ref"',
'import * as SandboxPolicy from "@/kilocode/sandbox/policy"',
'import { SandboxStore } from "@/kilocode/sandbox/store"',
@@ -143,6 +149,7 @@ linux("reports configured network namespace availability", async () => {
'import { Effect, Layer } from "effect"',
'import { Config } from "@/config/config"',
'import { Database } from "@opencode-ai/core/database/database"',
'import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"',
'import { InstanceRef } from "@/effect/instance-ref"',
'import * as SandboxPolicy from "@/kilocode/sandbox/policy"',
'import { SessionID } from "@/session/schema"',
+9 -5
View File
@@ -954,14 +954,18 @@ function App(props: { onSnapshot?: () => Promise<string[]>; pluginHost: TuiPlugi
dialog.clear()
},
},
// kilocode_change - this toggles the in-memory mode that `--auto`/`--yolo` seed, and is the
// only way to leave it mid-session. Kilo also ships `permission.allow_everything`
// (kilocode/cli/cmd/tui/app.tsx), which persists a server-side allow rule instead. Two
// entries with different scope is confusing and should be consolidated onto the Kilo one.
// kilocode_change - titled by scope. This toggles the in-memory mode that `--auto`/`--yolo`
// seed, which lasts for the TUI process and survives switching sessions. It is also the only
// way to leave that mode without restarting; Kilo's `permission.allow_everything`
// (kilocode/cli/cmd/tui/app.tsx) saves a global rule instead. Consolidating the two is a
// follow-up that has to cover VS Code and JetBrains as well.
{
name: "permission.mode",
title:
local.permission.mode === "auto" ? "Disable auto-approve permissions" : "Enable auto-approve permissions",
local.permission.mode === "auto"
? "Disable auto-approve for this TUI run"
: "Enable auto-approve for this TUI run",
desc: "Auto-approve permission prompts until you exit the TUI, nothing is saved", // kilocode_change
category: "System",
run: () => {
local.permission.toggle()
+289
View File
@@ -0,0 +1,289 @@
[data-component="markdown"] {
/* Reset & Base Typography */
min-width: 0;
max-width: 100%;
overflow-wrap: break-word;
color: var(--text-strong);
font-family: var(--font-family-sans);
font-size: var(--font-size-base); /* 14px */
line-height: 160%;
/* Spacing for flow */
> *:first-child {
margin-top: 0;
}
> *:last-child {
margin-bottom: 0;
}
> [data-markdown-block]:first-child > *:first-child {
margin-top: 0;
}
> [data-markdown-block]:last-child > *:last-child {
margin-bottom: 0;
}
/* Headings: Same size, distinguished by color and spacing */
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: 14px;
color: var(--text-strong);
font-weight: var(--font-weight-medium);
margin-top: 0px;
margin-bottom: 24px;
line-height: var(--line-height-large);
}
/* Emphasis & Strong: Neutral strong color */
strong,
b {
color: var(--text-strong);
font-weight: var(--font-weight-medium);
}
/* Paragraphs */
p {
margin-bottom: 12px;
}
/* Links */
a {
color: var(--text-interactive-base);
text-decoration: none;
font-weight: inherit;
}
a:hover {
text-decoration: underline;
text-underline-offset: 2px;
}
/* Lists */
ul,
ol {
margin-top: 8px;
margin-bottom: 12px;
margin-inline-start: 0; /* kilocode_change */
padding-inline-start: 32px; /* kilocode_change */
list-style-position: outside;
}
ul {
list-style-type: disc;
}
ol {
list-style-type: decimal;
padding-inline-start: 2.25rem; /* kilocode_change */
}
li {
margin-bottom: 8px;
}
li > p:first-child {
display: inline;
margin: 0;
}
li > p + p {
display: block;
margin-top: 0.5rem;
}
li::marker {
color: var(--text-weak);
}
/* Nested lists spacing */
li > ul,
li > ol {
margin-top: 0.25rem;
margin-bottom: 0.25rem;
padding-inline-start: 1rem; /* Minimal indent for nesting only */ /* kilocode_change */
}
li > ol {
padding-inline-start: 1.75rem; /* kilocode_change */
}
/* Blockquotes */
blockquote {
border-inline-start: 2px solid var(--border-weak-base); /* kilocode_change */
margin: 1.5rem 0;
padding-inline-start: 0.5rem; /* kilocode_change */
color: var(--text-weak);
font-style: normal;
}
/* Horizontal Rule - Invisible spacing only */
hr {
border: none;
height: 0;
margin: 40px 0;
}
.shiki {
background: var(--color-background-stronger);
color: var(--text-base);
font-size: 13px;
padding: 12px;
border-radius: 6px;
border: 0.5px solid var(--border-weak-base);
}
[data-component="markdown-code"] {
position: relative;
}
[data-slot="markdown-copy-button"] {
position: absolute;
top: 4px;
right: 4px;
opacity: 0;
transition: opacity 0.15s ease;
z-index: 1;
&::after {
content: attr(data-tooltip);
position: absolute;
left: 50%;
bottom: calc(100% + 4px);
transform: translateX(-50%);
z-index: 1000;
max-width: 320px;
border-radius: var(--radius-sm);
background: var(--surface-float-base);
color: var(--text-invert-strong);
padding: 2px 8px;
border: 1px solid var(--border-weak-base, rgba(0, 0, 0, 0.07));
box-shadow: var(--shadow-md);
pointer-events: none;
white-space: nowrap;
font-family: var(--font-family-sans);
font-size: var(--font-size-small);
font-style: normal;
font-weight: var(--font-weight-medium);
line-height: var(--line-height-large);
letter-spacing: var(--letter-spacing-normal);
opacity: 0;
transition: opacity 0.15s ease;
}
}
[data-slot="markdown-copy-button"]:hover::after,
[data-slot="markdown-copy-button"]:focus-visible::after {
opacity: 1;
}
[data-slot="markdown-copy-button"][data-variant="secondary"] {
box-shadow: none;
border: 1px solid var(--border-weak-base);
}
[data-slot="markdown-copy-button"][data-variant="secondary"] [data-slot="icon-svg"] {
color: var(--icon-base);
}
[data-component="markdown-code"]:hover [data-slot="markdown-copy-button"] {
opacity: 1;
}
[data-slot="markdown-copy-button"] [data-slot="check-icon"] {
display: none;
}
[data-slot="markdown-copy-button"][data-copied="true"] [data-slot="copy-icon"] {
display: none;
}
[data-slot="markdown-copy-button"][data-copied="true"] [data-slot="check-icon"] {
display: inline-flex;
}
pre {
margin-top: 12px;
margin-bottom: 32px;
overflow: auto;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
:not(pre) > code {
font-family: var(--font-family-mono);
font-feature-settings: var(--font-family-mono--font-feature-settings);
color: var(--syntax-string);
font-weight: var(--font-weight-medium);
/* font-size: 13px; */
/* padding: 2px 2px; */
/* margin: 0 1.5px; */
/* border-radius: 2px; */
/* background: var(--surface-base); */
/* box-shadow: 0 0 0 0.5px var(--border-weak-base); */
/* kilocode_change start */
&.file-link {
cursor: pointer;
text-decoration-line: underline;
text-decoration-style: dotted;
text-underline-offset: 2px;
transition: color 0.15s ease;
&:hover {
color: var(--text-interactive-base);
text-decoration-style: solid;
}
}
/* kilocode_change end */
}
/* Tables */
table {
width: 100%;
border-collapse: collapse;
margin: 24px 0;
font-size: var(--font-size-base);
display: block;
overflow-x: auto;
}
th,
td {
/* Minimal borders for structure, matching TUI "lines" roughly but keeping it web-clean */
border-bottom: 1px solid var(--border-weaker-base);
padding: 12px;
text-align: start; /* kilocode_change */
vertical-align: top;
}
th {
color: var(--text-strong);
font-weight: var(--font-weight-medium);
border-bottom: 1px solid var(--border-weak-base);
}
/* Images */
img {
max-width: 100%;
height: auto;
border-radius: 4px;
margin: 1.5rem 0;
display: block;
}
}
[data-component="markdown"] a.external-link:hover > code {
text-decoration: underline;
text-underline-offset: 2px;
}
+1
View File
@@ -33,6 +33,7 @@
@import "../components/inline-input.css" layer(components);
@import "../components/list.css" layer(components);
@import "../components/logo.css" layer(components);
@import "../components/markdown.css" layer(components);
@import "../kilocode/markdown-mermaid.css" layer(components); /* kilocode_change */
@import "../components/message-part.css" layer(components);
@import "../components/message-nav.css" layer(components);