//! The seam between a description and bytes. //! //! No host adapter emits markup, and that is the important decision in the set //! of them. //! //! A hosted axum route and a Tauri custom-protocol handler both serve HTML to a //! webview. If the HTML were generated in either, the other would need a second //! copy of it, and two copies of a markup emitter is the divergence the whole //! stack exists to end. So markup belongs to the webview renderer, which is //! `makeover-webview`, and every adapter takes it as a parameter. //! //! Today `makeover-webview` emits a stylesheet, form fields and table cells, //! which is phase A. Whole-screen markup is phase B and is not written, so an //! app supplies its own [`Serves`] in the meantime. When phase B lands, the //! implementation moves there and every host adapter picks it up at once //! instead of one at a time. use quasi_router::{Node, Screen}; /// Turns a description into the bytes a browser gets. /// /// An HTTP host's contract, and not the thing the three renderers share. It was /// called `Render` until 2026-08-12, which cost the first hour of writing the /// second renderer: `quasi-tui` implements none of this and cannot. A terminal /// answers cells in a buffer rather than a `String`, and a content type is an /// HTTP header. What a webview, a terminal and an egui frame have in common is /// the description itself, which is a type they already take; a trait over that /// would have one method taking `&Screen` and returning `()`, which buys /// nothing. Two renderers sharing no trait is a fact about renderers rather /// than a hole. /// /// Two methods, because [`Response`](quasi_router::Response) has two members /// and a renderer genuinely does different work for each: a screen is a /// document and a fragment is the inside of one element. /// /// Errors are not a third method. An adapter turns a /// [`RouteError`](quasi_router::RouteError) into a [`Node::Notice`] and asks /// for a fragment, so failure becomes UI through the same path as everything /// else. That is decision 9 holding at the boundary rather than only in the /// router. pub trait Serves: Send + Sync + 'static { /// A whole document. fn screen(&self, screen: &Screen) -> String; /// The inside of one region. fn fragment(&self, node: &Node) -> String; /// One region's new contents, carried beside the region being replaced. /// /// [`Response::invalidates`](quasi_router::Response::invalidates): the row /// the write was aimed at is the [`fragment`](Self::fragment), and the /// count in the header is one of these. Both travel on one answer, so the /// two never disagree and no second request is made for a fact the router /// already had. /// /// Unlike the other two this carries the region's id, because an /// out-of-band update is applied by name rather than by where it was /// aimed. A fragment does not need one: the router said where it goes in /// [`RETARGET`](crate::htmx::RETARGET). /// /// # Why it defaults to nothing /// /// A renderer answering something other than markup has no out-of-band /// channel to put this in, the same reason [`content_type`](Self::content_type) /// is overridable. Returning nothing is that renderer saying so, and it is /// the honest default: a JSON client is told what changed by the payload it /// already parses. fn invalidated(&self, region: &str, node: &Node) -> String { let _ = (region, node); String::new() } /// The inside of the overlay container, for a screen drawn over the page. /// /// [`Outcome::Over`](quasi_router::Outcome::Over). No document and no /// shell: what is under it already has both, and replacing them is exactly /// what an overlay is not. /// /// # Why it defaults to the whole screen /// /// A renderer with no overlay container cannot layer anything, and the /// honest fallback is to draw the screen: the user reaches the palette, /// without it floating. Returning nothing would be the silent no-op /// `Outcome`'s exhaustiveness exists to prevent, and this is the same /// argument one level in. fn overlay(&self, screen: &Screen) -> String { self.screen(screen) } /// The id of the element an overlay is drawn into. /// /// `None` says this renderer has no overlay container, which is what makes /// [`overlay`](Self::overlay)'s default a whole-screen swap rather than /// markup aimed at an element that is not there. fn overlay_target(&self) -> Option<&str> { None } /// What the document says it is. /// /// Overridable for a renderer answering something other than HTML, which is /// the seam a JSON client or a test harness needs. Defaults to HTML because /// that is what every webview host wants. fn content_type(&self) -> &'static str { "text/html; charset=utf-8" } }