feat: Add Focus Tools (ADHD-friendly productivity features)
continuous-integration/drone/push Build is passing

- Add Focus Tools dashboard with collapsible help section
- Implement Quick Wins page for tasks under 15 minutes
- Add Single Task Focus mode to reduce overwhelm
- Create Body Doubling page for virtual co-working
- Add visual timer, break reminders, and energy tracking
- Implement hyperfocus protection alerts
- Add ADHD settings panel with customizable options
- Include full English and German translations
- Fix larger touch targets CSS to not break button layouts
- Add Playwright tests for Focus Tools features
This commit is contained in:
Paul Nothaft
2026-01-15 21:20:04 +01:00
parent 74ffef48d3
commit 7ce4f7efdc
31 changed files with 5651 additions and 11 deletions
+181
View File
@@ -0,0 +1,181 @@
import { test, expect } from '@playwright/test';
test.describe('ADHD Mode Features', () => {
const username = 'adhd_test_' + Date.now();
const email = username + '@example.com';
const password = 'testpass123';
test.beforeEach(async ({ page }) => {
// Listen for console logs and errors
page.on('console', msg => {
if (msg.type() === 'error') {
console.log(`BROWSER ERROR: ${msg.text()}`);
}
});
page.on('pageerror', exception => console.log(`PAGE ERROR: ${exception}`));
});
test('ADHD Dashboard is accessible and displays features', async ({ page }) => {
test.setTimeout(120000);
// Go to app
await page.goto('http://localhost:2100');
await page.waitForTimeout(2000);
// Register if needed
const registerBtn = page.getByRole('button', { name: 'Register' });
if (await registerBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
await registerBtn.click();
await page.fill('input[name="username"]', username);
await page.fill('input[name="email"]', email);
await page.fill('input[name="password"]', password);
await page.click('button[type="submit"]');
await page.waitForTimeout(3000);
} else {
// Try login
await page.fill('input[name="username"]', 'admin');
await page.fill('input[name="password"]', 'admin');
await page.click('button[type="submit"]');
await page.waitForTimeout(3000);
}
// Navigate to ADHD Dashboard
await page.goto('http://localhost:2100/adhd');
await page.waitForTimeout(2000);
// Check if ADHD Dashboard loaded (look for any ADHD-related content)
const pageContent = await page.content();
console.log('Page loaded, checking for ADHD content...');
// Should see ADHD-related content
const hasADHDContent = pageContent.includes('ADHD') ||
pageContent.includes('ADHS') ||
pageContent.includes('Quick Win') ||
pageContent.includes('Schnelle') ||
pageContent.includes('Focus') ||
pageContent.includes('Fokus');
expect(hasADHDContent).toBeTruthy();
console.log('ADHD Dashboard test passed!');
});
test('ADHD navigation item exists in sidebar', async ({ page }) => {
test.setTimeout(60000);
await page.goto('http://localhost:2100');
await page.waitForTimeout(2000);
// Try to login first
const loginInput = page.locator('input[name="username"]');
if (await loginInput.isVisible({ timeout: 3000 }).catch(() => false)) {
await page.fill('input[name="username"]', 'admin');
await page.fill('input[name="password"]', 'admin');
await page.click('button[type="submit"]');
await page.waitForTimeout(3000);
}
// Check for ADHD navigation item (Brain icon or ADHD text)
const pageContent = await page.content();
const hasADHDNav = pageContent.includes('/adhd') ||
pageContent.includes('ADHD Mode') ||
pageContent.includes('ADHS-Modus');
console.log('Checking for ADHD navigation...');
expect(hasADHDNav).toBeTruthy();
console.log('ADHD navigation test passed!');
});
test('Quick Wins page loads and shows tasks', async ({ page }) => {
test.setTimeout(60000);
await page.goto('http://localhost:2100');
await page.waitForTimeout(2000);
// Login
const loginInput = page.locator('input[name="username"]');
if (await loginInput.isVisible({ timeout: 3000 }).catch(() => false)) {
await page.fill('input[name="username"]', 'admin');
await page.fill('input[name="password"]', 'admin');
await page.click('button[type="submit"]');
await page.waitForTimeout(3000);
}
// Navigate to Quick Wins page
await page.goto('http://localhost:2100/adhd/quick-wins');
await page.waitForTimeout(2000);
const pageContent = await page.content();
const hasQuickWinsContent = pageContent.includes('Quick Win') ||
pageContent.includes('Schnelle') ||
pageContent.includes('dopamine') ||
pageContent.includes('Dopamin');
console.log('Checking Quick Wins page...');
expect(hasQuickWinsContent).toBeTruthy();
console.log('Quick Wins page test passed!');
});
test('Single Task Focus page loads', async ({ page }) => {
test.setTimeout(60000);
await page.goto('http://localhost:2100');
await page.waitForTimeout(2000);
// Login
const loginInput = page.locator('input[name="username"]');
if (await loginInput.isVisible({ timeout: 3000 }).catch(() => false)) {
await page.fill('input[name="username"]', 'admin');
await page.fill('input[name="password"]', 'admin');
await page.click('button[type="submit"]');
await page.waitForTimeout(3000);
}
// Navigate to Single Task page
await page.goto('http://localhost:2100/adhd/single-task');
await page.waitForTimeout(2000);
const pageContent = await page.content();
const hasSingleTaskContent = pageContent.includes('Single Task') ||
pageContent.includes('Einzelaufgabe') ||
pageContent.includes('focus') ||
pageContent.includes('Fokus') ||
pageContent.includes('select') ||
pageContent.includes('wählen');
console.log('Checking Single Task page...');
expect(hasSingleTaskContent).toBeTruthy();
console.log('Single Task page test passed!');
});
test('Body Doubling page loads', async ({ page }) => {
test.setTimeout(60000);
await page.goto('http://localhost:2100');
await page.waitForTimeout(2000);
// Login
const loginInput = page.locator('input[name="username"]');
if (await loginInput.isVisible({ timeout: 3000 }).catch(() => false)) {
await page.fill('input[name="username"]', 'admin');
await page.fill('input[name="password"]', 'admin');
await page.click('button[type="submit"]');
await page.waitForTimeout(3000);
}
// Navigate to Body Doubling page
await page.goto('http://localhost:2100/adhd/body-doubling');
await page.waitForTimeout(2000);
const pageContent = await page.content();
const hasBodyDoublingContent = pageContent.includes('Body Doubling') ||
pageContent.includes('Virtuelles') ||
pageContent.includes('session') ||
pageContent.includes('Sitzung') ||
pageContent.includes('together') ||
pageContent.includes('gemeinsam');
console.log('Checking Body Doubling page...');
expect(hasBodyDoublingContent).toBeTruthy();
console.log('Body Doubling page test passed!');
});
});