Skip to content

PRD: visitor-ready portfolio enrichment convergence (covers + fill) #172

Description

@xenodeve

EN

Problem Statement

Visitors still see an incomplete portfolio after the #167 owner-reset unblock. Most published projects lack category/technologies/tags/deep content (~40/47 empty at last audit), and projects without a live_url never enter the screenshot pipeline — so cards stay on placeholders (e.g. Hype-Macro_Store) even when taxonomy and case-study copy are filled. Hourly cron at cap 1 converges too slowly for a live agency site, and the public UI has no automated proof that an enriched project actually renders its category, tech, and content.

Solution

Make published portfolio pages visitor-ready by (1) filling AI-owned empty taxonomy and case-study fields through the existing secret-guarded generators without raising the 60s serverless hard limit unsafely, (2) adding a GitHub repo social-preview cover path when there is no usable live demo URL, and (3) locking visitor-visible enrichment with Playwright suite + real-browser smoke assertions on a known enriched project.

User Stories

  1. As a site visitor, I want project cards to show a real cover image, so that the portfolio does not look unfinished.
  2. As a site visitor, I want project detail pages to show category, technologies, tags, and deep content when the team has published the project, so that I can evaluate fit.
  3. As a site visitor, I want category and technology filters to include AI-filled taxonomy, so that I can browse beyond a single category.
  4. As a prospect, I want Hype-Macro_Store and similar filled projects to look complete (not placeholder-only), so that I trust the agency showcase.
  5. As an operator, I want generators to prefer empty auto-owned candidates, so that each capped run makes visible progress.
  6. As an operator, I want dry-run responses to distinguish listed vs eligible candidates, so that I do not misread “candidates:46” as “46 LLM jobs needed.”
  7. As an operator, I want cover backfill for GitHub-bound projects without live_url, so that screenshot cron is not a hard dependency on demos.
  8. As an operator, I want cover backfill to be dry-run by default and apply-gated, so that production writes stay intentional.
  9. As an operator, I want cover URLs validated as absolute http(s) images, so that broken meta tags do not poison snapshot_image.
  10. As an operator, I want human-owned empty fields to remain skipped, so that editorial ownership is respected (ADR + fix: unblock project enrichment — owner defaults block AI backfill #167 lesson).
  11. As an operator, I want taxonomy create-or-select to keep working during convergence, so that new niches are not blocked.
  12. As an operator, I want case-study generation to keep the empty+auto backfill gate (D4: deep-detail content backfill via case-study path #160), so that unchanged README sha does not block empty content forever.
  13. As an operator, I want hourly cron to stay at a safe per-run cap, so that Vercel 60s timeouts do not regress.
  14. As an operator, I want an AFK-safe way to run many sequential capped applies, so that the backlog can clear without raising CASE_STUDY_MAX_PER_RUN unsafely.
  15. As an admin, I want revalidation after successful applies, so that ISR/public pages show new copy and covers promptly.
  16. As a developer, I want pure selection helpers for GitHub-cover targets, so that eligibility is unit-tested without hitting the network.
  17. As a developer, I want OG extraction reuse for github.com repo pages, so that cover logic matches the D5: snapshot cover — Playwright when live_url set + OG fallback #161 live_url OG fallback pattern.
  18. As a developer, I want E2E to fail if an enriched published project renders without category or content, so that empty shells cannot ship unnoticed.
  19. As a developer, I want real-browser smoke on /projects and an enriched detail slug, so that Playwright suite alone is not the only FE gate.
  20. As a maintainer, I want bilingual issue/PR evidence for each slice, so that tracker history stays agent-readable.
  21. As a maintainer, I want this epic to leave D2: re-run capped taxonomy + content enrichment after owner reset (#167) #169 cron convergence as complementary ops, not a substitute for cover + FE proof.
  22. As a content editor, I want MangaDock and other human-curated fields untouched by auto cover/content writes when ownership is human.
  23. As a visitor on mobile, I want enriched detail layout to remain readable with cover + taxonomy chips, so that completeness is not desktop-only.
  24. As an operator, I want failed LLM/cover fetches to fail soft per project, so that one bad repo does not abort the batch.
  25. As an operator, I want missing README snapshots to remain a skip (not a crash), so that missing-readme cron can unblock those rows first.
  26. As a stakeholder, I want a closing scoreboard (has_cat / has_content / has_shot counts) when the epic finishes, so that “done” is measurable.

Implementation Decisions

  • Reuse existing generators for taxonomy and case-study; do not invent a parallel LLM pipeline.
  • Do not raise default CASE_STUDY_MAX_PER_RUN above 1 on the 60s Vercel function; measured ~29s/generation. Convergence uses sequential capped applies (cron + operator loop), not a bigger single request.
  • Taxonomy may keep env-tuned TAXONOMY_MAX_PER_RUN if measured safe; default stays conservative.
  • New cover path: for published GitHub-bound projects with null snapshot_image and no usable live_url, fetch the GitHub repository page social preview (og:image / twitter:image) and persist the absolute URL into snapshot_image (same field the cards already read). Prefer this over inventing a second image column.
  • Ordering: generator loops should prefer empty auto-owned / missing-cover candidates first so capped runs are not dominated by no-op scans of already-complete rows.
  • Reporting: responses should expose eligible vs scanned counts where today’s candidates is ambiguous (case-study lists all published GitHub projects).
  • Authz: secret header x-refresh-secret for operator/cron endpoints; dry-run default; apply:true required to persist (match live-url / taxonomy / case-study pattern).
  • FE verification: extend Playwright public smoke for an enriched slug; completion also requires interactive browser smoke per repo dual-FE gate.
  • Ownership: never overwrite human-owned fields; cover fill only when snapshot_image is null.
  • Out of process for schema: owner defaults already shipped in D3: schema/import defaults — AI-fillable owners default to auto (#167) #170/fix(db): AI-fillable project owners default to auto (#170) #171; this epic does not reopen that.

Testing Decisions

  • Good tests assert external behavior at agreed seams only (eligibility helpers, HTTP contract dry-run vs apply, public page shows enrichment) — not private method structure.
  • Prefer pure helper unit tests (Bun) for cover-target selection and OG URL resolution; controller/service specs follow existing taxonomy/case-study/live-url prior art under nestjs/test/.
  • Frontend: Playwright .e2e.ts plus real Chromium browser smoke; happy-dom unit tests are insufficient alone.
  • Expected values come from fixtures / known slugs (e.g. enriched project with category+content), not recomputed tautologies.

Out of Scope

  • Raising serverless maxDuration or moving generation to a worker queue (follow-up if needed).
  • Inventing live demos / fabricating live_url when none exists.
  • Re-ranking (ADR 0008) or redesign of project card chrome.
  • Bulk human editorial rewrite of AI copy.
  • Changing RLS / admin CMS edit UX.
  • Closing D2: re-run capped taxonomy + content enrichment after owner reset (#167) #169 solely by waiting on cron without the cover + FE proof slices.

Further Notes

Proposed test seams (confirm)

  1. Pure cover eligibility + OG resolve helpers (highest seam for cover rules; no I/O).
  2. Secret-guarded cover backfill HTTP contract (dry-run vs apply counts) — same style as live-url fill.
  3. Public project detail / listing behavior via Playwright + browser smoke (enriched slug shows category, content, non-placeholder cover when DB has snapshot).

TH

ปัญหา

หลังปลดบล็อก owner (#167) ผู้เยี่ยมชมยังเห็นพอร์ตโฟลิโอไม่ครบ — ส่วนใหญ่ยังไม่มีหมวด/เทค/แท็ก/เนื้อหาลึก และโปรเจกต์ไม่มี live_url จะไม่ได้เข้า pipeline ถ่ายภาพ จึงค้าง placeholder (เช่น Hype-Macro_Store) แม้มี copy แล้ว Cron ชั่วโมงละ 1 ช้าเกินไป และยังไม่มี E2E พิสูจน์ว่าโปรเจกต์ที่ enrich แล้วโชว์ข้อมูลจริง

ทางแก้

(1) เติม taxonomy + case-study ผ่าน generator เดิมแบบ capped ปลอดภัย (2) ดึงปกจาก GitHub social preview เมื่อไม่มี live demo (3) ล็อกด้วย Playwright + browser smoke บนสลักที่ enrich แล้ว

เรื่องนอกขอบเขต

ไม่เร่ง CASE_STUDY_MAX_PER_RUN บนฟังก์ชัน 60s · ไม่สร้าง live_url ปลอม · ไม่รีดีไซน์การ์ด · ไม่เปิดคิว worker ในรอบนี้

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestepicTracks a multi-issue featureready-for-agentFully specified, ready for an AFK agent

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions