changeset 190:a2725419f988 hg-web

Updated so that bun builds will with already existing js files.
author MrJuneJune <me@mrjunejune.com>
date Sat, 24 Jan 2026 21:06:42 -0800
parents 32ce881452fa
children a06710325c30
files gui_ze/gui_ze.bzl hg-web/BUILD hg-web/main.c hg-web/src/build.ts hg-web/src/index.html hg-web/src/main.tsx hg-web/src/repo-browser.tsx markdown_converter/BUILD markdown_converter/markdown_to_html.c markdown_converter/markdown_to_html.css markdown_converter/wasm/BUILD markdown_converter/wasm/markdown_to_html_wasm.c markdown_converter/wasm/markdown_to_html_wasm.js third_party/bun/tsconfig.json
diffstat 14 files changed, 560 insertions(+), 993 deletions(-) [+]
line wrap: on
line diff
--- a/gui_ze/gui_ze.bzl	Fri Jan 23 22:50:28 2026 -0800
+++ b/gui_ze/gui_ze.bzl	Sat Jan 24 21:06:42 2026 -0800
@@ -91,6 +91,87 @@
     executable = True,
 )
 
+def _bun_bundle_impl(ctx):
+  """
+  Bundle TypeScript/JavaScript with Bun, resolving imports from bazel root.
+
+  Copies all dependencies (dereferencing symlinks) to create a flat structure
+  where imports can resolve correctly relative to the bazel workspace root.
+  """
+  out = ctx.actions.declare_file(ctx.label.name + ".js")
+
+  inputs = depset(
+    direct = [ctx.file.src],
+    transitive = [dep[DefaultInfo].files for dep in ctx.attr.deps]
+  )
+
+  # Get the source file's package directory (e.g., "hg-web" from "hg-web/src/main.tsx")
+  src_package = ctx.file.src.path.split("/")[0]
+
+  # Collect unique root directories to copy (deduped), excluding src_package (handled separately)
+  dirs_to_copy = {}
+  for f in ctx.files.deps:
+    # Find the root directory path by locating where short_path starts in full path
+    short_path_suffix = "/".join(f.short_path.split("/")[1:])
+    pos = f.path.find(short_path_suffix)
+    if pos > 0:
+      root_dir = f.path[:pos].rstrip("/")
+      # Skip src_package - it's a symlink that needs special handling
+      if not root_dir.endswith(src_package):
+        dirs_to_copy[root_dir] = True
+
+  # Build copy commands for each unique directory
+  copy_commands = ["cp -rL {dir} .".format(dir = d) for d in dirs_to_copy.keys()]
+
+  ctx.actions.run_shell(
+    inputs = inputs,
+    outputs = [out],
+    tools = [ctx.executable._bun] + [ctx.file.src] + ctx.files.deps + ctx.files.node_modules,
+    command = """
+cp -rL {src_package} {src_package}_tmp && rm -rf {src_package} && mv {src_package}_tmp {src_package}
+{copy_commands}
+export NODE_PATH=./third_party/bun/node_modules
+cp ./third_party/bun/tsconfig.json .
+{bun} build {entry} --outfile {output} --target browser
+""".format(
+      copy_commands = "\n".join(copy_commands),
+      src_package = src_package,
+      bun = ctx.executable._bun.path,
+      entry = ctx.file.src.path,
+      output = out.path,
+    ),
+    progress_message = "Bundling %s with Bun" % ctx.file.src.short_path,
+  )
+
+  return [DefaultInfo(files = depset([out]))]
+
+bun_bundle = rule(
+  implementation = _bun_bundle_impl,
+  attrs = {
+    "src": attr.label(
+      allow_single_file = [".ts", ".tsx", ".js", ".jsx"],
+      doc = "Entry point file to bundle",
+    ),
+    "deps": attr.label_list(
+      allow_files = True,
+      doc = "Source files and other dependencies to include",
+    ),
+    "node_modules": attr.label_list(
+      allow_files = True,
+      default = [Label("//third_party/bun:bun_files")],
+      doc = "Node modules for bundling (defaults to //third_party/bun:bun_files)",
+    ),
+    "_bun": attr.label(
+      default = Label("//third_party/bun:bun"),
+      executable = True,
+      cfg = "exec",
+    ),
+  },
+  doc = "Bundle TypeScript/JavaScript using Bun with bazel root imports",
+)
+
+
+
 def _bun_build_impl(ctx):
   """
   Run bun build on the folder
@@ -113,6 +194,7 @@
       && cp -r {src_folder}/** .  \
       && cp -r ./bazel-out/k8-fastbuild/bin/hg-web/src/** src \
       && ls src \
+      && pwd \
       && export NODE_PATH=./node_modules && {bun_path} build {input_path} --outfile {output_path} --target browser
       """.format(
       bun_path = ctx.executable._bun.path,
@@ -215,7 +297,7 @@
       arguments = [src.path, out.path],
     )
     outs.append(out)
-  return [DefaultInfo(files = depset(outs))]
+  return [DefaultInfo(files = depset(outs), runfiles = ctx.runfiles(files = outs))]
 
 move_files_into_dir = rule(
   implementation = _move_files_into_dir_impl,
--- a/hg-web/BUILD	Fri Jan 23 22:50:28 2026 -0800
+++ b/hg-web/BUILD	Sat Jan 24 21:06:42 2026 -0800
@@ -1,88 +1,66 @@
 load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
-load("//gui_ze:gui_ze.bzl", "move_files_into_dir", "bundle", "bun_build")
+load("//gui_ze:gui_ze.bzl", "move_files_into_dir", "bundle", "bun_bundle")
 
-# External
-move_files_into_dir(
-  name = "external_js_ts_moved",
-  srcs = [
-    "//markdown_converter:markdown_to_html_wasm",
-  ],
-  dest = "src",
-)
-
+# Source files
 filegroup(
-  name = "external_js_ts",
-  srcs = [":external_js_ts_moved"],
+  name = "src_ts_files",
+  srcs = glob([
+    "src/**/*.ts",
+    "src/**/*.tsx",
+    "src/**/*.js",
+    "src/**/*.jsx",
+  ], allow_empty = True),
 )
 
-# Internal
-filegroup(
-  name = "raw_file",
-  srcs = glob(["src/**"]),
-)
-
-filegroup(
-  name = "all_ts_files",
-  srcs = [":external_js_ts"] + glob([
-      "**/*.ts",
-      "**/*.tsx",
-      "**/*.js",
-      "**/*.jsx",
-  ], allow_empty=True)
-)
-
-# Generate js file...
-bun_build(
+# Bundle TypeScript with Bun
+bun_bundle(
   name = "page",
   src = "src/main.tsx",
-  src_folder = "hg-web",
-  data = [
-    "//third_party/bun:bun_files",
-    ":all_ts_files",
+  deps = [
+    ":src_ts_files",
+    "//markdown_converter:markdown_to_html_wasm",
   ],
   visibility = ["//visibility:public"],
 )
 
+# Prepare compiled assets
 move_files_into_dir(
-  name = "compiled_ts",
+  name = "compiled_js",
   srcs = [
     ":page",
+    "//markdown_converter:markdown_to_html_wasm",
+    "//third_party/highlight:js",
   ],
   dest = "src",
 )
 
 move_files_into_dir(
   name = "public_files",
-  srcs = [
-    "//mrjunejune:public_files"
-  ],
+  srcs = ["//mrjunejune:public_files"],
   dest = "src/public",
 )
 
 filegroup(
-  name = "src_files",
-  srcs = [":raw_file", ":compiled_ts", "public_files"],
+  name = "all_assets",
+  srcs = glob(["src/**"]) + [":compiled_js", ":public_files"],
 )
 
-# Binary
+# Server binaries
 cc_binary(
   name = "hg_web_server",
   srcs = ["main.c"],
-  deps = [
-    "//seobeo:seobeo",
-  ],
-  data = [":src_files"],
+  deps = ["//seobeo:seobeo"],
+  data = [":all_assets"],
+)
+
+cc_binary(
+  name = "hg_web_server_debug",
+  srcs = ["main.c"],
+  deps = ["//seobeo:seobeo"],
+  data = [":all_assets"],
 )
 
 bundle(
   name = "hg_web_server_bundle",
   binary = ":hg_web_server",
 )
-
-cc_binary(
-  name = "hg_web_server_debug",
-  srcs = ["main.c"],
-  deps = ["//seobeo:seobeo"],
-  data = [":src_files"],
-)
-
--- a/hg-web/main.c	Fri Jan 23 22:50:28 2026 -0800
+++ b/hg-web/main.c	Sat Jan 24 21:06:42 2026 -0800
@@ -154,7 +154,7 @@
   Seobeo_Log(SEOBEO_DEBUG, "ApiGetFile: status=%i body_len=%zu\n", hg_response->status_code, hg_response->body_length);
 
   char status[4];
-  snprintf(status, 3, "%i", hg_response->status_code);
+  snprintf(status, 4, "%i", hg_response->status_code);
 
   if (!hg_response->body)
   {
@@ -178,7 +178,7 @@
   char *temp2 = Dowa_Arena_Allocate(arena, 256);
   snprintf(temp2, 256, "%zu", hg_response->body_length);
 
-  Dowa_HashMap_Push_Arena(resp, "status", status, arena);
+  Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
   Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena);
   Dowa_HashMap_Push_Arena(resp, "body", temp1, arena);
   Dowa_HashMap_Push_Arena(resp, "content-length", temp2, arena);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hg-web/src/build.ts	Sat Jan 24 21:06:42 2026 -0800
@@ -0,0 +1,24 @@
+import { readdir } from "node:fs/promises";
+const files = await readdir(import.meta.dir);
+console.log(files);
+
+
+const outputPath = Bun.argv[2];
+
+if (!outputPath) {
+  console.error("Please provide an output path. Usage: bun build.ts <output_path>");
+  process.exit(1);
+}
+
+const build = await Bun.build({
+  entrypoints: ["./hg-web/src/main.tsx"], 
+  outdir: outputPath,
+  metafile: true, 
+});
+
+if (build.success) {
+  console.log(`Build successful! Files saved to: ${outputPath}`);
+  console.log(JSON.stringify(build.metafile, null, 2));
+} else {
+  console.error("Build failed:", build.logs);
+}
--- a/hg-web/src/index.html	Fri Jan 23 22:50:28 2026 -0800
+++ b/hg-web/src/index.html	Sat Jan 24 21:06:42 2026 -0800
@@ -1,40 +1,18 @@
 <!DOCTYPE html>
 <html lang="en">
-<head>
+  <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>Zenbu Repository</title>
+    <link rel="stylesheet" href="/a11y-dark.min.css"  media="(prefers-color-scheme: dark)">
+    <link rel="stylesheet" href="/a11y-light.min.css" media="(prefers-color-scheme: light)">
+    <script src="/highlight.min.js"></script>
     <link rel="icon" type="image/svg+xml" href="/public/epi_all_colors.svg">
-</head>
-<body>
+  </head>
+  <body>
     <main>
       <div id="root"></div>
-
-<!--        <div class="header">
-            <h1>Zenbu Repository</h1>
-            <p class="description">Browse and clone this mercurial repository</p>
-        </div>
-
-        <div class="clone-info">
-            <strong>Clone this repository:</strong><br>
-            <code>hg clone http://zenbu.babocoder.com/repo</code>
-        </div>
-
-        <div class="breadcrumb" id="breadcrumb"></div>
-
-        <div class="file-list" id="fileList"></div>
-
-        <div class="readme-section" id="readmeSection" style="display: none;">
-            <h2>README</h2>
-            <div class="readme-content" id="readmeContent"></div>
-        </div>
-
-        <div class="empty-state" id="emptyState" style="display: none;">
-            <p>No files found in this directory</p>
-        </div> -->
     </main>
-
-    <script src="/markdown_to_html.js"></script>
-    <script src="/page.js"></script>
-</body>
+    <script type="module" src="/page.js"></script>
+  </body>
 </html>
--- a/hg-web/src/main.tsx	Fri Jan 23 22:50:28 2026 -0800
+++ b/hg-web/src/main.tsx	Sat Jan 24 21:06:42 2026 -0800
@@ -1,6 +1,6 @@
 import React from 'react';
 import ReactDOM from 'react-dom/client';
-import { RepoBrowser } from "./repo-browser";
+import { RepoBrowser } from "hg-web/src/repo-browser";
 
 const root = ReactDOM.createRoot(document.getElementById('root'));
 
--- a/hg-web/src/repo-browser.tsx	Fri Jan 23 22:50:28 2026 -0800
+++ b/hg-web/src/repo-browser.tsx	Sat Jan 24 21:06:42 2026 -0800
@@ -1,7 +1,5 @@
-import React, { useState, useEffect } from 'react';
-import renderMarkdown from './markdown_to_html_bin.js';
-
-console.log(renderMarkdown);
+import React, { useState, useEffect, useRef } from 'react';
+import createMarkdownModule from 'markdown_converter/markdown_to_html_wasm/markdown_to_html_bin.js';
 
 // --- ICONS (Using CDN Links) ---
 const ICONS = {
@@ -273,7 +271,31 @@
 /**
  * Component: ReadmeViewer
  */
-function ReadmeViewer({ content }) {
+function ReadmeViewer({ content }: { content: string | null }) {
+  const contentRef = useRef<HTMLDivElement>(null);
+  const moduleRef = useRef<any>(null);
+  const [wasmReady, setWasmReady] = useState(false);
+
+  useEffect(() => {
+    createMarkdownModule().then((Module) => {
+      moduleRef.current = Module;
+      setWasmReady(true);
+    });
+  }, []);
+
+  useEffect(() => {
+    if (!content || !wasmReady || !contentRef.current || !moduleRef.current) return;
+
+    const Module = moduleRef.current;
+    const markdownToHtmlPtr = Module.cwrap('markdown_to_html', 'number', ['string']);
+    const markdownFree = Module.cwrap('markdown_free', null, ['number']);
+
+    const ptr = markdownToHtmlPtr(content);
+    const html = Module.UTF8ToString(ptr);
+    markdownFree(ptr);
+    contentRef.current.innerHTML = html;
+  }, [content, wasmReady]);
+
   if (!content) return null;
 
   return (
@@ -282,7 +304,9 @@
         <img src="https://img.icons8.com/material-outlined/24/000000/menu--v1.png" width="16" alt="" style={{opacity:0.5}} />
         README.md
       </div>
-      <div id="readmeContent"></div>
+      <div id="readmeContent" ref={contentRef}>
+        {!wasmReady && 'Loading...'}
+      </div>
     </div>
   );
 }
@@ -328,9 +352,12 @@
         : `${API_BASE}/list`;
       
       const response = await fetch(url);
-      const data = await response.json();
+      let data;
+      if (response.ok)
+        data = await response.json();
 
-      if (data.error) throw new Error(data.error);
+      if (data.error)
+        throw new Error(data.error);
       
       setContent({
         files: data.files || [],
@@ -345,16 +372,14 @@
   };
 
   const fetchReadme = async (path) => {
-    setReadme(null);
-    try {
-      const readmePath = path ? `${path}/README.md` : 'README.md';
-      const response = await fetch(`${API_BASE}/readme?path=${encodeURIComponent(readmePath)}`);
-      
-      if (response.ok) {
-        const text = await response.text();
-        setReadme(text);
-      }
-    } catch (err) { /* Silently fail */ }
+    const readmePath = path ? `${path}/README.md` : 'README.md';
+    const response = await fetch(`${API_BASE}/file?path=${encodeURIComponent(readmePath)}`);
+    console.log(response);
+    if (response.ok)
+    {
+      const text = await response.text();
+      setReadme(text);
+    }
   };
 
   return (
--- a/markdown_converter/BUILD	Fri Jan 23 22:50:28 2026 -0800
+++ b/markdown_converter/BUILD	Sat Jan 24 21:06:42 2026 -0800
@@ -38,6 +38,9 @@
     "-sALLOW_MEMORY_GROWTH",  # Allow memory to grow dynamically
     "-sEXPORTED_FUNCTIONS=['_markdown_to_html','_markdown_free','_markdown_get_length','_wasm_alloc','_wasm_free']",
     "-sEXPORTED_RUNTIME_METHODS=['cwrap','ccall','UTF8ToString','stringToUTF8','lengthBytesUTF8']",
+    "-sMODULARIZE",  # Output as factory function
+    "-sEXPORT_ES6",  # Use ES6 module exports
+    "-sENVIRONMENT=web",  # Browser-only code (no Node.js builtins)
   ],
   tags=["manual"],
 )
--- a/markdown_converter/markdown_to_html.c	Fri Jan 23 22:50:28 2026 -0800
+++ b/markdown_converter/markdown_to_html.c	Sat Jan 24 21:06:42 2026 -0800
@@ -84,6 +84,9 @@
   }
 }
 
+// Forward declaration
+static void process_inline(StringBuffer *buf, const char *text, size_t len);
+
 // Check if line starts with pattern (after trimming whitespace)
 static int starts_with(const char *line, const char *pattern)
 {
@@ -187,6 +190,157 @@
   return *line == '.' && line[1] == ' ';
 }
 
+// Check if line could be a table row (contains |)
+static int is_table_row(const char *line)
+{
+  line = skip_whitespace(line);
+  // Must contain at least one |
+  return strchr(line, '|') != NULL;
+}
+
+// Check if line is a table separator (|---|---|)
+static int is_table_separator(const char *line)
+{
+  line = skip_whitespace(line);
+  int has_dash = 0;
+  int has_pipe = 0;
+
+  while (*line) {
+    char c = *line;
+    if (c == '|') has_pipe = 1;
+    else if (c == '-') has_dash = 1;
+    else if (c == ':') ; // alignment marker, allowed
+    else if (isspace((unsigned char)c)) ; // whitespace allowed
+    else return 0; // invalid character for separator
+    line++;
+  }
+
+  return has_dash && has_pipe;
+}
+
+// Parse alignment from separator cell (e.g., ":---:", "---:", ":---")
+// Returns: 0 = left (default), 1 = center, 2 = right
+static int parse_alignment(const char *cell, size_t len)
+{
+  // Trim whitespace
+  while (len > 0 && isspace((unsigned char)*cell)) { cell++; len--; }
+  while (len > 0 && isspace((unsigned char)cell[len-1])) { len--; }
+
+  if (len == 0) return 0;
+
+  int left_colon = (cell[0] == ':');
+  int right_colon = (len > 0 && cell[len-1] == ':');
+
+  if (left_colon && right_colon) return 1; // center
+  if (right_colon) return 2; // right
+  return 0; // left (default)
+}
+
+// Count columns in a table row
+static int count_table_columns(const char *line)
+{
+  int count = 0;
+  int in_cell = 0;
+  line = skip_whitespace(line);
+
+  // Skip leading |
+  if (*line == '|') line++;
+
+  while (*line) {
+    if (*line == '|') {
+      count++;
+      in_cell = 0;
+    } else if (!isspace((unsigned char)*line)) {
+      in_cell = 1;
+    }
+    line++;
+  }
+
+  // Count last cell if there was content after last |
+  if (in_cell) count++;
+
+  return count > 0 ? count : 1;
+}
+
+// Parse table cells and call callback for each
+typedef void (*cell_callback)(StringBuffer *buf, const char *cell, size_t len, int align, int is_header);
+
+static void parse_table_row(StringBuffer *buf, const char *line, int *alignments, int num_cols, int is_header, cell_callback cb)
+{
+  line = skip_whitespace(line);
+
+  // Skip leading |
+  if (*line == '|') line++;
+
+  int col = 0;
+  const char *cell_start = line;
+
+  while (*line && col < num_cols) {
+    if (*line == '|' || *(line + 1) == '\0') {
+      // End of cell
+      size_t cell_len = line - cell_start;
+      if (*line != '|') cell_len++; // include last char if no trailing |
+
+      // Trim whitespace from cell
+      while (cell_len > 0 && isspace((unsigned char)*cell_start)) { cell_start++; cell_len--; }
+      while (cell_len > 0 && isspace((unsigned char)cell_start[cell_len-1])) { cell_len--; }
+
+      int align = (alignments && col < num_cols) ? alignments[col] : 0;
+      cb(buf, cell_start, cell_len, align, is_header);
+
+      col++;
+      cell_start = line + 1;
+    }
+    line++;
+  }
+
+  // Fill remaining columns with empty cells
+  while (col < num_cols) {
+    cb(buf, "", 0, alignments ? alignments[col] : 0, is_header);
+    col++;
+  }
+}
+
+static void emit_table_cell(StringBuffer *buf, const char *cell, size_t len, int align, int is_header)
+{
+  const char *tag = is_header ? "th" : "td";
+  const char *align_attr = "";
+
+  if (align == 1) align_attr = " style=\"text-align:center\"";
+  else if (align == 2) align_attr = " style=\"text-align:right\"";
+
+  buffer_append(buf, "<");
+  buffer_append(buf, tag);
+  buffer_append(buf, align_attr);
+  buffer_append(buf, ">");
+  process_inline(buf, cell, len);
+  buffer_append(buf, "</");
+  buffer_append(buf, tag);
+  buffer_append(buf, ">");
+}
+
+// Parse alignments from separator row
+static void parse_alignments(const char *line, int *alignments, int num_cols)
+{
+  line = skip_whitespace(line);
+  if (*line == '|') line++;
+
+  int col = 0;
+  const char *cell_start = line;
+
+  while (*line && col < num_cols) {
+    if (*line == '|' || *(line + 1) == '\0') {
+      size_t cell_len = line - cell_start;
+      if (*line != '|') cell_len++;
+
+      alignments[col] = parse_alignment(cell_start, cell_len);
+      col++;
+      cell_start = line + 1;
+    }
+    line++;
+  }
+}
+
 // Process inline markdown (bold, italic, code, links, strikethrough)
 static void process_inline(StringBuffer *buf, const char *text, size_t len)
 {
@@ -532,6 +686,79 @@
       continue;
     }
 
+    // Table: | col1 | col2 | followed by |---|---|
+    if (is_table_row(line)) {
+      // Peek at next line to see if it's a separator
+      const char *peek_ptr = ptr;
+      if (*peek_ptr == '\n') peek_ptr++;
+
+      const char *next_line_start = peek_ptr;
+      while (*peek_ptr && *peek_ptr != '\n') peek_ptr++;
+      size_t next_line_len = peek_ptr - next_line_start;
+
+      char *next_line = (char *)malloc(next_line_len + 1);
+      if (next_line) {
+        memcpy(next_line, next_line_start, next_line_len);
+        next_line[next_line_len] = '\0';
+
+        if (is_table_separator(next_line)) {
+          // It's a table!
+          int num_cols = count_table_columns(line);
+          int *alignments = (int *)calloc(num_cols, sizeof(int));
+
+          buffer_append(buf, "<table>");
+
+          // Header row
+          buffer_append(buf, "<thead><tr>");
+          parse_table_row(buf, line, NULL, num_cols, 1, emit_table_cell);
+          buffer_append(buf, "</tr></thead>");
+
+          free(line);
+          if (*ptr == '\n') ptr++;
+
+          // Parse alignments from separator
+          parse_alignments(next_line, alignments, num_cols);
+          free(next_line);
+
+          // Skip separator line
+          ptr = peek_ptr;
+          if (*ptr == '\n') ptr++;
+
+          // Body rows
+          buffer_append(buf, "<tbody>");
+
+          while (*ptr) {
+            line_start = ptr;
+            while (*ptr && *ptr != '\n') ptr++;
+            line_len = ptr - line_start;
+
+            line = (char *)malloc(line_len + 1);
+            if (!line) break;
+            memcpy(line, line_start, line_len);
+            line[line_len] = '\0';
+
+            if (!is_table_row(line) || is_empty_line(line)) {
+              ptr = line_start;
+              free(line);
+              break;
+            }
+
+            buffer_append(buf, "<tr>");
+            parse_table_row(buf, line, alignments, num_cols, 0, emit_table_cell);
+            buffer_append(buf, "</tr>");
+
+            free(line);
+            if (*ptr == '\n') ptr++;
+          }
+
+          buffer_append(buf, "</tbody></table>");
+          free(alignments);
+          continue;
+        }
+        free(next_line);
+      }
+    }
+
     // HTML block - pass through unchanged
     if (is_html_block_start(line)) {
       // Check if it's a script or style tag that needs special handling
@@ -607,6 +834,7 @@
           is_horizontal_rule(line) ||
           is_unordered_list(line) ||
           is_ordered_list(line) ||
+          is_table_row(line) ||
           is_html_block_start(line)) {
         ptr = line_start;
         free(line);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/markdown_converter/markdown_to_html.css	Sat Jan 24 21:06:42 2026 -0800
@@ -0,0 +1,133 @@
+/* markdown panel */
+#markdown {
+  flex: 1;
+  padding: 15px;
+  border: 1px solid rgba(var(--gray), 0.3);
+  border-radius: 4px;
+  background: var(--white);
+  color: rgb(var(--gray-dark));
+  overflow-y: auto;
+  font-size: 15px;
+}
+
+/* Markdown markdown styles */
+#markdown h1, #markdown h2, #markdown h3, #markdown h4, #markdown h5, #markdown h6 {
+  margin: 1em 0 0.5em 0;
+  color: rgb(var(--gray-dark));
+  line-height: 1.3;
+}
+
+#markdown h1:first-child, #markdown h2:first-child, #markdown h3:first-child {
+  margin-top: 0;
+}
+
+#markdown h1 { font-size: 1.8em; }
+#markdown h2 { font-size: 1.5em; }
+#markdown h3 { font-size: 1.25em; }
+#markdown h4 { font-size: 1.1em; }
+
+#markdown p {
+  margin: 0.8em 0;
+}
+
+#markdown ul, #markdown ol {
+  margin: 0.8em 0;
+  padding-left: 25px;
+}
+
+#markdown li {
+  margin: 0.3em 0;
+}
+
+#markdown code {
+  background: rgb(var(--gray-light));
+  padding: 2px 6px;
+  border-radius: 3px;
+  font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
+  font-size: 0.9em;
+  color: var(--accent);
+}
+
+#markdown pre {
+  background: var(--black);
+  color: var(--white);
+  padding: 15px;
+  border-radius: 6px;
+  overflow-x: auto;
+  margin: 1em 0;
+  line-height: 1.4;
+}
+
+#markdown pre code {
+  background: none;
+  color: inherit;
+  padding: 0;
+}
+
+#markdown blockquote {
+  border-left: 4px solid var(--accent);
+  padding: 10px 15px;
+  margin: 1em 0;
+  background: rgb(var(--gray-light));
+  color: rgb(var(--gray));
+  font-style: italic;
+}
+
+#markdown a {
+  color: var(--accent);
+  text-decoration: none;
+}
+
+#markdown a:hover {
+  text-decoration: underline;
+}
+
+#markdown hr {
+  border: none;
+  border-top: 1px solid rgb(var(--gray-light));
+  margin: 1.5em 0;
+}
+
+#markdown img {
+  max-width: 100%;
+  height: auto;
+  border-radius: 4px;
+}
+
+#markdown strong {
+  font-weight: 600;
+}
+
+#markdown em {
+  font-style: italic;
+}
+
+#markdown del {
+  text-decoration: line-through;
+  color: rgb(var(--gray));
+}
+
+/* Mobile */
+@media (max-width: 900px) {
+  body {
+    padding: 15px;
+  }
+
+  .container {
+    grid-template-columns: 1fr;
+    height: auto;
+    gap: 15px;
+  }
+
+  .panel {
+    min-height: 350px;
+  }
+
+  textarea {
+    min-height: 300px;
+  }
+
+  #markdown {
+    min-height: 300px;
+  }
+}
--- a/markdown_converter/wasm/BUILD	Fri Jan 23 22:50:28 2026 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-# load("@rules_cc//cc:defs.bzl", "cc_library")
-# load("@emsdk//emscripten_toolchain:wasm_rules.bzl", "wasm_cc_binary")
-# 
-# package(default_visibility = ["//visibility:public"])
-# 
-# cc_library(
-#   name = "hello-world",
-#   srcs = ["markdown_to_html_wasm.c"],
-# )
-# 
-# wasm_cc_binary(
-#   name = "markdown_to_html_wasm",
-#   cc_target = ":hello-world",
-# )
-# 
-# # JS to link wasm with 
-# filegroup(
-#   name = "markdown_to_html_wasm_js",
-#   srcs = glob([
-#       "**/*.js",
-#   ], allow_empty=True),
-# )
--- a/markdown_converter/wasm/markdown_to_html_wasm.c	Fri Jan 23 22:50:28 2026 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,698 +0,0 @@
-/**
- * Markdown to HTML Converter - Standalone WASM Implementation
- * No libc dependencies - can be compiled with: clang --target=wasm32
- */
-
-#define WASM_EXPORT __attribute__((visibility("default")))
-
-typedef unsigned long size_t;
-typedef int int32_t;
-
-// Simple bump allocator for WASM
-#define HEAP_SIZE (1024 * 1024)  // 1MB heap
-static char heap[HEAP_SIZE];
-static size_t heap_offset = 0;
-
-WASM_EXPORT void *malloc(size_t size)
-{
-  // Align to 8 bytes
-  size_t aligned_offset = (heap_offset + 7) & ~7;
-  if (aligned_offset + size > HEAP_SIZE) return 0;
-
-  void *ptr = &heap[aligned_offset];
-  heap_offset = aligned_offset + size;
-  return ptr;
-}
-
-WASM_EXPORT void free(void *ptr)
-{
-  // Simple bump allocator - no actual free
-  (void)ptr;
-}
-
-WASM_EXPORT void heap_reset(void)
-{
-  heap_offset = 0;
-}
-
-// String functions
-static size_t strlen(const char *s)
-{
-  size_t len = 0;
-  while (s[len]) len++;
-  return len;
-}
-
-static void *memcpy(void *dest, const void *src, size_t n)
-{
-  char *d = (char *)dest;
-  const char *s = (const char *)src;
-  while (n--) *d++ = *s++;
-  return dest;
-}
-
-static int isspace_c(int c)
-{
-  return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v';
-}
-
-static int isdigit_c(int c)
-{
-  return c >= '0' && c <= '9';
-}
-
-// String buffer for building HTML output
-typedef struct {
-  char  *data;
-  size_t length;
-  size_t capacity;
-} StringBuffer;
-
-static StringBuffer *buffer_create(size_t initial_capacity)
-{
-  StringBuffer *buf = (StringBuffer *)malloc(sizeof(StringBuffer));
-  if (!buf) return 0;
-
-  buf->data = (char *)malloc(initial_capacity);
-  if (!buf->data) return 0;
-
-  buf->data[0] = '\0';
-  buf->length = 0;
-  buf->capacity = initial_capacity;
-  return buf;
-}
-
-static void buffer_grow(StringBuffer *buf, size_t needed)
-{
-  if (buf->length + needed + 1 > buf->capacity) {
-    size_t new_capacity = buf->capacity * 2;
-    while (new_capacity < buf->length + needed + 1)
-      new_capacity *= 2;
-
-    char *new_data = (char *)malloc(new_capacity);
-    if (new_data) {
-      memcpy(new_data, buf->data, buf->length + 1);
-      buf->data = new_data;
-      buf->capacity = new_capacity;
-    }
-  }
-}
-
-static void buffer_append(StringBuffer *buf, const char *str)
-{
-  size_t len = strlen(str);
-  buffer_grow(buf, len);
-  memcpy(buf->data + buf->length, str, len + 1);
-  buf->length += len;
-}
-
-static void buffer_append_n(StringBuffer *buf, const char *str, size_t n)
-{
-  buffer_grow(buf, n);
-  memcpy(buf->data + buf->length, str, n);
-  buf->length += n;
-  buf->data[buf->length] = '\0';
-}
-
-static void buffer_append_char(StringBuffer *buf, char c)
-{
-  buffer_grow(buf, 1);
-  buf->data[buf->length++] = c;
-  buf->data[buf->length] = '\0';
-}
-
-// Check if line starts with pattern (after trimming whitespace)
-static int starts_with(const char *line, const char *pattern)
-{
-  while (*line && isspace_c(*line)) line++;
-  size_t plen = strlen(pattern);
-  for (size_t i = 0; i < plen; i++) {
-    if (line[i] != pattern[i]) return 0;
-  }
-  return 1;
-}
-
-// Count leading # characters
-static int count_heading_level(const char *line)
-{
-  int count = 0;
-  while (*line && isspace_c(*line)) line++;
-  while (line[count] == '#' && count < 6) count++;
-  if (count > 0 && line[count] == ' ') return count;
-  return 0;
-}
-
-// Skip whitespace
-static const char *skip_whitespace(const char *str)
-{
-  while (*str && isspace_c(*str)) str++;
-  return str;
-}
-
-// Check if line is empty
-static int is_empty_line(const char *line)
-{
-  while (*line) {
-    if (!isspace_c(*line)) return 0;
-    line++;
-  }
-  return 1;
-}
-
-// Check if line is horizontal rule
-static int is_horizontal_rule(const char *line)
-{
-  line = skip_whitespace(line);
-  char first = *line;
-  if (first != '-' && first != '*' && first != '_') return 0;
-
-  int count = 0;
-  while (*line) {
-    if (*line == first) count++;
-    else if (!isspace_c(*line)) return 0;
-    line++;
-  }
-  return count >= 3;
-}
-
-// Check if line is unordered list item
-static int is_unordered_list(const char *line)
-{
-  line = skip_whitespace(line);
-  return (*line == '-' || *line == '*' || *line == '+') && line[1] == ' ';
-}
-
-// Check if line is ordered list item
-static int is_ordered_list(const char *line)
-{
-  line = skip_whitespace(line);
-  while (*line && isdigit_c(*line)) line++;
-  return *line == '.' && line[1] == ' ';
-}
-
-// Check if line is a table row (starts with |)
-static int is_table_row(const char *line)
-{
-  line = skip_whitespace(line);
-  return *line == '|';
-}
-
-// Check if line is a table separator row (| --- | --- |)
-static int is_table_separator(const char *line)
-{
-  line = skip_whitespace(line);
-  if (*line != '|') return 0;
-  line++;
-
-  int has_dash = 0;
-  while (*line) {
-    if (*line == '-') has_dash = 1;
-    else if (*line == '|' || *line == ':' || isspace_c(*line)) { /* ok */ }
-    else return 0;
-    line++;
-  }
-  return has_dash;
-}
-
-// Forward declaration for process_inline
-static void process_inline(StringBuffer *buf, const char *text, size_t len);
-
-// Parse table cells from a row and append to buffer
-static void parse_table_row(StringBuffer *buf, const char *line, int is_header)
-{
-  const char *cell_tag = is_header ? "th" : "td";
-
-  buffer_append(buf, "<tr>");
-
-  line = skip_whitespace(line);
-  if (*line == '|') line++; // Skip leading |
-
-  while (*line) {
-    // Skip whitespace before cell content
-    while (*line && isspace_c(*line)) line++;
-
-    // Find cell end (next | or end of line)
-    const char *cell_start = line;
-    while (*line && *line != '|') line++;
-
-    // Trim trailing whitespace from cell
-    const char *cell_end = line;
-    while (cell_end > cell_start && isspace_c(*(cell_end - 1))) cell_end--;
-
-    size_t cell_len = cell_end - cell_start;
-
-    // Only output cell if we have content or more cells coming
-    if (cell_len > 0 || *line == '|') {
-      buffer_append(buf, "<");
-      buffer_append(buf, cell_tag);
-      buffer_append(buf, ">");
-      if (cell_len > 0) {
-        process_inline(buf, cell_start, cell_len);
-      }
-      buffer_append(buf, "</");
-      buffer_append(buf, cell_tag);
-      buffer_append(buf, ">");
-    }
-
-    if (*line == '|') line++; // Skip |
-
-    // Check if this was the trailing |
-    const char *rest = line;
-    while (*rest && isspace_c(*rest)) rest++;
-    if (!*rest) break; // End of line after trailing |
-  }
-
-  buffer_append(buf, "</tr>");
-}
-
-// Process inline markdown
-static void process_inline(StringBuffer *buf, const char *text, size_t len)
-{
-  size_t i = 0;
-
-  while (i < len) {
-    // Links: [text](url)
-    if (text[i] == '[') {
-      size_t link_start = i + 1;
-      size_t link_end = link_start;
-      while (link_end < len && text[link_end] != ']') link_end++;
-
-      if (link_end < len && link_end + 1 < len && text[link_end + 1] == '(') {
-        size_t url_start = link_end + 2;
-        size_t url_end = url_start;
-        while (url_end < len && text[url_end] != ')') url_end++;
-
-        if (url_end < len) {
-          buffer_append(buf, "<a href=\"");
-          buffer_append_n(buf, text + url_start, url_end - url_start);
-          buffer_append(buf, "\">");
-          buffer_append_n(buf, text + link_start, link_end - link_start);
-          buffer_append(buf, "</a>");
-          i = url_end + 1;
-          continue;
-        }
-      }
-    }
-
-    // Images: ![alt](url)
-    if (text[i] == '!' && i + 1 < len && text[i + 1] == '[') {
-      size_t alt_start = i + 2;
-      size_t alt_end = alt_start;
-      while (alt_end < len && text[alt_end] != ']') alt_end++;
-
-      if (alt_end < len && alt_end + 1 < len && text[alt_end + 1] == '(') {
-        size_t url_start = alt_end + 2;
-        size_t url_end = url_start;
-        while (url_end < len && text[url_end] != ')') url_end++;
-
-        if (url_end < len) {
-          buffer_append(buf, "<img src=\"");
-          buffer_append_n(buf, text + url_start, url_end - url_start);
-          buffer_append(buf, "\" alt=\"");
-          buffer_append_n(buf, text + alt_start, alt_end - alt_start);
-          buffer_append(buf, "\">");
-          i = url_end + 1;
-          continue;
-        }
-      }
-    }
-
-    // Bold: **text** or __text__
-    if ((text[i] == '*' && i + 1 < len && text[i + 1] == '*') ||
-        (text[i] == '_' && i + 1 < len && text[i + 1] == '_')) {
-      char marker = text[i];
-      size_t start = i + 2;
-      size_t end = start;
-      while (end + 1 < len && !(text[end] == marker && text[end + 1] == marker)) end++;
-
-      if (end + 1 < len) {
-        buffer_append(buf, "<strong>");
-        process_inline(buf, text + start, end - start);
-        buffer_append(buf, "</strong>");
-        i = end + 2;
-        continue;
-      }
-    }
-
-    // Strikethrough: ~~text~~
-    if (text[i] == '~' && i + 1 < len && text[i + 1] == '~') {
-      size_t start = i + 2;
-      size_t end = start;
-      while (end + 1 < len && !(text[end] == '~' && text[end + 1] == '~')) end++;
-
-      if (end + 1 < len) {
-        buffer_append(buf, "<del>");
-        process_inline(buf, text + start, end - start);
-        buffer_append(buf, "</del>");
-        i = end + 2;
-        continue;
-      }
-    }
-
-    // Italic: *text* or _text_
-    if ((text[i] == '*' || text[i] == '_') && i + 1 < len && !isspace_c(text[i + 1])) {
-      char marker = text[i];
-      size_t start = i + 1;
-      size_t end = start;
-      while (end < len && text[end] != marker) end++;
-
-      if (end < len && end > start) {
-        buffer_append(buf, "<em>");
-        process_inline(buf, text + start, end - start);
-        buffer_append(buf, "</em>");
-        i = end + 1;
-        continue;
-      }
-    }
-
-    // Inline code: `code`
-    if (text[i] == '`') {
-      size_t start = i + 1;
-      size_t end = start;
-      while (end < len && text[end] != '`') end++;
-
-      if (end < len) {
-        buffer_append(buf, "<code>");
-        buffer_append_n(buf, text + start, end - start);
-        buffer_append(buf, "</code>");
-        i = end + 1;
-        continue;
-      }
-    }
-
-    // HTML escape
-    if (text[i] == '<') {
-      buffer_append(buf, "&lt;");
-    } else if (text[i] == '>') {
-      buffer_append(buf, "&gt;");
-    } else if (text[i] == '&') {
-      buffer_append(buf, "&amp;");
-    } else {
-      buffer_append_char(buf, text[i]);
-    }
-    i++;
-  }
-}
-
-// Append heading tag
-static void append_heading_tag(StringBuffer *buf, int level, int closing)
-{
-  buffer_append_char(buf, '<');
-  if (closing) buffer_append_char(buf, '/');
-  buffer_append_char(buf, 'h');
-  buffer_append_char(buf, '0' + level);
-  buffer_append_char(buf, '>');
-}
-
-// Convert markdown to HTML
-WASM_EXPORT char *markdown_to_html(const char *markdown)
-{
-  if (!markdown) return 0;
-
-  StringBuffer *buf = buffer_create(4096);
-  if (!buf) return 0;
-
-  const char *ptr = markdown;
-  const char *line_start;
-
-  while (*ptr) {
-    line_start = ptr;
-
-    // Find end of line
-    while (*ptr && *ptr != '\n') ptr++;
-    size_t line_len = ptr - line_start;
-
-    // Create line copy
-    char *line = (char *)malloc(line_len + 1);
-    if (!line) return buf->data;
-    memcpy(line, line_start, line_len);
-    line[line_len] = '\0';
-
-    // Skip empty lines
-    if (is_empty_line(line)) {
-      if (*ptr == '\n') ptr++;
-      continue;
-    }
-
-    // Headings
-    int heading_level = count_heading_level(line);
-    if (heading_level > 0) {
-      const char *content = skip_whitespace(line);
-      while (*content == '#') content++;
-      content = skip_whitespace(content);
-
-      append_heading_tag(buf, heading_level, 0);
-      process_inline(buf, content, strlen(content));
-      append_heading_tag(buf, heading_level, 1);
-
-      if (*ptr == '\n') ptr++;
-      continue;
-    }
-
-    // Code block
-    if (starts_with(line, "```")) {
-      buffer_append(buf, "<pre><code>");
-      if (*ptr == '\n') ptr++;
-
-      while (*ptr) {
-        line_start = ptr;
-        while (*ptr && *ptr != '\n') ptr++;
-        line_len = ptr - line_start;
-
-        char *code_line = (char *)malloc(line_len + 1);
-        if (!code_line) break;
-        memcpy(code_line, line_start, line_len);
-        code_line[line_len] = '\0';
-
-        if (starts_with(code_line, "```")) {
-          if (*ptr == '\n') ptr++;
-          break;
-        }
-
-        for (size_t i = 0; i < line_len; i++) {
-          if (code_line[i] == '<') buffer_append(buf, "&lt;");
-          else if (code_line[i] == '>') buffer_append(buf, "&gt;");
-          else if (code_line[i] == '&') buffer_append(buf, "&amp;");
-          else buffer_append_char(buf, code_line[i]);
-        }
-        buffer_append_char(buf, '\n');
-
-        if (*ptr == '\n') ptr++;
-      }
-
-      buffer_append(buf, "</code></pre>");
-      continue;
-    }
-
-    // Blockquote
-    if (starts_with(line, ">")) {
-      buffer_append(buf, "<blockquote>");
-
-      while (1) {
-        const char *content = skip_whitespace(line);
-        if (*content == '>') content++;
-        content = skip_whitespace(content);
-        process_inline(buf, content, strlen(content));
-        buffer_append_char(buf, ' ');
-
-        if (*ptr == '\n') ptr++;
-        if (!*ptr) break;
-
-        line_start = ptr;
-        while (*ptr && *ptr != '\n') ptr++;
-        line_len = ptr - line_start;
-
-        line = (char *)malloc(line_len + 1);
-        if (!line) break;
-        memcpy(line, line_start, line_len);
-        line[line_len] = '\0';
-
-        if (!starts_with(line, ">")) {
-          ptr = line_start;
-          break;
-        }
-      }
-
-      buffer_append(buf, "</blockquote>");
-      continue;
-    }
-
-    // Horizontal rule
-    if (is_horizontal_rule(line)) {
-      buffer_append(buf, "<hr>");
-      if (*ptr == '\n') ptr++;
-      continue;
-    }
-
-    // Unordered list
-    if (is_unordered_list(line)) {
-      buffer_append(buf, "<ul>");
-
-      while (1) {
-        const char *content = skip_whitespace(line);
-        content += 2;
-
-        buffer_append(buf, "<li>");
-        process_inline(buf, content, strlen(content));
-        buffer_append(buf, "</li>");
-
-        if (*ptr == '\n') ptr++;
-        if (!*ptr) break;
-
-        line_start = ptr;
-        while (*ptr && *ptr != '\n') ptr++;
-        line_len = ptr - line_start;
-
-        line = (char *)malloc(line_len + 1);
-        if (!line) break;
-        memcpy(line, line_start, line_len);
-        line[line_len] = '\0';
-
-        if (!is_unordered_list(line)) {
-          ptr = line_start;
-          break;
-        }
-      }
-
-      buffer_append(buf, "</ul>");
-      continue;
-    }
-
-    // Ordered list
-    if (is_ordered_list(line)) {
-      buffer_append(buf, "<ol>");
-
-      while (1) {
-        const char *content = skip_whitespace(line);
-        while (*content && isdigit_c(*content)) content++;
-        if (*content == '.') content++;
-        content = skip_whitespace(content);
-
-        buffer_append(buf, "<li>");
-        process_inline(buf, content, strlen(content));
-        buffer_append(buf, "</li>");
-
-        if (*ptr == '\n') ptr++;
-        if (!*ptr) break;
-
-        line_start = ptr;
-        while (*ptr && *ptr != '\n') ptr++;
-        line_len = ptr - line_start;
-
-        line = (char *)malloc(line_len + 1);
-        if (!line) break;
-        memcpy(line, line_start, line_len);
-        line[line_len] = '\0';
-
-        if (!is_ordered_list(line)) {
-          ptr = line_start;
-          break;
-        }
-      }
-
-      buffer_append(buf, "</ol>");
-      continue;
-    }
-
-    // Table
-    if (is_table_row(line)) {
-      // Check if next line is a separator (to confirm this is a table)
-      const char *peek_ptr = ptr;
-      if (*peek_ptr == '\n') peek_ptr++;
-
-      const char *next_line_start = peek_ptr;
-      while (*peek_ptr && *peek_ptr != '\n') peek_ptr++;
-      size_t next_line_len = peek_ptr - next_line_start;
-
-      char *next_line = (char *)malloc(next_line_len + 1);
-      if (next_line) {
-        memcpy(next_line, next_line_start, next_line_len);
-        next_line[next_line_len] = '\0';
-
-        if (is_table_separator(next_line)) {
-          // It's a valid table
-          buffer_append(buf, "<table>");
-
-          // Header row
-          buffer_append(buf, "<thead>");
-          parse_table_row(buf, line, 1);
-          buffer_append(buf, "</thead>");
-
-          // Skip to after separator
-          if (*ptr == '\n') ptr++;
-          ptr = peek_ptr;
-          if (*ptr == '\n') ptr++;
-
-          // Body rows
-          buffer_append(buf, "<tbody>");
-          while (*ptr) {
-            line_start = ptr;
-            while (*ptr && *ptr != '\n') ptr++;
-            line_len = ptr - line_start;
-
-            line = (char *)malloc(line_len + 1);
-            if (!line) break;
-            memcpy(line, line_start, line_len);
-            line[line_len] = '\0';
-
-            if (!is_table_row(line) || is_empty_line(line)) {
-              ptr = line_start;
-              break;
-            }
-
-            parse_table_row(buf, line, 0);
-            if (*ptr == '\n') ptr++;
-          }
-          buffer_append(buf, "</tbody>");
-
-          buffer_append(buf, "</table>");
-          continue;
-        }
-      }
-    }
-
-    // Paragraph
-    buffer_append(buf, "<p>");
-
-    while (1) {
-      const char *content = skip_whitespace(line);
-      process_inline(buf, content, strlen(content));
-
-      if (*ptr == '\n') ptr++;
-      if (!*ptr) break;
-
-      line_start = ptr;
-      while (*ptr && *ptr != '\n') ptr++;
-      line_len = ptr - line_start;
-
-      line = (char *)malloc(line_len + 1);
-      if (!line) break;
-      memcpy(line, line_start, line_len);
-      line[line_len] = '\0';
-
-      if (is_empty_line(line) ||
-          count_heading_level(line) > 0 ||
-          starts_with(line, "```") ||
-          starts_with(line, ">") ||
-          is_horizontal_rule(line) ||
-          is_unordered_list(line) ||
-          is_ordered_list(line) ||
-          is_table_row(line)) {
-        ptr = line_start;
-        break;
-      }
-
-      buffer_append_char(buf, ' ');
-    }
-
-    buffer_append(buf, "</p>");
-  }
-
-  return buf->data;
-}
-
-// Get string length (for JS interop)
-WASM_EXPORT size_t markdown_strlen(const char *str)
-{
-  return str ? strlen(str) : 0;
-}
--- a/markdown_converter/wasm/markdown_to_html_wasm.js	Fri Jan 23 22:50:28 2026 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,143 +0,0 @@
-/**
- * Markdown to HTML Converter - WASM FFI Wrapper
- * Loads the C implementation via WebAssembly and provides JavaScript API
- *
- * Build the WASM with: bazel build //markdown_converter:markdown_to_html_wasm
- */
-
-class MarkdownConverterWasm {
-  constructor() {
-    this.wasm = undefined;
-    this.ready = false;
-  }
-
-  /**
-   * Initialize the WASM module
-   * @param {string} wasmPath - Path to the .wasm file
-   * @returns {Promise<void>}
-   */
-  async init(wasmPath = '/markdown_to_html_wasm.wasm') {
-    if (this.ready) return;
-
-    try {
-      this.wasm = await WebAssembly.instantiateStreaming(fetch(wasmPath), {
-        env: {}
-      });
-      this.ready = true;
-    } catch (err) {
-      console.error('Failed to load markdown WASM module:', err);
-      throw err;
-    }
-  }
-
-  /**
-   * Write string to WASM memory
-   * @private
-   */
-  _writeString(str) {
-    const encoder = new TextEncoder();
-    const bytes = encoder.encode(str + '\0');
-    const ptr = this.wasm.instance.exports.malloc(bytes.length);
-    const memory = new Uint8Array(this.wasm.instance.exports.memory.buffer);
-    memory.set(bytes, ptr);
-    return ptr;
-  }
-
-  /**
-   * Read string from WASM memory
-   * @private
-   */
-  _readString(ptr) {
-    const memory = new Uint8Array(this.wasm.instance.exports.memory.buffer);
-    let end = ptr;
-    while (memory[end] !== 0) end++;
-    const bytes = memory.slice(ptr, end);
-    const decoder = new TextDecoder();
-    return decoder.decode(bytes);
-  }
-
-  /**
-   * Reset the WASM heap (call between conversions to reclaim memory)
-   */
-  resetHeap() {
-    if (this.ready) {
-      this.wasm.instance.exports.heap_reset();
-    }
-  }
-
-  /**
-   * Convert markdown string to HTML string using WASM
-   * @param {string} markdown - The markdown text to convert
-   * @returns {string} The converted HTML
-   */
-  convert(markdown) {
-    if (!this.ready) {
-      throw new Error('WASM module not initialized. Call init() first.');
-    }
-
-    // Reset heap before each conversion to reclaim memory
-    this.resetHeap();
-
-    // Write markdown to WASM memory
-    const inputPtr = this._writeString(markdown);
-
-    // Call the C function
-    const outputPtr = this.wasm.instance.exports.markdown_to_html(inputPtr);
-
-    // Read the result
-    const html = this._readString(outputPtr);
-
-    return html;
-  }
-
-  /**
-   * Convert markdown to DOM elements (compatible with original API)
-   * @param {string} markdown - The markdown text to convert
-   * @returns {Array<HTMLElement>} Array of DOM elements
-   */
-  convertToElements(markdown) {
-    const html = this.convert(markdown);
-    const template = document.createElement('template');
-    template.innerHTML = html;
-    return Array.from(template.content.children);
-  }
-}
-
-// Singleton instance
-const markdownWasm = new MarkdownConverterWasm();
-
-/**
- * Convert markdown to DOM elements (WASM version)
- * Compatible with original markdownConverter() API
- * @param {string} value - Markdown string
- * @returns {Promise<Array<HTMLElement>>} Array of DOM elements
- */
-async function markdownConverterWasm(value) {
-  if (!markdownWasm.ready) {
-    await markdownWasm.init();
-  }
-  return markdownWasm.convertToElements(value);
-}
-
-/**
- * Render markdown to a container element (WASM version)
- * Compatible with original renderMarkdown() API
- * @param {HTMLElement} container - Container element
- * @param {string} markdown - Markdown string
- */
-async function renderMarkdownWasm(container, markdown) {
-  if (!markdownWasm.ready) {
-    await markdownWasm.init();
-  }
-  container.innerHTML = markdownWasm.convert(markdown);
-}
-
-// Export for use in other files
-if (typeof module !== 'undefined' && module.exports) {
-  module.exports = {
-    MarkdownConverterWasm,
-    markdownWasm,
-    markdownConverterWasm,
-    renderMarkdownWasm
-  };
-}
--- a/third_party/bun/tsconfig.json	Fri Jan 23 22:50:28 2026 -0800
+++ b/third_party/bun/tsconfig.json	Sat Jan 24 21:06:42 2026 -0800
@@ -1,29 +1,8 @@
 {
   "compilerOptions": {
-    // Environment setup & latest features
-    "lib": ["ESNext"],
-    "target": "ESNext",
-    "module": "Preserve",
-    "moduleDetection": "force",
-    "jsx": "react-jsx",
-    "allowJs": true,
-
-    // Bundler mode
-    "moduleResolution": "bundler",
-    "allowImportingTsExtensions": true,
-    "verbatimModuleSyntax": true,
-    "noEmit": true,
-
-    // Best practices
-    "strict": true,
-    "skipLibCheck": true,
-    "noFallthroughCasesInSwitch": true,
-    "noUncheckedIndexedAccess": true,
-    "noImplicitOverride": true,
-
-    // Some stricter flags (disabled by default)
-    "noUnusedLocals": false,
-    "noUnusedParameters": false,
-    "noPropertyAccessFromIndexSignature": false
+    "baseUrl": ".",
+    "paths": {
+      "*": ["*"]
+    }
   }
 }