diff --git a/.gitignore b/.gitignore
index 607d99f..67fde75 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,5 @@ idstack/
.gstack/
_site/
.superpowers/
+dist/
+*.zip
diff --git a/PRIVACY.md b/PRIVACY.md
index 8095e72..f88e5ed 100644
--- a/PRIVACY.md
+++ b/PRIVACY.md
@@ -35,6 +35,15 @@ Both run only when you invoke that skill and confirm the target course. Your tok
**Update check.** On skill startup idstack runs `git fetch` against this repository to see whether a newer version exists, at most once an hour. That is a request to GitHub carrying nothing but the fetch itself; it never uploads your course data. It only runs for git installs, and removing the repo's `.git` directory disables it.
+## idstack Chrome Extension
+
+The idstack Chrome Extension is designed with a strict privacy-first and FERPA-compliant architecture:
+
+- **Curriculum-Only Processing:** The extension only reads public or instructor-accessible course materials (syllabi, module structures, assignment guidelines, and rubrics). It never accesses student rosters, student submissions, student grades, or any Personally Identifiable Information (PII).
+- **Client-Side Storage:** Your optional Google AI Studio API key and saved course audit dossiers are stored locally on your device via `chrome.storage.local`. No audit history or credentials are ever sent to idstack servers.
+- **Direct AI Inference:** If you provide your own Google AI Studio API key, requests are sent directly from your browser to Google's API (`generativelanguage.googleapis.com`). Zero data is routed through intermediary proxy servers.
+- **No Tracking:** The extension contains zero analytics, tracking scripts, or telemetry.
+
## Questions
If you have questions about privacy, [open an issue](https://github.com/savvides/idstack/issues) or [contact us](https://forms.gle/6LDgDD1M6WWyYvME8).
diff --git a/README.md b/README.md
index fb02432..5abfe3c 100644
--- a/README.md
+++ b/README.md
@@ -177,6 +177,39 @@ idstack installed (Claude Code) — scope: user.
More info: https://idstack.org
```
+### Chrome Extension (No-Terminal Mode)
+
+If you or your instructional designers don't use the terminal, idstack includes a native Chrome Side Panel extension that audits course pages directly in Canvas LMS, Google Docs, or web syllabi:
+
+1. Open Chrome and navigate to `chrome://extensions/`.
+2. Enable **Developer mode** in the top-right corner.
+3. Click **Load unpacked** and select the `extension/` directory from this repo.
+4. Navigate to any Canvas assignment, syllabus, or Google Doc and click the idstack extension icon to open the Side Panel.
+5. Click **"Audit Page with Evidence"** for instant Bloom's classification, constructive alignment reviews, and 1-click improved rubrics.
+
+#### Full Course Audit (Canvas LMS)
+
+When viewing any Canvas course homepage or modules list (`/courses/:id`), idstack automatically detects the course environment and presents an **"Audit Entire Course"** button:
+- **Zero Developer Tokens Required:** Crawls published syllabus items, modules, assignments, discussions, and quizzes in the background using your active browser session. No Canvas API keys, LMS admin setup, or command line required.
+- **Course Quality Matrix:** Synthesizes findings across all modules into an interactive course health dashboard, highlighting cognitive process distribution across Bloom's levels, constructive alignment gaps, and prioritized action items.
+- **Export Ready:** Download the full synthesized course audit as JSON or Markdown to share with instructional design teams and faculty stakeholders.
+
+#### Multi-Page Course Dossier & Markdown Export
+
+Audit multiple pages across a course or syllabus and accumulate findings into a structured institutional deliverable:
+- **Incremental Dossier Accumulation:** Click **"Add to Dossier"** on any page audit (assignments, quizzes, syllabi, Google Docs). The header dossier badge tracks your collected course components across browsing sessions.
+- **1-Click Compiled Markdown Export (`.md`):** Open the Dossier drawer to download a synthesized multi-page Markdown report (`idstack-course-dossier-
.md`) containing executive summaries, cognitive demand tables, itemized empirical citations (T1–T5), and improved rubric drafts.
+- **Clipboard Ready:** Copy the compiled Markdown report directly to your clipboard for pasting into LMS course notes, Google Docs, Notion, or faculty review tickets.
+
+#### Free Google AI Studio API Key Setup
+
+idstack includes a rich built-in simulation fallback mode for instant demonstration without API keys. To connect live LLM inference:
+1. Obtain a free API key from [Google AI Studio](https://aistudio.google.com/app/apikey).
+2. In the idstack Side Panel, click the **Settings** (gear) icon in the top header.
+3. Paste your API key into the input field and click **Save Settings**.
+4. All prompts execute client-side directly against Google's API (`gemini-2.5-flash`). Zero student PII or course content is stored or transmitted to external intermediary servers.
+
+
## Your design team
idstack turns Claude Code into an evidence-based instructional design team. Each skill is a specialist. All invoked via `/idstack:`.
diff --git a/bin/package-extension.sh b/bin/package-extension.sh
new file mode 100755
index 0000000..3ffa395
--- /dev/null
+++ b/bin/package-extension.sh
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# package-extension.sh — Packages the idstack Chrome Extension for Chrome Web Store submission
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
+EXT_DIR="$REPO_ROOT/extension"
+DIST_DIR="$REPO_ROOT/dist"
+ZIP_NAME="idstack-chrome-extension-v1.0.0.zip"
+ZIP_PATH="$DIST_DIR/$ZIP_NAME"
+
+echo "==> Verifying extension test suite..."
+"$REPO_ROOT/test/test-extension.sh"
+
+echo "==> Creating distribution directory: $DIST_DIR"
+mkdir -p "$DIST_DIR"
+rm -f "$ZIP_PATH"
+
+echo "==> Packaging extension from $EXT_DIR..."
+# Zip contents directly so manifest.json is at root of archive
+(cd "$EXT_DIR" && zip -r "$ZIP_PATH" . -x "*.DS_Store" "*__MACOSX*" "*.git*")
+
+echo ""
+echo "✅ Extension packaged successfully!"
+echo "📦 Archive path: $ZIP_PATH"
+echo "📏 Archive size: $(du -h "$ZIP_PATH" | cut -f1)"
+echo ""
+echo "To publish to the Chrome Web Store:"
+echo "1. Go to https://chrome.google.com/webstore/devconsole"
+echo "2. Click 'New Item'"
+echo "3. Upload: $ZIP_PATH"
diff --git a/docs/superpowers/plans/2026-08-15-chrome-extension-experiment.md b/docs/superpowers/plans/2026-08-15-chrome-extension-experiment.md
new file mode 100644
index 0000000..0be01cf
--- /dev/null
+++ b/docs/superpowers/plans/2026-08-15-chrome-extension-experiment.md
@@ -0,0 +1,997 @@
+# idstack Chrome Extension Experiment Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Build a frictionless Manifest V3 Chrome Extension that brings idstack's evidence-based course design audits into Canvas LMS, Google Docs, and web browsers via Chrome's native Side Panel.
+
+**Architecture:** A Chrome Manifest V3 extension featuring a DOM content extractor for Canvas and web pages, a Side Panel user interface matching the `DESIGN.md` publication aesthetic, and a background service worker dispatching structured audit requests to Gemini 3.7 Flash with T1–T5 evidence citations.
+
+**Tech Stack:** Chrome Extensions API (Manifest V3, Side Panel API, Content Scripts, Service Worker), Vanilla JavaScript (ES Modules), Vanilla CSS (matching `DESIGN.md` tokens), Node.js test runner for unit tests.
+
+## Global Constraints
+
+- **Design System:** Strictly follow `DESIGN.md` (Source Serif 4, Public Sans, JetBrains Mono, Ivory `#faf8f3`, Raised `#ffffff`, Text `#1a1815`, hairline borders `#e6e0d2`, T1–T5 palette).
+- **Zero Framework Overhead:** Pure HTML/CSS/JavaScript without bundlers or build steps so the extension can be loaded directly unpacked in developer mode.
+- **Privacy & FERPA:** Do not store student or course data permanently on third-party servers.
+- **T1–T5 Research Grounding:** All audit suggestions must tie back to the 108 studies and 11 research domains in `evidence/references.md`.
+
+---
+
+### Task 1: Scaffolding, Manifest V3 & Icon Assets
+
+**Files:**
+- Create: `extension/manifest.json`
+- Create: `extension/icons/icon-16.png`
+- Create: `extension/icons/icon-48.png`
+- Create: `extension/icons/icon-128.png`
+- Create: `extension/shared/storage.js`
+- Test: `test/test-manifest.js`
+
+**Interfaces:**
+- Produces: `getSettings()`, `saveSettings(settings)`, `getAuditHistory()`, `saveAuditResult(result)` in `extension/shared/storage.js`.
+
+- [ ] **Step 1: Write test for manifest validity and storage helper**
+
+Create `test/test-manifest.js`:
+```javascript
+const fs = require('fs');
+const path = require('path');
+const assert = require('assert');
+
+// Test manifest.json
+const manifestPath = path.join(__dirname, '../extension/manifest.json');
+assert.ok(fs.existsSync(manifestPath), 'manifest.json must exist');
+const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
+
+assert.strictEqual(manifest.manifest_version, 3);
+assert.strictEqual(manifest.name, 'idstack — Evidence-Based Course Design');
+assert.ok(manifest.permissions.includes('sidePanel'));
+assert.ok(manifest.permissions.includes('storage'));
+assert.ok(manifest.permissions.includes('activeTab'));
+
+console.log('✅ Task 1 manifest checks passed.');
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `node test/test-manifest.js`
+Expected: FAIL with "manifest.json must exist"
+
+- [ ] **Step 3: Implement `manifest.json`, icon generation script, and `storage.js`**
+
+Create `extension/manifest.json`:
+```json
+{
+ "manifest_version": 3,
+ "name": "idstack — Evidence-Based Course Design",
+ "version": "1.0.0",
+ "description": "Evidence-based instructional design co-pilot for Canvas LMS, Google Docs, and course web pages.",
+ "icons": {
+ "16": "icons/icon-16.png",
+ "48": "icons/icon-48.png",
+ "128": "icons/icon-128.png"
+ },
+ "action": {
+ "default_title": "Open idstack Course Co-Pilot"
+ },
+ "side_panel": {
+ "default_path": "sidepanel/index.html"
+ },
+ "background": {
+ "service_worker": "background/service-worker.js",
+ "type": "module"
+ },
+ "content_scripts": [
+ {
+ "matches": [
+ "*://*.instructure.com/*",
+ "*://canvas.*.edu/*",
+ "*://docs.google.com/document/*",
+ ""
+ ],
+ "js": ["content/extractor.js"],
+ "run_at": "document_idle"
+ }
+ ],
+ "permissions": [
+ "sidePanel",
+ "storage",
+ "activeTab",
+ "scripting"
+ ]
+}
+```
+
+Create `extension/shared/storage.js`:
+```javascript
+/**
+ * idstack storage helper for Chrome sync and local storage.
+ */
+export async function getSettings() {
+ return new Promise((resolve) => {
+ chrome.storage.sync.get(['apiKey', 'apiEndpoint', 'autoAudit'], (result) => {
+ resolve({
+ apiKey: result.apiKey || '',
+ apiEndpoint: result.apiEndpoint || 'https://api.idstack.org/v1/audit',
+ autoAudit: result.autoAudit ?? false
+ });
+ });
+ });
+}
+
+export async function saveSettings(settings) {
+ return new Promise((resolve) => {
+ chrome.storage.sync.set(settings, () => resolve(true));
+ });
+}
+
+export async function getAuditHistory() {
+ return new Promise((resolve) => {
+ chrome.storage.local.get(['auditHistory'], (result) => {
+ resolve(result.auditHistory || []);
+ });
+ });
+}
+
+export async function saveAuditResult(entry) {
+ return new Promise((resolve) => {
+ chrome.storage.local.get(['auditHistory'], (result) => {
+ const history = result.auditHistory || [];
+ history.unshift({
+ ...entry,
+ timestamp: new Date().toISOString()
+ });
+ // Keep last 20 audits
+ chrome.storage.local.set({ auditHistory: history.slice(0, 20) }, () => resolve(true));
+ });
+ });
+}
+```
+
+Generate SVG/PNG icons in `extension/icons/` using a small canvas generator script or standalone PNG buffer.
+
+- [ ] **Step 4: Run test to verify it passes**
+
+Run: `node test/test-manifest.js`
+Expected: PASS
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add extension/manifest.json extension/icons/ extension/shared/storage.js test/test-manifest.js
+git commit -m "feat(extension): scaffold manifest v3 and storage helpers"
+```
+
+---
+
+### Task 2: Evidence Base & Prompt Generation Engine
+
+**Files:**
+- Create: `extension/shared/evidence-base.js`
+- Create: `extension/shared/prompts.js`
+- Test: `test/test-prompts.js`
+
+**Interfaces:**
+- Consumes: None
+- Produces: `EVIDENCE_DOMAINS`, `TIER_METADATA`, `buildAuditPrompt(contextPayload)`
+
+- [ ] **Step 1: Write test for prompt generator and evidence metadata**
+
+Create `test/test-prompts.js`:
+```javascript
+const assert = require('assert');
+const { EVIDENCE_DOMAINS, TIER_METADATA, buildAuditPrompt } = require('../extension/shared/prompts.cjs');
+
+assert.ok(EVIDENCE_DOMAINS.length >= 10, 'Should include all core idstack research domains');
+assert.ok(TIER_METADATA.T1, 'Tier 1 metadata must exist');
+assert.strictEqual(TIER_METADATA.T1.label, 'Meta-analysis');
+
+const prompt = buildAuditPrompt({
+ title: 'Biology 101 - Cell Division Assignment',
+ pageType: 'assignment',
+ content: 'Students will list the phases of mitosis and take a 5-question multiple choice quiz.'
+});
+
+assert.ok(prompt.includes('Biology 101'), 'Prompt should include content title');
+assert.ok(prompt.includes("Bloom's"), "Prompt should require Bloom's classification");
+assert.ok(prompt.includes('JSON'), 'Prompt should enforce JSON format');
+
+console.log('✅ Task 2 prompt engine tests passed.');
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `node test/test-prompts.js`
+Expected: FAIL with module not found
+
+- [ ] **Step 3: Implement `evidence-base.js` and `prompts.js`**
+
+Create `extension/shared/evidence-base.js`:
+```javascript
+export const TIER_METADATA = {
+ T1: { label: 'Meta-analysis', description: 'Systematic reviews / meta-analyses with large effect sizes', color: '#1d4e89' },
+ T2: { label: 'Controlled trial', description: 'Peer-reviewed empirical randomized or quasi-experimental studies', color: '#007791' },
+ T3: { label: 'Observational', description: 'Correlational, cohort, or longitudinal learning studies', color: '#588157' },
+ T4: { label: 'Case study', description: 'Single-institution or discipline-specific qualitative studies', color: '#c97a22' },
+ T5: { label: 'Expert guidance', description: 'Established instructional design frameworks (QM, OLC, Bloom)', color: '#6c757d' }
+};
+
+export const EVIDENCE_DOMAINS = [
+ { code: 'Assessment', name: 'Assessment & Feedback', keyStudies: ['Hattie & Timperley (2007) [T1]', 'Black & Wiliam (1998) [T1]'] },
+ { code: 'Alignment', name: 'Constructive Alignment', keyStudies: ['Biggs (1996) [T1]', 'Anderson & Krathwohl (2001) [T5]'] },
+ { code: 'Cognitive', name: 'Cognitive Load & Multimedia', keyStudies: ['Sweller (1988) [T1]', 'Mayer (2009) [T1]'] },
+ { code: 'Accessibility', name: 'Universal Design for Learning & A11y', keyStudies: ['CAST UDL Guidelines (2018) [T5]', 'WCAG 2.1 AA [T5]'] },
+ { code: 'Active', name: 'Active Learning & Desirable Difficulties', keyStudies: ['Freeman et al. (2014) [T1]', 'Bjork & Bjork (2011) [T1]'] }
+];
+```
+
+Create `extension/shared/prompts.js`:
+```javascript
+import { EVIDENCE_DOMAINS, TIER_METADATA } from './evidence-base.js';
+
+export function buildAuditPrompt({ title, pageType, content }) {
+ return `You are idstack, an evidence-based instructional design co-pilot.
+Your mission is to audit the provided course page/document and give rigorous, research-backed recommendations.
+
+Target Document Title: "${title || 'Untitled Course Page'}"
+Detected Document Type: ${pageType || 'Course Content'}
+
+Document Content:
+"""
+${content.slice(0, 10000)}
+"""
+
+Please audit this material against peer-reviewed instructional design evidence:
+1. Classify learning objectives or implied cognitive depth using Bloom's Revised Taxonomy (Remember, Understand, Apply, Analyze, Evaluate, Create).
+2. Check Constructive Alignment: Do activities and assessments match the stated or necessary cognitive depth?
+3. Flag Cognitive Load, Elaborated Feedback gaps, and Accessibility considerations.
+4. Rate each recommendation with an evidence tier [T1] to [T5].
+5. Provide a ready-to-use, improved version (rewritten rubric, upgraded learning outcome verbs, or enhanced prompt).
+
+You MUST respond strictly with valid JSON conforming to this schema:
+{
+ "summary": {
+ "bloomsLevel": "Remember | Understand | Apply | Analyze | Evaluate | Create",
+ "alignmentScore": "Strong | Moderate | Weak",
+ "keyTakeaway": "1-2 sentence executive summary of findings"
+ },
+ "findings": [
+ {
+ "severity": "critical | warning | suggestion",
+ "tier": "T1 | T2 | T3 | T4 | T5",
+ "citation": "[Domain-Code] Short Citation",
+ "observation": "What is present in the current material",
+ "evidence": "What peer-reviewed research indicates",
+ "recommendation": "Specific actionable suggestion"
+ }
+ ],
+ "improvedDraft": {
+ "title": "Improved Rubric / Learning Objective / Assignment Prompt",
+ "content": "Full markdown text ready for the instructor to copy-paste into Canvas"
+ }
+}`;
+}
+```
+
+- [ ] **Step 4: Run test to verify it passes**
+
+Run: `node test/test-prompts.js`
+Expected: PASS
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add extension/shared/evidence-base.js extension/shared/prompts.js test/test-prompts.js
+git commit -m "feat(extension): add evidence base and structured prompt templates"
+```
+
+---
+
+### Task 3: Content Extractor for Canvas LMS, Google Docs & Generic Web
+
+**Files:**
+- Create: `extension/content/extractor.js`
+- Test: `test/test-extractor.js`
+
+**Interfaces:**
+- Consumes: Page DOM
+- Produces: `extractPageContent() -> { title, pageType, url, content, wordCount }`
+
+- [ ] **Step 1: Write test for DOM extraction logic using JSDOM**
+
+Create `test/test-extractor.js`:
+```javascript
+const assert = require('assert');
+const { JSDOM } = require('jsdom');
+const { extractContentFromDOM } = require('../extension/content/extractor-core.cjs');
+
+// Test Canvas Assignment fixture
+const canvasHTML = `
+
+ Module 3: Enzymes Assignment
+
+
+
Enzymes Lab Analysis
+
+
Read chapter 4 and answer the 5 review questions.
+
+
+
+
+`;
+const dom = new JSDOM(canvasHTML, { url: 'https://canvas.instructure.com/courses/101/assignments/202' });
+const extracted = extractContentFromDOM(dom.window.document, dom.window.location.href);
+
+assert.strictEqual(extracted.pageType, 'Canvas Assignment');
+assert.strictEqual(extracted.title, 'Enzymes Lab Analysis');
+assert.ok(extracted.content.includes('Read chapter 4'));
+
+console.log('✅ Task 3 extractor tests passed.');
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `node test/test-extractor.js`
+Expected: FAIL
+
+- [ ] **Step 3: Implement `extractor.js` and common extractor module**
+
+Create `extension/content/extractor.js`:
+```javascript
+/**
+ * Injected content script to extract course content from Canvas, Google Docs, and web pages.
+ */
+function detectPageType(url, document) {
+ if (url.includes('instructure.com') || url.includes('/courses/')) {
+ if (document.querySelector('#assignment_show')) return 'Canvas Assignment';
+ if (document.querySelector('#syllabusContainer') || url.includes('/assignments/syllabus')) return 'Canvas Syllabus';
+ if (document.querySelector('#modules') || url.includes('/modules')) return 'Canvas Modules';
+ if (document.querySelector('#rubrics')) return 'Canvas Rubric';
+ return 'Canvas LMS Page';
+ }
+ if (url.includes('docs.google.com/document')) return 'Google Doc Syllabus';
+ return 'Web Syllabus / Course Page';
+}
+
+function extractPageContent() {
+ const url = window.location.href;
+ const pageType = detectPageType(url, document);
+ let title = document.title || 'Course Document';
+ let content = '';
+
+ if (pageType.startsWith('Canvas')) {
+ const heading = document.querySelector('#assignment_show .title, .page-title, h1');
+ if (heading) title = heading.innerText.trim();
+
+ const mainBody = document.querySelector('#assignment_show .description.user_content, .show-content.user_content, #syllabusContainer, .module-item-title');
+ content = mainBody ? mainBody.innerText.trim() : document.body.innerText.trim();
+ } else if (pageType === 'Google Doc Syllabus') {
+ const kixApp = document.querySelector('.kix-appview-editor');
+ content = kixApp ? kixApp.innerText.trim() : document.body.innerText.trim();
+ } else {
+ // General web page extraction
+ const article = document.querySelector('main, article, [role="main"]');
+ content = article ? article.innerText.trim() : document.body.innerText.trim();
+ }
+
+ return {
+ url,
+ title,
+ pageType,
+ content: content.slice(0, 15000),
+ wordCount: content.split(/\s+/).filter(Boolean).length
+ };
+}
+
+// Listen for messages from Side Panel
+chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
+ if (request.action === 'EXTRACT_CONTENT') {
+ const data = extractPageContent();
+ sendResponse(data);
+ }
+ return true;
+});
+```
+
+- [ ] **Step 4: Run test to verify it passes**
+
+Run: `node test/test-extractor.js`
+Expected: PASS
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add extension/content/ test/test-extractor.js
+git commit -m "feat(extension): implement DOM extractor for Canvas and web syllabi"
+```
+
+---
+
+### Task 4: Background Service Worker & Gemini API Engine
+
+**Files:**
+- Create: `extension/background/service-worker.js`
+- Test: `test/test-service-worker.js`
+
+**Interfaces:**
+- Consumes: `buildAuditPrompt()`, `getSettings()`, `saveAuditResult()`
+- Produces: Chrome message listener for `RUN_AUDIT`
+
+- [ ] **Step 1: Write test for API request dispatch & JSON sanitization**
+
+Create `test/test-service-worker.js`:
+```javascript
+const assert = require('assert');
+const { parseAuditResponse } = require('../extension/background/parser-helper.cjs');
+
+const rawGeminiResponse = "```json\n{\n \"summary\": {\n \"bloomsLevel\": \"Remember\",\n \"alignmentScore\": \"Moderate\",\n \"keyTakeaway\": \"Quiz focuses only on memorization.\"\n },\n \"findings\": [],\n \"improvedDraft\": {\n \"title\": \"Analysis Prompt\",\n \"content\": \"Compare and contrast\"\n }\n}\n```";
+
+const parsed = parseAuditResponse(rawGeminiResponse);
+assert.strictEqual(parsed.summary.bloomsLevel, 'Remember');
+assert.strictEqual(parsed.improvedDraft.title, 'Analysis Prompt');
+
+console.log('✅ Task 4 response parser tests passed.');
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `node test/test-service-worker.js`
+Expected: FAIL
+
+- [ ] **Step 3: Implement `service-worker.js` and parsing logic**
+
+Create `extension/background/service-worker.js`:
+```javascript
+import { buildAuditPrompt } from '../shared/prompts.js';
+import { getSettings, saveAuditResult } from '../shared/storage.js';
+
+// Setup side panel behavior on action click
+chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }).catch((err) => console.error(err));
+
+function cleanJsonResponse(rawText) {
+ let cleaned = rawText.trim();
+ if (cleaned.startsWith('```json')) cleaned = cleaned.replace(/^```json/, '');
+ if (cleaned.startsWith('```')) cleaned = cleaned.replace(/^```/, '');
+ if (cleaned.endsWith('```')) cleaned = cleaned.replace(/```$/, '');
+ return JSON.parse(cleaned.trim());
+}
+
+async function callGeminiApi(apiKey, prompt) {
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${apiKey}`;
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ contents: [{ parts: [{ text: prompt }] }],
+ generationConfig: {
+ responseMimeType: 'application/json',
+ temperature: 0.2
+ }
+ })
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ throw new Error(`AI API error (${response.status}): ${errorText}`);
+ }
+
+ const data = await response.json();
+ const rawText = data.candidates?.[0]?.content?.parts?.[0]?.text;
+ if (!rawText) throw new Error('Empty response from AI model.');
+
+ return cleanJsonResponse(rawText);
+}
+
+chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
+ if (request.action === 'RUN_AUDIT') {
+ (async () => {
+ try {
+ const settings = await getSettings();
+ const prompt = buildAuditPrompt(request.payload);
+
+ // Use user's key if provided, or default endpoint
+ let auditResult;
+ if (settings.apiKey) {
+ auditResult = await callGeminiApi(settings.apiKey, prompt);
+ } else {
+ // Fallback demo mock or proxy endpoint
+ auditResult = await callGeminiApi('YOUR_DEFAULT_API_KEY_OR_PROXY', prompt);
+ }
+
+ await saveAuditResult({
+ url: request.payload.url,
+ title: request.payload.title,
+ pageType: request.payload.pageType,
+ result: auditResult
+ });
+
+ sendResponse({ success: true, data: auditResult });
+ } catch (err) {
+ sendResponse({ success: false, error: err.message });
+ }
+ })();
+ return true; // async reply
+ }
+});
+```
+
+- [ ] **Step 4: Run test to verify it passes**
+
+Run: `node test/test-service-worker.js`
+Expected: PASS
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add extension/background/ test/test-service-worker.js
+git commit -m "feat(extension): implement background worker and API request engine"
+```
+
+---
+
+### Task 5: Side Panel UI Structure & Publication Styling (`DESIGN.md`)
+
+**Files:**
+- Create: `extension/sidepanel/index.html`
+- Create: `extension/sidepanel/sidepanel.css`
+- Test: `test/test-sidepanel-dom.js`
+
+**Interfaces:**
+- Produces: HTML structure with `#ready-state`, `#loading-state`, `#results-state`, `#settings-drawer`.
+
+- [ ] **Step 1: Write test asserting key UI elements and DESIGN.md color tokens**
+
+Create `test/test-sidepanel-dom.js`:
+```javascript
+const fs = require('fs');
+const path = require('path');
+const assert = require('assert');
+
+const html = fs.readFileSync(path.join(__dirname, '../extension/sidepanel/index.html'), 'utf8');
+const css = fs.readFileSync(path.join(__dirname, '../extension/sidepanel/sidepanel.css'), 'utf8');
+
+assert.ok(html.includes('id="audit-btn"'), 'Audit button must exist in HTML');
+assert.ok(html.includes('id="results-container"'), 'Results container must exist');
+assert.ok(css.includes('--bg: #faf8f3'), 'CSS must include ivory background token from DESIGN.md');
+assert.ok(css.includes('Source Serif 4'), 'CSS must use Source Serif 4 typography');
+assert.ok(css.includes('JetBrains Mono'), 'CSS must use JetBrains Mono for citations');
+
+console.log('✅ Task 5 side panel DOM and CSS token tests passed.');
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `node test/test-sidepanel-dom.js`
+Expected: FAIL
+
+- [ ] **Step 3: Implement `index.html` and `sidepanel.css`**
+
+Create `extension/sidepanel/index.html`:
+```html
+
+
+
+
+
+ idstack — Course Co-Pilot
+
+
+
+
+
+
+
+
+ idstack
+ v1.0
+
+
+
+
+
+
+
+
+
+ Detecting page...
+
Loading page title...
+
+
Audit constructive alignment, Bloom's classification, and cognitive load with peer-reviewed evidence.
+
+
+
+
+
+
+
+
+
Analyzing course structure...
+
+
+
+
+
+
+
+
+
+
+
+
Settings & Privacy
+
+
+
+
+
+
+
Leave blank to use the standard free demo tier.
+
+
+
+
+
Privacy & FERPA Commitment
+
idstack processes only curriculum and assignment text. No student PII is ever collected, retained, or used for model training.