From da90d1d50259dccfd1734da871d7547e19a8685c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 8 Aug 2026 19:39:07 +0000
Subject: [PATCH 1/3] feat: prevent empty messages and optimistic posting
Co-authored-by: benfoxall <51385+benfoxall@users.noreply.github.com>
---
src/components/form.test.ts | 60 ++++++++++++++++++++++++++-----------
src/components/form.ts | 40 ++++++++++++-------------
2 files changed, 61 insertions(+), 39 deletions(-)
diff --git a/src/components/form.test.ts b/src/components/form.test.ts
index 64b194d..d3f34f3 100644
--- a/src/components/form.test.ts
+++ b/src/components/form.test.ts
@@ -8,23 +8,15 @@ customElements.define('cycloops-form', CycloopsForm, { extends: 'form' })
vi.mock('../db', () => ({
db: {
notes: {
- add: vi.fn(),
+ add: vi.fn().mockResolvedValue(1),
+ update: vi.fn().mockResolvedValue(1),
},
},
}))
// Mock geolocation
const mockGeolocation = {
- getCurrentPosition: vi.fn().mockImplementation((success) =>
- Promise.resolve(
- success({
- coords: {
- latitude: 50,
- longitude: 50,
- },
- })
- )
- ),
+ getCurrentPosition: vi.fn(),
}
vi.stubGlobal('navigator', { geolocation: mockGeolocation })
@@ -37,22 +29,54 @@ describe('CycloopsForm component', () => {
form.innerHTML = ''
document.body.appendChild(form) // This should trigger connectedCallback
vi.clearAllMocks()
+ ;(db.notes.add as ReturnType).mockResolvedValue(1)
+ ;(db.notes.update as ReturnType).mockResolvedValue(1)
})
- it('should add a note on submit', async () => {
+ it('should optimistically add a note immediately with placeholder coords', async () => {
+ mockGeolocation.getCurrentPosition.mockImplementation(() => {
+ // never resolves during this test
+ })
+
const textarea = form.querySelector('textarea') as HTMLTextAreaElement
textarea.value = 'Test message'
- // The submit handler is async, so we need to wait for it to complete
await form.submitHandler(new Event('submit'))
expect(db.notes.add).toHaveBeenCalledOnce()
expect(db.notes.add).toHaveBeenCalledWith(
- expect.objectContaining({
- text: 'Test message',
- lat: 50,
- lon: 50,
- })
+ expect.objectContaining({ text: 'Test message', lat: 0, lon: 0 })
+ )
+ })
+
+ it('should update coords after geolocation resolves', async () => {
+ mockGeolocation.getCurrentPosition.mockImplementation((success: PositionCallback) =>
+ success({ coords: { latitude: 50, longitude: 50 } } as GeolocationPosition)
)
+
+ const textarea = form.querySelector('textarea') as HTMLTextAreaElement
+ textarea.value = 'Test message'
+
+ await form.submitHandler(new Event('submit'))
+
+ expect(db.notes.update).toHaveBeenCalledWith(1, { lat: 50, lon: 50 })
+ })
+
+ it('should not add a note when message is empty', async () => {
+ const textarea = form.querySelector('textarea') as HTMLTextAreaElement
+ textarea.value = ' '
+
+ await form.submitHandler(new Event('submit'))
+
+ expect(db.notes.add).not.toHaveBeenCalled()
+ })
+
+ it('should not add a note when message is blank', async () => {
+ const textarea = form.querySelector('textarea') as HTMLTextAreaElement
+ textarea.value = ''
+
+ await form.submitHandler(new Event('submit'))
+
+ expect(db.notes.add).not.toHaveBeenCalled()
})
})
\ No newline at end of file
diff --git a/src/components/form.ts b/src/components/form.ts
index 119136d..238e510 100644
--- a/src/components/form.ts
+++ b/src/components/form.ts
@@ -19,30 +19,28 @@ export class CycloopsForm extends HTMLFormElement {
e.preventDefault();
const data = new FormData(this);
- const text = data.get("message") as string;
- const time = Date.now();
+ const text = (data.get("message") as string).trim();
- const [lat, lon] = await new Promise<[number, number]>(
- (resolve, reject) => {
- navigator.geolocation.getCurrentPosition(
- (position) => {
- console.log(position.coords);
- resolve([position.coords.latitude, position.coords.longitude]);
- },
- () => {
- resolve([0, 0]);
- }
- );
- }
- );
+ if (!text) return;
+
+ const time = Date.now();
- await db.notes.add({
- time,
- text,
- lat,
- lon,
- });
+ // Optimistically add the note immediately so it appears in the list
+ const id = await db.notes.add({ time, text, lat: 0, lon: 0 });
this.reset();
+
+ // Update with real coordinates once geolocation resolves
+ navigator.geolocation.getCurrentPosition(
+ async (position) => {
+ await db.notes.update(id, {
+ lat: position.coords.latitude,
+ lon: position.coords.longitude,
+ });
+ },
+ async () => {
+ // Keep lat/lon as 0,0 on failure — note is already saved
+ }
+ );
}
}
From 482035eaebfa43e2756116981f41fb837a80049c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 8 Aug 2026 19:40:23 +0000
Subject: [PATCH 2/3] fix: review feedback - remove unnecessary async, clarify
test name
Co-authored-by: benfoxall <51385+benfoxall@users.noreply.github.com>
---
src/components/form.test.ts | 2 +-
src/components/form.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/components/form.test.ts b/src/components/form.test.ts
index d3f34f3..435ed4d 100644
--- a/src/components/form.test.ts
+++ b/src/components/form.test.ts
@@ -62,7 +62,7 @@ describe('CycloopsForm component', () => {
expect(db.notes.update).toHaveBeenCalledWith(1, { lat: 50, lon: 50 })
})
- it('should not add a note when message is empty', async () => {
+ it('should not add a note when message is whitespace only', async () => {
const textarea = form.querySelector('textarea') as HTMLTextAreaElement
textarea.value = ' '
diff --git a/src/components/form.ts b/src/components/form.ts
index 238e510..a8843e6 100644
--- a/src/components/form.ts
+++ b/src/components/form.ts
@@ -38,7 +38,7 @@ export class CycloopsForm extends HTMLFormElement {
lon: position.coords.longitude,
});
},
- async () => {
+ () => {
// Keep lat/lon as 0,0 on failure — note is already saved
}
);
From 0c8122ecd35a03a215378264a5d16134160794e2 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 8 Aug 2026 20:18:51 +0000
Subject: [PATCH 3/3] fix: exclude placeholder 0,0 coords from map
noteLocations
Co-authored-by: benfoxall <51385+benfoxall@users.noreply.github.com>
---
src/components/map.test.ts | 7 +++++++
src/components/map.ts | 4 +++-
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/components/map.test.ts b/src/components/map.test.ts
index 9520810..f8f84f7 100644
--- a/src/components/map.test.ts
+++ b/src/components/map.test.ts
@@ -21,6 +21,13 @@ describe('map computed signals', () => {
expect(noteLocations.value.features[1].properties.id).toBe(2)
})
+ it('noteLocations should exclude notes with placeholder 0,0 coordinates', () => {
+ const placeholder: Note = { id: 3, time: Date.now(), text: 'Pending', lat: 0, lon: 0 }
+ notes.value = [note1, placeholder]
+ expect(noteLocations.value.features.length).toBe(1)
+ expect(noteLocations.value.features[0].properties.id).toBe(1)
+ })
+
it('visibleLocations should filter notes based on visibility', () => {
notes.value = [note1, note2]
visible.value = new Set([1])
diff --git a/src/components/map.ts b/src/components/map.ts
index ff05366..394ec7d 100644
--- a/src/components/map.ts
+++ b/src/components/map.ts
@@ -11,7 +11,9 @@ type Locations = GeoJSON.FeatureCollection<
export const noteLocations = computed(() => ({
type: "FeatureCollection",
- features: notes.value.map((note) => ({
+ features: notes.value
+ .filter((note) => note.lat !== 0 || note.lon !== 0)
+ .map((note) => ({
type: "Feature",
properties: {
id: note.id,