Give MultiSelect chips accessible names and start creation fields empty.

Catalog chips now announce Select or Deselect so they are named for assistive tech. Method and value modals open blank; Generate draft inserts the catalog copy that used to be prefilled, and Customize still keeps the section headings.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
adilalloandCursor committed 2026-09-29 15:59:00 -06:00
1 parent fd9f82ea19
commit 20ea4a2f30
35 files changed
+498 -156

No files matched your search

+38 -9
View File
@@ -14,11 +14,10 @@ import type {
} from "../../app/(app)/create/types";
/**
* Per-method preset defaults shipped in `messages/en/create/customRule/*.json`.
* Used to seed the final-review edit modal when the user has no saved
* override yet, and to merge onto overrides when emitting the published rule
* document so every method carries a complete section payload even if the
* author only edited a subset of fields.
* Catalog copy for method/value sections. Generate draft inserts these into
* empty creation-modal fields. Publish still merges them onto saved overrides
* so every method carries a complete section payload when the author only
* edited a subset of fields.
*/
type CustomRuleMethodRow = {
id: string;
@@ -96,6 +95,10 @@ export function communicationPresetFor(
};
}
export function emptyCommunicationDetails(): CommunicationMethodDetailEntry {
return { corePrinciple: "", logisticsAdmin: "", codeOfConduct: "" };
}
export function membershipPresetFor(id: string): MembershipMethodDetailEntry {
const method = findMethod(membershipMessages, id);
const s = method?.sections ?? {};
@@ -106,6 +109,10 @@ export function membershipPresetFor(id: string): MembershipMethodDetailEntry {
};
}
export function emptyMembershipDetails(): MembershipMethodDetailEntry {
return { eligibility: "", joiningProcess: "", expectations: "" };
}
/** Default consensus level used when a **catalog** preset omits a value. */
export const DECISION_CONSENSUS_LEVEL_DEFAULT = 75;
@@ -132,6 +139,20 @@ export function decisionApproachPresetFor(
return entry;
}
/** Catalog scopes/consensus kept; text fields start empty for Generate draft. */
export function emptyDecisionApproachTextFields(
preset: DecisionApproachDetailEntry,
): DecisionApproachDetailEntry {
return {
...preset,
corePrinciple: "",
stepByStepInstructions: "",
objectionsDeadlocks: "",
applicableScope: [...preset.applicableScope],
selectedApplicableScope: [...preset.selectedApplicableScope],
};
}
export function conflictManagementPresetFor(
id: string,
): ConflictManagementDetailEntry {
@@ -146,11 +167,19 @@ export function conflictManagementPresetFor(
};
}
export function emptyConflictManagementDetails(): ConflictManagementDetailEntry {
return {
corePrinciple: "",
applicableScope: [],
selectedApplicableScope: [],
processProtocol: "",
restorationFallbacks: "",
};
}
/**
* Preset meaning/signals for a core value chip. Mirrors `CoreValuesSelectScreen`'s
* `getInitialTexts` so the final-review edit modal opens with the same
* preset copy the select screen would have shown — without requiring the
* user to have opened the select-screen modal first.
* Preset meaning/signals for a core value chip. Used by Generate draft and
* by publish/display merge when the author has not saved an override.
*
* Lookup order:
* 1. Numeric chip id → 1-based index into `coreValues.json` `values[]`
+7 -45
View File
@@ -89,46 +89,6 @@ function clampPercent(n: number): number {
return Math.min(100, Math.max(1, Math.round(n)));
}
function facetPrefillHasContent(prefill: MethodCardWizardFacetPrefill): boolean {
switch (prefill.group) {
case "communication":
return (
hasTrimmedText(prefill.draft.corePrinciple) ||
hasTrimmedText(prefill.draft.logisticsAdmin) ||
hasTrimmedText(prefill.draft.codeOfConduct)
);
case "membership":
return (
hasTrimmedText(prefill.draft.eligibility) ||
hasTrimmedText(prefill.draft.joiningProcess) ||
hasTrimmedText(prefill.draft.expectations)
);
case "decisionApproaches":
return (
hasTrimmedText(prefill.draft.corePrinciple) ||
hasTrimmedText(prefill.draft.stepByStepInstructions) ||
hasTrimmedText(prefill.draft.objectionsDeadlocks) ||
prefill.draft.applicableScope.length > 0 ||
prefill.draft.selectedApplicableScope.length > 0
);
case "conflictManagement":
return (
hasTrimmedText(prefill.draft.corePrinciple) ||
hasTrimmedText(prefill.draft.processProtocol) ||
hasTrimmedText(prefill.draft.restorationFallbacks) ||
formatConflictApplicableScopeForTextarea(
prefill.draft.selectedApplicableScope,
prefill.draft.applicableScope,
).trim().length > 0
);
case "coreValues":
return (
hasTrimmedText(prefill.draft.meaning) ||
hasTrimmedText(prefill.draft.signals)
);
}
}
function mapFacetPrefillToWizardFieldBlocks(
prefill: MethodCardWizardFacetPrefill,
): CustomMethodCardFieldBlock[] {
@@ -198,7 +158,11 @@ function mapFacetPrefillToWizardFieldBlocks(
);
if (
typeof prefill.draft.consensusLevel === "number" &&
facetPrefillHasContent(prefill)
(hasTrimmedText(prefill.draft.corePrinciple) ||
hasTrimmedText(prefill.draft.stepByStepInstructions) ||
hasTrimmedText(prefill.draft.objectionsDeadlocks) ||
prefill.draft.applicableScope.length > 0 ||
prefill.draft.selectedApplicableScope.length > 0)
) {
blocks.push({
kind: "proportion",
@@ -260,14 +224,12 @@ function mapFacetPrefillToWizardFieldBlocks(
/**
* Map a method card's facet editors onto wizard field blocks so Customize
* can show and edit the same sections on step 3.
* can show and edit the same sections on step 3. Empty copy still yields
* the catalog headings so authors can fill them in the wizard.
*/
export function facetDetailsToWizardFieldBlocks(
prefill: MethodCardWizardFacetPrefill,
): CustomMethodCardFieldBlock[] {
if (!facetPrefillHasContent(prefill)) {
return [];
}
return mapFacetPrefillToWizardFieldBlocks(prefill);
}