v2.0.0 — Re-architected workbench shell + workspace = project folder
v2.0 is the cut where every optional surface gets pulled out of the core, the workbench shell gets re-architected end-to-end, and a couple of API-level decisions get unwound now that they have stable replacements. Embedded hosts that vendored Kuestenlogik.Bowire directly will need a small set of explicit changes — see Breaking changes at the bottom.
Highlights
Workbench shell re-architected (#115)
The workbench surface that ships in bowire and embedded hosts was redrawn from the structural level up — Topbar carries identity + workspace context, the left rail holds mode switching, the sidebar holds per-mode lists, the main pane holds the editor, the new statusbar holds system state. Tabs cohere visually into their panes, empty-states are uniform across every surface, and a shared renderDrawer primitive backs Assistant / Help / Tests / Activity / future Inspector — drawer chrome is no longer hand-rolled per surface. Single-Accent + Protocol-Glyph is the only colour encoding; sharper radii (2/4/6 px) come from CSS custom properties. Sidebar means navigation; URL/Schema-Files/AI-Settings became their own surfaces.
Workspace = project folder (#147–#151 + #196 Phase 2)
bowire workspace init / export / import / migrate-format makes a workspace a real directory you can commit. Per-entity files (one JSON per request / environment / collection / recording) survive merges; secrets live in a sibling secrets/ tree that's .gitignored by default. The Kuestenlogik.Bowire.Workspace.Git runtime (shipped in this release) plumbs the per-entity layout through the workbench at read + write time, watches the directory for external edits via FileSystemWatcher + SSE so the workbench reload-toast fires when a teammate commits over your shoulder, merges <env>.json with its <env>.secrets.json sibling at resolve-time so the secret split stays a load-time concern instead of a save-time worry, and gates concurrent edits across two open Bowire instances via a stale-pid-aware .bowire.lock. For disk-mode workspaces (recordings / collections / scripts live on disk rather than in localStorage) the new workspace export <file.json> + workspace import CLI verbs capture every per-entity file in a single archive — .bww-style state download stays available for browser-mode workspaces.
Workspace integrity — plugin pins + scope-split settings (#193)
A workspace can now pin the protocol plugins it expects (workspace.pluginPins: { rest: ">=2.0", grpc: "*", mqtt: "5.x" }). When a team member opens the workspace, a banner fires if any pinned plugin isn't loaded — with "Install all" and "Open editor" actions instead of cryptic "no such protocol" errors at first request. The pin editor lives in the workspace's own Settings tab, so pins ride the workspace into git instead of getting buried in per-user state. The Settings dialog itself grows a scope-split tree: "My preferences" (per-user — Assistant config, theme, shortcuts, plugins) vs "This project" (per-workspace — pins, sources, env-inclusion, AI override). The Assistant tab supports per-workspace overrides directly: a scope chip in Settings, a matching reminder chip on the workspace surface, and one-click "Use global instead" to drop the override.
Optional packages live outside core
Core Kuestenlogik.Bowire ships with zero PackageReference entries — only the Microsoft.AspNetCore.App framework reference. Embedded hosts add only what they actually use:
Kuestenlogik.Bowire.Extension.MapLibre→Kuestenlogik.Bowire.Map— the v1.3.0-rc.1 of the MapLibre extension will be deprecated + unlisted on nuget.org after 2.0 ships (#197).- OpenTelemetry extracted into
Kuestenlogik.Bowire.Telemetry— embedded hosts that don't want self-observability no longer pull the OTel transitive weight. The standalone CLI keeps--telemetryworking because the tool transitively references it. .Ai,.Helpstay opt-in as before. Standalonebowirebundles them.
Naming convention going forward — optional first-party packages are Kuestenlogik.Bowire.<feature> (.Ai, .Help, .Telemetry, .Map) or Kuestenlogik.Bowire.<area>.<backend> (e.g. Kuestenlogik.Bowire.Workspace.Git, shipped in this release). The legacy .Extension.* prefix isn't used for new packages.
OpenAPI library decoupled via adapter packages
REST plugin no longer takes a hard dependency on Microsoft.OpenApi. A new IBowireOpenApiAdapter seam in Kuestenlogik.Bowire.Protocol.Rest lets the consumer pick the library version:
Kuestenlogik.Bowire.Protocol.Rest.OpenApi2— pinsMicrosoft.OpenApi2.x, matches what ASP.NET Core 10'sAddOpenApi()transitively pulls. Standalonebowirebundles this one.Kuestenlogik.Bowire.Protocol.Rest.OpenApi3— pinsMicrosoft.OpenApi3.x for hosts that want the modern grammar.
Both can be loaded side-by-side; the adapter registry auto-picks the one matching the runtime's Microsoft.OpenApi major. Resolves the OpenAPI-DLL-version conflict reported on .NET 10 hosts running side-by-side with ASP.NET's bundled discovery.
Across-the-pane omnibox (#124 / #162)
Cmd/Ctrl+K opens a single search line that ranks methods, recordings, collections, settings, and ?-prefixed AI prompts uniformly. Replaces the per-surface searches that used to live in the sidebar, drawer, and method picker.
Action log + Ctrl+Z (#168) and hint dismiss (#169)
Every destructive action (delete recording, delete collection, delete workspace, swap environment scope) is reversible from the Activity drawer or via Ctrl+Z. Hints carry a permanent dismiss key that lands them in Settings → Hints and warnings so the user can restore one without restarting.
Workspaces tree + per-plugin DisplayName (#192 / #167)
Sources, collections, recordings, and environments all live as nodes under the Workspaces tree on the left rail. Plugin Settings reads each plugin's DisplayName from its registration so the row labels match what the plugin author calls itself (no more raw assembly-name fallback).
Recordings as portable artefacts — .bwr format + bowire mock <recording.bwr> (#210 / #211)
Recordings get a standalone, self-contained file format. A .bwr is a single JSON archive that carries every step of a captured session — protocol metadata, request/response pairs, content-addressed bodies, timing — without depending on a workspace it was authored in. Drop one on a colleague's machine and it loads cleanly even when their workspace pins a different plugin set.
bowire recording validate <file.bwr> lints a recording before you share it. bowire mock <recording.bwr> is the positional one-shot replay verb: point it at a .bwr, the mock server spins up and matches incoming requests against the recorded steps via MockHandler.TryMatch, dispatching the recorded response body verbatim. Unmatched requests return a structured 404. All seven protocols (REST / gRPC / GraphQL / WebSocket / SSE / MQTT / SignalR) round-trip through replay; the integration suite pins each one.
AI side-panel — BYOK cloud + MCP-client reversal (#25 Phase 3 + 4)
AI providers ship as opt-in NuGet plugins through a new IBowireAiProviderFactory seam. The standalone CLI bundles every option out of the box; embedded hosts pay only for the providers they install — Kuestenlogik.Bowire.Ai core stays free of cloud-provider SDK weight.
Kuestenlogik.Bowire.Ai.OpenAi— BYOK OpenAI + OpenRouter viaMicrosoft.Extensions.AI.OpenAI. OpenRouter rides the same SDK with its own base URL, so one package covers both.Kuestenlogik.Bowire.Ai.Anthropic— BYOK Claude via the community-maintainedAnthropic.SDKwhoseMessagesproperty is already anIChatClient.Kuestenlogik.Bowire.Ai.Mcp— MCP-client reversal: Bowire connects as an MCP client to a user-configured host (stdio command or http URL) and routes chat through the first tool whose name reads as a chat / completion / sampling gateway. Lazy-connect on first call, so Settings-UI hot-swap stays cheap.
The Settings → Assistant tab grows a six-option provider dropdown (Ollama / LM Studio / OpenAI / Anthropic / OpenRouter / MCP), a password-input API-key row with a leave-blank-keep-existing convention + an explicit __bowire_clear__ sentinel, and a per-provider privacy banner that names exactly where prompts go ("Prompts go to api.openai.com — Küstenlogik never sees the key, prompts, or responses"). API keys are never echoed back over the wire; the status endpoint surfaces a hasApiKey boolean instead.
Benchmarks as a first-class rail — envelope architecture + 3 shapes (#131)
Benchmarks are their own rail-mode peer with Discover / Mocks / Flows / Recordings / Collections, no longer a buried inline expansion on a single request pane. The shipped surface is the envelope architecture — a saved Benchmark is a {targets[], phases[], mode} bundle rather than a one-method-one-config row — with three production-relevant target shapes wired:
- single — one unary call against a service + method
- collection — replay every item of a saved collection
- recording — replay every step of a saved recording
Phases follow the Artillery / k6 stages model (duration + arrivalRate | vus) so import / export round-trips cleanly to those formats:
- Round-trip exports: native Bowire envelope JSON · Artillery JSON · k6 script (a complete
.jswith stages + per-target HTTP calls) - Imports: Artillery JSON + Postman Collection
{{var}}↔${var}auto-rewrite at the import / export boundary so variables stay legal in both directions
Run output covers latency percentiles (p50 / p95 / p99), throughput (rps), and a status histogram (success / 4xx / 5xx / timeout / network). Saved runs persist in the active workspace + carry their spec so you can re-run them after switching workspaces — the sidebar shows a p95 meta on each saved row + live N/total while a run is in flight. Per-request "Benchmark this method" affordance on method-header + tree-row drops the current method into an existing envelope or spawns a new one seeded with it.
The remaining random + scheduled shapes, previous-run diff banner, and CSV / k6-summary / OTLP result exports ship in v2.1+ under their own tickets — #231 (random), #232 (scheduled + cron), #233 (diff banner), #234 (CSV / k6-summary / OTLP). #131 closes with the envelope architecture this release.
Recordings as GB-scale artefacts — chunked storage Phase 1 (#144 → closeout #220)
Recordings move off the legacy single-file shape onto a chunked disk layout: one JSON file per step under ~/.bowire/recordings/<id>/, with a manifest at <id>.json carrying the step ids + ordering. Request and response bodies extract into bodies/<sha256>.bin so duplicate payloads dedupe across steps + recordings — typical recordings of long-poll / SSE / WebSocket traffic shrink dramatically. The runtime reads the chunked layout transparently; old recordings load via the legacy reader and get re-saved into the chunked shape on the next mutation. Every entityKind / id / responseRef / step-file path passes through SanitiseId / SanitiseHash / SanitiseStepFile barriers that reject traversal / non-hex / out-of-bucket inputs — CodeQL cs/path-injection is pinned to zero alerts at the v2.0 cut. Filesystem watch + reconcile UI + lazy step-load on UI scroll continue under #144 in v2.1.
Per-mode saved configurations — Presets framework (#140 Phase 1 → closeout #221)
A generic presets API replaces the per-mode hand-rolled "save current config" patches: loadPresetsForMode / savePresetForMode / setDefaultPreset / deletePreset, per-workspace storage keyed under bowire_presets_<mode> so presets ride the .bww export, a uniform Manage-Presets modal across modes, and a reusable renderPresetsBar(mode, …) top-of-pane bar with picker + "Save current as preset…" + "Set default" + "Manage…". Benchmarks and the Discover request-pane integrate first — Discover's method header carries a presets dropdown with per-method save / apply / set-default / add-to-collection and a single Save-as-preset action that mirrors the workspace dropdown pattern. Mocks / Parallel / Security / Proxy / Catalogue integrations follow in v2.1 under #140.
Parallel sessions from recordings + collections — local fan-out (#132 minimal → closeout #222)
Recordings and collections get a "Run in parallel sessions" toolbar action that fans N copies of the run locally. Live state tracks per-session progress (started / active / completed counts, errors) and result aggregation reports per-session pass/fail + the overall outcome ("12 / 15 sessions completed, 3 errors at step 4"). No standalone rail mode — results land inline under the source that started the run so an operator chasing parallelism doesn't need to learn a new navigation step. Distributed fan-out across workers stays under #132 in v2.1.
UI polish across the rc series
Through the v2.0 rc cycle every rail in the new shell got a uniform polish pass so they read as part of the same family — not nine slightly-different layouts. The biggest wins:
- Recording detail pane — shared
.bowire-pane-headerchrome (heading + rename pencil + danger trash viabowirePrompt); the flat 10-button toolbar regroups into Run / Build / Export gangs, Export becomes a split-button ▾ menu (HAR / HTML report / JSON with one-line hints). - Discover sidebar consolidation — favorites toggle moves into the filter popup as its first option; the filter button moves up into the unified toolbar row next to
+ New. One filter control instead of two related-but-separate ones. - Flows + Proxy sidebars stop falling through to the legacy Discover services-tree path — each gets its own minimal sidebar renderer.
- Home rhythm + drawer — Continue / Start / Sections / Footer fit a 1080 px viewport without scrolling; Favorites / Recent titles open a right-side
renderDraweroverlay with the full list (recent activity tiles carry relative timestamps). - Preset picker in the Discover request-pane header — per-method save / apply / set-default / add-to-collection; default-star sits right in the tools cluster (analog of the workspace ✓ marker);
+ Save as preset…action row at the bottom of the dropdown. - Security rail drops its wrapper "Security" heading (no other rail puts a rail-name heading above its own content); Threat Model empty state swaps the bare ⚠ no-endpoints line for the shared empty-card chrome with "Open Discover" + "Add a source" CTAs.
- Workspace settings tabs reorder Variables → Secrets → Auth (Variables + Secrets are KV-shape, Auth is a different mental model).
- Root-cause fix —
code-export.jswas callingsyncFormToJsonfrom inside the render path, clobberingformValues+requestMessageswith stale pre-merge DOM values every frame and breaking preset apply / history replay / Repeat-last-call. The fix swaps the mutation for a read-onlycollectFormValuesFromStatesnapshot.
Breaking changes
Each change has been on a back-compat ramp through v1.9.x and is removed in 2.0.
Wire format: application/problem+json drops the { error } shim (#88 follow-up)
Every Bowire endpoint that returns a problem+json body has emitted both the RFC 7807 title + a legacy error field set to the same string. v2.0 drops the error field on the wire. Clients reading the response body should switch to body.title (RFC 7807) / body.detail. The shim is gone server-side; the JS workbench bundled with the CLI was updated in lock-step. OAuth callbacks keep their error field — that's RFC 6749 from the IdP, unrelated.
Microsoft.OpenApi is no longer a transitive of Kuestenlogik.Bowire.Protocol.Rest
Hosts that referenced REST + called the discovery helpers directly must now also reference one of Kuestenlogik.Bowire.Protocol.Rest.OpenApi2 or .OpenApi3. The standalone CLI bundles OpenApi2 (matches the .NET 10 ASP.NET ecosystem); embedded hosts pick whichever matches the rest of their stack.
Kuestenlogik.Bowire.Extension.MapLibre → Kuestenlogik.Bowire.Map
Old package will be deprecated + unlisted on nuget.org after this release (#197). Swap the <PackageReference> to the new name; no API surface change.
OpenTelemetry moved into Kuestenlogik.Bowire.Telemetry
Embedded hosts that want self-observability add a <PackageReference Include="Kuestenlogik.Bowire.Telemetry" />. Standalone CLI users see no behaviour change.
localStorage cosmetic-state reset on major bump
Persistent data (workspaces, environments, recordings, collections, history, favorites) and persistent user choices (theme, watch interval) are preserved across the v1.x → v2.0 upgrade. Cosmetic UI state (active rail mode, open drawer, expanded services, filter chips, split mode) is reset to v2.0 defaults on first boot so the new shell isn't fighting a stale layout. A toast announces the reset; data is untouched.
Workbench CSS surface
Several bowire-ai-* and helper classes were renamed or removed as part of the shell refactor (.bowire-ai-empty → .bowire-pane-empty, legacy .bowire-ai-drawer* chrome classes dropped, dead-class audit removed 20 unused rules). External CSS that hard-targeted these internal classes will need to update; no JS / HTML API surface is affected.
Migration guides
${name} → {{name}} variable syntax (#145 Phase 1 — soft deprecation)
Bowire has two interpolation syntaxes that resolve identically: the original Bash-style ${name} (escape: $${name}) and the Postman / Mustache {{name}} (escape: {{{{name}}}}). v2.0 starts the planned migration window — both syntaxes still work, but ${name} is now flagged as legacy and the canonical form going forward is {{name}}.
What changes in v2.0:
- A one-time per-workspace toast fires on workbench load when the active workspace's stored data contains
${...}placeholders. The toast is snoozed via localStorage so the operator isn't nagged on every reload. - The workspace-scope scanner walks recordings, collections, freeform requests, flows, and environments using the regex
/(?<!\$)\${[^}]+}/— correctly skips escaped$${...}. - New surfaces (Cmd+K palette, AI prompts, empty-state copy) only emit
{{...}}. - Documentation explicitly marks
${...}as legacy. substituteVars()continues to handle both syntaxes in parallel — no existing recording / collection / saved request breaks.
What you need to do for v2.0:
- Nothing forced. Existing workspaces keep working with both syntaxes.
- When the toast fires, you can either dismiss it (the legacy syntax will keep resolving forever in v2.0) or open Settings → Hints and warnings to permanently dismiss it.
- For new content authored in v2.0, prefer
{{name}}— and{{env.NAME}}/{{prev.field}}/{{step1.field}}/{{runtime.now}}/{{secret.NAME}}/{{ai.NAME}}for the source-prefixed forms that landed under #125.
What's planned for v2.1 (#145 Phase 2):
- A dedicated migration tool — Settings → Migration page or a one-shot CLI command — that walks every recording, collection, environment, flow, and freeform draft and rewrites
${name}→{{name}}(and${response.X}→{{prev.X}},${now}→{{runtime.now}}, &c.). - Dry-run + diff view before commit. Per-workspace scope. Disk-stored recordings get touched too. Migration emits a single transition record so the workspace metadata says "migrated to
{{}}-syntax at<ts>". - Hard removal of the
${name}parser is NOT planned for v2.1. The earliest deprecation cut would be v3.0, and only after the migration tool has been in place for a full minor.
Acknowledgements
Closes 72 issues across the milestone — the full list is on the v2.0 milestone page. Special thanks to everyone who exercised the rc series and reported off-by-one drawer behaviour, single-tab vs multi-tab edge cases, and .bowire-ai-* class targeting in downstream CSS — every one of those reports landed as a concrete fix above.