Skip to content

Feat/spec asset hub#29

Merged
apicircle-dev merged 16 commits into
mainfrom
feat/spec-asset-hub
Jul 18, 2026
Merged

Feat/spec asset hub#29
apicircle-dev merged 16 commits into
mainfrom
feat/spec-asset-hub

Conversation

@apicircle-dev

Copy link
Copy Markdown
Contributor

Mock → collection parity across the MCP server + VS Code.

Uploading an OpenAPI 3.x / Swagger 2.0 document into the Global Assets Files
library now parses it once on upload and records a SpecAssetMeta summary
(dialect, format, title, version, operationCount, warnings) on the asset.

- shared: additive GlobalFileAsset.spec + SpecAssetMeta type (+ index export)
- mock-server-core: browser-safe summarizeSpec() (detect dialect + count ops,
  no $ref resolution, no endpoint build)
- ui-components: parse-on-upload in addGlobalFileAsset + fillGlobalFileAssetBytes;
  Assets-dock spec badge (SpecAssetBadge) + parsed summary in the file editor
- mcp-server: assets.list_files envelope gains spec (null for ordinary files)
- docs: CHANGELOG, mcp-tools-reference envelope, Help "Files" section

Purely additive: existing assets and non-spec files are unaffected. Foundation
for spec-driven mock servers (Increment B) and code-vs-spec drift.
Build a mock server from an uploaded spec asset (GlobalFileAsset) in two modes:
"run live" (linked — endpoints derive from the asset, stay in sync, read-only)
or "import & edit" (materialized — parsed into editable endpoints).

- shared: MockServerSource `openapi-asset` variant + `isLinkedMockSource`
- ui-components: `resolveMockEndpoints` (asset bytes -> parse); createMockServer
  handles both modes; `refreshMockServer`; linked read-only guards + auto-refresh
  of linked mocks when the asset changes; "From spec asset" modal tab; sidebar
  Refresh / Re-import actions + read-only indicator
- mcp-server: `mock.refresh` tool + linked read-only guards on the 7 endpoint
  tools (catalog 94 -> 95)
- vscode: mockYaml serializes the new source variant
- docs: CHANGELOG, mcp-tools-reference, mock-server.md, Help; tool count
  reconciled across README / CLAUDE.md / AGENTS.md / docs

Additive; existing mocks and sources are unaffected.
The editor's unified Import modal now recognises OpenAPI 3.x / Swagger 2.0
(auto-detect, or an explicit "OpenAPI / Swagger" source) — paste or upload a
spec and it becomes a new folder with one request per operation (method, path,
query/header/path params). The Global Assets spec editor gains an "Import to
collection" button that imports straight from the stored asset.

- shared: additive ApiRequest.specAssetId + operationId back-refs
- ui-components: importOpenApiToCollection store action (reuses the mock
  resolver's parser dispatch); ImportModal OpenAPI source (sync detect via
  summarizeSpec, async import); Assets-dock "Import to collection" button
- docs: CHANGELOG, Help import section

Additive; existing requests and import formats are unaffected.
Every mock endpoint gains an "Add to collection" action (the mock sidebar's
endpoint kebab) that creates a saved request from its method + path pattern +
request-schema params — available even on read-only "run live" mocks.

- ui-components: promoteMockEndpointToRequest store action; a shared
  requestShapeFromMockEndpoint mapper (also used by the OpenAPI importer so
  promoted + imported requests are identical); MocksSidebar "Add to collection"
- mcp-server: mock.promote_endpoint tool (catalog 95 -> 96)
- docs: CHANGELOG, mcp-tools-reference, Help; count reconciled across
  README / CLAUDE.md / AGENTS.md / docs

Additive; existing mocks and requests are unaffected.
Promote the run-live (linked) mock path to a first-class, discoverable flow.
Previously "run live" was a radio buried in the New Mock Server modal, so a
contract server came out indistinguishable from a plain import.

- New "Serve OpenAPI contract" action in the Mocks header opens a dedicated
  ServeContractModal: pick a spec asset, see a contract preview (title, version,
  dialect, op count), name it, choose a port; creates a linked (read-only,
  in-sync) mock and activates it so Start/Stop is right there.
- The unified "New Mock Server > From spec asset" path now always IMPORTS
  (materialized/editable) and points to the new flow for run-live — removes the
  confusing dual toggle that made this ambiguous.
- The mock panel shows a "Served directly from contract" callout + a friendly
  source label ("OpenAPI contract (live)") for a linked mock.
- Store: mocksServeContractModalOpen + open/close actions (mirrors the create
  modal). No new persisted shape or MCP tool — reuses createMockServer +
  setMockServerDefaultPort. Additive; existing mocks and sources unaffected.

Tests: ServeContractModal (preview/create/port/validation/cancel/error),
sidebar 2-action menu, linked-mock panel callout, unified-modal import +
toggle-removal. Docs: CHANGELOG, mock-server.md, Help.
…sidebar names

A linked ("Serve OpenAPI contract") mock showed a read-only indicator but its
endpoint editor was fully interactive — the store rejected the edits, but the
inputs stayed enabled so it looked editable. Make read-only real:

- MockEndpointEditor wraps its header + node editor in <fieldset disabled> when
  the mock is linked (isLinkedMockSource), disabling every native control at
  once; the flow diagram stays enabled so the contract is still inspectable.
- The Monaco response-body editor isn't a form control, so it reads a new
  MockReadOnlyContext (standalone module, no import cycle) to go read-only too.
- An explanatory banner points users to "New Mock Server -> From spec asset" for
  an editable copy.

Also fix a sidebar bug: a long mock-server name (flex-1 without min-w-0) refused
to shrink and pushed the per-server actions (kebab) menu out of view. Add
min-w-0 so the existing truncate engages, plus shrink-0 on the row icons.

Tests: linked editor is read-only (banner + disabled controls); materialized
editor stays editable. Additive; materialized/manual/other mocks unaffected.
A "Serve OpenAPI contract" (linked, read-only) mock can now be unlocked for
editing in place, instead of re-creating it as a separate import.

- New convertMockToEditable(serverId) store action flips a linked openapi-asset
  source to materialized: endpoints are preserved (already on the server, no
  re-parse) and the spec link is kept, so "Re-import from spec" and asset-usage
  tracking keep working. No-op for non-linked/manual/unknown mocks; success toast.
- Surfaced in all three read-only contexts: the Mocks sidebar kebab ("Convert
  to editable mock"), the endpoint-editor read-only banner ("Convert to
  editable", replacing the old import hint), and the panel's "Served directly
  from contract" callout.
- Shorten the panel's source-kind label for a live contract from "OpenAPI
  contract (live)" to "OpenAPI contract" (the callout already conveys liveness).

Tests: store flip preserves endpoints + unlocks editing + no-op cases; sidebar
action flips to materialized; banner button unlocks the editor. Docs: CHANGELOG,
mock-server.md, Help. Additive; other mocks unaffected.
…spec

When a mock is served directly from an OpenAPI/Swagger contract, the user can
now re-upload the modified spec from the mock itself and the endpoints update
live — no hunting through Assets -> Files.

- New reuploadMockSpec(serverId, file) store action: validates the file IS a
  spec first (a non-spec upload is rejected with an error toast, leaving the
  mock untouched), then replaces the backing asset's bytes via
  fillGlobalFileAssetBytes (re-parses the summary + auto-refreshes every linked
  mock reading it), and toasts the new endpoint count. No-op for non-openapi
  -asset mocks.
- "Update spec..." file-picker surfaced in the panel's "Served directly from
  contract" callout and the sidebar kebab (linked mocks only).
- Export the existing bytesFromFile helper (with its jsdom FileReader fallback)
  so validation reads bytes the same robust way as the rest of the app.

Tests: replace+refresh, non-spec rejection, no-op manual, full sidebar upload
flow, callout button presence. Docs: CHANGELOG, mock-server.md, Help. Additive.
…itor

A live contract mock's editor was read-only via <fieldset disabled>, but the
Add/Import/Remove CTAs were still visible (disabled, non-working) — confusing.
Hide them in read-only mode and drop the empty-state "click Add ..." hints:

- Rule overviews: "Add rule" / "Import rule" hidden; "No validation/response
  rules." without the click-to-add hint.
- Request schema: "Derive from path" / "Add param" / row "Remove" hidden;
  "No <params>." without "Add one below."
- Default response: "+ Header" / header "Remove" / "Add multiplier" hidden; the
  multipliers empty hint drops "Add one to repeat ...".

All driven off the existing MockReadOnlyContext; inputs stay disabled+visible so
the contract remains fully inspectable. (Rule-editor / form-data / attachment
CTAs are unreachable for a spec-locked linked mock, so untouched.)

Tests: read-only hides CTAs across the validation + default-response nodes and
drops the hints; an editable mock keeps them. Additive.
…-mock option

Adding mock endpoints to a collection now produces runnable requests:

- A dedicated, activated "Mock" environment is ensured with MOCK_BASE_URL
  (http://localhost) + MOCK_PORT (prefilled from the server's port, else 8080);
  existing values are preserved on re-promote.
- Promoted request URLs are templated {{MOCK_BASE_URL}}:{{MOCK_PORT}}<path> so
  they target the live mock and can be retargeted by editing the env.
- Requests land in a "<name> (mock)" folder (reused across repeated promotes).
- The server kebab gains "Add all to collection" (promoteMockToCollection) to
  promote every endpoint at once, next to the per-endpoint "Add to collection".

requestShapeFromMockEndpoint gains an optional urlPrefix (import path unchanged).
The mock.promote_endpoint MCP tool still emits the basic mapping -- env/folder
auto-setup is UI-only for now.

Tests: env created+active+prefilled, folder created+reused, env-URL, whole-mock,
null cases. Docs: CHANGELOG, Help. Additive.
…e builder

Prep for MCP + VS Code parity: the env + folder + templated-request logic that
was inline in the web/desktop store now has one home, so all three surfaces
behave identically.

- @apicircle/shared: MOCK_ENV_NAME, MOCK_URL_PREFIX, mockEnvVarDefaults,
  mockFolderName, and requestShapeFromMockEndpoint (moved from ui-components).
- @apicircle/core: buildMockPromotion(synced, mock, endpoints, opts) returns the
  ordered WorkspacePatch sequence (ensure the active "Mock" env, find-or-create
  the "<name> (mock)" folder, one templated request per endpoint) + folderId +
  requestIds. Pure; every surface applies it via its own applyMutation path.
- ui-components: the two promote store actions now build patches via
  buildMockPromotion and apply them with applyMutation -- behavior-preserving
  (existing promote tests pass) and no more duplicated orchestration.

Tests: new core buildMockPromotion suite (env/folder/request, port prefill +
value preservation, folder reuse). Existing promote/import tests stay green.
Additive; no shipped shape changed.
…ension

Brings the env + folder + templated-URL promotion (increment J) to the two
surfaces that lacked it, all through the shared core buildMockPromotion (K1).

MCP (catalog 96 -> 97):
- mock.promote_endpoint now ensures the active "Mock" env (MOCK_BASE_URL +
  MOCK_PORT, prefilled from the mock's port else 8080), drops the request in a
  "<name> (mock)" folder, and templates the URL
  {{MOCK_BASE_URL}}:{{MOCK_PORT}}<path>.
- New mock.promote_to_collection tool promotes every endpoint at once.
- Count bumped across README (x3), mcp-server README (badge + heading), vscode
  README, mcp-tools-reference (+ rows), context doc, AGENTS, CLAUDE.

VS Code:
- New "Add to Collection" (mock endpoint node) + "Add All to Collection" (mock
  server node) commands via buildMockPromotion + active.apply, with command +
  view/item/context menu manifest entries.

All three surfaces now produce identical results (same Mock env, "<name> (mock)"
folder, templated URLs). Tests: MCP promote tools + vscode commands. Docs:
CHANGELOG. Additive.
…quest shape

K2 changed `mock.promote_endpoint` to emit a runnable request (active "Mock"
env + "<name> (mock)" folder + templated `{{MOCK_BASE_URL}}:{{MOCK_PORT}}<path>`
URL) but only `mocks.test.ts` was updated — `mockRefresh.test.ts` still asserted
the old bare `/pets` path, so it failed under the full suite. Assert the new,
already-shipped-and-documented behavior instead (templated URL, folder, active
Mock env with MOCK_PORT=8080 for the portless linked mock).
`vsce package` was bundling the extension's `coverage/` report (137 files,
~7.7 MB of HTML) and the `.turbo/` cache into the marketplace `.vsix` because
`.vscodeignore` didn't exclude them. Add both — the packaged extension drops
from 167 files / 2.88 MB to 28 files / 1.74 MB, shipping only the runtime
bundle, manifest, readme, changelog, license, media, schemas, and syntaxes.
Cut the 1.3.0 release. All workspace packages move to 1.3.0 in lockstep —
published: @apicircle/{shared,core,mock-server-core,mcp-server,cli} and the
apicircle-vscode extension; private: desktop, web, ui-components, git,
desktop-shell, e2e/*, examples (desktop-shell realigned from a stray 1.1.5).
Inter-package deps are workspace:*, so no dependency pins changed and the
lockfile is untouched.

- CHANGELOG.md: promote the accumulated "1.2.1 - Unreleased" block to
  "1.3.0 - 2026-07-18" (the spec asset hub + Mock -> collection parity across
  app / MCP / VS Code), and reconcile the promote_endpoint note now that the
  MCP + VS Code surfaces reached parity (no longer "UI-only").
- apps/vscode/CHANGELOG.md: 1.3.0 entry for the Add to Collection / Add All to
  Collection commands (the file the marketplace renders).

MCP catalog stays at 97 tools. Verified before cutting: pnpm check, lint,
build (10/10), knip (pre-existing baseline only), and the full unit suite
(~5450 tests) all green; the extension packages to a clean 1.74 MB VSIX.
@github-actions

Copy link
Copy Markdown

E2E coverage (strict mode)

Metric This PR main Δ
Live coverage 78.24% (2949/3769) 78.24% (2949/3769) ±0.00pp (+0)
Scaffold-only 93
Manual-residue 727
Passing TC-IDs this run 0

Live coverage unchanged.
Definitions: live = inline tc() / tcRange() in non-fixme test() calls. Scaffold-only = test.fixme() placeholders. Manual-residue = explicitly excluded from automation (see e2e/web/manual-residue.ts).
Full report in the e2e-coverage-${{ run_id }} artifact.

@apicircle-dev
apicircle-dev merged commit 64fa925 into main Jul 18, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant