# FlightDeck Architecture Diagrams
Visual architecture documentation for FlightDeck โ a personal work-intelligence dashboard that connects to Microsoft 365 via WorkIQ and Microsoft Copilot.
---
## 1. System Architecture
How FlightDeck's Electron app, WorkIQ CLI, Microsoft Copilot, and Microsoft 365 connect end-to-end.
```mermaid
graph TB
subgraph User["๐ค User"]
UI["FlightDeck Desktop App"]
end
subgraph Electron["Electron Application"]
direction TB
subgraph Renderer["Renderer Process ยท Browser"]
direction LR
App["app.js
Tab routing & init"]
Events["events.js
DOM event wiring"]
Monitor["monitor-engine.js
30s tick scheduler"]
ScannerEng["scanner-engine.js
Multi-scanner scheduler"]
Demo["demo.js
Fixture mode (?demo=1)"]
Prompts["prompts.js
Prompt editor & cache"]
Parser["json-parser.js
LLM output โ JSON"]
State["state.js
electron-store persistence"]
Utils["utils.js
escapeHtml, URL safety"]
subgraph Models["Models"]
Item["item.js
Unified item model"]
Scanner["scanner.js
Scanner definitions"]
Radar["radar.js"]
Tracking["tracking.js"]
Briefing["briefing.js"]
end
subgraph Views["Renderers"]
KPI["kpi.js"]
RadarR["radar.js"]
TrackingR["tracking.js"]
BriefingR["briefing.js"]
ScannerR["scanner.js"]
ActionsR["actions.js"]
HistoryR["history.js"]
end
end
subgraph Shared["Shared"]
Contract["ipc-contract.js
Channel name constants"]
end
Preload["preload.js
contextBridge โ window.workiq
(20 methods)"]
subgraph Main["Main Process ยท Node.js"]
direction LR
Index["index.js
App lifecycle & tray"]
IPC["ipc-handlers.js
IPC channel routing"]
PTY["pty-bridge.js
node-pty spawn"]
Store["store.js
electron-store
(data + cold)"]
PopoutIPC["ipc/tracker-popout.js
Pop-out window mgmt"]
PromptFiles["prompts/
radar-scan.md
briefing.md
day-briefing.md
scanner-template.md"]
end
end
subgraph External["External Services"]
WorkIQ["WorkIQ CLI
workiq.exe"]
Copilot["Microsoft Copilot
AI Engine"]
M365["Microsoft 365
Email ยท Teams ยท Calendar ยท Docs"]
end
UI --> Renderer
Renderer <-->|"IPC invoke/send"| Preload
Preload <-->|"ipcRenderer โ ipcMain"| Main
Shared --- Preload
Shared --- Main
IPC -->|"read prompt files"| PromptFiles
IPC -->|"spawn PTY"| PTY
IPC -->|"read/write"| Store
IPC -->|"pop-out"| PopoutIPC
PTY -->|"workiq ask -q 'prompt'"| WorkIQ
WorkIQ -->|"Copilot API"| Copilot
Copilot <-->|"Graph API"| M365
WorkIQ -->|"JSON + natural language"| PTY
PTY -->|"ANSI-stripped output"| IPC
```
**Key architecture decisions:**
- **Electron with context isolation** โ The renderer is sandboxed. All Node.js access goes through `preload.js`, which exposes exactly 20 whitelisted methods via `window.workiq`.
- **Shared IPC contract** โ All channel name strings are defined once in `shared/ipc-contract.js` and imported by both the main process and preload script, eliminating string duplication.
- **electron-store for persistence** โ State is backed by a JSON file on disk (`electron-store`) rather than `localStorage`. A separate cold store holds archived items to keep the active store lean.
- **PTY bridge, not HTTP** โ WorkIQ is a CLI tool, not an API. FlightDeck uses `node-pty` to spawn pseudo-terminal sessions, which handles interactive prompts (like EULA acceptance) and streaming output.
- **Multi-scanner architecture** โ Users can create multiple named scanners, each with independent prompts, schedules, and configurations. A dedicated scanner engine runs alongside the single-item monitor engine.
- **No framework, no bundler** โ The renderer is vanilla HTML/CSS/JS. This keeps the build chain minimal and the codebase auditable.
---
## 2. WorkIQ Call Pipeline
The complete data flow when FlightDeck sends a prompt to WorkIQ and renders the result.
```mermaid
sequenceDiagram
actor User
participant R as Renderer
(Browser)
participant P as Preload
(contextBridge)
participant M as Main Process
(ipc-handlers)
participant PTY as PTY Bridge
(node-pty)
participant WIQ as WorkIQ CLI
participant AI as Microsoft Copilot
participant M365 as Microsoft 365
User->>R: Click "Refresh" or scheduled tick fires
R->>R: Load prompt template
(radar-scan.md / briefing.md)
R->>R: Append JSON schema suffix
(constants.js)
R->>P: window.workiq.ask(prompt)
P->>M: ipcRenderer.invoke('ask-workiq', prompt)
M->>PTY: runWorkiqCommand(prompt)
PTY->>PTY: Resolve workiq.exe path
(bundled โ global โ JS fallback)
PTY->>WIQ: pty.spawn(workiq, ['ask', '-q', prompt])
Note over PTY,WIQ: 5-minute timeout guard
WIQ->>AI: Forward prompt to Copilot
AI->>M365: Query Graph API
(mail, chats, calendar, files)
M365-->>AI: M365 signals & data
AI-->>WIQ: Grounded AI response
(JSON + citations)
WIQ-->>PTY: Raw CLI output
(with ANSI codes)
PTY->>PTY: Strip ANSI escapes
Filter prompt lines
PTY-->>M: {success: true, answer: cleanedOutput}
M-->>P: IPC response
P-->>R: Promise resolves
R->>R: json-parser.js:
Extract JSON from fenced blocks
R->>R: models/: Normalize payload,
compute KPIs, detect changes
R->>R: renderers/: Update DOM
(cards, KPIs, badges)
R->>R: state.js: Persist to localStorage
R-->>User: Updated dashboard
```
**What makes this interesting:**
- **Prompt engineering is the product** โ Each feature (Radar, Briefings, Tracking) is driven by a carefully crafted markdown prompt that instructs Copilot on what to look for, how to classify it, and what JSON schema to return.
- **In-app prompt editor** โ Users can customize the radar and briefing prompts directly in the app. FlightDeck persists customizations to localStorage and falls back to the bundled defaults.
- **Robust JSON extraction** โ LLM output isn't pure JSON. The parser handles fenced code blocks, mixed text, Unicode artifacts, trailing commas, and ANSI contamination.
---
## 3. Feature Modes & Prompt Flow
How FlightDeck's three core features connect to prompt templates and M365 data.
```mermaid
flowchart LR
subgraph Prompts["Prompt Templates"]
RS["radar-scan.md
Scan M365 signals,
classify by urgency"]
ST["scanner-template.md
Scanner default prompt
with signal focus"]
BF["briefing.md
Meeting prep with
talk track & risks"]
DB["day-briefing.md
Morning summary
of full workday"]
end
subgraph Features["FlightDeck Features"]
direction TB
ScanF["๐ Scanners
Named scan definitions
Custom prompts + schedules"]
RadarF["๐ก Radar
Inbound signal scan
Critical ยท Elevated ยท Observe"]
TrackF["๐ Tracking
Monitor items over time
Interval ยท Weekly ยท One-time"]
BriefF["๐ Briefings
AI meeting prep
+ My Day overview"]
end
subgraph DataSources["Microsoft 365 Signals via WorkIQ + Copilot"]
Email["๐ง Email"]
Teams["๐ฌ Teams Chats"]
Calendar["๐
Calendar"]
Docs["๐ Documents"]
end
RS --> RadarF
ST --> ScanF
BF --> BriefF
DB --> BriefF
RS -.->|"Custom prompt
via in-app editor"| RadarF
ST -.->|"User-defined
scanner prompt"| ScanF
BF -.->|"Custom prompt
via in-app editor"| BriefF
DataSources -->|"Graph API โ Copilot โ WorkIQ"| Prompts
ScanF -->|"Discovered items"| RadarF
ScanF -->|"Auto-monitor
(severity threshold)"| TrackF
RadarF -->|"Track Item"| TrackF
RadarF -->|"Open Source Link"| Email
RadarF -->|"Open Source Link"| Teams
TrackF -->|"Desktop notification ๐
on substantive change"| User([๐ค User])
BriefF -->|"Talk track + follow-ups"| User
```
| Feature | Prompt | What it does |
|---------|--------|--------------|
| **Scanners** | `scanner-template.md` (default) or user-defined | Named scan definitions with independent prompts and schedules. Each scanner discovers items, deduplicates across scanners, and can auto-monitor new items based on severity thresholds. |
| **Radar** | `radar-scan.md` | Scans email, Teams, calendar, and documents for signals that need attention. Classifies each as Critical, Elevated, or Observe. Returns evidence links with deep URLs back to the source. |
| **Tracking** | Dynamic per-item | Monitors a specific item on a user-configured schedule. Includes the last 2 update summaries for de-duplication so the LLM only reports *new* information. |
| **Briefings** | `briefing.md` / `day-briefing.md` | Generates meeting prep (key updates, decisions needed, risks, talk track, follow-ups) or a full "My Day" morning briefing. |
---
## 4. Monitoring Engine โ Scheduled Task Update Cycle
The background engine that keeps tracked items up to date.
```mermaid
flowchart TD
Start([Monitor Engine Tick
every 30 seconds]) --> Check{Any tracked items
due now?}
Check -->|No| Sleep([Wait for next tick])
Check -->|Yes| BuildPrompt[Build prompt with:
โข Item context & title
โข Last 2 update summaries
โข De-duplication instructions]
BuildPrompt --> CallWorkIQ[window.workiq.ask โ prompt โ
IPC โ PTY โ WorkIQ CLI]
CallWorkIQ --> Parse[json-parser.js:
Extract JSON from response]
Parse --> HasNew{LLM says
hasNewInfo?}
HasNew -->|No / false| Silent[Silent log
Preserve existing fields
Update lastRunAt only]
HasNew -->|Yes| UpdateFields[Update item fields:
summary, status, severity,
owner, evidence links]
UpdateFields --> ComputeSig[Compute field-level
signature hash]
ComputeSig --> SigMatch{Signature changed
substantively?}
SigMatch -->|No| LogOnly[Log link-only or
cosmetic change]
SigMatch -->|Yes| Notify[Desktop notification ๐
+ Update badge
+ History entry]
Silent --> Schedule
LogOnly --> Schedule
Notify --> Schedule
Schedule[Compute next run time
based on schedule type] --> Persist[Save to localStorage
Broadcast state-changed
to pop-out windows]
Persist --> Render[Re-render tracking cards]
Render --> NextItem{More due items?}
NextItem -->|Yes| BuildPrompt
NextItem -->|No| Sleep
```
**How change detection works:**
1. Each tracked item has a **signature hash** computed from its status, severity, summary, and evidence links.
2. After the LLM returns an update, FlightDeck computes a new signature and compares it to the previous one.
3. Only **substantive changes** (status, severity, or meaningful summary differences) trigger desktop notifications. Link-only or cosmetic changes are logged silently.
4. The LLM's `hasNewInfo` flag provides a first-pass filter โ if the LLM says nothing changed, FlightDeck preserves all existing fields to prevent signature drift from rephrasing.
---
## 5. Security Model
How FlightDeck isolates the renderer from Node.js and validates external content.
```mermaid
graph TB
subgraph Security["Security Boundary"]
direction TB
subgraph RendererSandbox["Renderer Sandbox"]
CSP["CSP: default-src 'self'
No inline scripts
No external resources"]
NoNode["nodeIntegration: false
contextIsolation: true"]
Escape["All LLM output HTML-escaped
before DOM insertion"]
end
Bridge["contextBridge ยท preload.js
20 whitelisted methods"]
subgraph MainTrust["Main Process ยท Trusted"]
URLCheck["URL validation
HTTPS only"]
NavGuard["External navigation
intercepted โ system browser"]
PTYIsolation["PTY process isolation
5-min timeout kill"]
end
end
RendererSandbox <-->|"window.workiq.*
20 whitelisted methods"| Bridge
Bridge <-->|"Named IPC channels"| MainTrust
```
| Layer | Measure |
|-------|---------|
| **Content Security Policy** | `default-src 'self'; style-src 'self'; script-src 'self'` โ no inline scripts, no external resources |
| **Context isolation** | Renderer cannot access Node.js APIs directly |
| **Node integration** | Explicitly disabled |
| **IPC surface** | 20 named channels exposed through `preload.js`, defined centrally in `shared/ipc-contract.js` |
| **External navigation** | All navigation attempts intercepted and opened in the system browser, never in the Electron window |
| **URL validation** | Non-HTTPS schemes rejected before opening; generic M365 root URLs filtered out |
| **LLM output sanitization** | All AI-generated text is HTML-escaped before DOM insertion to prevent injection |
| **PTY timeout** | 5-minute hard timeout kills hung WorkIQ processes |
---
## 6. Responsible AI (RAI) Notes
| Concern | Mitigation |
|---------|------------|
| **Data access scope** | FlightDeck accesses only the signed-in user's own M365 data via WorkIQ + Microsoft Copilot. No cross-tenant or cross-user data access. Requires tenant admin consent. |
| **AI grounding** | All Copilot responses are grounded in the user's real M365 signals (email, Teams, calendar, documents). FlightDeck prompts explicitly request citations and evidence links back to source content. |
| **Hallucination mitigation** | JSON schema constraints in prompts enforce structured output. The parser validates response structure before rendering. Evidence links are validated against known URL patterns (Outlook, Teams, SharePoint). |
| **No data storage beyond device** | All user data is stored locally in `localStorage` and a window-state JSON file. No data is sent to external servers beyond the existing WorkIQ โ Copilot โ Graph API path. |
| **Prompt transparency** | Users can view and edit the exact prompts sent to Copilot via the in-app prompt editor. No hidden instructions. |
| **LLM output sanitization** | All AI-generated text is HTML-escaped before rendering. No raw HTML or script content from AI responses reaches the DOM. |
| **EULA and consent** | WorkIQ requires explicit EULA acceptance. FlightDeck auto-detects when the EULA needs re-acceptance and surfaces the "Enable WorkIQ" flow. |