changeset 114:e2a73e64e8e6

[Postdog] Got history working.
author June Park <parkjune1995@gmail.com>
date Tue, 06 Jan 2026 08:15:37 -0800
parents 7a4e942814bc
children 96db6c3f38d6
files postdog/BUILD postdog/Roboto-Regular.ttf postdog/history/d8ff107c-856d-e516-4863-53fe587855eb.txt postdog/history/fca62761-983e-c04b-2f77-c3bfde99f400.txt postdog/history/test.txt postdog/main.c third_party/raylib/include/raygui.h third_party/raylib/raylib.bzl
diffstat 8 files changed, 668 insertions(+), 69 deletions(-) [+]
line wrap: on
line diff
--- a/postdog/BUILD	Sun Jan 04 14:42:54 2026 -0800
+++ b/postdog/BUILD	Tue Jan 06 08:15:37 2026 -0800
@@ -10,6 +10,7 @@
     "//third_party/raylib:raylib",
     "//dowa:dowa",
   ],
+  data = [":all_static_assets",],
   linkopts_macos = [
     "-framework CoreVideo",
     "-framework IOKit",
@@ -36,3 +37,10 @@
   bundle_id = "com.june.postdog"
 )
 
+filegroup(
+  name = "all_static_assets",
+  srcs = glob([
+      "**/*.ttf",
+      "**/*.txt",
+  ], allow_empty=True)
+)
Binary file postdog/Roboto-Regular.ttf has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/postdog/history/d8ff107c-856d-e516-4863-53fe587855eb.txt	Tue Jan 06 08:15:37 2026 -0800
@@ -0,0 +1,240 @@
+https:/mrjunejune.com
+---
+GET
+---
+Content-Type: application/json
+---
+
+---
+HTTP Status: 200
+
+<!doctype html>
+<html lang="en">
+  <head>
+    <title> MrJuneJune </title>
+    <meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<link rel="icon" type="image/svg+xml" href="/public/epi_all_colors.svg">
+
+<link rel="preload" href="/public/fonts/Roboto-Regular.ttf" as="font"  crossorigin>
+<link rel="preload" href="/public/fonts/Roboto-Thin.ttf"as="font" crossorigin>
+
+<link rel="preload" href="/public/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin>
+<link rel="preload" href="/public/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin>
+
+<link rel="preload" href="/public/fonts/more-sugar.extras.otf" as="font" type="font/otf" crossorigin>
+<link rel="preload" href="/public/fonts/more-sugar.regular.otf" as="font" type="font/otf" crossorigin>
+<link rel="preload" href="/public/fonts/more-sugar.thin.otf" as="font" type="font/otf" crossorigin>
+
+<link rel="preload" href="/base.css" as="style" />
+<link rel="stylesheet" href="/base.css" />
+
+
+    <style>
+      .epi-photo {
+        display: flex;
+        justify-content: center;
+        margin-bottom: 10px;
+      }
+
+      .epi-photo img {
+        max-width: 100%;
+        height: auto;
+        border-radius: 8px;
+      }
+
+      @media (max-width: 720px) {
+        .epi-photo {
+          margin-bottom: 1.5em;
+        }
+
+        ul {
+          padding-left: 1.5em;
+        }
+
+        li {
+          margin-bottom: 0.75em;
+        }
+      }
+    </style>
+  </head>
+  <body>
+     <style>
+  :root {
+    --header-background: var(--white);
+    --header-color: rgb(var(--black));
+    --link-hover-accent: var(--awesome);
+  }
+
+  /* Fixed icon in top left corner */
+  #themeToggle {
+    position: fixed;
+    top: 20px;
+    left: 20px;
+    background: var(--header-background);
+    display: flex;
+    align-items: center;
+    border-radius: 50%;
+    cursor: pointer;
+    z-index: 1000;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+    transition: transform 0.2s ease;
+  }
+
+  #themeToggle:hover {
+    transform: scale(1.05);
+  }
+
+  /* Professional header */
+  header {
+    margin: auto;
+    padding: 1.5em 1em;
+    font-family: "More", sans-serif;
+    box-shadow: 0 2px 8px rgba(var(--black), 5%);
+    width: 720px;
+    max-width: calc(100% - 2em);
+    text-align: center;
+  }
+
+  header h1 {
+    margin: 0;
+    font-size: 1.8em;
+    font-weight: 700;
+    letter-spacing: -0.5px;
+  }
+
+  header h1 a {
+    text-decoration: none;
+    color: var(--header-color);
+  }
+
+  header h1 a::before {
+    display: none;
+  }
+
+  /* Mobile responsiveness */
+  @media (max-width: 720px) {
+    #themeToggle {
+      top: 15px;
+      left: 15px;
+    }
+
+    header {
+      padding: 1em;
+    }
+
+    header h1 {
+      font-size: 1.5em;
+    }
+  }
+
+  @media (max-width: 480px) {
+    #themeToggle {
+      top: 10px;
+      left: 10px;
+    }
+
+    #themeToggle img {
+      height: 40px;
+      width: 40px;
+    }
+
+    header h1 {
+      font-size: 1.3em;
+    }
+  }
+
+  #logo {
+    width: 300px;
+  }
+
+  /* 1. DEFINE THE DEFAULTS (Light Mode) */
+  :root {
+    --logo-invert: invert(0);
+    --epi-grayscale: grayscale(0) brightness(1);
+  }
+  
+  /* 2. MANUAL DARK OVERRIDE */
+  html.dark {
+    --logo-invert: invert(1);
+    --epi-grayscale: grayscale(1);
+  }
+  
+  /* 3. MANUAL LIGHT OVERRIDE */
+  html.light-mode {
+    --logo-invert: invert(0);
+    --epi-grayscale: brightness(2.9) grayscale(1);
+  }
+  
+  /* 4. SYSTEM PREFERENCE */
+  @media (prefers-color-scheme: dark) {
+    :root:not(.light-mode) {
+      --logo-invert: invert(1);
+    }
+  }
+  
+  /* 5. APPLY TO ELEMENTS */
+  #logo {
+    -webkit-filter: var(--logo-invert);
+    filter: var(--logo-invert);
+    transition: filter 0.3s ease;
+  }
+  
+  .epi-logo {
+    -webkit-filter: var(--epi-grayscale);
+    filter: var(--epi-grayscale);
+    transition: filter 0.3s ease;
+  }
+</style>
+
+<div id="themeToggle">
+  <img id="epiChan" class="epi-logo" aria-label="Toggle dark mode" src="/public/epi_all_colors.svg" height="50" width="50">
+</div>
+
+<header>
+  <h1><a href="/">MrJuneJune</a></h1>
+</header>
+<script src="/index.js"></script>
+
+
+     <main>
+       <p>Hi, my name is Juntae, but most people call me June or MrJuneJune.</p>
+
+       <p>I am a software engineer with experience spanning a wide range of companies, from small startups to FAANGs....</p>
+       <p>I know it is lame to work for them, but I have a dog so I need to put foods on my table.</p>
+
+       <div class="epi-photo">
+         <img id="currentPhoto" style="opacity: 0; transition: opacity 0.2s;" />
+       </div>
+
+       <p>During my free time, I like to write codes mostly in C, Python, and Typescript; all in mono repo styles using bazel. (I know that is mentally ill...)</p>
+       <p>Feel free to check it out my bad code!</p>
+
+       <h2>Links</h2>
+       <ul>
+         <li><a href="https://zenbu.babocoder.com/file/tip">Repository</a> - Check out my code</li>
+         <li><a href="/blog">Blogs</a> - My thoughts / Experiments </li>
+         <li><a href="/resume">Resume</a> - My professional experiences </li>
+         <li><a href="/tools">Tools</a> - Things I made for myself </li>
+       </ul> 
+     </main>
+     <div style="display: flex; align-items: center; justify-content: center; margin: 30px 0px;">
+  <small>&copy; 2026 June Park</small>
+</div>
+
+  <script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'9b9c39a8799bcbfd',t:'MTc2NzcxMzA5Nw=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>
+  <script>
+    let arr = Array.from({ length: 18 }, (_, i) => i+1);
+    function setRandomImages() {
+      const randomIndex = Math.floor(Math.random() * arr.length);
+      const pos = arr[randomIndex];
+      currentPhoto.src = `/public/epi-photos/webp/${pos}.webp`;
+      currentPhoto.onload = () => {
+        currentPhoto.style.opacity = "1";
+      };
+      setTimeout(() => setRandomImages(), 1000);
+    }
+    setRandomImages();
+  </script>
+</html>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/postdog/history/fca62761-983e-c04b-2f77-c3bfde99f400.txt	Tue Jan 06 08:15:37 2026 -0800
@@ -0,0 +1,22 @@
+https://httpbin.org/get
+---
+GET
+---
+Content-Type: application/json
+---
+
+---
+HTTP Status: 200
+
+{
+  "args": {}, 
+  "headers": {
+    "Accept": "*/*", 
+    "Content-Type": "application/json", 
+    "Host": "httpbin.org", 
+    "X-Amzn-Trace-Id": "Root=1-695d2b83-44f5bafa141af3382d8e56b8"
+  }, 
+  "origin": "134.128.194.38", 
+  "url": "https://httpbin.org/get"
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/postdog/history/test.txt	Tue Jan 06 08:15:37 2026 -0800
@@ -0,0 +1,23 @@
+https://httpbin.org/get
+---
+GET
+---
+Content-Type: application/json
+June: Park 
+---
+
+---
+HTTP Status: 200
+
+{
+  "args": {}, 
+  "headers": {
+    "Accept": "*/*", 
+    "Content-Type": "application/json", 
+    "Host": "httpbin.org", 
+    "X-Amzn-Trace-Id": "Root=1-695bdbd6-243c50b9737c539f43a5badc"
+  }, 
+  "origin": "134.128.194.38", 
+  "url": "https://httpbin.org/get"
+}
+
--- a/postdog/main.c	Sun Jan 04 14:42:54 2026 -0800
+++ b/postdog/main.c	Tue Jan 06 08:15:37 2026 -0800
@@ -3,7 +3,6 @@
 #include <string.h>
 #include <time.h>
 #include <sys/stat.h>
-#include <dirent.h>
 #include "dowa/dowa.h"
 
 #include <curl/curl.h>
@@ -11,6 +10,10 @@
 #define RAYGUI_IMPLEMENTATION
 #include "third_party/raylib/include/raygui.h"
 
+#ifndef POSTDOG_PATHS
+  #define POSTDOG_PATHS "/Users/mrjunejune/zenbu/postdog/history"
+#endif
+
 #define SCREEN_WIDTH 1280
 #define SCREEN_HEIGHT 780
 #define TEXT_SIZE 10
@@ -27,6 +30,19 @@
 #define RESULT_BUFFER_LENGTH 1024 * 1025 * 5
 #define AREANA_BUFFER_LENGTH 1024 * 1025 * 15
 
+
+#ifdef _WIN32
+    #include <direct.h>
+    #include <io.h>
+    #define mkdir(path, mode) _mkdir(path)
+    #define access _access
+    #define F_OK 0
+#else
+    #include <sys/stat.h>
+    #include <dirent.h>
+    #include <unistd.h>
+#endif
+
 typedef Dowa_KV(char*, char*) INPUT_HASHMAP;
 
 typedef struct {
@@ -35,12 +51,114 @@
 } ResponseBuffer;
 
 typedef struct {
-  char filename[256];
-  char displayName[128];
-  char method[16];
-  time_t timestamp;
+  char *filename;
+  Rectangle rect;
+  long time_modified;
 } HistoryItem;
 
+typedef struct {
+  Rectangle rectangle;
+  char *label;
+  bool active;
+} TabItem;
+
+typedef enum {
+  TAB_HEADER = 0,
+  TAB_BODY,
+  TAB_GET_PARAMS,
+  TAB_BAR,
+  TAB_LENGTH
+} PostDog_Tab_Enum;
+
+static uint32 counter = 0;
+HistoryItem *history_items = NULL;
+HistoryItem *new_history_items = NULL;
+
+int CompareHistoryItemsByDate(const void *a, const void *b) {
+  HistoryItem *itemA = (HistoryItem *)a;
+  HistoryItem *itemB = (HistoryItem *)b;
+  return (itemB->time_modified - itemA->time_modified); 
+}
+
+// TODO: Make this into generic fucntion so I can use it across different thing.
+void PostDog_List_Directory(const char *path, HistoryItem **p_file_arr)
+{
+  HistoryItem *file_arr = *p_file_arr;
+#ifdef _WIN32
+  struct _finddata_t fileinfo;
+  intptr_t handle;
+  char search_path[256];
+  sprintf(search_path, "%s\\*", path);
+
+  if ((handle = _findfirst(search_path, &fileinfo)) == -1L) {
+    printf("Directory is empty or cannot be read.\n");
+  } else {
+    do {
+      HistoryItem item = {0};
+      item.filename = strdup(fileinfo.name);
+      item.rect = (Rectangle){0};
+      item.time_modified = fileinfo.time_write; 
+      Dowa_Array_Push(file_arr, item);
+    } while (_findnext(handle, &fileinfo) == 0);
+    _findclose(handle);
+  }
+#else
+  struct dirent *entry;
+  struct stat file_stat;
+  DIR *dp = opendir(path);
+  if (dp == NULL) return;
+
+  char full_path[256];
+  while ((entry = readdir(dp)))
+  {
+    if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
+      continue;
+    snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name);
+    if (stat(full_path, &file_stat) == 0)
+    {
+      HistoryItem item = {0};
+      item.filename = strdup(entry->d_name);
+      item.time_modified = file_stat.st_mtime;
+      Dowa_Array_Push(file_arr, item);
+    }
+  }
+  closedir(dp);
+#endif
+  int count = Dowa_Array_Length(file_arr);
+  if (count > 1) {
+    qsort(file_arr, count, sizeof(HistoryItem), CompareHistoryItemsByDate);
+  }
+}
+
+int PostDog_History_Load(HistoryItem **p_history_files)
+{
+  if (access(POSTDOG_PATHS, F_OK) == -1)
+  {
+    printf("Directory '%s' not found. Creating it...\n", POSTDOG_PATHS);
+    if (mkdir(POSTDOG_PATHS, 0777) != 0)
+      return -1;
+    return 0;
+  }
+  printf("Directory '%s' already exists.\n", POSTDOG_PATHS);
+  PostDog_List_Directory(POSTDOG_PATHS, p_history_files);
+  return 0;
+}
+
+bool InArea(Vector2 mouse_position, Rectangle area)
+{
+  return (
+    mouse_position.x >= area.x &&
+    mouse_position.x < area.x + area.width &&
+    mouse_position.y >= area.y &&
+    mouse_position.y < area.y + area.height
+  );
+}
+
+bool Clicked(Vector2 mouse_position, Rectangle area)
+{
+  return (InArea(mouse_position, area) && IsMouseButtonPressed(MOUSE_BUTTON_LEFT));
+}
+
 char *PostDog_Enum_To_String(int active_enum)
 {
   switch(active_enum)
@@ -50,6 +168,74 @@
     case 2: return "PUT";
     case 3: return "DELETE";
   }
+  return 0;
+}
+
+void PostDog_History_CreateFile(char *filename, char* values)
+{
+  char full_file_path[512] = {0};
+  snprintf(full_file_path, 512, "%s/%s", POSTDOG_PATHS, filename);
+  FILE *file = fopen(full_file_path, "w");
+  if (!file)
+  {
+    printf("Failed to create a file: %s\n", full_file_path);
+    return;
+  }
+  fwrite(values, 1, strlen(values), file);
+  fclose(file);
+}
+
+void PostDog_Request_SaveFile(
+    const char *url,
+    const char *method,
+    const char *headers,
+    const char *body, 
+    const char *response)
+{
+  size_t new_file_size = 1024 * 1024;
+  Dowa_Arena *arena = Dowa_Arena_Create(1024 * 1024 * 2);
+  char *new_file = Dowa_Arena_Allocate(arena, 1024 * 1024);
+  snprintf(
+      new_file,
+      new_file_size,
+      "%s\n"
+      "---\n"
+      "%s\n"
+      "---\n"
+      "%s\n"
+      "---\n"
+      "%s\n"
+      "---\n"
+      "%s\n",
+      url,
+      method,
+      headers,
+      body,
+      response
+  );
+  char *filename = Dowa_Arena_Allocate(arena, 1024);
+  if (!filename)
+  {
+    perror("Error opening file");
+    exit(EXIT_FAILURE); 
+  }
+  char *uuid4 = (char *)Dowa_Arena_Allocate(arena, 37);
+  if (!uuid4)
+  {
+    perror("Error uuid");
+    exit(EXIT_FAILURE); 
+  }
+  
+  int32 seed = (uint32)time(NULL) ^ counter++;
+  Dowa_String_UUID(seed, uuid4);
+  snprintf(filename, 1024, "%s.txt", uuid4);
+  PostDog_History_CreateFile(filename, new_file);
+
+  HistoryItem item = (HistoryItem){ .filename = malloc(sizeof(char) * strlen(filename) + 1), .rect = (Rectangle){0} };
+  memcpy(item.filename, filename, strlen(filename) + 1);
+  Dowa_Array_Push(new_history_items, item);
+  
+  Dowa_Arena_Free(arena);
 }
 
 static size_t Postdog_Curl_Callback(void *contents, size_t size, size_t nmemb, void *userp)
@@ -72,7 +258,7 @@
   return real_size;
 }
 
-int PostDog_Make_HttpRequest(
+int PostDog_Http_Request(
     const char *url,
     const char *method,
     const char *headers,
@@ -106,16 +292,14 @@
     if (strcmp(method, "POST") == 0)
     {
       curl_easy_setopt(curl, CURLOPT_POST, 1L);
-      if (body && strlen(body) > 0) {
+      if (body && strlen(body) > 0)
         curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
-      }
     } 
     else if (strcmp(method, "PUT") == 0)
     {
       curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
-      if (body && strlen(body) > 0) {
+      if (body && strlen(body) > 0)
         curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
-      }
     }
     else if (strcmp(method, "DELETE") == 0)
     {
@@ -131,9 +315,8 @@
       while (line != NULL) {
         // Trim whitespace
         while (*line == ' ' || *line == '\t') line++;
-        if (strlen(line) > 0) {
+        if (strlen(line) > 0)
           headerList = curl_slist_append(headerList, line);
-        }
         line = strtok(NULL, "\n");
       }
       curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerList);
@@ -146,10 +329,8 @@
 
     // Follow redirects
     curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
-
     // Set timeout
     curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
-
     // Perform request
     res = curl_easy_perform(curl);
 
@@ -174,27 +355,20 @@
   free(buffer.data);
   curl_global_cleanup();
 
+  PostDog_Request_SaveFile(
+    url,
+    method,
+    headers,
+    body,
+    response);
   return 0;
 }
 
-typedef struct {
-  Rectangle rectangle;
-  char *label;
-  bool active;
-} TabItem;
-
-typedef enum {
-  TAB_HEADER = 0,
-  TAB_BODY,
-  TAB_GET_PARAMS,
-  TAB_BAR,
-} PostDog_Tab_Enum;
-
 void PostDog_Update_URL(char **p_url_input_text, char* get_params)
 {
   char *url_input_text = *p_url_input_text;
 
-  // Reset
+  // Reset 
   char *question_mark = strchr(url_input_text, '?');
   if (question_mark)
     *question_mark = '\0';
@@ -207,43 +381,23 @@
 
   Dowa_Arena *arena = Dowa_Arena_Create(1024*1024);
   char **lines = Dowa_String_Split(get_params, "\n", get_params_length, 1, arena);
-
-  size_t url_len = strlen(url_input_text);
-  size_t url_capacity = URL_TEXT_BUFFER;
-
   for (int i = 0; i < Dowa_Array_Length(lines); i++)
   {
     char *line = lines[i];
     char **key_value = Dowa_String_Split(line, " ", (int)strlen(line), 1, arena);
 
     if (Dowa_Array_Length(key_value) < 2)
-      continue;  // Skip this line, not break entire loop
-
-    // Check buffer capacity before each append
-    size_t needed = url_len + strlen(separator) + strlen(key_value[0]) + 1 + 10; // +10 for "=" and safety
-    if (needed >= url_capacity) break;
+      break;
 
     strcat(url_input_text, separator);
     strcat(url_input_text, key_value[0]);
     strcat(url_input_text, "=");
-    url_len = strlen(url_input_text);
-
     for (int i = 1; i < Dowa_Array_Length(key_value); i++)
     {
       if (!key_value[i] || key_value[i][0] == '\0')
         break;
-
-      size_t value_len = strlen(key_value[i]);
-      needed = url_len + value_len + 4; // +4 for "%20" if needed
-      if (needed >= url_capacity) break;
-
-      printf("\n\n------\n\n");
-      printf("key_value[%i]: %s, p = %p\n", i, key_value[i], key_value[i]);
-      printf("\n\n------\n\n");
-
       if (i > 1) strcat(url_input_text, "%20");
       strcat(url_input_text, key_value[i]);
-      url_len = strlen(url_input_text);
     }
     separator = "&";
   }
@@ -251,6 +405,100 @@
   Dowa_Arena_Free(arena);
 }
 
+int PostDog_String_To_MethodEnum(char *value)
+{
+  if (strstr(value, "GET"))
+    return 0;
+  if (strstr(value, "POST"))
+    return 1;
+  if (strstr(value, "PUT"))
+    return 2;
+  if (strstr(value, "DELETE"))
+    return 3;
+  return 0;
+}
+
+void PostDog_Params_Reset(
+    char **p_url_input_text,
+    int  *p_active_method_dropdown,
+    char **url_body_map,
+    char **p_url_result_text
+)
+{
+  char *url_input_text = *p_url_input_text;
+  char *url_result_text = *p_url_result_text;
+  int active_method_dropdown = *p_active_method_dropdown;
+
+  url_input_text = "";
+  url_result_text = "";
+  active_method_dropdown = 0;
+  for (int i = 0; i < Dowa_Array_Length(url_body_map); i++)
+    url_body_map[i] = "";
+}
+
+void PostDog_Load_File(
+    const char *filename,
+    char **p_url_input_text,
+    int  *p_active_method_dropdown,
+    char **url_body_map,
+    char **p_url_result_text
+) { 
+  char *url_input_text = *p_url_input_text;
+  char *url_result_text = *p_url_result_text;
+  int active_method_dropdown = *p_active_method_dropdown;
+
+  char full_file_path[512] = {0};
+  snprintf(full_file_path, 512, "%s/%s", POSTDOG_PATHS, filename);
+  FILE *file = fopen(full_file_path, "r");
+  if (!file)
+    return;
+
+  fseek(file, 0, SEEK_END);
+  size_t file_size = ftell(file);
+  fseek(file, 0, SEEK_SET);
+
+  Dowa_Arena *init_arena = Dowa_Arena_Create(file_size + 2);
+  Dowa_Arena *split_arena = Dowa_Arena_Create(file_size * 2);
+  char *file_buffer = Dowa_Arena_Allocate(init_arena, file_size+1);
+  fread(file_buffer, 1, file_size, file);
+  char **values = Dowa_String_Split(file_buffer, "---\n", file_size, 4, split_arena);
+  Dowa_Arena_Free(init_arena);
+  for (int i = 0; i < Dowa_Array_Length(values); i++)
+  {
+    if (i == 0)
+    {
+      snprintf(url_input_text, strlen(values[i]) + 1, "%s", values[i]);
+      url_input_text[strcspn(url_input_text, "\n")] = '\0';
+    }
+    else if (i == 1)
+      active_method_dropdown = PostDog_String_To_MethodEnum(values[i]);
+    else if (i <= 3)
+    {
+      snprintf(url_body_map[i-2], strlen(values[i]) + 1, "%s", values[i]);
+      for (int j = strlen(values[i]); j > 0; j--)
+      {
+        if (url_body_map[i-2][j] == '\n')
+        {
+          url_body_map[i-2][j] = '\0';
+          break;
+        }
+      }
+    }
+    else
+      snprintf(url_result_text, strlen(values[i]) + 1, "%s", values[i]);
+  }
+}
+
+Rectangle AddPadding(Rectangle rect, float padding)
+{
+  return (Rectangle){ 
+    rect.x + padding,
+    rect.y + padding,
+    rect.width - (2 * padding),
+    rect.height - (2 * padding)
+  };
+}
+
 int main()
 {
   // -- initizlied --//
@@ -260,31 +508,39 @@
 
   Dowa_Arena *arena = Dowa_Arena_Create(AREANA_BUFFER_LENGTH);
 
+  Font customFont = LoadFontEx("postdog/Roboto-Regular.ttf", 20, 0, 0);
+  GuiSetFont(customFont);
+  GuiSetStyle(DEFAULT, TEXT_SIZE, 20);
+
   // -- Starting pos ---//
+  // Everyhting is relative to sidebar at this point lol
   float side_bar_x = 10;
   float side_bar_y = 10;
-
   Rectangle history_sidebar = { .x = side_bar_x, .y = side_bar_y, .width = 0, .height = 0 };
+  Dowa_Array_Reserve(history_items, 10);
+  Dowa_Array_Reserve(new_history_items, 10);
+  PostDog_History_Load(&history_items);
 
   Rectangle url_area = { 0 };
   Rectangle textBounds = { 0 };
   Rectangle url_input_bounds = { 0 };
-  bool url_input_bool = false;
+  bool url_input_edit = false;
   Rectangle url_text_bounds = { 0 };
   Rectangle url_enter_button = { 0 };
   Rectangle method_dropdown = { 0 };
 
+
   char *url_input_text = (char *)Dowa_Arena_Allocate(arena, URL_TEXT_BUFFER);
   snprintf(url_input_text, URL_TEXT_BUFFER, "https://httpbin.org/get");
 
-  INPUT_HASHMAP *url_body_map = NULL;
-  Dowa_HashMap_Push_Arena(url_body_map, "Header", (char *)Dowa_Arena_Allocate(arena, HEADER_BUFFER_LENGTH), arena);
-  Dowa_HashMap_Push_Arena(url_body_map, "Body", (char *)Dowa_Arena_Allocate(arena, BODY_BUFFER_LENGTH), arena);
-  Dowa_HashMap_Push_Arena(url_body_map, "Get Param", (char *)Dowa_Arena_Allocate(arena, DEFAULT_TEXT_BUFFER_LENGTH), arena);
-  Dowa_HashMap_Push_Arena(url_body_map, "Bar",  (char *)Dowa_Arena_Allocate(arena, DEFAULT_TEXT_BUFFER_LENGTH), arena);
+  char **url_body_map = NULL;
+  Dowa_Array_Push_Arena(url_body_map, (char *)Dowa_Arena_Allocate(arena, HEADER_BUFFER_LENGTH), arena);
+  Dowa_Array_Push_Arena(url_body_map, (char *)Dowa_Arena_Allocate(arena, BODY_BUFFER_LENGTH), arena);
+  Dowa_Array_Push_Arena(url_body_map, (char *)Dowa_Arena_Allocate(arena, DEFAULT_TEXT_BUFFER_LENGTH), arena);
+  Dowa_Array_Push_Arena(url_body_map, (char *)Dowa_Arena_Allocate(arena, DEFAULT_TEXT_BUFFER_LENGTH), arena);
 
-  snprintf(url_body_map[0].value, HEADER_BUFFER_LENGTH, "Content-Type: application/json");
-  snprintf(url_body_map[1].value, HEADER_BUFFER_LENGTH, "");
+  snprintf(url_body_map[TAB_HEADER], HEADER_BUFFER_LENGTH, "Content-Type: application/json");
+  snprintf(url_body_map[TAB_BODY], HEADER_BUFFER_LENGTH, "");
 
   char *url_result_text = (char *)Dowa_Arena_Allocate(arena, RESULT_BUFFER_LENGTH);
 
@@ -305,7 +561,7 @@
   Rectangle result_body = { 0 };
 
   // General styling.
-  int padding = 10; // TODO make it % based?
+  float padding = 10; // TODO make it % based?
   int active_input_tab = 0;
 
   while (!WindowShouldClose())
@@ -314,7 +570,19 @@
     int screen_height = GetScreenHeight();
 
     history_sidebar.width = screen_width * 0.15;
-    history_sidebar.height = screen_width - 10;
+    history_sidebar.height = screen_width - 10; 
+
+    int32 new_history_items_length = Dowa_Array_Length(new_history_items);
+    int32 history_item_length = Dowa_Array_Length(history_items);
+    int32 total =  new_history_items_length + history_item_length;
+    for (int i = 0; i < total; i++)
+    {
+      HistoryItem *curr_history_items = i < new_history_items_length ? &new_history_items[i] : &history_items[i - new_history_items_length];
+      curr_history_items->rect.x = history_sidebar.x + padding;
+      curr_history_items->rect.y = history_sidebar.y + (padding * 2 * (i+1)) + (i * history_sidebar.height * 0.05);
+      curr_history_items->rect.width = history_sidebar.width - (padding * 2);
+      curr_history_items->rect.height = history_sidebar.height * 0.05;
+    }
 
     // -- URL Area --//
     url_area.x = (side_bar_x + history_sidebar.width);
@@ -371,24 +639,32 @@
     result_body.width = url_area.width * 0.49 - padding;
     result_body.height = result_area.height - input_tab.height - padding;
 
+    Vector2 mouse_position = GetMousePosition();
+
     BeginDrawing();
       ClearBackground(GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR)));
 
-      // Sidebar Rect
       DrawRectangleRec(history_sidebar, Fade(GRAY, 0.1f));
+      for (int i = 0; i < total; i++)
+      {
+        HistoryItem *curr_history_items = i < new_history_items_length ? &new_history_items[i] : &history_items[i - new_history_items_length];
+        DrawRectangleRec(curr_history_items->rect, Fade(RED, 0.1f)); 
+        GuiDrawText(curr_history_items->filename, AddPadding(curr_history_items->rect, padding), TEXT_ALIGN_CENTER, RED);
+      }
 
       // URL area Rect
       GuiDrawText("URL: ", url_text_bounds, TEXT_ALIGN_CENTER, RED); 
       DrawRectangleRec(url_area, Fade(RED, 0.1f));
-      if (GuiTextBox(url_input_bounds, url_input_text, DEFAULT_TEXT_BUFFER_LENGTH, url_input_bool))
-        url_input_bool = !url_input_bool;
+      if (GuiTextBox(url_input_bounds, url_input_text, DEFAULT_TEXT_BUFFER_LENGTH, url_input_edit))
+        url_input_edit = !url_input_edit;
+
       sendRequest = GuiButton(url_enter_button, "ENTER");
       if (sendRequest)
-        PostDog_Make_HttpRequest(
+        PostDog_Http_Request(
             url_input_text,
             PostDog_Enum_To_String(active_method_dropdown),
-            url_body_map[0].value,
-            url_body_map[1].value,
+            url_body_map[TAB_HEADER],
+            url_body_map[TAB_BODY],
             url_result_text,
             RESULT_BUFFER_LENGTH
         );
@@ -399,15 +675,43 @@
       DrawRectangleRec(input_area, Fade(BLUE, 0.1f));
       DrawRectangleRec(input_tab,  Fade(DARKBLUE, 0.1f));
       GuiSetStyle(TOGGLE, GROUP_PADDING, 0);
-      if (JUNE_GuiTextBox(input_body, url_body_map[active_input_tab].value, DEFAULT_TEXT_BUFFER_LENGTH, input_body_bool))
+      if (JUNE_GuiTextBox(input_body, url_body_map[active_input_tab], DEFAULT_TEXT_BUFFER_LENGTH, input_body_bool))
         input_body_bool = !input_body_bool;
       GuiToggleGroup(input_tab_item, "Header;Body;Get Param;Bar", &active_input_tab);
-      PostDog_Update_URL(&url_input_text, url_body_map[TAB_GET_PARAMS].value);
+
+      PostDog_Update_URL(&url_input_text, url_body_map[TAB_GET_PARAMS]);
 
       // Result Rect
       DrawRectangleRec(result_area, Fade(GREEN, 0.1f));
       DrawRectangleRec(result_body, Fade(DARKGREEN, 0.1f));
       GuiTextBoxMulti(result_body, url_result_text, RESULT_BUFFER_LENGTH, false);
+
+      if (url_input_edit && (IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C))
+      {
+        SetClipboardText(url_input_text);
+      }
+      else if (input_body_bool && (IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C))
+      {
+        SetClipboardText(url_body_map[active_input_tab]);
+      }
+      else if (InArea(mouse_position, result_body))
+      {
+        DrawRectangleRec(result_body, Fade(GREEN, 0.3f));
+        if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C))
+          SetClipboardText(url_result_text);
+      }
+
+      for (int i = 0; i < Dowa_Array_Length(history_items); i++)
+      {
+        if (Clicked(mouse_position, history_items[i].rect))
+          PostDog_Load_File(
+              history_items[i].filename,
+              &url_input_text,
+              &active_method_dropdown,
+              url_body_map,
+              &url_result_text
+          );
+      }
     EndDrawing();
   }
   CloseWindow();
--- a/third_party/raylib/include/raygui.h	Sun Jan 04 14:42:54 2026 -0800
+++ b/third_party/raylib/include/raygui.h	Tue Jan 06 08:15:37 2026 -0800
@@ -6594,7 +6594,7 @@
     {
         GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED)));
     }
-    else GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), BLANK);
+    else GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))),  GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED)));
 
     // Draw text considering index offset if required
     // NOTE: Text index offset depends on cursor position
--- a/third_party/raylib/raylib.bzl	Sun Jan 04 14:42:54 2026 -0800
+++ b/third_party/raylib/raylib.bzl	Tue Jan 06 08:15:37 2026 -0800
@@ -2,6 +2,7 @@
         name,
         srcs,
         deps = [],
+        data = [],
         deps_macos = [],
         deps_linux = [],
         deps_windows = [],
@@ -46,6 +47,7 @@
     native.cc_binary(
         name = name,
         srcs = srcs,
+        data = data,
         deps = deps + select({
             "//config:macos": deps_macos,
             "//config:linux": deps_linux,