commit c9ad47e7d9a25455e10faf80296119e2ec457f10
Author: tri <tri@thac.loan>
Date:   Sat Sep 27 23:04:15 2025 +0700

    skeleton for writing home page

diff --git a/src/git.zig b/src/git.zig
index 2810936..5976b6e 100644
--- a/src/git.zig
+++ b/src/git.zig
@@ -31,7 +31,7 @@ pub fn updateServerInfo(gpa: mem.Allocator, dir: fs.Dir) !void {
 pub const Commit = struct {
     hash: []const u8,
     subject: []const u8,
-    date: []const u8,
+    time: []const u8,
 };
 
 pub fn getCommits(arena: std.mem.Allocator, dir: fs.Dir) ![]Commit {
@@ -58,7 +58,7 @@ pub fn getCommits(arena: std.mem.Allocator, dir: fs.Dir) ![]Commit {
         var fields_iter = std.mem.splitSequence(u8, commit_text, "\n");
         try commits.append(arena, Commit{
             .hash = fields_iter.next().?,
-            .date = fields_iter.next().?,
+            .time = fields_iter.next().?,
             .subject = fields_iter.next().?,
         });
     }
diff --git a/src/main.zig b/src/main.zig
index 97c9a3b..9234960 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -3,6 +3,13 @@ const fs = std.fs;
 const println = @import("utils.zig").println;
 const git = @import("git.zig");
 
+const RepoSummary = struct {
+    name: []const u8,
+    commit_count: usize,
+    last_commit_time: []const u8,
+    last_commit_msg: []const u8,
+};
+
 pub fn main() !u8 {
     if (std.os.argv.len != 2) {
         println("Usage: khoe <dir>", .{});
@@ -14,11 +21,22 @@ pub fn main() !u8 {
     const dba = dba_impl.allocator();
 
     const target_dir_path = std.os.argv[1];
-    println("Processing dir: {s}", .{target_dir_path});
+    println("Targeting dir: {s}", .{target_dir_path});
 
     var target_dir = try fs.cwd().openDirZ(target_dir_path, .{ .iterate = true });
     defer target_dir.close();
 
+    // Global arena, mostly to avoid making tiny allocations. Freeing the arena
+    // at the very end is unnecessary, but it keeps DebugAllocator's leak
+    // checker happy, leaving it free to report other real problems.
+    var arena_impl: std.heap.ArenaAllocator = .init(dba);
+    defer arena_impl.deinit();
+    const arena = arena_impl.allocator();
+
+    var repo_summaries: std.ArrayList(RepoSummary) = try .initCapacity(arena, 32);
+    defer repo_summaries.deinit(arena);
+
+    // This arena is reset after each repo's iteration
     var repo_arena_impl: std.heap.ArenaAllocator = .init(dba);
     defer repo_arena_impl.deinit();
     const repo_arena = repo_arena_impl.allocator();
@@ -43,8 +61,34 @@ pub fn main() !u8 {
         try git.updateServerInfo(repo_arena, repo_dir);
 
         const commits = try git.getCommits(repo_arena, repo_dir);
-        println("repo {s}: {d} commits", .{ entry.name, commits.len });
+        println("Found repo {s}: {d} commits", .{ entry.name, commits.len });
+
+        try repo_summaries.append(arena, .{
+            .name = try arena.dupe(u8, entry.name),
+            .commit_count = commits.len,
+            .last_commit_msg = if (commits.len == 0) "" else try arena.dupe(u8, commits[0].subject),
+            .last_commit_time = if (commits.len == 0) "" else try arena.dupe(u8, commits[0].time),
+        });
+
+        // TODO: write repo's index
+        // TODO: write repo's commits
     }
 
+    try writeHomePage(repo_summaries.items);
+
     return 0;
 }
+
+pub fn writeHomePage(repos: []RepoSummary) !void {
+    for (repos) |repo| {
+        println(
+            "repo {s}: {d} commits | {s} | {s}",
+            .{
+                repo.name,
+                repo.commit_count,
+                repo.last_commit_time,
+                repo.last_commit_msg,
+            },
+        );
+    }
+}