# HG changeset patch # User MrJuneJune # Date 1769236739 28800 # Node ID 8cf4ec5e2191cb02122ccce556a410b52cb0589d # Parent fed99fc04e12612fa763acdf6d530c799c0c32c2# Parent dfdd6682539689c2b66b9d80ad0b0a4d3ea7b76c Fixed merge conflict. diff -r dfdd66825396 -r 8cf4ec5e2191 MODULE.bazel.lock --- a/MODULE.bazel.lock Fri Jan 23 22:22:30 2026 -0800 +++ b/MODULE.bazel.lock Fri Jan 23 22:38:59 2026 -0800 @@ -10,6 +10,13 @@ "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", "https://bcr.bazel.build/modules/abseil-cpp/20240116.2/MODULE.bazel": "73939767a4686cd9a520d16af5ab440071ed75cec1a876bf2fcfaf1f71987a16", + "https://bcr.bazel.build/modules/abseil-cpp/20250127.0/MODULE.bazel": "d1086e248cda6576862b4b3fe9ad76a214e08c189af5b42557a6e1888812c5d5", + "https://bcr.bazel.build/modules/abseil-cpp/20250127.1/MODULE.bazel": "c4a89e7ceb9bf1e25cf84a9f830ff6b817b72874088bf5141b314726e46a57c1", + "https://bcr.bazel.build/modules/abseil-cpp/20250512.1/MODULE.bazel": "d209fdb6f36ffaf61c509fcc81b19e81b411a999a934a032e10cd009a0226215", + "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/MODULE.bazel": "51f2312901470cdab0dbdf3b88c40cd21c62a7ed58a3de45b365ddc5b11bcab2", + "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/source.json": "cea3901d7e299da7320700abbaafe57a65d039f10d0d7ea601c4a66938ea4b0c", + "https://bcr.bazel.build/modules/abseil-py/2.1.0/MODULE.bazel": "5ebe5bf853769c65707e5c28f216798f7a4b1042015e6a36e6d03094d94bec8a", + "https://bcr.bazel.build/modules/abseil-py/2.1.0/source.json": "0e8fc4f088ce07099c1cd6594c20c7ddbb48b4b3c0849b7d94ba94be88ff042b", "https://bcr.bazel.build/modules/abseil-cpp/20250127.1/MODULE.bazel": "c4a89e7ceb9bf1e25cf84a9f830ff6b817b72874088bf5141b314726e46a57c1", "https://bcr.bazel.build/modules/abseil-cpp/20250512.1/MODULE.bazel": "d209fdb6f36ffaf61c509fcc81b19e81b411a999a934a032e10cd009a0226215", "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/MODULE.bazel": "51f2312901470cdab0dbdf3b88c40cd21c62a7ed58a3de45b365ddc5b11bcab2", @@ -20,6 +27,11 @@ "https://bcr.bazel.build/modules/apple_support/1.21.1/MODULE.bazel": "5809fa3efab15d1f3c3c635af6974044bac8a4919c62238cce06acee8a8c11f1", "https://bcr.bazel.build/modules/apple_support/1.24.2/MODULE.bazel": "0e62471818affb9f0b26f128831d5c40b074d32e6dda5a0d3852847215a41ca4", "https://bcr.bazel.build/modules/apple_support/1.24.2/source.json": "2c22c9827093250406c5568da6c54e6fdf0ef06238def3d99c71b12feb057a8d", + "https://bcr.bazel.build/modules/aspect_bazel_lib/1.42.3/MODULE.bazel": "e4529e12d8cd5b828e2b5960d07d3ec032541740d419d7d5b859cabbf5b056f9", + "https://bcr.bazel.build/modules/aspect_bazel_lib/1.42.3/source.json": "80cb66069ad626e0921555cd2bf278286fd7763fae2450e564e351792e8303f4", + "https://bcr.bazel.build/modules/aspect_rules_js/1.42.0/MODULE.bazel": "f19e6b4a16f77f8cf3728eac1f60dbfd8e043517fd4f4dbf17a75a6c50936d62", + "https://bcr.bazel.build/modules/aspect_rules_js/1.42.0/source.json": "abbb3eac3b6af76b8ce230a9a901c6d08d93f4f5ffd55314bf630827dddee57e", + "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.14.0/MODULE.bazel": "2b31ffcc9bdc8295b2167e07a757dbbc9ac8906e7028e5170a3708cecaac119f", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.2/MODULE.bazel": "30dfabbfae0139b1f0036e01c201dd4c0167da3017f0b7ef3820d78e07622989", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.2/source.json": "89d8e5d7088ae33972733099b756dae71e1647ae684ab50b26adfa853c506d01", @@ -42,6 +54,7 @@ "https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9", "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", "https://bcr.bazel.build/modules/bazel_features/1.33.0/MODULE.bazel": "8b8dc9d2a4c88609409c3191165bccec0e4cb044cd7a72ccbe826583303459f6", + "https://bcr.bazel.build/modules/bazel_features/1.33.0/source.json": "13617db3930328c2cd2807a0f13d52ca870ac05f96db9668655113265147b2a6", "https://bcr.bazel.build/modules/bazel_features/1.39.0/MODULE.bazel": "28739425c1fc283c91931619749c832b555e60bcd1010b40d8441ce0a5cf726d", "https://bcr.bazel.build/modules/bazel_features/1.39.0/source.json": "f63cbeb4c602098484d57001e5a07d31cb02bbccde9b5e2c9bf0b29d05283e93", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", @@ -63,6 +76,21 @@ "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb", + "https://bcr.bazel.build/modules/bazel_worker_api/0.0.1/MODULE.bazel": "02a13b77321773b2042e70ee5e4c5e099c8ddee4cf2da9cd420442c36938d4bd", + "https://bcr.bazel.build/modules/bazel_worker_api/0.0.4/MODULE.bazel": "460aa12d01231a80cce03c548287b433b321d205b0028ae596728c35e5ee442e", + "https://bcr.bazel.build/modules/bazel_worker_api/0.0.4/source.json": "d353c410d47a8b65d09fa98e83d57ebec257a2c2b9c6e42d6fda1cb25e5464a5", + "https://bcr.bazel.build/modules/bazel_worker_java/0.0.4/MODULE.bazel": "82494a01018bb7ef06d4a17ec4cd7a758721f10eb8b6c820a818e70d669500db", + "https://bcr.bazel.build/modules/bazel_worker_java/0.0.4/source.json": "a2d30458fd86cf022c2b6331e652526fa08e17573b2f5034a9dbcacdf9c2583c", + "https://bcr.bazel.build/modules/buildozer/8.2.1/MODULE.bazel": "61e9433c574c2bd9519cad7fa66b9c1d2b8e8d5f3ae5d6528a2c2d26e68d874d", + "https://bcr.bazel.build/modules/buildozer/8.2.1/source.json": "7c33f6a26ee0216f85544b4bca5e9044579e0219b6898dd653f5fb449cf2e484", + "https://bcr.bazel.build/modules/emsdk/4.0.17/MODULE.bazel": "a10c49d7063e5dffeec0c5b43753ab2575179fbb679851059861af7d2a781c6a", + "https://bcr.bazel.build/modules/emsdk/4.0.17/source.json": "8463664bc6319425d29ef0d7edd8660daf1bf0ec33c367420243d4db35e0f482", + "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8", + "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350", + "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a", + "https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0", + "https://bcr.bazel.build/modules/gazelle/0.40.0/MODULE.bazel": "42ba5378ebe845fca43989a53186ab436d956db498acde790685fe0e8f9c6146", + "https://bcr.bazel.build/modules/gazelle/0.40.0/source.json": "1e5ef6e4d8b9b6836d93273c781e78ff829ea2e077afef7a57298040fa4f010a", "https://bcr.bazel.build/modules/buildifier_prebuilt/7.1.2/MODULE.bazel": "d18b017dddf219626ea5b04028ff0db2397655fbdaa9d9258a6443ddafb303ab", "https://bcr.bazel.build/modules/buildifier_prebuilt/7.1.2/source.json": "5fb3a2433f6508f4b23934eaa2986d604ae65aff95d7476d1273bf5cd434eedc", "https://bcr.bazel.build/modules/buildozer/8.2.1/MODULE.bazel": "61e9433c574c2bd9519cad7fa66b9c1d2b8e8d5f3ae5d6528a2c2d26e68d874d", @@ -103,6 +131,9 @@ "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", "https://bcr.bazel.build/modules/protobuf/29.1/MODULE.bazel": "557c3457560ff49e122ed76c0bc3397a64af9574691cb8201b4e46d4ab2ecb95", "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/3.19.2/MODULE.bazel": "532ffe5f2186b69fdde039efe6df13ba726ff338c6bc82275ad433013fa10573", + "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", + "https://bcr.bazel.build/modules/protobuf/31.1/MODULE.bazel": "379a389bb330b7b8c1cdf331cc90bf3e13de5614799b3b52cdb7c6f389f6b38e", "https://bcr.bazel.build/modules/protobuf/32.1/MODULE.bazel": "89cd2866a9cb07fee9ff74c41ceace11554f32e0d849de4e23ac55515cfada4d", "https://bcr.bazel.build/modules/protobuf/33.4/MODULE.bazel": "114775b816b38b6d0ca620450d6b02550c60ceedfdc8d9a229833b34a223dc42", "https://bcr.bazel.build/modules/protobuf/33.4/source.json": "555f8686b4c7d6b5ba731fbea13bf656b4bfd9a7ff629c1d9d3f6e1d6155de79", @@ -149,7 +180,9 @@ "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", + "https://bcr.bazel.build/modules/rules_java/7.4.0/MODULE.bazel": "a592852f8a3dd539e82ee6542013bf2cadfc4c6946be8941e189d224500a8934", "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://bcr.bazel.build/modules/rules_java/8.13.0/MODULE.bazel": "0444ebf737d144cf2bb2ccb368e7f1cce735264285f2a3711785827c1686625e", "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", @@ -158,9 +191,11 @@ "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", + "https://bcr.bazel.build/modules/rules_jvm_external/6.2/MODULE.bazel": "36a6e52487a855f33cb960724eb56547fa87e2c98a0474c3acad94339d7f8e99", "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", "https://bcr.bazel.build/modules/rules_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd", "https://bcr.bazel.build/modules/rules_jvm_external/6.7/source.json": "5426f412d0a7fc6b611643376c7e4a82dec991491b9ce5cb1cfdd25fe2e92be4", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.5/MODULE.bazel": "043a16a572f610558ec2030db3ff0c9938574e7dd9f58bded1bb07c0192ef025", "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", @@ -189,12 +224,16 @@ "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", "https://bcr.bazel.build/modules/rules_python/0.33.2/MODULE.bazel": "3e036c4ad8d804a4dad897d333d8dce200d943df4827cb849840055be8d2e937", "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", + "https://bcr.bazel.build/modules/rules_python/1.0.0/MODULE.bazel": "898a3d999c22caa585eb062b600f88654bf92efb204fa346fb55f6f8edffca43", "https://bcr.bazel.build/modules/rules_python/1.1.0/MODULE.bazel": "57e01abae22956eb96d891572490d20e07d983e0c065de0b2170cafe5053e788", "https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13", "https://bcr.bazel.build/modules/rules_python/1.4.1/MODULE.bazel": "8991ad45bdc25018301d6b7e1d3626afc3c8af8aaf4bc04f23d0b99c938b73a6", "https://bcr.bazel.build/modules/rules_python/1.6.0/MODULE.bazel": "7e04ad8f8d5bea40451cf80b1bd8262552aa73f841415d20db96b7241bd027d8", "https://bcr.bazel.build/modules/rules_python/1.7.0/MODULE.bazel": "d01f995ecd137abf30238ad9ce97f8fc3ac57289c8b24bd0bf53324d937a14f8", "https://bcr.bazel.build/modules/rules_python/1.7.0/source.json": "028a084b65dcf8f4dc4f82f8778dbe65df133f234b316828a82e060d81bdce32", + "https://bcr.bazel.build/modules/rules_robolectric/4.14.1.2/MODULE.bazel": "d44fec647d0aeb67b9f3b980cf68ba634976f3ae7ccd6c07d790b59b87a4f251", + "https://bcr.bazel.build/modules/rules_robolectric/4.14.1.2/source.json": "37c10335f2361c337c5c1f34ed36d2da70534c23088062b33a8bdaab68aa9dea", + "https://bcr.bazel.build/modules/rules_shell/0.1.2/MODULE.bazel": "66e4ca3ce084b04af0b9ff05ff14cab4e5df7503973818bb91cbc6cda08d32fc", "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", "https://bcr.bazel.build/modules/rules_shell/0.4.0/MODULE.bazel": "0f8f11bb3cd11755f0b48c1de0bbcf62b4b34421023aa41a2fc74ef68d9584f0", @@ -208,6 +247,7 @@ "https://bcr.bazel.build/modules/rules_swift/3.1.2/source.json": "e85761f3098a6faf40b8187695e3de6d97944e98abd0d8ce579cb2daf6319a66", "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", + "https://bcr.bazel.build/modules/stardoc/0.5.4/MODULE.bazel": "6569966df04610b8520957cb8e97cf2e9faac2c0309657c537ab51c16c18a2a4", "https://bcr.bazel.build/modules/stardoc/0.6.2/MODULE.bazel": "7060193196395f5dd668eda046ccbeacebfd98efc77fed418dbe2b82ffaa39fd", "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", @@ -227,6 +267,7 @@ "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" }, "selectedYankedVersions": {}, + "moduleExtensions": {}, "moduleExtensions": { "@@buildifier_prebuilt+//:defs.bzl%buildifier_prebuilt_deps_extension": { "general": { diff -r dfdd66825396 -r 8cf4ec5e2191 gui_ze/gui_ze.bzl --- a/gui_ze/gui_ze.bzl Fri Jan 23 22:22:30 2026 -0800 +++ b/gui_ze/gui_ze.bzl Fri Jan 23 22:38:59 2026 -0800 @@ -111,11 +111,14 @@ command = """ cp -r third_party/bun/** . \ && cp -r {src_folder}/** . \ - && export NODE_PATH=./node_modules && {bun_path} build {input_path} --outfile {output_path} + && cp -r ./bazel-out/k8-fastbuild/bin/hg-web/src/** src \ + && ls src \ + && export NODE_PATH=./node_modules && {bun_path} build {input_path} --outfile {output_path} --target browser """.format( bun_path = ctx.executable._bun.path, src_folder = ctx.attr.src_folder, - input_path = ctx.file.src.path.split("/")[-1], + # Fix this lol + input_path = "/".join(ctx.file.src.path.split("/")[-2:]), output_path = out.path, ), progress_message = "Bundling {} with Bun!\n\n".format(ctx.file.src.path), @@ -208,7 +211,7 @@ ctx.actions.run_shell( inputs = [src], outputs = [out], - command = "cp \"$1\" \"$2\"", + command = "cp -r \"$1\" \"$2\"", arguments = [src.path, out.path], ) outs.append(out) diff -r dfdd66825396 -r 8cf4ec5e2191 hg-web/BUILD --- a/hg-web/BUILD Fri Jan 23 22:22:30 2026 -0800 +++ b/hg-web/BUILD Fri Jan 23 22:38:59 2026 -0800 @@ -1,8 +1,9 @@ load("@rules_cc//cc:cc_binary.bzl", "cc_binary") -load("//gui_ze:gui_ze.bzl", "move_files_into_dir", "bundle") +load("//gui_ze:gui_ze.bzl", "move_files_into_dir", "bundle", "bun_build") +# External move_files_into_dir( - name = "compiled_ts", + name = "external_js_ts_moved", srcs = [ "//markdown_converter:markdown_to_html", ], @@ -10,16 +11,67 @@ ) filegroup( - name = "src_files", - srcs = glob(["src/**"]) + [":compiled_ts"], + name = "external_js_ts", + srcs = [":external_js_ts_moved"], +) + +# 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( + name = "page", + src = "src/main.tsx", + src_folder = "hg-web", + data = [ + "//third_party/bun:bun_files", + ":all_ts_files", + ], + visibility = ["//visibility:public"], +) + +move_files_into_dir( + name = "compiled_ts", + srcs = [ + ":page", + ], + dest = "src", +) + +move_files_into_dir( + name = "public_files", + srcs = [ + "//mrjunejune:public_files" + ], + dest = "src/public", +) + +filegroup( + name = "src_files", + srcs = [":raw_file", ":compiled_ts", "public_files"], +) + +# Binary cc_binary( name = "hg_web_server", srcs = ["main.c"], - deps = ["//seobeo:seobeo_server"], + deps = [ + "//seobeo:seobeo", + ], data = [":src_files"], - defines = ["REPO_ROOT=\\\"\"/home/mrjunejune/zenbu\"\\\""], ) bundle( @@ -27,12 +79,10 @@ binary = ":hg_web_server", ) - cc_binary( name = "hg_web_server_debug", srcs = ["main.c"], - deps = ["//seobeo:seobeo_tcp_server_ws_debug"], + deps = ["//seobeo:seobeo"], data = [":src_files"], - defines = ["REPO_ROOT=\\\"\"/home/mrjunejune/zenbu\"\\\""], ) diff -r dfdd66825396 -r 8cf4ec5e2191 hg-web/main.c --- a/hg-web/main.c Fri Jan 23 22:22:30 2026 -0800 +++ b/hg-web/main.c Fri Jan 23 22:38:59 2026 -0800 @@ -11,44 +11,17 @@ #include #define HG_SERVE_HOST "127.0.0.1" -#define HG_SERVE_PORT 4444 +#define HG_SERVE_PORT "4444" #define MAX_PATH 4096 -// TODO: Move this to seobeo.... -// Asked AI to create this lol, probably should learn to decode it myself.. -static void url_decode(char *dst, const char *src) -{ - char a, b; - while (*src) { - if ((*src == '%') && - ((a = src[1]) && (b = src[2])) && - (isxdigit(a) && isxdigit(b))) { - if (a >= 'a') a -= 'a'-'A'; - if (a >= 'A') a -= ('A' - 10); - else a -= '0'; - if (b >= 'a') b -= 'a'-'A'; - if (b >= 'A') b -= ('A' - 10); - else b -= '0'; - *dst++ = 16*a+b; - src+=3; - } else if (*src == '+') { - *dst++ = ' '; - src++; - } else { - *dst++ = *src++; - } - } - *dst = '\0'; -} - static char* sanitize_path(const char *input_path, Dowa_Arena *arena) { if (!input_path || strlen(input_path) == 0) { - char *empty = Dowa_Arena_Allocate(arena, 1); - empty[0] = '\0'; - return empty; + char *empty = Dowa_Arena_Allocate(arena, 1); + empty[0] = '\0'; + return empty; } size_t len = strlen(input_path); @@ -59,9 +32,9 @@ { if (input_path[i] == '.' && (i == 0 || input_path[i-1] == '/')) { if (i + 1 < len && input_path[i+1] == '.') { - // Skip ".." - i++; - continue; + // Skip ".." + i++; + continue; } // Skip "." continue; @@ -72,153 +45,40 @@ // Remove leading/trailing slashes while (result[0] == '/') - memmove(result, result + 1, strlen(result)); + memmove(result, result + 1, strlen(result)); while (j > 0 && result[j-1] == '/') - result[--j] = '\0'; + result[--j] = '\0'; return result; } -// Helper to connect to hg serve -static int hg_proxy_connect(void) +Seobeo_Client_Response *hg_proxy_request( + const char *method, + const char *path, + const char *req_body, + const char *hg_custom) { - int sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock < 0) - { - Seobeo_Log(SEOBEO_DEBUG, "Failed to create socket\n"); - return -1; - } - - struct sockaddr_in server_addr; - memset(&server_addr, 0, sizeof(server_addr)); - server_addr.sin_family = AF_INET; - server_addr.sin_port = htons(HG_SERVE_PORT); - inet_pton(AF_INET, HG_SERVE_HOST, &server_addr.sin_addr); - - if (connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) - { - Seobeo_Log(SEOBEO_DEBUG, "Failed to connect to hg serve at %s:%d\n", HG_SERVE_HOST, HG_SERVE_PORT); - close(sock); - return -1; - } - - return sock; -} - -// Generic helper to proxy a request to hg serve and get the response body -// Returns allocated body on success, NULL on failure -// out_status and out_content_type are optional output parameters -// out_body_len returns the actual body length (for binary content) -static char* hg_proxy_request( - const char *method, - const char *path, - const char *req_body, - size_t body_len, - char *out_status, // should be at least 4 bytes - char *out_content_type, // should be at least 256 bytes - size_t *out_body_len, // optional: returns actual body length - Dowa_Arena *arena) -{ - int sock = hg_proxy_connect(); - if (sock < 0) return NULL; - - // Build HTTP request - char http_request[MAX_PATH * 2]; - snprintf(http_request, sizeof(http_request), - "%s %s HTTP/1.1\r\n" - "Host: %s:%d\r\n" - "Connection: close\r\n" - "Accept: application/json, text/plain, */*\r\n" - "Content-Length: %zu\r\n" - "\r\n", - method, path, HG_SERVE_HOST, HG_SERVE_PORT, body_len); - - Seobeo_Log(SEOBEO_DEBUG, "HG Proxy request: %s %s\n", method, path); - - if (send(sock, http_request, strlen(http_request), 0) < 0) - { - close(sock); - return NULL; - } - - if (body_len > 0 && req_body) - { - send(sock, req_body, body_len, 0); - } + char full_path[MAX_PATH]; + snprintf(full_path, MAX_PATH, "http://%s:%s%s", HG_SERVE_HOST, HG_SERVE_PORT, path); + Seobeo_Log(SEOBEO_DEBUG, "HG Proxy PATH %s\n", full_path); + Seobeo_Client_Request *p_req = Seobeo_Client_Request_Create(full_path); + Seobeo_Client_Request_Set_Method(p_req, method); + Seobeo_Client_Request_Add_Header_Array(p_req, "User-Agent: Seobeo/1.0"); + Seobeo_Client_Request_Add_Header_Array(p_req, "Accept: application/json"); - // Read response - int buffer_size = 1024 * 1024 * 5; // 5MB - char *response_buf = Dowa_Arena_Allocate(arena, buffer_size); - size_t total_read = 0; - ssize_t bytes_read; - - while ((bytes_read = recv(sock, response_buf + total_read, buffer_size - total_read - 1, 0)) > 0) - { - total_read += bytes_read; - if (total_read >= (size_t)(buffer_size - 1)) break; - } - response_buf[total_read] = '\0'; - close(sock); - - Seobeo_Log(SEOBEO_DEBUG, "HG Proxy: received %zu bytes\n", total_read); - - // Parse response headers - use memmem to handle binary content - char *headers_end = NULL; - for (size_t i = 0; i + 3 < total_read; i++) - { - if (response_buf[i] == '\r' && response_buf[i+1] == '\n' && - response_buf[i+2] == '\r' && response_buf[i+3] == '\n') - { - headers_end = response_buf + i; - break; - } - } - if (!headers_end) return NULL; + if (hg_custom && hg_custom[0] != '\0') + { + char buffer[1024]; + snprintf(buffer, 1024, "x-hgarg-1: %s", hg_custom); + Seobeo_Client_Request_Add_Header_Array(p_req, buffer); + Seobeo_Log(SEOBEO_DEBUG, "HG CUSTOM %s\n", buffer); + } - // Extract status - if (out_status && strncmp(response_buf, "HTTP/", 5) == 0) - { - char *status_start = strchr(response_buf, ' '); - if (status_start) - { - strncpy(out_status, status_start + 1, 3); - out_status[3] = '\0'; - } - } - - // Extract content-type - if (out_content_type) - { - out_content_type[0] = '\0'; - char *ct_header = strcasestr(response_buf, "Content-Type:"); - if (ct_header && ct_header < headers_end) - { - ct_header += 13; - while (*ct_header == ' ') ct_header++; - char *ct_end = strpbrk(ct_header, "\r\n"); - if (ct_end) - { - size_t ct_len = ct_end - ct_header; - if (ct_len < 256) - { - strncpy(out_content_type, ct_header, ct_len); - out_content_type[ct_len] = '\0'; - } - } - } - } - - // Return body (copy to fresh allocation for clean pointer) - char *body = headers_end + 4; - size_t body_size = total_read - (body - response_buf); - - if (out_body_len) *out_body_len = body_size; - - char *result = Dowa_Arena_Allocate(arena, body_size + 1); - memcpy(result, body, body_size); - result[body_size] = '\0'; - - return result; + if (req_body) + Seobeo_Client_Request_Set_Body(p_req, req_body, strlen(req_body)); + Seobeo_Client_Response *p_resp = Seobeo_Client_Request_Execute(p_req); + Seobeo_Client_Request_Destroy(p_req); + return p_resp; } Seobeo_Request_Entry* ApiListDirectory(Seobeo_Request_Entry *req, Dowa_Arena *arena) @@ -229,7 +89,7 @@ const char *rel_path = path_kv ? ((Seobeo_Request_Entry*)path_kv)->value : ""; char *decoded_path = Dowa_Arena_Allocate(arena, strlen(rel_path) + 1); - url_decode(decoded_path, rel_path); + Seobeo_Url_Decode(decoded_path, rel_path); char *safe_path = sanitize_path(decoded_path, arena); @@ -241,14 +101,11 @@ else snprintf(hg_path, sizeof(hg_path), "/file/tip/?style=json"); - char status[4] = "200"; - char content_type[256] = ""; - size_t body_len = 0; - char *hg_response = hg_proxy_request("GET", hg_path, NULL, 0, status, content_type, &body_len, arena); + Seobeo_Client_Response *hg_response = hg_proxy_request("GET", hg_path, NULL, NULL); - Seobeo_Log(SEOBEO_DEBUG, "ApiListDirectory: status=%s body_len=%zu\n", status, body_len); + Seobeo_Log(SEOBEO_DEBUG, "ApiListDirectory: status=%i body_len=%zu\n", hg_response->status_code, hg_response->body_length); - if (!hg_response || status[0] != '2') + if (hg_response->status_code != 200) { Seobeo_Log(SEOBEO_DEBUG, "Failed to get directory from hg serve\n"); Dowa_HashMap_Push_Arena(resp, "status", "502", arena); @@ -256,12 +113,15 @@ Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Failed to connect to hg serve\"}", arena); return resp; } - char *json = hg_response; + + char *temp1 = Dowa_Arena_Copy(arena, hg_response->body, hg_response->body_length); + char *temp2 = Dowa_Arena_Allocate(arena, 256); + snprintf(temp2, 256, "%zu", hg_response->body_length); Dowa_HashMap_Push_Arena(resp, "status", "200", arena); Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); - Dowa_HashMap_Push_Arena(resp, "body", json, arena); - + Dowa_HashMap_Push_Arena(resp, "body", temp1, arena); + Dowa_HashMap_Push_Arena(resp, "content-length", temp2, arena); return resp; } @@ -272,7 +132,7 @@ void *path_kv = Dowa_HashMap_Get_Ptr(req, "query_path"); const char *rel_path = path_kv ? ((Seobeo_Request_Entry*)path_kv)->value : ""; char *decoded_path = Dowa_Arena_Allocate(arena, strlen(rel_path) + 1); - url_decode(decoded_path, rel_path); + Seobeo_Url_Decode(decoded_path, rel_path); char *safe_path = sanitize_path(decoded_path, arena); Seobeo_Log(SEOBEO_INFO, "ApiGetFile: safe_path='%s'\n", safe_path); @@ -285,18 +145,16 @@ return resp; } - // Build hg serve URL: /raw-file/tip/ char hg_path[MAX_PATH]; snprintf(hg_path, sizeof(hg_path), "/raw-file/tip/%s", safe_path); + Seobeo_Client_Response *hg_response = hg_proxy_request("GET", hg_path, NULL, NULL); - char status[4] = "200"; - char content_type[256] = ""; - size_t body_len = 0; - char *body = hg_proxy_request("GET", hg_path, NULL, 0, status, content_type, &body_len, arena); + Seobeo_Log(SEOBEO_DEBUG, "ApiGetFile: status=%i body_len=%zu\n", hg_response->status_code, hg_response->body_length); - Seobeo_Log(SEOBEO_DEBUG, "ApiGetFile: status=%s body_len=%zu\n", status, body_len); + char status[4]; + snprintf(status, 3, "%i", hg_response->status_code); - if (!body) + if (!hg_response->body) { Dowa_HashMap_Push_Arena(resp, "status", "502", arena); Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); @@ -304,31 +162,24 @@ return resp; } - if (status[0] != '2') + if (hg_response->status_code != 200) { - Seobeo_Log(SEOBEO_DEBUG, "ApiGetFile: error response: %s\n", body); + Seobeo_Log(SEOBEO_DEBUG, "ApiGetFile: error hg_response: %s\n", hg_response->body); Dowa_HashMap_Push_Arena(resp, "status", status, arena); Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); - // Return actual error from hg serve if available - Dowa_HashMap_Push_Arena(resp, "body", body_len > 0 ? body : "File not found", arena); + Dowa_HashMap_Push_Arena(resp, "body", hg_response->body, arena); return resp; } - // Use content-type from hg serve, or determine from extension - const char *final_content_type = content_type; - if (strlen(content_type) == 0 || strcmp(content_type, "application/octet-stream") == 0) - { - final_content_type = "text/plain"; - if (strstr(safe_path, ".md")) final_content_type = "text/markdown"; - else if (strstr(safe_path, ".html")) final_content_type = "text/html"; - else if (strstr(safe_path, ".css")) final_content_type = "text/css"; - else if (strstr(safe_path, ".js")) final_content_type = "application/javascript"; - else if (strstr(safe_path, ".json")) final_content_type = "application/json"; - } + + char *temp1 = Dowa_Arena_Copy(arena, hg_response->body, hg_response->body_length); + char *temp2 = Dowa_Arena_Allocate(arena, 256); + snprintf(temp2, 256, "%zu", hg_response->body_length); - Dowa_HashMap_Push_Arena(resp, "status", "200", arena); - Dowa_HashMap_Push_Arena(resp, "content-type", final_content_type, arena); - Dowa_HashMap_Push_Arena(resp, "body", body, arena); + Dowa_HashMap_Push_Arena(resp, "status", status, 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); return resp; } @@ -337,139 +188,159 @@ return ApiGetFile(req, arena); } +// Streaming handler for hg wire protocol - pipes data directly without buffering +void StreamHgWireProtocol(Seobeo_Handle *p_client, Seobeo_Request_Entry *req, Dowa_Arena *arena) +{ + void *method_kv = Dowa_HashMap_Get_Ptr(req, "HTTP_Method"); + const char *method = method_kv ? ((Seobeo_Request_Entry*)method_kv)->value : "GET"; + + void *query_kv = Dowa_HashMap_Get_Ptr(req, "QueryString"); + const char *query_string = query_kv ? ((Seobeo_Request_Entry*)query_kv)->value : ""; + + void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body"); + const char *req_body = body_kv ? ((Seobeo_Request_Entry*)body_kv)->value : ""; + + const char *hg_custom = req[7].value; + + Seobeo_Log(SEOBEO_DEBUG, "HG Stream Proxy: method=%s query=%s\n", method, query_string); + + // THINKING: Connect to hg serve + // This kinda blows, but not a good way to handle it since my client API assumes it is all stored in + // buffer and what not. + Seobeo_Handle *p_upstream = Seobeo_Stream_Handle_Client_Create(HG_SERVE_HOST, HG_SERVE_PORT, FALSE); + if (!p_upstream || p_upstream->socket < 0) + { + const char *err_resp = "HTTP/1.1 502 Bad Gateway\r\nContent-Length: 26\r\n\r\nFailed to connect upstream"; + Seobeo_Handle_Queue(p_client, (uint8*)err_resp, strlen(err_resp)); + Seobeo_Handle_Flush(p_client); + if (p_upstream) + Seobeo_Handle_Destroy(p_upstream); + return; + } + + // Create headers + // we only allow x-hgarg-1 and content-length + char request_buf[8192]; + int req_len = snprintf(request_buf, sizeof(request_buf), + "%s /?%s HTTP/1.1\r\n" + "Host: %s:%s\r\n" + "User-Agent: Seobeo/1.0\r\n" + "Connection: close\r\n", + method, query_string, HG_SERVE_HOST, HG_SERVE_PORT); + + if (hg_custom && hg_custom[0] != '\0') + req_len += snprintf(request_buf + req_len, sizeof(request_buf) - req_len, "x-hgarg-1: %s\r\n", hg_custom); + + if (req_body && req_body[0] != '\0') + req_len += snprintf(request_buf + req_len, sizeof(request_buf) - req_len, "Content-Length: %zu\r\n\r\n%s", strlen(req_body), req_body); + else + req_len += snprintf(request_buf + req_len, sizeof(request_buf) - req_len, "\r\n"); + + Seobeo_Handle_Queue(p_upstream, (uint8*)request_buf, req_len); + if (Seobeo_Handle_Flush(p_upstream) < 0) + { + const char *err_resp = "HTTP/1.1 502 Bad Gateway\r\nContent-Length: 21\r\n\r\nUpstream write failed"; + Seobeo_Handle_Queue(p_client, (uint8*)err_resp, strlen(err_resp)); + Seobeo_Handle_Flush(p_client); + Seobeo_Handle_Destroy(p_upstream); + return; + } + + // Responses + while (1) + { + int r = Seobeo_Handle_Read(p_upstream); + if (r < 0) + { + Seobeo_Handle_Destroy(p_upstream); + return; + } + if (p_upstream->read_buffer_len >= 4 && + strstr((char*)p_upstream->read_buffer, "\r\n\r\n") != NULL) + break; + if (r == 0) + continue; + } + + // TODO: Maybe make this into a separate function instead of internal function as doing this over and over again blows. + char *hdr_end = strstr((char*)p_upstream->read_buffer, "\r\n\r\n"); + if (!hdr_end) + { + Seobeo_Handle_Destroy(p_upstream); + return; + } + size_t hdr_len = hdr_end - (char*)p_upstream->read_buffer + 4; + Seobeo_Handle_Queue(p_client, p_upstream->read_buffer, hdr_len); + Seobeo_Handle_Flush(p_client); + + // All body + size_t body_in_buffer = p_upstream->read_buffer_len - hdr_len; + if (body_in_buffer > 0) + { + Seobeo_Handle_Queue(p_client, p_upstream->read_buffer + hdr_len, body_in_buffer); + Seobeo_Handle_Flush(p_client); + } + Seobeo_Handle_Consume(p_upstream, p_upstream->read_buffer_len); + while (1) + { + int n = Seobeo_Handle_Read(p_upstream); + if (n > 0) + { + Seobeo_Handle_Queue(p_client, p_upstream->read_buffer, p_upstream->read_buffer_len); + Seobeo_Handle_Flush(p_client); + Seobeo_Handle_Consume(p_upstream, p_upstream->read_buffer_len); + } + else if (n == -2) + break; + else if (n < 0) + break; + } + + Seobeo_Handle_Destroy(p_upstream); +} + Seobeo_Request_Entry* ApiHgWireProtocol(Seobeo_Request_Entry *req, Dowa_Arena *arena) { - Seobeo_Request_Entry *resp = NULL; - - // Get method - void *method_kv = Dowa_HashMap_Get_Ptr(req, "HTTP_Method"); - const char *method = method_kv ? ((Seobeo_Request_Entry*)method_kv)->value : "GET"; - - // Get query string - void *query_kv = Dowa_HashMap_Get_Ptr(req, "QueryString"); - const char *query_string = query_kv ? ((Seobeo_Request_Entry*)query_kv)->value : ""; + Seobeo_Request_Entry *resp = NULL; - // Get request body for POST - void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body"); - const char *req_body = body_kv ? ((Seobeo_Request_Entry*)body_kv)->value : ""; - size_t body_len = strlen(req_body); - - Seobeo_Log(SEOBEO_DEBUG, "HG Proxy: method=%s query=%s body_len=%zu\n", method, query_string, body_len); + void *method_kv = Dowa_HashMap_Get_Ptr(req, "HTTP_Method"); + const char *method = method_kv ? ((Seobeo_Request_Entry*)method_kv)->value : "GET"; - // Connect to hg serve - int sock = hg_proxy_connect(); - if (sock < 0) - { - Dowa_HashMap_Push_Arena(resp, "status", "502", arena); - Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); - Dowa_HashMap_Push_Arena(resp, "body", "Failed to connect to hg serve", arena); - return resp; - } + void *query_kv = Dowa_HashMap_Get_Ptr(req, "QueryString"); + const char *query_string = query_kv ? ((Seobeo_Request_Entry*)query_kv)->value : ""; - // Build the HTTP request to forward to hg serve - char http_request[MAX_PATH * 2]; - if (strlen(query_string) > 0) - { - snprintf(http_request, sizeof(http_request), - "%s /?%s HTTP/1.1\r\n" - "Host: %s:%d\r\n" - "Connection: close\r\n" - "Content-Length: %zu\r\n" - "\r\n", - method, query_string, HG_SERVE_HOST, HG_SERVE_PORT, body_len); - } - else - { - snprintf(http_request, sizeof(http_request), - "%s / HTTP/1.1\r\n" - "Host: %s:%d\r\n" - "Connection: close\r\n" - "Content-Length: %zu\r\n" - "\r\n", - method, HG_SERVE_HOST, HG_SERVE_PORT, body_len); - } + void *body_kv = Dowa_HashMap_Get_Ptr(req, "Body"); + const char *req_body = body_kv ? ((Seobeo_Request_Entry*)body_kv)->value : ""; + size_t body_len = strlen(req_body); - // Send HTTP request headers - if (send(sock, http_request, strlen(http_request), 0) < 0) - { - Seobeo_Log(SEOBEO_DEBUG, "Failed to send request to hg serve\n"); - close(sock); - Dowa_HashMap_Push_Arena(resp, "status", "502", arena); - Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); - Dowa_HashMap_Push_Arena(resp, "body", "Failed to send to hg serve", arena); - return resp; - } + const char *hg_custom = req[7].value; + Seobeo_Log(SEOBEO_DEBUG, "HG Proxy: method=%s query=%s body_len=%zu\n", method, query_string, body_len); + + Seobeo_Client_Response *hg_response; + + char hg_path[MAX_PATH]; + snprintf(hg_path, sizeof(hg_path), "/?%s", query_string); - // Send body if present - if (body_len > 0) - { - send(sock, req_body, body_len, 0); - } + hg_response = hg_proxy_request(method, hg_path, req_body, hg_custom); - // Read response from hg serve - int buffer_size = 1024 * 1024 * 5; // 5MB - char *response_buf = Dowa_Arena_Allocate(arena, buffer_size); - size_t total_read = 0; - ssize_t bytes_read; + Seobeo_Log(SEOBEO_DEBUG, "HG Proxy: received %zu bytes\n", hg_response->body_length); - while ((bytes_read = recv(sock, response_buf + total_read, buffer_size - total_read - 1, 0)) > 0) - { - total_read += bytes_read; - if (total_read >= (size_t)(buffer_size - 1)) break; - } - response_buf[total_read] = '\0'; - close(sock); - - Seobeo_Log(SEOBEO_DEBUG, "HG Proxy: received %zu bytes\n", total_read); + Seobeo_Request_Entry *kv = Dowa_HashMap_Get_Ptr(hg_response->headers, "Content-Type"); - // Parse HTTP response - find headers end - char *headers_end = strstr(response_buf, "\r\n\r\n"); - if (!headers_end) - { - Dowa_HashMap_Push_Arena(resp, "status", "502", arena); - Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); - Dowa_HashMap_Push_Arena(resp, "body", "Invalid response from hg serve", arena); - return resp; - } + char *status = Dowa_Arena_Allocate(arena, 5); + snprintf(status, 4, "%i", hg_response->status_code); - // Extract status code from first line (e.g., "HTTP/1.1 200 OK") - char status_code[4] = "200"; - if (strncmp(response_buf, "HTTP/", 5) == 0) - { - char *status_start = strchr(response_buf, ' '); - if (status_start) - { - strncpy(status_code, status_start + 1, 3); - status_code[3] = '\0'; - } - } + // Use binary-safe copy to handle null bytes in mercurial bundle data + char *temp1 = Dowa_Arena_Copy(arena, hg_response->body, hg_response->body_length); + char *temp2 = Dowa_Arena_Allocate(arena, 256); + snprintf(temp2, 256, "%zu", hg_response->body_length); - // Extract content-type from headers - const char *content_type = "application/mercurial-0.1"; - char *ct_header = strcasestr(response_buf, "Content-Type:"); - if (ct_header && ct_header < headers_end) - { - ct_header += 13; // Skip "Content-Type:" - while (*ct_header == ' ') ct_header++; - char *ct_end = strpbrk(ct_header, "\r\n"); - if (ct_end) - { - size_t ct_len = ct_end - ct_header; - char *ct_copy = Dowa_Arena_Allocate(arena, ct_len + 1); - strncpy(ct_copy, ct_header, ct_len); - ct_copy[ct_len] = '\0'; - content_type = ct_copy; - } - } + Dowa_HashMap_Push_Arena(resp, "status", status, arena); + Dowa_HashMap_Push_Arena(resp, "content-type", kv->value, arena); + Dowa_HashMap_Push_Arena(resp, "body", temp1, arena); + Dowa_HashMap_Push_Arena(resp, "content-length", temp2, arena); - // Body starts after \r\n\r\n - char *body = headers_end + 4; - - Dowa_HashMap_Push_Arena(resp, "status", status_code, arena); - Dowa_HashMap_Push_Arena(resp, "content-type", content_type, arena); - Dowa_HashMap_Push_Arena(resp, "body", body, arena); - - return resp; + return resp; } int main(void) { @@ -479,13 +350,13 @@ Seobeo_Router_Register("GET", "/api/repo/file", ApiGetFile); Seobeo_Router_Register("GET", "/api/repo/readme", ApiGetReadme); - Seobeo_Router_Register("GET", "/repo", ApiHgWireProtocol); - Seobeo_Router_Register("POST", "/repo", ApiHgWireProtocol); + // Use streaming handler for hg wire protocol... + Seobeo_Router_Register_Stream("GET", "/repo", StreamHgWireProtocol); + Seobeo_Router_Register_Stream("POST", "/repo", StreamHgWireProtocol); printf("Starting on Port 6970...\n"); - printf("Repository: %s\n", REPO_ROOT); - int result = Seobeo_Web_Server_Start("hg-web/src", "6970", SEOBEO_MODE_EDGE, 4); + int result = Seobeo_Web_Server_Start("hg-web/src", "6970", SEOBEO_MODE_EDGE, 1); Seobeo_Router_Destroy(); diff -r dfdd66825396 -r 8cf4ec5e2191 hg-web/src/index.html --- a/hg-web/src/index.html Fri Jan 23 22:22:30 2026 -0800 +++ b/hg-web/src/index.html Fri Jan 23 22:38:59 2026 -0800 @@ -4,19 +4,20 @@ Zenbu Repository - - +
-
+
+ +
- + diff -r dfdd66825396 -r 8cf4ec5e2191 hg-web/src/main.tsx --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hg-web/src/main.tsx Fri Jan 23 22:38:59 2026 -0800 @@ -0,0 +1,8 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { RepoBrowser } from "./repo-browser"; + +const root = ReactDOM.createRoot(document.getElementById('root')); + +// Use JSX syntax () +root.render(); diff -r dfdd66825396 -r 8cf4ec5e2191 hg-web/src/repo-browser.tsx --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hg-web/src/repo-browser.tsx Fri Jan 23 22:38:59 2026 -0800 @@ -0,0 +1,406 @@ +import React, { useState, useEffect } from 'react'; +import { renderMarkdown } from './src/markdown_to_html.js'; + +// --- ICONS (Using CDN Links) --- +const ICONS = { + folder: "https://cdn-icons-png.flaticon.com/512/11471/11471391.png", + file: "https://raw.githubusercontent.com/PKief/vscode-material-icon-theme/main/icons/document.svg", + home: "https://cdn-icons-png.flaticon.com/512/1946/1946488.png", + repo: "/public/epi_all_colors.svg", + clone: "https://cdn-icons-png.flaticon.com/512/11471/11471391.png" +}; + +const API_BASE = '/api/repo'; + +/** + * Component: Styles + * Injected CSS for the polished look + */ +const GlobalStyles = () => ( + +); + +/** + * Component: Breadcrumb + */ +function Breadcrumb({ currentPath, onNavigate }) { + if (!currentPath) { + return ( + + ); + } + + const parts = currentPath.split('/').filter(p => p); + const crumbs = parts.map((part, index) => ({ + name: part, + fullPath: parts.slice(0, index + 1).join('/') + })); + + return ( + + ); +} + +/** + * Component: FileList + */ +function FileList({ directories, files, onNavigate }) { + const isEmpty = directories.length === 0 && files.length === 0; + + if (isEmpty) { + return ( +
+
This directory is empty.
+
+ ); + } + + return ( +
+ {/* Optional header row like GitHub */} +
+ Files +
+ +
+ {directories.map((dir) => ( + + ))} + + {files.map((file) => ( + + ))} +
+
+ ); +} + +/** + * Component: FileRow + */ +function FileRow({ item, iconUrl, isDir, onNavigate }) { + const handleClick = (e) => { + if (isDir) { + e.preventDefault(); + onNavigate(item.abspath); + } + }; + + const href = isDir + ? `?path=${encodeURIComponent(item.abspath)}` + : `/api/repo/file?path=${encodeURIComponent(item.abspath)}`; + + const target = isDir ? undefined : "_blank"; + + return ( +
+ + {isDir + + + + {item.basename} + + +
+ ); +} + +/** + * Component: ReadmeViewer + */ +function ReadmeViewer({ content }) { + if (!content) return null; + + useEffect(() => renderMarkdown(content, readmeContent), [content]); + + return ( +
+
+ + README.md +
+
+
+ ); +} + +/** + * Main Application Component + */ +function RepoBrowser() { + const [currentPath, setCurrentPath] = useState(getCurrentPath()); + const [content, setContent] = useState({ files: [], directories: [] }); + const [readme, setReadme] = useState(null); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + function getCurrentPath() { + const params = new URLSearchParams(window.location.search); + return params.get('path') || ''; + } + + useEffect(() => { + const handlePopState = () => setCurrentPath(getCurrentPath()); + window.addEventListener('popstate', handlePopState); + return () => window.removeEventListener('popstate', handlePopState); + }, []); + + useEffect(() => { + fetchDirectory(currentPath); + fetchReadme(currentPath); + }, [currentPath]); + + const navigate = (path) => { + const newUrl = path ? `?path=${encodeURIComponent(path)}` : '/'; + window.history.pushState({ path }, '', newUrl); + setCurrentPath(path); + }; + + const fetchDirectory = async (path) => { + setLoading(true); + setError(null); + try { + const url = path + ? `${API_BASE}/list?path=${encodeURIComponent(path)}` + : `${API_BASE}/list`; + + const response = await fetch(url); + const data = await response.json(); + + if (data.error) throw new Error(data.error); + + setContent({ + files: data.files || [], + directories: data.directories || [] + }); + } catch (err) { + console.error('Error loading directory:', err); + setError(err.message); + } finally { + setLoading(false); + } + }; + + 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 */ } + }; + + return ( + <> + +
+ + {/* Header */} +
+ Repo +
+

Zenbu Repository

+

Browse and manage the mercurial codebase

+
+
+ + {/* Clone Bar */} +
+
+ Clone HTTPS + hg clone http://zenbu.babocoder.com/repo +
+
+ + {/* Navigation & Content */} + + + {error &&
Error: {error}
} + + {loading ? ( +
+ Loading files... +
+ ) : ( + <> + + + + )} +
+ + ); +} + +export { RepoBrowser }; diff -r dfdd66825396 -r 8cf4ec5e2191 markdown_converter/BUILD diff -r dfdd66825396 -r 8cf4ec5e2191 mrjunejune/BUILD --- a/mrjunejune/BUILD Fri Jan 23 22:22:30 2026 -0800 +++ b/mrjunejune/BUILD Fri Jan 23 22:38:59 2026 -0800 @@ -27,6 +27,12 @@ ], dest = "src/public/highlight", ) + +filegroup( + name = "public_files", + srcs = glob(["src/public/**"]), + visibility = ["//visibility:public"], +) filegroup( name = "src_files", diff -r dfdd66825396 -r 8cf4ec5e2191 mrjunejune/src/public/epi-photos/.DS_Store Binary file mrjunejune/src/public/epi-photos/.DS_Store has changed diff -r dfdd66825396 -r 8cf4ec5e2191 mrjunejune/src/public/epi-photos/webp/.DS_Store Binary file mrjunejune/src/public/epi-photos/webp/.DS_Store has changed diff -r dfdd66825396 -r 8cf4ec5e2191 seobeo/BUILD --- a/seobeo/BUILD Fri Jan 23 22:22:30 2026 -0800 +++ b/seobeo/BUILD Fri Jan 23 22:38:59 2026 -0800 @@ -308,7 +308,7 @@ "//dowa:dowa", "@openssl//:ssl", ], - defines = ["SEOBEO_WEBSOCKET_SERVER"], + defines = ["SEOBEO_WEBSOCKET_SERVER", "SEOBEO_ENABLE_DEBUG"], target_compatible_with = [ "@platforms//os:osx", ], @@ -334,7 +334,7 @@ "//dowa:dowa", "@openssl//:ssl", ], - defines = ["SEOBEO_WEBSOCKET_SERVER"], + defines = ["SEOBEO_WEBSOCKET_SERVER", "SEOBEO_ENABLE_DEBUG"], target_compatible_with = [ "@platforms//os:linux", ], diff -r dfdd66825396 -r 8cf4ec5e2191 seobeo/s_http_client.c --- a/seobeo/s_http_client.c Fri Jan 23 22:22:30 2026 -0800 +++ b/seobeo/s_http_client.c Fri Jan 23 22:38:59 2026 -0800 @@ -69,7 +69,7 @@ memset(p_req, 0, sizeof(Seobeo_Client_Request)); - p_req->p_arena = Dowa_Arena_Create(1024 * 1024); + p_req->p_arena = Dowa_Arena_Create(1024 * 1024 * 5); if (!p_req->p_arena) { free(p_req); @@ -231,7 +231,7 @@ memset(p_resp, 0, sizeof(Seobeo_Client_Response)); - p_resp->p_arena = Dowa_Arena_Create(1024 * 1024 * 5); // 5 MB + p_resp->p_arena = Dowa_Arena_Create(1024 * 1024 * 10); // 10 MB if (!p_resp->p_arena) { free(p_resp); @@ -391,7 +391,7 @@ } else { - size_t cap = 1024 * 1024 * 3; + size_t cap = 1024 * 1024 * 5; size_t used = 0; char *body = download_path ? NULL : Dowa_Arena_Allocate(p_resp->p_arena, cap); @@ -414,7 +414,6 @@ } memcpy(body + used, p_handle->read_buffer, p_handle->read_buffer_len); used += p_handle->read_buffer_len; - Seobeo_Log(SEOBEO_DEBUG, "Copied %zu bytes, total %zu/%zu\n", used, used + p_handle->read_buffer_len, body_len); } Seobeo_Handle_Consume(p_handle, (uint32)p_handle->read_buffer_len); } diff -r dfdd66825396 -r 8cf4ec5e2191 seobeo/s_web.c --- a/seobeo/s_web.c Fri Jan 23 22:22:30 2026 -0800 +++ b/seobeo/s_web.c Fri Jan 23 22:38:59 2026 -0800 @@ -151,6 +151,11 @@ void *p_conn_kv = Dowa_HashMap_Get_Ptr(p_req_map, "Connection"); const char *conn_header = p_conn_kv ? ((Seobeo_Request_Entry*)p_conn_kv)->value : NULL; + void *p_real_ip_kv = Dowa_HashMap_Get_Ptr(p_req_map, "X-Real-IP"); + const char *real_ip = p_real_ip_kv ? ((Seobeo_Request_Entry*)p_real_ip_kv)->value : NULL; + if (!real_ip) + real_ip = p_cli_handle->host; + if (conn_header) { if (connection_header_contains(conn_header, "close")) @@ -185,6 +190,7 @@ // --- Check for WebSocket upgrade request --- #ifdef SEOBEO_WEBSOCKET_SERVER + Seobeo_Log(SEOBEO_DEBUG, "Web socket path \n"); if (Seobeo_WebSocket_Server_Handle_Upgrade(p_cli_handle, p_req_map, path)) { Seobeo_Log(SEOBEO_INFO, "WebSocket connection established\n"); @@ -194,7 +200,15 @@ } #endif - // --- Try to match API route first --- + // --- Try to match streaming route first --- + Seobeo_Stream_Handler stream_handler = Seobeo_Router_Find_Stream_Handler(method, path, &p_req_map, p_request_arena); + if (stream_handler != NULL) + { + stream_handler(p_cli_handle, p_req_map, p_response_arena); + goto clean_up; + } + + // --- Try to match API route --- Seobeo_Route_Handler handler = Seobeo_Router_Find_Handler(method, path, &p_req_map, p_request_arena); if (handler != NULL) { @@ -333,7 +347,10 @@ break; if (r == 0) - return 1; // EAGAIN, try again later TODO: Add this as part of Handle struct. + { + Seobeo_Log(SEOBEO_INFO, "Waiting?\n"); + continue; // EAGAIN, try again later TODO: Add this as part of Handle struct. + } } // "METHOD SP PATH SP VERSION CRLF" @@ -448,7 +465,6 @@ char *next = strstr(line, "\r\n"); if (!next) break; - // split at colon char *colon = memchr(line, ':', next - line); if (colon) { @@ -472,7 +488,6 @@ memcpy(val, val_start, value_len); val[value_len] = '\0'; - // Both key and value are arena-allocated, hashmap will use them Dowa_HashMap_Push_Arena(*pp_map, key, val, p_arena); } @@ -490,7 +505,6 @@ Seobeo_Log(SEOBEO_DEBUG, "Content-Length=%zu, reading body in chunks...\n", body_len); - // Allocate buffer for entire body char *body = Dowa_Arena_Allocate(p_arena, body_len + 1); if (!body) { @@ -606,6 +620,7 @@ char *method; // "GET", "POST", "PUT", "DELETE" char *path_pattern; // "/v1/users/:id/posts/:post_id" Seobeo_Route_Handler handler; + Seobeo_Stream_Handler stream_handler; // For streaming responses // Pre-parsed path segments for efficient matching char **path_segments; // ["v1", "users", ":id", "posts", ":post_id"] @@ -627,6 +642,25 @@ route.method = strdup(method); route.path_pattern = strdup(path_pattern); route.handler = handler; + route.stream_handler = NULL; + route.path_segments = Dowa_String_Split(path_pattern, "/", strlen(path_pattern), 1, NULL); + route.segment_count = Dowa_Array_Length(route.path_segments); + route.is_param = (boolean*)malloc(sizeof(boolean) * route.segment_count); + + for (size_t i = 0; i < route.segment_count; i++) + route.is_param[i] = (route.path_segments[i][0] == ':'); + + Dowa_Array_Push(g_routes, route); +} + +void Seobeo_Router_Register_Stream(const char *method, const char *path_pattern, Seobeo_Stream_Handler handler) +{ + Seobeo_Route route = {0}; + + route.method = strdup(method); + route.path_pattern = strdup(path_pattern); + route.handler = NULL; + route.stream_handler = handler; route.path_segments = Dowa_String_Split(path_pattern, "/", strlen(path_pattern), 1, NULL); route.segment_count = Dowa_Array_Length(route.path_segments); route.is_param = (boolean*)malloc(sizeof(boolean) * route.segment_count); @@ -709,6 +743,29 @@ return NULL; } +Seobeo_Stream_Handler Seobeo_Router_Find_Stream_Handler( + const char *method, + const char *path, + Seobeo_Request_Entry **pp_request_map, + Dowa_Arena *p_arena) +{ + if (g_routes == NULL || method == NULL || path == NULL) + return NULL; + + size_t route_count = Dowa_Array_Length(g_routes); + for (size_t i = 0; i < route_count; i++) + { + Seobeo_Route *route = &g_routes[i]; + if (strcmp(route->method, method) != 0) + continue; + + if (route->stream_handler && match_route_and_extract(route, path, pp_request_map, p_arena)) + return route->stream_handler; + } + + return NULL; +} + void Seobeo_Router_Send_Response( Seobeo_Handle *p_handle, Seobeo_Request_Entry *p_response_map, @@ -744,24 +801,23 @@ const char *body = ""; void *p_body_kv = Dowa_HashMap_Get_Ptr(p_response_map, "body"); if (p_body_kv) - { body = ((Seobeo_Request_Entry*)p_body_kv)->value; - } const char *content_type = "text/html"; void *p_content_type_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-type"); if (p_content_type_kv) - { content_type = ((Seobeo_Request_Entry*)p_content_type_kv)->value; - } - size_t body_length = strlen(body); + // TODO: Update this to be integer + size_t body_length; void *p_content_length_kv = Dowa_HashMap_Get_Ptr(p_response_map, "content-length"); if (p_content_length_kv) { const char *content_length_str = ((Seobeo_Request_Entry*)p_content_length_kv)->value; body_length = atoi(content_length_str); } + else + body_length = strlen(body); char *header = Dowa_Arena_Allocate(p_arena, 4096); Seobeo_Web_Header_Generate_KeepAlive(header, status, content_type, body_length, keep_alive); @@ -782,6 +838,8 @@ free(temp); } + printf("hEADER %s\n", header); + Seobeo_Handle_Queue(p_handle, (uint8_t*)header, strlen(header)); Seobeo_Handle_Queue(p_handle, (uint8_t*)body, body_length); Seobeo_Handle_Flush(p_handle); @@ -804,3 +862,44 @@ Dowa_Array_Free(g_routes); g_routes = NULL; } + +// Written by AI. I don't know what it does. +void Seobeo_Url_Decode(char *dst, const char *src) +{ + char a, b; + while (*src) { + /* Check if we have a % followed by two valid hex characters */ + if (*src == '%' && src[1] && src[2]) { + a = src[1]; + b = src[2]; + + /* Manual isxdigit check and conversion for 'a' */ + int a_val = -1; + if (a >= '0' && a <= '9') a_val = a - '0'; + else if (a >= 'a' && a <= 'f') a_val = a - 'a' + 10; + else if (a >= 'A' && a <= 'F') a_val = a - 'A' + 10; + + /* Manual isxdigit check and conversion for 'b' */ + int b_val = -1; + if (b >= '0' && b <= '9') b_val = b - '0'; + else if (b >= 'a' && b <= 'f') b_val = b - 'a' + 10; + else if (b >= 'A' && b <= 'F') b_val = b - 'A' + 10; + + /* If both were valid hex, combine them */ + if (a_val != -1 && b_val != -1) { + *dst++ = (char)((a_val << 4) | b_val); + src += 3; + continue; + } + } + + /* Handle '+' as space, otherwise copy character literally */ + if (*src == '+') { + *dst++ = ' '; + } else { + *dst++ = *src; + } + src++; + } + *dst = '\0'; +} diff -r dfdd66825396 -r 8cf4ec5e2191 seobeo/seobeo.h --- a/seobeo/seobeo.h Fri Jan 23 22:22:30 2026 -0800 +++ b/seobeo/seobeo.h Fri Jan 23 22:38:59 2026 -0800 @@ -91,6 +91,9 @@ /* Destroy response and free all resources. */ extern void Seobeo_Client_Response_Destroy(Seobeo_Client_Response *p_resp); +// --- HTTP Web related helper functions --- // +extern void Seobeo_Url_Decode(char *dst, const char *src); + /** * WebSocket Client API * ------ @@ -324,18 +327,22 @@ extern void Seobeo_WebSocket_Server_Connection_Close(Seobeo_WebSocket_Server_Connection *p_conn, uint16 code, const char *reason); /* Initialize the router system (called automatically by Seobeo_Web_Server_Start) */ -extern void Seobeo_Router_Init(); +extern void Seobeo_Router_Init(); /* Register an API route handler. Call before starting server. */ -extern void Seobeo_Router_Register(const char *method, const char *path_pattern, Seobeo_Route_Handler handler); +extern void Seobeo_Router_Register(const char *method, const char *path_pattern, Seobeo_Route_Handler handler); +/* Register a streaming route handler. Handler receives client handle for direct streaming. */ +extern void Seobeo_Router_Register_Stream(const char *method, const char *path_pattern, Seobeo_Stream_Handler handler); /* Clean up router resources */ -extern void Seobeo_Router_Destroy(); +extern void Seobeo_Router_Destroy(); /* Find matching route handler (internal use) */ -extern Seobeo_Route_Handler Seobeo_Router_Find_Handler(const char *method, const char *path, Seobeo_Request_Entry **pp_request_map, Dowa_Arena *p_arena); +extern Seobeo_Route_Handler Seobeo_Router_Find_Handler(const char *method, const char *path, Seobeo_Request_Entry **pp_request_map, Dowa_Arena *p_arena); /* Send HTTP response from response map (internal use) */ -extern void Seobeo_Router_Send_Response(Seobeo_Handle *p_handle, Seobeo_Request_Entry *p_response_map, Dowa_Arena *p_arena); +extern void Seobeo_Router_Send_Response(Seobeo_Handle *p_handle, Seobeo_Request_Entry *p_response_map, Dowa_Arena *p_arena); /* Send HTTP response with keep-alive option */ -extern void Seobeo_Router_Send_Response_KeepAlive(Seobeo_Handle *p_handle, Seobeo_Request_Entry *p_response_map, Dowa_Arena *p_arena, boolean keep_alive); -extern char *Seobeo_Web_LoadFile(const char *file_path, size_t *p_file_size); +extern void Seobeo_Router_Send_Response_KeepAlive(Seobeo_Handle *p_handle, Seobeo_Request_Entry *p_response_map, Dowa_Arena *p_arena, boolean keep_alive); +extern char *Seobeo_Web_LoadFile(const char *file_path, size_t *p_file_size); +/* Being a proxy and keeping the client open */ +extern Seobeo_Stream_Handler Seobeo_Router_Find_Stream_Handler(const char *method, const char *path, Seobeo_Request_Entry **pp_request_map, Dowa_Arena *p_arena); // --- Helper functions --- // /* Destroy handle. It will handle all NULL poointers. */ diff -r dfdd66825396 -r 8cf4ec5e2191 seobeo/seobeo_internal.h --- a/seobeo/seobeo_internal.h Fri Jan 23 22:22:30 2026 -0800 +++ b/seobeo/seobeo_internal.h Fri Jan 23 22:38:59 2026 -0800 @@ -87,6 +87,13 @@ Dowa_Arena *p_arena ); +// Streaming handler - gets direct access to client handle for streaming responses +typedef void (*Seobeo_Stream_Handler)( + Seobeo_Handle *p_client_handle, + Seobeo_Request_Entry *p_request_map, + Dowa_Arena *p_arena +); + // --- Parse Header into Dowa Map ---// extern int Seobeo_Web_Header_Parse(Seobeo_Handle *p_handle, Seobeo_Request_Entry **pp_map, Dowa_Arena *p_arena);