Kanban board tasků — filtr dle priority, labelu nebo runu.
Nový test v package.spec ověřuje že non-JSON upstream odpověď produkuje 502 s error.contains('malformed').
Pokud npmjs.org vrátí 200 ale non-JSON tělo (HTML chybová stránka při výpadku), worker padal na náhodné 500. Teď try/catch okolo JSON.parse vrací strukturované 502 Bad Gateway.
Zápis tarballu do R2 cache (ctx.waitUntil) mohl selhat tiše s unhandled rejection. Přidán .catch s logováním package+key, klient dostane stream bez ohledu na cache.
Fallback tarbally se v R2 hromadily bez TTL — pro popular balíčky to mohlo růst do GB ročně. Daily scheduled volá funkci, která projde R2 list s customMetadata, filtruje source=fallback + uploaded > 90 dní a batch maže.
wrangler.jsonc triggers.crons '0 3 * * *' — denně ve 3:00 UTC. Cron registrace ověřena přes CF API workers/scripts/.../schedules.
Vitest 3 testy: recent fallback survive, own packages never deleted, no side effects. Backdate uploaded netestováno (R2 binding read-only), jen invariants pro in-TTL chování.
Astro 6 adapter generoval jen fetch handler, scheduled cron nešel jednoduše přidat. Vytvořen custom worker entry kombinující Astro handle (přes @astrojs/cloudflare/handler) a scheduled handler.
Run 012 nepokrýval success path proxy stahování. Test ověří round-trip: metadata fetch z fallbacku má dist.tarball přepsaný na vlastní host, druhý request tarball stream vrátí mock body byte-by-byte.
Scoped balíčky byly bug-prone (regex transformace + scope strip). Test ověří úplný řetězec: URL-encoded /@types%2Fnode metadata → rewrite na double-scope, navazující tarball download projde 5-seg scoped route a strip-scope upstream fetch.
Po prvním cache miss by druhý fetch měl jít z R2 bez upstream callu. Test pre-populuje BUCKET (eliminuje waitUntil timing flakiness) a ověří, že read s cache-hit metadatami vrátí body bez jakéhokoli mocku k upstream.
Service změnila kontrakt z R2ObjectBody na ReadableStream — router teď wrapuje do Response s Content-Type: application/gzip a předává c.executionCtx pro async cache write.
Fallback metadata vracela URL na npmjs.org — v plném proxy režimu klient pak chodil mimo Repoflare. Teď JSON.parse + per-version dist.tarball rewrite na vlastní host; scoped balíčky se transformují na Repoflare double-scope formát, aby matchly existující 5-segment scoped route.
Návod byl psaný jen pro plný proxy režim, scope-first nebyl vysvětlen. Sekce 2 přepsána na scope-first jako doporučené, sekce 5 přejmenována na 'Plný proxy režim' pro lockdown setupy s anchor odkazem mezi nimi.
Tarbally veřejných balíčků se stahovaly přímo z npmjs.org → v lockdown sítích nešly a R2 cache se neplnila. Service teď na R2 miss fetchne upstream, tee() split stream do cache i klientovi, ctx.waitUntil schová R2 put na pozadí.
Texty u prázdných seznamů byly nekonzistentní ('Žádné tokeny.' vs jiné varianty). Sjednoceno na vzor 'Zatím tu nejsou žádné X.' u tokenů i balíčků.
Astro default 404 vrací plain text. Přidána vlastní stránka 404.astro s BaseLayout, headerem a CTA tlačítky na úvod a dashboard.
Při zamítnutí oprávnění na clipboard padalo unhandled promise. Tlačítka pro kopírování teď mají tristate (idle/copied/failed) s 2s flashí 'Kopírování selhalo'.
Chybová hláška u tokenu byla jen tichý červený text. Nyní má výraznější border, dismiss tlačítko a aria-describedby, takže screen reader chybu spojí s polem názvu.
Klávesnicová a screen-reader přístupnost. Šipky '←' v back-link odkazech obaleny aria-hidden, fokus-visible ring doplněn na plain <a> odkazy, search input dostal aria-label a počet je aria-live.
Middleware isAstro allowlist neměl /docs. Rozšířen o pathname === '/docs' — request projde Access verify a renderuje docs.astro.
Návštěvníci neměli kde najít jak registry používat. Vytvořen src/pages/docs.astro: 6 sekcí v CS (vygenerovat token z dashboardu, nastavit .npmrc per-host i per-scope, publish, install s npm.org fallback, scoped balíčky, troubleshoot 401/403/409).
Homepage měla 3 tlačítka (Dashboard, API dokumentace, GitHub) — bez návodu. Přidán Návod button mezi Dashboard a API dokumentace, variant=outline (secondary CTA).
Access app chránila jen / a /dashboard*. /docs by bez Access destination obešel ochranu (Astro middleware sice gate-uje, ale CF Access by header neposlal). Přidán třetí destination 'npm.vyvoj.dev/docs' (exact match) do existing Access app via API.
Catch-all middleware z Run 002 delegoval všechny non-/dashboard requesty na Hono. Pro novou homepage / je potřeba povolit Astro render. Přidána explicit kontrola isAstroRoot — / projde na next() (Astro), zbytek non-/dashboard dál na Hono (NPM protokol).
https://npm.vyvoj.dev/ vracelo Hono 404 — návštěvník netušil co je to za službu. Vytvořen src/pages/index.astro s minimal landing: heading, 2 odstavce popisu (D1+R2+CF Access+bearer tokeny, jak nastavit .npmrc), 3 Buttons (Otevřít dashboard primary, API dokumentace outline, GitHub ghost external s rel=noopener).
Run 003 nainstaloval jen Button. Run 005-007 napsaly komponenty s plain HTML inputy/selecty/error bloky — proti shadcn rule 'always use components'. Doinstalován Input, Select, Card, Alert (+ Separator transitively) přes shadcn CLI. Z select-content.svelte odstraněn defaultní shadow-md (skill no-shadow rule).
CreateTokenForm měl plain input pro name, plain select pro scope, custom red text pro error, custom green border pro success token reveal — nekonzistentní s design systemem. Refactorováno: Input pro name, Select.Root/Trigger/Content/Item pro scope (s triggerLabel $derived), Alert variant=destructive pro error, Alert default pro success s code+copy uvnitř Description.
PackageSearch měl plain <input type=search> s ručně napsanými border classes. Nahrazeno za <Input type=search class='max-w-md'> — méně klutterového kódu, konzistentní s ostatními formuláři.
Delete button v tokens listu byl plain <button> s ručně napsaným destructive border + hover bg. Nahrazeno za <Button variant=destructive size=sm>. Astro renderuje SSR-only (žádný client:* directive), zero JS shipped pro tento widget — submit je native form behavior.
List balíčků se nedal prohledávat — při růstu počtu nepoužitelný. Vytvořen PackageSearch.svelte: Svelte 5 island s runes ($state pro query, $derived pro filtered list), case-insensitive substring match. Empty state rozlišuje 'no packages' vs 'no match for query'.
Detail page nezobrazila install command — user musel hádat package name pro npm install. Vytvořen InstallSnippet.svelte: code block s 'npm install <name>' + Copy button (navigator.clipboard) s 2s 'Zkopírováno' flash, stejný pattern jako CreateTokenForm.
Inline list rendering v index.astro znemožnoval interaktivní filter. Refactor: list přesunut do PackageSearch islandu (mount client:load, packages prop), SSR fetch zůstává v frontmatter. Index.astro je teď tenký shell.
Detail page chyběl install snippet. Mount InstallSnippet islandu mezi description a README sekci s pkg.name jako prop, client:load pro interaktivitu copy buttonu.
Pod /dashboard/tokens neexistovala žádná stránka. Vytvořen tokens/index.astro: SSR fetch tokenů, masked display (jen xxxx…yyyy, plaintext nikdy v listu), scope summary, formátované createdAt, delete form per řádek + mount Svelte create islandu nahoře.
Form pro create token potřebuje JS pro one-shot reveal plaintext tokenu (no-JS fallback by chtěl session storage flash). CreateTokenForm.svelte: Svelte 5 island s runes ( pro inputs/loading/error/copied, pro canSubmit), fetch POST → success render token + Copy button (navigator.clipboard) s 2s flash 'Zkopírováno'.
Tokens stránky neměly query helper, pattern stejný jako u packages. Přidán listTokens v dashboard-queries (findMany ordered desc by createdAt) + samostatný mask-token util pro UI display ve formátu xxxx…yyyy bez plaintextu.
Bez Astro endpointu by Svelte island neměl kam fetchnout. Hono /-/npm/v1/tokens potřebuje bearer token (assertTokenAccess) — CF Access user ho nemá. Vytvořen create.ts: POST endpoint pod /dashboard/tokens/create (uvnitř middleware gate), zod validace + delegace na tokenService.createToken, vrací 201 s plaintext tokenem.
Form delete potřeboval endpoint pod /dashboard/* (NE /api/*) jinak middleware skipne a Hono catch-all vrátí 404. Vytvořen [token]/delete.ts: POST handler, deleteToken + 303 redirect zpět na list. CF Access JWT funguje jako de facto CSRF token (cross-origin requesty bez něj dostanou 403).
Detail page chyběl celý. Vytvořen [...name].astro (rest param kvůli scoped balíčkům @scope/pkg), fetch package + latest release, render README přes renderMarkdown. 404 pokud balíček neexistuje, fallback hláška pokud manifest nemá readme field.
Dev escape hatch v middleware nechá Astro.locals.user = null bez Access headeru. Bez gate by neautorizovaný request prošel na dashboard. Přidán shared util require-user, který vrací Response 403 nebo AccessUser — explicit return je idiomatic v Astro page frontmatter (oproti throw).
Dashboard stránky neměly query helpery, raw SQL na page by zaplevelilo frontmatter. Přidán dashboard-queries.ts: listPackages (findMany ordered desc) + getPackageWithLatestRelease (2 queries — package, pak release dle distTags.latest). Relational with-filter nešel kvůli závislosti na parent column.
marked v18 neescapuje raw HTML ani nesanitizuje URL schémata — README publisher může injectnout <script> nebo [link](javascript:...). Přidán render-markdown.ts: Marked instance s renderer.html escape + allowlist URL schemes (http/https/mailto/relative) v link/image renderer.
Pod /dashboard byl prázdný placeholder z Run 003. Index page nyní SSR fetchuje balíčky přes listPackages, zobrazí name + latest version + cs-CZ formátované updated_at, link na detail. Empty state pro 0 balíčků.
Astro middleware měl jen Hono delegaci a /dashboard/* puštěné dál veřejně. Přidána větev: pro /dashboard* ověří Access JWT, jinak delegate na Hono beze změny. NPM protokol nezměněn.
JWT verify extracted do samostatného lib/access-jwt.ts. Module-level JWKS singleton (createRemoteJWKSet) sdílený přes requesty, jwtVerify s issuer + audience, payload.email uložen do Astro.locals.user.
TEAM_DOMAIN a POLICY_AUD přidány do vars v wrangler.jsonc s empty defaults — produkce doplní podle access-setup.md. Wrangler types regenerated, env.* je type-safe.
App.Locals neměl typ pro přihlášeného uživatele. Nový src/env.d.ts augmentuje App.Locals o user: AccessUser | null, takže pages pod /dashboard/* můžou číst locals.user.email type-safe.
Žádný návod, jak Access nakonfigurovat. Nový docs/access-setup.md popisuje Self-Hosted Application + Policy + zkopírování AUD tagu, dev escape hatch a troubleshooting.
shadcn-svelte CLI v1.2.7 vyžaduje interaktivní preset selection, neslo skriptovat. Manuálně vytvořeny components.json + src/lib/utils.ts s cn helperem + src/styles/global.css s neutral oklch tokens. Outcome ekvivalentní.
Dashboard byl bare HTML bez stylingu. Vytvořen src/layouts/BaseLayout.astro (slot + global.css import + viewport meta), dashboard/index.astro ho používá s Tailwind container utilities.
Plán chtěl ověřit Svelte hydration na CF Workers. pnpm dlx shadcn-svelte add button vygeneroval Button komponentu, dashboard ji ukazuje 2× — client:load (Svelte island, hydratovaný) a outline (SSR-only). Bonus: check-types přepnut z tsc na astro check kvůli Svelte type re-exports.
astro.config.mjs měl jen adapter, web UI nemělo žádný styling. Přidán svelte() integration + @tailwindcss/vite plugin + $lib alias přes vite.resolve. Tailwind classes a Svelte 5 hydration teď fungují v .astro souborech.
Původně catch-all endpoint v src/pages/[...npm].ts vyhodil 404 pro /_ui/, protože Astro routing prioritizuje endpoint nad page. Přepsáno na src/middleware.ts který volá next() pro /dashboard/* a app.fetch() pro vše ostatní — žádný preempt.
wrangler.jsonc: main na @astrojs/cloudflare/entrypoints/server (Astro 6 v13 unified entry), přidán account_id, custom_domain route na npm.vyvoj.dev, kv/r2/d1 bindings s reálnými ID. Worker name přejmenován na npm-vyvoj-dev.
vitest.config.ts měl stale wrangler.toml path a src/index.ts main z Run 001. Fix na wrangler.jsonc + nový hono-app.ts main. package.json: dev na astro dev, check-types doplněn o astro sync (Locals types). Bonus: turbo/biome/knip/sherif odstraněny.
Lokální astro build + vitest run prošly (55/55). Wrangler deploy --dry-run čistý. Po deploy do produkce smoke test přes curl: /dashboard/ → Astro hello-world 200, /_/docs → Scalar 200, /<package> → npm proxy 200, /-/npm/v1/tokens admin auth 200.
Hono app byl přímo v src/index.ts a sloužil jako Worker entry. Přesun do src/lib/hono-app.ts s dual exportem (named app + default app) — named pro Astro middleware delegaci, default kvůli vitest pool worker main.
git mv apps/api → apps/worker, package name @npflared/api → @npflared/worker. biome.json path update. Workspace glob apps/* nepotřeboval změnu.
wrangler.toml → wrangler.jsonc se referencí, observability:enabled, compatibility_date 2026-05-10. Bindings (D1, R2, vars) zachovány s původními placeholder hodnotami.
pnpm add astro 6.3, @astrojs/cloudflare 13.5, @astrojs/svelte 8.1, svelte 5.55, jose 6.2, marked 18.0. Wrangler bumped 4.58 → 4.90 protože adapter peer dep požaduje ^4.83.
astro.config.mjs minimální (jen adapter cloudflare()), svelte() zatím vypnutá komentem - aktivuje Run 003. Hello-world stránka pod /_ui/ namespace aby budoucí catch-all na rootu nekolidovala s npm protokolem.
pnpm astro build prošel s 0 errors. Build vyprodukoval dist/client + dist/server (Astro 6 nová struktura, ne stará dist/_worker.js). Acceptance kritérium runu splněno.