// render.proto — block/template render payloads and the explicit // render-context envelope (WO-WZ-001). // // RenderContext serializes every value blocks currently read from ctx via // core/blocks/context.go. Function-valued context entries (SlotRenderer, // MediaResolver, EmbedResolver, the generic Queries value) cannot cross the // wire; see core/docs/wasm-abi.md for how each one maps. syntax = "proto3"; package abi.v1; import "google/protobuf/timestamp.proto"; option go_package = "git.dev.alexdunmow.com/block/pluginsdk/abi/v1;abiv1"; // RenderBlockRequest renders one block (blocks.BlockFunc). message RenderBlockRequest { string block_key = 1; // JSON encoding of the block's content map. bytes content_json = 2; RenderContext render_context = 3; } message RenderBlockResponse { // Final rendered HTML for a plain (non-template) block. Ignored when // `powered` is set. string html = 1; // Set when the block is "powered": instead of final HTML, the block hands // back a template string + data map (blocks.PoweredBlock) and the HOST // renders it (pongo2/ninjatpl, host-side) AFTER this RENDER_BLOCK call has // returned. Because the block-invoke has already returned, the guest // instance is free, so any plugin tag/filter the template hits (RENDER_TAG / // APPLY_FILTER) is a fresh invoke — no re-entrancy. A singular message field // has explicit presence: nil (GetPowered() == nil) means a plain HTML block. PoweredBlock powered = 2; } // PoweredBlock is a template-backed block result. The host renders `template` // with `data_json` as the pongo2 data map. See core/docs/wasm-abi.md // §"Powered blocks (render as a host capability)". message PoweredBlock { // ninjatpl/pongo2 template source the host renders host-side. string template = 1; // JSON encoding of the template data map (map[string]any). bytes data_json = 2; } // RenderTemplateRequest renders one template (templates.TemplateFunc). message RenderTemplateRequest { string template_key = 1; // JSON encoding of the template's doc map (the page document). bytes doc_json = 2; RenderContext render_context = 3; } message RenderTemplateResponse { bytes html = 1; } // RenderTagRequest invokes a plugin-declared template tag (HOOK_RENDER_TAG). // The host wires one pongo2 tag per manifest.declared_tags name; when its // engine hits that tag while rendering a powered block, it calls back here. // Re-entrancy-free: the originating RENDER_BLOCK has already returned. message RenderTagRequest { string tag_name = 1; // JSON encoding of the tag's parsed arguments (map[string]any). bytes args_json = 2; // The active render context (same envelope RENDER_BLOCK carries), so the // tag fn can read page/post/request state via blocks.Get*. RenderContext render_context = 3; } // RenderTagResponse returns the tag's rendered HTML. A non-empty `error` // carries the tag fn's returned error (distinct from an AbiError, which is // reserved for transport/dispatch/panic failures). message RenderTagResponse { string html = 1; string error = 2; } // ApplyFilterRequest invokes a plugin-declared template filter // (HOOK_APPLY_FILTER). The host wires one pongo2 filter per // manifest.declared_filters name. message ApplyFilterRequest { string filter_name = 1; // The value the filter is applied to (pongo2 passes strings). string input = 2; // JSON encoding of the filter's arguments (map[string]any). bytes args_json = 3; } // ApplyFilterResponse returns the filtered output. A non-empty `error` // carries the filter fn's returned error. message ApplyFilterResponse { string output = 1; string error = 2; } // RenderContext is the explicit envelope of the context values enumerated // from core/blocks/context.go. Absent sub-messages mean the corresponding // ctx value was not set (the getters return nil / zero values). message RenderContext { // blocks.GetRequest — subset of *http.Request that render code reads. RequestInfo request = 1; // blocks.GetBlockContext — the pongo2 template data struct. BlockContext block_context = 2; // blocks.GetTemplateKey. string template_key = 3; // blocks.GetCurrentPage. PageContext page = 4; // blocks.GetCurrentBlogPost. PostContext post = 5; // blocks.GetCurrentAuthor. AuthorContext author = 6; // blocks.GetCurrentCategory. CategoryContext category = 7; // blocks.GetMasterPage; presence doubles as IsMasterPageContext. MasterPageContext master_page = 8; // blocks.GetRequestedPath (404 pages). string requested_path = 9; // blocks.GetInjectedSlots (master page rendering). map injected_slots = 10; // blocks.IsEditor. bool is_editor = 11; // blocks.GetExpectedSlots. repeated string expected_slots = 12; // blocks.GetBlockID (UUID; empty for uuid.Nil). string block_id = 13; // blocks.GetCurrentPageID (UUID; empty for uuid.Nil). string current_page_id = 14; // blocks.GetHumanProofBanner. HumanProofBanner human_proof_banner = 15; // blocks.GetDetailRow. DetailRow detail_row = 16; // Active theme variables, JSON-encoded. bytes theme_json = 17; // Site locale (BCP 47). string locale = 18; } // RequestInfo carries the request subset render code reads from // blocks.GetRequest (single-valued header map, matching BlockContext). message RequestInfo { string method = 1; string url = 2; string path = 3; string host = 4; string raw_query = 5; map headers = 6; map cookies = 7; string remote_ip = 8; string referrer = 9; string user_agent = 10; } // PageContext mirrors blocks.PageContext. message PageContext { string id = 1; // UUID string slug = 2; string title = 3; string post_type = 4; // "page", "post", "master", "system" string status = 5; // "published", "draft", "scheduled" } // PostContext mirrors blocks.PostContext. message PostContext { string id = 1; // UUID string slug = 2; string title = 3; string excerpt = 4; string featured_image_url = 5; string author_id = 6; // UUID google.protobuf.Timestamp published_at = 7; int32 reading_time = 8; bool is_featured = 9; } // AuthorContext mirrors blocks.AuthorContext. message AuthorContext { string id = 1; // UUID string name = 2; string slug = 3; string bio = 4; string avatar_url = 5; } // CategoryContext mirrors blocks.CategoryContext. message CategoryContext { string id = 1; // UUID string name = 2; string slug = 3; } // MasterPageContext mirrors blocks.MasterPageContext. message MasterPageContext { string id = 1; // UUID string slug = 2; string title = 3; } // HumanProofBanner mirrors blocks.HumanProofBannerData. message HumanProofBanner { int32 active_time_minutes = 1; int32 keystroke_count = 2; int32 session_count = 3; string post_slug = 4; } // DetailRow mirrors blocks.DetailRowInfo. message DetailRow { string table_id = 1; string row_id = 2; // JSON encoding of the row data map. bytes data_json = 3; } // BlockContext mirrors blocks.BlockContext (the pongo2 data struct) 1:1. // map[string]any fields travel as JSON bytes. message BlockContext { string url = 1; string path = 2; string slug = 3; string page_id = 4; string page_title = 5; string template_key = 6; bool is_editor = 7; int64 timestamp = 8; google.protobuf.Timestamp now = 9; bool is_logged_in = 10; string user_id = 11; string user_email = 12; string user_role = 13; string method = 14; string host = 15; map query = 16; string referrer = 17; string user_agent = 18; string ip = 19; map cookies = 20; map headers = 21; string country = 22; string city = 23; string timezone = 24; bytes current_author_json = 25; bytes current_post_json = 26; bytes current_category_json = 27; bytes site_json = 28; bool is_public_logged_in = 29; string public_user_id = 30; string public_username = 31; string public_display_name = 32; bool public_email_verified = 33; string detail_row_id = 34; string detail_table_id = 35; bytes detail_row_data_json = 36; string blog_index_url = 37; string category_page_url = 38; }