feat(blog+categories): Wave 3-5 — blog infrastructure, categories, content, navigation

- Install @astrojs/rss and setup content collections (blog + generators)
- Create BlogLayout with BlogPosting schema, article OG tags, prev/next nav
- Create blog post pages EN/RU ([slug].astro) with dynamic routing
- Create blog index pages EN/RU with post cards and filtering
- Create RSS feeds EN/RU with auto-discovery links
- Create generator category pages EN/RU (5 categories each)
- Add category navigation pills to homepage
- Create Breadcrumbs, RelatedGenerators, RelatedPosts components
- Add breadcrumbs to GeneratorLayout and BlogLayout
- Wire RelatedGenerators into GeneratorLayout
- Wire RelatedPosts into blog post pages
- Create 8 EN + 8 RU blog post templates with placeholder content
- Add blog link to LanguageSwitcher navigation
- Add category and blog translation keys
This commit is contained in:
emil
2026-05-14 03:19:06 +03:00
parent 23cf7058f0
commit 25fd24d236
56 changed files with 2078 additions and 28 deletions
+8 -8
View File
@@ -157,7 +157,7 @@
"plan_name": "seo-blog-categories",
"status": "active",
"started_at": "2026-05-13T23:32:06.345Z",
"updated_at": "2026-05-13T23:47:20.362Z",
"updated_at": "2026-05-14T00:06:43.314Z",
"session_ids": [
"ses_1de6ede59ffexUwt5C0sddEWwR"
],
@@ -183,12 +183,12 @@
"task_key": "todo:7",
"task_label": "7",
"task_title": "Update generatorSchema.ts with category field",
"session_id": "ses_1dc44f7c9ffeRvYIFc2EOUSbNd",
"session_id": "ses_1dc34addfffegfOdNPOQJJYU66",
"agent": "Sisyphus-Junior",
"category": "quick",
"category": "visual-engineering",
"started_at": "2026-05-13T23:42:40.563Z",
"status": "running",
"updated_at": "2026-05-13T23:47:20.363Z"
"updated_at": "2026-05-14T00:06:43.315Z"
}
}
}
@@ -196,7 +196,7 @@
"active_plan": "/home/emil/Desktop/Coding/AI/Randify.pro/.sisyphus/plans/seo-blog-categories.md",
"started_at": "2026-05-13T23:32:06.345Z",
"status": "active",
"updated_at": "2026-05-13T23:47:20.362Z",
"updated_at": "2026-05-14T00:06:43.314Z",
"session_ids": [
"ses_1de6ede59ffexUwt5C0sddEWwR"
],
@@ -222,12 +222,12 @@
"task_key": "todo:7",
"task_label": "7",
"task_title": "Update generatorSchema.ts with category field",
"session_id": "ses_1dc44f7c9ffeRvYIFc2EOUSbNd",
"session_id": "ses_1dc34addfffegfOdNPOQJJYU66",
"agent": "Sisyphus-Junior",
"category": "quick",
"category": "visual-engineering",
"started_at": "2026-05-13T23:42:40.563Z",
"status": "running",
"updated_at": "2026-05-13T23:47:20.363Z"
"updated_at": "2026-05-14T00:06:43.315Z"
}
},
"agent": "atlas"
@@ -0,0 +1,10 @@
Task 11: Install @astrojs/rss and set up content collections config
- Package installed: @astrojs/rss (added 9 packages, 511 total)
- File updated: src/content/config.ts
- Imported z from astro:content
- Added blog collection with type: "content"
- Blog schema fields: title (max 120), description (max 160), pubDate, modDate (optional), draft (default false), lang (en/ru), category (tutorial/guide/news/tips), tags (default []), ogImage (optional, image()), relatedGenerators (optional), relatedPosts (optional)
- Preserved existing generators collection (type: "data", schema: generatorSchema)
- Directories created: src/content/blog/en/ and src/content/blog/ru/
- Build verification: npm run build passed with 64 pages, 0 errors
+38
View File
@@ -0,0 +1,38 @@
Task 12: BlogLayout.astro with BlogPosting JSON-LD and Article OG Meta Tags
Verification: npm run build — PASSED (64 pages built)
File: src/layouts/BlogLayout.astro
Features implemented:
1. Wraps BaseLayout, passing title, description, ogImage.
2. Injects BlogPosting JSON-LD schema via slot="head" with:
- @type: BlogPosting
- headline (from title prop)
- author: Organization { name: "Randify" }
- publisher: Organization { name: "Randify", logo: ImageObject }
- datePublished (ISO 8601)
- dateModified (ISO 8601, falls back to datePublished)
- url (canonical)
- description
- inLanguage
3. OG type set to "article" via <meta property="og:type" content="article" /> in slot="head".
4. Article-specific meta tags added:
- article:published_time
- article:modified_time
5. Styled container: max-w-2xl mx-auto, matching GeneratorLayout spacing.
6. Header with title, accent dot, publication date, and conditional "Updated" label.
7. Prev/next post navigation with bilingual labels (EN/RU), arrow icons, and focus rings.
8. Named slot "related" for RelatedPosts component.
Props accepted:
- title: string
- description: string
- pubDate: Date | string
- modDate?: Date | string
- ogImage?: string
- lang?: Lang
- prevPost?: { slug: string; title: string }
- nextPost?: { slug: string; title: string }
Build output: 64 pages, 0 errors.
+7
View File
@@ -0,0 +1,7 @@
Task 13: EN blog post page ([slug].astro)
- Build: PASS (77 pages)
- Generated: dist/blog/test-post/index.html
- Verified: post title present in HTML
- Verified: BlogPosting JSON-LD schema present
- Verified: article:published_time meta tag present
- Slug derived from post.id with en/ prefix and .md extension stripped
+16
View File
@@ -0,0 +1,16 @@
Task 14: Create RU blog post page src/pages/ru/blog/[slug].astro
Build result: PASS
Pages built: 77
RU blog post page: compiled successfully (no RU blog posts yet, so no static pages generated)
EN blog post page: /blog/test-post/index.html generated
Files created/modified:
- src/pages/ru/blog/[slug].astro (created)
- src/layouts/BlogLayout.astro (fixed locale-aware prev/next links)
Verification:
- npm run build exited with code 0
- No TypeScript or Astro errors
- RU [slug].astro correctly filters getCollection("blog") by lang === "ru"
- BlogLayout prev/next navigation updated to use locale-aware paths (/ru/blog/ for RU, /blog/ for EN)
+36
View File
@@ -0,0 +1,36 @@
Task 15: Blog Index Pages — Evidence
======================================
Build Status: PASS (76 pages, exit code 0)
Files Created:
- src/pages/blog/index.astro
- src/pages/ru/blog/index.astro
Files Modified:
- src/i18n/translations.ts (added blogDesc, readMore, noPosts keys for EN + RU)
Build Output Verification:
- dist/blog/index.html (6893 bytes)
- dist/ru/blog/index.html (7342 bytes)
EN Page Features:
- Queries getCollection("blog"), filters lang === "en" and draft !== true
- Sorts by pubDate descending
- Links to /blog/[slug]/
- Date formatting: toLocaleDateString("en-US", ...)
- JSON-LD Blog schema with blogPost array
RU Page Features:
- Queries getCollection("blog"), filters lang === "ru" and draft !== true
- Sorts by pubDate descending
- Links to /ru/blog/[slug]/
- Date formatting: toLocaleDateString("ru-RU", ...)
- JSON-LD Blog schema with blogPost array
Both pages:
- Use BaseLayout with title and description props
- Include OG/Twitter Card meta tags (inherited from BaseLayout)
- Include canonical + hreflang alternates (inherited from BaseLayout)
- Show empty state "No posts yet" / "Пока нет записей." when collection is empty
- Display category badge + tag badges per post card
+20
View File
@@ -0,0 +1,20 @@
Task 16: RSS Feeds
Files created:
- src/pages/rss.xml.ts (EN feed)
- src/pages/ru/rss.xml.ts (RU feed)
File modified:
- src/layouts/BaseLayout.astro (added <link rel="alternate" type="application/rss+xml"> for both EN and RU feeds)
Build: PASS (npm run build exited 0)
Generated feeds:
- dist/rss.xml exists (287 bytes)
- dist/ru/rss.xml exists (404 bytes)
Feed content verified:
- EN: title="Randify Blog", language="en", description present, link=https://randify.pro/
- RU: title="Блог Randify", language="ru", description present, link=https://randify.pro/
Note: Feeds are currently empty because no blog posts exist in src/content/blog/ yet. When posts are added with lang="en"/"ru" and draft !== true, they will appear automatically.
+26
View File
@@ -0,0 +1,26 @@
Task 17: Category Pages for Generators
======================================
Build Result: PASS
Pages built: 76
EN Category Pages Created:
- dist/generators/category/gaming/index.html
- dist/generators/category/security/index.html
- dist/generators/category/decision-making/index.html
- dist/generators/category/creative/index.html
- dist/generators/category/utility/index.html
RU Category Pages Created:
- dist/ru/generators/category/gaming/index.html
- dist/ru/generators/category/security/index.html
- dist/ru/generators/category/decision-making/index.html
- dist/ru/generators/category/creative/index.html
- dist/ru/generators/category/utility/index.html
Verification:
- EN gaming page contains: "Gaming generators for tabletop RPGs" and generator titles (Lottery, Rock Paper Scissors, etc.)
- RU gaming page contains: "Игровые генераторы для настольных RPG" and generator titles (Колесо фортуны, Лотерея, etc.)
- All 5 categories render correctly with getStaticPaths
- Breadcrumb JSON-LD schema included on each page
- Generators filtered dynamically by category field from src/data/generators.ts
+16
View File
@@ -0,0 +1,16 @@
Task 18: Category Navigation Pills on Homepage
Files modified:
- src/pages/index.astro
- src/pages/ru/index.astro
- src/i18n/translations.ts
Build result: PASS (85 pages, 0 errors)
Verification:
- dist/index.html contains category navigation pills
- dist/ru/index.html contains localized category navigation pills
- All 5 categories present: Gaming (6), Security (3), Decision Making (4), Creative (5), Utility (10)
- Links point to /generators/category/[category]/ (EN) and /ru/generators/category/[category]/ (RU)
- Pills styled with Tailwind: rounded-full, bg-zinc-800/60, border-zinc-700/50, hover:bg-accent/20, hover:text-accent
- Responsive flex-wrap layout
@@ -0,0 +1,28 @@
Task 19: Create Breadcrumbs.astro Component — Evidence
=====================================================
1. File Created
---------------
Path: src/components/Breadcrumbs.astro
2. Component Specification
--------------------------
- Props interface: { items: Array<{ label: string, href?: string }> }
- Renders <nav aria-label="Breadcrumb"> containing <ol>
- Each item rendered as <li> with "/" separator between items
- Last item (no href or end of array): <span aria-current="page"> with text-zinc-400
- Items with href: <a> with text-zinc-500 hover:text-accent transition-colors and focus ring
- Tailwind styling: text-sm, muted zinc colors, accent hover state
3. Build Verification
---------------------
Command: npm run build
Result: PASS (64 pages built, 0 errors)
4. Example Usage
----------------
<Breadcrumbs items={[
{ label: "Home", href: "/" },
{ label: "Gaming", href: "/generators/category/gaming/" },
{ label: "Dice Roller" }
]} />
+19
View File
@@ -0,0 +1,19 @@
Task 20: RelatedGenerators.astro
Files changed:
- src/components/RelatedGenerators.astro (created)
- src/layouts/GeneratorLayout.astro (updated — integrated RelatedGenerators)
- src/i18n/translations.ts (updated — added relatedGenerators key EN+RU)
Build result:
- npm run build: PASSED
- 76 pages built, 0 errors
Component behavior:
- Accepts props: currentSlug, category, optional lang
- Filters generators by category, excludes current slug, limits to 4
- Returns null (renders nothing) if no related generators exist
- Locale-aware titles and links (EN / RU)
- Compact card layout: icon + title in a responsive 2-column grid
- Uses Tailwind with existing design tokens (accent color, zinc palette)
- Includes focus-visible ring for accessibility
@@ -0,0 +1,24 @@
Task 21: RelatedPosts Component
================================
Files created:
- src/components/RelatedPosts.astro
Files modified:
- src/i18n/translations.ts (added relatedPosts key: en="Related posts", ru="Похожие статьи")
- src/pages/blog/[slug].astro (imported RelatedPosts, wired into "related" slot)
- src/pages/ru/blog/[slug].astro (imported RelatedPosts, wired into "related" slot)
Build result: PASS
- 83 pages built successfully
- No errors or warnings
Implementation details:
- Accepts post: CollectionEntry<"blog">
- Uses getCollection to fetch non-draft posts in same language
- Scores related posts by tag overlap count
- Shows top 3 posts with score > 0
- Hides section entirely if no related posts found
- Renders title, date, and description snippet for each related post
- Links use correct /blog/ or /ru/blog/ base path
- Styled with Tailwind matching existing dark theme (zinc palette, accent color)
+25
View File
@@ -0,0 +1,25 @@
Task 22: Create 8 English blog post templates
Date: 2026-05-14
Build result: PASS
Pages built: 85
Files created:
- src/content/blog/en/what-is-cryptographically-secure-randomness.md
- src/content/blog/en/how-to-create-strong-passwords.md
- src/content/blog/en/dice-notation-explained.md
- src/content/blog/en/psychology-of-randomness.md
- src/content/blog/en/rng-in-gaming.md
- src/content/blog/en/random-team-picker.md
- src/content/blog/en/color-theory-palettes.md
- src/content/blog/en/uuid-vs-sequential-ids.md
Build output confirms all 8 posts were generated alongside the existing test-post:
/blog/what-is-cryptographically-secure-randomness/index.html
/blog/how-to-create-strong-passwords/index.html
/blog/dice-notation-explained/index.html
/blog/psychology-of-randomness/index.html
/blog/rng-in-gaming/index.html
/blog/random-team-picker/index.html
/blog/color-theory-palettes/index.html
/blog/uuid-vs-sequential-ids/index.html
/blog/test-post/index.html
+20
View File
@@ -0,0 +1,20 @@
Build completed: 93 pages
RU blog posts generated (8):
- /ru/blog/what-is-cryptographically-secure-randomness/index.html
- /ru/blog/how-to-create-strong-passwords/index.html
- /ru/blog/dice-notation-guide/index.html
- /ru/blog/psychology-of-randomness/index.html
- /ru/blog/rng-in-gaming/index.html
- /ru/blog/random-team-selection/index.html
- /ru/blog/color-theory-guide/index.html
- /ru/blog/uuid-vs-sequential-ids/index.html
Files created:
- src/content/blog/ru/what-is-cryptographically-secure-randomness.md
- src/content/blog/ru/how-to-create-strong-passwords.md
- src/content/blog/ru/dice-notation-guide.md
- src/content/blog/ru/psychology-of-randomness.md
- src/content/blog/ru/rng-in-gaming.md
- src/content/blog/ru/random-team-selection.md
- src/content/blog/ru/color-theory-guide.md
- src/content/blog/ru/uuid-vs-sequential-ids.md
Build timestamp: 2026-05-14T03:08:00+03:00
+16
View File
@@ -0,0 +1,16 @@
Task 24 Evidence: Blog Navigation Link
=======================================
Build: PASS
- npm run build completed successfully (84 pages built)
Verification:
- EN blog link (/blog/) found in dist/index.html
- RU blog link (/ru/blog/) found in 38 RU pages including dist/ru/index.html
Changes made:
- Modified src/components/LanguageSwitcher.astro
- Added Blog / Блог link between language toggle and About link
- Link points to /blog/ on EN pages and /ru/blog/ on RU pages
- Used existing translation keys (blog: "Blog", blog: "Блог") from src/i18n/translations.ts
- Styled consistently with existing About/Privacy links
@@ -0,0 +1,17 @@
Task: Add Breadcrumbs component to generator and blog layouts
Date: 2026-05-14
1. npm run build
Result: PASS (93 pages built, exit code 0)
2. Grep dist/generators/dice/index.html for breadcrumb items:
- Found: Home, Gaming, Dice
- Command: grep -o 'Home\|Gaming\|Dice' dist/generators/dice/index.html
3. Grep dist/blog/test-post/index.html for breadcrumb items:
- Found: Home, Blog, Test Post
- Command: grep -o 'Home\|Blog\|Test Post' dist/blog/test-post/index.html
Files modified:
- src/layouts/GeneratorLayout.astro
- src/layouts/BlogLayout.astro
@@ -46,3 +46,154 @@
- Removed `.optional()` from `category` in `src/lib/generator-schema.ts` (line 24).
- Build passes with 64 pages and 0 Zod errors — schema now strictly enforces the category field.
- Order matters: all JSONs must have the field BEFORE removing `.optional()` from schema, otherwise build fails immediately.
## Task 11: Install @astrojs/rss and set up content collections config
- Installed `@astrojs/rss` package (9 packages added, 511 total).
- Updated `src/content/config.ts`:
- Added `z` import from `astro:content`.
- Defined `blog` collection with `type: "content"` and full schema: title (max 120), description (max 160), pubDate, modDate (optional), draft (default false), lang (en|ru), category (tutorial|guide|news|tips), tags (default []), ogImage (image().optional()), relatedGenerators (optional), relatedPosts (optional).
- Exported `collections = { generators, blog }` — preserved existing generators collection untouched.
- Created empty directories: `src/content/blog/en/` and `src/content/blog/ru/`.
- Build passes with 64 pages and 0 errors.
- Note: `src/data/generators.ts` remains the runtime loader for generators; the content collection config is for build-time validation only.
## Task 19: Reusable Breadcrumb Component
- Created `src/components/Breadcrumbs.astro` as a standalone, reusable component.
- Props: `items: Array<{ label: string, href?: string }>`.
- Accessibility: `<nav aria-label="Breadcrumb">` with `<ol>` list semantics; last item gets `aria-current="page"`.
- Separators are plain-text `/` wrapped in `<span aria-hidden="true">` to avoid screen-reader noise.
- Styling matches existing dark theme: `text-sm`, `text-zinc-500` for links, `text-zinc-400` for current page, `hover:text-accent` on links, plus `focus-visible:ring-accent` for keyboard navigation.
- Intentionally NOT added to `BaseLayout` — breadcrumbs are opt-in per page (e.g., category landing pages, blog posts).
- Build passes with 64 pages and 0 errors.
## Task 12: BlogLayout.astro
- Created `src/layouts/BlogLayout.astro` wrapping `BaseLayout`.
- Injected `BlogPosting` JSON-LD schema with Organization author/publisher, headline, dates, and canonical URL.
- Set OG type to `article` and added `article:published_time` / `article:modified_time` meta tags via `slot="head"`.
- Styled container follows `GeneratorLayout` pattern (`max-w-2xl mx-auto px-4 py-12 sm:py-20`).
- Prev/next navigation supports bilingual labels, arrow icons, and keyboard-focus rings.
- Exposed named slot `related` for a `RelatedPosts` component.
- Props: `title`, `description`, `pubDate`, `modDate`, `ogImage`, `lang`, `prevPost?`, `nextPost?`.
- `PostLink` interface: `{ slug: string; title: string }`.
- Build passes (64 pages, 0 errors).
## Task 16: RSS Feeds (2026-05-14)
- Astro RSS endpoint pattern: `src/pages/rss.xml.ts` exports `GET(context)` and returns `rss({...})` from `@astrojs/rss`.
- `context.site` is automatically populated from `astro.config.mjs` `site` field.
- Use `getCollection("blog")` then filter by `post.data.lang` and `post.data.draft` to keep languages separate and exclude drafts.
- Empty collections produce a valid but item-less RSS feed; build succeeds with a warning.
- RSS auto-discovery links should be `<link rel="alternate" type="application/rss+xml" title="..." href="..." />` inside `<head>`.
## Task 15: Blog Index Pages (EN + RU)
- Created `src/pages/blog/index.astro` and `src/pages/ru/blog/index.astro` using `getCollection("blog")`.
- Filter pattern: `post.data.lang === "en"/"ru" && post.data.draft !== true` — ensures no cross-language mixing and no draft posts leak.
- Sort: `b.data.pubDate.getTime() - a.data.pubDate.getTime()` for newest-first.
- Each post card displays: title, description, formatted date (locale-aware), category badge, tag badges.
- Links: `/blog/${post.slug}/` for EN, `/ru/blog/${post.slug}/` for RU.
- Added translation keys `blogDesc`, `readMore`, `noPosts` to both EN and RU objects.
- JSON-LD `Blog` schema injected via `slot="head"` with nested `BlogPosting` array for each post.
- Empty collection gracefully handled with localized "No posts yet" message.
- Build passes with 76 pages; warnings about empty blog collection are expected and harmless.
- Reused styling patterns from `src/pages/index.astro` (max-w-4xl, blur accent orb, card hover states).
## Task 17: Category Pages for Generators
- Created `src/pages/generators/category/[category].astro` and `src/pages/ru/generators/category/[category].astro`.
- Both pages use `getStaticPaths` with 5 hardcoded categories: gaming, security, decision-making, creative, utility.
- Generators are filtered dynamically via `generators.filter((g) => g.category === category)` — no hardcoded generator lists.
- Each page displays a breadcrumb (Home > Categories > [Category Name] / Главная > Категории > [Name]), category-specific title/description, and a grid of `GeneratorCard` components matching the `index.astro` layout.
- JSON-LD BreadcrumbList schema is injected via `<Fragment slot="head">` for SEO.
- Category names are formatted for display: EN uses hyphen-split capitalization ("Decision Making"), RU uses a translation map ("Принятие решений").
- Build passes with 76 pages (up from 64), confirming all 10 category variants (EN + RU × 5 categories) are statically generated.
- Verified `dist/generators/category/gaming/index.html` exists and contains correct content.
## Task 20: RelatedGenerators.astro
- Created `src/components/RelatedGenerators.astro` with props `currentSlug`, `category`, and optional `lang`.
- Filter logic: `generators.filter(g => g.category === category && g.slug !== currentSlug).slice(0, 4)`.
- Edge case handled: returns `null` (renders nothing) when no related generators exist.
- Locale-aware: titles and hrefs switch between EN (`/generators/{slug}/`) and RU (`/ru/generators/{slug}/`) based on `Astro.currentLocale`.
- Compact card design: icon (from inline `icons` map matching `GeneratorCard`) + title in a responsive 2-column grid (`grid-cols-1 sm:grid-cols-2`).
- Styling uses existing Tailwind tokens: `border-zinc-800`, `hover:border-accent`, `bg-accent/10`, `focus-visible:ring-accent`.
- Integrated into `GeneratorLayout.astro` at the bottom of the content area (after FAQ block).
- Added `relatedGenerators` translation key to both EN ("Related generators") and RU ("Похожие генераторы").
- Build passes with 76 pages and 0 errors.
## Task 13: EN Blog Post Page ([slug].astro)
- Created `src/pages/blog/[slug].astro` with `getStaticPaths` querying `getCollection("blog")`.
- Filtered posts by `lang === "en"` and excluded drafts.
- Sorted posts by `pubDate` ascending to calculate prev/next navigation chronologically.
- Derived clean slugs by stripping `en/` prefix and `.md` extension from `post.id` (Astro content collection `id` includes file extension).
- Rendered markdown via `post.render()` -> `<Content />`.
- Passed prev/next data to `BlogLayout` using `prevPost` / `nextPost` props (matching existing BlogLayout interface from Task 12).
- Build passes with 77 pages; verified output at `dist/blog/test-post/index.html`.
## Task 14: RU Blog Post Page ([slug].astro)
- Created `src/pages/ru/blog/[slug].astro` as an exact structural copy of the EN file, with two language-specific changes:
1. Filter: `post.data.lang === "ru"` (instead of `"en"`).
2. Slug normalization: stripped `ru/` prefix from `post.id` (instead of `en/`).
- Prev/next navigation uses `BlogLayout` props (`prevPost`, `nextPost`) just like EN.
- Rendered markdown via `post.render()` -> `<Content />` inside `<article>`.
- Passed `lang="ru"` prop to `BlogLayout` for explicit locale context.
- **Bug fix in `BlogLayout.astro`:** prev/next links were hardcoded to `/blog/${slug}/`, breaking RU navigation. Added `const blogBase = lang === "ru" ? "/ru/blog/" : "/blog/";` and updated both prev/next `href` attributes to use `${blogBase}${slug}/`. This makes BlogLayout fully locale-aware without duplicating layout logic.
- Build passes with 77 pages; RU `[slug].astro` compiles correctly. No RU blog posts exist yet, so no static RU post pages are generated (expected — will populate in Tasks 22-23).
- Evidence saved to `.sisyphus/evidence/task-14-ru-post.txt`.
## Task 21: RelatedPosts Component
- Created `src/components/RelatedPosts.astro` accepting `post: CollectionEntry<"blog">`.
- Tag overlap scoring: `p.data.tags.filter(t => post.data.tags.includes(t)).length`.
- Filters out current post, requires `score > 0`, sorts descending, slices top 3.
- Edge case: returns `null` (renders nothing) when no related posts — section is completely hidden.
- Language isolation: `getCollection("blog", ({ data }) => !data.draft && data.lang === post.data.lang)` ensures EN and RU posts never mix.
- Slug derivation: `post.id.replace(/^(en|ru)\//, "").replace(/\.md$/, "")` — Astro content collection `id` includes subdirectory prefix and file extension.
- Blog base path computed from `post.data.lang`: `/blog/` for EN, `/ru/blog/` for RU.
- Styled with Tailwind matching existing dark theme: `border-zinc-800`, `hover:border-accent`, `line-clamp-2` for description snippets.
- Wired into both `src/pages/blog/[slug].astro` and `src/pages/ru/blog/[slug].astro` via `<RelatedPosts post={post} slot="related" />`.
- Added `relatedPosts` translation key: EN "Related posts", RU "Похожие статьи".
- Build passes with 83 pages and 0 errors.
## Task 23: Russian Blog Post Templates (8 posts)
- Created 8 Russian blog post templates in `src/content/blog/ru/`.
- All posts include valid frontmatter with `lang: ru`, matching the EN schema.
- Frontmatter fields: title, description, pubDate, modDate, draft: false, lang: ru, category: guide, tags, relatedGenerators.
- `relatedGenerators` uses English slugs (same convention as EN posts).
- Staggered dates used: 2025-01-16 through 2025-01-23.
- Body content: 3 paragraphs of Russian placeholder text per post.
- Build passes with 93 pages (up from 77), confirming all 8 RU blog posts are statically generated.
- RU blog index (`/ru/blog/index.html`) and individual post pages (`/ru/blog/<slug>/index.html`) render correctly.
- Note: First build attempt hit a transient Astro module resolution bug on `src/pages/ru/generators/list.astro`; retry succeeded without changes.
## Task 26: Add Breadcrumbs to GeneratorLayout and BlogLayout
- Imported `Breadcrumbs` into `src/layouts/GeneratorLayout.astro` and `src/layouts/BlogLayout.astro`.
- **GeneratorLayout:** Added `<Breadcrumbs items={...} />` between the existing back-link nav and the `<header>`.
- Items: Home → Category → Generator Title.
- Category label is locale-aware: maps `generator.category` to translation keys (`catGaming`, `catSecurity`, etc.) for both EN and RU.
- Category href is prefixed with `/ru` when `isRu` is true.
- **BlogLayout:** Added `<Breadcrumbs items={...} />` inside `<article>`, before the `<header>`.
- Items: Home → Blog → Post Title.
- Labels and hrefs switch based on `lang === "ru"`.
- `npm run build` passes with 93 pages.
- Verified breadcrumb text presence in built HTML:
- `dist/generators/dice/index.html` contains "Home", "Gaming", "Dice".
- `dist/blog/test-post/index.html` contains "Home", "Blog", "Test Post".
- Evidence saved to `.sisyphus/evidence/task-26-breadcrumbs.txt`.
## Task 22: Create 8 English Blog Post Templates
- Created 8 markdown files in `src/content/blog/en/` with valid frontmatter matching the blog schema.
- All posts use `category: guide`, `draft: false`, `lang: en`, and include `relatedGenerators`.
- Dates are staggered from 2025-01-15 (newest) down to 2025-01-08 (oldest) for correct newest-first sorting.
- Body content is 3 paragraphs of placeholder text ("This article will explore... Stay tuned for the full guide.") per instructions.
- Build passes with 85 pages (up from 77), confirming 8 new static blog post pages were generated.
- Existing `test-post.md` remains untouched; total EN blog collection now has 9 posts.
+16 -16
View File
@@ -638,7 +638,7 @@ Max Concurrent: 6 (Wave 1), 4 (Wave 2), 6 (Wave 3), 8 (Wave 4), 2 (Wave 5)
**Commit**: YES (Wave 2)
- [ ] 11. Install @astrojs/rss, set up content collections config
- [x] 11. Install @astrojs/rss, set up content collections config
**What to do**:
- Run `npm install @astrojs/rss`
@@ -705,7 +705,7 @@ Max Concurrent: 6 (Wave 1), 4 (Wave 2), 6 (Wave 3), 8 (Wave 4), 2 (Wave 5)
**Commit**: YES (Wave 3)
- [ ] 12. Create BlogLayout.astro with BlogPosting schema
- [x] 12. Create BlogLayout.astro with BlogPosting schema
**What to do**:
- Create `src/layouts/BlogLayout.astro`
@@ -752,7 +752,7 @@ Max Concurrent: 6 (Wave 1), 4 (Wave 2), 6 (Wave 3), 8 (Wave 4), 2 (Wave 5)
**Commit**: YES (Wave 3)
- [ ] 13. Create blog post page EN ([slug].astro)
- [x] 13. Create blog post page EN ([slug].astro)
**What to do**:
- Create `src/pages/blog/[slug].astro`
@@ -799,7 +799,7 @@ Max Concurrent: 6 (Wave 1), 4 (Wave 2), 6 (Wave 3), 8 (Wave 4), 2 (Wave 5)
**Commit**: YES (Wave 3)
- [ ] 14. Create blog post page RU ([slug].astro)
- [x] 14. Create blog post page RU ([slug].astro)
**What to do**:
- Create `src/pages/ru/blog/[slug].astro`
@@ -842,7 +842,7 @@ Max Concurrent: 6 (Wave 1), 4 (Wave 2), 6 (Wave 3), 8 (Wave 4), 2 (Wave 5)
**Commit**: YES (Wave 3)
- [ ] 15. Create blog index pages (EN + RU)
- [x] 15. Create blog index pages (EN + RU)
**What to do**:
- Create `src/pages/blog/index.astro` — list all EN blog posts sorted by date (newest first)
@@ -889,7 +889,7 @@ Max Concurrent: 6 (Wave 1), 4 (Wave 2), 6 (Wave 3), 8 (Wave 4), 2 (Wave 5)
**Commit**: YES (Wave 3)
- [ ] 16. Create RSS feeds (EN + RU)
- [x] 16. Create RSS feeds (EN + RU)
**What to do**:
- Create `src/pages/rss.xml.ts` — English RSS feed
@@ -941,7 +941,7 @@ Max Concurrent: 6 (Wave 1), 4 (Wave 2), 6 (Wave 3), 8 (Wave 4), 2 (Wave 5)
**Commit**: YES (Wave 3)
- [ ] 17. Create category pages for generators (EN + RU)
- [x] 17. Create category pages for generators (EN + RU)
**What to do**:
- Create `src/pages/generators/category/[category].astro`
@@ -991,7 +991,7 @@ Max Concurrent: 6 (Wave 1), 4 (Wave 2), 6 (Wave 3), 8 (Wave 4), 2 (Wave 5)
**Commit**: YES (Wave 4)
- [ ] 18. Add category navigation to homepage
- [x] 18. Add category navigation to homepage
**What to do**:
- Update `src/pages/index.astro` to show category pills/links above or below the generator grid
@@ -1037,7 +1037,7 @@ Max Concurrent: 6 (Wave 1), 4 (Wave 2), 6 (Wave 3), 8 (Wave 4), 2 (Wave 5)
**Commit**: YES (Wave 4)
- [ ] 19. Create Breadcrumbs component
- [x] 19. Create Breadcrumbs component
**What to do**:
- Create `src/components/Breadcrumbs.astro`
@@ -1084,7 +1084,7 @@ Max Concurrent: 6 (Wave 1), 4 (Wave 2), 6 (Wave 3), 8 (Wave 4), 2 (Wave 5)
**Commit**: YES (Wave 4)
- [ ] 20. Create RelatedGenerators component
- [x] 20. Create RelatedGenerators component
**What to do**:
- Create `src/components/RelatedGenerators.astro`
@@ -1133,7 +1133,7 @@ Max Concurrent: 6 (Wave 1), 4 (Wave 2), 6 (Wave 3), 8 (Wave 4), 2 (Wave 5)
**Commit**: YES (Wave 4)
- [ ] 21. Create RelatedPosts component
- [x] 21. Create RelatedPosts component
**What to do**:
- Create `src/components/RelatedPosts.astro`
@@ -1190,7 +1190,7 @@ Max Concurrent: 6 (Wave 1), 4 (Wave 2), 6 (Wave 3), 8 (Wave 4), 2 (Wave 5)
**Commit**: YES (Wave 4)
- [ ] 22. Create 8 blog post templates EN
- [x] 22. Create 8 blog post templates EN
**What to do**:
- Create 8 markdown files in `src/content/blog/en/` with correct frontmatter:
@@ -1255,7 +1255,7 @@ Max Concurrent: 6 (Wave 1), 4 (Wave 2), 6 (Wave 3), 8 (Wave 4), 2 (Wave 5)
**Commit**: YES (Wave 5)
- [ ] 23. Create 8 blog post templates RU
- [x] 23. Create 8 blog post templates RU
**What to do**:
- Create 8 markdown files in `src/content/blog/ru/` with Russian titles/descriptions
@@ -1307,7 +1307,7 @@ Max Concurrent: 6 (Wave 1), 4 (Wave 2), 6 (Wave 3), 8 (Wave 4), 2 (Wave 5)
**Commit**: YES (Wave 5)
- [ ] 24. Add blog link to navigation
- [x] 24. Add blog link to navigation
**What to do**:
- Update `src/components/LanguageSwitcher.astro` or add a new nav component
@@ -1355,7 +1355,7 @@ Max Concurrent: 6 (Wave 1), 4 (Wave 2), 6 (Wave 3), 8 (Wave 4), 2 (Wave 5)
**Commit**: YES (Wave 5)
- [ ] 25. Add related generators to generator pages
- [x] 25. Add related generators to generator pages
**What to do**:
- Update `src/layouts/GeneratorLayout.astro` to import and render `<RelatedGenerators />`
@@ -1401,7 +1401,7 @@ Max Concurrent: 6 (Wave 1), 4 (Wave 2), 6 (Wave 3), 8 (Wave 4), 2 (Wave 5)
**Commit**: YES (Wave 5)
- [ ] 26. Add breadcrumbs to generator and blog layouts
- [x] 26. Add breadcrumbs to generator and blog layouts
**What to do**:
- Update `src/layouts/GeneratorLayout.astro` to include `<Breadcrumbs />`