Approvals — how it works
Approvals are node-native: two NodeTypes the mesh compiles live from Source/, plus a node
that contributes the menu entry. Nothing here is a compiled module, so installing or removing the
package changes the mesh immediately — no image, no restart.
The three moving parts
| Node | What it is |
|---|---|
Approvals/Approval |
The satellite TYPE. One approval = one {document}/_Approval/{id} node: who asked whom, what for, by when, and the decision. Its Overview shows the record (with Approve / Reject for the named approver of a pending request); its Thumbnail is the card the inline list renders. |
Approvals/Desk + Approvals/Workspace |
The document-anchored surfaces — RequestApproval (the form) and Approvals (the live list). The ONE Approvals/Workspace instance serves every document; the target arrives per render. |
Approvals/RequestApprovalMenu |
A UiContribution node: the Request Approval entry on every node's menu, contributed as DATA. |
Why one desk instead of every hub
The retired module registered its form, its list and its menu entry onto every per-node hub of
the mesh (ConfigureDefaultNodeHub) — something no in-mesh type can do, and something that put
a mesh-wide feature in the platform's build graph.
The desk needs none of it. Each area resolves its document per render:
- an explicit
?doc=Space/Path, - else the layout-area reference id an embedding page passes —
Controls.LayoutArea("Approvals/Workspace", "Approvals", documentPath), - else the desk node's own
documentPath.
So a document page embeds the approvals list, and the menu entry links to the form with the
current node in ?doc=. Precedence and normalization are pinned by ApprovalDeskTests.
Embedding the list on another type
Controls.LayoutArea(ApprovalDocs.DeskPath, ApprovalDeskAreas.ApprovalsArea, documentPath)
.WithShowProgress(false)
Probe first if the package may be absent — a query-index existence check on Approvals/Workspace,
never a hub probe of a missing node (which costs the full activation timeout). The platform's
markdown overview does exactly this.
What stays platform-level
The Approval record and the _Approval → annotations satellite mapping live in the
platform (MeshWeaver.Mesh.Contract). Storage placement is resolved from the path SEGMENT, not
from the node type, so approvals land in the right table and keep deserializing on a mesh where
this package is not installed. Access is the platform's ordinary path-based check: an approval sits
inside its document's partition and is readable exactly where the document is.
Invariants worth knowing
A decision is terminal, and only the named approver makes it.
Decidere-resolves the approval inside the owner's update lambda and refuses a second decision, so a stale tab cannot overwrite one that already happened.The activity entry is
{document}/_Activity/{id}typedActivity. That exact shape is what the running-activities stripe queries and what the Activity views bind to.Instants render in the viewer's zone; a due DATE never does. The 15th is the 15th in every zone — converting a due date shifts the deadline by a day for half the world.
The inline list is the platform's search control, and the desk decides only whether it exists.
ApprovalDeskAreas.ApprovalCardsis aMeshSearchControlover the anchoredApprovalsQuery(projected,contentnamed because the list orders by the approval's owncreatedAt, newest first), one approval per row through itsThumbnail, live, with no search box and no click-through. What the control cannot say is "no heading over an empty list", and every document page mounts this area — so a path-only probe of the same query decides whether the section (heading and list) renders at all. The list carries noWithNamespace: an anchored search hides results under an underscore segment, and every approval lives under_Approval.The record page and the card are templates.
ApprovalLayoutAreas.BuildOverviewTemplateandBuildThumbnailTemplatereturn the whole tree at once; every value binds to the approval's projection (ApprovalLayoutAreas.Project, published under/data/approvalView), which also carries which parts show — the decision buttons only for the named approver of a pending request. The header renders in its own nested slot with a skeleton. So a document page's approvals section shows its cards immediately instead of a placeholder per approval until each approval's hub has answered (core Doc/GUI/DataBinding → "Templates first, data later").A document the render names is resolved without reading the desk.
?doc=and the reference id are known on the render turn, soApprovalDeskAreas.DocumentStreamanswers at once and only falls back to the desk node'sdocumentPathwhen neither names one.
Run the Tests area on either type to execute the suites (CI asserts them green).