From 82fd2a07ffc91cfb34d5f33b74f8050c4c07afde Mon Sep 17 00:00:00 2001 From: Kim T Date: Sun, 19 Jul 2026 22:16:09 -0700 Subject: [PATCH 1/2] feat: surface validation errors and recommendations in the editor Wire packageErrors/packageRecommendations from @open-audio-stack/core into the metadata editor as inline warnings, so authors see schema issues and non-blocking recommendations before submitting. - Errors and recommendations only appear once the user actually changes a value (edits a field or the YAML directly), not on initial/blank load, so a freshly loaded form isn't immediately flooded with warnings. Loading a different template resets the baseline used for this comparison. - Nothing is blocked - Copy/Download/Submit via GitHub remain usable regardless of errors or recommendations. Co-Authored-By: Claude Opus 4.8 --- components/editor.tsx | 51 +++++++++++++++++++++++++++-- styles/components/editor.module.css | 25 +++++++++++++- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/components/editor.tsx b/components/editor.tsx index ebc80d8..c55a457 100644 --- a/components/editor.tsx +++ b/components/editor.tsx @@ -1,8 +1,15 @@ import AceEditor from 'react-ace'; import 'ace-builds/src-noconflict/mode-yaml'; import 'ace-builds/src-noconflict/theme-tomorrow_night_bright'; -import { packageJsToYaml, packageYamlToJs, PackageVersion, RegistryType } from '@open-audio-stack/core'; -import { Dispatch, SetStateAction, useEffect, useState, useRef } from 'react'; +import { + packageErrors, + packageJsToYaml, + packageRecommendations, + packageYamlToJs, + PackageVersion, + RegistryType, +} from '@open-audio-stack/core'; +import { Dispatch, SetStateAction, useEffect, useMemo, useState, useRef } from 'react'; import { Button } from '@mui/material'; import styles from '../styles/components/editor.module.css'; import Image from 'next/image'; @@ -70,6 +77,22 @@ const Editor = ({ form, pkgType, setForm, version }: EditorProps) => { const [yamlValue, setYamlValue] = useState(packageJsToYaml(form)); const debounceRef = useRef(null); + // Validation errors block submission-worthiness (missing/invalid required fields). + // Recommendations are non-blocking suggestions (e.g. missing arm64 support). + const errors = useMemo(() => packageErrors(form), [form]); + const recs = useMemo(() => packageRecommendations(form), [form]); + + // Only surface errors/recommendations once the user has actually changed a value, + // rather than immediately flooding a freshly loaded (or blank) form with warnings. + // Loading a different template/version resets the baseline to compare against. + const baselineRef = useRef(JSON.stringify(form)); + const prevVersionRef = useRef(version); + if (prevVersionRef.current !== version) { + prevVersionRef.current = version; + baselineRef.current = JSON.stringify(form); + } + const touched = JSON.stringify(form) !== baselineRef.current; + useEffect(() => { setYamlValue(packageJsToYaml(form)); }, [form]); @@ -104,6 +127,30 @@ const Editor = ({ form, pkgType, setForm, version }: EditorProps) => { }} /> {yamlError &&
YAML Error: {yamlError}
} + {touched && errors.length > 0 && ( +
+ {errors.length === 1 ? 'Error' : `${errors.length} errors`} +
    + {errors.map((error, index) => ( +
  • + {error.path.join('.') || 'package'}: {error.message} +
  • + ))} +
+
+ )} + {touched && recs.length > 0 && ( +
+ {recs.length === 1 ? 'Recommendation' : `${recs.length} recommendations`} +
    + {recs.map((rec, index) => ( +
  • + {rec.field}: {rec.rec} +
  • + ))} +
+
+ )}