diff --git a/CHANGELOG.md b/CHANGELOG.md index a8d5892..f185ec1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ version tags such as `v0.3.0`. ## Unreleased - Group Import Review candidates by normalized skill name, keep equivalent copies as locations of one Rust-qualified variant, require explicit selection when same-name variants differ, and submit at most one source per skill. -- Keep mixed User/Remote import suggestions inside one content-equivalent variant and require an explicit classification choice instead of manufacturing duplicate variants. +- Keep mixed User/Remote import suggestions inside one content-equivalent variant and present the explicit classification choice as a labeled required action instead of manufacturing duplicate variants. - Add explicit inbound user-skills Git updates through Check remote, reviewed repository-wide diffs, and preview-confirmed fast-forward apply. diff --git a/apps/desktop/src/cardLayout.test.js b/apps/desktop/src/cardLayout.test.js index 39a400f..cac849b 100644 --- a/apps/desktop/src/cardLayout.test.js +++ b/apps/desktop/src/cardLayout.test.js @@ -437,6 +437,9 @@ test('import candidate groups disclose locations and use radio variant selection )?.groups.body || ''; const disclosureRule = css.match(/\.candidateLocationsDisclosure\s*\{(?[^}]*)\}/s)?.groups.body || ''; const locationRule = css.match(/\.candidateLocation\s*\{(?[^}]*)\}/s)?.groups.body || ''; + const typeActionRule = css.match(/\.candidateTypeAction\s*\{(?[^}]*)\}/s)?.groups.body || ''; + const requiredTypeActionRule = css.match(/\.candidateTypeAction\.required\s*\{(?[^}]*)\}/s) + ?.groups.body || ''; assert.match(groupSource, /aria-controls=\{disclosureId\}/); assert.match(groupSource, /aria-expanded=\{expanded\}/); @@ -447,12 +450,26 @@ test('import candidate groups disclose locations and use radio variant selection assert.match(groupSource, /onSelectVariant\(group, variant\)/); assert.match(groupSource, /Source: \{compactPath\(location\.symlinkTargetPath \|\| location\.realPath\)\}/); assert.match(groupSource, /Mixed type suggestions/); - assert.match(groupSource, /Choose User or Remote before importing this skill/); + assert.match(groupSource, /id=\{typeLabelId\}>Skill type/); + assert.match(groupSource, /Required<\/strong>/); + assert.match(groupSource, /Choose where SkillBox should manage this skill/); + assert.match(groupSource, /aria-describedby=\{needsTypeChoice \? typeHelpId : undefined\}/); + assert.match(groupSource, /aria-labelledby=\{typeLabelId\}/); + assert.match(groupSource, /aria-required=\{needsTypeChoice\}/); + assert.match(groupSource, /role="radiogroup"/); + assert.match(groupSource, /role="radio"/); + assert.match(groupSource, /aria-checked=\{selectedVariant\?\.selectedType === 'user'\}/); + assert.match(groupSource, /aria-checked=\{selectedVariant\?\.selectedType === 'remote'\}/); + assert.doesNotMatch(groupSource, /Choose User or Remote before importing this skill/); assert.match(groupSource, /selectedVariant\?\.selectedType === 'user'/); assert.match(groupSource, /selectedVariant\?\.selectedType === 'remote'/); assert.match(groupSource, /disabled=\{!canClassifyImportCandidateGroup\(group\)\}/); assert.match(disclosureRule, /display:\s*inline-flex;/); assert.match(locationRule, /grid-template-columns:\s*96px minmax\(0,\s*1fr\);/); + assert.match(typeActionRule, /display:\s*grid;/); + assert.match(typeActionRule, /min-width:\s*142px;/); + assert.match(requiredTypeActionRule, /border-color:\s*var\(--skillbox-amber-border\);/); + assert.match(requiredTypeActionRule, /background:\s*var\(--skillbox-surface-orange\);/); }); test('import review uses the shared searchable candidate list template', () => { diff --git a/apps/desktop/src/components/importReview.jsx b/apps/desktop/src/components/importReview.jsx index 25e5fee..23c939c 100644 --- a/apps/desktop/src/components/importReview.jsx +++ b/apps/desktop/src/components/importReview.jsx @@ -361,6 +361,8 @@ function CandidateGroupCard({ group, onSelectVariant, onToggleSelected, onTypeCh const status = importCandidateGroupStatus(group); const locationCount = importCandidateGroupLocationCount(group); const disclosureId = `${group.id}-locations`; + const typeLabelId = `${group.id}-type-label`; + const typeHelpId = `${group.id}-type-help`; return (
@@ -387,9 +389,6 @@ function CandidateGroupCard({ group, onSelectVariant, onToggleSelected, onTypeCh {needsTypeChoice ? Mixed type suggestions : null}
{group.description || 'No description in SKILL.md'} - {needsTypeChoice ? ( -

Choose User or Remote before importing this skill.

- ) : null} - + + + ); diff --git a/apps/desktop/src/styles.css b/apps/desktop/src/styles.css index 7d15a94..c3fee2e 100644 --- a/apps/desktop/src/styles.css +++ b/apps/desktop/src/styles.css @@ -6501,12 +6501,6 @@ dd { margin: 7px 0; } -.candidateTypeReviewNote { - margin: 2px 0 0; - color: var(--skillbox-amber-text); - font-size: 12px; -} - .candidateLocation { display: grid; min-width: 0; @@ -6601,6 +6595,46 @@ dd { opacity: 0.55; } +.candidateTypeAction { + display: grid; + min-width: 142px; + justify-items: start; + gap: 5px; + border: 1px solid transparent; + border-radius: 8px; + padding: 8px; +} + +.candidateTypeAction.required { + border-color: var(--skillbox-amber-border); + background: var(--skillbox-surface-orange); +} + +.candidateTypeActionHeader { + display: flex; + width: 100%; + align-items: center; + justify-content: space-between; + gap: 8px; + color: var(--skillbox-text-secondary); + font-size: 11px; + font-weight: 720; +} + +.candidateTypeActionHeader strong { + color: var(--skillbox-amber-text); + font-size: 10px; + text-transform: uppercase; +} + +.candidateTypeAction p { + max-width: 190px; + margin: 0; + color: var(--skillbox-amber-text); + font-size: 11px; + line-height: 1.35; +} + .importSheetFooter { align-items: center; border-top: 1px solid var(--skillbox-slate-bg); @@ -6942,9 +6976,9 @@ dd { grid-template-columns: 28px minmax(0, 1fr); } - .candidateTypeSwitch { + .candidateTypeAction { grid-column: 2; - width: max-content; + width: min(100%, 320px); } .candidateLocation {