Skip to content

Commit 6c7df26

Browse files
eprifticlaude
andcommitted
Replace free-text language/data_type with selectable checkboxes
Prevents typos like "pow" instead of "pow2" by using checkbox groups with the valid options: bin/ter/ratio/pow2 for language, raw/prev/log for data type. The form value remains a comma-separated string. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9dfe8f1 commit 6c7df26

3 files changed

Lines changed: 109 additions & 9 deletions

File tree

‎frontend/src/__tests__/parameterDefs.test.js‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,15 @@ describe('PARAM_DEFS', () => {
6262
})
6363

6464
it('inputType is a valid type', () => {
65-
const validTypes = ['text', 'number', 'select', 'checkbox']
65+
const validTypes = ['text', 'number', 'select', 'checkbox', 'checkboxGroup']
6666
for (const p of PARAM_DEFS) {
6767
expect(validTypes).toContain(p.inputType)
6868
}
6969
})
7070

71-
it('select inputs have options array', () => {
71+
it('select and checkboxGroup inputs have options array', () => {
7272
for (const p of PARAM_DEFS) {
73-
if (p.inputType === 'select') {
73+
if (p.inputType === 'select' || p.inputType === 'checkboxGroup') {
7474
expect(Array.isArray(p.options)).toBe(true)
7575
expect(p.options.length).toBeGreaterThan(0)
7676
for (const opt of p.options) {

‎frontend/src/components/ParamSection.vue‎

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<!-- Basic params — always visible -->
1515
<div class="form-grid">
1616
<template v-for="p in basicParams" :key="p.key">
17-
<div class="form-field" :class="{ 'full-width': p.inputType === 'text' }">
17+
<div class="form-field" :class="{ 'full-width': p.inputType === 'text' || p.inputType === 'checkboxGroup' }">
1818
<label class="field-label">
1919
<span class="label-text">{{ p.label }}</span>
2020
<span v-if="p.unit" class="field-unit">({{ p.unit }})</span>
@@ -26,6 +26,23 @@
2626
<span class="check-label">{{ form[p.key] ? 'Enabled' : 'Disabled' }}</span>
2727
</label>
2828
</template>
29+
<template v-else-if="p.inputType === 'checkboxGroup'">
30+
<div class="checkbox-group">
31+
<label
32+
v-for="opt in p.options"
33+
:key="opt.value"
34+
class="checkbox-group-item"
35+
:class="{ active: isGroupChecked(form, p.key, opt.value) }"
36+
>
37+
<input
38+
type="checkbox"
39+
:checked="isGroupChecked(form, p.key, opt.value)"
40+
@change="toggleGroupValue(form, p.key, opt.value)"
41+
/>
42+
<span>{{ opt.label }}</span>
43+
</label>
44+
</div>
45+
</template>
2946
<template v-else-if="p.inputType === 'select'">
3047
<select v-model="form[p.key]">
3148
<option v-for="opt in p.options" :key="opt.value" :value="opt.value">{{ opt.label }}</option>
@@ -52,7 +69,7 @@
5269
<summary>Advanced ({{ advancedParams.length }})</summary>
5370
<div class="form-grid">
5471
<template v-for="p in advancedParams" :key="p.key">
55-
<div class="form-field" :class="{ 'full-width': p.inputType === 'text' }">
72+
<div class="form-field" :class="{ 'full-width': p.inputType === 'text' || p.inputType === 'checkboxGroup' }">
5673
<label class="field-label">
5774
<span class="label-text">{{ p.label }}</span>
5875
<span v-if="p.unit" class="field-unit">({{ p.unit }})</span>
@@ -64,6 +81,23 @@
6481
<span class="check-label">{{ form[p.key] ? 'Enabled' : 'Disabled' }}</span>
6582
</label>
6683
</template>
84+
<template v-else-if="p.inputType === 'checkboxGroup'">
85+
<div class="checkbox-group">
86+
<label
87+
v-for="opt in p.options"
88+
:key="opt.value"
89+
class="checkbox-group-item"
90+
:class="{ active: isGroupChecked(form, p.key, opt.value) }"
91+
>
92+
<input
93+
type="checkbox"
94+
:checked="isGroupChecked(form, p.key, opt.value)"
95+
@change="toggleGroupValue(form, p.key, opt.value)"
96+
/>
97+
<span>{{ opt.label }}</span>
98+
</label>
99+
</div>
100+
</template>
67101
<template v-else-if="p.inputType === 'select'">
68102
<select v-model="form[p.key]">
69103
<option v-for="opt in p.options" :key="opt.value" :value="opt.value">{{ opt.label }}</option>
@@ -99,6 +133,23 @@ const props = defineProps({
99133
100134
const basicParams = computed(() => props.params.filter(p => p.level === 'basic'))
101135
const advancedParams = computed(() => props.params.filter(p => p.level === 'advanced'))
136+
137+
// checkboxGroup helpers — form value is a comma-separated string like "bin,ter,ratio"
138+
function isGroupChecked(form, key, value) {
139+
const current = (form[key] || '').split(',').map(v => v.trim()).filter(Boolean)
140+
return current.includes(value)
141+
}
142+
143+
function toggleGroupValue(form, key, value) {
144+
const current = (form[key] || '').split(',').map(v => v.trim()).filter(Boolean)
145+
const idx = current.indexOf(value)
146+
if (idx >= 0) {
147+
current.splice(idx, 1)
148+
} else {
149+
current.push(value)
150+
}
151+
form[key] = current.join(',')
152+
}
102153
</script>
103154

104155
<style scoped>
@@ -234,6 +285,44 @@ input[type="checkbox"] {
234285
color: var(--text-secondary);
235286
}
236287
288+
/* Checkbox group (multi-select as toggle pills) */
289+
.checkbox-group {
290+
display: flex;
291+
flex-wrap: wrap;
292+
gap: 0.35rem;
293+
}
294+
295+
.checkbox-group-item {
296+
display: flex;
297+
align-items: center;
298+
gap: 0.3rem;
299+
padding: 0.25rem 0.6rem;
300+
border: 1px solid var(--border);
301+
border-radius: 6px;
302+
font-size: 0.8rem;
303+
color: var(--text-secondary);
304+
cursor: pointer;
305+
transition: all 0.15s;
306+
user-select: none;
307+
}
308+
309+
.checkbox-group-item input[type="checkbox"] {
310+
width: 14px;
311+
height: 14px;
312+
margin: 0;
313+
cursor: pointer;
314+
}
315+
316+
.checkbox-group-item.active {
317+
border-color: var(--accent);
318+
background: color-mix(in srgb, var(--accent) 10%, transparent);
319+
color: var(--text-primary);
320+
}
321+
322+
.checkbox-group-item:hover {
323+
border-color: var(--accent);
324+
}
325+
237326
/* Advanced toggle */
238327
.advanced-toggle {
239328
margin-top: 0.6rem;

‎frontend/src/data/parameterDefs.js‎

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,23 @@ export const PARAM_DEFS = [
3535
],
3636
},
3737
{
38-
key: 'language', label: 'Language', category: 'general', level: 'basic', inputType: 'text', defaultValue: 'bin,ter,ratio',
39-
description: 'Model languages: bin (binary), ter (ternary), ratio, pow2. Comma-separated list; initial population is split among them.',
38+
key: 'language', label: 'Language', category: 'general', level: 'basic', inputType: 'checkboxGroup', defaultValue: 'bin,ter,ratio',
39+
description: 'Model languages to explore. Initial population is split among selected languages.',
40+
options: [
41+
{ value: 'bin', label: 'Binary' },
42+
{ value: 'ter', label: 'Ternary' },
43+
{ value: 'ratio', label: 'Ratio' },
44+
{ value: 'pow2', label: 'Power of 2' },
45+
],
4046
},
4147
{
42-
key: 'data_type', label: 'Data type', category: 'general', level: 'basic', inputType: 'text', defaultValue: 'raw,prev',
43-
description: 'Data transformations: raw, prev (prevalence), log. Comma-separated list accepted.',
48+
key: 'data_type', label: 'Data type', category: 'general', level: 'basic', inputType: 'checkboxGroup', defaultValue: 'raw,prev',
49+
description: 'Data transformations to apply. Multiple selections create separate model populations.',
50+
options: [
51+
{ value: 'raw', label: 'Raw' },
52+
{ value: 'prev', label: 'Prevalence' },
53+
{ value: 'log', label: 'Log' },
54+
],
4455
},
4556
{
4657
key: 'seed', label: 'Random seed', category: 'general', level: 'basic', inputType: 'number', defaultValue: 42,

0 commit comments

Comments
 (0)