Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
53fe237
refactor: E2E infrastructure — delete old specs, rewrite helpers for …
ParkerES Feb 20, 2026
5545933
test: add auth flow E2E tests (01-auth.spec.ts)
ParkerES Feb 20, 2026
6dd5809
test: add dashboard deep E2E tests — 04-dashboard.spec.ts
ParkerES Feb 20, 2026
c2a161f
test: add Alerts + Communications E2E tests (09-alerts-comms.spec.ts)
ParkerES Feb 20, 2026
4a9d218
test: add navigation sweep and sidebar mechanics E2E specs
ParkerES Feb 20, 2026
a4e4382
test: add E2E specs for Briefing, My Work, and Notes pages
ParkerES Feb 20, 2026
51a4ca1
test: add global overlays + keyboard shortcuts E2E spec
ParkerES Feb 20, 2026
f2c2241
test: add Settings page E2E full sweep — 12-settings-full.spec.ts
ParkerES Feb 20, 2026
5e49406
test: add E2E tests for Fitness, Planner, and Productivity pages
ParkerES Feb 20, 2026
7af3054
test: add project management E2E spec (10-project-management)
ParkerES Feb 20, 2026
32599f4
Merge work/e2e-suite/auth-tests: E2E tests
ParkerES Feb 20, 2026
3c58bb2
Merge work/e2e-suite/nav-tests: E2E tests
ParkerES Feb 20, 2026
28605b7
Merge work/e2e-suite/dashboard-tests: E2E tests
ParkerES Feb 20, 2026
1f2778c
Merge work/e2e-suite/briefing-work-notes: E2E tests
ParkerES Feb 20, 2026
6d9a694
Merge work/e2e-suite/personal-tools: E2E tests
ParkerES Feb 20, 2026
b222c11
Merge work/e2e-suite/alerts-comms: E2E tests
ParkerES Feb 20, 2026
a154f12
Merge work/e2e-suite/project-mgmt: E2E tests
ParkerES Feb 20, 2026
e0965db
Merge work/e2e-suite/settings-full: E2E tests
ParkerES Feb 20, 2026
085432d
Merge work/e2e-suite/overlays: E2E tests
ParkerES Feb 20, 2026
6f85e83
test: add full smoke flow E2E spec (15-smoke-flow.spec.ts)
ParkerES Feb 20, 2026
94abee9
test(e2e): add theme visual verification — 14-theme-visual.spec.ts
ParkerES Feb 20, 2026
27c3da0
test: add project-scoped pages E2E sweep — 11-project-scoped-pages.sp…
ParkerES Feb 20, 2026
4f57949
Merge work/e2e-suite/project-scoped: E2E tests
ParkerES Feb 20, 2026
fcf37bd
Merge work/e2e-suite/theme-visual: E2E tests
ParkerES Feb 20, 2026
e545385
Merge work/e2e-suite/smoke-flow: E2E tests
ParkerES Feb 20, 2026
4d3ae5b
test: add E2E screenshot capture + PR gallery posting
ParkerES Feb 20, 2026
01a0e33
fix: use hash history for file:// protocol routing in Electron
ParkerES Feb 20, 2026
86caca5
fix: sidebar expand shows full labels (minSize 160px) + use bg-sideba…
ParkerES Feb 20, 2026
8be5b8e
fix: AG-Grid dark mode theming — add missing CSS vars + color-scheme …
ParkerES Feb 20, 2026
3c67d79
Merge work/ui-fixes-r2/hash-routing: Fix production routing with hash…
ParkerES Feb 20, 2026
d535a22
Merge work/ui-fixes-r2/sidebar-fix: Fix sidebar expand/collapse + bg-…
ParkerES Feb 20, 2026
29f6ba3
Merge work/ui-fixes-r2/ag-grid-theme: Fix AG-Grid dark mode theming
ParkerES Feb 20, 2026
a202218
fix: use ESM-compatible import.meta.url in screenshot helper
ParkerES Feb 20, 2026
eaa7b9f
fix: AG-Grid dark mode — force backgrounds on all internal containers
ParkerES Feb 20, 2026
662129d
chore: add E2E screenshots for PR review
ParkerES Feb 20, 2026
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ nul
coverage/
playwright-report/
test-results/

# E2E screenshots (generated, force-added on feature branches)
tests/e2e/screenshots/
4 changes: 2 additions & 2 deletions ai-docs/FEATURES-INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,9 +388,9 @@ Location: `src/renderer/app/layouts/`

| Layout | Purpose |
|--------|---------|
| `RootLayout.tsx` | Root shell: renders TitleBar at top, then `react-resizable-panels` (Group/Panel/Separator) for sidebar + content layout with localStorage persistence. Sidebar panel is collapsible and syncs with layout store. |
| `RootLayout.tsx` | Root shell: renders TitleBar at top, then `react-resizable-panels` (Group/Panel/Separator) for sidebar + content layout with localStorage persistence. Sidebar panel is collapsible (collapses to 56px, minSize 160px so labels are visible when expanded, maxSize 300px) and syncs with layout store. |
| `TitleBar.tsx` | Custom frameless window title bar (32px). Drag region for window movement + minimize/maximize/close controls. Uses `window.*` IPC channels. |
| `Sidebar.tsx` | Navigation sidebar (fills its parent panel, collapse state driven by layout store) |
| `Sidebar.tsx` | Navigation sidebar (fills its parent panel, collapse state driven by layout store). Uses `bg-sidebar text-sidebar-foreground` theme variables. |
| `TopBar.tsx` | Top bar with assistant command input |
| `CommandBar.tsx` | Global command palette (Cmd+K) |
| `ProjectTabBar.tsx` | Horizontal tab bar for switching between open projects |
Expand Down
25 changes: 25 additions & 0 deletions ai-docs/PATTERNS.md
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,20 @@ AG-Grid v35 uses the quartz theme with design-system token overrides. The theme
2. **Override class**: `ag-theme-claude` stacked with quartz for compound specificity
3. **CSS variables**: `--ag-*` properties mapped to design system tokens (`var(--card)`, `var(--foreground)`, etc.)
4. **Interactive states**: Use `color-mix()` for hover/selection (NEVER hardcode hex/rgb)
5. **Dark mode**: `.dark .ag-theme-quartz.ag-theme-claude` sets `color-scheme: dark` for native scrollbars/controls
6. **Explicit fallbacks**: `.ag-root-wrapper` and `.ag-body-viewport` get direct `background-color` overrides in case CSS variable inheritance doesn't cascade through AG-Grid's internal DOM

### Variable Categories

The theme CSS defines variables across these groups:
- **Core**: `--ag-background-color`, `--ag-foreground-color`, `--ag-data-background-color`, `--ag-border-color`, `--ag-secondary-border-color`
- **Header**: `--ag-header-background-color`, `--ag-header-foreground-color`, `--ag-header-cell-hover-background-color`
- **Rows**: `--ag-odd-row-background-color`, `--ag-row-hover-color`, `--ag-row-border-color`, `--ag-selected-row-background-color`, `--ag-range-selection-background-color`
- **Controls**: `--ag-input-focus-border-color`, `--ag-input-border-color`, `--ag-checkbox-checked-color`, `--ag-toggle-button-on-background-color`
- **Text**: `--ag-secondary-foreground-color`, `--ag-disabled-foreground-color`
- **Panels/Menus**: `--ag-control-panel-background-color`, `--ag-menu-background-color`, `--ag-panel-background-color`, `--ag-modal-overlay-background-color`, `--ag-tooltip-background-color`

**Critical**: `--ag-data-background-color` MUST be set explicitly — it controls the data viewport area and defaults to white if omitted, breaking dark mode.

### CSS Selector Pattern

Expand All @@ -545,10 +559,20 @@ AG-Grid v35 uses the quartz theme with design-system token overrides. The theme
.ag-theme-quartz.ag-theme-claude {
--ag-background-color: var(--card);
--ag-foreground-color: var(--foreground);
--ag-data-background-color: var(--card);
--ag-header-background-color: var(--muted);
--ag-row-hover-color: color-mix(in srgb, var(--accent) 50%, transparent);
/* ... */
}

/* Dark mode: native scrollbar + control dark rendering */
.dark .ag-theme-quartz.ag-theme-claude {
color-scheme: dark;
}

/* Explicit background fallbacks for AG-Grid internal DOM */
.ag-theme-quartz.ag-theme-claude .ag-root-wrapper { background-color: var(--card); }
.ag-theme-quartz.ag-theme-claude .ag-body-viewport { background-color: var(--card); }
```

### Component Usage
Expand All @@ -565,6 +589,7 @@ AG-Grid v35 uses the quartz theme with design-system token overrides. The theme
- **ALWAYS** use compound selector `.ag-theme-quartz.ag-theme-claude` (not `.ag-theme-claude` alone)
- **ALWAYS** wrap grid in `<Card>` from `@ui` for visual containment
- **NEVER** hardcode colors in the theme CSS -- use `var()` and `color-mix()` only
- **ALWAYS** set `--ag-data-background-color` alongside `--ag-background-color` — omitting it causes white viewport in dark mode
- **ALWAYS** add `?? []` fallback when passing `task.subtasks` to child components
- **ALWAYS** add `?? ''` fallback when accessing `task.description` in search/filter logic

Expand Down
4 changes: 2 additions & 2 deletions ai-docs/user-interface-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,8 @@ After auth + onboarding, the user sees the main app shell:

| Component | File | Purpose |
|-----------|------|---------|
| `RootLayout` | `src/renderer/app/layouts/RootLayout.tsx` | Shell: sidebar + topbar + outlet + notifications |
| `Sidebar` | `src/renderer/app/layouts/Sidebar.tsx` | Nav items (top-level + project-scoped), collapsible |
| `RootLayout` | `src/renderer/app/layouts/RootLayout.tsx` | Shell: sidebar (collapsible, minSize 160px) + topbar + outlet + notifications |
| `Sidebar` | `src/renderer/app/layouts/Sidebar.tsx` | Nav items (top-level + project-scoped), collapsible. Uses `bg-sidebar text-sidebar-foreground` theme vars. |
| `TopBar` | `src/renderer/app/layouts/TopBar.tsx` | Project tabs + add button + ScreenshotButton + Health + Hub status + command bar |
| `CommandBar` | `src/renderer/app/layouts/CommandBar.tsx` | Global assistant input (Cmd+K) |
| `ProjectTabBar` | `src/renderer/app/layouts/ProjectTabBar.tsx` | Horizontal tab bar for switching between open projects |
Expand Down
84 changes: 84 additions & 0 deletions docs/progress/e2e-screenshot-capture-progress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Feature: E2E Screenshot Capture + PR Description Posting

**Status**: COMPLETE
**Team**: e2e-screenshots
**Base Branch**: master
**Feature Branch**: feature/comprehensive-e2e-suite
**Design Doc**: (user-provided plan, no separate design doc)
**Started**: 2026-02-20 12:00
**Last Updated**: 2026-02-20 02:23
**Updated By**: team-lead

---

## Agent Registry

| Agent Name | Role | Worktree Branch | Task ID | Status | QA Round | Notes |
|------------|------|-----------------|---------|--------|----------|-------|
| test-infra | Test Infrastructure Engineer | feature/comprehensive-e2e-suite (shared) | #1 | COMPLETE | 0/3 | Helper + 5 test files + gitignore + package.json |
| script-eng | Script Engineer | feature/comprehensive-e2e-suite (shared) | #2 | COMPLETE | 0/3 | Post-test shell script |

---

## Task Progress

### Task #1: Screenshot Helper + Test Modifications [PENDING]
- **Agent**: test-infra
- **Worktree**: main (shared, no file overlap with Task #2)
- **Files Created**: `tests/e2e/helpers/screenshot.ts`
- **Files Modified**: `tests/e2e/03-sidebar-mechanics.spec.ts`, `tests/e2e/04-dashboard.spec.ts`, `tests/e2e/12-settings-full.spec.ts`, `tests/e2e/14-theme-visual.spec.ts`, `tests/e2e/15-smoke-flow.spec.ts`, `.gitignore`, `package.json`
- **Steps**:
- Step 1: Create screenshot helper module ⬜
- Step 2: Add screenshots to 5 test files ⬜
- Step 3: Update .gitignore + package.json ⬜
- Step 4: Run verification ⬜
- **QA Status**: NOT STARTED

### Task #2: Post-Test Shell Script [PENDING]
- **Agent**: script-eng
- **Worktree**: main (shared, no file overlap with Task #1)
- **Files Created**: `scripts/post-e2e-screenshots.sh`
- **Steps**:
- Step 1: Create shell script ⬜
- Step 2: Verify script is executable ⬜
- **QA Status**: NOT STARTED

---

## Dependency Graph

```
#1 Screenshot Helper + Test Mods ──┐
├──▶ Integration (merge + verify)
#2 Post-Test Shell Script ─────────┘
```

---

## Blockers

| Blocker | Affected Task | Reported By | Status | Resolution |
|---------|---------------|-------------|--------|------------|
| None | | | | |

---

## Integration Checklist

- [ ] All tasks COMPLETE with verification PASS
- [ ] `npm run lint` passes
- [ ] `npm run typecheck` passes
- [ ] `npm run test` passes
- [ ] `npm run build` passes
- [ ] Committed with descriptive message
- [ ] Progress file status set to COMPLETE

---

## Recovery Notes

If this feature is resumed by a new session:
1. Read this file for current state
2. Check TaskList for team task status
3. Resume from the first non-COMPLETE task
4. Update "Last Updated" and "Updated By" fields
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"test:watch": "vitest",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:report": "playwright test && bash scripts/post-e2e-screenshots.sh",
"check:docs": "node scripts/check-docs.mjs",
"check:agents": "node scripts/check-agents.mjs",
"validate:tracker": "node scripts/validate-tracker.mjs",
Expand Down
131 changes: 131 additions & 0 deletions scripts/capture-screenshots.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import http from 'node:http';
import fs from 'node:fs';
import path from 'node:path';
import WebSocket from 'ws';

const SCREENSHOTS_DIR = path.join('tests', 'e2e', 'screenshots');
fs.mkdirSync(SCREENSHOTS_DIR, { recursive: true });

function getTarget() {
return new Promise((resolve, reject) => {
http.get('http://localhost:9222/json', (res) => {
let data = '';
res.on('data', (chunk) => (data += chunk));
res.on('end', () => {
const targets = JSON.parse(data);
resolve(targets.find((t) => t.type === 'page'));
});
}).on('error', reject);
});
}

function cdpCommand(ws, method, params = {}) {
return new Promise((resolve) => {
const id = Math.floor(Math.random() * 100000);
const handler = (msg) => {
const resp = JSON.parse(msg.toString());
if (resp.id === id) {
ws.removeListener('message', handler);
resolve(resp.result);
}
};
ws.on('message', handler);
ws.send(JSON.stringify({ id, method, params }));
});
}

async function captureScreenshot(ws, name) {
const result = await cdpCommand(ws, 'Page.captureScreenshot', { format: 'png' });
const buf = Buffer.from(result.data, 'base64');
const outPath = path.join(SCREENSHOTS_DIR, name + '.png');
fs.writeFileSync(outPath, buf);
console.log('Saved:', name + '.png', '(' + buf.length + ' bytes)');
}

async function navigate(ws, url) {
await cdpCommand(ws, 'Page.navigate', { url });
await new Promise((r) => setTimeout(r, 3000));
}

async function clickByAriaLabel(ws, label) {
const expr = `(function() {
const els = document.querySelectorAll('button, [role="button"]');
for (const el of els) {
if (el.getAttribute('aria-label') === '${label}') {
el.click();
return 'clicked';
}
}
return 'not found';
})()`;
const result = await cdpCommand(ws, 'Runtime.evaluate', {
expression: expr,
returnByValue: true,
});
console.log(' click', label, '->', result?.result?.value);
await new Promise((r) => setTimeout(r, 2000));
}

async function clickByText(ws, text) {
const expr = `(function() {
const els = document.querySelectorAll('button, a, [role="button"], [role="tab"]');
for (const el of els) {
if (el.textContent.trim() === '${text}' || el.textContent.includes('${text}')) {
el.click();
return 'clicked: ' + el.tagName;
}
}
return 'not found';
})()`;
const result = await cdpCommand(ws, 'Runtime.evaluate', {
expression: expr,
returnByValue: true,
});
console.log(' click', text, '->', result?.result?.value);
await new Promise((r) => setTimeout(r, 2000));
}

async function main() {
const target = await getTarget();
console.log('Connected to:', target.url);
const ws = new WebSocket(target.webSocketDebuggerUrl);
await new Promise((r) => ws.on('open', r));

// 1. Dashboard with sidebar expanded
console.log('\n--- Dashboard (expanded sidebar) ---');
await navigate(ws, 'http://localhost:5173/#/dashboard');
await captureScreenshot(ws, 'after-dashboard-expanded');

// 2. Collapse sidebar
console.log('\n--- Sidebar collapsed ---');
await clickByAriaLabel(ws, 'Collapse sidebar');
await captureScreenshot(ws, 'after-sidebar-collapsed');

// 3. Expand sidebar
console.log('\n--- Sidebar expanded ---');
await clickByAriaLabel(ws, 'Expand sidebar');
await captureScreenshot(ws, 'after-sidebar-expanded');

// 4. Tasks / AG-Grid
console.log('\n--- AG-Grid tasks page ---');
await clickByText(ws, 'syrnia-helpsite');
await new Promise((r) => setTimeout(r, 2000));
await captureScreenshot(ws, 'after-ag-grid-tasks');

// 5. Settings
console.log('\n--- Settings page ---');
await navigate(ws, 'http://localhost:5173/#/settings');
await captureScreenshot(ws, 'after-settings');

ws.close();
console.log('\nAll screenshots captured!');

// List files
const files = fs.readdirSync(SCREENSHOTS_DIR).filter((f) => f.endsWith('.png'));
console.log('Files in', SCREENSHOTS_DIR + ':', files.join(', '));
}

main().catch((e) => {
console.error(e);
process.exit(1);
});
82 changes: 82 additions & 0 deletions scripts/post-e2e-screenshots.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env bash
set -euo pipefail

SCREENSHOTS_DIR="tests/e2e/screenshots"

# ── Step 1: Check for screenshots ──────────────────────────────────────────────
echo "==> Checking for E2E screenshots in ${SCREENSHOTS_DIR}/"

shopt -s nullglob
png_files=("${SCREENSHOTS_DIR}"/*.png)
shopt -u nullglob

if [[ ${#png_files[@]} -eq 0 ]]; then
echo " No .png files found in ${SCREENSHOTS_DIR}/. Nothing to do."
exit 0
fi

echo " Found ${#png_files[@]} screenshot(s)."

# ── Step 2: Git operations — commit and push screenshots ───────────────────────
echo "==> Staging screenshots..."
git add -f "${SCREENSHOTS_DIR}"/*.png

if git diff --cached --quiet; then
echo " Screenshots are unchanged from last commit. Skipping commit."
else
echo " Committing screenshots..."
git commit -m "chore: add E2E screenshots for PR review"
echo " Pushing to remote..."
git push origin HEAD
fi

# ── Step 3: Detect current PR ─────────────────────────────────────────────────
echo "==> Detecting pull request..."

PR_NUMBER=$(gh pr view --json number -q '.number' 2>/dev/null || true)

if [[ -z "${PR_NUMBER}" ]]; then
echo " No open PR found for the current branch. Skipping gallery post."
exit 0
fi

echo " Found PR #${PR_NUMBER}."

# ── Step 4: Build image gallery markdown ───────────────────────────────────────
echo "==> Building screenshot gallery..."

REPO=$(gh repo view --json nameWithOwner -q '.nameWithOwner')
BRANCH=$(git branch --show-current)

GALLERY=$'\n## E2E Screenshots\n\n| Screenshot | Preview |\n|------------|---------|'

for file in "${png_files[@]}"; do
filename=$(basename "${file}")
# Strip .png extension for the display name
name="${filename%.png}"
url="https://raw.githubusercontent.com/${REPO}/${BRANCH}/${SCREENSHOTS_DIR}/${filename}"
GALLERY+=$'\n'"| ${name} | ![${name}](${url}) |"
done

GALLERY+=$'\n'

echo " Gallery built with ${#png_files[@]} image(s)."

# ── Step 5: Update PR description ─────────────────────────────────────────────
echo "==> Updating PR #${PR_NUMBER} description..."

EXISTING_BODY=$(gh pr view --json body -q '.body')

# Strip any existing E2E Screenshots section (and everything after it)
CLEAN_BODY="${EXISTING_BODY}"
if [[ "${CLEAN_BODY}" == *"## E2E Screenshots"* ]]; then
CLEAN_BODY="${CLEAN_BODY%%## E2E Screenshots*}"
# Remove trailing whitespace/newlines from the clean body
CLEAN_BODY=$(printf '%s' "${CLEAN_BODY}" | sed -e 's/[[:space:]]*$//')
fi

NEW_BODY="${CLEAN_BODY}${GALLERY}"

gh pr edit "${PR_NUMBER}" --body "${NEW_BODY}"

echo "==> Done. PR #${PR_NUMBER} updated with screenshot gallery."
2 changes: 1 addition & 1 deletion src/renderer/app/layouts/RootLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export function RootLayout() {
defaultSize="208px"
id={SIDEBAR_PANEL_ID}
maxSize="300px"
minSize="56px"
minSize="160px"
panelRef={sidebarPanelRef}
>
<Sidebar />
Expand Down
Loading
Loading