create-rigline-plugin 1.0.0-alpha.10 → 1.0.0-alpha.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -82,7 +82,7 @@ export function nextSteps(result) {
82
82
  "",
83
83
  "Then reload the webview: Developer: Reload Webviews.",
84
84
  "",
85
- `${join(here, "README.md")} has the rest, including the four rules worth reading first.`,
85
+ `${join(here, "README.md")} has the rest, including the rules worth reading first.`,
86
86
  "",
87
87
  "Read the plugin policy once — what Rigline promises Anthropic, and the part your plugin has",
88
88
  "to hold up. It applies whether or not you ever publish this:",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-rigline-plugin",
3
- "version": "1.0.0-alpha.10",
3
+ "version": "1.0.0-alpha.12",
4
4
  "description": "Scaffold a workspace for Claude Code VS Code extension plugins",
5
5
  "keywords": [
6
6
  "rigline",
@@ -32,7 +32,7 @@
32
32
  "create-rigline-plugin": "./dist/index.js"
33
33
  },
34
34
  "devDependencies": {
35
- "@rigline/plugin-api": "1.0.0-alpha.10"
35
+ "@rigline/plugin-api": "1.0.0-alpha.12"
36
36
  },
37
37
  "scripts": {
38
38
  "build": "node ../../scripts/clean-dist.mjs && tsc -p tsconfig.build.json",
@@ -37,6 +37,10 @@ which is the whole reason to declare rather than to reach.
37
37
  Put a dependency you can do without under `uses.optional`: it is checked the same way and costs the
38
38
  plugin that one decoration rather than the whole plugin.
39
39
 
40
+ `elements` is the other half: what the plugin contributes, each with the places it may go and where
41
+ it goes by default — or `null` for off. A place this extension version cannot provide costs that
42
+ element and nothing else, and `check` says so.
43
+
40
44
  ## The plugin policy
41
45
 
42
46
  **This applies whether or not you ever publish.** A plugin you wrote for yourself and will never
@@ -48,8 +52,8 @@ takes a minute.
48
52
  Rigline modifies Anthropic's extension and publishes a compliance position saying what it does and
49
53
  does not do. Your plugin runs inside that modification, so the position has to hold for it too.
50
54
 
51
- Most of it is not left to you — the webview has no network egress, no filesystem, and no way into
52
- the extension host, so the usual ways to do harm are absent rather than forbidden. What the policy
55
+ Most of it is not left to you — the webview has no network egress, no filesystem, and no way to run
56
+ code in the extension host, so the usual ways to do harm are absent rather than forbidden. What the policy
53
57
  asks is the part the architecture cannot cover: do not deceive the person using it, do not reach for
54
58
  credentials, do not carry conversation content off the machine by a path the closed network does not
55
59
  cover, and keep any host patch to switching on a capability the extension already has.
@@ -115,6 +119,8 @@ what you bumped.
115
119
  you placed.
116
120
  - **Ask what a container does about its children before decorating it.** The composer footer
117
121
  measures its own element children and re-measures on any foreign change inside it; footer
118
- decorations go beside `footerSpacer`, with `ctx.mountBefore`.
122
+ elements go before `footerSpacer`.
123
+ - **Keep what you place steady.** An element in the footer whose text keeps changing makes the
124
+ footer re-measure each time, and one in `rigRow` whose height keeps changing re-renders the panel.
119
125
  - **Do not poll for an element.** `ctx.watch(name, …)` hands it over when it appears and again when
120
126
  the app replaces it.
@@ -4,15 +4,16 @@ __DESCRIPTION__
4
4
 
5
5
  ## Depends on
6
6
 
7
- - `uses.anchors: ["footerSpacer"]` — the flexible gap dividing the composer footer's left cluster
8
- from its right, and the anchor every footer decoration uses. The footer measures the widths of its
9
- own element children to pick a fit stage; the spacer renders in every stage, so a decoration
10
- beside it contributes a constant width and the measurement settles.
11
- - `uses.mount` — `ctx.watch` and `ctx.mountBefore`, which place the badge and keep it placed across
12
- a re-render.
13
- - `uses.style` — one stylesheet, scoped to the class this plugin puts on its own element.
14
7
  - `uses.tools` — `ctx.onToolUse`, every completed tool call the assistant makes.
15
8
 
9
+ ## Contributes
10
+
11
+ - `elements.badge` — the tool-call count, before `footerSpacer` by default: the flexible gap
12
+ dividing the composer footer's left cluster from its right, and the place footer elements go. The
13
+ footer measures the widths of its own element children to pick a fit stage; the spacer renders in
14
+ every stage, so an element beside it contributes a constant width and the measurement settles. It
15
+ may also go in `rigRow`, the row under the composer's controls.
16
+
16
17
  ## Notes
17
18
 
18
19
  `badgeText` is exported and tested because it is the half a plain test run can hold. Everything
@@ -13,6 +13,9 @@
13
13
  },
14
14
  "devDependencies": {
15
15
  "@rigline/core": "__RIGLINE_RANGE__",
16
- "@rigline/plugin-api": "__RIGLINE_RANGE__"
16
+ "@rigline/plugin-api": "__RIGLINE_RANGE__",
17
+ "@types/react": "^19.3.0",
18
+ "react": "^19.3.0",
19
+ "react-dom": "^19.3.0"
17
20
  }
18
21
  }
@@ -6,9 +6,13 @@
6
6
  "entry": "dist/index.js",
7
7
  "surfaces": ["editor", "sidebar"],
8
8
  "uses": {
9
- "anchors": ["footerSpacer"],
10
- "mount": true,
11
- "style": true,
12
9
  "tools": true
10
+ },
11
+ "elements": {
12
+ "badge": {
13
+ "title": "Tool calls",
14
+ "placements": [{ "anchor": "footerSpacer", "at": "before" }, "rigRow"],
15
+ "default": { "anchor": "footerSpacer", "at": "before" }
16
+ }
13
17
  }
14
18
  }
@@ -7,7 +7,7 @@
7
7
  * and only the app can answer it: `pnpm build`, `pnpm rigline add`, reload the webview, look.
8
8
  */
9
9
  import { describe, expect, it } from "vitest";
10
- import { badgeText } from "./index.ts";
10
+ import { badgeText } from "./index.tsx";
11
11
 
12
12
  describe("badgeText", () => {
13
13
  it("says something before anything has happened", () => {
@@ -0,0 +1,64 @@
1
+ /**
2
+ * __DESCRIPTION__
3
+ *
4
+ * A worked example of the loop every plugin is: declare what you contribute and depend on in
5
+ * `rigline.json`, keep what you learn in a store, and render it from a component. Replace the body;
6
+ * keep the shape.
7
+ */
8
+ import {
9
+ definePlugin,
10
+ type PluginContext,
11
+ type Store,
12
+ store,
13
+ type Teardown,
14
+ } from "@rigline/plugin-api";
15
+ import { Pill, useStore } from "@rigline/plugin-api/ui";
16
+ import type { ReactNode } from "react";
17
+
18
+ /**
19
+ * What the badge reads, for a given number of tool calls.
20
+ *
21
+ * Pure, and exported, because this is the half a plain `vitest` run can hold: anything that renders
22
+ * wants the app itself, and anything that does not should not need it. See `src/index.test.ts`.
23
+ */
24
+ export function badgeText(calls: number): string {
25
+ if (calls === 0) return "no tools yet";
26
+ return `${calls} tool ${calls === 1 ? "call" : "calls"}`;
27
+ }
28
+
29
+ function Badge(props: { readonly calls: Store<number> }): ReactNode {
30
+ return <Pill>{badgeText(useStore(props.calls))}</Pill>;
31
+ }
32
+
33
+ export default definePlugin({
34
+ setup(ctx: PluginContext): Teardown {
35
+ // A store, made here rather than in the component, because state caught in `setup` is caught
36
+ // from the moment the plugin loads, and the badge and anything else can share it.
37
+ const calls = store(0);
38
+
39
+ // Every completed tool call the assistant makes. `uses.tools` is what makes this fire; without
40
+ // the declaration it throws and disables the plugin, which is the point of declaring.
41
+ const stopTools = ctx.onToolUse(() => calls.set(calls.get() + 1));
42
+
43
+ // `badge` is declared under `elements` in rigline.json, which says where it goes by default:
44
+ // before `footerSpacer`, at the end of the composer footer's left cluster. Rigline places it,
45
+ // keeps it placed, and moves it if the user asks for it somewhere else.
46
+ const stopBadge = ctx.element("badge", () => <Badge calls={calls} />);
47
+
48
+ // One line in Rigline's diagnostics panel, under this plugin's name. Whether the badge is on
49
+ // screen is Rigline's to say, and it does; ask what only your own state can answer, and say
50
+ // `n/a` with a reason when there is nothing to report yet. The host runs this about once a
51
+ // second, so read state you already keep rather than computing anything here.
52
+ const stopCheck = ctx.check("tool calls observed", () =>
53
+ calls.get() === 0
54
+ ? { verdict: "n/a", detail: "none yet" }
55
+ : { verdict: "pass", detail: badgeText(calls.get()) },
56
+ );
57
+
58
+ return () => {
59
+ stopCheck();
60
+ stopBadge();
61
+ stopTools();
62
+ };
63
+ },
64
+ });
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "extends": "../../tsconfig.plugin.json",
3
- "include": ["src/**/*.ts"],
3
+ "include": ["src/**/*.ts", "src/**/*.tsx"],
4
4
  "exclude": ["src/**/*.test.ts"]
5
5
  }
@@ -5,7 +5,8 @@
5
5
  "lib": ["esnext", "dom"],
6
6
  "types": [],
7
7
  "noEmit": true,
8
- "allowImportingTsExtensions": true
8
+ "allowImportingTsExtensions": true,
9
+ "jsx": "react-jsx"
9
10
  },
10
11
  "files": ["generated.ts"]
11
12
  }
@@ -1,82 +0,0 @@
1
- /**
2
- * __DESCRIPTION__
3
- *
4
- * A worked example of the loop every plugin is: declare what you depend on in `rigline.json`, wait
5
- * to be handed the element you decorate, and put something beside it. Replace the body; keep the
6
- * shape.
7
- */
8
- import { definePlugin, type PluginContext, type Teardown } from "@rigline/plugin-api";
9
-
10
- /**
11
- * What the badge reads, for a given number of tool calls.
12
- *
13
- * Pure, and exported, because this is the half a plain `vitest` run can hold: anything that touches
14
- * the DOM wants the app itself, and anything that does not should not need it. See
15
- * `src/index.test.ts`.
16
- */
17
- export function badgeText(calls: number): string {
18
- if (calls === 0) return "no tools yet";
19
- return `${calls} tool ${calls === 1 ? "call" : "calls"}`;
20
- }
21
-
22
- export default definePlugin({
23
- setup(ctx: PluginContext): Teardown {
24
- let calls = 0;
25
- const badge = document.createElement("span");
26
- badge.className = "example-badge";
27
- badge.textContent = badgeText(calls);
28
-
29
- // Scoped to a class this plugin put on its own element. Never scope a rule to an anchor's bare
30
- // class: one class is applied wherever that look is wanted, so a rule written against it lands
31
- // on every control wearing it. See the anchors guide.
32
- const stopStyle = ctx.style(`
33
- .example-badge {
34
- font-size: 11px;
35
- opacity: 0.7;
36
- padding: 0 6px;
37
- white-space: nowrap;
38
- }
39
- `);
40
-
41
- // Every completed tool call the assistant makes. `uses.tools` is what makes this fire; without
42
- // the declaration it throws and disables the plugin, which is the point of declaring.
43
- const stopTools = ctx.onToolUse(() => {
44
- calls += 1;
45
- badge.textContent = badgeText(calls);
46
- });
47
-
48
- // `watch` hands over the element for an anchor whenever one is in the document, and again if
49
- // the app replaces it. No plugin polls for an element.
50
- //
51
- // `footerSpacer` and `mountBefore` together, rather than any other footer anchor: the composer
52
- // footer measures the widths of its own element children to pick a fit stage, and resets that
53
- // measurement on any foreign change inside it. A decoration whose membership of the footer
54
- // changes with the stage fights the ladder that moved it. The spacer renders in every stage, so
55
- // a decoration beside it contributes a constant width and the ladder settles.
56
- const stopWatch = ctx.watch("footerSpacer", (spacer) => ctx.mountBefore(spacer, () => badge));
57
-
58
- // One line in Rigline's diagnostics panel, under this plugin's name. It declares nothing: the
59
- // host calls it, hands it nothing, and gets a verdict back.
60
- //
61
- // Worth the four lines from the first day, because the failure a plugin has is normally silent.
62
- // An extension update can leave this one loaded, declared, styled and drawing nothing, and
63
- // every other line in that panel will say it is fine. Ask the question only your own state can
64
- // answer — here, whether the badge is actually in the document — and say `n/a` with a reason
65
- // when there is nothing to report yet.
66
- //
67
- // The host runs this about once a second for the life of the window, so read state you already
68
- // keep. Do not walk the DOM or recompute an answer here; do that work where it already happens.
69
- const stopCheck = ctx.check("badge is mounted", () =>
70
- badge.isConnected
71
- ? { verdict: "pass", detail: badgeText(calls) }
72
- : { verdict: "fail", detail: "the badge is not in the document" },
73
- );
74
-
75
- return () => {
76
- stopCheck();
77
- stopWatch();
78
- stopTools();
79
- stopStyle();
80
- };
81
- },
82
- });