Select the membrane model and stage scientifically sensitive construction parameters.
unknown—implementation
Equation family—
Model and construction parameter changes are scientifically sensitive and require a new run/restart. Existing live neuron state is not silently rewritten.
Load parameters to edit the Stage-0 cell model.
`;
parameterCard.parentElement.insertBefore(card, parameterCard);
}
_bindEvents() {
if (this._elements.search) {
this._elements.search.addEventListener("input", (e) => {
this.filter = (e.target.value || "").toLowerCase().trim();
this._render();
});
}
if (this._elements.reload) {
this._elements.reload.addEventListener("click", () => this.refresh());
}
if (this._elements.applyBtn) {
this._elements.applyBtn.addEventListener("click", () => this._apply(false));
}
if (this._elements.saveProfileBtn) {
this._elements.saveProfileBtn.addEventListener("click", () => this._apply(true));
}
if (this._elements.cancelBtn) {
this._elements.cancelBtn.addEventListener("click", () => this._cancel());
}
if (this._elements.neuronModel) {
this._elements.neuronModel.addEventListener("change", () => this._renderNeuronModelEditor());
}
if (this._elements.neuronStage) {
this._elements.neuronStage.addEventListener("click", () => this._stageNeuronModelChanges());
}
if (this._elements.neuronFocus) {
this._elements.neuronFocus.addEventListener("click", () => this._focusNeuronParameters());
}
const table = byId("parameter-table");
if (table) {
table.addEventListener("click", (e) => this._handleTableClick(e));
}
}
async refresh() {
try {
const paramsData = await ParameterAPI.listParameters();
const pendingData = await ParameterAPI.pending();
this.parameters = paramsData.parameters || {};
this.pending = pendingData.pending || {};
this.history = pendingData.history || [];
this._render();
} catch (error) {
this._log(`Failed to load parameters: ${error.message}`, "error");
this._setNeuronStatus(`Failed to load neuron settings: ${error.message}`, "error");
}
}
_handleTableClick(e) {
const editBtn = e.target.closest(".param-edit");
if (editBtn) {
e.preventDefault();
const name = editBtn.dataset.name;
if (name) this._editParameter(name);
return;
}
const resetBtn = e.target.closest(".param-reset");
if (resetBtn) {
e.preventDefault();
const name = resetBtn.dataset.name;
if (name) this._resetToDefault(name);
return;
}
const cancelBtn = e.target.closest(".param-cancel");
if (cancelBtn) {
e.preventDefault();
const name = cancelBtn.dataset.name;
if (name) this._cancel([name]);
}
}
async _editParameter(name) {
const parameter = this.parameters[name];
if (!parameter) return;
const raw = prompt(`New value for ${name}:`, formatValue(parameter.value));
if (raw === null) return;
let value;
try {
value = this._parseInput(raw, parameter, name);
} catch (error) {
this._log(`Invalid value for ${name}: ${error.message}`, "error");
return;
}
try {
await ParameterAPI.proposeChange(name, value);
this._log(`Pending change recorded for ${name}`, "info");
await this.refresh();
} catch (error) {
this._log(`Failed to propose change: ${error.message}`, "error");
}
}
async _resetToDefault(name) {
const parameter = this.parameters[name];
if (!parameter || parameter.default === null || parameter.default === undefined) {
this._log(`No default value for ${name}`, "warning");
return;
}
try {
await ParameterAPI.proposeChange(name, parameter.default);
this._log(`Pending reset to default for ${name}`, "info");
await this.refresh();
} catch (error) {
this._log(`Failed to reset ${name}: ${error.message}`, "error");
}
}
async _apply(saveProfile) {
if (this.commandInFlight) return;
this.commandInFlight = true;
try {
const result = await ParameterAPI.apply(null, saveProfile);
this._log(
`Applied ${result.applied?.length || 0} parameter(s)${saveProfile ? " and saved profile" : ""}`,
"success"
);
await this.refresh();
} catch (error) {
this._log(`Apply failed: ${error.message}`, "error");
} finally {
this.commandInFlight = false;
}
}
async _cancel(names = null) {
if (this.commandInFlight) return;
this.commandInFlight = true;
try {
const result = await ParameterAPI.cancel(names);
this._log(`Cancelled ${result.cancelled?.length || 0} pending change(s)`, "info");
await this.refresh();
} catch (error) {
this._log(`Cancel failed: ${error.message}`, "error");
} finally {
this.commandInFlight = false;
}
}
_parseInput(raw, parameter, name = parameter?.name || "") {
if (typeof parameter.value === "boolean") {
const normalized = raw.toLowerCase().trim();
if (!["true", "1", "yes", "on", "false", "0", "no", "off"].includes(normalized)) {
throw new Error("expected true/false");
}
return ["true", "1", "yes", "on"].includes(normalized);
}
if (typeof parameter.value === "number") {
const num = Number(raw);
if (!Number.isFinite(num)) throw new Error("not a finite number");
if (parameter.min !== null && parameter.min !== undefined && num < Number(parameter.min)) {
throw new Error(`must be >= ${parameter.min}`);
}
if (parameter.max !== null && parameter.max !== undefined && num > Number(parameter.max)) {
throw new Error(`must be <= ${parameter.max}`);
}
return Number.isInteger(parameter.value) ? Math.trunc(num) : num;
}
if (typeof parameter.value === "string") {
if (name === "neuron.model" && !Object.hasOwn(NEURON_MODEL_CATALOG, raw)) {
throw new Error(`unsupported neuron model '${raw}'`);
}
return raw;
}
try {
return JSON.parse(raw);
} catch {
return raw;
}
}
_effectiveParameterValue(name) {
const pending = this.pending[name];
if (pending && Object.hasOwn(pending, "proposed_value")) return pending.proposed_value;
return this.parameters[name]?.value;
}
_renderNeuronModelEditor() {
const select = this._elements.neuronModel;
const fieldsRoot = this._elements.neuronFields;
if (!select || !fieldsRoot) return;
const configuredModel = this._effectiveParameterValue("neuron.model");
if (!select.dataset.userTouched && configuredModel && Object.hasOwn(NEURON_MODEL_CATALOG, configuredModel)) {
select.value = configuredModel;
}
select.oninput = () => {
select.dataset.userTouched = "true";
};
const selectedModel = Object.hasOwn(NEURON_MODEL_CATALOG, select.value)
? select.value
: "izhikevich-2003";
const descriptor = NEURON_MODEL_CATALOG[selectedModel];
if (this._elements.neuronMaturity) {
this._elements.neuronMaturity.textContent = descriptor.maturity;
this._elements.neuronMaturity.dataset.maturity = descriptor.maturity;
}
if (this._elements.neuronVersion) this._elements.neuronVersion.textContent = descriptor.version;
if (this._elements.neuronEquation) this._elements.neuronEquation.textContent = descriptor.equation;
if (this._elements.neuronNotice) this._elements.neuronNotice.textContent = descriptor.note;
const names = [...descriptor.fields, ...COMMON_NEURON_FIELDS];
fieldsRoot.innerHTML = names.map((name) => this._renderNeuronField(name)).join("");
const modelPending = this.pending["neuron.model"];
const modelChanged = modelPending
? modelPending.proposed_value !== this.parameters["neuron.model"]?.value
: selectedModel !== configuredModel;
this._setNeuronStatus(
modelChanged
? "Model treatment differs from the active configuration. Stage changes, then apply through the global pending-change bar."
: "Editor is synchronized with the active/pending neuron configuration.",
modelChanged ? "warning" : "ok"
);
}
_renderNeuronField(name) {
const parameter = this.parameters[name];
if (!parameter) {
return ``;
}
const value = this._effectiveParameterValue(name);
const label = NEURON_FIELD_LABELS[name] || name;
const pending = this.pending[name];
const pendingBadge = pending ? 'pending' : "";
if (typeof parameter.value === "boolean") {
return `
`;
}
const min = parameter.min !== null && parameter.min !== undefined ? ` min="${escapeHtml(parameter.min)}"` : "";
const max = parameter.max !== null && parameter.max !== undefined ? ` max="${escapeHtml(parameter.max)}"` : "";
const step = Number.isInteger(parameter.value) ? "1" : "any";
const unit = parameter.unit ? `${escapeHtml(parameter.unit)}` : "";
return `
`;
}
async _stageNeuronModelChanges() {
if (this.commandInFlight) return;
const select = this._elements.neuronModel;
const fieldsRoot = this._elements.neuronFields;
if (!select || !fieldsRoot) return;
const model = select.value;
if (!Object.hasOwn(NEURON_MODEL_CATALOG, model)) {
this._setNeuronStatus(`Unsupported neuron model: ${model}`, "error");
return;
}
const proposals = [];
const modelParameter = this.parameters["neuron.model"];
if (!modelParameter) {
this._setNeuronStatus("neuron.model is not exposed by the runtime configuration.", "error");
return;
}
if (this._effectiveParameterValue("neuron.model") !== model) {
proposals.push(["neuron.model", model]);
}
for (const input of fieldsRoot.querySelectorAll("[data-neuron-parameter]")) {
const name = input.dataset.neuronParameter;
const parameter = this.parameters[name];
if (!name || !parameter) continue;
let value;
try {
value = typeof parameter.value === "boolean"
? Boolean(input.checked)
: this._parseInput(input.value, parameter, name);
} catch (error) {
this._setNeuronStatus(`${name}: ${error.message}`, "error");
input.focus();
return;
}
if (value !== this._effectiveParameterValue(name)) proposals.push([name, value]);
}
if (proposals.length === 0) {
this._setNeuronStatus("No neuron changes to stage.", "ok");
return;
}
this.commandInFlight = true;
this._elements.neuronStage.disabled = true;
try {
for (const [name, value] of proposals) {
await ParameterAPI.proposeChange(name, value);
}
delete select.dataset.userTouched;
this._log(`Staged ${proposals.length} neuron parameter change(s)`, "info");
this._setNeuronStatus(`Staged ${proposals.length} change(s). Review and apply them below; restart/new run is required for fixed model parameters.`, "warning");
await this.refresh();
} catch (error) {
this._setNeuronStatus(`Failed to stage neuron changes: ${error.message}`, "error");
this._log(`Failed to stage neuron changes: ${error.message}`, "error");
} finally {
this.commandInFlight = false;
this._elements.neuronStage.disabled = false;
}
}
_focusNeuronParameters() {
if (!this._elements.search) return;
this._elements.search.value = "neuron.";
this._elements.search.dispatchEvent(new Event("input", { bubbles: true }));
byId("parameter-inspector-card")?.scrollIntoView({ behavior: "smooth", block: "start" });
}
_setNeuronStatus(message, state = "ok") {
if (!this._elements?.neuronStatus) return;
this._elements.neuronStatus.textContent = message;
this._elements.neuronStatus.dataset.state = state;
}
_render() {
this._renderTable();
this._renderPendingBar();
this._renderHistory();
this._renderNeuronModelEditor();
}
_renderTable() {
const tbody = this._elements.tableBody;
if (!tbody) return;
const names = Object.keys(this.parameters).filter((name) =>
this.filter ? name.toLowerCase().includes(this.filter) : true
);
if (names.length === 0) {
tbody.innerHTML = `