+ }
value={
<>
{attentionPercent}% / {attentionNeededPercent}%
@@ -197,10 +209,16 @@ const ProposalPP: React.FC = () => {
valueClassName="text-2xl font-semibold"
/>
+ }
value={
<>
- {upvoteCurrentPercent}% / {upvoteFloorPercent}%
+ {upvoteFloorProgressPercent}% / {upvoteFloorFractionPercent}%
>
}
variant="panel"
diff --git a/src/pages/proposals/Proposals.tsx b/src/pages/proposals/Proposals.tsx
index 7c15140..efb681a 100644
--- a/src/pages/proposals/Proposals.tsx
+++ b/src/pages/proposals/Proposals.tsx
@@ -14,6 +14,7 @@ import type { ProposalStage } from "@/types/stages";
import { CardActionsRow } from "@/components/CardActionsRow";
import { Surface } from "@/components/Surface";
import { NoDataYetBar } from "@/components/NoDataYetBar";
+import { HintLabel } from "@/components/Hint";
import { getFormationProgress } from "@/lib/dtoParsers";
import {
apiProposalChamberPage,
@@ -310,19 +311,30 @@ const Proposals: React.FC = () => {
const attentionNeededPercent = Math.round(
poolPage.attentionQuorum * 100,
);
- const upvoteFloorPercent = Math.round(
- (poolPage.upvoteFloor / activeGovernors) * 100,
+ const upvoteFloorFractionPercent = Math.round(
+ ((poolPage.thresholdContext?.quorumThreshold
+ ?.upvoteFloorFraction ?? 0.1) *
+ 1000) /
+ 10,
);
- const upvoteCurrentPercent = Math.round(
- (poolPage.upvotes / activeGovernors) * 100,
+ const upvoteFloorProgressPercent = Math.round(
+ Math.min(
+ 1,
+ poolPage.upvoteFloor > 0
+ ? poolPage.upvotes / poolPage.upvoteFloor
+ : 0,
+ ) * upvoteFloorFractionPercent,
);
- const meetsAttention =
- engaged / activeGovernors >= poolPage.attentionQuorum;
+ const engagedNeeded = Math.min(
+ activeGovernors,
+ Math.max(
+ 1,
+ Math.ceil(poolPage.attentionQuorum * activeGovernors),
+ ),
+ );
+ const meetsAttention = engaged >= engagedNeeded;
const meetsUpvoteFloor =
poolPage.upvotes >= poolPage.upvoteFloor;
- const engagedNeeded = Math.ceil(
- poolPage.attentionQuorum * activeGovernors,
- );
return {
activeGovernors,
@@ -334,8 +346,8 @@ const Proposals: React.FC = () => {
engaged,
attentionPercent,
attentionNeededPercent,
- upvoteFloorPercent,
- upvoteCurrentPercent,
+ upvoteFloorFractionPercent,
+ upvoteFloorProgressPercent,
meetsAttention,
meetsUpvoteFloor,
engagedNeeded,
@@ -477,15 +489,25 @@ const Proposals: React.FC = () => {
+ }
description={`Engaged ${poolStats.engaged} / ${poolStats.engagedNeeded} governors`}
value={`${poolStats.attentionPercent}% / ${poolStats.attentionNeededPercent}%`}
tone={poolStats.meetsAttention ? "ok" : "warn"}
/>
+ }
description={`Upvotes ${poolPage.upvotes} / ${poolStats.upvoteFloor} governors`}
- value={`${poolStats.upvoteCurrentPercent}% / ${poolStats.upvoteFloorPercent}%`}
+ value={`${poolStats.upvoteFloorProgressPercent}% / ${poolStats.upvoteFloorFractionPercent}%`}
tone={poolStats.meetsUpvoteFloor ? "ok" : "warn"}
/>
diff --git a/src/types/api.ts b/src/types/api.ts
index b7d4f86..378e67f 100644
--- a/src/types/api.ts
+++ b/src/types/api.ts
@@ -98,6 +98,13 @@ export type FactionDto = {
replies: number;
createdAt: string;
updatedAt: string;
+ messages?: Array<{
+ id: string;
+ authorAddress: string;
+ body: string;
+ createdAt: string;
+ updatedAt: string;
+ }>;
}>;
initiativesDetailed?: Array<{
id: string;
@@ -501,6 +508,22 @@ export type PoolProposalPageDto = {
executionPlan: string[];
budgetScope: string;
invisionInsight: InvisionInsightDto;
+ thresholdContext?: {
+ activityThreshold: {
+ categories: string[];
+ qualificationEra: number | null;
+ activationEra: number | null;
+ };
+ quorumThreshold: {
+ stage: "pool";
+ denominator: number;
+ source: "snapshot" | "fallback";
+ snapshotEra: number | null;
+ snapshotCapturedAt: string | null;
+ attentionQuorumFraction: number;
+ upvoteFloorFraction: number;
+ };
+ };
};
export type ChamberProposalPageDto = {
@@ -515,6 +538,7 @@ export type ChamberProposalPageDto = {
timeLeft: string;
votes: { yes: number; no: number; abstain: number };
attentionQuorum: number;
+ quorumNeeded: number;
passingRule: string;
engagedGovernors: number;
activeGovernors: number;
@@ -527,6 +551,22 @@ export type ChamberProposalPageDto = {
executionPlan: string[];
budgetScope: string;
invisionInsight: InvisionInsightDto;
+ thresholdContext?: {
+ activityThreshold: {
+ categories: string[];
+ qualificationEra: number | null;
+ activationEra: number | null;
+ };
+ quorumThreshold: {
+ stage: "vote";
+ denominator: number;
+ source: "snapshot" | "fallback";
+ snapshotEra: number | null;
+ snapshotCapturedAt: string | null;
+ quorumFraction: number;
+ passingFraction: number;
+ };
+ };
};
export type FormationProposalPageDto = {