//! 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::{Anchor, Candidate, Node, Screen}; /// Turns a description into the bytes a browser gets. /// /// An HTTP host's contract, and not the thing the three renderers share. 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) } /// One field's suggestion list, for the field that owns it. /// /// [`Outcome::Suggestions`](quasi_router::Outcome::Suggestions). The field /// is named by [`Field::name`](quasi_router::Field::name) and never by an /// id: the list belongs to the control, so a renderer that needs a document /// id derives one from the name and says so in /// [`suggestions_target`](Self::suggestions_target). /// /// # Why this has no default /// /// A default returning nothing is the silent no-op /// [`Outcome`](quasi_router::Outcome)'s exhaustiveness exists to prevent, /// one level in: the route answered with candidates, the user typed to get /// them, and a renderer that quietly drew none would look like a route with /// nothing to suggest. [`overlay`](Self::overlay) can default because /// drawing the screen unlayered is a worse *rendering* of the same answer; /// there is no such fallback here. fn suggestions(&self, field: &str, options: &[Candidate]) -> String; /// The id of the element a field's suggestion list is drawn into. /// /// [`overlay_target`](Self::overlay_target)'s counterpart, and `None` means /// the same thing: this renderer has no container to aim at, so the answer /// is sent without a retarget and the client puts it wherever it was aimed. /// /// Derived from the field's name rather than authored, which is what makes /// a suggestion list a thing a field owns rather than two elements a /// description has to keep pointing at each other. fn suggestions_target(&self, field: &str) -> Option { let _ = field; None } /// The inside of a popover container, for a screen drawn at a point. /// /// [`Outcome::Anchored`](quasi_router::Outcome::Anchored). No document and /// no shell, for [`overlay`](Self::overlay)'s reason: what is under it has /// both. /// /// The anchor is not a parameter because it is already spent. It named the /// container this lands in, which is what /// [`anchored_target`](Self::anchored_target) answered, and asking a /// renderer to write the same fact into the markup as well would be one /// thing decided twice. /// /// # Why it defaults to the overlay /// /// A renderer with no popover container cannot draw at a point, and an /// unanchored overlay is the honest fallback the same way an unlayered /// screen is [`overlay`](Self::overlay)'s: the user reaches the menu, /// without it floating where they pressed. Drawing nothing would be the /// silent no-op [`Outcome`](quasi_router::Outcome)'s exhaustiveness exists /// to prevent. fn anchored(&self, screen: &Screen) -> String { self.overlay(screen) } /// The id of the element an anchored screen is drawn into. /// /// [`overlay_target`](Self::overlay_target)'s counterpart, taking the /// anchor because that is the whole difference: an overlay lands in the one /// container an app has, and this lands in the container belonging to the /// thing that was anchored to. /// /// `None` means what it means on the other two: this renderer has no such /// container, so the answer is sent without a retarget and lands where it /// was aimed. That is also the honest answer for an anchor naming something /// this document does not have — a region that is not on the screen, a /// control carrying no [`Act::id`](quasi_router::Act::id) — which is a /// description bug and is treated the way every other one is. fn anchored_target(&self, anchor: &Anchor) -> Option { let _ = anchor; None } /// 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" } }