web: panel management, slice 7 of M3

The kebab menu goes live: Edit and Rename open the PanelEditorPopover
(rename focuses the label in its header), Duplicate copies with a
' copy' suffix and the next accent, Remove deletes immediately and is
disabled on the last panel. Add scenario opens the new panel's editor
right away, still capped at six.

The editor offers the strategy select plus every Config field as a row
against the base value: a differing value becomes an override
(highlighted row, per-field reset, chips on the card), typing the base
value back clears it, and runs touch only that panel through the
debounce. Engine 400s appear inline in the popover with the offending
field marked.

Per-panel failures now leave a rose dot on the kebab while the panel
keeps its last good result; the store tracks failed panel ids and
clears them on the next successful run. Field and strategy labels moved
to lib/fieldLabels so controls, editor, and table share one vocabulary.
This commit is contained in:
Justin Visser 2026-06-10 17:51:21 +02:00
parent fdc28ac686
commit eb1f7427c4
9 changed files with 545 additions and 20 deletions

View file

@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useSimStore } from '@/composables/useSimStore'
import { CONFIG_FIELD_LABELS } from '@/lib/fieldLabels'
import type { Config } from '@/types/engine'
// The collapsible advanced grid (spec section 4): world-shaping numbers
@ -17,10 +18,10 @@ interface NumberFieldDef {
}
const numberFields: NumberFieldDef[] = [
{ field: 'numStudents', label: 'Students', min: 2, step: 1 },
{ field: 'edgesPerNode', label: 'Friends per student', min: 1, step: 1 },
{ field: 'triangleProb', label: 'Clique tendency', min: 0, step: 0.05 },
{ field: 'origin', label: 'First poster', min: 0, step: 1 },
{ field: 'numStudents', label: CONFIG_FIELD_LABELS.numStudents, min: 2, step: 1 },
{ field: 'edgesPerNode', label: CONFIG_FIELD_LABELS.edgesPerNode, min: 1, step: 1 },
{ field: 'triangleProb', label: CONFIG_FIELD_LABELS.triangleProb, min: 0, step: 0.05 },
{ field: 'origin', label: CONFIG_FIELD_LABELS.origin, min: 0, step: 1 },
]
interface SeedFieldDef {
@ -29,9 +30,9 @@ interface SeedFieldDef {
}
const seedFields: SeedFieldDef[] = [
{ field: 'graphSeed', label: 'Friendship network' },
{ field: 'thresholdSeed', label: 'Who resists' },
{ field: 'educationSeed', label: 'Random picks' },
{ field: 'graphSeed', label: CONFIG_FIELD_LABELS.graphSeed },
{ field: 'thresholdSeed', label: CONFIG_FIELD_LABELS.thresholdSeed },
{ field: 'educationSeed', label: CONFIG_FIELD_LABELS.educationSeed },
]
// The engine's 400 message names the offending Go/JSON field; mark it.