///
/**
* Gender Module - Pattern for gender identity selection
*
* A composable pattern that can be used standalone or embedded in containers
* like Record. Provides gender selection with inclusive predefined options.
*/
import {
computed,
type Default,
handler,
NAME,
pattern,
UI,
Writable,
} from "commontools";
import type { ModuleMetadata } from "./container-protocol.ts";
// ===== Self-Describing Metadata =====
export const MODULE_METADATA: ModuleMetadata = {
type: "gender",
label: "Gender",
icon: "\u26A7", // ⚧ transgender/gender symbol
schema: {
gender: {
type: "string",
enum: ["", "woman", "man", "non-binary", "other", "prefer-not-to-say"],
description: "Gender identity",
},
},
fieldMapping: ["gender"],
};
// ===== Types =====
/** Gender identity values */
type GenderValue =
| "woman"
| "man"
| "non-binary"
| "other"
| "prefer-not-to-say";
export interface GenderModuleInput {
/** Gender identity - Writable<> required for setGender handler to call .set() */
gender: Writable>;
}
// ===== Constants =====
const GENDER_OPTIONS = [
{ value: "", label: "Not specified" },
{ value: "woman", label: "Woman" },
{ value: "man", label: "Man" },
{ value: "non-binary", label: "Non-binary" },
{ value: "other", label: "Other" },
{ value: "prefer-not-to-say", label: "Prefer not to say" },
];
// Valid gender values for normalization
const VALID_GENDER_VALUES = new Set([
"",
"woman",
"man",
"non-binary",
"other",
"prefer-not-to-say",
]);
/**
* Normalize gender input to a valid enum value.
* Handles case variations (e.g., "Non-binary" -> "non-binary").
*/
function normalizeGenderValue(input: string): GenderValue | "" {
if (!input || typeof input !== "string") return "";
const normalized = input.toLowerCase().trim();
if (VALID_GENDER_VALUES.has(normalized)) {
return normalized as GenderValue | "";
}
// Handle common variations
if (normalized === "nonbinary" || normalized === "non binary") {
return "non-binary";
}
if (normalized === "prefer not to say" || normalized === "prefernotosay") {
return "prefer-not-to-say";
}
return "";
}
// ===== Handlers =====
/**
* Handler for setting gender value programmatically.
* Normalizes input to handle case variations and common aliases.
*
* This handler is LLM-callable via Omnibot's invoke() tool.
* IMPORTANT: Handlers must accept result?: Writable and use result.set()
* to return data to the LLM. The 'result' Cell is injected by llm-dialog.ts.
*/
const setGender = handler<
{ value: string; result?: Writable },
{ gender: Writable }
>(({ value, result }, { gender }) => {
const normalized = normalizeGenderValue(value);
gender.set(normalized);
// Return confirmation to the LLM
if (result) {
const displayLabel = GENDER_OPTIONS.find((o) =>
o.value === normalized
)?.label ||
"Not specified";
result.set({
success: true,
value: normalized,
displayLabel,
message: `Gender set to ${displayLabel}`,
});
}
});
// ===== The Pattern =====
export const GenderModule = pattern(
({ gender }) => {
const displayText = computed(() => {
const currentGender = gender.get();
const opt = GENDER_OPTIONS.find((o) => o.value === currentGender);
return opt?.label || "Not specified";
});
return {
[NAME]: computed(() => `${MODULE_METADATA.icon} Gender: ${displayText}`),
[UI]: (
),
gender,
setGender: setGender({ gender }),
};
},
);
export default GenderModule;