Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
252 changes: 252 additions & 0 deletions JOBS-TO-BE-DONE.md

Large diffs are not rendered by default.

73 changes: 73 additions & 0 deletions STYLEGUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Drawbridge Style Guide

## Typography

### Fonts

| Purpose | Font | Fallbacks |
|---------|------|-----------|
| **UI Text** | `Space Grotesk` | `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif` |
| **Code** | `SF Mono` | `Monaco, Consolas, monospace` |

```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300..700&display=swap" rel="stylesheet">
```

### Type Scale

| Style | Size | Weight |
|-------|------|--------|
| H1 | 20px | 600 |
| H2 | 18px | 600 |
| H3 | 16px | 600 |
| Body | 14px | 400 |
| Small | 12px | 500 |
| Caption | 11px | 500 |
| Code | 13px | 400 |

---

## Colors

### Light Theme

| Token | Value |
|-------|-------|
| **Background** | `#ffffff` |
| **Surface** | `#f9fafb` |
| **Surface Alt** | `#f3f4f6` |
| **Border** | `#e5e7eb` |
| **Text** | `#1f2937` |
| **Text Secondary** | `#4b5563` |
| **Text Muted** | `#9ca3af` |
| **Accent** | `#3b82f6` |
| **Success** | `#059669` |
| **Warning** | `#d97706` |
| **Error** | `#dc2626` |

### Dark Theme

| Token | Value |
|-------|-------|
| **Background** | `#111827` |
| **Surface** | `#1f2937` |
| **Surface Alt** | `#374151` |
| **Border** | `#374151` |
| **Text** | `#f9fafb` |
| **Text Secondary** | `#e5e7eb` |
| **Text Muted** | `#9ca3af` |
| **Accent** | `#60a5fa` |
| **Success** | `#10b981` |
| **Warning** | `#fbbf24` |
| **Error** | `#f87171` |

### Status Colors (Both Themes)

| Status | Color |
|--------|-------|
| To-Do | `#F59E0B` |
| In Progress | `#3B82F6` |
| Done | `#10B981` |
| Error | `#EF4444` |
61 changes: 61 additions & 0 deletions V2_CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Drawbridge v2 Changelog

**Version bump:** 1.1.0 → **2.0.0**

## Architecture: Side Panel Migration

The biggest change is moving from an **injected in-page sidebar** (`moat.js` + `moat.css`) to Chrome's native **Side Panel API** (`chrome.sidePanel`). This is a fundamental UI architecture shift:

| | v1 (main) | v2 |
|---|---|---|
| **UI host** | `moat.js` injected into web pages (~3,900 lines) | `sidepanel/` — dedicated Chrome side panel (3 files) |
| **Styles** | `moat.css` (2,390 lines injected into pages) | `sidepanel.css` (629 lines, scoped) + `content.css` (169 lines, minimal page injection) |
| **Activation** | Extension icon → `toggleMoat` message → in-page sidebar | Extension icon → `chrome.sidePanel.open()` |
| **Communication** | Direct — JS runs in page context | Message relay via `background.js` (content script ↔ side panel) |

## Deleted Files (-6,319 lines)
- **`moat.js`** — entire in-page sidebar UI removed
- **`moat.css`** — all in-page sidebar styles removed

## New Files
- **`sidepanel/sidepanel.html`** — side panel markup with header, tabs (To Do / Doing / Done), tools dropdown, project connection UI
- **`sidepanel/sidepanel.js`** (~730 lines) — full side panel logic: task rendering, project connect/disconnect, thumbnail loading, theme toggle, retry-based content script communication
- **`sidepanel/sidepanel.css`** (~630 lines) — polished styles with light/dark theme support via CSS custom properties
- **`content.css`** (~170 lines) — lightweight styles for overlays, comment boxes, and drawing canvas injected into pages
- **`STYLEGUIDE.md`** — design system documentation (typography, colors, status colors)

## Modified Files

### `manifest.json`
- Added `sidePanel` permission
- Registered `sidepanel/sidepanel.html` as default side panel
- Removed `moat.js` and `moat.css` from content scripts
- Added `content.css` as the only injected stylesheet

### `background.js`
- Replaced `toggleMoat` messaging with `chrome.sidePanel.open()`
- Added message relay system: routes messages between content script and side panel (both directions)
- Handles ~10 message types: `ENTER_COMMENT_MODE`, `SETUP_PROJECT`, `DISCONNECT_PROJECT`, `LOAD_TASKS`, etc.

### `content_script.js` (+290 lines)
- Added `relayToSidePanel()` helper for background-mediated communication
- Added handlers for side panel messages: project setup/disconnect, task CRUD, annotation modes, screenshot management
- Added `loadTasksForSidePanel()`, `updateTaskStatusFromSidePanel()`, `deleteTaskFromSidePanel()`
- Added `getThumbnailDataUrl()` — reads screenshots from File System API, converts to data URLs for side panel display
- Added `getScreenshotCount()` and `clearScreenshots()` utilities
- Improved disconnect logic with `chrome.storage.local` flag to persist disconnect across reloads
- Removed `Cmd+Shift+F` toggle sidebar shortcut (no longer needed)
- Relays project connection and task update events to side panel

### Demo Site Updates
- Expanded pricing section from 1 card to 3 cards in a grid
- Replaced emoji icons with inline SVGs
- Fixed typos (Conact → Contact, intergration → integration, etc.)
- Removed typewriter animation and glow effects
- Cleaned up "flashing" class usage from buttons

## Key Architectural Decisions
1. **Content script keeps file system access** — all File System API operations stay in the content script since side panels can't directly access page-scoped handles
2. **Background script as message bus** — all communication between side panel and content script is relayed through background.js
3. **Thumbnail data URLs** — screenshots are read from disk and sent as base64 data URLs to the side panel (since it can't access the file handles directly)
4. **Disconnect persistence** — uses `chrome.storage.local` flags per-origin so disconnects survive page reloads
61 changes: 47 additions & 14 deletions chrome-extension/background.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// chrome-extension/background.js

// Handle extension icon click - toggle Drawbridge sidebar
// Handle extension icon click - open side panel
chrome.action.onClicked.addListener(async (tab) => {
// Check for restricted URL schemes where content scripts cannot run
const restrictedSchemes = [
Expand All @@ -13,30 +13,24 @@ chrome.action.onClicked.addListener(async (tab) => {
];

const isRestricted = restrictedSchemes.some(scheme => tab.url?.startsWith(scheme));

if (isRestricted || !tab.id || !tab.url) {
console.warn('Drawbridge: Cannot open on restricted page:', tab.url);
return;
}

try {
// Send message to content script to toggle the Drawbridge sidebar
// Content scripts are auto-injected via manifest, so this should work
chrome.tabs.sendMessage(tab.id, { action: 'toggleMoat' }, (response) => {
if (chrome.runtime.lastError) {
console.warn('Drawbridge: Content script may not be ready yet:', chrome.runtime.lastError.message);
// This is okay - content script will be injected on next page load
} else {
console.log('Drawbridge: Sidebar toggled successfully');
}
});
// Open the side panel for this tab
await chrome.sidePanel.open({ tabId: tab.id });
console.log('Drawbridge: Side panel opened');
} catch (error) {
console.error('Drawbridge: Failed to toggle sidebar:', error);
console.error('Drawbridge: Failed to open side panel:', error);
}
});

// Handle screenshot capture requests from content scripts
// Handle messages from content scripts and side panel
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
// Screenshot capture requests from content scripts
if (message.type === 'CAPTURE_SCREENSHOT') {
chrome.tabs.captureVisibleTab(
sender.tab.windowId,
Expand All @@ -52,4 +46,43 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
);
return true; // Required for async sendResponse
}

// Relay messages from content script to side panel
if (message.type === 'RELAY_TO_SIDEPANEL') {
// Broadcast to all extension pages (including side panel)
chrome.runtime.sendMessage(message.payload).catch(() => {
// Side panel might not be open, that's okay
});
return false;
}

// Messages that need to be relayed to the active tab's content script
const contentScriptMessages = [
'ENTER_COMMENT_MODE',
'ENTER_DRAWING_MODE',
'EXIT_ANNOTATION_MODE',
'SETUP_PROJECT',
'DISCONNECT_PROJECT',
'LOAD_TASKS',
'UPDATE_TASK_STATUS',
'DELETE_TASK',
'GET_CONNECTION_STATUS'
];

if (contentScriptMessages.includes(message.type)) {
// This message came from side panel, relay to active tab
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (tabs[0]?.id) {
chrome.tabs.sendMessage(tabs[0].id, message, (response) => {
if (chrome.runtime.lastError) {
console.warn('Relay to content script failed:', chrome.runtime.lastError.message);
sendResponse({ success: false, error: chrome.runtime.lastError.message });
} else {
sendResponse(response);
}
});
}
});
return true; // Keep channel open for async response
}
});
168 changes: 168 additions & 0 deletions chrome-extension/content.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/* Drawbridge Content Script Styles */
/* These styles are injected into web pages for overlays and comment boxes */

/* Comment Mode Cursor */
body.float-comment-mode {
cursor: pointer !important;
}

body.float-comment-mode * {
cursor: pointer !important;
}

/* Drawing Mode Cursor */
body.float-drawing-mode {
cursor: crosshair !important;
}

body.float-drawing-mode * {
cursor: crosshair !important;
}

/* Element Highlight */
.float-highlight {
outline: 2px solid #3B82F6 !important;
outline-offset: 2px !important;
background-color: rgba(59, 130, 246, 0.1) !important;
transition: all 0.2s ease !important;
}

.float-highlight:not(img):not(button):not(a):not(input) {
min-height: 20px !important;
min-width: 20px !important;
}

/* Drawing Canvas Overlay */
.float-drawing-canvas {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: 9999;
background: transparent;
}

.float-drawing-canvas[style*="pointer-events: auto"] {
cursor: crosshair;
}

/* Comment Box */
.float-comment-box {
position: fixed;
width: 300px;
background: #ffffff;
border-radius: 8px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
padding: 16px;
z-index: 10001;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

.float-comment-input {
width: 100%;
min-height: 80px;
padding: 12px;
border: 1px solid #e5e7eb;
border-radius: 6px;
font-size: 14px;
resize: vertical;
font-family: inherit;
transition: border-color 0.2s;
background: #ffffff;
color: #1f2937;
box-sizing: border-box;
}

.float-comment-input:focus {
outline: none;
border-color: #3b82f6;
}

.float-comment-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 12px;
}

.float-comment-actions button {
padding: 6px 14px;
border: none;
border-radius: 6px;
height: 32px;
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
font-family: inherit;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}

.float-comment-cancel {
background: #ffffff;
color: #4b5563;
border: 1px solid #e5e7eb !important;
}

.float-comment-cancel:hover {
background: #f9fafb;
color: #1f2937;
border-color: #d1d5db !important;
}

.float-comment-submit {
background: #3b82f6;
color: white;
font-weight: 500;
border: 1px solid #3b82f6 !important;
}

.float-comment-submit:hover {
background: #2563eb;
}

/* Shake animation for comment box */
.float-shake {
animation: float-shake 0.5s ease-in-out !important;
}

@keyframes float-shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-10px); }
20%, 40%, 60%, 80% { transform: translateX(10px); }
}

/* Notification Toast */
.float-notification {
position: fixed;
bottom: 20px;
right: 20px;
background: #1f2937;
color: #ffffff;
padding: 12px 24px;
border-radius: 8px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 10002;
animation: float-slide-up 0.3s ease;
max-width: 300px;
pointer-events: auto;
}

.float-notification.float-error {
background: #dc2626;
color: white;
}

@keyframes float-slide-up {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
Loading