GE-Enforce editor: onboarding field guidance
All checks were successful
CI / backend (push) Successful in 1m38s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s

Per-method detection hint: a plain-language line explains what "already
correct" means for the selected detection method (Registry/File/FileVersion/
Hash/MarkerFile/ValueMatches/pnputil/Always), updating live as the author
picks one. Lives in entryForm.js (DETECTION_METHOD_HINTS + detectionMethodHint)
with 4 new vitest cases; the editor renders it under the dropdown.

Also: relative-path hint on Installer/Source (path under the scope payload
folder or an inline payload), an InUseCheck behavior hint, and refresh the
entryForm.js header now that the editor imports these helpers directly.

vitest 49 pass; frontend build green; naming green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-07-13 07:31:43 -04:00
parent 456a44104b
commit d7b777a7a0
3 changed files with 57 additions and 9 deletions

View File

@@ -334,7 +334,11 @@
<!-- payload fields per type -->
<div v-if="['MSI','EXE','CMD','BAT','INF'].includes(entryForm.Type)" class="settings-grid">
<div class="setting-row">
<label><span>Installer (relative path)</span><input v-model="entryForm.Installer" /></label>
<label>
<span>Installer (relative path)</span>
<input v-model="entryForm.Installer" />
<small class="input-hint">Path under the scope payload folder on the share, or an inline payload attached below.</small>
</label>
</div>
<div class="setting-row">
<label><span>Install args</span><input v-model="entryForm.InstallArgs" /></label>
@@ -350,7 +354,11 @@
</div>
<div v-if="entryForm.Type === 'File'" class="settings-grid">
<div class="setting-row">
<label><span>Source (relative)</span><input v-model="entryForm.Source" /></label>
<label>
<span>Source (relative)</span>
<input v-model="entryForm.Source" />
<small class="input-hint">Path under the scope payload folder on the share, or an inline payload attached below.</small>
</label>
</div>
<div class="setting-row">
<label><span>Destination (absolute)</span><input v-model="entryForm.Destination" /></label>
@@ -408,6 +416,7 @@
<option value="">(none: always installs)</option>
<option v-for="method in availableDetectionMethods" :key="method" :value="method">{{ method }}</option>
</select>
<small v-if="detectionMethodHint" class="input-hint">{{ detectionMethodHint }}</small>
<small v-if="isPreinstallScope" class="input-hint">Preinstall supports Registry/File detection only.</small>
</label>
</div>
@@ -497,6 +506,7 @@
<option value="">(none)</option>
<option v-for="behavior in INUSE_BEHAVIORS" :key="behavior" :value="behavior">{{ behavior }}</option>
</select>
<small class="input-hint">What to do when a process below is running: Defer skips this cycle, CloseAndReopen restarts it, ForceClose kills it, ScheduleForReboot defers to next reboot.</small>
</label>
</div>
<div v-if="entryForm.inuseBehavior" class="process-editor">
@@ -569,7 +579,7 @@ import {
describeEntry, availableEntryTypes as availableEntryTypesFor,
availableDetectionMethods as availableDetectionMethodsFor,
targetingGates as targetingGatesFor, targetingHint as targetingHintFor,
scopeSummary as scopeSummaryFor,
scopeSummary as scopeSummaryFor, detectionMethodHint as detectionMethodHintFor,
} from './entryForm'
const scopes = ref([])
@@ -588,6 +598,8 @@ const availableEntryTypes = computed(() =>
availableEntryTypesFor(isPreinstallScope.value, entryForm.value?.Type))
const availableDetectionMethods = computed(() =>
availableDetectionMethodsFor(isPreinstallScope.value, entryForm.value?.DetectionMethod))
const detectionMethodHint = computed(() =>
detectionMethodHintFor(entryForm.value?.DetectionMethod))
const scopeSummary = computed(() => scopeSummaryFor(detail.value))
const showAllTargeting = ref(false)
const targetingGates = computed(() => targetingGatesFor(detail.value))

View File

@@ -1,11 +1,8 @@
// Pure, framework-free helpers for the GE-Enforce manifest editor.
//
// These functions are lifted verbatim (behavior-for-behavior) from
// ManifestEditor.vue so they can be unit tested without mounting the whole
// component. ManifestEditor.vue still hosts its own copies today; a follow-up
// should point the component at this module (import from here) so the tested
// code and the shipped code are the same source. Until then keep the two in
// sync: any change to the editor logic must land here too.
// ManifestEditor.vue imports these directly (the component no longer keeps its
// own copies), so the unit tests in entryForm.spec.js exercise the shipped
// code path. Change the editor logic HERE.
//
// Everything here is a plain function of its inputs. No Vue, no reactivity,
// no network. That is the whole point - deterministic logic we can pin down.
@@ -16,6 +13,26 @@ export const DETECTION_METHODS = ['Registry', 'File', 'FileVersion', 'Hash', 'Ma
'ValueMatches', 'pnputil', 'Always']
export const INUSE_BEHAVIORS = ['Defer', 'CloseAndReopen', 'ForceClose', 'ScheduleForReboot']
// Plain-language description of each detection method, shown under the Detection
// method dropdown so a first-time site admin understands what "present" means
// for the method they picked. Key '' is the no-detection case.
export const DETECTION_METHOD_HINTS = {
'': 'No detection rule: the action runs every cycle.',
Registry: 'Already correct if the registry value at Detection path/name exists (and equals Detection value when one is set).',
File: 'Already correct if the file at Detection path exists.',
FileVersion: 'Already correct if the file at Detection path is at Detection value or newer. This target feeds the Compliance panel.',
Hash: 'Already correct if the file at Detection path matches the SHA256 in Detection value. Re-copies when the file changed.',
MarkerFile: 'Already correct if the marker file at Detection path exists. Installs once, then the marker suppresses reruns.',
ValueMatches: 'Already correct if the registry value at Detection path/name equals Detection value exactly.',
pnputil: 'Already correct if a driver matching Detection pattern is staged in the Windows driver store. For INF entries.',
Always: 'Never counts as present, so the action runs every cycle. Same effect as no detection rule.',
}
// Description for the currently selected detection method, or '' if unknown.
export function detectionMethodHint(method) {
return DETECTION_METHOD_HINTS[method || ''] || ''
}
// One blank entry form, matching the shape ManifestEditor seeds for a new entry.
export function blankEntry() {
return { Type: 'MSI', DetectionMethod: '', RegType: 'String',

View File

@@ -9,6 +9,7 @@ import {
targetingHint,
scopeSummary,
describeEntry,
detectionMethodHint,
ENTRY_TYPES,
DETECTION_METHODS,
} from './entryForm.js'
@@ -300,3 +301,21 @@ describe('describeEntry', () => {
expect(describeEntry({ Type: 'Weird', Name: 'x' })).toMatch(/^Applies x/)
})
})
describe('detectionMethodHint', () => {
it('has a non-empty hint for every detection method', () => {
for (const method of DETECTION_METHODS) {
expect(detectionMethodHint(method).length).toBeGreaterThan(0)
}
})
it('describes the no-detection case for empty/undefined', () => {
expect(detectionMethodHint('')).toMatch(/every cycle/)
expect(detectionMethodHint(undefined)).toMatch(/every cycle/)
})
it('ties FileVersion to the compliance panel', () => {
expect(detectionMethodHint('FileVersion')).toMatch(/Compliance/)
})
it('returns empty string for an unknown method', () => {
expect(detectionMethodHint('Nonsense')).toBe('')
})
})