---
name: verify-changes
description: Post-change verification workflow — build, reload plugin in Obsidian, run health checks, and verify no regressions via the operability facade.
triggers:
    - verify
    - check changes
    - test in obsidian
    - reload plugin
    - does it work
argument-hint: '[file-or-area-changed]'
---

# Verify Changes Skill

## Purpose

After modifying quartz-syncer source code, build the plugin, reload it in a running Obsidian instance, and verify it works correctly using the operability facade (`window.__QS__`). This replaces manual "open Obsidian and click around" verification.

## When to Activate

Activate after any code change that affects runtime behavior — source files in `src/`, settings, CLI handlers, views, services. NOT needed for test-only changes or documentation.

## 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).
- The Obsidian CLI must be registered and working (`obsidian eval code="1+1" 2>/dev/null` should return `=> 2`).
- Console capture requires the debugger: run `obsidian dev:debug on 2>/dev/null` once per Obsidian session before using `dev:console`.

## Important: CLI Patterns

**Suppress GTK warnings.** Always append `2>/dev/null` to every `obsidian` CLI command. Linux produces GTK/Electron warnings that clutter output.

**Async eval needs the IIFE + `console.log` pattern.** Top-level `await` produces no output:
```bash
# WRONG — returns nothing:
obsidian eval code="await window.__QS__.act({name:'pub.open'})" 2>/dev/null

# CORRECT — prints the result:
obsidian eval code="(async()=>{const r=await window.__QS__.act({name:'pub.open'});console.log(JSON.stringify(r))})()" 2>/dev/null
```

Synchronous calls return values directly:
```bash
obsidian eval code="typeof window.__QS__" 2>/dev/null
# => object
```

**`obsidian eval` only captures output emitted within ~5–15 ms.** The limit is duration, not asynchrony: microtasks and sub-frame timers resolve in time, real waits do not. Output logged after `eval` returns is silently lost — an empty result, not an error.

Actions that do real work may or may not print depending on cache warmth, so **never read missing output as failure.** Confirm with a follow-up synchronous query instead:
```bash
obsidian eval code="(async()=>{const r=await window.__QS__.act({name:'pub.open'});console.log(JSON.stringify(r))})()" 2>/dev/null
sleep 2
obsidian dev:dom selector='[data-qs="pub-center"]' total 2>/dev/null   # this is the real check
```

For anything that deliberately waits, stash the result and read it back synchronously:
```bash
obsidian eval code="window.__probe='pending';(async()=>{/* ...slow work... */ window.__probe=JSON.stringify(result)})()" 2>/dev/null
sleep 5
obsidian eval code="window.__probe" 2>/dev/null
```

**`JSON.stringify` throws on `status.refresh`.** Its `data` is a live `PublishStatus` holding `PublishFile` objects with circular references. Log a projection, not the whole result. `snapshot()` is always safe to stringify.

## Workflow

### Step 1: Build and deploy to test vault

```bash
npm run build:dev
```

This builds with `__DEV__=true` (facade mounts automatically), includes sourcemaps, and copies `main.js` to `test-vault/.obsidian/plugins/quartz-syncer/`. If the build fails, fix the build error first.

Do NOT use `npm run build` (production) for verification — it does not copy to the test vault and strips the `__DEV__` flag.

### Step 2: Reload plugin

Build alone does NOT update the running instance. You must reload:

```bash
obsidian eval code="(async()=>{await app.plugins.disablePlugin('quartz-syncer');await new Promise(r=>setTimeout(r,1000));await app.plugins.enablePlugin('quartz-syncer')})()" 2>/dev/null
```

Wait 3 seconds after this command, then verify the plugin loaded:
```bash
sleep 3 && obsidian eval code="typeof window.__QS__" 2>/dev/null
```
Expected: `=> object`. If `=> undefined`, the facade didn't mount — check that the dev build was used.

### Step 3: Health check

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

Expected: `{"pass":true,"details":{...}}`. If `pass` is `false`, the plugin failed to initialize — check `details` for the reason.

### Step 4: Snapshot

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

Inspect the snapshot for anomalies:
- `plugin.loaded` should be `true`
- `engine.running` should be `true`
- `statusBar.state` should be `"ready"` or `"compiling"` (not `"error"`)
- `errors.count` should be `0`

### Step 5: Check for errors

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

If errors are present, investigate. Use the event buffer for context:

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

### Step 6: Area-specific verification

Based on what was changed:

**Compiler/frontmatter changes:** Refresh status and check file counts.
```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
```

**UI/view changes:** Open the affected modal, verify DOM, take screenshot.
```bash
obsidian eval code="(async()=>{const r=await window.__QS__.act({name:'pub.open'});console.log(JSON.stringify(r))})()" 2>/dev/null
sleep 2
obsidian dev:dom selector='[data-qs="pub-center"]' total 2>/dev/null
obsidian dev:screenshot path=/tmp/verify.png 2>/dev/null
```

**Settings changes:** Verify settings are readable.
```bash
obsidian eval code="(async()=>{const r=await window.__QS__.act({name:'settings.get',params:{key:'gitRemoteUrl'}});console.log(JSON.stringify(r))})()" 2>/dev/null
```

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

**Quartz Hub changes:** Open the Hub and verify.
```bash
obsidian command id=quartz-syncer:open-hub 2>/dev/null
sleep 2
obsidian dev:dom selector='[data-qs="hub"]' total 2>/dev/null
obsidian dev:dom selector='[data-qs="hub-action"]' total 2>/dev/null
obsidian dev:screenshot path=/tmp/hub-verify.png 2>/dev/null
```

## MUST DO

- Always append `2>/dev/null` to all `obsidian` CLI commands.
- Always build AND reload — build alone doesn't update the running instance.
- Always health-check after reload — a successful build doesn't guarantee successful initialization.
- Always use the IIFE pattern for async facade calls.
- Always wait after operations (3s after reload, 2s after modal open).
- Always verify actions took effect — follow up a click with a DOM query or screenshot.
- Parse all JSON responses — don't assume success. Check `pass` or `success` fields.
- If health check fails, collect a failure bundle before attempting fixes.

## MUST NOT DO

- Do NOT omit `2>/dev/null` — GTK warnings will pollute output parsing.
- Do NOT skip the reload step — Obsidian caches the old plugin code until disabled/enabled.
- Do NOT assume the plugin loaded correctly just because the build succeeded.
- Do NOT use `await` at the top level of `obsidian eval` — it produces no output. Use the IIFE pattern.
- Do NOT treat missing eval output as failure — it usually means the work outlasted the ~5–15 ms capture window. Verify with a synchronous follow-up query.
- Do NOT set input `.value` without `dispatchEvent(new Event('input', { bubbles: true }))` — event listeners won't fire.
- Do NOT modify code and re-verify without rebuilding AND reloading.

## Failure Bundle

When something goes wrong, collect all diagnostic data before investigating:

```bash
obsidian eval code="JSON.stringify(window.__QS__.snapshot())" 2>/dev/null
obsidian eval code="JSON.stringify(window.__QS__.events.tail(20))" 2>/dev/null
obsidian dev:errors 2>/dev/null
obsidian dev:console level=error 2>/dev/null
obsidian dev:screenshot path=/tmp/failure.png 2>/dev/null
```
