diff --git a/src/logger.c b/src/logger.c
index de2eead..997a975 100644
--- a/src/logger.c
+++ b/src/logger.c
@@ -71,9 +71,9 @@ typedef enum { LVL_INFO, LVL_OK, LVL_WARN, LVL_ERROR } LogLevel;
 static const char *level_tag(LogLevel lv)
 {
     switch (lv) {
-        case LVL_INFO:  return "[INFO ]";
-        case LVL_OK:    return "[OK   ]";
-        case LVL_WARN:  return "[WARN ]";
+        case LVL_INFO:  return "[INFO]";
+        case LVL_OK:    return "[OK]";
+        case LVL_WARN:  return "[WARN]";
         case LVL_ERROR: return "[ERROR]";
     }
     return "[     ]";
diff --git a/bin/gbuild b/bin/gbuild
deleted file mode 100755
index dc9ad62..0000000
Binary files a/bin/gbuild and /dev/null differ
diff --git a/bin/gconfig b/bin/gconfig
deleted file mode 100755
index 2b1ee61..0000000
Binary files a/bin/gconfig and /dev/null differ
diff --git a/dist/gbuild-1.0.0-1.x86_64.rpm b/dist/gbuild-1.0.0-1.x86_64.rpm
deleted file mode 100644
index 2ac5ced..0000000
Binary files a/dist/gbuild-1.0.0-1.x86_64.rpm and /dev/null differ
diff --git a/dist/gbuild-1.0.0-x86_64-bin.tar.gz b/dist/gbuild-1.0.0-x86_64-bin.tar.gz
deleted file mode 100644
index 8e410d7..0000000
Binary files a/dist/gbuild-1.0.0-x86_64-bin.tar.gz and /dev/null differ
diff --git a/dist/gbuild-1.0.0.tar.gz b/dist/gbuild-1.0.0.tar.gz
deleted file mode 100644
index 4fea580..0000000
Binary files a/dist/gbuild-1.0.0.tar.gz and /dev/null differ
diff --git a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm b/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm
deleted file mode 100644
index c0bcf34..0000000
Binary files a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm and /dev/null differ
diff --git a/dist/gbuild_1.0.0_amd64.deb b/dist/gbuild_1.0.0_amd64.deb
deleted file mode 100644
index 58b5e08..0000000
Binary files a/dist/gbuild_1.0.0_amd64.deb and /dev/null differ
diff --git a/bin/gbuild b/bin/gbuild
deleted file mode 100755
index dc9ad62..0000000
Binary files a/bin/gbuild and /dev/null differ
diff --git a/bin/gconfig b/bin/gconfig
deleted file mode 100755
index 2b1ee61..0000000
Binary files a/bin/gconfig and /dev/null differ
diff --git a/changelog.txt b/changelog.txt
index 2367a82..374e581 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -15,3 +15,31 @@ index de2eead..997a975 100644
          case LVL_ERROR: return "[ERROR]";
      }
      return "[     ]";
+diff --git a/bin/gbuild b/bin/gbuild
+deleted file mode 100755
+index dc9ad62..0000000
+Binary files a/bin/gbuild and /dev/null differ
+diff --git a/bin/gconfig b/bin/gconfig
+deleted file mode 100755
+index 2b1ee61..0000000
+Binary files a/bin/gconfig and /dev/null differ
+diff --git a/dist/gbuild-1.0.0-1.x86_64.rpm b/dist/gbuild-1.0.0-1.x86_64.rpm
+deleted file mode 100644
+index 2ac5ced..0000000
+Binary files a/dist/gbuild-1.0.0-1.x86_64.rpm and /dev/null differ
+diff --git a/dist/gbuild-1.0.0-x86_64-bin.tar.gz b/dist/gbuild-1.0.0-x86_64-bin.tar.gz
+deleted file mode 100644
+index 8e410d7..0000000
+Binary files a/dist/gbuild-1.0.0-x86_64-bin.tar.gz and /dev/null differ
+diff --git a/dist/gbuild-1.0.0.tar.gz b/dist/gbuild-1.0.0.tar.gz
+deleted file mode 100644
+index 4fea580..0000000
+Binary files a/dist/gbuild-1.0.0.tar.gz and /dev/null differ
+diff --git a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm b/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm
+deleted file mode 100644
+index c0bcf34..0000000
+Binary files a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm and /dev/null differ
+diff --git a/dist/gbuild_1.0.0_amd64.deb b/dist/gbuild_1.0.0_amd64.deb
+deleted file mode 100644
+index 58b5e08..0000000
+Binary files a/dist/gbuild_1.0.0_amd64.deb and /dev/null differ
diff --git a/dist/gbuild-1.0.0-1.x86_64.rpm b/dist/gbuild-1.0.0-1.x86_64.rpm
deleted file mode 100644
index 2ac5ced..0000000
Binary files a/dist/gbuild-1.0.0-1.x86_64.rpm and /dev/null differ
diff --git a/dist/gbuild-1.0.0-x86_64-bin.tar.gz b/dist/gbuild-1.0.0-x86_64-bin.tar.gz
deleted file mode 100644
index 8e410d7..0000000
Binary files a/dist/gbuild-1.0.0-x86_64-bin.tar.gz and /dev/null differ
diff --git a/dist/gbuild-1.0.0.tar.gz b/dist/gbuild-1.0.0.tar.gz
deleted file mode 100644
index 4fea580..0000000
Binary files a/dist/gbuild-1.0.0.tar.gz and /dev/null differ
diff --git a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm b/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm
deleted file mode 100644
index c0bcf34..0000000
Binary files a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm and /dev/null differ
diff --git a/dist/gbuild_1.0.0_amd64.deb b/dist/gbuild_1.0.0_amd64.deb
deleted file mode 100644
index 58b5e08..0000000
Binary files a/dist/gbuild_1.0.0_amd64.deb and /dev/null differ
diff --git a/src/tui.c b/src/tui.c
index b07ea65..a2465df 100644
--- a/src/tui.c
+++ b/src/tui.c
@@ -247,10 +247,10 @@ void tui_log_browser(const char *log_dir)
     if (has_colors()) {
         start_color();
         use_default_colors();
-        init_pair(1, COLOR_BLACK, COLOR_CYAN);   /* selected row  */
-        init_pair(2, COLOR_CYAN,  -1);           /* border        */
+        init_pair(1, COLOR_BLACK, COLOR_WHITE);   /* selected row  */
+        init_pair(2, COLOR_WHITE,  -1);           /* border        */
         init_pair(3, COLOR_WHITE, -1);           /* normal row    */
-        init_pair(4, COLOR_YELLOW,-1);           /* preview text  */
+        init_pair(4, COLOR_WHITE,-1);           /* preview text  */
         init_pair(5, COLOR_RED,   -1);           /* status/warn   */
     }
 
@@ -282,7 +282,7 @@ void tui_log_browser(const char *log_dir)
         /* ---- hint bar ---- */
         if (has_colors()) attron(COLOR_PAIR(2));
         mvprintw(rows - 2, 0,
-                 "  \u2191\u2193 navigate   Enter open in less   d delete   q quit   "
+                 "  J - DOWN   K - UP navigate   Enter - open in less   d - delete   q - quit   "
                  "                              ");
         if (has_colors()) attroff(COLOR_PAIR(2));
 
diff --git a/src/tui.c b/src/tui.c
index a2465df..103babc 100644
--- a/src/tui.c
+++ b/src/tui.c
@@ -282,7 +282,7 @@ void tui_log_browser(const char *log_dir)
         /* ---- hint bar ---- */
         if (has_colors()) attron(COLOR_PAIR(2));
         mvprintw(rows - 2, 0,
-                 "  J - DOWN   K - UP navigate   Enter - open in less   d - delete   q - quit   "
+                 "  K - UP  J - DOWN to navigate   Enter - open in less   d - delete   q - quit  (Press J to start) "
                  "                              ");
         if (has_colors()) attroff(COLOR_PAIR(2));
 
@@ -389,10 +389,10 @@ void tui_log_browser(const char *log_dir)
                     if (has_colors()) {
                         start_color();
                         use_default_colors();
-                        init_pair(1, COLOR_BLACK, COLOR_CYAN);
-                        init_pair(2, COLOR_CYAN,  -1);
+                        init_pair(1, COLOR_BLACK, COLOR_WHITE);
+                        init_pair(2, COLOR_WHITE,  -1);
                         init_pair(3, COLOR_WHITE, -1);
-                        init_pair(4, COLOR_YELLOW,-1);
+                        init_pair(4, COLOR_WHITE,-1);
                         init_pair(5, COLOR_RED,   -1);
                     }
                     clear();
diff --git a/bin/gbuild b/bin/gbuild
index 3585f82..09371a5 100755
Binary files a/bin/gbuild and b/bin/gbuild differ
diff --git a/dist/gbuild-1.0.0-1.x86_64.rpm b/dist/gbuild-1.0.0-1.x86_64.rpm
deleted file mode 100644
index 2343555..0000000
Binary files a/dist/gbuild-1.0.0-1.x86_64.rpm and /dev/null differ
diff --git a/dist/gbuild-1.0.0-x86_64-bin.tar.gz b/dist/gbuild-1.0.0-x86_64-bin.tar.gz
deleted file mode 100644
index cdbc281..0000000
Binary files a/dist/gbuild-1.0.0-x86_64-bin.tar.gz and /dev/null differ
diff --git a/dist/gbuild-1.0.0.tar.gz b/dist/gbuild-1.0.0.tar.gz
deleted file mode 100644
index c8fea5c..0000000
Binary files a/dist/gbuild-1.0.0.tar.gz and /dev/null differ
diff --git a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm b/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm
deleted file mode 100644
index 04797e8..0000000
Binary files a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm and /dev/null differ
diff --git a/dist/gbuild_1.0.0_amd64.deb b/dist/gbuild_1.0.0_amd64.deb
deleted file mode 100644
index a0b5475..0000000
Binary files a/dist/gbuild_1.0.0_amd64.deb and /dev/null differ
diff --git a/src/tui.c b/src/tui.c
index 103babc..1442086 100644
--- a/src/tui.c
+++ b/src/tui.c
@@ -189,7 +189,7 @@ static int load_log_entries(const char *log_dir,
  * Returns the number of lines read.
  * Caller owns the memory and should free each pointer.
  */
-#define PREVIEW_MAX 128
+#define PREVIEW_MAX 200
 
 static int read_tail(const char *path, char **lines, int max_lines)
 {
@@ -279,11 +279,6 @@ void tui_log_browser(const char *log_dir)
         attroff(A_BOLD);
         clrtoeol();
 
-        /* ---- hint bar ---- */
-        if (has_colors()) attron(COLOR_PAIR(2));
-        mvprintw(rows - 2, 0,
-                 "  K - UP  J - DOWN to navigate   Enter - open in less   d - delete   q - quit  (Press J to start) "
-                 "                              ");
         if (has_colors()) attroff(COLOR_PAIR(2));
 
         /* ---- vertical divider ---- */
@@ -351,7 +346,13 @@ void tui_log_browser(const char *log_dir)
             mvwprintw(wright, 2, 2, "(no log selected)");
         }
         wrefresh(wright);
-        refresh();
+
+        /* ---- hint bar ---- */
+        if (has_colors()) attron(COLOR_PAIR(2));
+        mvprintw(rows - 2, 0,
+                 "  K - UP  J - DOWN to navigate   Enter - open in less   d - delete   q - quit  (Press J to start) "
+                 "                              ");
+		refresh();
 
         /* ---- input ---- */
         int ch = getch();
diff --git a/bin/gbuild b/bin/gbuild
index 09371a5..e95c8a0 100755
Binary files a/bin/gbuild and b/bin/gbuild differ
diff --git a/dist/gbuild-1.0.0-1.x86_64.rpm b/dist/gbuild-1.0.0-1.x86_64.rpm
index 6df2d36..611a7ef 100644
Binary files a/dist/gbuild-1.0.0-1.x86_64.rpm and b/dist/gbuild-1.0.0-1.x86_64.rpm differ
diff --git a/dist/gbuild-1.0.0-x86_64-bin.tar.gz b/dist/gbuild-1.0.0-x86_64-bin.tar.gz
index 60a5227..a8e0d0e 100644
Binary files a/dist/gbuild-1.0.0-x86_64-bin.tar.gz and b/dist/gbuild-1.0.0-x86_64-bin.tar.gz differ
diff --git a/dist/gbuild-1.0.0.tar.gz b/dist/gbuild-1.0.0.tar.gz
index 3a83082..6010827 100644
Binary files a/dist/gbuild-1.0.0.tar.gz and b/dist/gbuild-1.0.0.tar.gz differ
diff --git a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm b/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm
index 813ee98..ba7fe6d 100644
Binary files a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm and b/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm differ
diff --git a/dist/gbuild_1.0.0_amd64.deb b/dist/gbuild_1.0.0_amd64.deb
index 6f52b7e..a365173 100644
Binary files a/dist/gbuild_1.0.0_amd64.deb and b/dist/gbuild_1.0.0_amd64.deb differ
diff --git a/src/tui.c b/src/tui.c
index b5df72f..c5e660a 100644
--- a/src/tui.c
+++ b/src/tui.c
@@ -61,7 +61,7 @@ int tui_pick_target(const MakeTargets *targets, char *selected, size_t sel_size)
     /* header hint */
     attron(A_DIM);
     mvprintw(win_y - 2, win_x,
-             "Select make target  K - UP  J - DOWN to navigate   Enter - select  \u00b7  q - cancel");
+             "Select make target  \u2191\u2193 navigate  \u00b7  Enter select  \u00b7  q cancel");
     attroff(A_DIM);
     refresh();
 
@@ -189,7 +189,7 @@ static int load_log_entries(const char *log_dir,
  * Returns the number of lines read.
  * Caller owns the memory and should free each pointer.
  */
-#define PREVIEW_MAX 200
+#define PREVIEW_MAX 128
 
 static int read_tail(const char *path, char **lines, int max_lines)
 {
@@ -247,24 +247,24 @@ void tui_log_browser(const char *log_dir)
     if (has_colors()) {
         start_color();
         use_default_colors();
-        init_pair(1, COLOR_BLACK, COLOR_WHITE);   /* selected row  */
-        init_pair(2, COLOR_WHITE,  -1);           /* border        */
-        init_pair(3, COLOR_WHITE, -1);           /* normal row    */
-        init_pair(4, COLOR_WHITE,-1);           /* preview text  */
-        init_pair(5, COLOR_RED,   -1);           /* status/warn   */
+        init_pair(1, COLOR_BLACK, COLOR_WHITE);
+        init_pair(2, COLOR_WHITE, -1);
+        init_pair(3, COLOR_WHITE, -1);
+        init_pair(4, COLOR_WHITE, -1);
+        init_pair(5, COLOR_RED,   -1);
     }
 
     int rows, cols;
     getmaxyx(stdscr, rows, cols);
 
-    /* layout: left pane = 40% width, right pane = rest */
     int left_w  = cols * 40 / 100;
     if (left_w < 30) left_w = 30;
-    int right_w = cols - left_w - 1;   /* -1 for divider */
-    int pane_h  = rows - 3;            /* -3 for title + hint bar */
+    int right_w = cols - left_w - 1;
+    int pane_h  = rows - 3;
 
     WINDOW *wleft  = newwin(pane_h, left_w,  1, 0);
     WINDOW *wright = newwin(pane_h, right_w, 1, left_w + 1);
+    keypad(wleft, TRUE);
 
     int cur = 0, scroll = 0;
     int done = 0;
@@ -279,6 +279,11 @@ void tui_log_browser(const char *log_dir)
         attroff(A_BOLD);
         clrtoeol();
 
+        /* ---- hint bar ---- */
+        if (has_colors()) attron(COLOR_PAIR(2));
+        mvprintw(rows - 2, 0,
+                 "  K - UP  J - DOWN to  navigate   Enter - open in less   d - delete   q - quit   "
+                 "                              ");
         if (has_colors()) attroff(COLOR_PAIR(2));
 
         /* ---- vertical divider ---- */
@@ -299,7 +304,6 @@ void tui_log_browser(const char *log_dir)
         } else {
             for (int i = 0; i < visible && (i + scroll) < count; i++) {
                 int idx = i + scroll;
-                /* trim the .log extension for display */
                 char disp[256];
                 strncpy(disp, entries[idx].name, sizeof(disp) - 1);
                 char *dot = strrchr(disp, '.');
@@ -318,7 +322,7 @@ void tui_log_browser(const char *log_dir)
                 }
             }
         }
-        wrefresh(wleft);
+        /* NOTE: no wrefresh(wleft) here — flush everything together below */
 
         /* ---- right pane: preview ---- */
         werase(wright);
@@ -345,17 +349,17 @@ void tui_log_browser(const char *log_dir)
         } else {
             mvwprintw(wright, 2, 2, "(no log selected)");
         }
-        wrefresh(wright);
 
-       	refresh();
-		 /* ---- hint bar ---- */
-        if (has_colors()) attron(COLOR_PAIR(2));
-        mvprintw(rows - 2, 0,
-                 "  K - UP  J - DOWN to navigate   Enter - open in less   d - delete   q - quit  (Press J to start) "
-                 "                              ");
-	
+        /* Flush all layers atomically: stdscr first (background), then the two
+         * panes on top. doupdate() does one physical write so nothing
+         * overwrites anything else. */
+        wnoutrefresh(stdscr);
+        wnoutrefresh(wleft);
+        wnoutrefresh(wright);
+        doupdate();
+
         /* ---- input ---- */
-        int ch = getch();
+        int ch = wgetch(wleft);
         switch (ch) {
             case KEY_UP:
             case 'k':
@@ -381,7 +385,6 @@ void tui_log_browser(const char *log_dir)
                     snprintf(cmd, sizeof(cmd),
                              "less \"%s\"", entries[cur].path);
                     system(cmd);
-                    /* re-init after less exits */
                     initscr();
                     noecho();
                     cbreak();
@@ -391,24 +394,24 @@ void tui_log_browser(const char *log_dir)
                         start_color();
                         use_default_colors();
                         init_pair(1, COLOR_BLACK, COLOR_WHITE);
-                        init_pair(2, COLOR_WHITE,  -1);
+                        init_pair(2, COLOR_WHITE, -1);
                         init_pair(3, COLOR_WHITE, -1);
-                        init_pair(4, COLOR_WHITE,-1);
+                        init_pair(4, COLOR_WHITE, -1);
                         init_pair(5, COLOR_RED,   -1);
                     }
+                    touchwin(wleft);
+                    touchwin(wright);
                     clear();
                 }
                 break;
             case 'd':
                 if (count > 0 && cur < count) {
-                    /* confirm */
                     mvprintw(rows - 1, 0,
                              "Delete %s? [y/N] ", entries[cur].name);
                     refresh();
                     int confirm = getch();
                     if (confirm == 'y' || confirm == 'Y') {
                         remove(entries[cur].path);
-                        /* reload list */
                         count = load_log_entries(log_dir, entries, MAX_LOGS);
                         if (cur >= count) cur = count > 0 ? count - 1 : 0;
                         if (scroll > cur) scroll = cur;
@@ -417,7 +420,7 @@ void tui_log_browser(const char *log_dir)
                 }
                 break;
             case 'q':
-            case 27: /* ESC */
+            case 27:
                 done = 1;
                 break;
         }
diff --git a/Makefile b/Makefile
index 2c21f21..b2e3f5c 100644
--- a/Makefile
+++ b/Makefile
@@ -30,6 +30,7 @@ COMMON_SRCS = src/config.c
 GBUILD_SRCS = \
     src/gbuild.c   \
     src/git_ops.c  \
+    src/index.c    \
     src/logger.c   \
     src/make_ops.c \
     src/tui.c      \
diff --git a/README.md b/README.md
index 0a8ef78..13e5301 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# gbuild 
+# gbuild — C rewrite
 
 A C rewrite of the original [gbuild](https://spdlab.hu/gbuild) bash tool.
 Clone, pull, pick a target, build — all from one command.
@@ -18,7 +18,7 @@ Clone, pull, pick a target, build — all from one command.
 
 ## Dependencies
 
-| Library  | Purpose                      | 
+| Library  | Purpose                      |
 |----------|------------------------------|
 | ncurses  | TUI for picker & log browser |
 | git      | Clone / pull                 |
@@ -29,14 +29,9 @@ Clone, pull, pick a target, build — all from one command.
 
 ## Build
 
-### Linux
-
 ```sh
-# Install these packages with your package manager
-libncurses-dev make
-
-# Clone this repository
-git clone https://github.com/jokerz/gbuild.git
+# Install ncurses dev headers if needed
+sudo apt install libncurses-dev
 
 # Build both binaries into bin/
 make
@@ -70,7 +65,7 @@ gbuild myproject
 
 [git]
 GIT_URL  = http://localhost:3000
-GIT_USER = Username
+GIT_USER = 
 
 [auth]
 # Use token-based OR password-based auth; leave the other blank
diff --git a/include/git_ops.h b/include/git_ops.h
index bf22f63..bd5ae79 100644
--- a/include/git_ops.h
+++ b/include/git_ops.h
@@ -16,3 +16,7 @@ int git_clone(const char *base_url, const char *user,
 int git_pull(const char *repo_path, Logger *log);
 
 #endif /* GIT_OPS_H */
+
+/* Read the current HEAD hash of repo_path into out (at least 41 bytes).
+ * Returns 0 on success, -1 on failure. */
+int git_head_hash(const char *repo_path, char *out, size_t n);
diff --git a/include/tui.h b/include/tui.h
index a2c035f..ca6cd1d 100644
--- a/include/tui.h
+++ b/include/tui.h
@@ -13,4 +13,17 @@ int  tui_pick_target(const MakeTargets *targets, char *selected, size_t sel_size
  * Keys: ↑↓ navigate  Enter open in less  d delete  q quit */
 void tui_log_browser(const char *log_dir);
 
+#include "index.h"
+
+/* Full-screen project overview.
+ * Lists all projects in idx with last-build status, timestamp and HEAD hash.
+ * On Enter, fills selected_project and returns the row index.
+ * Returns -1 if the user quit without selecting.
+ * Keys: ↑↓ navigate  Enter build  l log-browser  q/ESC quit */
+int tui_project_overview(ProjectIndex *idx,
+                         const char *clone_dir,
+                         const char *log_dir,
+                         char *selected_project,
+                         size_t sel_size);
+
 #endif /* TUI_H */
diff --git a/src/config.c b/src/config.c
index a89ba18..46b8b5f 100644
--- a/src/config.c
+++ b/src/config.c
@@ -1,12 +1,3 @@
-/*
-
-This file is licensed under the MIT license.
-
-Copyright (c) Schmidt Peter Daniel 2026
-
-
-*/
-
 #include "config.h"
 
 #include <stdio.h>
diff --git a/src/gbuild.c b/src/gbuild.c
index 9bc8b8a..ff8514e 100644
--- a/src/gbuild.c
+++ b/src/gbuild.c
@@ -1,5 +1,6 @@
 #include "config.h"
 #include "git_ops.h"
+#include "index.h"
 #include "logger.h"
 #include "make_ops.h"
 #include "tui.h"
@@ -20,20 +21,126 @@ static void print_usage(const char *prog)
         "gbuild %s — A build tool for your Linux distro\n\n"
         "Usage:\n"
         "  %s [options] <project>\n"
+        "  %s                     (no args — opens project overview TUI)\n"
         "  %s --logs\n\n"
         "Options:\n"
-        "  --url   <url>     Override Git base URL  (default: ~/.gconfig)\n"
-        "  --user  <name>    Override Git username   (default: ~/.gconfig)\n"
-        "  --target <tgt>   Run target directly, skip interactive picker\n"
+        "  --url    <url>    Override Git base URL  (default: ~/.gconfig)\n"
+        "  --user   <name>   Override Git username   (default: ~/.gconfig)\n"
+        "  --target <tgt>    Run target directly, skip interactive picker\n"
         "  --logs            Open the interactive log browser\n"
+        "  --no-tui          With no <project>, print usage instead of opening TUI\n"
         "  -h, --help        Print this help and exit\n\n"
         "Examples:\n"
+        "  gbuild                         (overview TUI — pick and build)\n"
         "  gbuild myproject\n"
         "  gbuild --target clean myproject\n"
         "  gbuild --url http://10.0.0.5:3000 --user alice myproject\n"
         "  gbuild --logs\n\n"
         "Configuration is read from ~/.gconfig. Run 'gconfig init' to set it up.\n",
-        VERSION, prog, prog);
+        VERSION, prog, prog, prog);
+}
+
+/* ----------------------------------------------------------------- build one project */
+
+static int build_project(const char *project,
+                          const char *arg_target,
+                          GConfig    *cfg,
+                          const char *clone_dir,
+                          const char *log_dir,
+                          ProjectIndex *idx)
+{
+    /* ---- open logger ---- */
+    Logger log;
+    memset(&log, 0, sizeof(log));
+    if (cfg->log_enabled) {
+        if (logger_open(&log, log_dir, project) == 0)
+            logger_info(&log, "Logging to: %s", log.path);
+        else
+            fprintf(stderr, "[WARN ] Could not open log file in %s\n", log_dir);
+    }
+
+    logger_info(&log, "gbuild started for project: %s", project);
+
+    /* ---- clone or pull ---- */
+    char repo_path[CFG_MAX * 2];
+    snprintf(repo_path, sizeof(repo_path), "%s/%s", clone_dir, project);
+
+    if (git_repo_exists(clone_dir, project)) {
+        logger_info(&log, "Repo already cloned — pulling latest changes ...");
+        int rc = git_pull(repo_path, &log);
+        if (rc != 0) {
+            logger_error(&log, "git pull failed (exit %d).", rc);
+            logger_close(&log);
+            return 1;
+        }
+        logger_ok(&log, "Repository updated.");
+    } else {
+        logger_info(&log, "Cloning %s/%s/%s ...",
+                    cfg->git_url, cfg->git_user, project);
+        int rc = git_clone(cfg->git_url,   cfg->git_user,
+                           cfg->git_token,  cfg->git_password,
+                           project,         clone_dir, &log);
+        if (rc != 0) {
+            logger_error(&log, "git clone failed (exit %d).", rc);
+            logger_close(&log);
+            return 1;
+        }
+        logger_ok(&log, "Repository cloned.");
+    }
+
+    /* ---- record HEAD hash ---- */
+    ProjectRecord *rec = index_upsert(idx, project);
+    if (rec)
+        git_head_hash(repo_path, rec->last_head_hash, IDX_HASH_LEN);
+
+    /* ---- resolve make target ---- */
+    char target[TARGET_NAME];
+    memset(target, 0, sizeof(target));
+
+    if (arg_target) {
+        strncpy(target, arg_target, sizeof(target) - 1);
+        logger_info(&log, "Selected target: %s", target);
+    } else if (cfg->default_target[0]) {
+        strncpy(target, cfg->default_target, sizeof(target) - 1);
+        logger_info(&log, "Using default target: %s", target);
+    } else {
+        MakeTargets targets;
+        if (make_parse_targets(repo_path, &targets) != 0) {
+            logger_warn(&log,
+                "No targets found in Makefile — running 'make' with no target.");
+            target[0] = '\0';
+        } else {
+            if (tui_pick_target(&targets, target, sizeof(target)) < 0) {
+                logger_warn(&log, "No target selected — aborting.");
+                logger_close(&log);
+                return 0;
+            }
+        }
+        logger_info(&log, "Selected target: %s",
+                    target[0] ? target : "(default)");
+    }
+
+    /* ---- build ---- */
+    logger_info(&log, "Building '%s' target '%s' ...",
+                project, target[0] ? target : "(default)");
+
+    int rc = make_build(repo_path, target, &log);
+
+    /* ---- update index ---- */
+    if (rec) {
+        rec->last_build_rc = rc;
+        rec->last_build_ts = time(NULL);
+    }
+
+    if (rc != 0) {
+        logger_error(&log, "Build failed (exit %d).", rc);
+        logger_close(&log);
+        return 1;
+    }
+
+    logger_ok(&log, "gbuild complete.");
+    logger_close(&log);
+    return 0;
 }
 
 /* ----------------------------------------------------------------- main */
@@ -46,6 +153,7 @@ int main(int argc, char *argv[])
     const char *arg_target  = NULL;
     const char *arg_project = NULL;
     int         flag_logs   = 0;
+    int         flag_no_tui = 0;
 
     for (int i = 1; i < argc; i++) {
         if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
@@ -62,6 +170,8 @@ int main(int argc, char *argv[])
             arg_target = argv[i];
         } else if (!strcmp(argv[i], "--logs")) {
             flag_logs = 1;
+        } else if (!strcmp(argv[i], "--no-tui")) {
+            flag_no_tui = 1;
         } else if (argv[i][0] == '-') {
             fprintf(stderr, "error: unknown flag '%s'\n", argv[i]);
             return 1;
@@ -86,109 +196,54 @@ int main(int argc, char *argv[])
         config_defaults(&cfg);
     }
 
-    /* apply overrides */
     if (arg_url)  strncpy(cfg.git_url,  arg_url,  CFG_MAX - 1);
     if (arg_user) strncpy(cfg.git_user, arg_user, sizeof(cfg.git_user) - 1);
 
-    /* expand paths that might contain ~ */
     char clone_dir[CFG_MAX], log_dir[CFG_MAX];
     expand_tilde(cfg.clone_dir, clone_dir, sizeof(clone_dir));
     expand_tilde(cfg.log_dir,   log_dir,   sizeof(log_dir));
 
+    /* ---- load / scan project index ---- */
+    ProjectIndex idx;
+    char idx_path[512];
+    index_get_path(idx_path, sizeof(idx_path));
+    index_load(&idx, idx_path);        /* ok if file doesn't exist yet */
+    index_scan(&idx, clone_dir);       /* pick up any new repos on disk */
+
     /* ---- log browser mode ---- */
     if (flag_logs) {
         tui_log_browser(log_dir);
         return 0;
     }
 
-    /* ---- need a project name ---- */
+    /* ---- no project given: open overview TUI or print usage ---- */
     if (!arg_project) {
-        fprintf(stderr, "error: no project specified.\n\n");
-        print_usage(argv[0]);
-        return 1;
-    }
-
-    /* ---- open logger ---- */
-    Logger log;
-    memset(&log, 0, sizeof(log));
-    if (cfg.log_enabled) {
-        if (logger_open(&log, log_dir, arg_project) == 0)
-            logger_info(&log, "Logging to: %s", log.path);
-        else
-            fprintf(stderr, "[WARN ] Could not open log file in %s\n", log_dir);
-    }
-
-    logger_info(&log, "gbuild started for project: %s", arg_project);
-
-    /* ---- clone or pull ---- */
-    char repo_path[CFG_MAX * 2];
-    snprintf(repo_path, sizeof(repo_path), "%s/%s", clone_dir, arg_project);
-
-    if (git_repo_exists(clone_dir, arg_project)) {
-        logger_info(&log, "Repo already cloned — pulling latest changes ...");
-        int rc = git_pull(repo_path, &log);
-        if (rc != 0) {
-            logger_error(&log, "git pull failed (exit %d).", rc);
-            logger_close(&log);
-            return 1;
+        if (flag_no_tui || idx.count == 0) {
+            if (idx.count == 0 && !flag_no_tui)
+                fprintf(stderr,
+                        "No projects found in %s.\n"
+                        "Run 'gbuild <project>' to clone and build one first.\n",
+                        clone_dir);
+            else
+                print_usage(argv[0]);
+            return (idx.count == 0) ? 1 : 0;
         }
-        logger_ok(&log, "Repository updated.");
-    } else {
-        logger_info(&log, "Cloning %s/%s/%s ...",
-                    cfg.git_url, cfg.git_user, arg_project);
-        int rc = git_clone(cfg.git_url,  cfg.git_user,
-                           cfg.git_token, cfg.git_password,
-                           arg_project,  clone_dir, &log);
-        if (rc != 0) {
-            logger_error(&log, "git clone failed (exit %d).", rc);
-            logger_close(&log);
-            return 1;
-        }
-        logger_ok(&log, "Repository cloned.");
-    }
 
-    /* ---- resolve make target ---- */
-    char target[TARGET_NAME];
-    memset(target, 0, sizeof(target));
+        static char chosen[IDX_NAME_LEN];
+        memset(chosen, 0, sizeof(chosen));
+        if (tui_project_overview(&idx, clone_dir, log_dir,
+                                 chosen, sizeof(chosen)) < 0)
+            return 0;   /* user quit without picking */
 
-    if (arg_target) {
-        /* explicit --target flag */
-        strncpy(target, arg_target, sizeof(target) - 1);
-        logger_info(&log, "Selected target: %s", target);
-    } else if (cfg.default_target[0]) {
-        /* default from config */
-        strncpy(target, cfg.default_target, sizeof(target) - 1);
-        logger_info(&log, "Using default target: %s", target);
-    } else {
-        /* interactive TUI picker */
-        MakeTargets targets;
-        if (make_parse_targets(repo_path, &targets) != 0) {
-            logger_warn(&log,
-                "No targets found in Makefile — running 'make' with no target.");
-            target[0] = '\0';
-        } else {
-            if (tui_pick_target(&targets, target, sizeof(target)) < 0) {
-                logger_warn(&log, "No target selected — aborting.");
-                logger_close(&log);
-                return 0;
-            }
-        }
-        logger_info(&log, "Selected target: %s",
-                    target[0] ? target : "(default)");
+        arg_project = chosen;
     }
 
     /* ---- build ---- */
-    logger_info(&log, "gbuild: building '%s' target '%s' ...",
-                arg_project, target[0] ? target : "(default)");
+    int rc = build_project(arg_project, arg_target,
+                           &cfg, clone_dir, log_dir, &idx);
 
-    int rc = make_build(repo_path, target, &log);
-    if (rc != 0) {
-        logger_error(&log, "Build failed (exit %d).", rc);
-        logger_close(&log);
-        return 1;
-    }
+    /* ---- persist updated index ---- */
+    index_save(&idx, idx_path);
 
-    logger_ok(&log, "gbuild complete.");
-    logger_close(&log);
-    return 0;
+    return rc;
 }
diff --git a/src/git_ops.c b/src/git_ops.c
index 69588eb..3f995f1 100644
--- a/src/git_ops.c
+++ b/src/git_ops.c
@@ -1,13 +1,6 @@
-/*
-
-This file is licensed under the MIT license.
-Copyright (c) Schmidt Peter Daniel 2026.
-
-
-*/
-
 #include "git_ops.h"
 #include "config.h"
+#include "index.h"
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -131,3 +124,27 @@ int git_pull(const char *repo_path, Logger *log)
              "git -C \"%s\" pull 2>&1", repo_path);
     return run_stream(cmd, log);
 }
+
+int git_head_hash(const char *repo_path, char *out, size_t n)
+{
+    char cmd[IDX_PATH_LEN + 64];
+    snprintf(cmd, sizeof(cmd),
+             "git -C \"%s\" rev-parse HEAD 2>/dev/null", repo_path);
+
+    FILE *p = popen(cmd, "r");
+    if (!p) return -1;
+
+    char buf[64] = {0};
+    int ok = (fgets(buf, sizeof(buf), p) != NULL);
+    pclose(p);
+
+    if (!ok || buf[0] == '\0') return -1;
+
+    /* strip newline */
+    size_t l = strlen(buf);
+    if (l > 0 && buf[l-1] == '\n') buf[--l] = '\0';
+
+    strncpy(out, buf, n - 1);
+    out[n - 1] = '\0';
+    return 0;
+}
diff --git a/src/logger.c b/src/logger.c
index 997a975..de2eead 100644
--- a/src/logger.c
+++ b/src/logger.c
@@ -71,9 +71,9 @@ typedef enum { LVL_INFO, LVL_OK, LVL_WARN, LVL_ERROR } LogLevel;
 static const char *level_tag(LogLevel lv)
 {
     switch (lv) {
-        case LVL_INFO:  return "[INFO]";
-        case LVL_OK:    return "[OK]";
-        case LVL_WARN:  return "[WARN]";
+        case LVL_INFO:  return "[INFO ]";
+        case LVL_OK:    return "[OK   ]";
+        case LVL_WARN:  return "[WARN ]";
         case LVL_ERROR: return "[ERROR]";
     }
     return "[     ]";
diff --git a/src/tui.c b/src/tui.c
index c5e660a..605fba6 100644
--- a/src/tui.c
+++ b/src/tui.c
@@ -10,7 +10,7 @@
 #include <errno.h>
 
 /* ================================================================
- *  SECTION 1 — TARGET PICKER
+ *  SECTION 1 - TARGET PICKER
  * ================================================================ */
 
 #define PICKER_MIN_W  40
@@ -29,18 +29,15 @@ int tui_pick_target(const MakeTargets *targets, char *selected, size_t sel_size)
     if (has_colors()) {
         start_color();
         use_default_colors();
-        init_pair(1, COLOR_BLACK, COLOR_WHITE);   /* selected row   */
-        init_pair(2, COLOR_WHITE,  -1);            /* border colour  */
-        init_pair(3, COLOR_WHITE, -1);            /* normal row     */
+        init_pair(1, COLOR_BLACK, COLOR_WHITE);  /* selected row */
     }
 
     int rows, cols;
     getmaxyx(stdscr, rows, cols);
 
-    int list_h = targets->count + 2;            /* +2 for border  */
+    int list_h = targets->count + 2;
     int list_w = PICKER_MIN_W;
 
-    /* find longest target name and widen accordingly */
     for (int i = 0; i < targets->count; i++) {
         int l = (int)strlen(targets->names[i]) + 4;
         if (l > list_w) list_w = l;
@@ -51,17 +48,16 @@ int tui_pick_target(const MakeTargets *targets, char *selected, size_t sel_size)
     int win_y = (rows - list_h) / 2;
     int win_x = (cols - list_w) / 2;
 
-    /* instruction line above the box */
     int cur = 0;
     int scroll_offset = 0;
-    int visible = list_h - 2;           /* rows inside border */
+    int visible = list_h - 2;
 
     WINDOW *win = newwin(list_h, list_w, win_y, win_x);
+    keypad(win, TRUE);
 
-    /* header hint */
     attron(A_DIM);
     mvprintw(win_y - 2, win_x,
-             "Select make target  \u2191\u2193 navigate  \u00b7  Enter select  \u00b7  q cancel");
+             "Select make target  J/K navigate  |  Enter select  |  q cancel");
     attroff(A_DIM);
     refresh();
 
@@ -70,9 +66,7 @@ int tui_pick_target(const MakeTargets *targets, char *selected, size_t sel_size)
 
     while (!done) {
         werase(win);
-        if (has_colors()) wattron(win, COLOR_PAIR(2));
         box(win, 0, 0);
-        if (has_colors()) wattroff(win, COLOR_PAIR(2));
 
         for (int i = 0; i < visible && (i + scroll_offset) < targets->count; i++) {
             int idx = i + scroll_offset;
@@ -84,10 +78,8 @@ int tui_pick_target(const MakeTargets *targets, char *selected, size_t sel_size)
                 if (has_colors()) wattroff(win, COLOR_PAIR(1) | A_BOLD);
                 else               wattroff(win, A_REVERSE);
             } else {
-                if (has_colors()) wattron(win, COLOR_PAIR(3));
                 mvwprintw(win, i + 1, 1, "  %-*s",
                           list_w - 4, targets->names[idx]);
-                if (has_colors()) wattroff(win, COLOR_PAIR(3));
             }
         }
 
@@ -119,7 +111,7 @@ int tui_pick_target(const MakeTargets *targets, char *selected, size_t sel_size)
                 done = 1;
                 break;
             case 'q':
-            case 27: /* ESC */
+            case 27:
                 done = 1;
                 break;
         }
@@ -131,20 +123,19 @@ int tui_pick_target(const MakeTargets *targets, char *selected, size_t sel_size)
 }
 
 /* ================================================================
- *  SECTION 2 — LOG BROWSER
+ *  SECTION 2 - LOG BROWSER
  * ================================================================ */
 
 #define MAX_LOGS 512
 
 typedef struct {
-    char name[256];     /* filename only */
-    char path[768];     /* full path     */
+    char   name[256];
+    char   path[768];
     time_t mtime;
 } LogEntry;
 
 static int log_entry_cmp(const void *a, const void *b)
 {
-    /* newest first */
     const LogEntry *la = (const LogEntry *)a;
     const LogEntry *lb = (const LogEntry *)b;
     if (lb->mtime > la->mtime) return  1;
@@ -162,7 +153,6 @@ static int load_log_entries(const char *log_dir,
     struct dirent *de;
     while ((de = readdir(d)) && count < max) {
         if (de->d_name[0] == '.') continue;
-        /* only .log files */
         const char *dot = strrchr(de->d_name, '.');
         if (!dot || strcmp(dot, ".log")) continue;
 
@@ -184,11 +174,6 @@ static int load_log_entries(const char *log_dir,
     return count;
 }
 
-/*
- * Read up to `max_lines` trailing lines from `path` into `lines`.
- * Returns the number of lines read.
- * Caller owns the memory and should free each pointer.
- */
 #define PREVIEW_MAX 128
 
 static int read_tail(const char *path, char **lines, int max_lines)
@@ -196,17 +181,14 @@ static int read_tail(const char *path, char **lines, int max_lines)
     FILE *f = fopen(path, "r");
     if (!f) return 0;
 
-    /* Circular buffer approach */
     char **buf = calloc(max_lines, sizeof(char *));
     if (!buf) { fclose(f); return 0; }
 
     char tmp[1024];
     int idx = 0, total = 0;
     while (fgets(tmp, sizeof(tmp), f)) {
-        /* strip newline */
         size_t l = strlen(tmp);
         if (l > 0 && tmp[l-1] == '\n') tmp[l-1] = '\0';
-
         free(buf[idx % max_lines]);
         buf[idx % max_lines] = strdup(tmp);
         idx++;
@@ -214,13 +196,12 @@ static int read_tail(const char *path, char **lines, int max_lines)
     }
     fclose(f);
 
-    int have = (total < max_lines) ? total : max_lines;
+    int have  = (total < max_lines) ? total : max_lines;
     int start = (total >= max_lines) ? (idx % max_lines) : 0;
 
     for (int i = 0; i < have; i++)
         lines[i] = buf[(start + i) % max_lines];
 
-    /* free slots that weren't returned */
     for (int i = have; i < max_lines; i++) {
         free(buf[(start + i) % max_lines]);
         buf[(start + i) % max_lines] = NULL;
@@ -247,11 +228,7 @@ void tui_log_browser(const char *log_dir)
     if (has_colors()) {
         start_color();
         use_default_colors();
-        init_pair(1, COLOR_BLACK, COLOR_WHITE);
-        init_pair(2, COLOR_WHITE, -1);
-        init_pair(3, COLOR_WHITE, -1);
-        init_pair(4, COLOR_WHITE, -1);
-        init_pair(5, COLOR_RED,   -1);
+        init_pair(1, COLOR_BLACK, COLOR_WHITE);  /* selected row */
     }
 
     int rows, cols;
@@ -280,11 +257,11 @@ void tui_log_browser(const char *log_dir)
         clrtoeol();
 
         /* ---- hint bar ---- */
-        if (has_colors()) attron(COLOR_PAIR(2));
+        attron(A_REVERSE);
         mvprintw(rows - 2, 0,
-                 "  K - UP  J - DOWN to  navigate   Enter - open in less   d - delete   q - quit   "
+                 "  J/K navigate   Enter open in less   d delete   q quit   "
                  "                              ");
-        if (has_colors()) attroff(COLOR_PAIR(2));
+        attroff(A_REVERSE);
 
         /* ---- vertical divider ---- */
         for (int y = 1; y < rows - 2; y++)
@@ -292,9 +269,7 @@ void tui_log_browser(const char *log_dir)
 
         /* ---- left pane: file list ---- */
         werase(wleft);
-        if (has_colors()) wattron(wleft, COLOR_PAIR(2));
         box(wleft, 0, 0);
-        if (has_colors()) wattroff(wleft, COLOR_PAIR(2));
 
         int visible = pane_h - 2;
         if (visible < 1) visible = 1;
@@ -322,20 +297,17 @@ void tui_log_browser(const char *log_dir)
                 }
             }
         }
-        /* NOTE: no wrefresh(wleft) here — flush everything together below */
+        /* NOTE: no wrefresh(wleft) here - flush everything together below */
 
         /* ---- right pane: preview ---- */
         werase(wright);
-        if (has_colors()) wattron(wright, COLOR_PAIR(2));
         box(wright, 0, 0);
-        if (has_colors()) wattroff(wright, COLOR_PAIR(2));
 
         if (count > 0 && cur < count) {
             int preview_lines = pane_h - 2;
             char **lines = calloc(preview_lines, sizeof(char *));
             if (lines) {
                 int n = read_tail(entries[cur].path, lines, preview_lines);
-                if (has_colors()) wattron(wright, COLOR_PAIR(4));
                 for (int i = 0; i < n; i++) {
                     if (lines[i]) {
                         mvwprintw(wright, i + 1, 1, "%-*.*s",
@@ -343,16 +315,13 @@ void tui_log_browser(const char *log_dir)
                         free(lines[i]);
                     }
                 }
-                if (has_colors()) wattroff(wright, COLOR_PAIR(4));
                 free(lines);
             }
         } else {
             mvwprintw(wright, 2, 2, "(no log selected)");
         }
 
-        /* Flush all layers atomically: stdscr first (background), then the two
-         * panes on top. doupdate() does one physical write so nothing
-         * overwrites anything else. */
+        /* Atomic flush - stdscr first, then both panes on top */
         wnoutrefresh(stdscr);
         wnoutrefresh(wleft);
         wnoutrefresh(wright);
@@ -394,10 +363,6 @@ void tui_log_browser(const char *log_dir)
                         start_color();
                         use_default_colors();
                         init_pair(1, COLOR_BLACK, COLOR_WHITE);
-                        init_pair(2, COLOR_WHITE, -1);
-                        init_pair(3, COLOR_WHITE, -1);
-                        init_pair(4, COLOR_WHITE, -1);
-                        init_pair(5, COLOR_RED,   -1);
                     }
                     touchwin(wleft);
                     touchwin(wright);
@@ -431,3 +396,202 @@ void tui_log_browser(const char *log_dir)
     endwin();
     free(entries);
 }
+
+/* ================================================================
+ *  SECTION 3 - PROJECT OVERVIEW
+ * ================================================================ */
+
+/*
+ * Column layout:
+ *   ST  PROJECT NAME      LAST BUILD       RESULT    HEAD HASH
+ *
+ * Status symbols:
+ *   *  last build passed
+ *   X  last build failed
+ *   -  never built
+ *
+ * Keys:
+ *   J/K navigate   Enter build   l logs   q/ESC quit
+ */
+
+#include "index.h"
+#include "git_ops.h"
+
+#define COL_STATUS_W   3
+#define COL_NAME_W     24
+#define COL_TIME_W     18
+#define COL_RC_W       8
+#define COL_HASH_W     10
+
+static void fmt_ts(time_t ts, char *out, size_t n)
+{
+    if (ts == 0) {
+        strncpy(out, "(never)", n - 1);
+        out[n-1] = '\0';
+        return;
+    }
+    struct tm *t = localtime(&ts);
+    strftime(out, n, "%d %b %H:%M", t);
+}
+
+int tui_project_overview(ProjectIndex *idx,
+                         const char *clone_dir,
+                         const char *log_dir,
+                         char *selected_project,
+                         size_t sel_size)
+{
+    (void)clone_dir;
+
+    if (!idx || idx->count == 0) return -1;
+
+    initscr();
+    noecho();
+    cbreak();
+    keypad(stdscr, TRUE);
+    curs_set(0);
+
+    if (has_colors()) {
+        start_color();
+        use_default_colors();
+        init_pair(1, COLOR_BLACK, COLOR_WHITE);  /* selected row */
+    }
+
+    int rows, cols;
+    getmaxyx(stdscr, rows, cols);
+
+    int cur = 0, scroll = 0;
+    int done = 0;
+    int result = -1;
+
+    while (!done) {
+        getmaxyx(stdscr, rows, cols);
+        int visible = rows - 5;
+        if (visible < 1) visible = 1;
+
+        erase();
+
+        /* ---- title bar ---- */
+        attron(A_BOLD);
+        mvprintw(0, 2, "gbuild 1.0.0 - project overview");
+        attroff(A_BOLD);
+
+        /* ---- column header ---- */
+        attron(A_BOLD);
+        mvprintw(2, 2, "  %-*s  %-*s  %-*s  %-*s",
+                 COL_NAME_W, "PROJECT",
+                 COL_TIME_W, "LAST BUILD",
+                 COL_RC_W,   "RESULT",
+                 COL_HASH_W, "HEAD");
+        attroff(A_BOLD);
+
+        /* separator */
+        mvhline(3, 0, ACS_HLINE, cols);
+
+        /* ---- project rows ---- */
+        for (int i = 0; i < visible && (i + scroll) < idx->count; i++) {
+            int idx_i = i + scroll;
+            ProjectRecord *r = &idx->projects[idx_i];
+
+            char ts_str[32];
+            fmt_ts(r->last_build_ts, ts_str, sizeof(ts_str));
+
+            char shorthash[12] = "--------";
+            if (r->last_head_hash[0])
+                strncpy(shorthash, r->last_head_hash, 8);
+            shorthash[8] = '\0';
+
+            char rc_str[16];
+            if (r->last_build_rc == -1)     strncpy(rc_str, "-",    sizeof(rc_str) - 1);
+            else if (r->last_build_rc == 0) strncpy(rc_str, "PASS", sizeof(rc_str) - 1);
+            else                            snprintf(rc_str, sizeof(rc_str), "FAIL(%d)", r->last_build_rc);
+
+            const char *sym = (r->last_build_rc == 0)  ? "*" :
+                              (r->last_build_rc  > 0)  ? "X" : "-";
+
+            int y = 4 + i;
+
+            if (idx_i == cur) {
+                /* fill the whole row first */
+                if (has_colors()) attron(COLOR_PAIR(1) | A_BOLD);
+                else               attron(A_REVERSE);
+                mvprintw(y, 0, "%*s", cols, "");
+                mvprintw(y, 2, "%s %-*s  %-*s  %-*s  %-*s",
+                         sym,
+                         COL_NAME_W, r->name,
+                         COL_TIME_W, ts_str,
+                         COL_RC_W,   rc_str,
+                         COL_HASH_W, shorthash);
+                if (has_colors()) attroff(COLOR_PAIR(1) | A_BOLD);
+                else               attroff(A_REVERSE);
+            } else {
+                mvprintw(y, 2, "%s %-*s  %-*s  %-*s  %-*s",
+                         sym,
+                         COL_NAME_W, r->name,
+                         COL_TIME_W, ts_str,
+                         COL_RC_W,   rc_str,
+                         COL_HASH_W, shorthash);
+            }
+        }
+
+        /* ---- hint bar ---- */
+        attron(A_REVERSE);
+        mvprintw(rows - 1, 0,
+                 "  J/K navigate   Enter build   l logs   q quit"
+                 "                                        ");
+        attroff(A_REVERSE);
+
+        wnoutrefresh(stdscr);
+        doupdate();
+
+        /* ---- input ---- */
+        int ch = getch();
+        switch (ch) {
+            case KEY_UP:
+            case 'k':
+                if (cur > 0) {
+                    cur--;
+                    if (cur < scroll) scroll = cur;
+                }
+                break;
+            case KEY_DOWN:
+            case 'j':
+                if (cur < idx->count - 1) {
+                    cur++;
+                    if (cur >= scroll + visible)
+                        scroll = cur - visible + 1;
+                }
+                break;
+            case '\n':
+            case '\r':
+            case KEY_ENTER:
+                strncpy(selected_project,
+                        idx->projects[cur].name, sel_size - 1);
+                selected_project[sel_size - 1] = '\0';
+                result = cur;
+                done = 1;
+                break;
+            case 'l':
+                endwin();
+                tui_log_browser(log_dir);
+                initscr();
+                noecho();
+                cbreak();
+                keypad(stdscr, TRUE);
+                curs_set(0);
+                if (has_colors()) {
+                    start_color();
+                    use_default_colors();
+                    init_pair(1, COLOR_BLACK, COLOR_WHITE);
+                }
+                clear();
+                break;
+            case 'q':
+            case 27:
+                done = 1;
+                break;
+        }
+    }
+
+    endwin();
+    return result;
+}
diff --git a/bin/gbuild b/bin/gbuild
deleted file mode 100755
index 23d9fe9..0000000
Binary files a/bin/gbuild and /dev/null differ
diff --git a/bin/gconfig b/bin/gconfig
deleted file mode 100755
index 2b1ee61..0000000
Binary files a/bin/gconfig and /dev/null differ
diff --git a/dist/gbuild-1.0.0-1.x86_64.rpm b/dist/gbuild-1.0.0-1.x86_64.rpm
deleted file mode 100644
index f0d9432..0000000
Binary files a/dist/gbuild-1.0.0-1.x86_64.rpm and /dev/null differ
diff --git a/dist/gbuild-1.0.0-x86_64-bin.tar.gz b/dist/gbuild-1.0.0-x86_64-bin.tar.gz
deleted file mode 100644
index 40bbc22..0000000
Binary files a/dist/gbuild-1.0.0-x86_64-bin.tar.gz and /dev/null differ
diff --git a/dist/gbuild-1.0.0.tar.gz b/dist/gbuild-1.0.0.tar.gz
deleted file mode 100644
index 3cc865a..0000000
Binary files a/dist/gbuild-1.0.0.tar.gz and /dev/null differ
diff --git a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm b/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm
deleted file mode 100644
index 4903e10..0000000
Binary files a/dist/gbuild-debuginfo-1.0.0-1.x86_64.rpm and /dev/null differ
diff --git a/dist/gbuild_1.0.0_amd64.deb b/dist/gbuild_1.0.0_amd64.deb
deleted file mode 100644
index 0a722ed..0000000
Binary files a/dist/gbuild_1.0.0_amd64.deb and /dev/null differ
