From 98a28e1b829d54fbf900eadeca80c0848c9f07c1 Mon Sep 17 00:00:00 2001 From: Marie Harris Date: Tue, 10 Mar 2026 22:59:23 -0400 Subject: [PATCH 1/9] feat: show required form inputs and compatible component types When building a form, users had no visibility into what inputs needed to be mapped or what form component types to use for each input. - formSchemaUtils: add extractInputPaths() to flatten a JSONSchema7 inputDefinition into { path, type } pairs - pathOptionsService: export TYPE_COMPATIBILITY, COMPONENT_TYPE_LABELS, and compatibleComponentLabels() for use outside Form-JS modules - SelectedEligibilityCheck: render Required Form Inputs section showing each input path, its type, and compatible component types (filling in the existing placeholder comment) - FormValidationDrawer: show compatible component type hints on each missing input row so users know what field type to add - FormValidationDrawer: rename trigger to 'Required Inputs' and add a red badge showing the missing input count at a glance --- .../src/components/project/FormEditorView.tsx | 25 ++++++--- .../customKeyDropdown/pathOptionsService.ts | 25 ++++++++- .../SelectedEligibilityCheck.tsx | 23 ++++++-- builder-frontend/src/utils/formSchemaUtils.ts | 52 +++++++++++++++++++ 4 files changed, 115 insertions(+), 10 deletions(-) diff --git a/builder-frontend/src/components/project/FormEditorView.tsx b/builder-frontend/src/components/project/FormEditorView.tsx index 72a1be5c..258dd013 100644 --- a/builder-frontend/src/components/project/FormEditorView.tsx +++ b/builder-frontend/src/components/project/FormEditorView.tsx @@ -12,7 +12,7 @@ import Drawer from "@corvu/drawer"; // 'corvu/drawer' import CustomFormFieldsModule from "./formJsExtensions/customFormFields"; import { customKeyModule } from './formJsExtensions/customKeyDropdown/customKeyDropdownProvider'; -import PathOptionsService, { pathOptionsModule } from './formJsExtensions/customKeyDropdown/pathOptionsService'; +import PathOptionsService, { compatibleComponentLabels, pathOptionsModule } from './formJsExtensions/customKeyDropdown/pathOptionsService'; import { saveFormSchema, fetchFormPaths } from "../../api/screener"; import { extractFormPaths } from "../../utils/formSchemaUtils"; @@ -226,8 +226,18 @@ const FormValidationDrawer = ( my-auto rounded-lg text-lg font-medium transition-all duration-100 " > -
- Validate Form Outputs +
+ Required Inputs + 0}> + + {missingInputs().length} + + + 0}> + + ✓ + +
@@ -251,7 +261,7 @@ const FormValidationDrawer = ( data-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)] overflow-y-scroll" > - Form Validation + Required Inputs {/* Form Outputs Section */} @@ -289,8 +299,11 @@ const FormValidationDrawer = ( } > {(formPath) => ( -
- {formPath.path} ({formPath.type}) +
+
{formPath.path}
+
+ Use: {compatibleComponentLabels(formPath.type).join(", ")} +
)} diff --git a/builder-frontend/src/components/project/formJsExtensions/customKeyDropdown/pathOptionsService.ts b/builder-frontend/src/components/project/formJsExtensions/customKeyDropdown/pathOptionsService.ts index 1c862cc0..24c0654d 100644 --- a/builder-frontend/src/components/project/formJsExtensions/customKeyDropdown/pathOptionsService.ts +++ b/builder-frontend/src/components/project/formJsExtensions/customKeyDropdown/pathOptionsService.ts @@ -5,7 +5,7 @@ interface PathOption { disabled?: boolean; } -const TYPE_COMPATIBILITY: Record = { +export const TYPE_COMPATIBILITY: Record = { // String types 'string': ['textfield', 'textarea', 'select', 'radio', 'checklist', 'taglist'], // Number types @@ -25,6 +25,29 @@ const TYPE_COMPATIBILITY: Record = { 'any': ['textfield', 'textarea', 'number', 'checkbox', 'select', 'radio', 'checklist', 'taglist', 'datetime', 'yes_no'], }; +/** Human-readable labels for Form-JS component types. */ +export const COMPONENT_TYPE_LABELS: Record = { + textfield: "Text field", + textarea: "Text area", + number: "Number", + checkbox: "Checkbox", + yes_no: "Yes / No", + radio: "Radio buttons", + select: "Dropdown", + checklist: "Checklist", + taglist: "Tag list", + datetime: "Date picker", +}; + +/** + * Returns the compatible Form-JS component types for a given JSON Schema type, + * as human-readable labels. + */ +export function compatibleComponentLabels(schemaType: string): string[] { + const componentTypes = TYPE_COMPATIBILITY[schemaType] ?? []; + return componentTypes.map((t) => COMPONENT_TYPE_LABELS[t] ?? t); +} + interface EventBus { fire(event: string, payload: { options: PathOption[] }): void; } diff --git a/builder-frontend/src/components/project/manageBenefits/configureBenefit/SelectedEligibilityCheck.tsx b/builder-frontend/src/components/project/manageBenefits/configureBenefit/SelectedEligibilityCheck.tsx index 7fa68da9..09213b72 100644 --- a/builder-frontend/src/components/project/manageBenefits/configureBenefit/SelectedEligibilityCheck.tsx +++ b/builder-frontend/src/components/project/manageBenefits/configureBenefit/SelectedEligibilityCheck.tsx @@ -3,6 +3,8 @@ import { Accessor, createSignal, For, Show } from "solid-js"; import ConfigureCheckModal from "./modals/ConfigureCheckModal"; import { titleCase } from "@/utils/title_case"; +import { extractInputPaths } from "@/utils/formSchemaUtils"; +import { compatibleComponentLabels } from "@/components/project/formJsExtensions/customKeyDropdown/pathOptionsService"; import type { CheckConfig, @@ -60,9 +62,24 @@ const SelectedEligibilityCheck = ({
{checkConfig().checkDescription}
- { - // Place to display information about expected inputs for check - } + 0}> +
+
Required Form Inputs
+ + {(input) => ( +
+
+
{input.path}
+
({input.type})
+
+
+ Use: {compatibleComponentLabels(input.type).join(", ")} +
+
+ )} +
+
+
{checkConfig().parameterDefinitions && checkConfig().parameterDefinitions.length > 0 && (
diff --git a/builder-frontend/src/utils/formSchemaUtils.ts b/builder-frontend/src/utils/formSchemaUtils.ts index fd632293..d45bcf1e 100644 --- a/builder-frontend/src/utils/formSchemaUtils.ts +++ b/builder-frontend/src/utils/formSchemaUtils.ts @@ -1,3 +1,55 @@ +import type { JSONSchema7 } from "json-schema"; + +export interface InputPath { + path: string; + type: string; +} + +/** + * Recursively flattens a JSONSchema7 inputDefinition into dot-separated + * { path, type } pairs. Types match the keys used in TYPE_COMPATIBILITY + * (e.g. "string", "number", "boolean", "date", "date-time", "array:string"). + * + * @param schema - The JSONSchema7 inputDefinition from a CheckConfig + * @returns Array of { path, type } pairs for every leaf field + */ +export function extractInputPaths(schema: JSONSchema7 | undefined): InputPath[] { + if (!schema) return []; + + const results: InputPath[] = []; + + function resolveLeafType(node: JSONSchema7): string { + const type = Array.isArray(node.type) ? node.type[0] : node.type; + if (type === "string") { + if (node.format === "date") return "date"; + if (node.format === "date-time") return "date-time"; + if (node.format === "time") return "time"; + } + return (type as string) || "string"; + } + + function traverse(node: JSONSchema7, prefix: string) { + if (node.type === "object" && node.properties) { + for (const [key, value] of Object.entries(node.properties)) { + if (typeof value === "boolean") continue; + traverse(value, prefix ? `${prefix}.${key}` : key); + } + } else if (node.type === "array") { + const items = node.items; + let itemType = "string"; + if (items && typeof items !== "boolean" && !Array.isArray(items)) { + itemType = resolveLeafType(items as JSONSchema7); + } + if (prefix) results.push({ path: prefix, type: `array:${itemType}` }); + } else { + if (prefix) results.push({ path: prefix, type: resolveLeafType(node) }); + } + } + + traverse(schema, ""); + return results; +} + interface FormComponent { type: string; key?: string; From cdf89e2e21172044ba8420a81cfa524a6547d6bf Mon Sep 17 00:00:00 2001 From: Marie Harris Date: Tue, 10 Mar 2026 23:31:49 -0400 Subject: [PATCH 2/9] refine form input panel: compact pills, palette highlight, ARIA support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace stacked card layout with horizontal pill strip in form editor toolbar - Hover/click a pill to dim incompatible palette fields and ring compatible ones via injected