Skip to content

fillTemplates: substituteFields skips in-string substitution for numeric/boolean field values #1249

Description

@JamesPHoughton

Bug

`substituteFields` in server/src/preFlight/fillTemplates.js only does in-string ${key} replacement when the field value is a string. Numeric and boolean values silently fall through the embedded-string path, so a YAML-native int like roundN: 1 leaves ${roundN} literal inside longer strings such as round_${roundN}_choice. The whole-value "${key}" regex (which JSON-stringifies) already works for all types — only the embedded-string path is missing.

The in-line comment already describes the intended behaviour as "string or number" — only the implementation forgot the number case:

// if the value is just a string or number, we can also replace instances of ${key} within other strings
if (typeof value === "string") {   // ← should also accept number / boolean
  const stringReplacementRegex = new RegExp(`\\$\\{${key}\\}`, "g");
  stringifiedTemplate = stringifiedTemplate.replace(
    stringReplacementRegex,
    value,
  );
}

Repro

substituteFields({
  templateContent: { name: "round_${roundN}_choice" },
  fields: { roundN: 1 },
});
// → { name: "round_${roundN}_choice" }   ← bug, placeholder survives

With roundN: "1" (string), substitution works. With roundN: 1 (number), it doesn't.

This was originally surfaced in deliberation-lab/stagebook (which has its own copy of fillTemplates) when a Prisoner's Dilemma example used a ${roundN} broadcast over numeric round IDs and the viewer prompted for a roundN field at study start. We fixed it there in deliberation-lab/stagebook#276 — same fix should apply here.

Suggested fix

Extend the type check to cover all stringifiable scalars and use String(value) to handle non-string values cleanly:

if (
  typeof value === "string" ||
  typeof value === "number" ||
  typeof value === "boolean"
) {
  const stringReplacementRegex = new RegExp(`\\$\\{${key}\\}`, "g");
  stringifiedTemplate = stringifiedTemplate.replace(
    stringReplacementRegex,
    String(value),
  );
}

The whole-value "${key}" replacement above this block already handles object/array values via JSON.stringify, so this branch only needs to cover scalars that can meaningfully be embedded in a string. Booleans get the same treatment for symmetry — ${flag}"true" / "false".

Suggested regression tests (mirroring what we added in stagebook):

  • Numeric field embedded in a string: { count: "${num}" } + { num: 42 }{ count: 42 } (already works), but { name: "x_${num}_y" } + { num: 42 }{ name: "x_42_y" } (the regression).
  • Numeric broadcast field substituted into stage names — exact repro shape.
  • Boolean field embedded in another string for symmetry.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions