Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
87 changes: 87 additions & 0 deletions e2e/calendar.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2573,4 +2573,91 @@ test.describe('FreeShard WebCalendar E2E Suite', () => {
}
}
});

test.describe('Issue #110 β€” stylesheet and markup agree', () => {
// Computed value of a design token, as an rgb() string comparable to getComputedStyle colours.
async function tokenColor(page, token) {
return page.evaluate(t => {
const probe = document.createElement('span');
probe.style.color = `var(${t})`;
document.body.appendChild(probe);
const c = getComputedStyle(probe).color;
probe.remove();
return c;
}, token);
}

test('a warning toast carries its own glyph and the warning border colour', async ({ page }) => {
await page.goto('/');
await expect(page.locator('#app')).not.toHaveClass(/hidden/, { timeout: 10000 });
await page.locator('#calendar-list .calendar-item').first().locator('[data-share-cal]').click();
await expect(page.locator('#share-cal-dialog-backdrop')).toBeVisible();

// Adding a share with nothing entered is the share flow's warning.
await page.click('#share-add-btn');
const toast = page.locator('#toast-container .toast.warning');
await expect(toast).toBeVisible();
await expect(toast.locator('.toast-icon')).not.toHaveText('β„Ή');
await expect(toast.locator('.toast-icon')).toHaveText('⚠');
const border = await toast.evaluate(el => {
const s = getComputedStyle(el);
return { width: s.borderLeftWidth, color: s.borderLeftColor };
});
expect(border).toEqual({ width: '3px', color: await tokenColor(page, '--warning') });

// The user-search suggestions sit over the dialog's content on an opaque surface.
await page.locator('#share-search-input').fill('testuser');
const dropdown = page.locator('#share-autocomplete-dropdown');
await expect(dropdown).not.toHaveClass(/hidden/, { timeout: 10000 });
expect(await dropdown.evaluate(el => getComputedStyle(el).backgroundColor)).toBe(await tokenColor(page, '--surface'));
});

test('an attendee\'s participation status renders as a muted pill', async ({ page }) => {
await page.goto('/');
await expect(page.locator('#app')).not.toHaveClass(/hidden/, { timeout: 10000 });
await page.click('#new-event-btn');
await page.fill('#ev-attendee-name', 'Test User');
await page.fill('#ev-attendee-email', 'testuser@example.com');
await page.click('#add-attendee-btn');

const status = page.locator('#ev-attendee-list .attendee-status').first();
await expect(status).toHaveText('Ausstehend');
const style = await status.evaluate(el => {
const s = getComputedStyle(el);
return { color: s.color, paddingLeft: s.paddingLeft, borderRadius: s.borderTopLeftRadius };
});
expect(style.color).toBe(await tokenColor(page, '--ink-muted'));
expect(parseFloat(style.paddingLeft)).toBeGreaterThan(0);
expect(parseFloat(style.borderRadius)).toBeGreaterThan(0);
});

test('empty lists use the muted empty-state text', async ({ page }) => {
await page.route('**/api/msgraph/accounts', route => route.fulfill({
contentType: 'application/json',
body: JSON.stringify({ success: true, available: true, accounts: [] }),
}));
await page.goto('/');
await expect(page.locator('#app')).not.toHaveClass(/hidden/, { timeout: 10000 });
await page.click('#sidebar-settings-btn');
await page.click('#settings-add-account-btn');
await page.click('#btn-link-msgraph');
await expect(page.locator('#msgraph-modal-backdrop')).not.toHaveClass(/hidden/);

const empty = page.locator('#msgraph-accounts-list .empty-state');
await expect(empty).toHaveText('Keine Office 365 Konten verbunden.');
expect(await empty.evaluate(el => getComputedStyle(el).color)).toBe(await tokenColor(page, '--ink-muted'));
});

test('the welcome dialog\'s calendar glyph is drawn in the accent line colour', async ({ page }) => {
// Registered after the suite's init script, so it runs later and re-enables the welcome dialog.
await page.addInitScript(() => {
if (window.origin !== 'null') window.localStorage.removeItem('welcome-popup-closed');
});
await page.goto('/');
await expect(page.locator('#welcome-modal-backdrop')).not.toHaveClass(/hidden/, { timeout: 10000 });
const glyph = page.locator('#welcome-modal .modal-body svg').first();
expect(await glyph.evaluate(el => getComputedStyle(el).stroke)).toBe(await tokenColor(page, '--line'));
await page.click('#welcome-btn-close');
});
});
});
102 changes: 42 additions & 60 deletions public/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,6 @@ a { color: inherit; text-decoration: none; }
text-transform: uppercase; letter-spacing: var(--ls-label);
color: var(--ink-muted);
}
.fs-mono {
font: 400 var(--fs-mono)/1 var(--font-mono);
letter-spacing: var(--ls-mono);
color: var(--ink-muted);
}

/* ── 5. Loading Screen ────────────────────────────────────────────────────── */
.loading-screen {
Expand Down Expand Up @@ -158,7 +153,7 @@ a { color: inherit; text-decoration: none; }
background: var(--surface);
border-right: var(--border-w) solid var(--border);
display: flex; flex-direction: column;
overflow-y: auto; overflow-x: hidden;
overflow: hidden; /* .sidebar-content scrolls; the footer stays put */
height: 100%;
position: relative; /* containing block for .sidebar-settings-drawer */
}
Expand Down Expand Up @@ -240,7 +235,7 @@ a { color: inherit; text-decoration: none; }
}

.mini-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; }
.mini-cal-dow, .mini-cal-day-name {
.mini-cal-dow {
font: 600 9px/1 var(--font-ui);
text-transform: uppercase; letter-spacing: 0.1em;
color: var(--ink-muted); text-align: center;
Expand Down Expand Up @@ -516,6 +511,10 @@ a { color: inherit; text-decoration: none; }
.event-chip-title {
overflow: hidden; text-overflow: ellipsis;
}
.recur-icon {
margin-left: 4px; flex-shrink: 0;
display: inline-block; vertical-align: middle;
}
.month-more {
font: 600 10px/1 var(--font-ui);
color: var(--ink-muted); margin-top: 2px; cursor: pointer;
Expand Down Expand Up @@ -938,6 +937,18 @@ textarea.form-input { height: auto; padding: var(--sp-3); resize: vertical; }
}
.attendee-name { font-size: var(--fs-small); color: var(--ink); }
.attendee-email { font-size: 11px; color: var(--ink-muted); font-family: var(--font-mono); }
/* PARTSTAT pill; the second class is the RFC 5545 value */
.attendee-status {
margin-left: auto;
padding: 1px var(--sp-2);
border: var(--border-w) solid currentColor;
border-radius: var(--radius-pill);
font-size: 11px; white-space: nowrap;
color: var(--ink-muted);
}
.attendee-status.ACCEPTED { color: var(--success); }
.attendee-status.TENTATIVE { color: var(--warning); }
.attendee-status.DECLINED { color: var(--danger); }
.attendee-remove { color: var(--ink-muted); transition: color var(--t); padding: 2px; border-radius: 4px; }
.attendee-remove:hover { color: var(--danger); }
.attendee-remove svg { width: 13px; height: 13px; display: block; }
Expand Down Expand Up @@ -1092,15 +1103,10 @@ textarea.form-input { height: auto; padding: var(--sp-3); resize: vertical; }
.toast.success { border-left: 3px solid var(--data); }
.toast.error { border-left: 3px solid var(--danger); }
.toast.info { border-left: 3px solid var(--line); }
.toast.warning { border-left: 3px solid var(--warning); }

/* ── 19. Keyboard focus chrome ────────────────────────────────────────────── */
/* Teal outline + slight scale on keyboard nav only (DECISIONS.md) */
.fs-focusable { transform-origin: center; }
.fs-focusable:focus-visible {
outline: 2px solid var(--line);
outline-offset: 3px;
position: relative; z-index: 3;
}
/* Buttons & tiles get mild scale; full-width rows do not */
.toolbar-btn:focus-visible,
.btn:focus-visible {
Expand All @@ -1121,7 +1127,6 @@ textarea.form-input { height: auto; padding: var(--sp-3); resize: vertical; }
}
.sidebar.open { transform: translateX(0); visibility: visible; transition: transform var(--t-slow); }
.app { flex-direction: column; }
.month-grid-header-day { font-size: 9px; }

/* Responsive Grid-based Toolbar Layout */
.toolbar {
Expand Down Expand Up @@ -1312,26 +1317,6 @@ textarea.form-input, .form-textarea {
}
.onboarding-form { display: flex; flex-direction: column; }

/* Sidebar section/account classes the old HTML used β€” now remapped */
.sidebar-title { font: 600 var(--fs-h3)/1 var(--font-ui); color: var(--line); }

/* Calendar/Account list sections β€” old class names still referenced */
.calendar-list-section,
.account-list-section {
padding: var(--sp-4) var(--sp-3);
border-top: var(--border-w) solid var(--border);
}
.section-header {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: var(--sp-2);
}
.section-title {
font: 600 var(--fs-label)/1 var(--font-ui);
text-transform: uppercase; letter-spacing: var(--ls-label);
color: var(--ink-muted);
}

/* Mini calendar section compatibility */
.mini-calendar { padding: 0 var(--sp-1); }


Expand Down Expand Up @@ -1434,7 +1419,14 @@ textarea.form-input, .form-textarea {
background: rgba(255, 255, 255, 0.04);
border-color: #555;
}
.oauth-icon {
.btn-msgraph {
margin-top: 8px;
background: #0078D4; /* Microsoft brand blue */
border-color: #0078D4;
color: #ffffff;
}
.oauth-icon,
.google-badge-icon {
flex-shrink: 0;
}
.modal-divider {
Expand All @@ -1459,14 +1451,6 @@ textarea.form-input, .form-textarea {
}

/* Sidebar Restructuring & Drawer */
/* NOTE: must not set `position` here β€” this rule comes after the mobile media
query and would override the off-canvas `position: fixed` of the sidebar. */
.sidebar {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden !important;
}
.sidebar-content {
flex: 1;
overflow-y: auto;
Expand Down Expand Up @@ -1687,6 +1671,7 @@ textarea.form-input, .form-textarea {
[data-theme="dark"] .kanban-card {
background: var(--surface-bg-alt);
}
.kanban-card-body { min-width: 0; }
.kanban-card-title {
font-weight: 600;
font-size: 14px;
Expand Down Expand Up @@ -1756,6 +1741,12 @@ textarea.form-input, .form-textarea {
}

/* ── Empty State Styles ─────────────────────────────────────────────────── */
.empty-state {
padding: var(--sp-4) var(--sp-3);
text-align: center;
font-size: var(--fs-small);
color: var(--ink-muted);
}
.kanban-empty-state {
margin: 12px 0 16px 0;
padding: 16px 20px;
Expand Down Expand Up @@ -1817,6 +1808,9 @@ textarea.form-input, .form-textarea {
margin-top: 3px;
cursor: pointer;
}
.sidebar-task-info { flex: 1; min-width: 0; cursor: pointer; }
.sidebar-task-meta { font-size: 0.75rem; color: var(--ink-muted); }
.sidebar-tasks-empty { font-size: 0.78rem; color: var(--ink-muted); padding: 4px 8px; }
.sidebar-task-title {
font-size: 0.82rem;
font-weight: 500;
Expand Down Expand Up @@ -1960,11 +1954,6 @@ textarea.form-input, .form-textarea {
background: #c69500;
}

/* ── Print & Vector PDF Export Utility Classes ────────────────────────────────── */
.print-only {
display: none !important;
}

/* ── Print & Vector PDF Export Styles ─────────────────────────────────────── */
@media print {
body {
Expand All @@ -1983,19 +1972,11 @@ textarea.form-input, .form-textarea {
.status-bar,
.modal-backdrop,
.popover,
.notifications-toast,
#kanban-side-drawer,
.drawer-backdrop,
.shortcuts-modal-backdrop,
.toolbar-tz-select-wrapper,
.quick-add-container,
.header-actions,
.no-print {
.toolbar-tz-select-wrapper {
display: none !important;
}
.print-only {
display: block !important;
}
.app {
display: block !important;
overflow: visible !important;
Expand All @@ -2018,7 +1999,8 @@ textarea.form-input, .form-textarea {
}
.calendar-container,
.month-grid,
.week-grid,
.week-grid-body,
.day-grid-body,
.agenda-view,
.kanban-board {
width: 100% !important;
Expand All @@ -2028,11 +2010,11 @@ textarea.form-input, .form-textarea {
break-inside: avoid !important;
page-break-inside: avoid !important;
}
.month-day-cell,
.month-day,
.week-day-col,
.event-chip,
.kanban-card,
.agenda-item {
.agenda-event {
border: 1px solid #000 !important;
color: #000 !important;
background: #ffffff !important;
Expand Down
Loading
Loading