Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ honest. Report any part Weaver does not support as a boundary.
2. Read the relevant current sections under **Contract routing** before choosing
elements, hooks, providers, classes, assets, network access, or capabilities.
When the request uses interaction, changing time, provider data, or replay,
read [`docs/agent-widget-capture.md`](../../docs/agent-widget-capture.md) now.
read [`docs/agent-widget-capture.md`](../../../docs/agent-widget-capture.md) now.
Define stable capture inputs and a name for each state the request needs so
every pass renders the same evidence.
3. Start the **Live loop** before the first source edit. Run
Expand Down Expand Up @@ -93,7 +93,7 @@ For each defined state:

## Contract routing

[`sdk/CONTRACT.md`](../../sdk/CONTRACT.md) is authoritative and chronological;
[`sdk/CONTRACT.md`](../../../sdk/CONTRACT.md) is authoritative and chronological;
later amendments supersede earlier scheduling notes. Read only the headings the
widget needs:

Expand All @@ -112,6 +112,26 @@ widget needs:
a number only for an intentional fixed cadence, and `0` once animation
settles.

Read the shipped example closest to the request before writing the first
element; they are the house style and each one proves one slice of the
contract on the current runtime:

- [`examples/clock`](../../../examples/clock/widget.tsx): `time` provider,
layered gradient surface, a canvas that redraws once per render.
- [`examples/system`](../../../examples/system/widget.tsx): `cpu` and `memory`
providers, provider history in an effect, canvas charts with explicit sizes.
- [`examples/pomodoro`](../../../examples/pomodoro/widget.tsx): `useStorage`,
`useInterval`, buttons with `hover:`/`pressed:` states, centered labels.
- [`examples/now-playing`](../../../examples/now-playing/widget.tsx): `media`
provider, conditional artwork, transport capability, click-to-seek.
- [`examples/weather`](../../../examples/weather/widget.tsx): declared
`origins`, `wfetch` with honest loading and failure states, literal icons
chosen per branch.
- [`examples/noro-shell`](../../../examples/noro-shell/widget.tsx) and
[`examples/visualizer`](../../../examples/visualizer/widget.tsx): bundled
fonts, tiled image assets, and an `audio` signal driving a display-rate
canvas.

Use `weaver check` as the final authority for statically knowable widget errors.
Do not infer browser DOM, CSS, package, or network behavior that the contract
does not provide.
Expand Down
147 changes: 147 additions & 0 deletions .claude/skills/conjure-widget/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
---
name: conjure-widget
description: Create or change a Weaver desktop widget, then prove its code, pixels, semantics, and requested interactions. Use for widget authoring requests; framework implementation belongs to Weaver maintainer workflows.
---

# Conjure a Weaver widget

Turn the request into one checked and captured widget while the user watches it
take shape on the desktop. Run the CLI as `weaver …`; it is on `PATH` after
`npm run link` in the Weaver repository. Without that link, `npx --no-install
weaver …` works only from inside the repository tree, and from anywhere else
fails with an unrelated npm message about a missing `weaver` package. Start the **Live loop** before the first source edit.
Its audience is the user. It keeps `weaver dev` running in the background so
each valid save can appear while the next edit is underway. Use the **Render
loop** for the agent's deterministic inspection and interaction proof. Get the
first coherent tree on screen early, then inspect each capture while changing
the code. Preserve the requested visual and interaction intent. Keep the widget
honest. Report any part Weaver does not support as a boundary.

## Workflow

1. Inspect the target before editing. For a new widget, run
`weaver init <path>` from the Weaver repository root; the
final path segment becomes the starter display name. For an existing widget,
read its `widget.tsx`, local modules, assets, and licenses without running
`init` over it.
2. Read the relevant current sections under **Contract routing** before choosing
elements, hooks, providers, classes, assets, network access, or capabilities.
When the request uses interaction, changing time, provider data, or replay,
read [`docs/agent-widget-capture.md`](../../../docs/agent-widget-capture.md) now.
Define stable capture inputs and a name for each state the request needs so
every pass renders the same evidence.
3. Start the **Live loop** before the first source edit. Run
`weaver dev <path>` in a long-lived background session and
keep its output available and its widget visible to the user. Return to
authoring once the command reports that it is watching. Keep observing the
Live loop for the next 10 seconds while authoring continues. Report the
user-facing live view as available only if that interval ends without a
`weaver dev ERROR` presentation-health diagnostic. Rebuilds and hot swaps can
finish while later edits continue. If the current platform cannot run
Weaver's desktop host or presentation health fails, record the exact failure,
use the Render loop, and report the user-facing live view as unavailable.
4. Build the first coherent visual slice in `<path>/widget.tsx` with one literal
default export:
`export default widget({ ... }, () => <... />);`. Import Weaver APIs from
`@weaver/sdk`; keep other modules, assets, and their licenses inside the
widget source root.
5. Enter the **Render loop** as soon as that slice can render, then after each
added visual region (header, then each content block, then the controls and
every requested state), and after any edit that changes semantics or
interaction behavior. Each pass is a step the user can watch land. When the
user is not watching the desktop, one pass after the last edit is enough.
The final pass is never optional: no widget is complete until its last PNG
has been opened and inspected and its snapshot read. Fix what contradicts
the request or the contract. When a capture shows renderer behavior that
contradicts the contract, keep the requested design and report a framework
reproduction (see **Framework failures**); do not redesign the request
around the surprise.
6. After the final source save, keep the **Live loop** running until it reports
that save through `weaver dev bundle ready for in-place hot swap` or
`weaver dev restarted widget: window config changed`. An `OUT OF DATE`
message keeps this step open until the source is fixed and `weaver dev`
reports that it caught up. Then keep the process running when the user wants
the final widget left open. Otherwise, stop it. Use Render loop artifacts for
every visual, semantic, and interaction receipt.
7. Report the widget path, captured images, behavior exercised, receipt evidence,
whether the user-facing live view ran or its exact failure, and each remaining
boundary. Completion requires the requested result, not merely successful
commands.

## Render loop

For each defined state:

1. Run `weaver check <path>` until it exits successfully. Fix
every named widget error. Preserve unsupported intent as a reported boundary
rather than suppressing unknown utilities, undeclared providers or origins,
invalid assets, or import failures.
2. Run `weaver capture <path> --out <capture-name>.png` with the
state's fixed clock, semantic actions, provider fixture, or session journal.
3. Open the PNG with the available image-viewing tool. File creation and a green
receipt are not visual proof. Inspect layout, overlap, clipping, duplication,
spacing, alignment, contrast, assets, and the requested state at the actual
widget dimensions. Secondary text on the dark house surface needs at least
`/45` opacity to read at 1×; dimmer text is the most common thing a first
capture reveals.
4. Inspect `<capture-name>.snapshot.txt` and `<capture-name>.receipt.json`. The
receipt must have `status: "ok"`; the semantic tree must expose the intended
content and controls; every warning and pending item must be understood.
5. Compare the rendered state with the request. Fix the visible and semantic
mismatches found in that pass, then restart the loop. For interactions, prove
both the initial state and every requested post-action state.

## Contract routing

[`sdk/CONTRACT.md`](../../../sdk/CONTRACT.md) is authoritative and chronological;
later amendments supersede earlier scheduling notes. Read only the headings the
widget needs:

- For module shape, literal config, hooks, and reload behavior, read **Module
shape**, **`widget(config, component)`**, **Hooks**, and **Hot swap**.
- For the current element and class set, read **Consolidated v0.4 authoring
tables**. Read **Bundled fonts**, **Icons**, and the matching styling amendment
when those branches apply.
- For buttons, sliders, press coordinates, or interaction styles, read **PR 11:
native interaction states and press events**. Use native `hover:` and
`pressed:` classes for visual feedback instead of rendering pointer state
through JavaScript.
- For fetch, storage, CPU, or memory, read the matching M2 heading. For canvas,
audio, media observation, artwork, or transport, read the matching M3 or
**Media v2 amendment** heading. Use `fps="display"` for fluid canvas motion,
a number only for an intentional fixed cadence, and `0` once animation
settles.

Read the shipped example closest to the request before writing the first
element; they are the house style and each one proves one slice of the
contract on the current runtime:

- [`examples/clock`](../../../examples/clock/widget.tsx): `time` provider,
layered gradient surface, a canvas that redraws once per render.
- [`examples/system`](../../../examples/system/widget.tsx): `cpu` and `memory`
providers, provider history in an effect, canvas charts with explicit sizes.
- [`examples/pomodoro`](../../../examples/pomodoro/widget.tsx): `useStorage`,
`useInterval`, buttons with `hover:`/`pressed:` states, centered labels.
- [`examples/now-playing`](../../../examples/now-playing/widget.tsx): `media`
provider, conditional artwork, transport capability, click-to-seek.
- [`examples/weather`](../../../examples/weather/widget.tsx): declared
`origins`, `wfetch` with honest loading and failure states, literal icons
chosen per branch.
- [`examples/noro-shell`](../../../examples/noro-shell/widget.tsx) and
[`examples/visualizer`](../../../examples/visualizer/widget.tsx): bundled
fonts, tiled image assets, and an `audio` signal driving a display-rate
canvas.

Use `weaver check` as the final authority for statically knowable widget errors.
Do not infer browser DOM, CSS, package, or network behavior that the contract
does not provide.

## Framework failures

A documented unsupported behavior is a widget boundary. A minimized supported
widget that still fails is a framework reproduction. So is invalid widget input
that produces an opaque or internal error instead of an actionable diagnostic.
Preserve the widget, exact command, complete output, and platform. Inside the
Weaver source checkout, follow the root instructions for framework friction.
Outside it, report the reproduction and blocker without weakening the widget or
claiming completion.
4 changes: 4 additions & 0 deletions .claude/skills/conjure-widget/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Conjure Widget"
short_description: "Create or change a Weaver desktop widget"
default_prompt: "Use $conjure-widget to make me a desktop widget."
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ Thumbs.db
# Syncthing
.stfolder/
.stignore

# Agent worktrees
.claude/worktrees/
27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,17 +217,40 @@ See [`macos-m12-results.md`](docs/macos-m12-results.md) and the live
blockers.

Or do it the intended way: point your coding agent at
[`skills/conjure-widget/SKILL.md`](skills/conjure-widget/SKILL.md) and ask it
[`.agents/skills/conjure-widget/SKILL.md`](.agents/skills/conjure-widget/SKILL.md) and ask it
for the widget you actually want.

## Examples

Each directory under [`examples/`](examples) is one complete widget that
checks and captures on the current runtime. They are the house style, and the
first thing an agent should read before conjuring something similar.

| Example | Shows |
|---|---|
| [`clock`](examples/clock/widget.tsx) | the `time` provider, a layered gradient surface, a canvas dial that redraws once per render |
| [`system`](examples/system/widget.tsx) | `cpu` and `memory` providers, a minute of history kept in an effect, canvas charts |
| [`pomodoro`](examples/pomodoro/widget.tsx) | `useStorage`, `useInterval`, buttons with native `hover:` and `pressed:` states |
| [`now-playing`](examples/now-playing/widget.tsx) | the `media` provider, conditional artwork, `media-transport`, click-to-seek |
| [`weather`](examples/weather/widget.tsx) | declared `origins` and `wfetch` against Open-Meteo, with honest loading and failure states |
| [`noro-shell`](examples/noro-shell/widget.tsx), [`noro-signal`](examples/noro-signal/widget.tsx) | pixel-faithful media skins: bundled fonts, tiled image assets, transport |
| [`visualizer`](examples/visualizer/widget.tsx) | the `audio` signal driving a display-rate canvas that sleeps on silence |

Render any of them without a desktop session:

```sh
weaver capture examples/system \
--provider-fixture test/capture/system.provider.json --out /tmp/system.png
```

## How it's put together

| Path | What |
|---|---|
| `runtime/` | `weaver-widget[.exe]` — Zig, embeds QuickJS-NG, renders via the Native SDK fork (submodule `runtime/native-sdk`) |
| `sdk/` | `@weaver/sdk` — the authoring API: reconciler, hooks, class compiler. Contract frozen in [`sdk/CONTRACT.md`](sdk/CONTRACT.md) |
| `cli/` | `weaver` — init / check / bundle / capture / dev / pack / inspect / install / uninstall / logs |
| `skills/` | agent skills (conjuring is the primary authoring path) |
| `.agents/skills/` | agent skills, mirrored in `.claude/skills/` (conjuring is the primary authoring path) |
| `docs/adr/` | why things are the way they are — start here to understand the project |
| `CONTEXT.md` | the domain glossary |

Expand Down
37 changes: 21 additions & 16 deletions cli/test/example-surface-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,34 @@ import { fileURLToPath } from "node:url";

const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
const cli = join(repoRoot, "cli", "bin", "weaver.js");
const fixtures = [
"clock",
"pomodoro",
"system",
"now-playing",
"noro-shell",
"visualizer",
"dpi-diagnostic",
"m4b-parity",
"m4b-synthetic",
// Every shipped example plus the measurement fixtures that scripts/ and docs/
// still drive. Each must check and bundle on a clean checkout.
const surfaces = [
"examples/clock",
"examples/system",
"examples/pomodoro",
"examples/now-playing",
"examples/weather",
"examples/noro-shell",
"examples/noro-signal",
"examples/visualizer",
"test/fixtures/dpi-diagnostic",
"test/fixtures/m4b-parity",
"test/fixtures/m4b-synthetic",
"test/fixtures/gradient-stack",
];

for (const fixture of fixtures) {
const source = join(repoRoot, "examples", fixture);
for (const surface of surfaces) {
const source = join(repoRoot, surface);
const dist = join(source, "dist");
const distExisted = existsSync(dist);
for (const command of ["check", "bundle"]) {
const result = spawnSync(process.execPath, [cli, command, source], { cwd: repoRoot, encoding: "utf8" });
assert.equal(result.status, 0, `${command} failed for ${fixture}\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`);
assert.equal(result.status, 0, `${command} failed for ${surface}\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`);
}
assert.equal(existsSync(join(dist, "bundle.js")), true, `${fixture} bundle is missing`);
assert.equal(existsSync(join(dist, "widget.json")), true, `${fixture} manifest is missing`);
assert.equal(existsSync(join(dist, "bundle.js")), true, `${surface} bundle is missing`);
assert.equal(existsSync(join(dist, "widget.json")), true, `${surface} manifest is missing`);
if (!distExisted) rmSync(dist, { recursive: true, force: true });
}

process.stdout.write(`Checked and bundled ${fixtures.length} portable example surfaces.\n`);
process.stdout.write(`Checked and bundled ${surfaces.length} portable widget surfaces.\n`);
2 changes: 1 addition & 1 deletion docs/agent-widget-capture.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ node cli/bin/weaver.js capture examples/pomodoro \
--action-file test/capture/pomodoro.actions \
--out /tmp/pomodoro.png

node cli/bin/weaver.js capture examples/styling-interaction \
node cli/bin/weaver.js capture test/fixtures/styling-interaction \
--action-file test/capture/styling-interaction.actions \
--out /tmp/styling-interaction.png
```
Expand Down
2 changes: 1 addition & 1 deletion docs/dpi-scaling.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ clean version-mismatch failure instead of framing deadlock.

## Deterministic and live verification

`examples/dpi-diagnostic` is a transparent 480 x 320 DIP fixture with a
`test/fixtures/dpi-diagnostic` is a transparent 480 x 320 DIP fixture with a
retained card, immediate canvas, four colored edge markers and corners, content
crossing the retained/immediate seam, and right/bottom clickable targets.
`scripts/verify-dpi.ps1` launches the real host, widget runtime, named pipe,
Expand Down
2 changes: 1 addition & 1 deletion docs/media-v2-results.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ The Windows slice is complete through the noro gate:
- Existing previous, play/pause, and next buttons deliver real SMTC commands.
- The former static 312x3 progress stack is a pixel-matched click-to-seek
button using the press event's normalized local `event.u`.
- `skills/conjure-widget/SKILL.md` teaches `MediaData.artPath`,
- `.agents/skills/conjure-widget/SKILL.md` teaches `MediaData.artPath`,
`useMediaTransport`, the `media-transport` capability, and promise semantics.

The full viewed visual checklist and capture inventory are in
Expand Down
52 changes: 0 additions & 52 deletions examples/clock/bundle.js

This file was deleted.

8 changes: 0 additions & 8 deletions examples/clock/widget.json

This file was deleted.

Loading
Loading