chore: initial commit of TaskTracker project

Existing ASP.NET API with vanilla JS SPA, WindowWatcher, Chrome extension, and MCP server.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 22:08:45 -05:00
commit e12f78c479
66 changed files with 5170 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
const API_BASE = "http://localhost:5200";
let debounceTimer = null;
let lastReportedUrl = "";
let lastReportedTitle = "";
function reportTab(tab) {
if (!tab || !tab.url || tab.url.startsWith("chrome://") || tab.url.startsWith("chrome-extension://")) {
return;
}
// Skip if same as last reported
if (tab.url === lastReportedUrl && tab.title === lastReportedTitle) {
return;
}
// Debounce: wait 2 seconds before reporting
if (debounceTimer) {
clearTimeout(debounceTimer);
}
debounceTimer = setTimeout(() => {
const payload = {
source: "ChromeExtension",
appName: "chrome.exe",
windowTitle: tab.title || "",
url: tab.url
};
fetch(`${API_BASE}/api/context`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
})
.then(() => {
lastReportedUrl = tab.url;
lastReportedTitle = tab.title;
})
.catch(err => console.warn("Failed to report context:", err));
}, 2000);
}
// Tab switched
chrome.tabs.onActivated.addListener((activeInfo) => {
chrome.tabs.get(activeInfo.tabId, (tab) => {
if (chrome.runtime.lastError) return;
reportTab(tab);
});
});
// Navigation within tab
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.status === "complete" && tab.active) {
reportTab(tab);
}
});

View File

@@ -0,0 +1,16 @@
{
"manifest_version": 3,
"name": "Work Context Tracker",
"version": "1.0",
"description": "Reports active tab URL and title to the Work Context Tracker API",
"permissions": ["tabs", "activeTab", "storage"],
"background": {
"service_worker": "background.js"
},
"action": {
"default_popup": "popup.html"
},
"host_permissions": [
"http://localhost:5200/*"
]
}

View File

@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
width: 300px;
padding: 12px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 13px;
color: #333;
}
h3 {
margin: 0 0 8px 0;
font-size: 14px;
color: #1a73e8;
}
.task-title {
font-weight: 600;
font-size: 14px;
margin-bottom: 4px;
}
.task-status {
display: inline-block;
padding: 2px 8px;
border-radius: 12px;
font-size: 11px;
font-weight: 500;
margin-bottom: 8px;
}
.status-Active { background: #e8f5e9; color: #2e7d32; }
.status-Paused { background: #fff3e0; color: #e65100; }
.status-Pending { background: #e3f2fd; color: #1565c0; }
.no-task {
color: #888;
font-style: italic;
}
button {
display: block;
width: 100%;
padding: 8px;
margin-top: 8px;
border: 1px solid #ddd;
border-radius: 6px;
background: #fff;
cursor: pointer;
font-size: 12px;
}
button:hover { background: #f5f5f5; }
button:disabled { opacity: 0.5; cursor: not-allowed; }
#error { color: #d32f2f; font-size: 11px; margin-top: 8px; }
</style>
</head>
<body>
<h3>Work Context Tracker</h3>
<div id="content">Loading...</div>
<div id="error"></div>
<script src="popup.js"></script>
</body>
</html>

62
ChromeExtension/popup.js Normal file
View File

@@ -0,0 +1,62 @@
const API_BASE = "http://localhost:5200";
const content = document.getElementById("content");
const errorDiv = document.getElementById("error");
let currentTask = null;
async function loadActiveTask() {
try {
const res = await fetch(`${API_BASE}/api/tasks/active`);
const json = await res.json();
if (!json.success || !json.data) {
content.innerHTML = '<p class="no-task">No active task</p>';
return;
}
currentTask = json.data;
renderTask(currentTask);
} catch (err) {
errorDiv.textContent = "Cannot connect to API";
}
}
function renderTask(task) {
const isActive = task.status === "Active";
const isPaused = task.status === "Paused";
content.innerHTML = `
<div class="task-title">${escapeHtml(task.title)}</div>
<span class="task-status status-${task.status}">${task.status}</span>
${task.category ? `<div style="font-size:11px;color:#666;">Category: ${escapeHtml(task.category)}</div>` : ""}
<button id="toggleBtn">${isActive ? "Pause Task" : "Resume Task"}</button>
`;
document.getElementById("toggleBtn").addEventListener("click", () => toggleTask(task.id, isActive));
}
async function toggleTask(taskId, isCurrentlyActive) {
try {
const action = isCurrentlyActive ? "pause" : "resume";
const res = await fetch(`${API_BASE}/api/tasks/${taskId}/${action}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({})
});
const json = await res.json();
if (json.success && json.data) {
currentTask = json.data;
renderTask(currentTask);
}
} catch (err) {
errorDiv.textContent = "Failed to update task";
}
}
function escapeHtml(text) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
loadActiveTask();