# HG changeset patch # User MrJuneJune # Date 1769317602 28800 # Node ID a2725419f988b129c23fe8ac8eade8ee7811ef0f # Parent 32ce881452fa85330f0131daf9c7118981981760 Updated so that bun builds will with already existing js files. diff -r 32ce881452fa -r a2725419f988 gui_ze/gui_ze.bzl --- 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, diff -r 32ce881452fa -r a2725419f988 hg-web/BUILD --- 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"], -) - diff -r 32ce881452fa -r a2725419f988 hg-web/main.c --- 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); diff -r 32ce881452fa -r a2725419f988 hg-web/src/build.ts --- /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 "); + 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); +} diff -r 32ce881452fa -r a2725419f988 hg-web/src/index.html --- 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 @@ - + Zenbu Repository + + + - - + +
- -
- - - - + + diff -r 32ce881452fa -r a2725419f988 hg-web/src/main.tsx --- 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')); diff -r 32ce881452fa -r a2725419f988 hg-web/src/repo-browser.tsx --- 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(null); + const moduleRef = useRef(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 @@ README.md -
+
+ {!wasmReady && 'Loading...'} +
); } @@ -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 ( diff -r 32ce881452fa -r a2725419f988 markdown_converter/BUILD --- 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"], ) diff -r 32ce881452fa -r a2725419f988 markdown_converter/markdown_to_html.c --- 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, ""); +} + +// 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, ""); + + // Header row + buffer_append(buf, ""); + parse_table_row(buf, line, NULL, num_cols, 1, emit_table_cell); + buffer_append(buf, ""); + + 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, ""); + + 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, ""); + parse_table_row(buf, line, alignments, num_cols, 0, emit_table_cell); + buffer_append(buf, ""); + + free(line); + if (*ptr == '\n') ptr++; + } + + buffer_append(buf, "
"); + 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); diff -r 32ce881452fa -r a2725419f988 markdown_converter/markdown_to_html.css --- /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; + } +} diff -r 32ce881452fa -r a2725419f988 markdown_converter/wasm/BUILD --- 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), -# ) diff -r 32ce881452fa -r a2725419f988 markdown_converter/wasm/markdown_to_html_wasm.c --- 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, ""); - - 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, ""); - } - - 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, ""); -} - -// 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, ""); - buffer_append_n(buf, text + link_start, link_end - link_start); - buffer_append(buf, ""); - 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, "\"");"); - 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, ""); - process_inline(buf, text + start, end - start); - buffer_append(buf, ""); - 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, ""); - process_inline(buf, text + start, end - start); - buffer_append(buf, ""); - 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, ""); - process_inline(buf, text + start, end - start); - buffer_append(buf, ""); - 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, ""); - buffer_append_n(buf, text + start, end - start); - buffer_append(buf, ""); - i = end + 1; - continue; - } - } - - // HTML escape - if (text[i] == '<') { - buffer_append(buf, "<"); - } else if (text[i] == '>') { - buffer_append(buf, ">"); - } else if (text[i] == '&') { - buffer_append(buf, "&"); - } 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, "
");
-      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, "<");
-          else if (code_line[i] == '>') buffer_append(buf, ">");
-          else if (code_line[i] == '&') buffer_append(buf, "&");
-          else buffer_append_char(buf, code_line[i]);
-        }
-        buffer_append_char(buf, '\n');
-
-        if (*ptr == '\n') ptr++;
-      }
-
-      buffer_append(buf, "
"); - continue; - } - - // Blockquote - if (starts_with(line, ">")) { - buffer_append(buf, "
"); - - 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, "
"); - continue; - } - - // Horizontal rule - if (is_horizontal_rule(line)) { - buffer_append(buf, "
"); - if (*ptr == '\n') ptr++; - continue; - } - - // Unordered list - if (is_unordered_list(line)) { - buffer_append(buf, "
    "); - - while (1) { - const char *content = skip_whitespace(line); - content += 2; - - buffer_append(buf, "
  • "); - process_inline(buf, content, strlen(content)); - buffer_append(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 (!is_unordered_list(line)) { - ptr = line_start; - break; - } - } - - buffer_append(buf, "
"); - continue; - } - - // Ordered list - if (is_ordered_list(line)) { - buffer_append(buf, "
    "); - - while (1) { - const char *content = skip_whitespace(line); - while (*content && isdigit_c(*content)) content++; - if (*content == '.') content++; - content = skip_whitespace(content); - - buffer_append(buf, "
  1. "); - process_inline(buf, content, strlen(content)); - buffer_append(buf, "
  2. "); - - 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, "
"); - 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, ""); - - // Header row - buffer_append(buf, ""); - parse_table_row(buf, line, 1); - buffer_append(buf, ""); - - // Skip to after separator - if (*ptr == '\n') ptr++; - ptr = peek_ptr; - if (*ptr == '\n') ptr++; - - // Body rows - buffer_append(buf, ""); - 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, ""); - - buffer_append(buf, "
"); - continue; - } - } - } - - // Paragraph - buffer_append(buf, "

"); - - 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, "

"); - } - - return buf->data; -} - -// Get string length (for JS interop) -WASM_EXPORT size_t markdown_strlen(const char *str) -{ - return str ? strlen(str) : 0; -} diff -r 32ce881452fa -r a2725419f988 markdown_converter/wasm/markdown_to_html_wasm.js --- 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} - */ - 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} 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 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 - }; -} diff -r 32ce881452fa -r a2725419f988 third_party/bun/tsconfig.json --- 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": { + "*": ["*"] + } } }