feat(form): Error msges can now be overrided neatly#292
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces customizable Zod validation message formatting (global via ArmstrongConfigProvider and per-form via useForm), and expands the cz-ai commit message CLI with direct OpenAI/Gemini providers plus improved terminal UI output.
Changes:
- Add
ValidationMessageFormatterand wire it throughuseForm→getMyZodErrorswith fallback-to-default behavior. - Extend Armstrong global config with
defaultValidationMessageFormatterand add tests for formatter priority/fallback. - Enhance
cz-aiwith direct OpenAI/Gemini API support, model grouping, and boxed suggestion display.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/cz-ai/index.js | Adds direct OpenAI/Gemini providers, model selection grouping, and boxed output UI. |
| module/src/form/utils/validation.ts | Adds optional formatter callback support when mapping Zod issues to Armstrong validation errors. |
| module/src/form/utils/validation.spec.ts | Adds tests for formatter usage and fallback behavior in getMyZodErrors. |
| module/src/form/types.ts | Introduces formatter types and adds formatValidationMessage to IFormConfig. |
| module/src/form/hooks/useForm.ts | Applies global/per-form formatter when generating field validation errors. |
| module/src/form/hooks/useForm.spec.ts | Tests global default formatter and form-level override priority. |
| module/src/components/config/config.context.tsx | Adds global defaultValidationMessageFormatter to Armstrong config context typing. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| async function callOpenAiDirect(prompt, model) { | ||
| const apiKey = process.env.OPENAI_API_KEY; | ||
| const response = await fetch('https://api.openai.com/v1/responses', { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| Authorization: `Bearer ${apiKey}`, | ||
| }, |
There was a problem hiding this comment.
callOpenAiDirect() uses OPENAI_API_KEY without verifying it’s set. In cases like skipModel=true or misconfigured settings, this sends Authorization: Bearer undefined and yields a confusing API error. Add an explicit check for a missing/empty key and throw a clear error before making the request.
| async function callGeminiDirect(prompt, model) { | ||
| const apiKey = process.env.GEMINI_API_KEY; | ||
| const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(model)}:generateContent`, { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| 'x-goog-api-key': apiKey, | ||
| }, |
There was a problem hiding this comment.
callGeminiDirect() uses GEMINI_API_KEY without validating it’s present. If the model is selected via settings (e.g. skipModel) without an env var, the request is sent with an empty key and fails with an unclear response. Add a guard that throws a helpful error when the key is missing.
| const hasOpenRouterKey = !!process.env.OPENROUTER_API_KEY; | ||
| const hasOpenAiKey = !!process.env.OPENAI_API_KEY; | ||
| const hasGeminiKey = !!process.env.GEMINI_API_KEY; | ||
| const hasAnyApiKey = hasOpenRouterKey || hasOpenAiKey || hasGeminiKey; | ||
| const resolved = resolveSettings(); | ||
| const settings = hasApiKey ? resolved : { ...resolved, model: CLAUDE_CLI, skipModel: false }; | ||
| const settings = hasAnyApiKey ? resolved : { ...resolved, model: CLAUDE_CLI, skipModel: false }; | ||
|
|
There was a problem hiding this comment.
When skipModel is true, settings.model can still point at an OpenRouter model even if only OPENAI_API_KEY/GEMINI_API_KEY are set (or vice-versa). That leads to calling a provider without its corresponding key. Consider validating the selected model/provider against available keys up front and falling back to a working provider (e.g. Claude CLI) or prompting the user.
|
|
||
| words.forEach(word => { | ||
| if (/^\s+$/.test(word)) { | ||
| if (current && current.length < width) current += word; |
There was a problem hiding this comment.
wrapLine() can append whitespace tokens even when they push current beyond width (e.g. multiple spaces when current.length < width). That can produce wrapped lines longer than maxContentWidth, causing the box borders to misalign. Consider normalizing whitespace to a single space and/or only appending whitespace when it fits within the remaining width.
| if (current && current.length < width) current += word; | |
| if (current && current.length + 1 <= width) current += ' '; |
What's new?
This pull request introduces a new global and per-form validation message formatting system for Zod-based form validation, and adds direct support for OpenAI and Gemini API providers to the AI commit message suggestion CLI (
cz-ai). It also includes improvements to error handling, configuration, and user interface for both form validation and the CLI tool.Form Validation Enhancements:
ValidationMessageFormattertype and related context interface, allowing developers to globally or per-form customize how Zod validation messages are formatted and displayed. The global formatter can be set viaArmstrongConfigProviderand overridden per form using theformatValidationMessageoption inuseForm. [1] [2] [3]getMyZodErrorsutility to accept an optional formatter callback, using it to generate validation messages and falling back to Zod's default messages if the formatter returnsundefinedor throws. [1] [2]useFormto utilize the new global and per-form validation message formatter, and added corresponding tests to ensure correct prioritization and fallback behavior. [1] [2] [3] [4] [5] [6] [7]AI Commit CLI (
cz-ai) Improvements:These changes provide more flexible and user-friendly validation error messaging for forms, expand AI provider support for commit message suggestions, and improve the usability and configurability of the CLI tool.
board
Checklist