Files
Jakub Zych b05e85cd55 fix(10): revise plans based on checker feedback
- Plan 10-02: Added explicit UserMailService wiring in Account.scala
  onRegister handler for activation emails when activateMode == User
- Plan 10-04: Split into backend-only (models, services, admin controller)
  reducing from 16 to 15 files, estimated context ~50%
- Plan 10-05: New plan for frontend components (Posts, Post, Categories,
  RelatedPosts), Wave 3 depends on 10-04
- Updated ROADMAP.md to reflect 5 plans for Phase 10
2026-02-05 16:13:31 +01:00

288 lines
9.8 KiB
Markdown

---
phase: 10-core-plugins
plan: 05
type: execute
wave: 3
depends_on: ["10-04"]
files_modified:
- plugins/golem15/blog/components/Posts.scala
- plugins/golem15/blog/components/Post.scala
- plugins/golem15/blog/components/Categories.scala
- plugins/golem15/blog/components/RelatedPosts.scala
- plugins/golem15/blog/resources/views/posts/default.peb
- plugins/golem15/blog/resources/views/posts/item.peb
- plugins/golem15/blog/resources/views/post/default.peb
- plugins/golem15/blog/resources/views/categories/default.peb
- plugins/golem15/blog/resources/views/relatedposts/default.peb
- plugins/golem15/blog/Plugin.scala
autonomous: true
must_haves:
truths:
- "Frontend displays post listings with pagination"
- "Frontend displays individual post pages"
- "Related posts shown based on shared categories/tags"
- "Category tree/list component available for navigation"
- "Infinite scroll works via HTMX"
artifacts:
- path: "plugins/golem15/blog/components/Posts.scala"
provides: "Frontend post listing component"
exports: ["PostsComponent", "onLoadMore"]
- path: "plugins/golem15/blog/components/Post.scala"
provides: "Single post display component"
exports: ["PostComponent"]
- path: "plugins/golem15/blog/components/Categories.scala"
provides: "Category listing/tree component"
exports: ["CategoriesComponent"]
- path: "plugins/golem15/blog/components/RelatedPosts.scala"
provides: "Related posts component"
exports: ["RelatedPostsComponent"]
- path: "plugins/golem15/blog/resources/views/posts/default.peb"
provides: "Post listing template"
contains: "hx-post"
key_links:
- from: "plugins/golem15/blog/components/Posts.scala"
to: "BlogPostService"
via: "post listing"
pattern: "postService\\.listFrontend"
- from: "plugins/golem15/blog/components/Post.scala"
to: "BlogPostService"
via: "single post lookup"
pattern: "postService\\.findBySlug"
- from: "plugins/golem15/blog/components/RelatedPosts.scala"
to: "BlogPostService"
via: "related posts query"
pattern: "postService\\.getRelatedPosts"
---
<objective>
Create frontend components for blog display with pagination and HTMX interactions.
Purpose: Enable visitors to browse and read blog content, demonstrating frontend component patterns with HTMX.
Output: Complete Blog frontend with:
- Posts component with pagination/infinite scroll
- Post component for single post display with OpenGraph meta
- Categories component for navigation
- RelatedPosts component for content discovery
</objective>
<execution_context>
@/home/jin/.claude/get-shit-done/workflows/execute-plan.md
@/home/jin/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/10-core-plugins/10-CONTEXT.md
@.planning/phases/10-core-plugins/10-RESEARCH.md
# Prior plans in this phase
@.planning/phases/10-core-plugins/10-03-SUMMARY.md
@.planning/phases/10-core-plugins/10-04-SUMMARY.md
# Reference: Phase 3 component patterns for HTMX
</context>
<tasks>
<task type="auto">
<name>Task 1: Posts and Post listing components</name>
<files>
plugins/golem15/blog/components/Posts.scala
plugins/golem15/blog/components/Post.scala
plugins/golem15/blog/resources/views/posts/default.peb
plugins/golem15/blog/resources/views/posts/item.peb
plugins/golem15/blog/resources/views/post/default.peb
</files>
<action>
Create frontend components for blog display:
**Posts.scala (research Pattern 8):**
- Extend SummerComponent trait
- ComponentDetails: name "Posts", description "Blog post listing"
- Property schema:
- pageNumber: String, URL param for pagination, default "{{ :page }}"
- categoryFilter: String, filter by category slug
- postsPerPage: String, default "10", validation 1-100
- noPostsMessage: String, default "No posts found."
- sortOrder: Dropdown with BlogPost.allowedSortingOptions
- throwNotFound: Checkbox, 404 on empty results
- Page variables: posts (PaginatedResult), category (Option), noPostsMessage
- onRun lifecycle:
- Load category if categoryFilter set
- Build PostListOptions from properties
- Call postService.listFrontend or listByCategory
- Handle empty results per throwNotFound
- Set page context variables
- onLoadMore HTMX handler:
- Parse page number from form
- Load next page of posts
- Return items partial
- Trigger "postsExhausted" when no more
**Post.scala component:**
- ComponentDetails: name "Post", description "Single post display"
- Property schema:
- slug: String, URL param for post slug, default "{{ :slug }}"
- notFoundMessage: String
- Page variables: post (BlogPostWithRelations)
- onRun lifecycle:
- Get slug from URL
- Call postService.findBySlug
- 404 if not found
- Set page context: post, author, categories, tags, readingTime
- Set OpenGraph meta tags for social sharing
**Templates:**
**posts/default.peb (research code example):**
```html
<div class="blog-posts" id="posts-{{ __SELF__ }}">
{% if posts.items is empty %}
<div class="no-posts">{{ noPostsMessage }}</div>
{% else %}
<div class="posts-list">
{% for post in posts.items %}
{% include "posts/item" %}
{% endfor %}
</div>
{% if posts.hasNext %}
<div class="load-more">
<button
hx-post="{{ componentHandler('onLoadMore') }}"
hx-vals='{"page": {{ posts.currentPage + 1 }}}'
hx-target="#posts-{{ __SELF__ }} .posts-list"
hx-swap="beforeend"
hx-indicator=".htmx-indicator">
Load More
<span class="htmx-indicator">Loading...</span>
</button>
</div>
{% endif %}
{% endif %}
</div>
```
**posts/item.peb:**
- Article element with post data
- Featured image (lazy load)
- Title as link to post page
- Excerpt or auto-generated summary
- Author name, published date, reading time
- Category and tag links
**post/default.peb:**
- Full post display
- OpenGraph meta tags in head
- Title, featured image, author, date
- Content HTML
- Categories and tags
- Share buttons (copy link, Twitter, Facebook)
- Include RelatedPosts component placeholder
</action>
<verify>
./mill summercms.compile succeeds
Posts component has onLoadMore HTMX handler
Post component sets OpenGraph meta tags
Templates have valid Pebble syntax
</verify>
<done>
Posts component displays paginated listings with infinite scroll.
Post component displays single post with meta tags.
Templates render with HTMX interactions.
</done>
</task>
<task type="auto">
<name>Task 2: Categories and RelatedPosts components</name>
<files>
plugins/golem15/blog/components/Categories.scala
plugins/golem15/blog/components/RelatedPosts.scala
plugins/golem15/blog/resources/views/categories/default.peb
plugins/golem15/blog/resources/views/relatedposts/default.peb
plugins/golem15/blog/Plugin.scala (modify)
</files>
<action>
Create category navigation and related posts components:
**Categories.scala component:**
- ComponentDetails: name "Categories", description "Category listing/tree"
- Property schema:
- displayMode: Dropdown (tree/flat), default "tree"
- showPostCount: Checkbox, default true
- includeEmpty: Checkbox, show categories with no posts
- Page variables: categories (tree or flat list)
- onRun: Load category tree, filter empty if needed
**RelatedPosts.scala component:**
- ComponentDetails: name "RelatedPosts", description "Related posts by category/tag"
- Property schema:
- limit: String, max posts to show, default "3"
- postSlug: String, current post slug (from context)
- Page variables: relatedPosts (List)
- onRun: Call postService.getRelatedPosts, set context
**Templates:**
**categories/default.peb:**
- Tree or flat list based on displayMode
- Category name as link
- Post count if showPostCount
- Recursive include for tree mode
**relatedposts/default.peb:**
- Grid of related post cards
- Image, title, excerpt snippet
- Link to full post
**Register all components in Plugin.scala boot method:**
- Posts component
- Post component
- Categories component
- RelatedPosts component
</action>
<verify>
./mill summercms.compile succeeds
Components registered in Plugin.scala
Templates have valid Pebble syntax
HTMX attributes properly formatted
</verify>
<done>
Categories component shows category tree/list.
RelatedPosts component shows related content.
All components registered in Plugin.scala.
Blog frontend fully functional.
</done>
</task>
</tasks>
<verification>
After all tasks complete:
1. Blog plugin compiles: `./mill summercms.compile`
2. All four components registered in Plugin.scala
3. Posts component loads and paginates
4. Post component displays with OpenGraph meta
5. Categories component renders tree structure
6. RelatedPosts component shows related content
</verification>
<success_criteria>
- Frontend /blog shows post listing with load more
- Frontend /blog/:slug shows full post with related posts
- Category pages filter posts by category and children
- Tag pages filter posts by tag
- Infinite scroll works via HTMX onLoadMore handler
- OpenGraph meta tags present for social sharing
- Category tree/list renders correctly
- Related posts show content with shared categories/tags
</success_criteria>
<output>
After completion, create `.planning/phases/10-core-plugins/10-05-SUMMARY.md`
</output>