You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: address bugs B1–B8 from IMPROVEMENTS.md (#213)
* fix: address bugs B1–B8 from IMPROVEMENTS.md
B1 (App.svelte): store media query handler in named var so removeEventListener removes the correct reference
B2 (Menu.svelte): move ClipboardJS into onMount, narrow selector to [data-clipboard-text], destroy on cleanup
B3 (Board.svelte): guard accepts callback against missing card during store mid-update
B4 (Board.svelte): guard drop handler against missing card during store mid-update
B5 (api.js): throw on non-OK HTTP status in requestJson so callers can distinguish failure
B6 (firestore.js): optional-chain column/owner in normaliseCard to survive partial Firestore writes
B7 (firestore.js): replace private _document field access with Date.now() fallback
B8 (store.js): cancel stale passwordValid promises with a generation flag to prevent stale results
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: handle requestJson errors in Board.svelte callers; remove bugs section
- getBoard (onMount): wrap in try/catch so HTTP 404 navigates to /not-found
instead of propagating as an unhandled rejection
- updateBoard (board subscriber): use .catch() since the call is fire-and-forget
and a sync try/catch cannot catch async errors
- Remove resolved Bugs section from IMPROVEMENTS.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: IMPROVEMENTS.md
-181Lines changed: 0 additions & 181 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,187 +4,6 @@ Findings from a comprehensive review of the retro.tools frontend. Covers Svelte
4
4
5
5
---
6
6
7
-
## Bugs
8
-
9
-
### B1 — Memory leak in dark mode media query listener (`src/App.svelte:20–34`)
10
-
11
-
**Severity: High**
12
-
13
-
The `change` listener on `prefersDarkScheme` is added as an anonymous arrow function on line 20, but the `onMount` cleanup on line 33 tries to remove `darkModeChangeListener` — a different function reference used for the colorMode subscription. The anonymous media-query listener is never removed, leaking on every unmount.
14
-
15
-
```js
16
-
// Current (line 20): listener added as anonymous function
### B2 — ClipboardJS instance never stored or destroyed (`src/components/Menu.svelte:42`)
40
-
41
-
**Severity: High**
42
-
43
-
`new ClipboardJS("button")` runs at module scope (outside any lifecycle hook), so the instance is created but never stored or `.destroy()`-ed when the component unmounts. The `"button"` selector also matches all buttons in the document, including those not yet mounted.
44
-
45
-
**Fix:** Move into `onMount`, store the instance, destroy it in the cleanup:
### B4 — Null dereference in drag `drop` handler (`src/Board.svelte:80`)
82
-
83
-
**Severity: High**
84
-
85
-
Same pattern as B3. `const card = $cards.find(...)` is used immediately without a null check. If the card is absent, subsequent property access throws.
### B5 — `requestJson` doesn't check HTTP status (`src/api.js:16–18`)
100
-
101
-
**Severity: Medium**
102
-
103
-
```js
104
-
asyncfunctionrequestJson(input, init) {
105
-
return (awaitfetch(input, init)).json();
106
-
}
107
-
```
108
-
109
-
On a 4xx or 5xx response, `.json()` either throws a parse error (if the body isn't JSON) or silently decodes the error body as if it were valid data. Callers have no way to distinguish success from failure.
110
-
111
-
**Fix:**
112
-
113
-
```js
114
-
asyncfunctionrequestJson(input, init) {
115
-
constresponse=awaitfetch(input, init);
116
-
if (!response.ok) thrownewError(`HTTP ${response.status}`);
117
-
returnresponse.json();
118
-
}
119
-
```
120
-
121
-
---
122
-
123
-
### B6 — Missing null guards in `normaliseCard` (`src/firestore.js:115–116`)
124
-
125
-
**Severity: Medium**
126
-
127
-
`data.column.id` and `data.owner.id` are accessed directly without checking that `column` or `owner` exist. If a Firestore document is missing either field (due to a partial write, schema migration, or corrupted data), this throws inside the `onSnapshot` callback and breaks all card subscriptions silently.
128
-
129
-
**Fix:**
130
-
131
-
```js
132
-
column:data.column?.id??null,
133
-
owner:data.owner?.id===get(uid),
134
-
```
135
-
136
-
---
137
-
138
-
### B7 — Private Firestore SDK field access (`src/firestore.js:124`)
### B8 — Race condition in async derived store (`src/store.js:70–76`)
157
-
158
-
**Severity: Medium**
159
-
160
-
```js
161
-
exportconstpasswordValid=derived(
162
-
[board, password],
163
-
([$board, $password], set) => {
164
-
checkBoardPassword($board, $password).then(set);
165
-
},
166
-
null,
167
-
);
168
-
```
169
-
170
-
If `board` or `password` changes rapidly, multiple async calls can be in-flight simultaneously. Whichever resolves last wins — which may not be the most recent call. This can leave `passwordValid` set to a stale result.
171
-
172
-
**Fix:** Cancel the previous promise by tracking a generation counter:
173
-
174
-
```js
175
-
exportconstpasswordValid=derived(
176
-
[board, password],
177
-
([$board, $password], set) => {
178
-
let cancelled =false;
179
-
checkBoardPassword($board, $password).then((v) => { if (!cancelled) set(v); });
180
-
return () => { cancelled =true; };
181
-
},
182
-
null,
183
-
);
184
-
```
185
-
186
-
---
187
-
188
7
## Security
189
8
190
9
### S1 — Weak encryption: no key derivation, no authentication (`src/encryption.js:1–20`)
0 commit comments