Fetch blog posts from cl8y-research API (blocked until API is up) #7

Open
opened 2026-08-31 05:34:25 +00:00 by PlasticDigits · 1 comment
PlasticDigits commented 2026-08-31 05:34:25 +00:00 (Migrated from gitlab.com)

Summary

Blocked on the cl8y-research posts API being up. Do not merge a fetcher that points at an empty or unpublished API.

CL8Y-web stays a static Vite SPA: fetch published posts (list, body, hero, RSS as needed) from cl8y-research instead of compiling MDX from src/blog/posts/*.mdx and opening a marketing-site MR for every weekly recap.

Option B approved: gaps/GAP_1788152435.md §3.3. Research implementation: cl8y-research#2 (this ticket must stay is_blocked_by that issue).

Unblock signal: research GET /health (or /ready) is 200 and GET /v1/posts includes seeded cl8y-roadmap-cmm-bridge-yieldomega.


Current codebase

  • Routes: /blog (BlogListPage.tsx), /blog/:slug (BlogPostPage.tsx + ArticleLayout.tsx + SEO.tsx).
  • Index: src/blog/blogIndex.ts — import.meta.glob("./posts/*.mdx") + virtual:blog-post-meta.
  • Prerender: vite.config.ts getBlogPrerenderRoutes() from the posts directory.
  • RSS: scripts/generate-rss.mjs from MDX frontmatter; dev middleware /rss.xml.
  • One post: src/blog/posts/cl8y-roadmap-cmm-bridge-yieldomega.mdx + public/images/blog/cl8y-roadmap-cmm-bridge-yieldomega-hero.jpg.
  • Nav/footer: “Blog”; list eyebrow: “CL8Y Research”.
  • Playwright: zero /blog coverage (e2e/ is token, listings, chrome, discovery, trade).
  • Headers: clickjacking DENY / frame-ancestors 'none'; keep /blog rewrites (render.yaml, host-headers skill).
  • Voice remains blog_gen/SKILL.md. Do not move the worker into this repo (#4 moved to cl8y-research#1).

wordCount is computed at build from MDX. After fetch, use API-provided computed wordCount (research must not let the model author it).


Why this is needed

Per-post MDX MRs on the marketing site are slow and mix journalism with positioning/token-directory review. The worker already exists so this SPA does not hold Telegram/Replicate secrets. Fetching published JSON/JPEG/RSS keeps prerender/SEO if build or runtime fetch is done carefully.

The roadmap post must remain on cl8y.com the day the glob is removed — that requires the research import seed to be live first.


Constraints / guardrails

  1. Do not ship the fetcher to production until the API is up and returns the seeded post. This issue is gated.
  2. Do not import cl8y-research as an npm package. HTTPS GET only.
  3. Do not put REPLICATE_* / TELEGRAM_* / DATABASE_URL in Vite env.
  4. Public env: VITE_RESEARCH_API_ORIGIN (or similar) = origin only, https, allowlisted host (e.g. https://research.cl8y.com) — same host-header discipline as other product URLs (GitLab #3).
  5. Do not remount RETIRED_HOMEPAGE_MODULES. Do not rewrite CL8Y_WHITEPAPER.md. Do not change #token directory (GitLab #2) except links into blog if already present.
  6. Empty/error/loading states on /blog and /blog/:slug. No blank white page if API is down; do not fall back to executing random MDX from the network without sanitizing (prefer JSON + a constrained renderer, or precompiled HTML from API if research serves it).
  7. XSS: do not dangerouslySetInnerHTML on API HTML unless research guarantees sanitized HTML and CSP is reviewed. Prefer MDX compile at API publish time to a safe representation, or keep Markdown-to-React with the same sanitizer rules.
  8. CORS is research’s job; this app only calls the configured origin.
  9. Keep X-Frame-Options / CSP for /blog/*.
  10. No auto-deploy coupling that publishes drafts.

Relevant files

Path Role
src/blog/blogIndex.ts Glob index to replace
src/blog/BlogListPage.tsx List UI
src/blog/BlogPostPage.tsx Post + 404
src/blog/ArticleLayout.tsx Hero, reading time
vite.config.ts Prerender routes, virtual meta
scripts/generate-rss.mjs RSS from MDX
render.yaml / _redirects /blog rewrites
e2e/*.spec.ts Add blog specs
src/lib/siteUrls.ts / env API origin
blog_gen/SKILL.md Voice unchanged

  1. Wait for research public GET contract (OpenAPI/schema).
  2. Fetch at build/prerender (preferred for OG/SEO): Vite plugin hits GET /v1/posts and /v1/posts/:slug at build; prerender those routes; RSS from API or from fetched meta. Runtime fetch as enhancement for freshness between deploys or webhook-triggered rebuild (document).
  3. Heroes: <img src="{API}/v1/images/blog/{slug}-hero.jpg"> or copy into dist at build.
  4. Playwright: /blog, featured title of seeded post, /blog/:slug, 404 slug, (optional) RSS contains item.
  5. Remove git MDX as source of truth after seed is confirmed live; keep files only if a short dual-run is needed, then delete in a follow-up commit.

Acceptance criteria

  • Gated: production/staging API origin documented; /v1/posts contains cl8y-roadmap-cmm-bridge-yieldomega.
  • /blog lists that post (title/date) without MDX glob as the source of truth.
  • /blog/cl8y-roadmap-cmm-bridge-yieldomega renders body + hero (no 404 image).
  • Unknown slug 404 UI.
  • RSS still has the item (from API or generated from fetch).
  • yarn test && yarn typecheck && yarn lint && yarn build green.
  • Playwright covers /blog and one post + 404.
  • API down at build fails closed or uses last known snapshot (document); API down at runtime (if used) shows error state, not a crash loop.
  • No worker secrets in the SPA bundle (grep dist).

Test plan — functional paths

  1. Mock research API in Vitest/Playwright with the seeded post JSON + JPEG.
  2. List, post, 404, RSS.
  3. Prerender meta verify (scripts/verify-prerender-meta.mjs) includes /blog/<slug>.
  4. Empty list → empty state, not exception.

Test plan — attack, hack, and abuse

  1. XSS — API body containing <script>alert(1)</script> must not execute in the browser.
  2. javascript: href — stripped or non-clickable.
  3. Lookalike product URL — bridge.cl8y.com.evil not linked.
  4. Open redirect via API image field (//evil.test or javascript:).
  5. Origin confusion — VITE_RESEARCH_API_ORIGIN cannot be a relative URL or http:// in production builds.
  6. Clickjacking — /blog/* still DENY.
  7. Draft leak — client never requests ?status=draft and ignores unknown fields that would show unpublished.

Verification criteria

  1. Research issue accepted (health + seeded post).
  2. Reviewer loads /blog and the roadmap article against staging API.
  3. dist/ has no REPLICATE / BOT_TOKEN.
  4. Playwright blog specs green in CI.
## Summary **Blocked on the cl8y-research posts API being up.** Do not merge a fetcher that points at an empty or unpublished API. `CL8Y-web` stays a static Vite SPA: **fetch published posts** (list, body, hero, RSS as needed) from `cl8y-research` instead of compiling MDX from `src/blog/posts/*.mdx` and opening a marketing-site MR for every weekly recap. Option B approved: [`gaps/GAP_1788152435.md`](https://gitlab.com/PlasticDigits/cl8y-research/-/blob/main/gaps/GAP_1788152435.md) §3.3. Research implementation: **[cl8y-research#2](https://gitlab.com/PlasticDigits/cl8y-research/-/issues/2)** (this ticket must stay `is_blocked_by` that issue). Unblock signal: research `GET /health` (or `/ready`) is 200 **and** `GET /v1/posts` includes seeded `cl8y-roadmap-cmm-bridge-yieldomega`. --- ## Current codebase - Routes: `/blog` (`BlogListPage.tsx`), `/blog/:slug` (`BlogPostPage.tsx` + `ArticleLayout.tsx` + `SEO.tsx`). - Index: `src/blog/blogIndex.ts` — `import.meta.glob("./posts/*.mdx")` + `virtual:blog-post-meta`. - Prerender: `vite.config.ts` `getBlogPrerenderRoutes()` from the posts directory. - RSS: `scripts/generate-rss.mjs` from MDX frontmatter; dev middleware `/rss.xml`. - **One post:** `src/blog/posts/cl8y-roadmap-cmm-bridge-yieldomega.mdx` + `public/images/blog/cl8y-roadmap-cmm-bridge-yieldomega-hero.jpg`. - Nav/footer: “Blog”; list eyebrow: “CL8Y Research”. - Playwright: **zero** `/blog` coverage (`e2e/` is token, listings, chrome, discovery, trade). - Headers: clickjacking `DENY` / `frame-ancestors 'none'`; keep `/blog` rewrites (`render.yaml`, host-headers skill). - Voice remains `blog_gen/SKILL.md`. Do not move the worker into this repo ([#4](https://gitlab.com/PlasticDigits/CL8Y-web/-/issues/4) moved to cl8y-research#1). `wordCount` is computed at **build** from MDX. After fetch, use API-provided computed `wordCount` (research must not let the model author it). --- ## Why this is needed Per-post MDX MRs on the marketing site are slow and mix journalism with positioning/token-directory review. The worker already exists so this SPA does not hold Telegram/Replicate secrets. Fetching published JSON/JPEG/RSS keeps prerender/SEO if build or runtime fetch is done carefully. The roadmap post must remain on cl8y.com the day the glob is removed — that requires the research **import seed** to be live first. --- ## Constraints / guardrails 1. **Do not ship the fetcher to production until the API is up** and returns the seeded post. This issue is gated. 2. Do not import `cl8y-research` as an npm package. HTTPS GET only. 3. Do not put `REPLICATE_*` / `TELEGRAM_*` / `DATABASE_URL` in Vite env. 4. Public env: `VITE_RESEARCH_API_ORIGIN` (or similar) = origin only, https, allowlisted host (e.g. `https://research.cl8y.com`) — same host-header discipline as other product URLs (GitLab #3). 5. Do not remount `RETIRED_HOMEPAGE_MODULES`. Do not rewrite `CL8Y_WHITEPAPER.md`. Do not change `#token` directory (GitLab #2) except links into blog if already present. 6. Empty/error/loading states on `/blog` and `/blog/:slug`. No blank white page if API is down; do not fall back to executing random MDX from the network without sanitizing (prefer JSON + a constrained renderer, or precompiled HTML from API if research serves it). 7. XSS: do not `dangerouslySetInnerHTML` on API HTML unless research guarantees sanitized HTML and CSP is reviewed. Prefer MDX compile **at API publish time** to a safe representation, or keep Markdown-to-React with the same sanitizer rules. 8. CORS is research’s job; this app only calls the configured origin. 9. Keep `X-Frame-Options` / CSP for `/blog/*`. 10. No auto-deploy coupling that publishes drafts. --- ## Relevant files | Path | Role | | --- | --- | | `src/blog/blogIndex.ts` | Glob index to replace | | `src/blog/BlogListPage.tsx` | List UI | | `src/blog/BlogPostPage.tsx` | Post + 404 | | `src/blog/ArticleLayout.tsx` | Hero, reading time | | `vite.config.ts` | Prerender routes, virtual meta | | `scripts/generate-rss.mjs` | RSS from MDX | | `render.yaml` / `_redirects` | `/blog` rewrites | | `e2e/*.spec.ts` | Add blog specs | | `src/lib/siteUrls.ts` / env | API origin | | `blog_gen/SKILL.md` | Voice unchanged | --- ## Recommended direction 1. Wait for research public GET contract (OpenAPI/schema). 2. Fetch at **build/prerender** (preferred for OG/SEO): Vite plugin hits `GET /v1/posts` and `/v1/posts/:slug` at build; prerender those routes; RSS from API or from fetched meta. Runtime fetch as enhancement for freshness between deploys **or** webhook-triggered rebuild (document). 3. Heroes: `<img src="{API}/v1/images/blog/{slug}-hero.jpg">` or copy into `dist` at build. 4. Playwright: `/blog`, featured title of seeded post, `/blog/:slug`, 404 slug, (optional) RSS contains item. 5. Remove git MDX as source of truth after seed is confirmed live; keep files only if a short dual-run is needed, then delete in a follow-up commit. --- ## Acceptance criteria - [ ] **Gated:** production/staging API origin documented; `/v1/posts` contains `cl8y-roadmap-cmm-bridge-yieldomega`. - [ ] `/blog` lists that post (title/date) without MDX glob as the source of truth. - [ ] `/blog/cl8y-roadmap-cmm-bridge-yieldomega` renders body + hero (no 404 image). - [ ] Unknown slug 404 UI. - [ ] RSS still has the item (from API or generated from fetch). - [ ] `yarn test && yarn typecheck && yarn lint && yarn build` green. - [ ] Playwright covers `/blog` and one post + 404. - [ ] API down at **build** fails closed or uses last known snapshot (document); API down at **runtime** (if used) shows error state, not a crash loop. - [ ] No worker secrets in the SPA bundle (`grep` dist). --- ## Test plan — functional paths 1. Mock research API in Vitest/Playwright with the seeded post JSON + JPEG. 2. List, post, 404, RSS. 3. Prerender meta verify (`scripts/verify-prerender-meta.mjs`) includes `/blog/<slug>`. 4. Empty list → empty state, not exception. --- ## Test plan — attack, hack, and abuse 1. **XSS** — API body containing `<script>alert(1)</script>` must not execute in the browser. 2. **javascript: href** — stripped or non-clickable. 3. **Lookalike product URL** — `bridge.cl8y.com.evil` not linked. 4. **Open redirect** via API `image` field (`//evil.test` or `javascript:`). 5. **Origin confusion** — `VITE_RESEARCH_API_ORIGIN` cannot be a relative URL or `http://` in production builds. 6. **Clickjacking** — `/blog/*` still DENY. 7. **Draft leak** — client never requests `?status=draft` and ignores unknown fields that would show unpublished. --- ## Verification criteria 1. Research issue accepted (health + seeded post). 2. Reviewer loads `/blog` and the roadmap article against staging API. 3. `dist/` has no `REPLICATE` / `BOT_TOKEN`. 4. Playwright blog specs green in CI.
PlasticDigits commented 2026-08-31 05:35:43 +00:00 (Migrated from gitlab.com)

Gate: GitLab Free cannot set is_blocked_by. This issue relates to cl8y-research#2.

Do not merge the fetcher until that API is up: GET /health (or /ready) is 200 and GET /v1/posts includes seeded cl8y-roadmap-cmm-bridge-yieldomega.

**Gate:** GitLab Free cannot set `is_blocked_by`. This issue **relates to** [cl8y-research#2](https://gitlab.com/PlasticDigits/cl8y-research/-/issues/2). Do not merge the fetcher until that API is up: `GET /health` (or `/ready`) is 200 **and** `GET /v1/posts` includes seeded `cl8y-roadmap-cmm-bridge-yieldomega`.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/CL8Y-web#7
No description provided.