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 diff --git a/.planning/phases/10-core-plugins/10-05-PLAN.md b/.planning/phases/10-core-plugins/10-05-PLAN.md new file mode 100644 index 0000000..9e7dc54 --- /dev/null +++ b/.planning/phases/10-core-plugins/10-05-PLAN.md @@ -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" +--- + + +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 + + + +@/home/jin/.claude/get-shit-done/workflows/execute-plan.md +@/home/jin/.claude/get-shit-done/templates/summary.md + + + +@.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 + + + + + + Task 1: Posts and Post listing components + + 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 + + + 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 +
+ {% 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 +
+ + ./mill summercms.compile succeeds + Posts component has onLoadMore HTMX handler + Post component sets OpenGraph meta tags + Templates have valid Pebble syntax + + + Posts component displays paginated listings with infinite scroll. + Post component displays single post with meta tags. + Templates render with HTMX interactions. + +
+ + + Task 2: Categories and RelatedPosts components + + 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) + + + 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 + + + ./mill summercms.compile succeeds + Components registered in Plugin.scala + Templates have valid Pebble syntax + HTMX attributes properly formatted + + + Categories component shows category tree/list. + RelatedPosts component shows related content. + All components registered in Plugin.scala. + Blog frontend fully functional. + + + +
+ + +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 + + + +- 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 + + + +After completion, create `.planning/phases/10-core-plugins/10-05-SUMMARY.md` +