---
name: verify-publish
description: End-to-end publish verification — refresh status, publish files, verify the result, and confirm status changes via the operability facade.
triggers:
    - verify publish
    - test publish
    - publish flow
    - does publish work
    - end to end
argument-hint: '[file-path]'
---

# Verify Publish Skill

## Purpose

Verify the full publish pipeline works end-to-end: status computation, file compilation, Git push, and status update. Uses the operability facade for structured verification rather than manual Obsidian interaction.

## When to Activate

Activate when:
- Changes were made to Publisher, PublishStatusManager, BundledGitBackend, or SyncerPageCompiler
- Changes were made to the publication center UI that affect the publish flow
- An agent needs to confirm a publish operation completed successfully
- Testing after Git/auth configuration changes

## Prerequisites

- Obsidian must be running with the test vault open.
- Plugin must be built with `npm run build:dev` (copies to test vault, enables facade via `__DEV__` flag).
- Facade must be available (`obsidian eval code="typeof window.__QS__" 2>/dev/null` returns `=> object`).
- Console capture requires `obsidian dev:debug on 2>/dev/null` (once per session).
- Plugin must be loaded and healthy (`assert('health.core')` passes).
- Repository must be configured (`assert('health.configured')` passes).
- At least one file must be marked with `publish: true` in frontmatter.

## Workflow

### Step 1: Verify prerequisites

```bash
obsidian eval code="JSON.stringify(window.__QS__.assert('health.core'))" 2>/dev/null
obsidian eval code="JSON.stringify(window.__QS__.assert('health.configured'))" 2>/dev/null
```

Both must return `{"pass":true,...}`. If `health.configured` fails, the repo is not set up — use `obsidian quartz-syncer:config` to configure.

### Step 2: Refresh publish status

```bash
obsidian eval code="(async()=>{const r=await window.__QS__.act({name:'status.refresh'});console.log(JSON.stringify({success:r.success,counts:{unpublished:r.data.unpublished.length,changed:r.data.changed.length,published:r.data.published.length,deleted:r.data.deleted.length}}))})()" 2>/dev/null
```

Expected: `{"success":true}`. Then check the snapshot:

```bash
obsidian eval code="JSON.stringify(window.__QS__.snapshot().publishStatus)" 2>/dev/null
```

This shows counts of unpublished, changed, published, deleted files. If `unpublished` and `changed` are both 0, there's nothing to publish.

### Step 3: Test connection

```bash
obsidian eval code="(async()=>{const r=await window.__QS__.act({name:'connection.test'});console.log(JSON.stringify(r))})()" 2>/dev/null
```

Expected: `{"success":true,"data":{"readAccess":true,"writeAccess":true}}`. If write access is false, credentials may be wrong.

### Step 4: Publish

Publishing pushes over the network, so it will normally outlast the ~5–15 ms eval capture window and print **nothing**. Stash the result and read it back, rather than relying on the inline log:

```bash
obsidian eval code="window.__pub='pending';(async()=>{try{const r=await window.__QS__.act({name:'pub.publish',params:{message:'Test publish',confirm:true}});window.__pub=JSON.stringify({success:r.success,data:r.data,error:r.error})}catch(e){window.__pub=JSON.stringify({thrown:String(e)})}})()" 2>/dev/null
sleep 15
obsidian eval code="window.__pub" 2>/dev/null
```

Expected: `{"success":true,"data":{"filesPublished":N,"filesDeleted":0,...}}`. If success is false, check the error message. If it still reads `pending`, the publish has not finished — wait longer rather than re-running it.

The same applies to `connection.test` in Step 3: on a slow network it prints nothing. That is not a failure — stash and re-read it the same way.

### Step 5: Verify result

Refresh status again and confirm file counts changed:

```bash
obsidian eval code="(async()=>{const r=await window.__QS__.act({name:'status.refresh'});console.log(JSON.stringify({success:r.success,counts:{unpublished:r.data.unpublished.length,changed:r.data.changed.length,published:r.data.published.length,deleted:r.data.deleted.length}}))})()" 2>/dev/null
obsidian eval code="JSON.stringify(window.__QS__.snapshot().publishStatus)" 2>/dev/null
```

The `unpublished` and `changed` counts should have decreased (or be 0). The `published` count should have increased.

### Step 6: Check events

```bash
obsidian eval code="JSON.stringify(window.__QS__.events.tail(5))" 2>/dev/null
```

Look for `publish.completed` event with `commitSha` in the payload. This confirms the Git push succeeded.

### Step 7: Check for errors

```bash
obsidian dev:errors 2>/dev/null
obsidian dev:console level=error 2>/dev/null
```

No errors should be present.

## Dry-run Alternative

To test the pipeline without actually pushing to the remote:

```bash
obsidian quartz-syncer:publish dry-run 2>/dev/null
```

This uses the CLI dry-run flag which shows what would be published without making changes.

## MUST DO

- Always append `2>/dev/null` to all `obsidian` CLI commands.
- Always use the IIFE + `console.log` pattern for async facade calls.
- Always refresh status before and after publish — status is cached and must be explicitly refreshed.
- Always check the `success` field in action results — don't assume success.
- Always stash-and-re-read results for network operations (`pub.publish`, `connection.test`); their output does not survive the eval capture window.
- Always test connection before publish — avoids wasting time on auth failures.
- Check events after publish for the `commitSha` — this confirms the push actually reached the remote.

## MUST NOT DO

- Do NOT publish without checking `health.configured` first — it will fail with an unclear error.
- Do NOT skip the post-publish status refresh — without it, the snapshot still shows stale data.
- Do NOT run publish in rapid succession — wait for each operation to complete before starting another.
- Do NOT omit `2>/dev/null` — GTK warnings will pollute output.
- Do NOT use top-level `await` in eval — use the IIFE pattern.
- Do NOT re-run a publish because it printed nothing — confirm via `window.__pub`, `snapshot()`, or the event buffer first. Re-running risks a second commit.
