Stop seeding 75% consensus on custom decision cards so wizard copy shows on the published rule.
Wire Back on the final-review chip editor to the same dismiss path as close, instead of a no-op. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -249,6 +249,55 @@ describe("buildPublishPayload — methodSelections", () => {
|
||||
expect(entry.sections.corePrinciple.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("does not seed 75% consensus on a custom decision-making card", () => {
|
||||
const customId = "00000000-0000-4000-8000-000000000170";
|
||||
const r = buildPublishPayload({
|
||||
title: "T",
|
||||
selectedDecisionApproachIds: [customId],
|
||||
customMethodCardMetaById: {
|
||||
[customId]: {
|
||||
label: "Forum proposals",
|
||||
supportText: "Anyone can start a thread.",
|
||||
},
|
||||
},
|
||||
decisionApproachDetailsById: {
|
||||
[customId]: {
|
||||
corePrinciple: "",
|
||||
applicableScope: [],
|
||||
selectedApplicableScope: [],
|
||||
stepByStepInstructions: "",
|
||||
consensusLevel: 75,
|
||||
objectionsDeadlocks: "",
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(r.ok).toBe(true);
|
||||
if (!r.ok) return;
|
||||
const ms = r.document.methodSelections as {
|
||||
decisionApproaches?: Array<{
|
||||
supportText?: string;
|
||||
sections: { consensusLevel?: number };
|
||||
}>;
|
||||
};
|
||||
expect(ms.decisionApproaches?.[0]?.supportText).toBe(
|
||||
"Anyone can start a thread.",
|
||||
);
|
||||
expect(ms.decisionApproaches?.[0]?.sections.consensusLevel).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps catalog decision consensus in methodSelections", () => {
|
||||
const r = buildPublishPayload({
|
||||
title: "T",
|
||||
selectedDecisionApproachIds: ["lazy-consensus"],
|
||||
});
|
||||
expect(r.ok).toBe(true);
|
||||
if (!r.ok) return;
|
||||
const ms = r.document.methodSelections as {
|
||||
decisionApproaches?: Array<{ sections: { consensusLevel?: number } }>;
|
||||
};
|
||||
expect(ms.decisionApproaches?.[0]?.sections.consensusLevel).toBe(100);
|
||||
});
|
||||
|
||||
it("merges override on top of preset for the selected method", () => {
|
||||
const r = buildPublishPayload({
|
||||
title: "T",
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
buildMethodCardWizardInitialValues,
|
||||
coreValueDetailsFromWizardFieldBlocks,
|
||||
facetDetailsToWizardFieldBlocks,
|
||||
overlayFacetPrefillValues,
|
||||
} from "../../lib/create/methodCardWizardPrefill";
|
||||
import type { CustomMethodCardFieldBlock } from "../../lib/create/customMethodCardFieldBlocks";
|
||||
|
||||
@@ -219,6 +220,49 @@ describe("facetDetailsToWizardFieldBlocks", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("omits a consensus proportion when the draft has no consensus level", () => {
|
||||
const blocksOut = facetDetailsToWizardFieldBlocks({
|
||||
group: "decisionApproaches",
|
||||
draft: {
|
||||
corePrinciple: "Momentum.",
|
||||
applicableScope: [],
|
||||
selectedApplicableScope: [],
|
||||
stepByStepInstructions: "Post a deadline.",
|
||||
objectionsDeadlocks: "",
|
||||
},
|
||||
headings: {
|
||||
corePrinciple: "Core Principle",
|
||||
applicableScope: "Applicable Scope",
|
||||
stepByStepInstructions: "Step-by-Step Instructions",
|
||||
consensusLevel: "Consensus Level",
|
||||
objectionsDeadlocks: "Objections & Deadlocks",
|
||||
},
|
||||
});
|
||||
expect(blocksOut.find((b) => b.kind === "proportion")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not inject a seeded 75% proportion onto empty custom drafts", () => {
|
||||
const next = overlayFacetPrefillValues([], {
|
||||
group: "decisionApproaches",
|
||||
draft: {
|
||||
corePrinciple: "",
|
||||
applicableScope: [],
|
||||
selectedApplicableScope: [],
|
||||
stepByStepInstructions: "",
|
||||
consensusLevel: 75,
|
||||
objectionsDeadlocks: "",
|
||||
},
|
||||
headings: {
|
||||
corePrinciple: "Core Principle",
|
||||
applicableScope: "Applicable Scope",
|
||||
stepByStepInstructions: "Step-by-Step Instructions",
|
||||
consensusLevel: "Consensus Level",
|
||||
objectionsDeadlocks: "Objections & Deadlocks",
|
||||
},
|
||||
});
|
||||
expect(next).toEqual([]);
|
||||
});
|
||||
|
||||
it("maps core value meaning and signals onto text blocks", () => {
|
||||
expect(
|
||||
facetDetailsToWizardFieldBlocks({
|
||||
|
||||
@@ -314,6 +314,37 @@ describe("createFlowStateFromPublishedRule", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("hydrates wizard supportText from published methodSelections", () => {
|
||||
const customId = "b7c0a9f3-0000-4000-8000-000000000002";
|
||||
const partial = createFlowStateFromPublishedRule({
|
||||
id: "rule-custom-desc",
|
||||
title: "C",
|
||||
summary: "",
|
||||
document: {
|
||||
methodSelections: {
|
||||
decisionApproaches: [
|
||||
{
|
||||
id: customId,
|
||||
label: "Forum proposals",
|
||||
supportText: "Anyone can start a thread.",
|
||||
sections: {
|
||||
corePrinciple: "",
|
||||
applicableScope: [],
|
||||
selectedApplicableScope: [],
|
||||
stepByStepInstructions: "",
|
||||
objectionsDeadlocks: "",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(partial.customMethodCardMetaById?.[customId]).toEqual({
|
||||
label: "Forum proposals",
|
||||
supportText: "Anyone can start a thread.",
|
||||
});
|
||||
});
|
||||
|
||||
it("sets sections to [] even when methodSelections is missing (edit hydrate)", () => {
|
||||
const partial = createFlowStateFromPublishedRule({
|
||||
id: "rule-2",
|
||||
|
||||
@@ -248,4 +248,35 @@ describe("parsePublishedDocumentForCommunityRuleDisplay", () => {
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("shows custom decision description without a seeded 75% consensus row", () => {
|
||||
const customId = "00000000-0000-4000-8000-000000000170";
|
||||
const out = parsePublishedDocumentForCommunityRuleDisplay({
|
||||
sections: [],
|
||||
methodSelections: {
|
||||
decisionApproaches: [
|
||||
{
|
||||
id: customId,
|
||||
label: "Forum proposals",
|
||||
supportText: "Anyone can start a thread.",
|
||||
sections: {
|
||||
corePrinciple: "",
|
||||
applicableScope: [],
|
||||
selectedApplicableScope: [],
|
||||
stepByStepInstructions: "",
|
||||
consensusLevel: 75,
|
||||
objectionsDeadlocks: "",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const decision = out.find((s) => s.categoryName === "Decision-making");
|
||||
expect(decision?.entries).toEqual([
|
||||
{
|
||||
title: "Forum proposals",
|
||||
body: "Anyone can start a thread.",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
communityRuleEntryFromMethodChip,
|
||||
sectionFromDecision,
|
||||
withoutUnpublishedDecisionConsensus,
|
||||
} from "../../lib/create/ruleSectionsFromMethodSelections";
|
||||
|
||||
const emptyDecisionSections = {
|
||||
corePrinciple: "",
|
||||
applicableScope: [] as string[],
|
||||
selectedApplicableScope: [] as string[],
|
||||
stepByStepInstructions: "",
|
||||
consensusLevel: 75,
|
||||
objectionsDeadlocks: "",
|
||||
};
|
||||
|
||||
describe("withoutUnpublishedDecisionConsensus", () => {
|
||||
it("keeps catalog consensus when facet copy is present", () => {
|
||||
const next = withoutUnpublishedDecisionConsensus({
|
||||
corePrinciple: "Momentum.",
|
||||
consensusLevel: 100,
|
||||
});
|
||||
expect(next.consensusLevel).toBe(100);
|
||||
});
|
||||
|
||||
it("drops seeded consensus on empty custom facet copy", () => {
|
||||
const next = withoutUnpublishedDecisionConsensus({ ...emptyDecisionSections });
|
||||
expect(next.consensusLevel).toBeUndefined();
|
||||
});
|
||||
|
||||
it("drops keyed consensus when wizard field blocks exist", () => {
|
||||
const next = withoutUnpublishedDecisionConsensus(
|
||||
{ corePrinciple: "Momentum.", consensusLevel: 75 },
|
||||
[
|
||||
{
|
||||
kind: "proportion",
|
||||
id: "p1",
|
||||
blockTitle: "Quorum",
|
||||
defaultPercent: 60,
|
||||
},
|
||||
],
|
||||
);
|
||||
expect(next.consensusLevel).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("communityRuleEntryFromMethodChip", () => {
|
||||
it("publishes supportText as body when there are no labeled blocks", () => {
|
||||
expect(
|
||||
communityRuleEntryFromMethodChip(
|
||||
"Our process",
|
||||
{ corePrinciple: "" },
|
||||
{ corePrinciple: "Core Principle" },
|
||||
{ supportText: " Members propose in the forum. " },
|
||||
),
|
||||
).toEqual({
|
||||
title: "Our process",
|
||||
body: "Members propose in the forum.",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps supportText alongside wizard blocks", () => {
|
||||
const entry = communityRuleEntryFromMethodChip(
|
||||
"Our process",
|
||||
{ corePrinciple: "" },
|
||||
{ corePrinciple: "Core Principle" },
|
||||
{
|
||||
supportText: "How we decide.",
|
||||
customFieldBlocks: [
|
||||
{
|
||||
kind: "text",
|
||||
id: "b1",
|
||||
blockTitle: "Notes",
|
||||
placeholderText: "Post in #governance.",
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
expect(entry).toEqual({
|
||||
title: "Our process",
|
||||
body: "How we decide.",
|
||||
blocks: [{ label: "Notes", body: "Post in #governance." }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("sectionFromDecision", () => {
|
||||
it("omits seeded 75% and shows the policy description on a custom card", () => {
|
||||
const section = sectionFromDecision([
|
||||
{
|
||||
id: "00000000-0000-4000-8000-000000000001",
|
||||
label: "Forum proposals",
|
||||
supportText: "Anyone can start a thread; silence after a week is consent.",
|
||||
sections: { ...emptyDecisionSections },
|
||||
},
|
||||
]);
|
||||
expect(section?.categoryName).toBe("Decision-making");
|
||||
expect(section?.entries).toEqual([
|
||||
{
|
||||
title: "Forum proposals",
|
||||
body: "Anyone can start a thread; silence after a week is consent.",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("still emits catalog consensus when facet copy is present", () => {
|
||||
const section = sectionFromDecision([
|
||||
{
|
||||
id: "lazy-consensus",
|
||||
label: "Lazy Consensus",
|
||||
sections: {
|
||||
corePrinciple: "Silence is consent.",
|
||||
applicableScope: [],
|
||||
selectedApplicableScope: [],
|
||||
stepByStepInstructions: "Post a deadline.",
|
||||
consensusLevel: 100,
|
||||
objectionsDeadlocks: "Any member can block.",
|
||||
},
|
||||
},
|
||||
]);
|
||||
const blocks = section?.entries[0]?.blocks ?? [];
|
||||
expect(blocks).toContainEqual({
|
||||
label: "Consensus Level",
|
||||
body: "100%",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not duplicate consensus when a wizard proportion block exists", () => {
|
||||
const section = sectionFromDecision(
|
||||
[
|
||||
{
|
||||
id: "00000000-0000-4000-8000-000000000002",
|
||||
label: "Custom vote",
|
||||
supportText: "We vote in person.",
|
||||
sections: {
|
||||
...emptyDecisionSections,
|
||||
consensusLevel: 75,
|
||||
},
|
||||
},
|
||||
],
|
||||
{
|
||||
"00000000-0000-4000-8000-000000000002": [
|
||||
{
|
||||
kind: "proportion",
|
||||
id: "q",
|
||||
blockTitle: "Quorum",
|
||||
defaultPercent: 60,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
const blocks = section?.entries[0]?.blocks ?? [];
|
||||
expect(blocks).toEqual([{ label: "Quorum", body: "60%" }]);
|
||||
expect(section?.entries[0]?.body).toBe("We vote in person.");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user