Why
After #125 v1 landed, Bowire has two interpolation syntaxes that resolve identically:
${name} — Bowire's original Bash-style syntax (escape: $${name})
{{name}} — Postman / Mustache convention (escape: {{{{name}}}})
Two syntaxes for one operation is confusing — every doc reader, new operator, and recording author has to learn both. {{...}} won the design call because:
- It matches Postman / Insomnia / Hoppscotch — every importer hits it for free
- It composes cleanly with source prefixes (
{{env.X}}, {{prev.X}}, {{step1.Y}})
- Phase-2 features (autocomplete dropdown, color-coded chips, source picker) target it
${...} stays as a legacy alias only because every existing recording / collection / saved request uses it. We need a planned migration window before deprecation.
Plan
Phase 0 — Today
- Both syntaxes work. Documentation + new examples should already prefer
{{...}}.
Phase 1 — Soft deprecation
- Add a one-time toast on workbench load when the active workspace's stored data contains
${...} placeholders: "This workspace uses the legacy ${name} syntax. The canonical syntax is {{name}}. Open Settings → Migration to convert."
- New surfaces (Cmd+K palette, AI prompts, empty-state copy) only emit
{{...}}.
- Docs explicitly mark
${...} as legacy.
Phase 2 — Migration tool
- Settings → Migration page or one-shot migration command.
- Walks every recording, collection, environment, flow, freeform draft — rewrites
${name} to {{name}} (and ${response.X} to {{prev.X}}, ${now} to {{runtime.now}}, etc.).
- Dry-run + diff view before commit. Per-workspace scope.
- Disk-stored recordings get touched too.
- Migration emits a single commit-style transition record so the workspace metadata says "migrated to {{}}-syntax at ".
Phase 3 — Hard deprecation
${name} substitution stops resolving. Placeholders pass through literally (so the operator sees the typo).
- One-line warning in the workbench console: "Legacy ${} interpolation is no longer resolved. Run Settings → Migration to convert."
- Major-version bump.
Phase 4 — Removal
- Parser drops the
${...} branch entirely. Next major version after Phase 3.
Migration script — concrete rewrites
| From |
To |
${NAME} |
{{NAME}} |
${env.NAME} |
{{env.NAME}} |
${response.path} |
{{prev.path}} |
${response} |
{{prev}} |
${now} / ${nowMs} / ${timestamp} |
{{runtime.now}} / {{runtime.nowMs}} / {{runtime.timestamp}} |
${uuid} / ${random} |
{{runtime.uuid}} / {{runtime.random}} |
${now+3600} |
{{runtime.now+3600}} |
$${NAME} (literal escape) |
{{{{NAME}}}} (quadruple-brace escape) |
The migration walks string-typed fields in:
recordingsList[].steps[].body / messages[] / metadata
collectionsList[].items[].body / headers / params
getEnvironments()[].vars[].value
flowsList[].nodes[].config.<various>
freeformRequest.body / serverUrl / metadata
Acceptance — per phase
Phase 1
Phase 2
Phase 3
Phase 4
Composes with
Out of scope
- Auto-migration on first read of a recording. Too magical; operator must opt in via the migration tool.
- Re-encoding
${...} to \\{...\\} or other escape forms. The canonical move is the syntactic swap, not a hybrid.
Why
After #125 v1 landed, Bowire has two interpolation syntaxes that resolve identically:
${name}— Bowire's original Bash-style syntax (escape:$${name}){{name}}— Postman / Mustache convention (escape:{{{{name}}}})Two syntaxes for one operation is confusing — every doc reader, new operator, and recording author has to learn both.
{{...}}won the design call because:{{env.X}},{{prev.X}},{{step1.Y}})${...}stays as a legacy alias only because every existing recording / collection / saved request uses it. We need a planned migration window before deprecation.Plan
Phase 0 — Today
{{...}}.Phase 1 — Soft deprecation
${...}placeholders: "This workspace uses the legacy${name}syntax. The canonical syntax is{{name}}. Open Settings → Migration to convert."{{...}}.${...}as legacy.Phase 2 — Migration tool
${name}to{{name}}(and${response.X}to{{prev.X}},${now}to{{runtime.now}}, etc.).Phase 3 — Hard deprecation
${name}substitution stops resolving. Placeholders pass through literally (so the operator sees the typo).Phase 4 — Removal
${...}branch entirely. Next major version after Phase 3.Migration script — concrete rewrites
${NAME}{{NAME}}${env.NAME}{{env.NAME}}${response.path}{{prev.path}}${response}{{prev}}${now}/${nowMs}/${timestamp}{{runtime.now}}/{{runtime.nowMs}}/{{runtime.timestamp}}${uuid}/${random}{{runtime.uuid}}/{{runtime.random}}${now+3600}{{runtime.now+3600}}$${NAME}(literal escape){{{{NAME}}}}(quadruple-brace escape)The migration walks string-typed fields in:
recordingsList[].steps[].body/messages[]/metadatacollectionsList[].items[].body/headers/paramsgetEnvironments()[].vars[].valueflowsList[].nodes[].config.<various>freeformRequest.body/serverUrl/metadataAcceptance — per phase
Phase 1
${...}placeholders detected.{{...}}.{{...}}examples only.Phase 2
Bowire.Cli migrate-vars(for embedded / headless).Phase 3
${...}no longer substitutes; renders literally.${...}still present.Phase 4
Composes with
Out of scope
${...}to\\{...\\}or other escape forms. The canonical move is the syntactic swap, not a hybrid.