refactor(web): replace :global(.line) with CSS module scoped class

Quentin Gliech and Claude Opus 4.6 (1M context) created

Use a Shiki transformer to replace the default "line" class with our
CSS module's scoped .line class. No more :global() needed — everything
is properly module-scoped.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Change summary

webui2/src/components/code/file-viewer.module.css |  8 ++++----
webui2/src/components/code/file-viewer.tsx        | 10 ++++++----
2 files changed, 10 insertions(+), 8 deletions(-)

Detailed changes

webui2/src/components/code/file-viewer.module.css 🔗

@@ -14,22 +14,22 @@
   display: block;
 }
 
-.code-content code > :global(.line) {
+.line {
   display: block;
   min-width: 100%;
   padding-right: 1rem;
 }
 
-.code-content code > :global(.line):first-child {
+.line:first-child {
   padding-top: 0.5rem;
 }
 
-.code-content code > :global(.line):last-child {
+.line:last-child {
   padding-bottom: 0.5rem;
 }
 
 /* Line numbers via ::before pseudo-element */
-.code-content code > :global(.line)::before {
+.line::before {
   content: attr(data-line-number);
   display: inline-block;
   width: 3rem;

webui2/src/components/code/file-viewer.tsx 🔗

@@ -94,13 +94,15 @@ function getLangEntry(path: string): LangEntry | undefined {
 function lineNumberTransformer(): ShikiTransformer {
   return {
     line(node, line) {
+      // Replace Shiki's "line" class with our CSS module class
+      node.properties["className"] = [styles["line"]!];
       node.properties["dataLineNumber"] = line;
-      // Append a \n text node inside each .line so copy-paste preserves newlines
+      // Append a \n text node so copy-paste preserves newlines
       // (we strip the inter-element whitespace nodes in code() below).
       node.children.push({ type: "text", value: "\n" });
     },
     // Remove whitespace text nodes between .line spans — they create
-    // empty anonymous table rows when using display: table-row.
+    // empty anonymous rows when using display: block.
     code(node) {
       node.children = node.children.filter(
         (c) => !(c.type === "text" && c.value.trim() === ""),
@@ -289,10 +291,10 @@ function CodeBlock({ selectedRange, onLineClick, children }: CodeBlockProps) {
   // targeting the CSS module's scoped class.
   const highlightStyle = (() => {
     if (!selectedRange) return null;
-    const scope = `.${styles["code-content"]}`;
+    const lineClass = styles["line"];
     const selectors: string[] = [];
     for (let i = selectedRange.start; i <= selectedRange.end; i++) {
-      selectors.push(`${scope} code > .line:nth-child(${i})`);
+      selectors.push(`.${lineClass}:nth-child(${i})`);
     }
     const rule = selectors.join(",");
     return <style>{`${rule}{background-color:rgba(255,235,59,0.3)}:root.dark ${rule}{background-color:rgba(255,235,59,0.15)}`}</style>;