Kanban board tasků — filtr dle priority, labelu nebo runu.
Hero search bar v patičce ukazoval mock 10 fake položek. Teď se po 250ms debounce zeptá API a klik na výsledek vede přímo na detail spotu. Generation-token guard zahodí pomalý response, kdyby user mezitím doťukal další písmeno.
Vyhledávací input nahoře na /mapa byl decorative — neposílal nic. Obalený do <form action=/mapa>, Enter teď refreshuje stránku s ?q=… a backend filtruje. Stávající filtry (amenities, terrain, rating) přežijí přes hidden inputy.
Backend listSpots neuměl text search — UI parametr ?q=... se ignoroval. Teď filtruje přes LIKE na názvu, regionu a popisu. SQLite default ASCII case-insensitive, diakritika přesný match (pro v1 dataset stovek spotů OK).
Záložka Oblíbené v profilu místo placeholderu načítá první stránku skutečně uložených spotů (přes /api/favorites) a zobrazí je stejnými kartami. Prázdný stav má vlastní hlášku a tlačítko na mapu.
Číslo u záložky Oblíbené bylo hardcoded 0. /api/users/me/stats teď vrací reálný počet z DB a profil ho zobrazí v záložce.
Smoke test produkce zbývá ručně po deploy: kliknout ♡ na kartě, ověřit že se ve /ucet objeví, refresh drží stav, druhý klik odebere.
♡ tlačítko na kartě a detailu spotu chybělo — bylo jen statický placeholder. Přidaný islet s animací prázdné→plné srdce, který reaguje hned (optimistic), a v případě chyby se vrátí do původního stavu. Pro nepřihlášené přesměruje na /sign-in.
Karty spotů (homepage Top spoty + /ucet) i stránka detailu teď mají funkční ♡. Backend vrací stav přes ?include=isFavorited, takže po refreshi srdce drží uloženou hodnotu.
Vytvořena migration 0007 + Drizzle schema pro user_favorite tabulku — relation many-to-many (user, spot) s unique(userId, spotId), cascade delete + 2 indexy pro listFavorites a include batch lookup. Před prvním deployem fronend musí proběhnout 'wrangler d1 migrations apply hammocknook-prod --remote'.
Přidán Effect Schema FavoriteResponse (id, userId, spotId, createdAt) v libs/api-models/src/favorite.ts + isFavorited optional Boolean field na SpotResponse. Reuse SpotListResponse pro GET /api/favorites — žádný extra DTO.
FavoritesApi HttpApiGroup s 3 endpointy v libs/api-models/src/api/favorites.ts: POST/DELETE /api/spots/:spotId/favorite + GET /api/favorites (auth, vrací plný SpotListResponse). Wired do HammocknookApi.
Handlery v api/src/api-groups/favorites.ts: idempotent POST přes onConflictDoNothing+SELECT fallback, idempotent DELETE bez existence check, listFavorites s INNER JOIN spot + cursor pagination + privacy filter (ADMIN vidí vše, jinak isPrivate=false OR createdBy=me).
?include=isFavorited query flag: listSpots batch lookup přes inArray() na page IDs (1 query pro celou stránku), getSpot single SELECT row check. Pro neauth user se flag tiše ignoruje, isFavorited se neobjeví v response.
Profil potřeboval real čísla místo hardcodovaných. Přibyl GET /api/users/me/stats — vrací počet spotů, recenzí, hlasů a datum registrace. 4 paralelní COUNT queries přes Effect.all, jeden D1 roundtrip.
Endpoint potřeboval session ověření a živou dokumentaci. Mount pod /api/users/* za sessionMiddleware, schema UserStatsResponse v api-models a OpenAPI 3.1 metadata pro Scalar UI.
Profil ukazoval jen 'X sdíleno' a hardcoded '14 lesů'. Header teď má 3 živá počítadla (sdíleno · recenzí · hlasů) + sekce 'S Hammocknookem od {měsíc rok}' z reálného joinedAt.
Taby na profilu byly statické. Teď fungují přes ?tab= URL parametr — 'Moje spoty' default ukazuje cards, 'Oblíbené' prázdný state (přijde Sprint 012). 'Výlety'/'Nastavení' zůstávají vizuálně disabled.
Doposud chybělo 9 auth endpointů v dokumentaci. Doplněn celý better-auth-parity router (sign-up/sign-in email i sociální, OAuth callback, refresh, sign-out, forget+reset password, verify-email). Body shapes jako generic object placeholder s odkazem na better-auth, protože ne Effect Schema.
Hlasování (upvote/downvote) bylo neviditelné v Scalar UI. Přidán PUT endpoint s VoteInput/VoteResponse schemas — body popsán jako idempotent toggle (1 upvote, -1 downvote, 0 remove) a response counters jako celkový stav po batch update.
Admin endpointy (role flip + anonymizace) chyběly. Doplněny s tagy ['Admin'], security pro session cookie a description vysvětlující last-admin guard (403 při pokusu sundat posledního ADMINa, 409 při pokusu ho anonymizovat).
Build celého OpenAPI dokumentu (Effect Schema → JSON Schema walk + ref rewrite) běžel při každém GET /openapi.json — zbytečný CPU cost. Přidán module-level cachedDocument; první request build-uje, další serve cached. Cold-start race je idempotent, takže safe.
Curl /openapi.json: 200 application/json, 31KB validní OpenAPI 3.1, 18 paths / 24 operations / 13 schemas. Sprint 016 hotovo, sprint.md status změněn na 'hotovo (čeká audit)'.
JSON Schema výstup byl ošklivý — Schema.Number povoluje NaN/Infinity přes string encoding (anyOf branche). Swap na Schema.Finite (dedikované schema, ne jen check) tyhle větve odstraní → lat/lng/rating mají v Scalar UI čisté {type:'number'}.
Spots měly hand-tagged jen GET list (R059 PoC). Doplněno POST /api/spots, GET /api/spots/{id}, PATCH /api/spots/{id}, DELETE /api/spots/{id} — security pro auth-protected, response refs na Spot.* schemas, error responses 400/401/403/404 dokumentovány.
Tři Photos endpointy chyběly v dokumentaci — POST link userFile ke spotu, GET list (s visitorOnly filtrem co odděluje hero galerii od galerie návštěvníků), DELETE unlink (multi-owner check: file owner / spot owner / ADMIN). Photo.* schemas přidány do components.
Čtyři Reviews endpointy doplněny: POST + GET pod /api/spots/{spotId}/reviews, PATCH + DELETE top-level /api/reviews/{reviewId}. Doc původně zmiňoval že ADMIN nesmí PATCH cizí recenze — code skutečně dovoluje, opraveno v review fází. Review.* schemas v components.
Curl /openapi.json vrátil 200 application/json (23KB validní OpenAPI 3.1, 6 paths / 12 operations / 11 named schemas). SpotResponse.lat má teď shape {type:'number'} bez anyOf NaN/Infinity větví. Typecheck/oxlint čistý, 35/35 vitest pass.
Endpoint pro list spotů (12 query params, response refs na SpotListResponse) je první a vzor pro R060/R061. Tagy, summary, parameters jsou ručně — Effect Schema neumí URL routing, takže path/query metadata se píší per-route.
Routes /docs (Scalar UI) a /openapi.json (runtime spec) namountovány v app.ts mimo blok session middleware — jsou public, žádný auth pro doc browsing. Pozice mimo chained routes const je záměr (nepatří do Hono RPC AppType, web wrapper je nepotřebuje).
curl /openapi.json vrátil 200 application/json (16KB validní OpenAPI 3.1: openapi/info/servers/components/paths). curl /docs vrátil 200 text/html (Scalar HTML shell). Typecheck/oxlint čistý, 35/35 vitest pass.
Kořenový openapi.json byl mrtvý NestJS dump z dob před přepisem na Hono. generate-openapi script ukazoval na neexistující soubor a táhl s sebou jiti devDep. Vše smazáno (api + root), místo file dumpu nasazujeme runtime endpoint. @scalar/hono-api-reference instalován jako základ pro UI.
Effect Schema (single source of truth pro DTO) se převádí na OpenAPI components přes builtin converter. Vlastní openapi.ts builder volá Schema.toJsonSchemaDocument na pojmenované schemas a toMultiDocumentOpenApi3_1 přepíše refy do OpenAPI tvaru. Bez ručního duplikování shape v Zod nebo jiném schema lib.