I shipped a plugin with one branch I hadn't tested — so I built a fallback
I wrote the Obsidian plugin with Claude Code: it typed the code; the rules, reviews, and decisions were mine. I led, argued with it, redid things, and submitted the plugin for review myself. I'm not an engineer. My job is getting people to actually use our product. The plugin itself is simple: one main.js, 557 lines, zero dependencies, Obsidian API only. It takes the note you have open and sends it to your social accounts on a schedule. Most of the evening, though, went into getting it through the Obsidian catalog. Here's what I tripped over, so hopefully you don't have to. Six fixes the Obsidian review asked for Before a plugin lands in the catalog, it gets reviewed against the Plugin guidelines. I read them beforehand and still came back with notes. Some requirements are much easier to notice once a reviewer points directly at them. These were my six: No innerHTML. Build the DOM with createEl. They check for this because innerHTML plus third-party text is an open door. Styles go in styles.css, not inline in JS. Inline styles got bounced. Modal titles use titleEl, not your own heading inside the modal. No top-level heading in settings, and use sentence case, not Title Case Across Every Word. Drop the plugin name from command titles. Obsidian adds the prefix itself; otherwise the command palette shows Publora: Publora: Send note. It stutters. Network requests go through requestUrl. Plain fetch runs into problems on mobile, so if the plugin is isDesktopOnly: false, use Obsidian's API. None of this changed what the plugin did. But doing it upfront would have saved me a review round and a couple of days. A portal that won't explain its own errors Submission goes through community.obsidian.md: sign in with your Obsidian account, then connect GitHub separately. The happy path is documented. The potholes aren't. Mine: "You do not own this repository" — when you clearly have access. If the repo belongs to an organization, choose the organization as the submission owner instead of Myself. The error disappeared immediately. Your GitHub organization membership has to be public. The portal only sees public members. None of ours were public, so as far as the portal was concerned, our organization contained approximately nobody. One request fixes your membership: PUT /orgs/{org}/public_members/{username} The catch: you can only make your own membership public this way. Each person has to do it with their own token. The rate limiter. After several attempts, the portal starts replying with Please wait before trying again. It doesn't say how long, and clicking again only makes things worse. The solution turned out to be extremely technical: leave it alone and come back later. Then click once. Proving the release files came from your code The review passed, but there was one note left: the release files had no artifact attestation. If you haven't run into this before, the problem is pretty straightforward. Users don't install the source code they're looking at in your GitHub repository. They install the built main.js attached to a GitHub Release. Those two files can be different. Artifact attestation lets someone verify where the release file came from. GitHub Actions, through Sigstore, ties the artifact to a specific commit and workflow. This is the release.yml I ended up with — it also checks the tag matches the manifest version before it publishes: name: Release on: push: tags: - '*' permissions: contents: write id-token: write attestations: write jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Check the tag matches the manifest run: | tag="${GITHUB_REF_NAME}" manifest="$(node -p "require('./manifest.json').version")" if [ "$tag" != "$manifest" ]; then echo "Tag $tag does not match manifest version $manifest" exit 1 fi - name: Attest the release assets uses: actions/attest-build-provenance@v2 with: subject-path: | main.js manifest.json styles.css - name: Publish the release env: GH_TOKEN: ${{ github.token }} run: | gh release create "${GITHUB_REF_NAME}" \ main.js manifest.json styles.css \ --title "${GITHUB_REF_NAME}" \ --generate-notes The part that cost me the most time was these two permissions: id-token: write attestations: write Without them, my workflow went green but no attestation appeared. Everything looked fine. Everything was not fine. Once I added the permissions, the check actually passed and verified GitHub artifact attestation showed up in the review. This is probably my favorite part of the whole process: nobody has to take your word for it. The release itself carries proof of where it came from. The branch I hadn't tested At submission time, I still had one thing I hadn't managed to verify. Login in the plugin goes through OAuth, so the user gets a token. Our REST API, meanwhile, had historically been used with an API key. Would the API accept the token from this new OAuth flow? Probably. Had I actually tested it? No. I could ship it and find out from the first person whose button stopped working. People do this more often than conference talks would have you believe. Instead, I built a fallback. The plugin sends the credential — whether that's the signed-in token or a pasted key — in the same x-publora-key header. If the request comes back 401 and the token was the one that failed, it retries once with the API key from settings instead of stranding the user mid-post. If there's no key to fall back to, it says so in plain words rather than leaving a dead button: async function callApi(settings, method, path, body, plugin) { const credential = plugin ? await plugin.credential() : settings.apiKey; if (!credential) { throw new Error('Not connected yet. Open Settings, then Publora, and press Connect.'); } const usedToken = Boolean( plugin && plugin.settings.oauth && credential === plugin.settings.oauth.accessToken ); const response = await requestUrl({ url: BASE_URL + path, method, headers: { 'x-publora-key': credential, 'Content-Type': 'application/json' }, body: body ? JSON.stringify(body) : undefined, throw: false, }); if (response.status === 401) { // The signed-in token was refused. If a key is also configured, use it // rather than stranding the user mid-post. if (usedToken && settings.apiKey) { return callApi(Object.assign({}, settings, { oauth: null }), method, path, body, null); } throw new Error( usedToken ? 'Publora refused the signed-in account. Reconnect in Settings → Publora, or paste an API key there under Advanced.' : 'Publora rejected the API key. Check it in Settings → Publora.', ); } return response.json; } The retry is just the same function calling itself with oauth nulled out, so it falls through to the key. A week later, working on OAuth for another add-on, I finally got to test the real thing: the REST API accepts the token fine, and the fallback never fired. But I didn't know that when I submitted the plugin. That's the bit I want to keep from this whole exercise. Sometimes you have a branch you can't test before release. You don't have to pretend otherwise. If the failure mode is predictable and the fallback is cheap, you can put the uncertainty into the program instead of handing it to the user. Numbers I actually trust As of August 19: 33 installs. Not a lot. But the version distribution shows most of those installs on the latest version, so people are updating rather than installing once and disappearing. For comparison, our extension on another marketplace shows 772 "downloads." That counter also eats mirrors and editor caches. At one point it jumped by 26 in half an hour when basically nobody knew the extension existed. So right now I'll take the 33. At least I know what they mean. I built this plugin with AI, and I'm not particularly interested in hiding that. Claude Code typed most of it. I decided what it should do, reviewed what it produced, dealt with the submission, and decided what to do when I couldn't verify something before release. The typing is increasingly the easy part. The annoying part is still noticing the thing you haven't tested. What do you do when you reach release with one branch still uncertain: ship it and wait for the bug report, or build the fallback first?
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to