git / brickware / marrow.git - f942600

(22 hours ago)commit f942600: smarter breadcrumbs

Summary | History | Files

commit f942600

authorTanner Stenson <tanner@brickware.sh>
dateWed Jan 7 23:20:07 2026 -0500

message

smarter breadcrumbs

diff

commit f942600e99a031235597e6858d965cd12934203b
Author: Tanner Stenson <tanner@brickware.sh>
Date:   Wed Jan 7 23:20:07 2026 -0500

    smarter breadcrumbs

diff --git a/marrow-static.c b/marrow-static.c
index 72d52aa..0b4b4a6 100644
--- a/marrow-static.c
+++ b/marrow-static.c
@@ -170,8 +170,57 @@ generate_html_header(FILE *out, const char *repo_name, const char *suffix) {
     fprintf(out, "</style>\n");
     fprintf(out, "</head>\n<body>\n");
 
-    /* Header with repo name as link */
-    fprintf(out, "<div class=\"header\"><h1><a href=\"../../../\">%s</a>", repo_name);
+    /* Header with breadcrumb navigation */
+    fprintf(out, "<div class=\"header\"><h1>");
+
+    /* Count slashes in repo_name to determine directory depth */
+    int32_t repo_depth = 0;
+    for (const char *p = repo_name; *p; p++) {
+        if ('/' == *p) repo_depth++;
+    }
+
+    /* Root "git" link - go up through branch/commit structure AND repo path */
+    fprintf(out, "<a href=\"");
+    /* From branch/main/tree or commit/hash/summary (3 levels) up through repo path */
+    for (int32_t i = 0; i < 3 + repo_depth; i++) {
+        fprintf(out, "../");
+    }
+    fprintf(out, "\">git</a>");
+
+    /* Parse repo path and create breadcrumb links */
+    if (repo_name && repo_name[0]) {
+        char path_copy[PATH_MAX];
+        snprintf(path_copy, sizeof(path_copy), "%s", repo_name);
+
+        char *token = strtok(path_copy, "/");
+
+        while (token) {
+            fprintf(out, " / ");
+
+            /* Calculate how many more segments remain */
+            char *remaining = strtok(NULL, "/");
+            int32_t remaining_segments = 0;
+            if (remaining) {
+                remaining_segments = 1;
+                for (char *p = remaining; *p; p++) {
+                    if ('/' == *p) remaining_segments++;
+                }
+            }
+
+            /* Create link */
+            fprintf(out, "<a href=\"");
+            /* Go up 3 levels (branch/main/tree) plus remaining segments in path */
+            for (int32_t i = 0; i < 3 + remaining_segments; i++) {
+                fprintf(out, "../");
+            }
+            fprintf(out, "\">");
+            html_escape(out, token);
+            fprintf(out, "</a>");
+
+            token = remaining;
+        }
+    }
+
     if (suffix && suffix[0]) {
         fprintf(out, " - %s", suffix);
     }
@@ -1372,13 +1421,71 @@ generate_directory_index_header(FILE *out, const char *dir_path) {
     fprintf(out, "</style>\n");
     fprintf(out, "</head>\n<body>\n");
 
+    /* Generate breadcrumb header */
     fprintf(out, "<div class=\"header\"><h1>");
+
     if (dir_path && dir_path[0]) {
-        fprintf(out, "git / ");
-        html_escape(out, dir_path);
+        /* Root link */
+        fprintf(out, "<a href=\"");
+
+        /* Calculate relative path back to root */
+        /* Count directory segments, not slashes (e.g., "user" = 1, "user/team" = 2) */
+        int32_t depth = 1;  /* Start at 1 for first directory */
+        for (const char *p = dir_path; *p; p++) {
+            if ('/' == *p) depth++;
+        }
+        for (int32_t i = 0; i < depth; i++) {
+            fprintf(out, "../");
+        }
+        fprintf(out, "\">git</a>");
+
+        /* Parse path and create breadcrumb links */
+        char path_copy[PATH_MAX];
+        snprintf(path_copy, sizeof(path_copy), "%s", dir_path);
+
+        char *token = strtok(path_copy, "/");
+        char accumulated_path[PATH_MAX] = {0};
+
+        while (token) {
+            fprintf(out, " / ");
+
+            /* Build accumulated path for this segment */
+            if (accumulated_path[0]) {
+                strncat(accumulated_path, "/", sizeof(accumulated_path) - strlen(accumulated_path) - 1);
+            }
+            strncat(accumulated_path, token, sizeof(accumulated_path) - strlen(accumulated_path) - 1);
+
+            /* Check if this is the last segment */
+            char *remaining = strtok(NULL, "/");
+
+            if (remaining) {
+                /* Not last segment - make it a link */
+                fprintf(out, "<a href=\"");
+
+                /* Calculate relative path to this segment */
+                int32_t remaining_depth = 1;
+                for (char *p = remaining; *p; p++) {
+                    if ('/' == *p) remaining_depth++;
+                }
+                for (int32_t i = 0; i < remaining_depth; i++) {
+                    fprintf(out, "../");
+                }
+
+                fprintf(out, "\">");
+                html_escape(out, token);
+                fprintf(out, "</a>");
+
+                token = remaining;
+            } else {
+                /* Last segment - no link */
+                html_escape(out, token);
+                token = NULL;
+            }
+        }
     } else {
         fprintf(out, "git repositories");
     }
+
     fprintf(out, "</h1></div>\n");
 }
 
@@ -1431,8 +1538,10 @@ generate_directory_index(const char *output_dir, const char *git_base_path,
     /* Generate header */
     generate_directory_index_header(out, relative_dir_path);
 
-    /* Generate subdirectories table if any exist */
-    if (dirs.count > 0) {
+    /* Generate subdirectories table if any exist OR if we need parent link */
+    int32_t show_dirs_table = (dirs.count > 0) || (relative_dir_path && relative_dir_path[0]);
+
+    if (show_dirs_table) {
         fprintf(out, "<h2>directories</h2>\n");
         fprintf(out, "<table>\n");
         fprintf(out, "<thead><tr><th>name</th><th>description</th></tr></thead>\n");