Landing Pages — Hub Package Pages
Estrutura, meta tags Open Graph + Twitter Card, e composição da OG image para páginas de pacote individual no Koder Hub (`hub.koder.dev/apps/{slug}`, `/skills/{slug}`, `/bundles/{slug}`). Garante que sharing via WhatsApp/Facebook/Twitter/etc. mostre ícone + nome + descrição do pacote, não thumbnail genérico.
Spec — Hub Package Pages (Open Graph + Social Sharing)
*tatus:*Normative *pplies to:*Every package page rendered by the Koder Hub web client at https://hub.koder.dev/apps/{slug} (and equivalent under future kpkg.type paths: /skills/{slug}, /bundles/{slug}). *rigin:*Bug observed on 20260429 — sharing a Koder Hub package page via WhatsApp showed the generic Store thumbnail instead of the package's own icon, name and description, because the SPA serves a static OG-tagged index.html for every route.
1. Requirement
*very Hub package page MUST be discoverable to social-media scrapers*— WhatsApp, Facebook, Twitter/X, LinkedIn, Telegram, Slack, iMessage, Discord — with *ackage-specific*Open Graph metadata that yields a rich preview containing:
- The package's *con*(the same SVG/PNG master used by the package itself, scaled to OG dimensions).
- The package's *ame*(
namefield in the package'skpkg.toml/ Hub catalog entry). - The package's *rief description*(
descriptionfield, ≤200 chars).
Static SPA-served OG tags are *nsufficient*and violate this spec.
2. Implementation Contract
The Hub backend (products/dev/hub/depot/, currently products/dev/store/depot/) MUST handle package page URLs as follows:
2.1 Routing
The web server intercepts GET requests to /apps/{slug}, /skills/{slug}, /bundles/{slug}. For these routes:
- If the request comes from a *craper user-agent*(regex covering
facebookexternalhit,whatsapp,twitterbot,linkedinbot,slackbot,telegrambot,discordbot,applebot,redditbot,pinterest,skypeuripreview), or - If the query string contains
?og=1(manual override for testing), or - *lways*(preferred — avoids fragile UA sniffing and benefits humans whose browsers prefetch links),
the server returns a server-rendered HTML shell that contains the *omplete OG/Twitter Card meta block*for the specific package, followed by the SPA hydration script.
The hydrated SPA then takes over for interactive use as before. Result: humans see the same SPA UX, scrapers see correct meta.
2.2 Required meta tags per package page
<!-- Open Graph -->
<meta property="og:type" content="website">
<meta property="og:site_name" content="Koder Hub">
<meta property="og:url" content="https://hub.koder.dev/apps/{slug}">
<meta property="og:title" content="{name} — Koder Hub">
<meta property="og:description" content="{description}">
<meta property="og:image" content="https://hub.koder.dev/apps/{slug}/og-image.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="{name} — {short_tagline}">
<!-- Twitter / X -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{name} — Koder Hub">
<meta name="twitter:description" content="{description}">
<meta name="twitter:image" content="https://hub.koder.dev/apps/{slug}/og-image.png">
<!-- Canonical + page <title>/<meta name="description"> as usual -->
<title>{name} — Koder Hub</title>
<meta name="description" content="{description}">
<link rel="canonical" href="https://hub.koder.dev/apps/{slug}">2.3 OG image generation
Each package page exposes /apps/{slug}/og-image.png (and equivalent for skills, bundles). The image is *ynamically composed*by the Hub backend with the following layout (1200×630):
- Left third (400×630): the package's icon, centered, with subtle drop
shadow and the Hub's accent gradient as background. Icon is the master SVG rasterized to ~360×360 (transparent background preserved).
- Right two-thirds (800×630): the package's
name(large, bold, brandfont),
description(medium, two lines max with ellipsis), and the Koder Hub wordmark + URL in the bottom-right corner.
The composer reads from the catalog entry (no need to fetch the kpkg artifact). Cache the rendered PNG on first request, invalidate on package version bump.
Reference implementation lives in products/dev/hub/depot/handlers/og_image.go (to be created; see ticket).
2.4 Fallback
If the catalog entry is missing icon, name, or description (data integrity failure), serve the generic Hub OG image (hub.koder.dev/og-image.png) with og:title="{slug} — Koder Hub" and a placeholder description, AND emit a backend warning to the audit log so the gap can be fixed.
3. Validation
- Manual:
curl -sA "WhatsApp/2.0" https://hub.koder.dev/apps/koder-talk | grep -E 'og:|twitter:'returns the package-specific block. - Automated: a regression test (per
policies/regression-tests.kmd) hits theendpoint with each of the listed scraper user-agents AND with a regular browser UA, asserting that all required meta tags are present and that the image URL resolves to a non-empty PNG of the correct dimensions.
- The
/k-housekeepaudit MUST flag any package in the Hub catalog whosepage does not return correct OG meta when probed.
4. Coverage of Existing Pages
This spec applies retroactively. When the Hub backend gains the renderer (see ticket), every existing package in the registry is automatically covered without per-package work — the renderer reads from catalog data.
5. Non-Goals
- Marketing landing pages (
<product>.koder.dev/about, etc.) — covered bylanding-pages/products.kmd. This spec is for the *atalog*pages on the Hub itself, not for product landing pages. - Per-package custom OG art. The composer renders a uniform layout for
every package; allowing custom OG images is a future concern and would open the door to inconsistent shareability.
6. Related
landing-pages/products.kmd— OG conventions for product landing pages(1200×630 image, Twitter Card summarylargeimage, etc.)
landing-pages/catalog.kmd— top-level catalog page (hub.koder.dev)metadata
hub-RFC-006-unification-and-rename.md— Hub naming, supersedes "Store"references in any future spec text
- Bug origin: shared a package page via WhatsApp on 2026
0429; previewshowed generic Koder Hub thumbnail instead of package-specific.