From 210ed7a3528b1c33f2a441919b7255d4800a55da Mon Sep 17 00:00:00 2001 From: Cyber Preacher <72062250+Cyber-preacher@users.noreply.github.com> Date: Mon, 29 Jun 2026 17:23:46 +0400 Subject: [PATCH] Build Initiative workspaces --- src/app/AppRoutes.tsx | 6 + src/app/AppSidebar.tsx | 2 + src/components/GlassyRecordCard.css | 13 + src/components/GlassyRecordCard.tsx | 7 + src/components/ProposalPageHeader.tsx | 11 + src/components/StageChip.css | 24 ++ src/components/StageChip.tsx | 1 + src/components/Textarea.tsx | 18 ++ src/data/vortexopedia.ts | 35 +++ src/lib/api/initiatives.ts | 240 +++++++++++++++++ src/lib/apiClient.ts | 2 + src/lib/initiativeUi.ts | 111 ++++++++ src/pages/feed/components/FeedListSection.tsx | 5 + src/pages/initiatives/Initiative.tsx | 203 ++++++++++++++ src/pages/initiatives/InitiativeCreate.tsx | 155 +++++++++++ src/pages/initiatives/Initiatives.tsx | 193 +++++++++++++ .../components/InitiativeBoardSection.tsx | 216 +++++++++++++++ .../initiatives/components/InitiativeCard.tsx | 83 ++++++ .../components/InitiativeChatSection.tsx | 90 +++++++ .../components/InitiativeMembersSection.tsx | 124 +++++++++ .../components/InitiativeProposalsSection.tsx | 42 +++ .../components/InitiativeSettingsSection.tsx | 123 +++++++++ .../components/InitiativeThreadsSection.tsx | 255 ++++++++++++++++++ src/pages/proposals/ProposalCreation.tsx | 37 ++- src/pages/proposals/list/ProposalListCard.tsx | 5 + .../ProposalCreationStepCard.tsx | 6 + .../proposalCreation/steps/EssentialsStep.tsx | 27 ++ .../proposalCreation/steps/ReviewStep.tsx | 7 + .../proposals/proposalCreation/toApiForm.ts | 1 + src/pages/proposals/proposalCreation/types.ts | 2 + .../useProposalCreationReferenceData.ts | 43 ++- src/types/api.ts | 99 +++++++ src/types/stages.ts | 4 + tests/api/api-client-runtime.test.js | 33 +++ tests/api/cross-repo-contract.test.js | 1 + tests/unit/initiative-ui.test.ts | 72 +++++ tests/unit/phase89-visual-contract.test.ts | 2 +- 37 files changed, 2282 insertions(+), 16 deletions(-) create mode 100644 src/components/Textarea.tsx create mode 100644 src/lib/api/initiatives.ts create mode 100644 src/lib/initiativeUi.ts create mode 100644 src/pages/initiatives/Initiative.tsx create mode 100644 src/pages/initiatives/InitiativeCreate.tsx create mode 100644 src/pages/initiatives/Initiatives.tsx create mode 100644 src/pages/initiatives/components/InitiativeBoardSection.tsx create mode 100644 src/pages/initiatives/components/InitiativeCard.tsx create mode 100644 src/pages/initiatives/components/InitiativeChatSection.tsx create mode 100644 src/pages/initiatives/components/InitiativeMembersSection.tsx create mode 100644 src/pages/initiatives/components/InitiativeProposalsSection.tsx create mode 100644 src/pages/initiatives/components/InitiativeSettingsSection.tsx create mode 100644 src/pages/initiatives/components/InitiativeThreadsSection.tsx create mode 100644 tests/unit/initiative-ui.test.ts diff --git a/src/app/AppRoutes.tsx b/src/app/AppRoutes.tsx index 668f4bb..c35d0cb 100644 --- a/src/app/AppRoutes.tsx +++ b/src/app/AppRoutes.tsx @@ -36,6 +36,9 @@ import Landing from "../pages/Landing"; import Paper from "../pages/Paper"; import Guide from "../pages/Guide"; import Settings from "../pages/settings/Settings"; +import Initiatives from "../pages/initiatives/Initiatives"; +import Initiative from "../pages/initiatives/Initiative"; +import InitiativeCreate from "../pages/initiatives/InitiativeCreate"; const AppRoutes: React.FC = () => { return ( @@ -110,6 +113,9 @@ const AppRoutes: React.FC = () => { } /> } /> } /> + } /> + } /> + } /> } /> } /> } /> diff --git a/src/app/AppSidebar.tsx b/src/app/AppSidebar.tsx index 3984e6e..e6dc94d 100644 --- a/src/app/AppSidebar.tsx +++ b/src/app/AppSidebar.tsx @@ -10,6 +10,7 @@ import { Gavel, Landmark, Lightbulb, + Network, Rocket, Scale, Settings, @@ -49,6 +50,7 @@ const AppSidebar: React.FC = ({ children }) => { { to: "/app/my-governance", label: "My governance", Icon: Gavel }, { to: "/app/proposals", label: "Proposals", Icon: FileText }, { to: "/app/formation", label: "Formation", Icon: Rocket }, + { to: "/app/initiatives", label: "Initiatives", Icon: Network }, ], }, { diff --git a/src/components/GlassyRecordCard.css b/src/components/GlassyRecordCard.css index 268871e..28ad14f 100644 --- a/src/components/GlassyRecordCard.css +++ b/src/components/GlassyRecordCard.css @@ -163,6 +163,19 @@ overflow-wrap: anywhere; } +.glassy-record-card__association { + align-self: flex-start; + max-width: 100%; + color: var(--record-meta-text); + background: var(--record-meta-bg); +} + +.glassy-record-card__association > span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .glassy-record-card__stage { width: 100%; min-width: 0; diff --git a/src/components/GlassyRecordCard.tsx b/src/components/GlassyRecordCard.tsx index b85f947..d74e658 100644 --- a/src/components/GlassyRecordCard.tsx +++ b/src/components/GlassyRecordCard.tsx @@ -11,6 +11,7 @@ import type { Stage } from "@/types/stages"; import "./GlassyRecordCard.css"; type GlassyRecordCardProps = { + association?: ReactNode; children: ReactNode; className?: string; dateText?: ReactNode; @@ -25,6 +26,7 @@ type GlassyRecordCardProps = { }; export function GlassyRecordCard({ + association, children, className, dateText, @@ -62,6 +64,11 @@ export function GlassyRecordCard({ {title} {renderedSummary} + {association ? ( + + {association} + + ) : null} {meta ? ( diff --git a/src/components/ProposalPageHeader.tsx b/src/components/ProposalPageHeader.tsx index 514f997..3833adc 100644 --- a/src/components/ProposalPageHeader.tsx +++ b/src/components/ProposalPageHeader.tsx @@ -1,5 +1,7 @@ import { useEffect, useMemo, useState, type ReactNode } from "react"; +import { Link } from "react-router"; +import { Chip } from "@/components/Chip"; import { buildProposalStageLinks, ProposalStageBar, @@ -74,6 +76,15 @@ export function ProposalPageHeader({ return (

{title}

+ {status?.initiative ? ( +
+ + + Initiative · {status.initiative.title} + + +
+ ) : null} = { thread: "stage-chip--thread", courts: "stage-chip--courts", faction: "stage-chip--faction", + initiative: "stage-chip--initiative", system: "stage-chip--system", }; diff --git a/src/components/Textarea.tsx b/src/components/Textarea.tsx new file mode 100644 index 0000000..e6ef2cd --- /dev/null +++ b/src/components/Textarea.tsx @@ -0,0 +1,18 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +export interface TextareaProps + extends React.TextareaHTMLAttributes {} + +const base = + "flex min-h-28 w-full rounded-xl border border-[color:var(--surface-glass-border)] bg-[color:var(--control-glass-bg)] px-3 py-2 text-sm text-[var(--text)] shadow-[var(--shadow-control)] transition supports-[backdrop-filter]:backdrop-blur-md " + + "placeholder:text-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--primary-dim)] focus-visible:ring-offset-2 focus-visible:ring-offset-panel focus-visible:shadow-[var(--shadow-tile)] " + + "hover:border-[color:var(--surface-glass-hover-border)] hover:bg-[color:var(--control-glass-hover-bg)] disabled:cursor-not-allowed disabled:opacity-60"; + +export const Textarea = React.forwardRef( + ({ className, ...props }, ref) => { + return