---
phase: 09-content-management
plan: 02
type: execute
wave: 2
depends_on: ["09-01"]
files_modified:
  - summercms/src/content/cms/pebble/ComponentTag.scala
  - summercms/src/content/cms/pebble/PlaceholderTag.scala
  - summercms/src/content/cms/pebble/PartialTag.scala
  - summercms/src/content/cms/pebble/PutTag.scala
  - summercms/src/content/cms/pebble/CmsPebbleExtension.scala
  - summercms/src/content/cms/PageRenderContext.scala
  - summercms/src/content/cms/CmsPageService.scala
autonomous: true

must_haves:
  truths:
    - "Components can be embedded in CMS pages via {% component 'alias' %} syntax"
    - "Components receive properties from page config and inline overrides"
    - "Layouts support {% placeholder 'name' %}default{% endplaceholder %} blocks"
    - "Pages can fill placeholders with {% put 'name' %}content{% endput %}"
    - "Partials render via {% partial 'name' %} with variable passing"
  artifacts:
    - path: "summercms/src/content/cms/pebble/ComponentTag.scala"
      provides: "{% component %} tag implementation"
      contains: "class ComponentTag extends TokenParser"
    - path: "summercms/src/content/cms/pebble/PlaceholderTag.scala"
      provides: "{% placeholder %} tag with defaults"
      contains: "class PlaceholderTag extends TokenParser"
    - path: "summercms/src/content/cms/pebble/CmsPebbleExtension.scala"
      provides: "Extension registering all CMS tags"
      contains: "class CmsPebbleExtension extends Extension"
  key_links:
    - from: "ComponentTag"
      to: "PageRenderContext.components"
      via: "resolves component by alias from page context"
      pattern: "pageContext\\.components\\.get"
    - from: "PlaceholderTag"
      to: "PageRenderContext.placeholderContent"
      via: "checks for put content before rendering default"
      pattern: "placeholderContent\\.get"
---

<objective>
Implement component embedding and placeholder system for CMS pages

Purpose: Enable pages to embed dynamic components via Twig-like syntax and use layouts with placeholders that pages can fill. This completes the template composition system per CONTEXT.md decisions.

Output: Custom Pebble tags (component, placeholder, put, partial), CmsPebbleExtension registering all tags, updated CmsPageService render pipeline.
</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/phases/09-content-management/09-RESEARCH.md
@.planning/phases/09-content-management/09-01-SUMMARY.md
@.planning/phases/03-component-system/03-RESEARCH.md (Component system patterns)
@summercms/src/content/cms/CmsPage.scala
@summercms/src/content/cms/CmsPageService.scala
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create PageRenderContext and placeholder/put tags</name>
  <files>
    summercms/src/content/cms/PageRenderContext.scala
    summercms/src/content/cms/pebble/PlaceholderTag.scala
    summercms/src/content/cms/pebble/PutTag.scala
  </files>
  <action>
**PageRenderContext.scala:**
Create render context passed through Pebble evaluation:
```scala
case class PageRenderContext(
  page: CmsPage,
  components: Map[String, ComponentInstance],  // alias -> initialized component
  placeholderContent: mutable.Map[String, String],  // name -> rendered content
  runtime: Runtime[Any]  // for ZIO effect execution in sync context
)
```

**PlaceholderTag.scala:**
Implement Pebble TokenParser for `{% placeholder 'name' %}default{% endplaceholder %}`:
- Parse placeholder name expression
- Parse body until `endplaceholder`
- Create PlaceholderNode that:
  - Evaluates name from context
  - Checks PageRenderContext.placeholderContent for that name
  - If content exists, write it
  - Otherwise, render default body

Per CONTEXT.md: Flat layouts only, placeholders with defaults.

**PutTag.scala:**
Implement `{% put 'name' %}content{% endput %}`:
- Parse placeholder name expression
- Parse body until `endput`
- Create PutNode that:
  - Renders body to string
  - Stores in PageRenderContext.placeholderContent[name]
  - Writes nothing to output (content stored for later use)

Two-pass rendering needed: First pass collects `put` content, second pass renders layout with placeholders resolved.
  </action>
  <verify>
`./mill summercms.compile` succeeds with new Pebble tags
  </verify>
  <done>
PlaceholderTag and PutTag allow layouts to define placeholders with defaults that pages can override. PageRenderContext stores render state.
  </done>
</task>

<task type="auto">
  <name>Task 2: Implement component and partial tags</name>
  <files>
    summercms/src/content/cms/pebble/ComponentTag.scala
    summercms/src/content/cms/pebble/PartialTag.scala
  </files>
  <action>
**ComponentTag.scala:**
Implement `{% component 'alias' prop1='value' %}`:
- Parse required alias expression (string)
- Parse optional key=value property pairs until EXECUTE_END
- Create ComponentNode that:
  - Gets PageRenderContext from evaluation context
  - Looks up component by alias in pageContext.components
  - If not found, throw PebbleException with line number
  - Override component properties with tag-provided values
  - Call component.render() using pageContext.runtime.unsafeRun
  - Write rendered HTML to output

Component lookup: Page's INI section defines components like:
```
[blogPosts posts]
postsPerPage = 5
```
Where `blogPosts` is component class, `posts` is alias, properties follow.

**PartialTag.scala:**
Implement `{% partial 'name' var1='value' %}`:
- Parse partial name expression
- Parse optional variable assignments
- Create PartialNode that:
  - Resolves partial from theme (partials/{name}.htm)
  - Or from plugin namespace (plugin::partialName)
  - Renders partial template with provided variables merged into context
  - Write output

Per CONTEXT.md: `plugin::partial` for plugin partials, plain name for theme partials.
  </action>
  <verify>
`./mill summercms.compile` succeeds
ComponentTag parses component alias and properties
PartialTag resolves partial files correctly
  </verify>
  <done>
ComponentTag embeds components by alias with property overrides. PartialTag includes partial templates with variable passing. Both integrate with Pebble rendering.
  </done>
</task>

<task type="auto">
  <name>Task 3: Create CmsPebbleExtension and update render pipeline</name>
  <files>
    summercms/src/content/cms/pebble/CmsPebbleExtension.scala
    summercms/src/content/cms/CmsPageService.scala
  </files>
  <action>
**CmsPebbleExtension.scala:**
Create Pebble Extension registering all CMS tags:
```scala
class CmsPebbleExtension extends Extension:
  override def getTokenParsers: java.util.List[TokenParser] =
    java.util.Arrays.asList(
      new ComponentTag(),
      new PlaceholderTag(),
      new PutTag(),
      new PartialTag()
    )

  override def getGlobalVariables: java.util.Map[String, Object] = null
  override def getFilters: java.util.Map[String, Filter] = null
  // ... other Extension methods return null/empty
```

**Update CmsPageService.scala render method:**
Implement two-pass rendering:

1. Initialize components from page config:
   - Parse component definitions from page settings
   - For each `[componentClass alias]` section, create ComponentInstance
   - Run component lifecycle (init, onRun)

2. First pass - render page content:
   - Create PageRenderContext with mutable.Map for placeholderContent
   - Render page.markup with Pebble
   - This collects {% put %} content into placeholderContent

3. Second pass - render layout:
   - Get layout from page.config.layout
   - Add pageContent variable with rendered page HTML
   - Render layout.markup
   - {% placeholder %} tags now have access to collected content
   - {% page %} tag outputs pageContent variable

Add `{% page %}` as simple Pebble output: `{{ pageContent }}` or custom PageTag.

Note: Must configure PebbleEngine with CmsPebbleExtension when building the engine layer.
  </action>
  <verify>
`./mill summercms.compile` succeeds
End-to-end test: Create sample page with component, partial, and placeholder
Render should produce composed HTML with all elements
  </verify>
  <done>
CmsPebbleExtension registers all CMS tags with Pebble. CmsPageService.render uses two-pass rendering: first to collect put content, then to render layout with placeholders resolved.
  </done>
</task>

</tasks>

<verification>
After all tasks complete:
1. `./mill summercms.compile` - all code compiles
2. Create test page with all features:
```
url = "/test"
layout = "default"

[myComponent demo]
property = "value"
==
{% put 'sidebar' %}
  <aside>Custom sidebar</aside>
{% endput %}

<main>
  {% component 'demo' extraProp='override' %}
  {% partial 'shared-block' title='Hello' %}
</main>
```
3. Create test layout:
```
<!DOCTYPE html>
<html>
<body>
  {% placeholder 'sidebar' %}
    <aside>Default sidebar</aside>
  {% endplaceholder %}
  <div class="content">
    {% page %}
  </div>
</body>
</html>
```
4. Render produces correct composed HTML with custom sidebar
</verification>

<success_criteria>
- {% component 'alias' %} embeds components with property passing
- {% placeholder 'name' %}default{% endplaceholder %} in layouts with page-fillable content
- {% put 'name' %}content{% endput %} in pages fills layout placeholders
- {% partial 'name' %} includes partials with variables
- Two-pass rendering correctly resolves all template composition
</success_criteria>

<output>
After completion, create `.planning/phases/09-content-management/09-02-SUMMARY.md`
</output>
