('[data-slot="dialog-footer"]');
+ expect(content).not.toBeNull();
+ expect(content!.className).toContain("dbx-ddl-view-dialog");
+ expect(content!.className).toContain("max-w-sm");
+ // The dialog content element is rendered by the child DialogContent component
+ // through reka-ui's portal Teleport, so the parent scoped-style attribute
+ // never reaches it; the footer element is one level down and does get it.
+ expect(content!.hasAttribute(scopeId!)).toBe(false);
+ expect(footer!.hasAttribute(scopeId!)).toBe(true);
+ });
+
+ it("keeps the DDL dialog free of per-dialog legacy fallback rules", () => {
+ // The dialog content element is rendered through reka-ui's portal Teleport and
+ // never carries this component's scoped data-v attribute, so per-dialog rules
+ // are avoided entirely: width comes from the global sm:max-w-190 legacy entry
+ // and the footer layout from the global dialog-footer rule.
+ expect(ddlViewDialogSource).not.toContain("dbx-legacy-webview");
+ expect(ddlViewDialogSource).not.toContain("@media");
+ expect(ddlViewDialogSource).toContain('class="dbx-ddl-view-dialog sm:max-w-190"');
+ });
+});
diff --git a/apps/desktop/src/lib/ai/__tests__/aiCodeHighlighter.spec.ts b/apps/desktop/src/lib/ai/__tests__/aiCodeHighlighter.spec.ts
index 7e1d4ce8ee..121da84c11 100644
--- a/apps/desktop/src/lib/ai/__tests__/aiCodeHighlighter.spec.ts
+++ b/apps/desktop/src/lib/ai/__tests__/aiCodeHighlighter.spec.ts
@@ -25,4 +25,21 @@ describe("createAiShikiBlockCodeHighlighter", () => {
expect(html).not.toContain(" {
+ // Mirrors the exact wiring loadAiShikiHighlighter uses on WebKit engines
+ // that cannot compile the JavaScript engine's lookbehind patterns, so a
+ // broken shiki/wasm integration fails here instead of on a packaged app.
+ const [{ createHighlighterCore }, { createOnigurumaEngine }, { default: sql }, { default: githubLight }] = await Promise.all([import("shiki/core"), import("shiki/engine/oniguruma"), import("shiki/langs/sql.mjs"), import("shiki/themes/github-light.mjs")]);
+ const highlighter = await createHighlighterCore({
+ engine: await createOnigurumaEngine(import("shiki/wasm")),
+ langs: [sql],
+ themes: [githubLight],
+ });
+
+ const html = highlighter.codeToHtml("SELECT id FROM users", { lang: "sql", structure: "classic", theme: "github-light" });
+
+ expect(html).toContain('class="line"');
+ expect(html).toMatch(/style="color:#/);
+ }, 30_000);
});
diff --git a/apps/desktop/src/lib/ai/aiCodeHighlighter.ts b/apps/desktop/src/lib/ai/aiCodeHighlighter.ts
index b9ceb5f9d0..c92fa1033f 100644
--- a/apps/desktop/src/lib/ai/aiCodeHighlighter.ts
+++ b/apps/desktop/src/lib/ai/aiCodeHighlighter.ts
@@ -1,4 +1,5 @@
import type { AppThemeAppearance } from "@/lib/app/appTheme";
+import { supportsRegExpLookbehind } from "@/lib/ui/legacyWebView";
export type AiCodeHighlighter = (content: string, lang: string, appearance?: AppThemeAppearance) => string;
@@ -96,9 +97,15 @@ function getAiShikiHighlighter(): Promise {
}
async function loadAiShikiHighlighter(): Promise {
- const [{ createHighlighterCore }, { createJavaScriptRegexEngine }, githubDark, githubLight, bash, css, go, html, java, javascript, json, markdown, php, python, rust, shellscript, sql, tsx, typescript, vue, xml, yaml] = await Promise.all([
+ const [{ createHighlighterCore }, { createJavaScriptRegexEngine }, onigurumaEngineModules, githubDark, githubLight, bash, css, go, html, java, javascript, json, markdown, php, python, rust, shellscript, sql, tsx, typescript, vue, xml, yaml] = await Promise.all([
import("shiki/core"),
import("shiki/engine/javascript"),
+ // Old WebKit (Safari < 16.4) throws SyntaxError while compiling the
+ // lookbehind / named-group patterns the JavaScript engine generates from
+ // TextMate grammars (code snapshots and AI code blocks then lose all
+ // highlighting). Only fetch the heavier inlined-Oniguruma WASM bundle on
+ // engines that need it.
+ supportsRegExpLookbehind() ? Promise.resolve(null) : Promise.all([import("shiki/engine/oniguruma"), import("shiki/wasm")]),
import("shiki/themes/github-dark.mjs"),
import("shiki/themes/github-light.mjs"),
import("shiki/langs/bash.mjs"),
@@ -122,7 +129,7 @@ async function loadAiShikiHighlighter(): Promise {
]);
return createHighlighterCore({
- engine: createJavaScriptRegexEngine(),
+ engine: onigurumaEngineModules ? await onigurumaEngineModules[0].createOnigurumaEngine(onigurumaEngineModules[1]) : createJavaScriptRegexEngine(),
langs: [bash.default, css.default, go.default, html.default, java.default, javascript.default, json.default, markdown.default, php.default, python.default, rust.default, shellscript.default, sql.default, tsx.default, typescript.default, vue.default, xml.default, yaml.default],
themes: [githubDark.default, githubLight.default],
});
diff --git a/apps/desktop/src/lib/codeSnapshot/__tests__/codeSnapshot.spec.ts b/apps/desktop/src/lib/codeSnapshot/__tests__/codeSnapshot.spec.ts
index 0d0b965ac5..2627469761 100644
--- a/apps/desktop/src/lib/codeSnapshot/__tests__/codeSnapshot.spec.ts
+++ b/apps/desktop/src/lib/codeSnapshot/__tests__/codeSnapshot.spec.ts
@@ -119,6 +119,60 @@ describe("renderCodeSnapshotHtml", () => {
expect(html).not.toContain('class="dbx-code-snapshot__pre dbx-code-snapshot__pre--numbered"');
});
+ it("gives SQL table names their own color, distinct from quoted fields", async () => {
+ // Mirror shiki's classic structure: one .line span per source line.
+ highlightCode.mockImplementation((content: string) =>
+ content
+ .split(/\r\n|\r|\n/)
+ .map((line) => `${line}`)
+ .join(""),
+ );
+ const html = await renderCodeSnapshotHtml({ code: "SELECT `id`, `title`\nFROM `tb_book`;", lang: "sql" }, { appearance: "light" });
+
+ expect(html).toContain('data-sql-token="table"');
+ expect(html).toContain("color:#116329");
+ // Only the table name is marked; the selected fields keep the identifier color.
+ expect(html.match(/data-sql-token="table"/g)).toHaveLength(1);
+ expect(html).toMatch(/data-sql-token="table"[^>]*>`?tb_book`);
+ expect(html).not.toMatch(/data-sql-token="table"[^>]*>`?id);
+ });
+
+ it("marks every table across FROM lists, aliases and JOIN clauses", async () => {
+ highlightCode.mockImplementation((content: string) =>
+ content
+ .split(/\r\n|\r|\n/)
+ .map((line) => `${line}`)
+ .join(""),
+ );
+ const html = await renderCodeSnapshotHtml({ code: "SELECT *\nFROM users u, orders o\nJOIN items i ON i.order_id = u.id", lang: "sql" }, { appearance: "dark" });
+
+ expect(html).toContain("color:#7ee787");
+ for (const table of ["users", "orders", "items"]) {
+ expect(html).toMatch(new RegExp(`data-sql-token="table"[^>]*>${table}