# Browser Tabs Implementation Plan ## Overview Add full browser tab support to sovereign_browser. Each tab gets its own toolbar (URL bar + hamburger menu) and WebKitWebView. All tabs share a single `WebKitWebContext`, so the `sovereign://` Nostr bridge, security settings, and TLS policy apply automatically to every tab. Tab behavior is configurable via a new settings module. ## Architecture ```mermaid flowchart TB subgraph Window Notebook[GtkNotebook — tab strip] NewTabBtn[New Tab Button +] end subgraph TabPage Toolbar[Toolbar: hamburger + URL entry] Webview[WebKitWebView] end Notebook -->|page 0| TabPage Notebook -->|page 1| TabPage Notebook -->|page N| TabPage TabPage -->|shared| Ctx[WebKitWebContext — sovereign bridge, security, TLS] Ctx --> NostrBridge[nostr_bridge.c] Ctx --> NostrInject[nostr_inject.c — per-webview] TabManager[tab_manager.c] -->|manages| Notebook Settings[settings.c] -->|configures| TabManager Session[session.c] -->|save/restore| TabManager ``` ### Key design decisions 1. **Per-tab toolbar.** Each notebook page is a vertical `GtkBox` containing the toolbar (hamburger + URL entry) on top and the `WebKitWebView` below. This is simpler than a shared toolbar that has to swap signal handlers on tab switch, and it matches the user's preference. 2. **Shared `WebKitWebContext`.** All `WebKitWebView` instances are created from the same context. The `sovereign://` URI scheme registration, security manager flags, and TLS error policy are set once on the context and apply to all tabs. No per-tab re-registration needed. 3. **Per-webview `nostr_inject_setup()`.** The `window.nostr` injection uses `WebKitUserContentManager`, which is per-webview. Each new tab calls `nostr_inject_setup()` on its own webview. 4. **`tab_manager.c` owns the `GtkNotebook`.** It provides functions to create, close, switch, and query tabs. Each tab is tracked in a struct holding the webview, URL entry, hamburger button, and tab label widget. 5. **Settings module.** A new `settings.c`/`settings.h` persists user preferences to `~/.sovereign_browser/settings.conf` (simple key=value text file). Configurable options: | Setting | Default | Description | |---------|---------|-------------| | `restore_session` | `true` | Restore open tabs on next launch | | `new_tab_url` | `https://example.com` | URL loaded in new tabs | | `tab_bar_position` | `top` | `top` / `bottom` / `left` / `right` | | `show_tab_close_buttons` | `true` | Show per-tab close button | | `middle_click_close` | `true` | Middle-click on tab closes it | | `ctrl_tab_switch` | `true` | Ctrl+Tab cycles tabs | | `max_tabs` | `50` | Maximum simultaneous tabs | | `tab_drag_reorder` | `true` | Allow drag to reorder tabs | 6. **Session module.** `session.c`/`session.h` saves the list of open tab URLs (one per line) to `~/.sovereign_browser/session.txt` on window destroy, and restores them on startup if `settings.restore_session` is true. ## New files ### `src/settings.h` / `src/settings.c` ```c typedef struct { gboolean restore_session; char new_tab_url[512]; int tab_bar_position; /* GTK_POS_TOP, etc. */ gboolean show_tab_close_buttons; gboolean middle_click_close; gboolean ctrl_tab_switch; int max_tabs; gboolean tab_drag_reorder; } browser_settings_t; void settings_load(browser_settings_t *out); void settings_save(const browser_settings_t *s); const browser_settings_t *settings_get(void); /* global singleton */ ``` ### `src/tab_manager.h` / `src/tab_manager.c` ```c typedef struct { WebKitWebView *webview; GtkWidget *url_entry; GtkWidget *hamburger; GtkWidget *tab_label; /* composite: icon + title + close btn */ char current_url[2048]; char title[256]; } tab_info_t; void tab_manager_init(GtkContainer *parent, WebKitWebContext *ctx); int tab_manager_new_tab(const char *url); void tab_manager_close_tab(int index); void tab_manager_close_active(void); int tab_manager_get_active_index(void); tab_info_t *tab_manager_get_active(void); tab_info_t *tab_manager_get(int index); int tab_manager_count(void); void tab_manager_set_title(int index, const char *title); ``` ### `src/session.h` / `src/session.c` ```c void session_save(void); /* called on window destroy */ int session_restore(void); /* called at startup, returns tab count created */ ``` ## Modified files ### `src/main.c` — major refactor - Remove the single `webview` / `url_entry` / `toolbar` creation. - Create `WebKitWebContext` once, configure security + `sovereign://` bridge on it (existing code moves here, unchanged logic). - Call `tab_manager_init()` with the window's vbox and the shared context. - The hamburger menu builder moves into `tab_manager.c` (or a shared helper) since it now needs to reference the per-tab webview, not a global. - All menu callbacks (`on_menu_reload`, `on_menu_stop`, `on_menu_inspector`, `on_menu_open_file`, `on_menu_history_item`) fetch the active tab's webview via `tab_manager_get_active()` instead of receiving it as a parameter. - `on_load_changed` and `on_load_failed` are connected per-tab inside `tab_manager_new_tab()` and update the per-tab URL entry and tab title. - `on_url_activate` loads the URL into the active tab's webview. - Keyboard shortcuts: a `key-press-event` handler on the main window checks for Ctrl+T / Ctrl+W / Ctrl+Tab / Ctrl+Shift+Tab / Ctrl+PageUp / Ctrl+PageDown. - `on_window_destroy` calls `session_save()` before cleanup. ### `src/nostr_inject.c` — no changes `nostr_inject_setup()` already takes a `WebKitWebView*` and is called per-webview. The tab manager calls it for each new tab. ### `src/nostr_bridge.c` — no changes The bridge is registered on the shared `WebKitWebContext`. All webviews on that context automatically get the `sovereign://` scheme handler. ### `src/history.c` — no changes History is already global. All tabs contribute to and read from the same history list. ### `Makefile` Add `src/settings.c src/tab_manager.c src/session.c` to `SRC`. ## Tab UI layout ``` ┌──────────────────────────────────────────────────────┐ │ [Tab 1 ×] [Tab 2 ×] [Tab 3 ×] [+] │ ← GtkNotebook tab strip ├──────────────────────────────────────────────────────┤ │ [☰] https://example.com____________ │ ← per-tab toolbar ├──────────────────────────────────────────────────────┤ │ │ │ WebKitWebView │ ← per-tab webview │ │ └──────────────────────────────────────────────────────┘ ``` ### Tab label widget (composite) Each tab label is a horizontal `GtkBox`: ``` [favicon] [Title text…] [×] ``` - Favicon: placeholder icon for now (can be wired to WebKit favicon later) - Title: `GtkLabel` with ellipsize end, max width ~150px - Close button: `GtkButton` with `window-close-symbolic` icon, only shown if `settings.show_tab_close_buttons` is true ### Right-click context menu on tab label - New Tab - Close Tab - Close Other Tabs - Close Tabs to the Right - Duplicate Tab - Reload Tab ### Drag reordering `GtkNotebook` supports drag reordering natively via `gtk_notebook_set_tab_reorderable(notebook, child, TRUE)`. Enabled when `settings.tab_drag_reorder` is true. ## Keyboard shortcuts | Shortcut | Action | |----------|--------| | Ctrl+T | New tab | | Ctrl+W | Close active tab | | Ctrl+Tab | Next tab | | Ctrl+Shift+Tab | Previous tab | | Ctrl+PageUp | Previous tab | | Ctrl+PageDown | Next tab | | Ctrl+L | Focus URL bar of active tab | ## Session restore On startup, if `settings.restore_session` is true and `~/.sovereign_browser/session.txt` exists and is non-empty, each line is loaded as a tab URL. If the file is missing or empty, a single tab is opened with the start URL (from command-line arg or `settings.new_tab_url`). On window destroy, before `gtk_main_quit()`, the list of open tab URLs is written to `~/.sovereign_browser/session.txt`. ## Settings UI A new menu item "Settings…" in the hamburger menu opens a native GTK dialog (not a web page, to keep it simple and avoid bootstrapping a `sovereign://settings` renderer). The dialog has checkboxes and a text entry for each configurable option. On "OK", `settings_save()` is called and applicable changes (like tab bar position) are applied live. ## Implementation order 1. **settings.c/h** — foundation, no UI yet, just load/save + global singleton 2. **tab_manager.c/h** — core notebook management, per-tab page creation, tab label widget, new/close/switch 3. **session.c/h** — save/restore using tab_manager API 4. **main.c refactor** — wire up tab_manager, move context setup, rewire callbacks to use active tab 5. **Tab interactions** — middle-click close, right-click menu, drag reorder 6. **Keyboard shortcuts** — window-level key-press handler 7. **Session save/restore** — integrate into startup and destroy 8. **Settings dialog** — GTK dialog wired to settings module 9. **Makefile + docs** — build and documentation updates