Skip to content

feat(blog): sync loaders, types, and draft lifecycle with deco-cx/apps - #94

Open
aka-sacci-ccr wants to merge 2 commits into
mainfrom
blog-loaders-sync
Open

feat(blog): sync loaders, types, and draft lifecycle with deco-cx/apps#94
aka-sacci-ccr wants to merge 2 commits into
mainfrom
blog-loaders-sync

Conversation

@aka-sacci-ccr

@aka-sacci-ccr aka-sacci-ccr commented Aug 20, 2026

Copy link
Copy Markdown

Objetivo

Sincronizar o app blog do apps-start com o estado atual de deco-cx/apps@main (blog/ na v0.161.1, incluindo o #1658 — draft/published lifecycle), portando o que faz sentido no runtime TanStack Start.

O que foi portado

Ciclo de vida draft/published (paridade com deco-cx/apps#1658)

  • PostStatus (draft | published | archived | generating | awaiting_review) e isPublishedStatus() em blog/types.ts, com allowlist: status desconhecido = post que o CMS não considera pronto, logo fora das listagens.
  • Status ausente conta como publicado — posts antigos foram escritos antes do campo existir; exigir published explícito esvaziaria os blogs em produção no bump.
  • filterRoutablePosts() roda antes do slicePosts, então count continua entregando count posts renderizáveis. Também derruba posts sem slug (não têm rota).
  • BlogPostPage e BlogPostItem continuam servindo post não publicado — aquela página é o preview do CMS — mas forçam seo.noIndexing.

Loaders novos

  • blog/loaders/BlogpostList.tsBlogPost[] puro, sem wrapper de pageInfo/seo (diferente do BlogpostListing).
  • blog/loaders/BlogPostItem.ts — post único por slug, retornando BlogPost direto.
  • Ambos registrados no loaderMap.ts (chaves com e sem .ts, casando os __resolveType do app Deno) e no manifest.gen.ts.

Ajustes de fidelidade

  • handlePosts/sortPosts: parse de data pinado em UTC (ISO_WITHOUT_TIMEZONE), fallback 0 em data inválida para não vazar NaN no comparador, VALID_SORT_ORDERS validado.
  • BlogpostListing: categorias sempre retornadas (menus, chips) e falha na leitura delas não derruba a listagem; categoria ativa vem da collection, com a cópia inline do post como fallback.
  • GetCategories: isValidCategory extraído e reutilizado pelo BlogpostListing — categoria sem name/slug não é linkável e quebraria o localeCompare.
  • blog adicionado ao scripts/generate-manifests.ts, então o manifest agora é gerado e não editado à mão.
  • package.json: ./blog/commerceLoaders apontava para blog/commerceLoaders.ts, arquivo já renomeado para loaderMap.ts — subpath quebrado. Agora aponta para o arquivo certo e o nome atual (./blog/loaderMap) também está exportado.
  • Exports públicos: filterRoutablePosts, isPublishedStatus, PostStatus, Publisher.

O que ficou de fora (de propósito)

  • Ordenação por views (view_asc / view_desc) e todo o resto que depende de Deco Records/Drizzle: actions/submit{Rating,Review,View}, loaders/extensions/* (ratings/reviews), db/schema.ts. Sem Drizzle nesse runtime; SortBy não expõe as opções de view e um sortBy=view_* vindo da URL cai no default por data.
  • sections/* (Template, blocos de conteúdo, SEO JSON-LD) — o port é loaders-only; renderização fica no storefront.
  • imageCarousel em BlogPost — campo puramente de conteúdo, sem nenhuma section aqui para consumir.

Testes

blog/__tests__/handlePosts.test.ts e blog/__tests__/loaders.test.ts cobrem sort/filter/slice, o lifecycle de status, paginação, fallback de categoria e os dois loaders novos.

npm test        → 53 arquivos, 694 testes, todos passando
npm run check   → tsc + biome + knip limpos (warnings restantes são pré-existentes de outros apps)

Ports the recent deco-cx/apps blog work (up to #1658) to the TanStack
Start port. The loaders here are sync and have no ctx, so each change is
adapted rather than copied.

Types:
- PostStatus + isPublishedStatus() — an allowlist, so a status this app
  does not recognize is treated as not ready and never leaks into a
  listing. An absent status stays published: the field was added long
  after the first posts, so requiring it would empty every live blog.
- BlogPost.status / .dateModified, Author.type, Category.description /
  .sections, BlogPostListingPage.category / .categories, Publisher.

Core:
- dateToTime() compares full ISO timestamps and pins offset-less values
  to UTC, so ordering no longer depends on the machine timezone. The
  previous `${date}T00:00:00` produced NaN for any date carrying a time.
- filterRoutablePosts() drops slugless and unpublished posts before
  slicePosts, so `count` still yields `count` renderable posts.
- getRecordsByPath skips blocks with no string `name` instead of
  emitting a record with `id: undefined`.

Loaders:
- BlogPostItem / BlogPostPage still serve an unpublished post — that page
  is the CMS preview — but force noIndexing so it cannot be indexed.
- BlogpostListing always returns the category list and resolves the
  active category from the collection (so it carries description and
  sections), falling back to the copy inlined on a post.
- GetCategories filters categories missing a name or slug before sorting;
  the guard is shared with BlogpostListing as isValidCategory.
- New BlogpostList loader. The loader map pointed
  blog/loaders/BlogpostList at BlogpostListing, which returns a
  BlogPostListingPage rather than the BlogPost[] callers expect.

The manifest generator did not list blog, so blog/manifest.gen.ts was
stale and missing BlogPostItem. Added and regenerated.

Sections, actions, db/schema and the ratings/reviews/views extensions are
deliberately left out: they are Preact components or depend on drizzle
and ctx.invoke.records, neither of which exists in this port.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@aka-sacci-ccr
aka-sacci-ccr requested a review from a team August 20, 2026 19:21
The file was renamed to loaderMap.ts, but package.json kept exporting the
old path, so the subpath resolved to a file that no longer exists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant