diff --git a/.planning/phases/02-plugin-system/02-01-PLAN.md b/.planning/phases/02-plugin-system/02-01-PLAN.md
index 34453c9..a968ebd 100644
--- a/.planning/phases/02-plugin-system/02-01-PLAN.md
+++ b/.planning/phases/02-plugin-system/02-01-PLAN.md
@@ -282,7 +282,25 @@ dependencies:
golem15.user: "^1.0.0"
```
-Then verify parsing works by checking the code compiles and the types are correct.
+Then test discovery works by running this command in the Mill REPL:
+```bash
+./mill -i summercms.console
+```
+
+Once in the REPL, verify parsing:
+```scala
+import plugin._
+val yaml = """
+vendor: test
+name: sample
+version: 1.0.0
+description: Test plugin
+"""
+val result = PluginManifest.parse(yaml)
+println(result) // Should print Right(PluginManifest(test,sample,1.0.0,...))
+```
+
+Exit REPL with `:quit` after verification.
PluginManifest parses YAML manifests, PluginDiscovery scans plugins/ directory, plugins/ directory exists
diff --git a/.planning/phases/02-plugin-system/02-02-PLAN.md b/.planning/phases/02-plugin-system/02-02-PLAN.md
index efa9404..ee57da9 100644
--- a/.planning/phases/02-plugin-system/02-02-PLAN.md
+++ b/.planning/phases/02-plugin-system/02-02-PLAN.md
@@ -136,7 +136,9 @@ case class PluginRegistration(
// Event subscriptions (Phase 2 extension API)
events: List[EventSubscription] = List.empty,
// Extensions to other plugins (Phase 2 extension API)
- extensions: List[ExtensionDef] = List.empty
+ extensions: List[ExtensionDef] = List.empty,
+ // Form field definitions (for YAML-driven forms)
+ fields: List[FieldDef] = List.empty
)
object PluginRegistration:
@@ -149,6 +151,7 @@ case class NavigationDef(id: String, label: String, url: String, icon: String =
case class SettingDef(key: String, label: String, icon: String = "")
case class EventSubscription(eventType: String, handler: String)
case class ExtensionDef(target: String, extensionClass: String)
+case class FieldDef(name: String, fieldType: String)
```
**SummerPlugin.scala:**
@@ -172,6 +175,9 @@ trait SummerPlugin:
* Async boot phase - can perform effects.
* Called after all dependencies have booted.
* Use for: database setup, event subscriptions, service initialization.
+ *
+ * PluginEnv is a placeholder that will be expanded in 02-03 to include
+ * EventService and ExtensionRegistry when the extension API is implemented.
*/
def boot: ZIO[PluginEnv, PluginError, Unit] = ZIO.unit
@@ -181,12 +187,15 @@ trait SummerPlugin:
*/
def shutdown: ZIO[Any, Nothing, Unit] = ZIO.unit
-/** Environment available to plugins during boot */
+/**
+ * Environment available to plugins during boot.
+ * Initially just PluginContext; extended in 02-03 to include EventService & ExtensionRegistry.
+ */
type PluginEnv = PluginContext
```
Run `./mill summercms.compile` - all types compile without errors
- PluginState enum with all lifecycle states, PluginContext, PluginRegistration with placeholder defs, SummerPlugin trait
+ PluginState enum with all lifecycle states, PluginContext, PluginRegistration with placeholder defs including FieldDef, SummerPlugin trait
diff --git a/.planning/phases/02-plugin-system/02-03-PLAN.md b/.planning/phases/02-plugin-system/02-03-PLAN.md
index 6942fc8..2c97ef9 100644
--- a/.planning/phases/02-plugin-system/02-03-PLAN.md
+++ b/.planning/phases/02-plugin-system/02-03-PLAN.md
@@ -9,6 +9,7 @@ files_modified:
- summercms/src/plugin/ExtensionRegistry.scala
- summercms/src/plugin/SummerEvent.scala
- summercms/src/plugin/package.scala
+ - summercms/src/plugin/SummerPlugin.scala
- summercms/src/Main.scala
autonomous: true
@@ -311,7 +312,7 @@ trait BlogExtension:
/** Validate post data */
def validate(data: Map[String, Any]): Either[String, Unit] = Right(())
-// FieldDef is already defined in PluginRegistration.scala, reuse it
+// FieldDef is defined in PluginRegistration.scala
```
Run `./mill summercms.compile` - ExtensionRegistry compiles. It provides type-safe extension registration keyed by ClassTag.
@@ -319,9 +320,10 @@ trait BlogExtension:
- Task 3: Create plugin package exports and integrate with Main
+ Task 3: Create plugin package exports, update PluginEnv, and integrate with Main
summercms/src/plugin/package.scala
+ summercms/src/plugin/SummerPlugin.scala
summercms/src/Main.scala
@@ -340,87 +342,47 @@ package object plugin:
type PluginBootEnv = PluginContext & EventService & ExtensionRegistry
```
-**Update Main.scala** to integrate the plugin system:
+**Update SummerPlugin.scala** to use the expanded PluginEnv:
+
+Read the existing file from 02-02 and update ONLY the PluginEnv type alias at the bottom:
+
```scala
+package plugin
+
import zio.*
-import zio.http.*
-import zio.config.typesafe.TypesafeConfigProvider
-import api.Routes
-import _root_.config.{AppConfig as SummerConfig}
-import db.QuillContext
-import plugin.{PluginManager, PluginDiscovery, pluginLayer}
+/** Base trait for all SummerCMS plugins */
+trait SummerPlugin:
+ /** Plugin identifier - must match manifest */
+ def id: PluginId
-object Main extends ZIOAppDefault {
+ /**
+ * Synchronous registration phase - pure data, no effects.
+ * Called during discovery to collect plugin declarations.
+ */
+ def register(ctx: PluginContext): PluginRegistration = PluginRegistration.empty
- private val banner: String =
- """
- |
- | | .
- | `. * | .'
- | `. ._|_* .' .
- | . * .' `. *
- | -------| |-------
- | . *`.___.' * .
- | .' |* `. *
- | .' * | . `.
- | . |
- |
- | S U M M E R C M S
- |""".stripMargin
+ /**
+ * Async boot phase - can perform effects.
+ * Called after all dependencies have booted.
+ * Use for: database setup, event subscriptions, service initialization.
+ */
+ def boot: ZIO[PluginEnv, PluginError, Unit] = ZIO.unit
- override val bootstrap: ZLayer[ZIOAppArgs, Any, Any] =
- Runtime.setConfigProvider(TypesafeConfigProvider.fromResourcePath())
+ /**
+ * Async shutdown phase - cleanup resources.
+ * Called in reverse dependency order during application shutdown.
+ */
+ def shutdown: ZIO[Any, Nothing, Unit] = ZIO.unit
- override def run: ZIO[Any, Any, Any] =
- for {
- cfg <- ZIO.config[SummerConfig](SummerConfig.config)
- _ <- Console.printLine(banner)
- _ <- Console.printLine(s"Starting on port ${cfg.server.port}...")
- _ <- Console.printLine("")
- // Initialize plugin system
- _ <- Console.printLine("Loading plugins...")
- _ <- PluginManager.loadPlugins(PluginDiscovery.defaultPluginsDir)
- .catchAll(e => Console.printLine(s"Plugin loading warning: ${e.message}").as(()))
- _ <- PluginManager.bootAll
- .catchAll(e => Console.printLine(s"Plugin boot warning: ${e.message}").as(()))
- plugins <- PluginManager.listPlugins
- _ <- Console.printLine(s"Loaded ${plugins.count(_._2.isActive)} plugin(s)")
- _ <- Console.printLine("")
- // Note: Migrations are NOT auto-run. Use CLI to run migrations (Phase 5).
- _ <- Server.serve(Routes.routes).provide(
- Server.defaultWithPort(cfg.server.port),
- QuillContext.dataSourceLayer
- )
- } yield ()
-
- // Provide plugin layers
- override def run: ZIO[Any, Any, Any] =
- (for {
- cfg <- ZIO.config[SummerConfig](SummerConfig.config)
- _ <- Console.printLine(banner)
- _ <- Console.printLine(s"Starting on port ${cfg.server.port}...")
- _ <- Console.printLine("")
- // Initialize plugin system
- _ <- Console.printLine("Loading plugins...")
- _ <- PluginManager.loadPlugins(PluginDiscovery.defaultPluginsDir)
- .catchAll(e => Console.printLine(s"Plugin loading warning: ${e.message}").as(()))
- _ <- PluginManager.bootAll
- .catchAll(e => Console.printLine(s"Plugin boot warning: ${e.message}").as(()))
- plugins <- PluginManager.listPlugins
- _ <- Console.printLine(s"Loaded ${plugins.count(_._2.isActive)} plugin(s)")
- _ <- Console.printLine("")
- // Note: Migrations are NOT auto-run. Use CLI to run migrations (Phase 5).
- _ <- Server.serve(Routes.routes).provide(
- Server.defaultWithPort(cfg.server.port),
- QuillContext.dataSourceLayer
- )
- } yield ()).provide(pluginLayer)
+/**
+ * Environment available to plugins during boot.
+ * Includes context, events, and extension registry for full plugin capabilities.
+ */
+type PluginEnv = PluginContext & EventService & ExtensionRegistry
```
-Wait - the above has a duplicate `def run`. Let me provide the correct version:
-
-**Main.scala (corrected full file):**
+**Main.scala** (complete replacement):
```scala
import zio.*
import zio.http.*
@@ -483,7 +445,7 @@ object Main extends ZIOAppDefault {
Run `./mill summercms.compile` - Main.scala compiles with plugin integration.
Run `./mill summercms.run` - Server starts, logs "Loading plugins..." and "Loaded 0 plugin(s)" (since no plugins exist yet).
- Plugin package exports combined layer, Main.scala loads and boots plugins on startup
+ Plugin package exports combined layer, PluginEnv updated to include EventService and ExtensionRegistry, Main.scala loads and boots plugins on startup