diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 2367eae..f515ef4 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -195,13 +195,14 @@ Plans:
6. Blog posts can be organized into categories
7. Blog posts can be tagged with multiple tags
8. Frontend displays blog post listings via components
-**Plans**: 4 plans
+**Plans**: 5 plans
Plans:
- [ ] 10-01-PLAN.md - User plugin models, services, and authentication components
- [ ] 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-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
@@ -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 | - |
| 8. Admin Dashboard | 0/4 | Planned | - |
| 9. Content Management | 0/7 | Planned | - |
-| 10. Core Plugins | 0/4 | Planned | - |
+| 10. Core Plugins | 0/5 | Planned | - |
---
*Roadmap created: 2026-02-04*
diff --git a/.planning/phases/10-core-plugins/10-02-PLAN.md b/.planning/phases/10-core-plugins/10-02-PLAN.md
index c0bbd5a..f5c7290 100644
--- a/.planning/phases/10-core-plugins/10-02-PLAN.md
+++ b/.planning/phases/10-core-plugins/10-02-PLAN.md
@@ -6,6 +6,7 @@ wave: 2
depends_on: ["10-01"]
files_modified:
- plugins/golem15/user/components/ResetPassword.scala
+ - plugins/golem15/user/components/Account.scala
- plugins/golem15/user/services/FrontendUserService.scala
- plugins/golem15/user/services/UserMailService.scala
- plugins/golem15/user/resources/views/account/profile.peb
@@ -155,6 +156,7 @@ Output: Working profile and reset features:
Task 2: Password reset flow and email service
plugins/golem15/user/components/ResetPassword.scala
+ plugins/golem15/user/components/Account.scala (modify)
plugins/golem15/user/services/UserMailService.scala
plugins/golem15/user/services/FrontendUserService.scala (modify)
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
- 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:**
- requestPasswordReset(email: String): Unit
- Find user by email (don't reveal if not found - timing attack)
@@ -230,13 +239,14 @@ Output: Working profile and reset features:
./mill summercms.compile succeeds
ResetPassword component registered in Plugin.scala
+ Account.scala onRegister calls mailService.sendActivationEmail when activateMode == User
Email templates have valid Pebble syntax
Reset code generation uses SecureRandom
Password reset request sends email (or stubs if no SMTP).
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).
@@ -247,9 +257,10 @@ Output: Working profile and reset features:
After all tasks complete:
1. User plugin compiles: `./mill summercms.compile`
2. Account component has all handlers: onSignin, onRegister, onActivate, onUpdate, onUploadAvatar
-3. ResetPassword component handles: onRequest, onReset
-4. Email service can send activation and reset emails
-5. Templates render valid forms with HTMX
+3. Account.scala onRegister integrates UserMailService for activation emails
+4. ResetPassword component handles: onRequest, onReset
+5. Email service can send activation and reset emails
+6. Templates render valid forms with HTMX
@@ -257,6 +268,7 @@ After all tasks complete:
- Profile update saves name/surname changes
- Password change requires current password confirmation
- 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)
- Reset link with valid code allows new password entry
- User automatically logged in after successful reset
diff --git a/.planning/phases/10-core-plugins/10-04-PLAN.md b/.planning/phases/10-core-plugins/10-04-PLAN.md
index c7020e8..ce86d18 100644
--- a/.planning/phases/10-core-plugins/10-04-PLAN.md
+++ b/.planning/phases/10-core-plugins/10-04-PLAN.md
@@ -7,21 +7,21 @@ depends_on: ["10-03"]
files_modified:
- plugins/golem15/blog/models/Category.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/TagRepository.scala
- plugins/golem15/blog/services/CategoryService.scala
- plugins/golem15/blog/services/TagService.scala
+ - plugins/golem15/blog/services/NestedTree.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/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/controllers/posts/fields.yaml
- plugins/golem15/blog/resources/controllers/categories/fields.yaml
- - 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/controllers/categories/columns.yaml
autonomous: true
must_haves:
@@ -29,9 +29,8 @@ must_haves:
- "Blog posts can be assigned to categories"
- "Categories are hierarchical (parent/child)"
- "Blog posts can have multiple tags"
- - "Frontend displays post listings with pagination"
- - "Frontend displays individual post pages"
- - "Related posts shown based on shared categories/tags"
+ - "Admin can manage categories in tree view"
+ - "Related posts calculated by shared categories/tags"
artifacts:
- path: "plugins/golem15/blog/models/Category.scala"
provides: "Hierarchical category model with nested set"
@@ -39,20 +38,16 @@ must_haves:
- path: "plugins/golem15/blog/models/Tag.scala"
provides: "Tag model for free-form labeling"
contains: "case class Tag"
- - 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/services/CategoryService.scala"
+ provides: "Category CRUD with tree operations"
+ exports: ["CategoryService", "getTree", "getAllChildrenAndSelf"]
+ - path: "plugins/golem15/blog/controllers/Categories.scala"
+ provides: "Admin controller for category management"
+ exports: ["CategoriesController"]
- path: "plugins/golem15/blog/resources/db/migration/V10_2_2__blog_categories_tags.sql"
provides: "Categories, tags, and pivot tables"
contains: "CREATE TABLE blog_categories"
key_links:
- - from: "plugins/golem15/blog/components/Posts.scala"
- to: "BlogPostService"
- via: "post listing"
- pattern: "postService\\.listFrontend"
- from: "plugins/golem15/blog/services/BlogPostService.scala"
to: "CategoryService"
via: "category filtering"
@@ -61,19 +56,23 @@ must_haves:
to: "NestedTree"
via: "tree operations"
pattern: "NestedTree\\."
+ - from: "plugins/golem15/blog/controllers/Categories.scala"
+ to: "CategoryService"
+ via: "CRUD operations"
+ pattern: "categoryService\\."
---
-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
- Tags for free-form labeling
-- Frontend Posts component with pagination/infinite scroll
-- Frontend Post component for single post display
-- Related posts based on shared categories/tags
+- Admin controller for category management
+- BlogPostService updated with category/tag relations
+- Related posts algorithm based on shared categories/tags
@@ -91,7 +90,7 @@ Output: Complete Blog plugin with:
# Prior plan in this phase
@.planning/phases/10-core-plugins/10-03-SUMMARY.md
-# Reference: Phase 3 component patterns for HTMX
+# Reference: Phase 7 admin controller patterns
@@ -209,7 +208,7 @@ Output: Complete Blog plugin with:
- Task 2: Update BlogPostService for relations
+ Task 2: Update BlogPostService and admin for relations
plugins/golem15/blog/services/BlogPostService.scala (modify)
plugins/golem15/blog/repositories/BlogPostRepository.scala (modify)
@@ -340,150 +339,6 @@ Output: Complete Blog plugin with:
-
- Task 3: Frontend listing components
-
- 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)
-
-
- 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
-
- {% if posts.items is empty %}
-
{{ noPostsMessage }}
- {% else %}
-
- {% for post in posts.items %}
- {% include "posts/item" %}
- {% endfor %}
-
-
- {% if posts.hasNext %}
-
-
-
- {% endif %}
- {% endif %}
-
- ```
-
- **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**
-
-
- ./mill summercms.compile succeeds
- Components registered in Plugin.scala
- Templates have valid Pebble syntax
- HTMX attributes properly formatted
-
-
- 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.
-
-
-
@@ -491,9 +346,8 @@ After all tasks complete:
1. Blog plugin compiles: `./mill summercms.compile`
2. Migration creates all tables: blog_categories, blog_tags, pivot tables
3. Category nested tree rebuilds correctly on insert/delete
-4. Posts component loads and paginates
-5. Post component displays with OpenGraph meta
-6. Related posts algorithm returns relevant content
+4. Posts admin form shows category tree selector and tag input
+5. Related posts algorithm returns relevant content
@@ -501,10 +355,8 @@ After all tasks complete:
- Nested set left/right values correct after operations
- Tags can be created on-the-fly when typing
- Post admin form shows category tree selector and tag input
-- 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
+- Admin can manage categories with tree view and drag-drop
+- Related posts calculated by shared categories/tags