104 lines
3.8 KiB
TypeScript
104 lines
3.8 KiB
TypeScript
/**
|
|
* rGov applet definitions — Signoff Gate + Governance Circuit.
|
|
*/
|
|
|
|
import type { AppletDefinition, AppletLiveData, AppletSubNode, AppletSubEdge } from "../../shared/applet-types";
|
|
import type { PortDescriptor } from "../../lib/data-types";
|
|
|
|
const signoffGate: AppletDefinition = {
|
|
id: "signoff-gate",
|
|
label: "Signoff Gate",
|
|
icon: "⚖️",
|
|
accentColor: "#7c3aed",
|
|
ports: [
|
|
{ name: "decision-in", type: "json", direction: "input" },
|
|
{ name: "gate-out", type: "json", direction: "output" },
|
|
],
|
|
renderCompact(data: AppletLiveData): string {
|
|
const { snapshot } = data;
|
|
const title = (snapshot.title as string) || "Signoff Required";
|
|
const satisfied = !!snapshot.satisfied;
|
|
const assignee = (snapshot.assignee as string) || "anyone";
|
|
const statusColor = satisfied ? "#22c55e" : "#f59e0b";
|
|
const statusText = satisfied ? "SATISFIED" : "WAITING";
|
|
const checkIcon = satisfied ? "✓" : "○";
|
|
|
|
return `
|
|
<div style="text-align:center">
|
|
<div style="font-size:13px;font-weight:600;margin-bottom:6px">${title}</div>
|
|
<div style="font-size:11px;color:#94a3b8;margin-bottom:8px">Assignee: ${assignee}</div>
|
|
<div style="font-size:28px;margin-bottom:4px">${checkIcon}</div>
|
|
<div style="font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:0.5px;color:${statusColor}">
|
|
${statusText}
|
|
</div>
|
|
</div>
|
|
`;
|
|
},
|
|
onInputReceived(portName, value, ctx) {
|
|
if (portName === "decision-in" && value && typeof value === "object") {
|
|
const decision = value as Record<string, unknown>;
|
|
ctx.emitOutput("gate-out", {
|
|
satisfied: !!decision.approved,
|
|
source: "decision-in",
|
|
timestamp: Date.now(),
|
|
});
|
|
}
|
|
},
|
|
};
|
|
|
|
const governanceCircuit: AppletDefinition = {
|
|
id: "governance-circuit",
|
|
label: "Governance Circuit",
|
|
icon: "🔀",
|
|
accentColor: "#6366f1",
|
|
ports: [
|
|
{ name: "proposal-in", type: "json", direction: "input" },
|
|
{ name: "decision-out", type: "json", direction: "output" },
|
|
],
|
|
renderCompact(data: AppletLiveData): string {
|
|
const { snapshot } = data;
|
|
const gateCount = (snapshot.gateCount as number) || 0;
|
|
const satisfiedCount = (snapshot.satisfiedCount as number) || 0;
|
|
const name = (snapshot.circuitName as string) || "Circuit";
|
|
|
|
return `
|
|
<div style="text-align:center">
|
|
<div style="font-size:13px;font-weight:600;margin-bottom:8px">${name}</div>
|
|
<div style="font-size:24px;font-weight:700;color:#e2e8f0">${satisfiedCount}/${gateCount}</div>
|
|
<div style="font-size:10px;color:#94a3b8;margin-top:2px">gates satisfied</div>
|
|
<div style="margin-top:8px;background:#334155;border-radius:3px;height:6px;overflow:hidden">
|
|
<div style="background:#6366f1;width:${gateCount > 0 ? Math.round((satisfiedCount / gateCount) * 100) : 0}%;height:100%;border-radius:3px;transition:width 0.3s"></div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
},
|
|
getCircuit(space: string): { nodes: AppletSubNode[]; edges: AppletSubEdge[] } {
|
|
// Demo circuit — in production this would read from the space's governance doc
|
|
return {
|
|
nodes: [
|
|
{
|
|
id: "gate-1", type: "signoff", label: "Community Approval",
|
|
icon: "✓", position: { x: 50, y: 40 },
|
|
config: { assignee: "Community", satisfied: false },
|
|
},
|
|
{
|
|
id: "gate-2", type: "threshold", label: "Budget Threshold",
|
|
icon: "📊", position: { x: 50, y: 160 },
|
|
config: { target: 1000, current: 650, unit: "$" },
|
|
},
|
|
{
|
|
id: "project", type: "project", label: "Project Decision",
|
|
icon: "🎯", position: { x: 350, y: 100 },
|
|
config: { gatesSatisfied: 0, gatesTotal: 2 },
|
|
},
|
|
],
|
|
edges: [
|
|
{ id: "e1", fromNode: "gate-1", fromPort: "out", toNode: "project", toPort: "in" },
|
|
{ id: "e2", fromNode: "gate-2", fromPort: "out", toNode: "project", toPort: "in" },
|
|
],
|
|
};
|
|
},
|
|
};
|
|
|
|
export const govApplets: AppletDefinition[] = [signoffGate, governanceCircuit];
|