diff --git a/src/components/ProposalSections.tsx b/src/components/ProposalSections.tsx
index bde42d7..a8def44 100644
--- a/src/components/ProposalSections.tsx
+++ b/src/components/ProposalSections.tsx
@@ -9,6 +9,7 @@ import { StatTile } from "@/components/StatTile";
import { Surface } from "@/components/Surface";
import { TitledSurface } from "@/components/TitledSurface";
import { formatDateTime } from "@/lib/dateTime";
+import { Link } from "react-router";
export type ProposalSummaryStat = {
label: string;
@@ -41,6 +42,13 @@ export type ProposalTimelineItem = {
title: string;
detail?: string;
actor?: string;
+ snapshot?: {
+ fromStage: "pool" | "vote" | "build";
+ toStage: "vote" | "build" | "passed" | "failed";
+ reason?: string;
+ milestoneIndex?: number | null;
+ metrics: Array<{ label: string; value: string }>;
+ };
};
type ProposalSummaryCardProps = {
@@ -228,13 +236,29 @@ export function ProposalInvisionInsightCard({
type ProposalTimelineCardProps = {
items: ProposalTimelineItem[];
+ proposalId?: string;
};
function isLikelyAddress(value: string): boolean {
return /^[a-z0-9]{6,}$/i.test(value) && value.length >= 20;
}
-export function ProposalTimelineCard({ items }: ProposalTimelineCardProps) {
+function snapshotStageHref(
+ proposalId: string,
+ stage: "pool" | "vote" | "build",
+): string | null {
+ if (stage === "pool")
+ return `/app/proposals/${proposalId}/pp?snapshotStage=pool`;
+ if (stage === "vote")
+ return `/app/proposals/${proposalId}/chamber?snapshotStage=vote`;
+ // `build` stage can become unavailable after terminal transition.
+ return null;
+}
+
+export function ProposalTimelineCard({
+ items,
+ proposalId,
+}: ProposalTimelineCardProps) {
return (
Timeline
@@ -271,6 +295,49 @@ export function ProposalTimelineCard({ items }: ProposalTimelineCardProps) {
)}
+ Stage transition: {item.snapshot.fromStage} →{" "} + {item.snapshot.toStage} +
+ {item.snapshot.reason ? ( +{item.snapshot.reason}
+ ) : null} + {item.snapshot.metrics.length > 0 ? ( +- A short, human-readable map of what you’re seeing in this demo, why - Vortex exists, and how each page works. + A short, human-readable map of what you’re seeing in the simulator, + why Vortex exists, and how each page works.
- This repo is a demo mockup of that experience. - Numbers, identities, and statuses are illustrative. + This repo is a living simulator UI of that + experience. Data and governance state come from the current + simulation backend.
@@ -401,8 +402,8 @@ const Guide: React.FC = () => { > Proposal Creation {" "} - is a multi-step wizard in this demo. Steps are navigable so you - can explore the structure even if you don’t fill everything in. + is a multi-step wizard. Steps are navigable so you can explore + structure and submit complete proposals.Drafts:{" "} @@ -521,8 +522,7 @@ const Guide: React.FC = () => { >
Invision is where governance becomes measurable: it summarizes - behavior and delivery patterns into scannable insights (still a - mock in this repo). + behavior and delivery patterns into scannable insights.
In the app you’ll see “Invision insight” snippets attached to
@@ -534,7 +534,7 @@ const Guide: React.FC = () => {
Courts is the judicial layer. Cases are structured around a
@@ -546,8 +546,8 @@ const Guide: React.FC = () => {
Statuses (jury / live / ended) reflect the state of the case.
The CM Panel is a specialized admin-style surface. In a real
@@ -566,7 +566,7 @@ const Guide: React.FC = () => {
Profile is your personal view. Settings is where future
@@ -627,8 +627,8 @@ const Guide: React.FC = () => {
{threadError}
++ {formatLoadError(threadError)} +
) : null}{loadError}
++ {formatLoadError(loadError)} +
) : null}Who (auto-filled)
-+ Proposer (auto-filled) +
+