diff --git a/src/components/StageChip.tsx b/src/components/StageChip.tsx index 641b007..94889f9 100644 --- a/src/components/StageChip.tsx +++ b/src/components/StageChip.tsx @@ -17,6 +17,7 @@ const chipClasses: Record = { thread: "bg-panel-alt text-muted", courts: "bg-[color:var(--accent-warm)]/15 text-[var(--accent-warm)]", faction: "bg-panel-alt text-muted", + system: "bg-[color:var(--danger)]/12 text-[color:var(--danger)]", }; const hintByKind: Partial> = { diff --git a/src/pages/courts/Courts.tsx b/src/pages/courts/Courts.tsx index 4247bc4..ac65d48 100644 --- a/src/pages/courts/Courts.tsx +++ b/src/pages/courts/Courts.tsx @@ -184,10 +184,16 @@ const Courts: React.FC = () => { )} +
-

- coming sooner than you think... -

+ +

+ Courts +

+

+ coming sooner than you think... +

+
); diff --git a/src/pages/feed/Feed.tsx b/src/pages/feed/Feed.tsx index a490756..dc8991d 100644 --- a/src/pages/feed/Feed.tsx +++ b/src/pages/feed/Feed.tsx @@ -152,7 +152,7 @@ async function loadUrgentFeedItems(input: { limit: number; isGovernorActive: boolean; }): Promise { - const [base, pool, vote, build, invites] = await Promise.all([ + const [base, pool, vote, build, invites, system] = await Promise.all([ apiFeed({ chambers: input.chambers, limit: input.limit }), apiFeed({ stage: "pool", @@ -178,6 +178,13 @@ async function loadUrgentFeedItems(input: { limit: FEED_MIN_PAGE_SIZE, }) : Promise.resolve({ items: [] as FeedItemDto[] }), + input.address + ? apiFeed({ + actor: input.address, + stage: "system", + limit: URGENT_STAGE_LIMIT, + }) + : Promise.resolve({ items: [] as FeedItemDto[] }), ]); return toUrgentItems( @@ -187,6 +194,7 @@ async function loadUrgentFeedItems(input: { ...vote.items, ...build.items, ...invites.items, + ...system.items, ], input.isGovernorActive, input.address, diff --git a/src/pages/invision/Invision.tsx b/src/pages/invision/Invision.tsx index 8c921bf..e7abd02 100644 --- a/src/pages/invision/Invision.tsx +++ b/src/pages/invision/Invision.tsx @@ -80,12 +80,18 @@ const Invision: React.FC = () => { const secondaryGovernanceMetrics = ( invision?.governanceState.metrics ?? [] ).slice(3); + const stability = invision?.stability ?? null; + const stabilityToneClass = (tone: "positive" | "watch" | "critical") => { + if (tone === "critical") return "text-destructive"; + if (tone === "watch") return "text-primary"; + return "text-text"; + }; return (
- {invision === null ? ( + {invision === null && !loadError ? ( Loading Invision… @@ -145,6 +151,64 @@ const Invision: React.FC = () => { ) : null} + {stability ? ( + + + Stability engine + + +
+
+ Band +

+ {stability.band} +

+
+
+ Confidence +

+ {stability.confidence}% · {stability.confidenceBand} +

+
+
+ Window +

+ {stability.windowLabel} +

+
+
+ +
+ {stability.components.map((component) => ( +
+ {component.label} +

+ {component.score}% +

+

{component.detail}

+
+ ))} +
+ + {stability.capsApplied.length > 0 ? ( +
+ Active caps +
+ {stability.capsApplied.map((cap) => ( +

{cap}

+ ))} +
+
+ ) : null} +
+
+ ) : null} + setSearch(e.target.value)} diff --git a/src/pages/proposals/proposalCreation/presets/registry.ts b/src/pages/proposals/proposalCreation/presets/registry.ts index a9fb876..dcee8ec 100644 --- a/src/pages/proposals/proposalCreation/presets/registry.ts +++ b/src/pages/proposals/proposalCreation/presets/registry.ts @@ -656,7 +656,7 @@ export const PROPOSAL_PRESETS: ProposalPreset[] = [ }, { id: "project.dao_core.governing-threshold.policy", - label: "Governing threshhold", + label: "Governing threshold", description: "Governing thresholds and quorum constraints.", templateId: "project", proposalType: "dao-core", diff --git a/src/types/api.ts b/src/types/api.ts index 741c9b6..f007087 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -271,6 +271,21 @@ export type InvisionGovernanceStateDto = { label: string; metrics: InvisionGovernanceMetricDto[]; }; +export type InvisionStabilityComponentDto = { + label: string; + score: number; + detail: string; + tone: "positive" | "watch" | "critical"; +}; +export type InvisionStabilityDto = { + score: number; + band: "Stable" | "Watch" | "Unstable"; + confidence: number; + confidenceBand: "Low" | "Medium" | "High"; + windowLabel: string; + capsApplied: string[]; + components: InvisionStabilityComponentDto[]; +}; export type InvisionEconomicIndicatorDto = { label: string; value: string; @@ -288,6 +303,7 @@ export type InvisionChamberProposalDto = { }; export type GetInvisionResponse = { governanceState: InvisionGovernanceStateDto; + stability: InvisionStabilityDto; economicIndicators: InvisionEconomicIndicatorDto[]; riskSignals: InvisionRiskSignalDto[]; chamberProposals: InvisionChamberProposalDto[]; diff --git a/src/types/stages.ts b/src/types/stages.ts index a6bb435..c1dcff3 100644 --- a/src/types/stages.ts +++ b/src/types/stages.ts @@ -15,6 +15,7 @@ export const feedStages = [ "thread", "courts", "faction", + "system", ] as const; export type FeedStage = (typeof feedStages)[number]; @@ -28,7 +29,8 @@ export type StageChipKind = | "passed" | "thread" | "courts" - | "faction"; + | "faction" + | "system"; export const stageToChipKind = { pool: "proposal_pool", @@ -39,6 +41,7 @@ export const stageToChipKind = { thread: "thread", courts: "courts", faction: "faction", + system: "system", } as const satisfies Record; export const stageLabel = { @@ -50,6 +53,7 @@ export const stageLabel = { thread: "Thread", courts: "Courts", faction: "Faction", + system: "System", } as const satisfies Record; export const proposalStageToChipKind: Record =