225 lines
6.2 KiB
Markdown
225 lines
6.2 KiB
Markdown
# Hydra Browser Architecture
|
||
|
||
This document explains the Hydra Browser architecture for a developer who knows Python but is new to frontend and Electron. It focuses on how the app is structured, how data flows, and how the main features work.
|
||
|
||
## 1. What This App Is
|
||
|
||
Hydra is a **desktop app** (Electron) that renders **web pages inside a canvas of linked windows**. Each window is a React Flow node. Links can open new windows and form a graph of browsing paths.
|
||
|
||
Key ideas:
|
||
- **Electron** runs a desktop window using Chromium.
|
||
- The **renderer** is a React app (frontend UI).
|
||
- A **local proxy server** fetches web pages and injects scripts to intercept clicks.
|
||
- **Zustand store** holds all app state (nodes, edges, UI state).
|
||
|
||
## 2. High-Level Architecture
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph Electron App
|
||
Main[Main Process
|
||
src/main/index.js]
|
||
Renderer[Renderer Process
|
||
React UI]
|
||
end
|
||
|
||
Renderer -->|HTTP| Proxy[Local Proxy Server
|
||
src/server/proxy.js]
|
||
Renderer <-->|IPC| Main
|
||
Proxy -->|Fetch| Internet[Websites]
|
||
```
|
||
|
||
### Roles
|
||
- **Main Process**: bootstraps Electron and starts the proxy server.
|
||
- **Renderer**: React UI + state. Renders the canvas and windows.
|
||
- **Proxy Server**: fetches web content, injects scripts, returns HTML to iframes.
|
||
|
||
## 3. Key Modules
|
||
|
||
### 3.1 Main Process (Electron)
|
||
**File:** `src/main/index.js`
|
||
|
||
Responsibilities:
|
||
- Create the Electron window.
|
||
- Start the local proxy server (port 3001).
|
||
- Provide IPC handlers for file-based session save/load.
|
||
|
||
### 3.2 Proxy Server
|
||
**File:** `src/server/proxy.js`
|
||
|
||
Responsibilities:
|
||
- Fetch remote pages (via Axios).
|
||
- Inject a script into HTML to:
|
||
- intercept clicks
|
||
- intercept form submissions
|
||
- intercept programmatic navigations
|
||
- send `postMessage` events back to the renderer
|
||
- provide page text for summaries/chat
|
||
|
||
### 3.3 Renderer
|
||
**Root:** `src/renderer`
|
||
|
||
Key pieces:
|
||
- `components/ReactFlowCanvas.tsx`: canvas and node types
|
||
- `components/BrowserWindowNode.tsx`: web window node
|
||
- `components/NoteWindowNode.tsx`: notes
|
||
- `components/SummaryWindowNode.tsx`: AI summary output
|
||
- `components/ChatWindowNode.tsx`: AI chat window
|
||
- `store/hydraStore.ts`: Zustand store (all app state)
|
||
|
||
## 4. Data Flow: Opening a Page
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant UI as BrowserWindowNode
|
||
participant Store as Hydra Store
|
||
participant Proxy as Proxy Server
|
||
participant Web as Website
|
||
|
||
UI->>Store: addBrowserWindow(url)
|
||
Store->>UI: node created (BrowserWindowNode)
|
||
UI->>Proxy: GET /fetch?url=...
|
||
Proxy->>Web: fetch page
|
||
Web->>Proxy: HTML response
|
||
Proxy->>Proxy: inject link interception script
|
||
Proxy->>UI: HTML (blob URL)
|
||
UI->>UI: iframe loads blob
|
||
```
|
||
|
||
## 5. How Link Expansion Works
|
||
|
||
When the injected script sees a link click or navigation, it sends:
|
||
|
||
```js
|
||
window.parent.postMessage({
|
||
type: 'HYDRA_LINK_CLICK',
|
||
url: 'https://example.com',
|
||
openInNewWindow: true|false
|
||
}, '*')
|
||
```
|
||
|
||
The renderer listens for these messages and either:
|
||
- opens a new node and connects it with an edge, or
|
||
- navigates within the same node
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
Click[User clicks link] --> Script[Injected script]
|
||
Script -->|postMessage| Renderer
|
||
Renderer -->|openInNewWindow| StoreAdd[addBrowserWindow]
|
||
Renderer -->|same window| StoreNav[navigateBrowserWindow]
|
||
```
|
||
|
||
## 6. State Management (Zustand)
|
||
|
||
**File:** `src/renderer/store/hydraStore.ts`
|
||
|
||
The store keeps:
|
||
- **nodes**: all windows on the canvas
|
||
- **edges**: relationships between windows
|
||
- **theme**: dark/light
|
||
- **AI settings**: provider, API key, model
|
||
- **session info**: file path + name
|
||
|
||
### Example: Creating a Browser Window Node
|
||
|
||
```ts
|
||
addBrowserWindow(url, parentId?)
|
||
```
|
||
- Creates a new React Flow node
|
||
- Optionally creates an edge from the parent
|
||
- Re-layouts the graph so windows align
|
||
|
||
## 7. Layout Engine
|
||
|
||
**File:** `src/renderer/store/hydraStore.ts`
|
||
|
||
A custom layout function positions nodes:
|
||
- Root nodes are stacked vertically
|
||
- Children appear to the right of their parent
|
||
- Subtrees never overlap
|
||
|
||
```mermaid
|
||
graph LR
|
||
A[Root 1] --> B[Child 1]
|
||
A --> C[Child 2]
|
||
D[Root 2] --> E[Child]
|
||
```
|
||
|
||
## 8. Node Types
|
||
|
||
| Type | Purpose | File |
|
||
|------|---------|------|
|
||
| browserWindow | Render a web page | `BrowserWindowNode.tsx` |
|
||
| noteWindow | Editable note | `NoteWindowNode.tsx` |
|
||
| summaryWindow | AI summary output | `SummaryWindowNode.tsx` |
|
||
| chatWindow | AI chat UI | `ChatWindowNode.tsx` |
|
||
|
||
## 9. AI Features
|
||
|
||
### 9.1 Summaries
|
||
- Renderer requests page text via `postMessage`.
|
||
- Sends request to `/summarize` on the proxy.
|
||
- Proxy calls OpenAI/Anthropic API.
|
||
- Summary text displayed in a Summary node.
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant UI as BrowserWindowNode
|
||
participant Proxy as Proxy Server
|
||
participant LLM as AI Provider
|
||
|
||
UI->>Proxy: POST /summarize {text, url}
|
||
Proxy->>LLM: summarize
|
||
LLM->>Proxy: summary
|
||
Proxy->>UI: summary
|
||
```
|
||
|
||
### 9.2 Chat
|
||
- Chat node sends messages to `/chat` on proxy.
|
||
- Proxy adds context text and calls LLM API.
|
||
|
||
## 10. Session Files (File-Based)
|
||
|
||
Sessions are saved to disk using file dialogs. No in-app storage.
|
||
|
||
- **Save**: user chooses a `.hydra` file.
|
||
- **Load**: user selects a `.hydra` file.
|
||
|
||
Flow:
|
||
```mermaid
|
||
flowchart LR
|
||
UI -->|IPC| Main
|
||
Main -->|showSaveDialog/openDialog| OS
|
||
OS --> Main
|
||
Main -->|read/write file| FileSystem
|
||
```
|
||
|
||
## 11. Important Constraints
|
||
|
||
- Some sites block iframes (`X-Frame-Options`, `CSP frame-ancestors`).
|
||
- JavaScript-driven navigation can bypass the proxy if not intercepted.
|
||
- Removing `allow-same-origin` increases safety but prevents direct DOM access.
|
||
|
||
## 12. Mental Model (Python Developer Friendly)
|
||
|
||
Think of it as:
|
||
- **Electron Main** = Python `if __name__ == '__main__':` boot process
|
||
- **Renderer** = GUI app logic (like a web app)
|
||
- **Proxy Server** = a local Flask server that fetches and rewrites HTML
|
||
- **Zustand Store** = a global state dictionary
|
||
- **React Flow** = a canvas + graph UI library
|
||
|
||
## 13. Where to Start Reading
|
||
|
||
Recommended order:
|
||
1. `src/main/index.js` – app bootstrap
|
||
2. `src/server/proxy.js` – page fetching and injection
|
||
3. `src/renderer/store/hydraStore.ts` – core data model
|
||
4. `src/renderer/components/ReactFlowCanvas.tsx` – node registration
|
||
5. `src/renderer/components/BrowserWindowNode.tsx` – core window node
|
||
|
||
---
|
||
|
||
If you want a deeper walkthrough of any module, ask and I’ll expand that section.
|