310 lines
10 KiB
Protocol Buffer
310 lines
10 KiB
Protocol Buffer
// 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;
|
||
// Requested HTTP response status. Before writing headers, the host accepts
|
||
// only 200–599, uses the highest status class, and keeps the first result
|
||
// encountered within that class.
|
||
int32 status_code = 3;
|
||
// Structured document metadata merged by the host into the page head. The
|
||
// first non-empty value for each field wins in deterministic render order.
|
||
DocumentMetadata metadata = 4;
|
||
}
|
||
|
||
// DocumentMetadata is the safe, shared subset of document head data a plugin
|
||
// may provide. The host validates and escapes every value before rendering.
|
||
message DocumentMetadata {
|
||
string title = 1;
|
||
string description = 2;
|
||
string canonical_url = 3;
|
||
string image_url = 4;
|
||
string robots = 5;
|
||
}
|
||
|
||
// 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 {
|
||
// Field 1 was `bytes html` — the guest-rendered full document, retired
|
||
// flag-day in P3 (host-side chrome composition). Reserved so a stale
|
||
// pre-v0.2.0 artifact decodes to an EMPTY document and the host can fail
|
||
// that render with an explicit republish error instead of mis-parsing.
|
||
reserved 1;
|
||
TemplateDocument document = 2;
|
||
}
|
||
|
||
// TemplateDocument is a guest template's contribution to a page. The HOST
|
||
// owns the document envelope: it composes doctype/<html>/<head>/<body> with
|
||
// the cms-authored bn chrome, built from the same doc map it sent the guest
|
||
// as doc_json (see cms docs/superpowers/specs/2026-07-07-host-chrome-injection-design.md).
|
||
message TemplateDocument {
|
||
bytes body_html = 1; // inner <body> content
|
||
string body_class = 2; // <body class="...">
|
||
string lang = 3; // <html lang>; empty means host default "en"
|
||
bytes head_extra_html = 4; // appended inside <head>, after bn.Head
|
||
bytes body_end_extra_html = 5; // appended before </body>, after bn.BodyEnd
|
||
// Extra <html>-element attributes (e.g. data-theme). A "lang" key here is
|
||
// ignored — the lang field wins.
|
||
map<string, string> html_attrs = 6;
|
||
// Requested HTTP response status. The host applies the same validation and
|
||
// precedence rules as PoweredBlock.status_code.
|
||
int32 status_code = 7;
|
||
// Structured document metadata merged by the host into the page head.
|
||
DocumentMetadata metadata = 8;
|
||
}
|
||
|
||
// 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<string, string> 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<string, string> headers = 6;
|
||
map<string, string> 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"
|
||
// Slug derived from the resolved page hierarchy.
|
||
string derived_slug = 6;
|
||
// Effective mount path after host inheritance and normalization.
|
||
string mount_path = 7;
|
||
}
|
||
|
||
// 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<string, string> query = 16;
|
||
string referrer = 17;
|
||
string user_agent = 18;
|
||
string ip = 19;
|
||
map<string, string> cookies = 20;
|
||
map<string, string> 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;
|
||
// Slug derived from the resolved page hierarchy.
|
||
string derived_slug = 39;
|
||
// Effective mount path after host inheritance and normalization.
|
||
string mount_path = 40;
|
||
}
|