Skip to content

Commit 8ed2ece

Browse files
committed
docs: update retrospective for v3.1
1 parent 734b67d commit 8ed2ece

1 file changed

Lines changed: 52 additions & 0 deletions

File tree

.planning/RETROSPECTIVE.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,54 @@
213213

214214
---
215215

216+
## Milestone: v3.1 — Deck Discovery
217+
218+
**Shipped:** 2026-03-16
219+
**Phases:** 4 | **Plans:** 8 | **Sessions:** ~3
220+
221+
### What Was Built
222+
- deck_index table with weighted tsvector/GIN fulltext search and immutable wrapper function for generated column
223+
- search_decks RPC with prefix matching for search-as-you-type, tag filtering, and card_count at query time
224+
- Subfolder-aware study RPCs: transparent subfolder_path filtering on 7 JOINs (zero signature changes)
225+
- docora-webhook deck.yaml detection, parsing (parseYaml reusing parseFrontmatter), and idempotent deck_index upsert/delete
226+
- deck-commit commit_yaml action with server-enforced author and lightweight YAML serialization
227+
- DeckMetadataForm in deck builder with collapsible UI, dirty tracking, YAML load/save, EN/IT i18n
228+
- Discovery tab (4th bottom tab) with search-as-you-type, tag chip bar, optimistic subscribe/unsubscribe
229+
- Shared deck entries in Repos screen with display_name enrichment from deck_index
230+
231+
### What Worked
232+
- End-to-end pipeline approach: DB schema → webhook indexing → deck builder metadata → mobile discovery — each phase cleanly consumed the prior one's output
233+
- Transparent subfolder filter pattern: added to 7 JOINs across 2 RPCs with zero signature changes — existing mobile code worked unchanged
234+
- parseYaml wrapper reusing parseFrontmatter avoided adding a YAML library to the edge function
235+
- Prefix matching fix found during device verification (search-as-you-type requires `:*` suffix, not websearch_to_tsquery)
236+
- Optimistic UI with Set-based key tracking for subscribe/unsubscribe gives instant feedback
237+
238+
### What Was Inefficient
239+
- websearch_to_tsquery chosen in Phase 41 had to be replaced with prefix matching in Phase 44 when device testing revealed partial typing didn't match — research didn't test search-as-you-type behavior
240+
- Client-side join between user_repositories and deck_index is a workaround for PostgREST not supporting embedding without FK — adds 2 round trips
241+
242+
### Patterns Established
243+
- Immutable tsvector wrapper: wrap generated column expression in IMMUTABLE SQL function when using STABLE functions
244+
- COALESCE-based unique index: for nullable columns in unique constraints
245+
- Transparent subfolder filter: `AND (ur.subfolder_path IS NULL OR c.file_path LIKE ur.subfolder_path || '%')` on every user_repositories JOIN
246+
- Optimistic UI with Set<string> rollback: add key immediately, revert on error
247+
- Debounced search via useRef setTimeout: 300ms on text, immediate on tag selection
248+
- Prefix tsquery pattern: exact match all words except last, `:*` on last word
249+
250+
### Key Lessons
251+
1. Search-as-you-type requires prefix matching (`:*`), not websearch_to_tsquery — test with partial input during research, not just complete words
252+
2. When PostgREST can't embed (no FK), client-side join with Map lookup is clean and fast enough for small result sets
253+
3. Transparent filter patterns (adding conditions to existing JOINs) are the cleanest way to extend RPCs without breaking callers
254+
4. Idempotent operations (UPSERT, 409-as-success) simplify webhook and UI code simultaneously
255+
5. Device verification catches real UX issues (search behavior, key collisions) that code review and unit tests miss
256+
257+
### Cost Observations
258+
- Model mix: 80% opus, 20% sonnet (quality profile)
259+
- Sessions: ~3 (4 days, with gap between Phase 43 and 44)
260+
- Notable: 4 phases, 8 plans, 17 tasks — full end-to-end pipeline from DB to mobile UI in 4 days
261+
262+
---
263+
216264
## Cross-Milestone Trends
217265

218266
### Process Evolution
@@ -224,6 +272,7 @@
224272
| v2.2 | ~2 | 2 | Cleanest milestone — zero deviations, zero issues |
225273
| v2.3 | ~1 | 2 | Fastest milestone — pure UI polish, same-day ship |
226274
| v3.0 | ~5 | 5 | First web app milestone; new platform (Vite/React SPA); clean sequential chain |
275+
| v3.1 | ~3 | 4 | First cross-platform pipeline (DB → webhook → web → mobile); prefix matching fix during device test |
227276

228277
### Cumulative Quality
229278

@@ -234,6 +283,7 @@
234283
| v2.2 || Session limit (manual) ||
235284
| v2.3 || UI polish (UAT) ||
236285
| v3.0 | 38+ (auth/theme/i18n/validation) | Lib layer | Vite 7, React 19, Tailwind 4, @uiw/react-md-editor, katex, yaml |
286+
| v3.1 | 5 (api.test.ts) | API layer | — (zero new dependencies) |
237287

238288
### Top Lessons (Verified Across Milestones)
239289

@@ -245,3 +295,5 @@
245295
6. Pure UI polish milestones are fast, low-risk, and benefit most from UAT (v2.3)
246296
7. Shared backend across multiple frontends is a massive complexity reducer — one auth, one DB, no sync (v3.0)
247297
8. Browser compatibility for npm packages must be verified during research, not mid-implementation (v3.0)
298+
9. Search-as-you-type requires prefix matching — test with partial input during research, not just complete words (v3.1)
299+
10. Transparent filter patterns on existing JOINs are the cleanest way to extend RPCs without breaking callers (v3.1)

0 commit comments

Comments
 (0)