Compare commits

..
6 Commits
Author SHA1 Message Date
github-actions[bot] f27489463c chore: bump version to 0.21.1 2026-03-12 08:45:06 +00:00
Frederic CabassutandClaude Opus 4.6 990639d804 fix: resolve flaky offline sync e2e test in CI
Playwright's setOffline(false) doesn't reliably emit the browser's
'online' DOM event in headless/CI environments. Manually dispatch it
as a safety net so the sync client always triggers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 09:31:11 +01:00
Claude 8108e62cf1 fix: remove stopPropagation on editor card that blocked toolbar dropdown close
The editor card's onpointerdown stopPropagation prevented the
FormattingToolbar's document-level pointerdown listener from
detecting clicks outside the dropdown. Since both overlay handlers
already use e.target === e.currentTarget to ignore bubbled events,
no stopPropagation is needed on the card.

https://claude.ai/code/session_018DYwSysqysMmaRi5XkqTpo
2026-03-12 09:30:47 +01:00
Claude b0a3a1fcb8 fix: use pointer events for backdrop close instead of timeout guard
Pointer events are never synthetically re-dispatched from touch
(unlike mouse events), so the tap that opened the editor cannot
ghost-click the overlay. This eliminates the mobile first-load bug
without relying on an arbitrary delay.

Both pointerdown and pointerup must target the overlay itself to
trigger close, which also handles the text-selection-outside-editor
edge case.

https://claude.ai/code/session_018DYwSysqysMmaRi5XkqTpo
2026-03-12 09:30:47 +01:00
Claude c411975bb9 fix: use timestamp guard instead of rAF for mobile backdrop click
On first page load on mobile, the synthetic mousedown from a tap can
arrive ~300ms after the touch event — well after a single
requestAnimationFrame (~16ms). Replace the rAF-based overlayReady
guard with a 400ms timestamp check from mount time to reliably
ignore stale synthetic events on slow first loads.

https://claude.ai/code/session_018DYwSysqysMmaRi5XkqTpo
2026-03-12 09:30:47 +01:00
github-actions[bot] e08a4883f1 chore: pin docker-compose to v0.21.0 [skip ci] 2026-03-11 20:22:43 +00:00
6 changed files with 20 additions and 34 deletions
+1
View File
@@ -180,6 +180,7 @@ Write tests first or alongside features. Tests serve as living documentation of
- Test database: `./data/test-crumbs.db` (cleaned via `global-setup.ts`)
- Auth fixture: `tests/e2e/helpers/fixtures.ts` provides `authenticatedPage` (handles setup/login race conditions across parallel workers)
- Auth tests use `test.describe.serial` because they depend on sequential database state
- **Offline/online tests**: After `setOffline(false)`, always manually dispatch the `online` event via `page.evaluate(() => window.dispatchEvent(new Event('online')))` — Playwright doesn't reliably emit it in headless/CI environments, causing flaky `waitForResponse` timeouts
### Gherkin-style Given/When/Then
+1 -1
View File
@@ -1,6 +1,6 @@
services:
crumbs:
image: ghcr.io/bretzel-app/crumbs:0.20.0
image: ghcr.io/bretzel-app/crumbs:0.21.0
container_name: crumbs
ports:
- "3000:3000"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "crumbs",
"private": true,
"version": "0.21.0",
"version": "0.21.1",
"type": "module",
"license": "MIT",
"author": "Bretzel (https://bretzel.app)",
+12 -30
View File
@@ -170,36 +170,19 @@
bgStyle = `background-color: ${colors.bg}`;
});
// Track whether mousedown started on the overlay (not inside the editor).
// Prevents closing when the user selects text inside the editor and
// releases the mouse outside — the click event fires on the overlay,
// but we only want to close if the press also started there.
let mousedownOnOverlay = false;
// Use pointer events to detect backdrop clicks. Unlike mouse events,
// pointer events are never synthetically re-dispatched from touch, so
// the tap that opened the editor on mobile cannot ghost-click the overlay.
// Both pointerdown and pointerup must target the overlay itself to close.
let pointerdownOnOverlay = false;
// On mobile, the tap that opens the editor produces synthetic mouse events
// (mousedown → mouseup → click) at the original touch coordinates. If those
// coordinates land on the overlay backdrop, the editor would close instantly.
// Guard against this by ignoring mousedowns that arrive within the first
// animation frame after mount.
let overlayReady = false;
$effect(() => {
const id = requestAnimationFrame(() => {
overlayReady = true;
});
return () => {
overlayReady = false;
cancelAnimationFrame(id);
};
});
function handleOverlayMousedown(e: MouseEvent) {
if (!overlayReady) return;
mousedownOnOverlay = e.target === e.currentTarget;
function handleOverlayPointerdown(e: PointerEvent) {
pointerdownOnOverlay = e.target === e.currentTarget;
}
function handleOverlayClick() {
if (mousedownOnOverlay) saveAndClose();
mousedownOnOverlay = false;
function handleOverlayPointerup(e: PointerEvent) {
if (pointerdownOnOverlay && e.target === e.currentTarget) saveAndClose();
pointerdownOnOverlay = false;
}
async function saveAndClose() {
@@ -263,8 +246,8 @@
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="fixed inset-0 z-40 flex items-start justify-center overflow-y-auto bg-black/50 pt-20 pb-10 animate-[fade-in_150ms_ease-out]"
onmousedown={handleOverlayMousedown}
onclick={handleOverlayClick}
onpointerdown={handleOverlayPointerdown}
onpointerup={handleOverlayPointerup}
onkeydown={handleKeydown}
data-testid="note-editor-overlay"
>
@@ -272,7 +255,6 @@
<div
class="mx-4 flex w-full max-w-xl md:max-w-2xl flex-col overflow-hidden rounded-sm border border-[var(--border)] shadow-[var(--card-shadow)] animate-[pop-in_150ms_ease-out]"
style={bgStyle}
onclick={(e) => e.stopPropagation()}
onkeydown={(e) => { e.stopPropagation(); handleKeydown(e); }}
data-testid="note-editor"
>
+4 -1
View File
@@ -62,8 +62,11 @@ test.describe('Offline Support', () => {
await expect(page.getByText('Survive Reload')).toBeVisible();
// When the network is restored and sync completes
const syncResponse = page.waitForResponse('**/api/sync');
const syncResponse = page.waitForResponse((resp) => resp.url().includes('/api/sync'));
await page.context().setOffline(false);
// Manually dispatch the online event as a safety net — in CI/headless
// environments, setOffline(false) doesn't always emit it reliably
await page.evaluate(() => window.dispatchEvent(new Event('online')));
await syncResponse;
// And the user reloads the page
+1 -1
View File
@@ -253,7 +253,7 @@
<li><a href="https://github.com/bretzel-app/crumbs/blob/main/docs/DEPLOYMENT.md" target="_blank" rel="noopener">Docs</a></li>
<li><a href="https://github.com/bretzel-app/crumbs/blob/main/LICENSE" target="_blank" rel="noopener">License</a></li>
</ul>
<span class="footer-version">v0.21.0</span>
<span class="footer-version">v0.21.1</span>
</div>
</footer>