mirror of
https://github.com/BradGroux/veritas-kanban.git
synced 2026-08-28 02:44:59 +00:00
fix: keep mobile notifications above navigation (#880)
[author: gpt-5-codex][reviewed-by: grok-4.3] Co-authored-by: bradgroux <brad@digitalmeld.io>
This commit is contained in:
parent
d12e6f5f67
commit
82b83ad284
5 changed files with 93 additions and 1 deletions
|
|
@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Kept mobile notifications above the safe-area-aware bottom navigation so
|
||||
long-lived toasts cannot intercept touch input after Task Detail closes
|
||||
(#869).
|
||||
|
||||
## [5.2.4] - 2026-07-13
|
||||
|
||||
### Fixed
|
||||
|
|
|
|||
|
|
@ -155,4 +155,73 @@ test.describe('mobile responsive flows', () => {
|
|||
await expect(page.getByRole('button', { name: 'Login' })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Forgot password?' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('keeps mobile notifications operable above a conflict toast after task detail closes', async ({
|
||||
page,
|
||||
}) => {
|
||||
const taskTitle = `E2E Mobile Notification Conflict ${Date.now()}`;
|
||||
const task = await seedTestTask(page, {
|
||||
title: taskTitle,
|
||||
description: 'Verify notifications remain touchable while a conflict toast is visible.',
|
||||
status: 'todo',
|
||||
priority: 'high',
|
||||
type: 'code',
|
||||
});
|
||||
testTaskId = (task as { id: string }).id;
|
||||
|
||||
let returnedConflict = false;
|
||||
await page.route(`**/api/tasks/${testTaskId}`, async (route) => {
|
||||
if (!returnedConflict && route.request().method() === 'PATCH') {
|
||||
returnedConflict = true;
|
||||
await route.fulfill({
|
||||
status: 409,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'CONFLICT',
|
||||
message: 'Task revision conflict',
|
||||
details: { current: task },
|
||||
},
|
||||
meta: { timestamp: new Date().toISOString() },
|
||||
}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await route.fallback();
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
const statusSelect = page.getByRole('combobox', {
|
||||
name: `Change status for ${taskTitle}`,
|
||||
});
|
||||
await statusSelect.click();
|
||||
await page.getByRole('option', { name: 'Blocked' }).click();
|
||||
await expect(page.getByText('Task changed elsewhere')).toBeVisible();
|
||||
|
||||
await page.getByText(taskTitle).first().click();
|
||||
const detail = page.getByTestId('task-detail-panel');
|
||||
await expect(detail).toBeVisible();
|
||||
await detail.getByRole('button', { name: 'Close task details' }).click();
|
||||
await expect(detail).not.toBeVisible();
|
||||
|
||||
const mobileNavigation = page.getByRole('navigation', { name: 'Mobile navigation' });
|
||||
const notificationViewport = page.locator(
|
||||
".veritas-notifications[data-position='bottom-right']"
|
||||
);
|
||||
const mobileNotificationsButton = page.getByRole('button', { name: 'Mobile notifications' });
|
||||
const [navigationBox, notificationBox] = await Promise.all([
|
||||
mobileNavigation.boundingBox(),
|
||||
notificationViewport.boundingBox(),
|
||||
]);
|
||||
|
||||
expect(navigationBox).not.toBeNull();
|
||||
expect(notificationBox).not.toBeNull();
|
||||
expect(notificationBox!.y + notificationBox!.height).toBeLessThanOrEqual(navigationBox!.y);
|
||||
|
||||
await tapLocatorCenter(page, mobileNotificationsButton);
|
||||
await expect(page.getByLabel('Notifications', { exact: true })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -316,6 +316,13 @@ describe('Mantine-backed shared UI primitives', () => {
|
|||
expect(screen.getByText('Saved')).toBeDefined();
|
||||
expect(screen.getByText('Mantine notification bridge is active.')).toBeDefined();
|
||||
});
|
||||
|
||||
expect(
|
||||
document.querySelector(".veritas-notifications[data-position='bottom-right']")
|
||||
).not.toBeNull();
|
||||
expect(globalStyles).toMatch(
|
||||
/\.veritas-notifications\[data-position\^='bottom-'\]\s*\{[^}]*bottom:\s*calc\(5\.5rem \+ env\(safe-area-inset-bottom\)\);/s
|
||||
);
|
||||
});
|
||||
|
||||
it('preserves tabs value changes through the legacy onValueChange contract', () => {
|
||||
|
|
|
|||
|
|
@ -139,6 +139,11 @@
|
|||
right: max(1rem, env(safe-area-inset-right));
|
||||
bottom: calc(5.5rem + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
/* Keep high-z-index toasts from intercepting the fixed mobile navigation. */
|
||||
.veritas-notifications[data-position^='bottom-'] {
|
||||
bottom: calc(5.5rem + env(safe-area-inset-bottom));
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-transparency: reduce) {
|
||||
|
|
|
|||
|
|
@ -75,7 +75,12 @@ export function MantineRoot({ children, env = 'default' }: MantineRootProps) {
|
|||
{env === 'test' ? <VeritasTestColorSchemeSync /> : <VeritasColorSchemeSync />}
|
||||
<ModalsProvider>
|
||||
{children}
|
||||
<Notifications position="bottom-right" limit={5} zIndex={5000} />
|
||||
<Notifications
|
||||
className="veritas-notifications"
|
||||
position="bottom-right"
|
||||
limit={5}
|
||||
zIndex={5000}
|
||||
/>
|
||||
</ModalsProvider>
|
||||
</MantineProvider>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue