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
This commit is contained in:
@@ -195,13 +195,14 @@ Plans:
|
|||||||
6. Blog posts can be organized into categories
|
6. Blog posts can be organized into categories
|
||||||
7. Blog posts can be tagged with multiple tags
|
7. Blog posts can be tagged with multiple tags
|
||||||
8. Frontend displays blog post listings via components
|
8. Frontend displays blog post listings via components
|
||||||
**Plans**: 4 plans
|
**Plans**: 5 plans
|
||||||
|
|
||||||
Plans:
|
Plans:
|
||||||
- [ ] 10-01-PLAN.md - User plugin models, services, and authentication components
|
- [ ] 10-01-PLAN.md - User plugin models, services, and authentication components
|
||||||
- [ ] 10-02-PLAN.md - User profile management and password reset flow
|
- [ ] 10-02-PLAN.md - User profile management and password reset flow
|
||||||
- [ ] 10-03-PLAN.md - Blog post model, services, and admin controller with TinyMCE
|
- [ ] 10-03-PLAN.md - Blog post model, services, and admin controller with TinyMCE
|
||||||
- [ ] 10-04-PLAN.md - Blog categories, tags, and frontend listing components
|
- [ ] 10-04-PLAN.md - Blog categories, tags, and admin management
|
||||||
|
- [ ] 10-05-PLAN.md - Blog frontend listing components
|
||||||
|
|
||||||
## Progress
|
## Progress
|
||||||
|
|
||||||
@@ -219,7 +220,7 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10
|
|||||||
| 7. Admin Forms & Lists | 0/3 | Planned | - |
|
| 7. Admin Forms & Lists | 0/3 | Planned | - |
|
||||||
| 8. Admin Dashboard | 0/4 | Planned | - |
|
| 8. Admin Dashboard | 0/4 | Planned | - |
|
||||||
| 9. Content Management | 0/7 | Planned | - |
|
| 9. Content Management | 0/7 | Planned | - |
|
||||||
| 10. Core Plugins | 0/4 | Planned | - |
|
| 10. Core Plugins | 0/5 | Planned | - |
|
||||||
|
|
||||||
---
|
---
|
||||||
*Roadmap created: 2026-02-04*
|
*Roadmap created: 2026-02-04*
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ wave: 2
|
|||||||
depends_on: ["10-01"]
|
depends_on: ["10-01"]
|
||||||
files_modified:
|
files_modified:
|
||||||
- plugins/golem15/user/components/ResetPassword.scala
|
- plugins/golem15/user/components/ResetPassword.scala
|
||||||
|
- plugins/golem15/user/components/Account.scala
|
||||||
- plugins/golem15/user/services/FrontendUserService.scala
|
- plugins/golem15/user/services/FrontendUserService.scala
|
||||||
- plugins/golem15/user/services/UserMailService.scala
|
- plugins/golem15/user/services/UserMailService.scala
|
||||||
- plugins/golem15/user/resources/views/account/profile.peb
|
- plugins/golem15/user/resources/views/account/profile.peb
|
||||||
@@ -155,6 +156,7 @@ Output: Working profile and reset features:
|
|||||||
<name>Task 2: Password reset flow and email service</name>
|
<name>Task 2: Password reset flow and email service</name>
|
||||||
<files>
|
<files>
|
||||||
plugins/golem15/user/components/ResetPassword.scala
|
plugins/golem15/user/components/ResetPassword.scala
|
||||||
|
plugins/golem15/user/components/Account.scala (modify)
|
||||||
plugins/golem15/user/services/UserMailService.scala
|
plugins/golem15/user/services/UserMailService.scala
|
||||||
plugins/golem15/user/services/FrontendUserService.scala (modify)
|
plugins/golem15/user/services/FrontendUserService.scala (modify)
|
||||||
plugins/golem15/user/resources/views/resetpassword/request.peb
|
plugins/golem15/user/resources/views/resetpassword/request.peb
|
||||||
@@ -175,6 +177,13 @@ Output: Working profile and reset features:
|
|||||||
- Send via configured SMTP or email service
|
- Send via configured SMTP or email service
|
||||||
- MailError ADT: SendFailed, TemplateError, ConfigurationError
|
- MailError ADT: SendFailed, TemplateError, ConfigurationError
|
||||||
|
|
||||||
|
**Account.scala modification (CRITICAL - wire activation email):**
|
||||||
|
- Modify the onRegister handler created in Plan 01 to integrate UserMailService:
|
||||||
|
- Inject UserMailService dependency into Account component
|
||||||
|
- After successful userService.register call, check UserSettings.activateMode
|
||||||
|
- If activateMode == ActivateMode.User, call mailService.sendActivationEmail(user, activationCode)
|
||||||
|
- This completes the key_link from Account.scala to UserMailService
|
||||||
|
|
||||||
**FrontendUserService.scala additions:**
|
**FrontendUserService.scala additions:**
|
||||||
- requestPasswordReset(email: String): Unit
|
- requestPasswordReset(email: String): Unit
|
||||||
- Find user by email (don't reveal if not found - timing attack)
|
- Find user by email (don't reveal if not found - timing attack)
|
||||||
@@ -230,13 +239,14 @@ Output: Working profile and reset features:
|
|||||||
<verify>
|
<verify>
|
||||||
./mill summercms.compile succeeds
|
./mill summercms.compile succeeds
|
||||||
ResetPassword component registered in Plugin.scala
|
ResetPassword component registered in Plugin.scala
|
||||||
|
Account.scala onRegister calls mailService.sendActivationEmail when activateMode == User
|
||||||
Email templates have valid Pebble syntax
|
Email templates have valid Pebble syntax
|
||||||
Reset code generation uses SecureRandom
|
Reset code generation uses SecureRandom
|
||||||
</verify>
|
</verify>
|
||||||
<done>
|
<done>
|
||||||
Password reset request sends email (or stubs if no SMTP).
|
Password reset request sends email (or stubs if no SMTP).
|
||||||
Reset link with token allows setting new password.
|
Reset link with token allows setting new password.
|
||||||
Activation email template ready for registration flow.
|
Activation email sent during registration when activateMode is User.
|
||||||
No email enumeration possible (generic success messages).
|
No email enumeration possible (generic success messages).
|
||||||
</done>
|
</done>
|
||||||
</task>
|
</task>
|
||||||
@@ -247,9 +257,10 @@ Output: Working profile and reset features:
|
|||||||
After all tasks complete:
|
After all tasks complete:
|
||||||
1. User plugin compiles: `./mill summercms.compile`
|
1. User plugin compiles: `./mill summercms.compile`
|
||||||
2. Account component has all handlers: onSignin, onRegister, onActivate, onUpdate, onUploadAvatar
|
2. Account component has all handlers: onSignin, onRegister, onActivate, onUpdate, onUploadAvatar
|
||||||
3. ResetPassword component handles: onRequest, onReset
|
3. Account.scala onRegister integrates UserMailService for activation emails
|
||||||
4. Email service can send activation and reset emails
|
4. ResetPassword component handles: onRequest, onReset
|
||||||
5. Templates render valid forms with HTMX
|
5. Email service can send activation and reset emails
|
||||||
|
6. Templates render valid forms with HTMX
|
||||||
</verification>
|
</verification>
|
||||||
|
|
||||||
<success_criteria>
|
<success_criteria>
|
||||||
@@ -257,6 +268,7 @@ After all tasks complete:
|
|||||||
- Profile update saves name/surname changes
|
- Profile update saves name/surname changes
|
||||||
- Password change requires current password confirmation
|
- Password change requires current password confirmation
|
||||||
- Avatar upload stores image and updates user record
|
- Avatar upload stores image and updates user record
|
||||||
|
- Registration with activateMode=User triggers activation email via UserMailService
|
||||||
- Password reset request always shows success (no enumeration)
|
- Password reset request always shows success (no enumeration)
|
||||||
- Reset link with valid code allows new password entry
|
- Reset link with valid code allows new password entry
|
||||||
- User automatically logged in after successful reset
|
- User automatically logged in after successful reset
|
||||||
|
|||||||
@@ -7,21 +7,21 @@ depends_on: ["10-03"]
|
|||||||
files_modified:
|
files_modified:
|
||||||
- plugins/golem15/blog/models/Category.scala
|
- plugins/golem15/blog/models/Category.scala
|
||||||
- plugins/golem15/blog/models/Tag.scala
|
- plugins/golem15/blog/models/Tag.scala
|
||||||
|
- plugins/golem15/blog/models/PostCategory.scala
|
||||||
|
- plugins/golem15/blog/models/PostTag.scala
|
||||||
- plugins/golem15/blog/repositories/CategoryRepository.scala
|
- plugins/golem15/blog/repositories/CategoryRepository.scala
|
||||||
- plugins/golem15/blog/repositories/TagRepository.scala
|
- plugins/golem15/blog/repositories/TagRepository.scala
|
||||||
- plugins/golem15/blog/services/CategoryService.scala
|
- plugins/golem15/blog/services/CategoryService.scala
|
||||||
- plugins/golem15/blog/services/TagService.scala
|
- plugins/golem15/blog/services/TagService.scala
|
||||||
|
- plugins/golem15/blog/services/NestedTree.scala
|
||||||
- plugins/golem15/blog/services/BlogPostService.scala
|
- plugins/golem15/blog/services/BlogPostService.scala
|
||||||
|
- plugins/golem15/blog/repositories/BlogPostRepository.scala
|
||||||
|
- plugins/golem15/blog/controllers/Posts.scala
|
||||||
- plugins/golem15/blog/controllers/Categories.scala
|
- plugins/golem15/blog/controllers/Categories.scala
|
||||||
- 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/db/migration/V10_2_2__blog_categories_tags.sql
|
- plugins/golem15/blog/resources/db/migration/V10_2_2__blog_categories_tags.sql
|
||||||
|
- plugins/golem15/blog/resources/controllers/posts/fields.yaml
|
||||||
- plugins/golem15/blog/resources/controllers/categories/fields.yaml
|
- plugins/golem15/blog/resources/controllers/categories/fields.yaml
|
||||||
- plugins/golem15/blog/resources/views/posts/default.peb
|
- plugins/golem15/blog/resources/controllers/categories/columns.yaml
|
||||||
- plugins/golem15/blog/resources/views/posts/item.peb
|
|
||||||
- plugins/golem15/blog/resources/views/post/default.peb
|
|
||||||
autonomous: true
|
autonomous: true
|
||||||
|
|
||||||
must_haves:
|
must_haves:
|
||||||
@@ -29,9 +29,8 @@ must_haves:
|
|||||||
- "Blog posts can be assigned to categories"
|
- "Blog posts can be assigned to categories"
|
||||||
- "Categories are hierarchical (parent/child)"
|
- "Categories are hierarchical (parent/child)"
|
||||||
- "Blog posts can have multiple tags"
|
- "Blog posts can have multiple tags"
|
||||||
- "Frontend displays post listings with pagination"
|
- "Admin can manage categories in tree view"
|
||||||
- "Frontend displays individual post pages"
|
- "Related posts calculated by shared categories/tags"
|
||||||
- "Related posts shown based on shared categories/tags"
|
|
||||||
artifacts:
|
artifacts:
|
||||||
- path: "plugins/golem15/blog/models/Category.scala"
|
- path: "plugins/golem15/blog/models/Category.scala"
|
||||||
provides: "Hierarchical category model with nested set"
|
provides: "Hierarchical category model with nested set"
|
||||||
@@ -39,20 +38,16 @@ must_haves:
|
|||||||
- path: "plugins/golem15/blog/models/Tag.scala"
|
- path: "plugins/golem15/blog/models/Tag.scala"
|
||||||
provides: "Tag model for free-form labeling"
|
provides: "Tag model for free-form labeling"
|
||||||
contains: "case class Tag"
|
contains: "case class Tag"
|
||||||
- path: "plugins/golem15/blog/components/Posts.scala"
|
- path: "plugins/golem15/blog/services/CategoryService.scala"
|
||||||
provides: "Frontend post listing component"
|
provides: "Category CRUD with tree operations"
|
||||||
exports: ["PostsComponent", "onLoadMore"]
|
exports: ["CategoryService", "getTree", "getAllChildrenAndSelf"]
|
||||||
- path: "plugins/golem15/blog/components/Post.scala"
|
- path: "plugins/golem15/blog/controllers/Categories.scala"
|
||||||
provides: "Single post display component"
|
provides: "Admin controller for category management"
|
||||||
exports: ["PostComponent"]
|
exports: ["CategoriesController"]
|
||||||
- path: "plugins/golem15/blog/resources/db/migration/V10_2_2__blog_categories_tags.sql"
|
- path: "plugins/golem15/blog/resources/db/migration/V10_2_2__blog_categories_tags.sql"
|
||||||
provides: "Categories, tags, and pivot tables"
|
provides: "Categories, tags, and pivot tables"
|
||||||
contains: "CREATE TABLE blog_categories"
|
contains: "CREATE TABLE blog_categories"
|
||||||
key_links:
|
key_links:
|
||||||
- from: "plugins/golem15/blog/components/Posts.scala"
|
|
||||||
to: "BlogPostService"
|
|
||||||
via: "post listing"
|
|
||||||
pattern: "postService\\.listFrontend"
|
|
||||||
- from: "plugins/golem15/blog/services/BlogPostService.scala"
|
- from: "plugins/golem15/blog/services/BlogPostService.scala"
|
||||||
to: "CategoryService"
|
to: "CategoryService"
|
||||||
via: "category filtering"
|
via: "category filtering"
|
||||||
@@ -61,19 +56,23 @@ must_haves:
|
|||||||
to: "NestedTree"
|
to: "NestedTree"
|
||||||
via: "tree operations"
|
via: "tree operations"
|
||||||
pattern: "NestedTree\\."
|
pattern: "NestedTree\\."
|
||||||
|
- from: "plugins/golem15/blog/controllers/Categories.scala"
|
||||||
|
to: "CategoryService"
|
||||||
|
via: "CRUD operations"
|
||||||
|
pattern: "categoryService\\."
|
||||||
---
|
---
|
||||||
|
|
||||||
<objective>
|
<objective>
|
||||||
Complete the Blog plugin with categories, tags, and frontend listing components.
|
Add categories and tags to the Blog plugin with admin management.
|
||||||
|
|
||||||
Purpose: Enable content organization and frontend display, demonstrating hierarchical data structures and frontend component patterns.
|
Purpose: Enable content organization with hierarchical categories and free-form tags, providing the data layer for frontend components.
|
||||||
|
|
||||||
Output: Complete Blog plugin with:
|
Output: Blog plugin extended with:
|
||||||
- Hierarchical categories using nested set model
|
- Hierarchical categories using nested set model
|
||||||
- Tags for free-form labeling
|
- Tags for free-form labeling
|
||||||
- Frontend Posts component with pagination/infinite scroll
|
- Admin controller for category management
|
||||||
- Frontend Post component for single post display
|
- BlogPostService updated with category/tag relations
|
||||||
- Related posts based on shared categories/tags
|
- Related posts algorithm based on shared categories/tags
|
||||||
</objective>
|
</objective>
|
||||||
|
|
||||||
<execution_context>
|
<execution_context>
|
||||||
@@ -91,7 +90,7 @@ Output: Complete Blog plugin with:
|
|||||||
# Prior plan in this phase
|
# Prior plan in this phase
|
||||||
@.planning/phases/10-core-plugins/10-03-SUMMARY.md
|
@.planning/phases/10-core-plugins/10-03-SUMMARY.md
|
||||||
|
|
||||||
# Reference: Phase 3 component patterns for HTMX
|
# Reference: Phase 7 admin controller patterns
|
||||||
</context>
|
</context>
|
||||||
|
|
||||||
<tasks>
|
<tasks>
|
||||||
@@ -209,7 +208,7 @@ Output: Complete Blog plugin with:
|
|||||||
</task>
|
</task>
|
||||||
|
|
||||||
<task type="auto">
|
<task type="auto">
|
||||||
<name>Task 2: Update BlogPostService for relations</name>
|
<name>Task 2: Update BlogPostService and admin for relations</name>
|
||||||
<files>
|
<files>
|
||||||
plugins/golem15/blog/services/BlogPostService.scala (modify)
|
plugins/golem15/blog/services/BlogPostService.scala (modify)
|
||||||
plugins/golem15/blog/repositories/BlogPostRepository.scala (modify)
|
plugins/golem15/blog/repositories/BlogPostRepository.scala (modify)
|
||||||
@@ -340,150 +339,6 @@ Output: Complete Blog plugin with:
|
|||||||
</done>
|
</done>
|
||||||
</task>
|
</task>
|
||||||
|
|
||||||
<task type="auto">
|
|
||||||
<name>Task 3: Frontend listing components</name>
|
|
||||||
<files>
|
|
||||||
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 (modify)
|
|
||||||
</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
|
|
||||||
|
|
||||||
**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:**
|
|
||||||
|
|
||||||
**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
|
|
||||||
|
|
||||||
**categories/default.peb:**
|
|
||||||
- Tree or flat list based on displayMode
|
|
||||||
- Category name as link
|
|
||||||
- Post count if showPostCount
|
|
||||||
|
|
||||||
**relatedposts/default.peb:**
|
|
||||||
- Grid of related post cards
|
|
||||||
- Image, title, excerpt snippet
|
|
||||||
|
|
||||||
**Register all components in Plugin.scala boot method**
|
|
||||||
</action>
|
|
||||||
<verify>
|
|
||||||
./mill summercms.compile succeeds
|
|
||||||
Components registered in Plugin.scala
|
|
||||||
Templates have valid Pebble syntax
|
|
||||||
HTMX attributes properly formatted
|
|
||||||
</verify>
|
|
||||||
<done>
|
|
||||||
Posts component displays paginated listings with infinite scroll.
|
|
||||||
Post component displays single post with meta tags.
|
|
||||||
Categories component shows category tree/list.
|
|
||||||
RelatedPosts component shows related content.
|
|
||||||
All templates render with HTMX interactions.
|
|
||||||
</done>
|
|
||||||
</task>
|
|
||||||
|
|
||||||
</tasks>
|
</tasks>
|
||||||
|
|
||||||
<verification>
|
<verification>
|
||||||
@@ -491,9 +346,8 @@ After all tasks complete:
|
|||||||
1. Blog plugin compiles: `./mill summercms.compile`
|
1. Blog plugin compiles: `./mill summercms.compile`
|
||||||
2. Migration creates all tables: blog_categories, blog_tags, pivot tables
|
2. Migration creates all tables: blog_categories, blog_tags, pivot tables
|
||||||
3. Category nested tree rebuilds correctly on insert/delete
|
3. Category nested tree rebuilds correctly on insert/delete
|
||||||
4. Posts component loads and paginates
|
4. Posts admin form shows category tree selector and tag input
|
||||||
5. Post component displays with OpenGraph meta
|
5. Related posts algorithm returns relevant content
|
||||||
6. Related posts algorithm returns relevant content
|
|
||||||
</verification>
|
</verification>
|
||||||
|
|
||||||
<success_criteria>
|
<success_criteria>
|
||||||
@@ -501,10 +355,8 @@ After all tasks complete:
|
|||||||
- Nested set left/right values correct after operations
|
- Nested set left/right values correct after operations
|
||||||
- Tags can be created on-the-fly when typing
|
- Tags can be created on-the-fly when typing
|
||||||
- Post admin form shows category tree selector and tag input
|
- Post admin form shows category tree selector and tag input
|
||||||
- Frontend /blog shows post listing with load more
|
- Admin can manage categories with tree view and drag-drop
|
||||||
- Frontend /blog/:slug shows full post with related posts
|
- Related posts calculated by shared categories/tags
|
||||||
- Category pages filter posts by category and children
|
|
||||||
- Tag pages filter posts by tag
|
|
||||||
</success_criteria>
|
</success_criteria>
|
||||||
|
|
||||||
<output>
|
<output>
|
||||||
|
|||||||
287
.planning/phases/10-core-plugins/10-05-PLAN.md
Normal file
287
.planning/phases/10-core-plugins/10-05-PLAN.md
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
---
|
||||||
|
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>
|
||||||
Reference in New Issue
Block a user