ARGUS — spec

Functional specification for the capture system.

ARGUS — Argument Research & Graph Utility System

Project Specification v1.0


1. Project Overview

ARGUS is a browser-based research capture tool comprising three tightly integrated components:

  1. Browser Extension — captures selected text, grabs page URL, renders the side panel UI
  2. Background State Manager — a local Python WebSocket server that holds session state and writes to disk
  3. Macro Pad Integration — 9-button macro pad mapped to core capture and navigation actions

The tool allows a researcher to rotate between webpages, capture highlighted text with a classification weight, organise captures under themes and subtopics, and visually connect related ideas in a live feed and graph view — all exportable as JSON for further processing with an LLM.


2. System Architecture

┌──────────────────────────────────────────────────────┐
│                  Browser (Chrome/Firefox)            │
│                                                      │
│  ┌─────────────────────┐   ┌───────────────────────┐ │
│  │   Content Script    │   │     Side Panel UI     │ │
│  │  - Text selection   │   │  - Feed view          │ │
│  │  - URL capture      │   │  - Graph view         │ │
│  │  - Keyboard events  │   │  - Theme/subtopic nav │ │
│  └──────────┬──────────┘   └──────────┬────────────┘ │
│             │    Extension Messaging   │              │
│             └──────────┬──────────────┘              │
│                        │                             │
│              ┌─────────▼──────────┐                  │
│              │  Background Script │                  │
│              │  (Service Worker)  │                  │
│              └─────────┬──────────┘                  │
└────────────────────────┼─────────────────────────────┘
                         │ WebSocket (ws://localhost:8765)
                ┌────────▼────────┐
                │  Python Server  │
                │  - State mgmt   │
                │  - JSON file I/O│
                │  - Keybind listen│
                └─────────────────┘
                         ▲
                         │ USB HID / keyboard events
                ┌────────┴────────┐
                │   Macro Pad     │
                │   (9 buttons)   │
                └─────────────────┘

3. Macro Pad Button Layout

[ 1 Prev Theme  ] [ 2 Cycle Subtopic ] [ 3 Next Theme  ]
[ 4 Review Last ] [ 5  Toggle Panel  ] [ 6   RESERVED  ]
[ 7  Green  ✓   ] [ 8  Orange  ⚡    ] [ 9   Red  ✗    ]

Button Definitions

Button Action Detail
1 Previous Theme Cycles backward through theme list
2 Cycle Subtopic Cycles forward through subtopics of current theme; wraps with option to create new
3 Next Theme Cycles forward through theme list
4 Review Last Change Opens a quick-view overlay of the most recent capture with option to undo
5 Toggle Panel Opens/closes the side panel UI
6 Reserved Placeholder for future Mendeley citation integration
7 Capture — Green Copies selected text → captures as supporting argument
8 Capture — Orange Copies selected text → captures as counter-argument
9 Capture — Red Copies selected text → captures as strong counter/critical argument

Keybind Mapping (configurable)

Macro pad buttons send unique key combinations that the Python server and/or extension intercept:

Button 1 → Ctrl+Shift+F1
Button 2 → Ctrl+Shift+F2
Button 3 → Ctrl+Shift+F3
Button 4 → Ctrl+Shift+F4
Button 5 → Ctrl+Shift+F5
Button 6 → Ctrl+Shift+F6
Button 7 → Ctrl+Shift+F7
Button 8 → Ctrl+Shift+F8
Button 9 → Ctrl+Shift+F9

These are configurable in config.json.


4. Data Model

4.1 Capture Object

{
  "id": "cap_20260429_143200_001",
  "text": "Selected text content here",
  "weight": "green",
  "theme": "Climate Policy",
  "subtopic": "Carbon Tax",
  "source_url": "https://example.com/article",
  "source_title": "Article Page Title",
  "timestamp": "2026-04-29T14:32:00Z",
  "connections": ["cap_20260429_143500_003"],
  "flagged": false
}

Weight values: "green" | "orange" | "red"

4.2 Connection Object

{
  "from_id": "cap_20260429_143200_001",
  "to_id": "cap_20260429_143500_003",
  "relationship": "contradicts",
  "created_at": "2026-04-29T15:00:00Z"
}

Relationship values: "supports" | "contradicts" | "extends" | "see also" | "custom"

4.3 Session File Structure

{
  "session_id": "session_20260429",
  "created_at": "2026-04-29T12:00:00Z",
  "last_updated": "2026-04-29T16:00:00Z",
  "themes": [
    {
      "id": "theme_001",
      "name": "Climate Policy",
      "subtopics": ["Carbon Tax", "Emissions Trading", "Green New Deal"]
    }
  ],
  "captures": [...],
  "connections": [...]
}

5. Browser Extension

5.1 File Structure

extension/
├── manifest.json          (Manifest V3)
├── background.js          (Service Worker)
├── content.js             (Injected into all pages)
├── panel/
│   ├── panel.html
│   ├── panel.jsx          (React — main UI)
│   ├── FeedView.jsx
│   ├── GraphView.jsx
│   └── panel.css
├── icons/
│   └── icon-48.png
└── config.json

5.2 manifest.json (key fields)

{
  "manifest_version": 3,
  "name": "ARGUS Research Capture",
  "version": "1.0.0",
  "permissions": ["activeTab", "scripting", "sidePanel", "storage"],
  "host_permissions": ["<all_urls>"],
  "background": { "service_worker": "background.js" },
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": ["content.js"]
  }],
  "side_panel": { "default_path": "panel/panel.html" },
  "action": { "default_title": "ARGUS" }
}

5.3 content.js Responsibilities

  • Listen for the keyboard shortcuts (Ctrl+Shift+F1 through F9)
  • On capture shortcut (F7/F8/F9): grab window.getSelection().toString() and document.title + window.location.href
  • Send message to background.js via chrome.runtime.sendMessage()
  • Receive state updates (current theme/subtopic) from background to display a small HUD toast

5.4 background.js Responsibilities

  • Maintain current session state: active theme index, active subtopic index, last capture
  • Relay captures to Python server via WebSocket (ws://localhost:8765)
  • Handle panel open/close toggle
  • On connection failure to Python server: buffer captures locally using chrome.storage.local

5.5 Message Protocol (Extension ↔ Background)

// content.js → background.js
{ type: "CAPTURE", payload: { text, url, title, weight } }
{ type: "NAV", action: "PREV_THEME" | "NEXT_THEME" | "CYCLE_SUBTOPIC" }
{ type: "REVIEW_LAST" }
{ type: "TOGGLE_PANEL" }

// background.js → content.js (state broadcast)
{ type: "STATE_UPDATE", state: { theme, subtopic, lastCapture } }

5.6 WebSocket Protocol (Extension ↔ Python Server)

// Extension → Server
{ "event": "CAPTURE", "data": { ...capture object } }
{ "event": "NAV", "action": "PREV_THEME" }
{ "event": "UNDO_LAST" }
{ "event": "GET_STATE" }

// Server → Extension
{ "event": "STATE", "data": { "theme": "...", "subtopic": "...", "captures": [...] } }
{ "event": "CAPTURE_ACK", "data": { "id": "cap_..." } }
{ "event": "ERROR", "message": "..." }

6. Side Panel UI

6.1 Layout

┌─────────────────────────────────┐
│  🔵 ARGUS         [Feed] [Graph]│  ← view toggle
├─────────────────────────────────┤
│  Theme: Climate Policy   ◀  ▶   │
│  Subtopic: Carbon Tax    ◀  ▶   │
├─────────────────────────────────┤
│  FEED VIEW:                     │
│  ┌───────────────────────────┐  │
│  │ 🟢 "Selected text here…"  │  │
│  │ Carbon Tax • example.com  │  │
│  │ 14:32 · [link] [🔗] [✕]   │  │
│  └───────────────────────────┘  │
│  ┌───────────────────────────┐  │
│  │ 🟠 "Another argument…"    │  │
│  └───────────────────────────┘  │
│  [ + Add Theme ] [ Export JSON ]│
└─────────────────────────────────┘

6.2 Feed View (FeedView.jsx)

  • Renders capture cards sorted by timestamp descending
  • Each card shows: weight indicator dot, text snippet (truncated to 3 lines, expandable), source title + URL, timestamp
  • Card actions: open source URL, create connection (🔗), delete (with confirm)
  • Cards are filterable by theme, subtopic, and weight
  • Drag handle on each card to initiate a connection drag

Drag-to-connect interaction: - User drags from the 🔗 icon on Card A - Hovers over Card B → highlight appears - On drop: a small popover appears asking for relationship type (supports / contradicts / extends / see also / custom) - Connection is stored and visualised with a line in graph view

6.3 Graph View (GraphView.jsx)

  • Built with React Flow (npm install reactflow)
  • Each capture = a node, colour-coded by weight (green/orange/red border)
  • Connections = edges, labelled with relationship type
  • Nodes grouped visually by subtopic using React Flow's grouping/subflow feature
  • Supports zoom, pan, drag-to-reposition
  • Double-click a node to expand full text
  • Same drag-to-connect gesture available here

6.4 State Management

Use React useReducer + useContext for global state. No external state library needed.

// Actions
LOAD_STATE       // Hydrate from server on panel open
ADD_CAPTURE      // New capture arrives
UNDO_LAST        // Remove most recent capture
SET_THEME        // Change active theme
SET_SUBTOPIC     // Change active subtopic
ADD_CONNECTION   // Link two captures
REMOVE_CAPTURE   // Delete a card

7. Python Background Server

7.1 File Structure

server/
├── main.py              (entry point)
├── websocket_server.py  (ws handler)
├── state_manager.py     (theme/subtopic/session state)
├── file_io.py           (JSON read/write)
├── keybind_listener.py  (global hotkey listener)
├── config.py            (loads config.json)
└── data/
    └── sessions/        (auto-created, one JSON per session)

7.2 Dependencies

websockets>=12.0
pynput>=1.7       # global keybind listening
python-dateutil

7.3 main.py Startup

  1. Load config.json
  2. Load or create today's session file from data/sessions/
  3. Start keybind_listener in a background thread
  4. Start WebSocket server on ws://localhost:8765
  5. Print status to terminal

7.4 state_manager.py

Holds in-memory state:

state = {
    "active_theme_index": 0,
    "active_subtopic_index": 0,
    "session": { ...session dict... },
    "last_capture_id": None
}

Methods: - get_current_theme() → theme object - get_current_subtopic() → string - next_theme() / prev_theme() - cycle_subtopic() — wraps with new-subtopic prompt via WebSocket message - add_capture(capture) → writes to session, saves file - undo_last() → removes last capture by ID - add_connection(from_id, to_id, relationship) - save_session() → atomic write to JSON file

7.5 keybind_listener.py

Uses pynput to listen globally for the configured shortcuts. On trigger, sends an internal event to the state manager identically to how the WebSocket would.

This allows macro pad keypresses to work even when the browser is not focused.

7.6 Export

The session JSON file is the export artifact. Additionally expose a GET /export HTTP endpoint (simple http.server) that serves the current session as a download — useful for feeding to Claude Opus.


8. Configuration File

config.json (lives in repo root, loaded by both extension and server):

{
  "websocket_port": 8765,
  "session_dir": "./server/data/sessions",
  "keybinds": {
    "prev_theme":     "ctrl+shift+f1",
    "cycle_subtopic": "ctrl+shift+f2",
    "next_theme":     "ctrl+shift+f3",
    "review_last":    "ctrl+shift+f4",
    "toggle_panel":   "ctrl+shift+f5",
    "reserved":       "ctrl+shift+f6",
    "capture_green":  "ctrl+shift+f7",
    "capture_orange": "ctrl+shift+f8",
    "capture_red":    "ctrl+shift+f9"
  },
  "default_themes": ["Research", "Uncategorized"],
  "default_subtopics": ["General"],
  "toast_duration_ms": 2000
}

9. Build & Development Setup

9.1 Prerequisites

  • Node.js 20+
  • Python 3.11+
  • Chrome or Firefox (with extension dev mode enabled)

9.2 Install

# Clone repo
git clone https://github.com/yourname/argus.git
cd argus

# Python server
cd server
pip install -r requirements.txt
python main.py

# Extension (in a separate terminal)
cd ../extension
npm install
npm run build   # outputs to extension/dist/

9.3 Load Extension in Chrome

  1. Go to chrome://extensions
  2. Enable Developer Mode
  3. Click "Load unpacked" → select extension/dist/
  • ESLint
  • Prettier
  • Python (Pylance)
  • React snippets (dsznajder.es7-react-js-snippets)

10. Build Phases

Phase 1 — Core Capture Loop

  • [ ] Extension skeleton with manifest V3
  • [ ] content.js keyboard listener + text/URL grab
  • [ ] background.js WebSocket client
  • [ ] Python server: WebSocket handler + state manager + JSON file write
  • [ ] Basic side panel: feed view (no drag/connect yet)

Phase 2 — Navigation

  • [ ] Theme and subtopic cycling (buttons 1, 2, 3)
  • [ ] HUD toast in browser showing current theme/subtopic
  • [ ] Review last + undo (button 4)
  • [ ] Panel toggle (button 5)

Phase 3 — Connections & Graph

  • [ ] Drag-to-connect in feed view
  • [ ] Relationship popover
  • [ ] React Flow graph view
  • [ ] Toggle between feed and graph

Phase 4 — Polish & Export

  • [ ] Filter/search in feed
  • [ ] Export JSON button
  • [ ] Session management (new session, load previous)
  • [ ] Add theme / add subtopic UI flows
  • [ ] Button 6 — Mendeley / Citation.js integration (future)

11. JSON Export Schema for Claude Opus

When exporting for LLM processing, include a meta block to give Claude context:

{
  "meta": {
    "export_version": "1.0",
    "exported_at": "2026-04-29T16:00:00Z",
    "session_id": "session_20260429",
    "total_captures": 34,
    "total_connections": 12,
    "themes": ["Climate Policy", "Economic Impact"],
    "instructions": "This is a structured research capture. Captures have weights: green = supporting argument, orange = counter-argument, red = strong objection. Connections describe relationships between captures. Please synthesise the arguments by theme and subtopic."
  },
  "themes": [...],
  "captures": [...],
  "connections": [...]
}

12. Key Technical Decisions

Decision Choice Reason
Extension API Manifest V3 Required for modern Chrome; better security model
Side panel Chrome Side Panel API Native browser panel, no popup instability
UI framework React (via Vite) Component model suits feed + graph views
Graph library React Flow Purpose-built for node graphs, free, well maintained
Local server Python + websockets Simple, cross-platform, easy for you to extend
Global keybinds pynput Works across platforms, minimal deps
State useReducer + Context No Redux overhead for this scope
Storage Flat JSON files per session Easy to inspect, version control, and feed to LLMs