Skip to content

Marketplace-ready: F5 with no launch.json, a Registers view, and the debug adapter from the release - #8

Merged
w1ne merged 3 commits into
mainfrom
feat/marketplace-debugger
Jul 27, 2026
Merged

w1ne merged 3 commits into
mainfrom
feat/marketplace-debugger

Conversation

@w1ne

@w1ne w1ne commented Jul 27, 2026 •

Copy link
Copy Markdown
Owner

Takes the extension from "only works if you built labwired-core from source" to a .vsix that installs and debugs a chip.

The debug adapter was unreachable

findDapPath walked five levels of parent directories looking for target/release/labwired-dap. That path exists only on a machine that has built labwired-core. For anyone installing from the Marketplace, F5 could never have worked.

Deleted. Both binaries resolve through one cascade in cli.ts (setting → bundled → PATH → downloaded release), sharing a single download, guarded so a Run and an F5 racing on first use cannot both fetch it. Depends on labwired-core#686, which puts labwired-dap in the release tarball — it had never shipped in one.

F5 needed a hand-written launch.json

No provideDebugConfigurations existed, so F5 in a fresh project opened a picker and then a JSON file to fill in by hand. Now F5 synthesises a config, resolving program from .labwired/lab.yaml or by finding the built ELF.

src/firmware.ts does the discovery, and its rules exist because the obvious implementation picks the wrong file:

rule why
check ELF magic, not the filename target/release/ also holds build scripts and xtasks
skip .d / .rlib / .bin siblings they sit right beside the firmware
cross-compiled outranks a newer host binary on an embedded project, a binary under target/release with no target triple is almost never the firmware

Two package.json fixes were required for any of that to work: "*" activation removed (it activated on every window for every user), and program is no longer required — it was, which makes VS Code reject exactly the zero-config launch this adds.

See all registers

New Registers view: CPU core registers plus decoded peripheral registers, searchable, with values that changed since the last stop highlighted — a tree is the wrong shape for the question people actually ask while stepping.

Naming is architecture-specific (Cortex-M R0..R12/SP/LR/PC, RISC-V x0..x31/pc), so it renders whatever the adapter reports rather than assuming a set. Peripherals with no register schema are shown as "simulated natively — no register names available" rather than hidden: the peripheral exists and is being simulated, only its names are unknown.

The repo had no CI, and adding it immediately paid for itself

Nothing compiled the extension, nothing ran its tests, and src/test/suite/index.ts globs from its own directory — so out/test/unit/** was unreachable and the unit tests already in the tree ran nowhere.

The new workflow caught three bugs of mine that would otherwise have shipped:

  1. A dialog was holding the launch open. The "no firmware found" path awaited showErrorMessage, so resolveDebugConfiguration did not return until the user clicked. A real user would have seen a debug session that appeared to hang.
  2. A test was passing by accident — "Commands should be registered" relied on "*" having eagerly activated the extension, and never activated anything itself.
  3. The events test's dependency stub was incomplete, so wiring the new panel into the refresh fan-out crashed it.

The E2E test now skips with its reason printed when the adapter/firmware artifacts are absent, instead of sitting through a 60-second timeout and reporting a failure that says nothing about the code.

Marketplace metadata

Icon (the real LabWired mark), repository/bugs/homepage/keywords/galleryBanner, categories, a CHANGELOG that did not exist, and a README rewritten around install → F5. release.yml publishes on a vX.Y.Z tag, runs both suites first, fails if the tag disagrees with package.json, and still attaches the .vsix when VSCE_PAT is absent.

Verification

npm run test:unit   14 passing
npm test            21 passing, 1 pending
vsce package        216.57 KB — icon, changelog, out/extension.js all present
code --install-extension  →  w1ne.labwired-vscode@0.13.0

Installed manifest confirmed: intended activation events, 15 commands, no required: program.

Not done, and not claimed: the circuit webview. labwired#1033 landed the scoped @labwired/ui/canvas entry and systemToDiagram it needs, and the DAP already emits live board_io pin state, but the panel itself is not built — topologyPanel.ts still renders its own SVG.

Blocked: the w1ne Marketplace publisher does not exist. Create it at https://marketplace.visualstudio.com/manage and add a VSCE_PAT repo secret scoped to Marketplace → Manage; tagging v0.13.0 then publishes.

w1ne added 3 commits July 27, 2026 15:16
Two things stood between "installed the extension" and "debugging a chip".

**The debug adapter was unreachable.** `findDapPath` walked five levels of
parent directories looking for `target/release/labwired-dap` — a path that only
exists on a machine that has built labwired-core from source. Deleted. Both
binaries now resolve through one cascade in cli.ts (setting → bundled → PATH →
downloaded release), so `resolveCli` and `resolveDap` share a single download,
guarded so a Run and an F5 racing on first use cannot both fetch it.
Requires labwired-core#686, which puts labwired-dap in the release tarball.

**F5 needed a hand-written launch.json.** There was no
`provideDebugConfigurations`, so pressing F5 in a fresh project dropped the user
into a configuration picker and then a JSON file to fill in by hand. Now:

- F5 with no launch.json synthesises a config
- `program` resolves from .labwired/lab.yaml, else by finding the built ELF
- no firmware found offers "Set up LabWired" instead of a dead error

src/firmware.ts does the discovery, and its rules exist because the obvious
implementation picks the wrong file. It checks ELF magic rather than trusting
names (target/release also holds build scripts and xtasks), skips .d/.rlib/.bin
siblings, and ranks a cross-compiled binary above a *newer* host one — on an
embedded project a binary under target/release without a target triple is
almost never the firmware.

Also in package.json:
- `"*"` activation removed. It activated on every window for every user; the
  remaining events cover every way the extension is actually reached.
- `program` is no longer `required`. It was, which makes VS Code reject exactly
  the zero-config launch this change introduces.

**The repo had no CI.** Nothing compiled the extension, nothing ran its tests,
and out/test/unit was not reachable from the test runner at all — so the unit
tests present in the tree never ran anywhere. Adds a workflow that compiles,
runs the unit tests, packages a .vsix, and asserts out/extension.js is inside
it: a package missing its compiled output installs fine and then silently does
nothing. The new tests follow the existing BDD convention so they run under the
same runner.

Verified `vsce package` produces a 207 KB .vsix containing extension.js,
cli.js, and firmware.js. .vscodeignore now also drops docs/, .github/, and a
stray test_output.txt that was being shipped to users.
Adds a Registers view: CPU core registers plus every peripheral register the
chip can decode, searchable, refreshed on each stop. The Peripherals tree lists
the same data, but a tree is the wrong shape for the question people actually
ask while stepping — *what changed?* — so this diffs against the previous stop
and marks the deltas.

Register naming is architecture-specific (Cortex-M R0..R12/SP/LR/PC, RISC-V
x0..x31/pc), so it renders whatever the adapter's Registers scope reports rather
than assuming a set. Peripherals whose model advertises no schema report
kind: "native" with no registers, and the panel says "simulated natively — no
register names available" rather than hiding them: the peripheral exists and is
being simulated, only its register NAMES are unknown.

## Three failures the new CI caught, all mine

Adding CI immediately paid for itself. Each of these would have shipped.

1. **A dialog was holding the launch open.** The "no firmware found" path
   awaited showErrorMessage, so resolveDebugConfiguration did not return until
   the user clicked something. Two tests timed out; a real user would have seen
   a debug session that appeared to hang. The offer to fix it is an aside, not
   a gate — it is no longer awaited, and the launch aborts immediately.

2. **A test was passing by accident.** "Commands should be registered" relied on
   `activationEvents: ["*"]` having eagerly activated the extension. Removing
   `"*"` (correct on its own merits) exposed that it never activated anything
   itself. It now activates explicitly, which is what it always meant to assert.

3. **The events test's dependency stub was incomplete**, so adding the panel to
   the refresh fan-out crashed it. Extended, with an assertion that the panel
   actually refreshes on continued/stopped — otherwise the wiring is untested.

## The E2E test now skips instead of failing

dap.test.ts needs a built labwired-dap and a cross-compiled firmware from a
sibling labwired-core tree. On a clean checkout it sat through a 60-second
timeout and reported a failure that said nothing about the code. It now skips
with the reason printed. A red test whose only cause is a missing artifact
trains people to ignore the suite.

Local: 21 passing, 1 pending (host suite); 14 passing (unit); .vsix packages at
212 KB.
…flow

Everything needed to publish, except the credential.

- `icon` — the real LabWired mark, downscaled from landing_page/icon-512.png.
  The listing had no icon at all.
- `repository`, `bugs`, `homepage`, `keywords`, `galleryBanner`, and
  `categories` (Debuggers, Visualization, Testing). Without a repository field
  the Marketplace shows no source link.
- `description` rewritten to say what it does rather than what it is: "Debug
  firmware on a simulated chip: breakpoints, stepping, and every register — no
  hardware required."
- Version 0.13.0.

CHANGELOG.md did not exist. It now leads with the thing that actually changed:
this is the first release where debugging works on a machine that has not built
LabWired from source.

README rewritten around install → F5, since it is the Marketplace landing page.
The old "Features" list was dropped: it duplicated the new table and advertised
a "Register Inspector" that predates the Registers view.

release.yml publishes on a `vX.Y.Z` tag. Three deliberate choices:
- it runs the unit AND extension-host suites first, so a release can never
  skip gates a PR has to pass;
- it fails if the tag disagrees with package.json, which would otherwise
  produce a listing whose version does not match its source;
- publishing is skipped when VSCE_PAT is absent, and the .vsix is still
  attached to the GitHub release — a tag should not fail purely for want of a
  credential.

Verified end to end: `vsce package` produces a 216 KB .vsix carrying the icon,
changelog, and compiled output, and it installs into real VS Code as
w1ne.labwired-vscode@0.13.0 with the intended activation events and no
`required: program`.

Still blocked on the `w1ne` Marketplace publisher, which does not exist yet.
@w1ne w1ne changed the title F5 works with no launch.json, and the debug adapter comes from the release Marketplace-ready: F5 with no launch.json, a Registers view, and the debug adapter from the release Jul 27, 2026
@w1ne
w1ne merged commit 6d69e6e into main Jul 27, 2026
2 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