package main import ( "context" "github.com/a-h/templ" "git.dev.alexdunmow.com/block/core/blocks" "git.dev.alexdunmow.com/block/core/plugin" "git.dev.alexdunmow.com/block/core/templates" ) // wrap adapts a templ-returning render function to templates.TemplateFunc. // templ.Component already implements templates.HTMLComponent via Render. func wrap(f func(ctx context.Context, doc map[string]any) templ.Component) templates.TemplateFunc { return func(ctx context.Context, doc map[string]any) templates.HTMLComponent { return f(ctx, doc) } } // Register is the plugin entry point. It registers the system template, the // four page templates, the six theme-specific blocks, the three overrides, // and the email wrapper. // // IMPORTANT: schemas are loaded BEFORE any br.Register(...) call — this is a // hard rule (CLAUDE.md, UAT §3) so the block registry binds content schemas // at registration time. func Register(tr templates.TemplateRegistry, br blocks.BlockRegistry) error { // --- System template --- tr.RegisterSystemTemplate(templates.SystemTemplateMeta{ Key: "scifi-clean", Title: "Sci-Fi Clean", Description: "Minimalist futurist theme for aerospace, robotics, and R&D — off-white surfaces, technical blue, signal-orange accents, mono numerals.", }) // --- Page templates (spec §6) --- if err := tr.RegisterPageTemplate("scifi-clean", templates.PageTemplateMeta{ Key: "default", Title: "Default", Description: "Standard cleanroom layout with header rail, main column, and footer instrument panel.", Slots: []string{"header", "main", "footer"}, }, wrap(RenderScifiClean)); err != nil { return err } if err := tr.RegisterPageTemplate("scifi-clean", templates.PageTemplateMeta{ Key: "landing", Title: "Landing", Description: "Hero diagram + spec strip + CTA banner.", Slots: []string{"hero", "specs", "main", "cta", "footer"}, }, wrap(RenderScifiCleanLanding)); err != nil { return err } if err := tr.RegisterPageTemplate("scifi-clean", templates.PageTemplateMeta{ Key: "article", Title: "Article", Description: "Narrow column with rail metadata and figure margin.", Slots: []string{"header", "rail", "main", "footer"}, }, wrap(RenderScifiCleanArticle)); err != nil { return err } if err := tr.RegisterPageTemplate("scifi-clean", templates.PageTemplateMeta{ Key: "full-width", Title: "Full Width", Description: "Edge-to-edge schematic surface for diagrams and dashboards.", Slots: []string{"header", "main", "footer"}, }, wrap(RenderScifiCleanFullWidth)); err != nil { return err } // --- Load JSON Schemas BEFORE any block registration --- if err := br.LoadSchemasFromFS(Schemas()); err != nil { return err } // --- Theme-specific blocks (spec §8) --- br.Register(TechSpecBlockMeta, TechSpecBlock) br.Register(DiagramCaptionBlockMeta, DiagramCaptionBlock) br.Register(MissionStatBlockMeta, MissionStatBlock) br.Register(StatusBarBlockMeta, StatusBarBlock) br.Register(FooterBlockMeta, FooterBlock) br.Register(SchematicHeroBlockMeta, SchematicHeroBlock) // --- Built-in overrides (spec §9) --- br.RegisterTemplateOverride("scifi-clean", "heading", ScifiHeadingBlock) br.RegisterTemplateOverride("scifi-clean", "text", ScifiTextBlock) br.RegisterTemplateOverride("scifi-clean", "button", ScifiButtonBlock) // --- Email wrapper (spec §10) --- tr.RegisterEmailWrapper("scifi-clean", ScifiEmailWrapper) return nil } // DefaultMasterPages returns the two master pages that Sci-Fi Clean provisions // on first plugin load (spec §7, UAT §9). // // - scifi-clean:default-master → attached to `default`, `article`, `full-width` // - scifi-clean:landing-master → attached to `landing`, pre-populates `hero` // with a mission_stat and `specs` with a tech_spec. func DefaultMasterPages() []plugin.MasterPageDefinition { defaultMaster := plugin.MasterPageDefinition{ Key: "scifi-clean:default-master", Title: "Sci-Fi Clean Default Master", PageTemplates: []string{"default", "article", "full-width"}, Blocks: []plugin.MasterPageBlock{ { BlockKey: "navbar", Title: "Mission Header", Content: map[string]any{"menuName": "main"}, Slot: "header", SortOrder: 0, }, { BlockKey: "scifi-clean:status_bar", Title: "System Status Rail", Content: map[string]any{"showClock": true, "showBuild": true, "callsign": "SCF-CLN"}, Slot: "header", SortOrder: 10, }, { BlockKey: "slot", Title: "Main Content", Content: map[string]any{"slotName": "main", "placeholder": "Page payload"}, Slot: "main", SortOrder: 0, }, { BlockKey: "scifi-clean:footer", Title: "Instrument Footer", Content: map[string]any{"showSignup": true, "callsign": "SCF-CLN"}, Slot: "footer", SortOrder: 0, }, }, } landingMaster := plugin.MasterPageDefinition{ Key: "scifi-clean:landing-master", Title: "Sci-Fi Clean Landing Master", PageTemplates: []string{"landing"}, Blocks: []plugin.MasterPageBlock{ { BlockKey: "scifi-clean:status_bar", Title: "System Status Rail", Content: map[string]any{"showClock": true, "showBuild": true, "callsign": "SCF-CLN"}, Slot: "header", SortOrder: 0, }, { BlockKey: "scifi-clean:mission_stat", Title: "Hero Mission Stat", Content: map[string]any{ "metric": "Apogee", "value": "412", "unit": "km", "delta": "+12", "trend": "up", }, Slot: "hero", SortOrder: 0, }, { BlockKey: "scifi-clean:tech_spec", Title: "Hero Tech Spec", Content: map[string]any{ "caption": "Launch envelope", "rows": []any{ map[string]any{"label": "Thrust", "value": "1.42", "unit": "MN"}, map[string]any{"label": "Burn time", "value": "186", "unit": "s"}, map[string]any{"label": "Payload", "value": "320", "unit": "kg"}, }, }, Slot: "specs", SortOrder: 0, }, { BlockKey: "slot", Title: "Main Content", Content: map[string]any{"slotName": "main", "placeholder": "Page payload"}, Slot: "main", SortOrder: 0, }, { BlockKey: "scifi-clean:footer", Title: "Instrument Footer", Content: map[string]any{"showSignup": true, "callsign": "SCF-CLN"}, Slot: "footer", SortOrder: 0, }, }, } return []plugin.MasterPageDefinition{defaultMaster, landingMaster} }