```

College Deadline Tracker

A single-pane-of-glass application workflow for rising high school seniors: college lists, essay prompts, deadlines, calendars, free-time reminders, and a support chatbot—all living inside the browser.

TYPESCRIPT REACT 18 VITE MV3 ZUSTAND CLIENT-SIDE

01 Overview

The College Deadline Tracker is a Manifest V3 browser extension targeting Chrome and Edge, with Firefox compatibility as a stretch goal. It gives rising high school seniors a single-pane-of-glass for managing their college application workflow.

The extension handles:
  • College list building
  • Supplement essay prompts
  • Application deadlines
  • Google Calendar synchronization
  • Smart free-time reminders
  • In-extension AI support
Client-side by design.

Student data lives in chrome.storage.local. There is no proprietary backend server.

External Network Calls

  1. Third-party supplement prompt source
  2. Google Calendar API v3
  3. OpenAI Chat Completions API

Technology Stack

Layer Technology
Language TypeScript
Build Vite + @crxjs/vite-plugin
UI React 18 + CSS Modules
State Zustand
Testing Vitest + fast-check
Formatting ESLint + Prettier

02 Architecture

The extension is divided into four execution contexts that communicate through the Chrome extension messaging system.

POPUP
React SPA
→
SERVICE WORKER
background.ts
→
EXTERNAL APIS
REST / OAuth

Browser Contexts

  • Popup: React interface and user interaction.
  • Service Worker: networking, persistence, authentication, scheduling.
  • Content Script: optional page scraping and DOM extraction.

Storage

All persistent state is stored in:

chrome.storage.local

The service worker does not rely on window, localStorage, or persistent DOM state.

MV3 constraint:

Service workers may be terminated between events. Every operation therefore assumes that the worker may disappear and restart.

03 Communication

The popup never calls external APIs directly. It sends typed messages to the service worker, which performs network I/O and storage operations.

type ExtensionMessage =
```

| { type: 'ADD_COLLEGE'; payload: { name: string } }
| { type: 'REMOVE_COLLEGE'; payload: { id: string } }
| { type: 'ADD_DEADLINE'; payload: {
collegeId: string;
label: string;
date: string;
} }
| { type: 'REMOVE_DEADLINE'; payload: {
deadlineId: string;
} }
| { type: 'EDIT_DEADLINE'; payload: {
deadlineId: string;
label: string;
date: string;
} }
| { type: 'CONNECT_CALENDAR' }
| { type: 'DISCONNECT_CALENDAR' }
| { type: 'ENABLE_FREE_TIME_REMINDERS'; payload: {
deadlineId: string;
} }
| { type: 'DISABLE_FREE_TIME_REMINDERS'; payload: {
deadlineId: string;
} }
| { type: 'RETRY_PROMPT_FETCH'; payload: {
collegeId: string;
} }
| { type: 'SEND_CHAT_MESSAGE'; payload: {
message: string;
sessionHistory: ChatMessage[];
} }
| { type: 'GET_STATE' };
```

04 Module Boundaries

Module Location Responsibility
StorageService src/services/storage.ts Typed chrome.storage wrappers
CollegeListService src/services/collegeList.ts College CRUD + validation
DeadlineService src/services/deadline.ts Deadline CRUD + validation
PromptFetcherService src/services/promptFetcher.ts Prompt fetching + caching
CalendarService src/services/calendar.ts Google Calendar integration
AuthService src/services/auth.ts OAuth lifecycle
ChatbotService src/services/chatbot.ts AI calls + session isolation
MessageRouter src/background/router.ts Message dispatch
Popup src/popup/ React UI

05 Components & Interfaces

Service Worker

chrome.runtime.onInstalled.addListener(initializeStorage);
```

chrome.runtime.onMessage.addListener(
MessageRouter.handle
);

chrome.alarms.onAlarm.addListener(
handleAlarm
);

chrome.alarms.create(
'refreshCommonAppPrompts',
{ periodInMinutes: 1440 }
);
```

StorageService

interface StorageSchema {
```

collegeList: CollegeEntry[];
commonAppPrompts: CommonAppPrompt[];
commonAppPromptsLastUpdated: number;
promptCache: Record;
authState: AuthState | null;
chatOnboardingShown: boolean;
onboardingDismissed: boolean;
}

class StorageService { static async get(
key: K
): Promise;

static async set(
key: K,
value: StorageSchema[K]
): Promise;

static async remove(
key: K
): Promise;

static async clear(): Promise;
}
```

College List

interface AddCollegeResult {
```

success: true;
entry: CollegeEntry;
} | {
success: false;
error:
| 'DUPLICATE'
| 'MAX_REACHED'
| 'EMPTY_NAME'
| 'STORAGE_FAILURE';
}
```

Deadline Service

interface DeadlineValidationError {
```

field: 'label' | 'date';
reason:
| 'EMPTY'
| 'TOO_LONG'
| 'INVALID_DATE';
}
```

Calendar

Calendar synchronization uses exponential backoff:

  • Initial delay: 2 seconds
  • Each retry doubles the delay
  • Maximum delay: 30 seconds
  • Maximum retries: 3

Chatbot

Privacy boundary

Only message text is sent to the AI API. No PII or authentication tokens are included.

Popup Structure

src/popup/
```

├── App.tsx
├── pages/
│   ├── CollegeListPage.tsx
│   ├── DeadlinesPage.tsx
│   ├── EssayPromptsPage.tsx
│   ├── CalendarSettingsPage.tsx
│   └── SupportChatPage.tsx
│
├── components/
│   ├── CollegeSearchBar.tsx
│   ├── CollegeEntryRow.tsx
│   ├── DeadlineItem.tsx
│   ├── DeadlineForm.tsx
│   ├── PromptCard.tsx
│   ├── ChatMessageBubble.tsx
│   ├── LoadingIndicator.tsx
│   ├── ErrorMessage.tsx
│   ├── SyncStatusIcon.tsx
│   ├── NavBar.tsx
│   └── OnboardingPrompt.tsx
│
└── hooks/
├── useStorage.ts
├── useTheme.ts
└── useMessageDispatch.ts
```

06 Data Models

Persistent application state lives entirely within chrome.storage.local.

CollegeEntry

interface CollegeEntry {
```

id: string;
name: string;
addedAt: number;
deadlines: Deadline[];
calendarSyncStatus:
| 'synced'
| 'unsynced'
| 'partial';
}
```

Deadline

interface Deadline {
```

id: string;
collegeId: string;
label: string;
date: string;
calendarEventId: string | null;
freeTimeReminderIds: string[];

syncStatus:
| 'synced'
| 'unsynced'
| 'failed';

createdAt: number;
updatedAt: number;
}
```

Prompt Cache

interface PromptCacheEntry {
```

collegeId: string;
collegeName: string;
prompts: SupplementPrompt[];
fetchedAt: number;

status:
| 'fresh'
| 'stale'
| 'error';
}

interface SupplementPrompt {
id: string;
title: string;
body: string;
wordLimit: number | null;
}
```

ChatMessage

interface ChatMessage {
```

id: string;
role: 'user' | 'assistant';
content: string;
timestamp: number;
}
```
Session isolation

Chat messages are session-scoped and are never persisted to chrome.storage.local.

Storage Layout

chrome.storage.local = {
```

"collegeList": CollegeEntry[],
"commonAppPrompts": CommonAppPrompt[],
"commonAppPromptsLastUpdated": number,
"promptCache": {
[collegeId: string]: PromptCacheEntry
},
"authState": AuthState | null,
"onboardingDismissed": boolean
}
```

Deadline Classification

type DeadlineStatus =
```

| 'passed'
| 'upcoming'
| 'normal';

function classifyDeadline(
dateStr: string,
now: Date = new Date()
): DeadlineStatus {

const deadline = new Date(dateStr);

if (deadline < now)
return 'passed';

const daysUntil =
(deadline.getTime() - now.getTime())
/ (1000 * 60 * 60 * 24);

if (daysUntil <= 14)
return 'upcoming';

return 'normal';
}
```

07 Correctness Properties

These properties define behaviors that should remain true across valid executions of the system and form the foundation of the property-based testing strategy.

PROPERTY 01 // College Addition

Adding a valid, non-duplicate college to a list under the 30-entry limit results in exactly one entry containing that college.

VALIDATES: 1.1, 1.5
PROPERTY 02 // Duplicate Protection

Adding the same college name again, case-insensitively and after trimming, must fail with DUPLICATE.

VALIDATES: 1.6
PROPERTY 03 // Empty Names

Empty or whitespace-only college names must be rejected.

VALIDATES: 1.9
PROPERTY 04 // Maximum Capacity

A list containing exactly 30 entries must reject additional colleges.

VALIDATES: 1.7, 1.8
PROPERTY 05 // Deadline Label Validation

Empty, whitespace-only, or over-50-character labels cannot be persisted.

VALIDATES: 3.5
PROPERTY 06 // Deadline Date Validation

Invalid dates such as 2024-02-30 or non-date strings must be rejected.

VALIDATES: 3.6
PROPERTY 07 // Deadline Classification

Dates before now are passed; dates within 14 days are upcoming; all others are normal.

VALIDATES: 3.3, 3.4
PROPERTY 08 // Alphabetical Ordering

Sorting preserves all entries, orders them by locale-sensitive college name, and is idempotent.

VALIDATES: 1.5
PROPERTY 09 // Deadline Round Trip

A valid deadline written to storage must return with identical label and date values.

VALIDATES: 3.1
PROPERTY 10 // Chronological Deadlines

Deadlines returned for a college are ordered chronologically.

VALIDATES: 3.2
PROPERTY 11 // Free-Time Bounds

Free slots must be at least 60 minutes, occur between 07:00 and 22:00, and never overlap an existing event.

VALIDATES: 6.2
PROPERTY 12 // Reminder Limit

No more than three free-time reminders may be scheduled, distributed across distinct days where possible.

VALIDATES: 6.3
PROPERTY 13 // AI Privacy Boundary

Chatbot requests contain only role/content message data and never include auth tokens, emails, or other identifiers.

VALIDATES: 9.5

08 Error Handling

Storage Failures

Storage writes occur only after validation. In-memory state is never considered committed until the storage operation succeeds.

Prompt Fetch Timeout

  • 5-second timeout
  • Previous data → stale
  • No previous data → error
  • UI provides retry functionality

Calendar Retry Logic

async function fetchWithRetry(
```

url: string,
opts: RequestInit,
attempt = 0
): Promise {

const response = await fetch(url, opts);

if (response.ok)
return response;

if (attempt >= 3)
throw new CalendarSyncError(
'MAX_RETRIES_EXCEEDED'
);

const delay = Math.min(
2000 * 2 ** attempt,
30000
);

await sleep(delay);

return fetchWithRetry(
url,
opts,
attempt + 1
);
}
```

OAuth Expiration

Tokens approaching expiration are refreshed silently. Failed refreshes trigger a re-authentication prompt and abort pending Calendar calls.

Chatbot Failure

Network failures and non-2xx responses produce a typed error response. The UI provides external help resources rather than leaving the user without support.

Widget Timeout

Async popup operations have a 10-second timeout. Previously loaded information remains visible when a new request fails.

09 Testing Strategy

Unit Tests

Vitest tests each service in isolation with Chrome APIs mocked.

Service Focus
CollegeListService Add / remove / duplicate / cap / validation
DeadlineService Validation / classification / sorting
PromptFetcherService Timeout / stale fallback / caching
AuthService Expiration / disconnect
CalendarService Retries / event payloads / free slots
ChatbotService Privacy / prompts / disclaimers

Property-Based Testing

fast-check runs every correctness property with a minimum of 100 generated cases.

// Feature: college-deadline-tracker
```

// Property 7: Deadline classification correctness

fc.assert(
fc.property(
fc.date(),
fc.date(),
(deadline, now) => {
// invariant checks...
}
),
{ numRuns: 100 }
);
```

Integration Tests

  • Add college → fetch prompts → create deadline → calendar sync
  • OAuth happy path
  • Storage cleanup on uninstall

End-to-End Tests

  • Extension opens from toolbar
  • College add/remove is visible
  • 14-day deadline visual state
  • Dark/light theme switching

10 Accessibility

WCAG 2.1 AA target

Interactive elements must meet a minimum 4.5:1 text contrast ratio and 3:1 contrast for UI components.

Automated accessibility checks run through axe-core during Playwright tests.

Full compliance also requires manual testing with screen readers including NVDA and VoiceOver.

```