wings-plugin-sdk 0.5.0 → 0.7.0

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/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # wings-plugin-sdk
2
2
 
3
- Types, a React hook and a build command for [Wings](https://github.com/josippapez/Wings) plugins.
3
+ The `wings` API types, the page frame, React hooks and the build command for [Wings](https://github.com/josippapez/Wings)
4
+ plugins. The components for plugin pages are in [wings-plugin-ui](../plugin-ui/README.md).
4
5
 
5
6
  Wings itself gives each plugin the `wings` object at runtime. This package doesn't contain it: it types it, and
6
7
  bundles your TypeScript and React into the plain scripts Wings loads.
@@ -9,7 +10,7 @@ bundles your TypeScript and React into the plain scripts Wings loads.
9
10
 
10
11
  ```sh
11
12
  npm install --save-dev wings-plugin-sdk typescript
12
- npm install --save-dev react react-dom @types/react @types/react-dom # only for React pages
13
+ npm install --save-dev wings-plugin-ui react react-dom @types/react @types/react-dom # only for React pages
13
14
  ```
14
15
 
15
16
  `tsconfig.json`:
@@ -33,7 +34,7 @@ npm install --save-dev react react-dom @types/react @types/react-dom # only fo
33
34
 
34
35
  Put the code in `src/`, next to `wings-plugin.json`. Every file at the top of `src/` builds to the file of the
35
36
  same name, so `src/main.ts` becomes `main.js` and `src/sidebar.tsx` becomes `sidebar.js`. A page that uses
36
- `wings-plugin-sdk/ui` also gets its `.html` written for it, unless you wrote your own. Folders inside `src/` hold
37
+ `wings-plugin-ui` also gets its `.html` written for it, unless you wrote your own. Folders inside `src/` hold
37
38
  the modules they import.
38
39
 
39
40
  ```sh
@@ -47,11 +48,19 @@ as it is, like the plugins in the Wings repo.
47
48
 
48
49
  ## Pages
49
50
 
50
- A sidebar, popover, view, tab or settings page is a file in `src/` that calls one of the `show*` functions in
51
- [UI](#ui). The build writes its HTML. Every `wings.on*` call returns a function that removes the listener, so
52
- `useBroadcast` and `useMainState`, or your own `useEffect`, clean up when a component goes. The API reference is
51
+ A sidebar, popover, view, tab or settings page is a file in `src/` that calls one of the `show*` functions of
52
+ [wings-plugin-ui](../plugin-ui/README.md). The build writes its HTML. A page that builds its DOM itself, without
53
+ React, gets the same frame from `mountShell(place, options)` in `wings-plugin-sdk/shell`, which returns the element
54
+ to fill. Every `wings.on*` call returns a function that removes the listener, so `useBroadcast` from
55
+ `wings-plugin-sdk/react`, or your own `useEffect`, cleans up when a component goes. The API reference is
53
56
  [plugins/README.md](https://github.com/josippapez/Wings/blob/main/plugins/README.md) in the Wings repo.
54
57
 
58
+ ## Polling
59
+
60
+ Wings is often in the background, and a timer that runs `git` or a network call then costs battery for nothing.
61
+ `pollWhileFocused(tick, ms)` from `wings-plugin-sdk/poll` runs `tick` every `ms` only while a Wings window is in
62
+ front, and once at once when you come back. It uses `wings.onFocus`.
63
+
55
64
  ## Pack
56
65
 
57
66
  A plugin in its own repo doesn't need to commit what it builds. `wings-plugin pack` builds it into a temporary
@@ -67,178 +76,3 @@ files your plugin ships, like its own `.html`, `.css` and images. It leaves out
67
76
  `package.json` and lock files, `tsconfig*.json`, `README*`, `HANDOVER*`, dotfiles and other `.wings-plugin` files.
68
77
  Your folder gets no built files. From a script, `pack(dir, { out })` in `wings-plugin-sdk/build` does the same and
69
78
  resolves the package's path.
70
-
71
- ## UI
72
-
73
- `wings-plugin-sdk/ui` lets a page say what it shows and where, and Wings decides how it looks. A page built from
74
- it never touches the DOM, writes HTML or lays itself out with CSS, so when Wings moves or redesigns a place,
75
- your page follows without a release.
76
-
77
- ```tsx
78
- import { Card, Gauge, Heading, showSidebar, useMainState } from "wings-plugin-sdk/ui";
79
-
80
- type Usage = { type: "usage"; percent: number };
81
-
82
- function Usage() {
83
- // Says hello to your main script once, then shows the newest `usage` message it broadcasts.
84
- const usage = useMainState<Usage>("usage");
85
- if (!usage) return null;
86
- return (
87
- <>
88
- <Heading>Usage</Heading>
89
- <Card>
90
- <Gauge label="Today" percent={usage.percent} levels={[75, 90]} />
91
- </Card>
92
- </>
93
- );
94
- }
95
-
96
- showSidebar(<Usage />);
97
- ```
98
-
99
- ### Places
100
-
101
- `showSidebar`, `showPopover`, `showView`, `showTab` and `showSettings` take any React element, your own
102
- components too, and mount it in that place's shell: Wings' padding, spacing and scrolling for a sidebar, a
103
- popover, a view (in a modal or a window), a tab or the settings page. Call one per page. `{ fill: true }` lays
104
- the page out edge to edge with no padding, for a page that scrolls on its own, like a canvas.
105
-
106
- You don't write the page's HTML. For `src/sidebar.tsx` that calls one of these, `wings-plugin build` writes
107
- `sidebar.html` with a mount point, its CSS and its script, titled like the manifest's page. A `sidebar.html` you
108
- write yourself wins: the build only writes a page that's missing or that it wrote before (it carries a
109
- `Generated by wings-plugin build` line). A popover's page takes its sidebar's title, the name Wings gives the
110
- popover too. A page that builds its DOM itself, without React, gets the same shell
111
- from `mountShell(place, options)` in `wings-plugin-sdk/shell`, which returns the element to fill.
112
-
113
- ### Talking to main
114
-
115
- `useMainState<T>(type, hello?)` returns the newest message of `type` your plugin's other pages broadcast, or
116
- `null` until one comes. It sends `hello` (`{ type: "hello" }` by default, `null` for none) once when the
117
- component mounts, so main answers with what it has. `useBroadcast(listener)` gets every message.
118
-
119
- ### Components
120
-
121
- | Component | Props | Renders |
122
- |---|---|---|
123
- | `Row`, `Stack` | `gap` (`none`, `xs`, `s`, `m`, `l`), `grow`; `Row` also `align`, `justify`, `wrap`; `as` (`form`, `ul`, `ol` too) | Children side by side or one under the other; as a `ul` or `ol`, a list with no bullets. With `as`, the props and handlers are the element's: `<Row as="form" onSubmit={(e) => e.currentTarget.requestSubmit()}>` has a `HTMLFormElement` |
124
- | `Text` | `tone` (`muted`, `info`, `success`, `warn`, `danger`), `size` (`small`, `tiny`), `weight` (`medium`, `strong`), `mono`, `numeric`, `truncate`, `clamp`, `grow`, `as` | A span, or the element in `as` |
125
- | `Title` | `as` (`h1`, `h2`, `h3`), `size` (`large`), `truncate` | A page's or a card's title, like a sprint's name or a setup step's question |
126
- | `Heading` | `level` (2, 3, 4), `actions` | A small section heading, `h2` by default, with chips or a button on its right |
127
- | `Card` | `compact`, `as` (`form`, `ul`, `ol` too) | A card, a card that's a form, or a list. Typed for its `as` element like `Row` and `Stack` |
128
- | `Gauge`, `Meter` | `label`, `percent`, `note`, `tone` or `levels` | A labelled bar with its percentage, or the bar alone, with the meter role |
129
- | `Stats` | `items: { label, value, tone? }[]` | Small figures side by side, label over value |
130
- | `Properties` | `items: { label, value }[]` | Label and value pairs |
131
- | `List`, `ListItem`, `ListButton` | `ordered` and `compact` on `List`, `align` on the rows | A list of rows; a `ListButton` in a `ListItem` makes the row a button |
132
- | `Dot`, `Spinner` | `tone`, `size`, `label`; `Dot` also `color` | A status dot or a spinner, announced only with a `label`. A dot's `color` comes from your data, like a work item type's `#cc293d` |
133
- | `Pill`, `Tag` | `tone` | A filled pill, or an outlined tag like a branch |
134
- | `Loading`, `Empty` | `Empty`: `title`, `detail` | Before the first data, and nothing to show |
135
- | `Button` | `variant` (`primary`, `ghost`, `danger`), `size` (`small`), `busy` | A button, `type="button"`; `busy` shows a spinner and ignores clicks, keeping focus |
136
- | `Chip`, `Chips` | `pressed`; `Chips`: `label`, `options`, `value`, `onChange` | A small quiet button, and a group of them that picks one, with `aria-pressed`. An option's other props, like `id`, go to its chip |
137
- | `Tabs` | `label`, `items`, `value`, `onChange`, `children` | Tabs with arrow keys, Home and End, over their panel |
138
- | `Field` | `label`, `hint`, `error`, `id`, `inline` | A labelled field that names and describes the input in it. `id` becomes the input's, for a page that puts focus back by id; `inline` puts the label beside it |
139
- | `FieldGroup` | `label`, `hint`, `error` | Several controls under one label, like checkboxes: a `fieldset` with its `legend`, described by the hint; an error marks each checkbox invalid |
140
- | `TextInput`, `SearchField`, `Select` | `label`; `Select`: `options`, `value`, `onChange` | Inputs Wings styles; outside a `Field`, `label` names them |
141
- | `Checkbox` | `checked`, `onChange`, `children` | A checkbox with its label |
142
- | `Combobox` | `label`, `value`, `onInput`, `options: { value, label, detail? }[]`, `onPick`, `onEnter`, `onKeyDown`, `freeText`, `openOnFocus`, `open`, `filter`, `empty` | A text field with suggestions under it: arrows, Enter, Escape and Tab, and a press outside closes it. With `freeText` it keeps what you type; otherwise it picks one of `options`. `openOnFocus={false}` for a page that puts focus back into it after a render. Without `freeText` the picked label is selected when the field gets focus and after a pick, so typing replaces it. `onEnter(text)` runs on Enter when no option is highlighted, with the typed text, and stops the Enter from submitting a form around it. An `onKeyDown` you pass runs first; `e.preventDefault()` in it skips the combobox's own keys. ArrowDown on a closed combobox opens it and highlights the first option, as in the [WAI-ARIA APG combobox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/). `open="whileText"` keeps the list shown for as long as the field has text and an option matches, even after Escape, Tab or a press outside (those only clear the highlighted option) and it closes when the text is emptied or an option is picked; `aria-expanded` follows whether the list is shown. Accessibility caveat: the APG pattern has Escape dismiss the popup and treats it as shown only while the user is working in the combobox, so this mode departs from it. Use it only where the suggestions are the page's main content, such as a people search that is useful to keep in view, and not for a field where a stray list would cover other controls |
143
- | `Menu` | `label`, `items: { id, label, onSelect, tone? }[]`, `align`, `variant`, `size` | A button that opens a menu: arrows, Home, End, Enter, Escape |
144
- | `Disclosure` | `summary` | More behind a line that opens it |
145
-
146
- Every component passes other props, like `id`, `title`, `className`, `onClick` and `aria-*`, to its element.
147
- `TextInput` and `SearchField` pass `ref` to the input on React 19.
148
-
149
- A button that's working on something is `busy`: it shows a spinner, ignores clicks and says so with `aria-busy`
150
- and `aria-disabled`, and unlike `disabled` it keeps the keyboard focus:
151
-
152
- ```tsx
153
- <Button variant="primary" busy={saving} onClick={save}>
154
- Save
155
- </Button>
156
- ```
157
-
158
- ### Ours or your own
159
-
160
- The components are there for a page that looks like the rest of Wings without effort, not a rule. Write your
161
- own components next to them, and your own CSS: import it from the page, `import "./sidebar.css"`, and the build
162
- bundles it into `sidebar.css` and links it from the generated page. Match Wings with its variables, which every
163
- page has:
164
-
165
- | Variable | What |
166
- |---|---|
167
- | `--wings-fg`, `--wings-muted` | Text, and quieter text |
168
- | `--wings-surface`, `--wings-hover`, `--wings-pressed`, `--wings-input` | Card, hovered, pressed and input backgrounds |
169
- | `--wings-hairline`, `--wings-hairline-strong` | Borders |
170
- | `--wings-ring` | The focus ring |
171
- | `--wings-primary`, `--wings-primary-fg` | The primary button, and its text |
172
- | `--wings-info`, `--wings-success`, `--wings-warning`, `--wings-danger` | Tones |
173
- | `--wings-radius` | A card's corners |
174
- | `--wings-menu-bg`, `--wings-menu-ring`, `--wings-menu-active`, `--wings-shadow` | A raised layer like a menu or a list of suggestions, as Wings' own menus draw it: its opaque background, its edge, the highlighted option and its shadow |
175
- | `--wings-font`, `--wings-mono` | Fonts |
176
-
177
- ```tsx
178
- // src/sidebar.tsx
179
- import "./sidebar.css";
180
-
181
- function Banner({ children }: { children: string }) {
182
- return <div className="banner">{children}</div>;
183
- }
184
- ```
185
-
186
- ```css
187
- /* src/sidebar.css */
188
- .banner {
189
- padding: 8px 10px;
190
- border-radius: var(--wings-radius);
191
- background: color-mix(in srgb, var(--wings-info) 14%, transparent);
192
- color: var(--wings-info);
193
- }
194
- ```
195
-
196
- Wings' base styles cover plain elements (buttons, inputs, selects and the page's font and colours), the `w-*`
197
- classes and two older helpers, `.muted` and `.card`; nothing else, so your other classes are yours. Your CSS
198
- loads after Wings', so it wins where you style a component, like `.my-list .w-list-button { height: 24px; }`.
199
-
200
- ### The class contract
201
-
202
- The components render plain elements with `w-*` class names and data attributes, like
203
- `<div class="w-card" data-size="compact">`, and no styles of their own. Wings styles them in its
204
- `plugin-ui.css`, which ships with the app, so a Wings update restyles every plugin. The names are a versioned
205
- public contract: `--wings-ui` on the page's root is its version, `2` now, and a version only adds classes and
206
- rules. `Meter` sets `--w-value`, how full it is, and a `Dot` with a `color` sets `--w-color`; both are data
207
- rather than styles.
208
-
209
- `w-shell` with `data-place` (`sidebar`, `popover`, `view`, `tab`, `settings`) and `data-fill`; `w-row`,
210
- `w-stack` with `data-gap`, `data-align`, `data-justify`, `data-wrap`, `data-grow`; `w-text` with `data-tone`,
211
- `data-size`, `data-weight`, `data-mono`, `data-numeric`, `data-truncate`, `data-clamp`, `data-grow`;
212
- `w-title` with `data-size`; `w-heading`, `w-heading-row`; `w-card` with `data-size`; `w-gauge`, `w-gauge-label`, `w-gauge-value`,
213
- `w-gauge-note`, `w-meter` with `data-tone`, `w-meter-fill`; `w-stats`, `w-stat-label`, `w-stat-value`;
214
- `w-props`, `w-prop`, `w-prop-label`, `w-prop-value`; `w-list` with `data-density`, `w-list-item`,
215
- `w-list-button` with `data-align`; `w-dot` with `data-tone` or `data-color`; `w-pill`, `w-tag` with `data-tone`; `w-spinner` with `data-size`;
216
- `w-loading`, `w-empty`, `w-empty-detail`; `w-button` with `data-variant`, `data-size`, `aria-disabled`; `w-chips`, `w-chip`;
217
- `w-tabs`, `w-tab`; `w-field` with `data-layout`, `w-field-row`, `w-field-label`, `w-field-hint`,
218
- `w-field-group`, `w-input`, `w-search`, `w-select`, `w-check`; `w-combobox`, `w-listbox`, `w-option`,
219
- `w-option-label`, `w-option-detail`, `w-option-empty`; `w-menu-root`, `w-menu` with `data-align`, `w-menu-item`
220
- with `data-tone`; `w-disclosure`. A `ul` or `ol` with `w-row`, `w-stack` or `w-card` loses its bullets.
221
-
222
- Version 2 added `w-title`, `Heading`'s levels, `w-field-group`, `w-field-row`, the combobox and menu classes,
223
- `data-color` on `w-dot`, busy buttons (`aria-disabled` on `w-button`), lists as rows, stacks and cards, and the four raised-layer variables.
224
-
225
- ### Older Wings
226
-
227
- Wings before 0.1.15 has no `w-*` styles, and a Wings with `--wings-ui: 1` lacks version 2's. A page built with
228
- `wings-plugin-sdk/ui` checks `--wings-ui` when it mounts, and on a Wings without it, or with an older version,
229
- adds the package's own copy of the styles, before Wings' and your CSS. Since a version only adds rules, the old
230
- ones Wings has and the new ones from the package fit together. So a plugin works on older Wings without a `minWingsVersion`, and on a current Wings it always gets
231
- Wings' own styles.
232
-
233
- ### A linked SDK
234
-
235
- With the SDK linked from a local folder (`npm link`, or `link:` in `package.json`) rather than installed,
236
- TypeScript looks for React next to the SDK's folder. Point it at yours in `tsconfig.json`; the build already
237
- does:
238
-
239
- ```json
240
- "paths": {
241
- "react": ["./node_modules/@types/react"],
242
- "react/*": ["./node_modules/@types/react/*"]
243
- }
244
- ```
package/build.d.ts CHANGED
@@ -3,7 +3,7 @@ export function entries(dir: string): { src: string; out: string }[];
3
3
 
4
4
  /**
5
5
  * Builds the plugin in `dir`: `src/main.ts` to `main.js`, `src/sidebar.tsx` to `sidebar.js`, and so on. CSS a page
6
- * imports becomes `<name>.css`, and a page that mounts with `wings-plugin-sdk/ui` or `/shell` gets a generated
6
+ * imports becomes `<name>.css`, and a page that mounts with `wings-plugin-ui` or `wings-plugin-sdk/shell` gets a generated
7
7
  * `<name>.html` unless the plugin has its own. With `check`, writes nothing and lists in `stale` the outputs that
8
8
  * differ from what's on disk. With `outDir`, writes there instead of `dir`.
9
9
  */
package/build.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  // becomes main.js and src/sidebar.tsx becomes sidebar.js, next to wings-plugin.json. Folders inside src/ hold
3
3
  // the modules they import. Wings runs plain scripts, so each file is one bundle with its imports, React too.
4
4
  // The CSS a page imports, like `import "./sidebar.css"`, becomes <name>.css, and a page that mounts with
5
- // wings-plugin-sdk/ui or /shell gets a <name>.html written for it unless the plugin has its own.
5
+ // wings-plugin-ui or wings-plugin-sdk/shell gets a <name>.html written for it unless the plugin has its own.
6
6
  import * as esbuild from "esbuild";
7
7
  import { zipSync } from "fflate";
8
8
  import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
@@ -54,12 +54,12 @@ ${hasCss ? ` <link rel="stylesheet" href="${name}.css" />\n` : ""} </head>
54
54
  /** Whether `file` is missing or was written by this build, so the build may write it. */
55
55
  const ours = (file) => !existsSync(file) || readFileSync(file, "utf8").includes(GENERATED);
56
56
 
57
- /** Notes whether the bundle imports wings-plugin-sdk's ui or shell, which mount the page themselves. */
57
+ /** Notes whether the bundle imports wings-plugin-ui or wings-plugin-sdk/shell, which mount the page themselves. */
58
58
  function shellPlugin(seen) {
59
59
  return {
60
60
  name: "wings-shell",
61
61
  setup(build) {
62
- build.onResolve({ filter: /^wings-plugin-sdk\/(ui|shell)$/ }, () => {
62
+ build.onResolve({ filter: /^(wings-plugin-ui|wings-plugin-sdk\/shell)$/ }, () => {
63
63
  seen.shell = true;
64
64
  // Resolve as usual.
65
65
  return undefined;
@@ -69,8 +69,8 @@ function shellPlugin(seen) {
69
69
  }
70
70
 
71
71
  /**
72
- * Resolves React from the plugin's folder wherever it's imported, so a page and a linked wings-plugin-sdk (npm
73
- * link, or link: in package.json) share one React instead of the SDK finding its own, or none.
72
+ * Resolves React from the plugin's folder wherever it's imported, so a page and a linked wings-plugin-ui (npm
73
+ * link, or link: in package.json) share one React instead of the package finding its own, or none.
74
74
  */
75
75
  function oneReact(dir) {
76
76
  return {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "wings-plugin-sdk",
3
- "version": "0.5.0",
4
- "description": "Types, React hooks, UI components and a build command for Wings plugins.",
3
+ "version": "0.7.0",
4
+ "description": "The wings API types, the page frame, React hooks and the build command for Wings plugins.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "types": "./wings.d.ts",
@@ -13,21 +13,21 @@
13
13
  "types": "./react.d.ts",
14
14
  "default": "./react.js"
15
15
  },
16
- "./ui": {
17
- "types": "./ui.d.ts",
18
- "default": "./ui.js"
19
- },
20
16
  "./shell": {
21
17
  "types": "./shell.d.ts",
22
18
  "default": "./shell.js"
23
19
  },
20
+ "./poll": {
21
+ "types": "./poll.d.ts",
22
+ "default": "./poll.js"
23
+ },
24
24
  "./build": {
25
25
  "types": "./build.d.ts",
26
26
  "default": "./build.mjs"
27
27
  }
28
28
  },
29
29
  "scripts": {
30
- "test": "tsc -p tsconfig.json && node --test"
30
+ "test": "node --test"
31
31
  },
32
32
  "bin": {
33
33
  "wings-plugin": "bin.mjs"
@@ -36,12 +36,10 @@
36
36
  "wings.d.ts",
37
37
  "react.js",
38
38
  "react.d.ts",
39
- "ui.js",
40
- "ui.d.ts",
41
- "ui-css.js",
42
- "ui-css.d.ts",
43
39
  "shell.js",
44
40
  "shell.d.ts",
41
+ "poll.js",
42
+ "poll.d.ts",
45
43
  "build.mjs",
46
44
  "build.d.ts",
47
45
  "bin.mjs",
@@ -71,7 +69,6 @@
71
69
  },
72
70
  "devDependencies": {
73
71
  "@types/react": "^19.1.8",
74
- "jsdom": "^30.1.2",
75
72
  "react": "^19.1.0",
76
73
  "react-dom": "^19.1.0",
77
74
  "typescript": "~6.0.3"
package/poll.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /// <reference path="./wings.d.ts" />
2
+ /**
3
+ * Calls `tick` every `ms` while Wings is focused. When Wings loses focus the timer stops; when it regains focus,
4
+ * `tick` runs once at once and the timer starts again. `tick` is not called for the state Wings starts in, so
5
+ * a plugin keeps doing its own first check. Returns a function that stops everything.
6
+ */
7
+ export function pollWhileFocused(tick: () => void, ms: number, w?: Pick<Wings, "focused" | "onFocus">): () => void;
package/poll.js ADDED
@@ -0,0 +1,28 @@
1
+ // A timer that runs only while the user is in Wings.
2
+
3
+ /**
4
+ * Calls `tick` every `ms` while Wings is focused. When Wings loses focus the timer stops; when it regains focus,
5
+ * `tick` runs once at once and the timer starts again. `tick` is not called for the state Wings starts in, so
6
+ * a plugin keeps doing its own first check. Returns a function that stops everything.
7
+ */
8
+ export function pollWhileFocused(tick, ms, w = globalThis.wings) {
9
+ let timer;
10
+ let wasFocused = w.focused;
11
+ const start = () => (timer ??= setInterval(tick, ms));
12
+ const stop = () => {
13
+ clearInterval(timer);
14
+ timer = undefined;
15
+ };
16
+ if (wasFocused) start();
17
+ const off = w.onFocus(({ focused }) => {
18
+ if (focused === wasFocused) return;
19
+ wasFocused = focused;
20
+ if (!focused) return stop();
21
+ tick();
22
+ start();
23
+ });
24
+ return () => {
25
+ off();
26
+ stop();
27
+ };
28
+ }
package/shell.d.ts CHANGED
@@ -9,6 +9,6 @@ export interface ShellOptions {
9
9
 
10
10
  /**
11
11
  * Makes the page's `#app` element (or a new `<main>`) the shell for `place`, and returns it, for a page that
12
- * builds its DOM itself. React pages use `showSidebar` and the others from `wings-plugin-sdk/ui` instead.
12
+ * builds its DOM itself. React pages use `showSidebar` and the others from `wings-plugin-ui` instead.
13
13
  */
14
14
  export function mountShell(place: Place, options?: ShellOptions): HTMLElement;
package/shell.js CHANGED
@@ -1,28 +1,15 @@
1
1
  // The frame a plugin page lives in, without React: where it is, its padding and how it scrolls come from Wings'
2
- // plugin-ui.css, so a page only says which place it is. wings-plugin-sdk/ui mounts React into it.
3
- import { fallbackCss, UI_VERSION } from "./ui-css.js";
4
-
2
+ // plugin-ui.css, so a page only says which place it is. wings-plugin-ui mounts React into it.
5
3
  const PLACES = ["sidebar", "popover", "view", "tab", "settings"];
6
4
  // Sidebars and popovers change while you look at them, so a screen reader hears what changed.
7
5
  const LIVE = new Set(["sidebar", "popover"]);
8
6
 
9
- /** Wings older than the classes this package renders have none, or older ones: bring them, before the page's own CSS. */
10
- function ensureStyles() {
11
- const host = Number(getComputedStyle(document.documentElement).getPropertyValue("--wings-ui")) || 0;
12
- if (host >= UI_VERSION || document.querySelector("style[data-wings-ui]")) return;
13
- const style = document.createElement("style");
14
- style.dataset.wingsUi = String(UI_VERSION);
15
- style.textContent = fallbackCss;
16
- document.head.prepend(style);
17
- }
18
-
19
7
  /**
20
8
  * Makes the page's `#app` element (or a new `<main>`) the shell for `place`, and returns it. Calling it again
21
9
  * returns the same element. `fill` lays the page out edge to edge, for a page that scrolls on its own.
22
10
  */
23
11
  export function mountShell(place, { fill = false } = {}) {
24
12
  if (!PLACES.includes(place)) throw new Error(`Unknown place ${place}: use one of ${PLACES.join(", ")}`);
25
- ensureStyles();
26
13
  let shell = document.getElementById("app");
27
14
  if (!shell) {
28
15
  shell = document.createElement("main");
package/wings.d.ts CHANGED
@@ -102,6 +102,14 @@ interface Wings {
102
102
  // Every `on*` call returns a function that removes the listener, for cleaning up in a React effect.
103
103
  /** Every terminal pane, on every change. */
104
104
  onPanes(listener: (panes: WingsPane[]) => void): () => void;
105
+ /**
106
+ * Whether the user is in Wings: true while any Wings window (the main one or a view window) is in front, false
107
+ * while another app is. Called once with the current state, then on each change. Needs no permission. Pause timers
108
+ * while it is false and check once when it turns true, or use `pollWhileFocused` from `wings-plugin-sdk/poll`.
109
+ */
110
+ onFocus(listener: (event: { focused: boolean }) => void): () => void;
111
+ /** The state `onFocus` last reported. True until Wings says otherwise, which it does as soon as the frame is ready. */
112
+ readonly focused: boolean;
105
113
  /**
106
114
  * A badge action was clicked. Return a promise: the button spins until it settles (up to 5 min), and a thrown
107
115
  * error shows in the card. What it resolves to is ignored.
@@ -154,7 +162,7 @@ interface Wings {
154
162
  /** Needs `permissions.notify`. A desktop notification, up to 3 a minute. The title is cut at 64 characters, the body at 256. */
155
163
  notify(notification: { title: string; body?: string }): Promise<void>;
156
164
  /** Needs `"diff"` in `contributes.ui`. Leave out `patch` to open it loading, then call `updateDiff`. */
157
- openDiff(diff: { title: string; subtitle?: string; patch?: string; comments?: WingsComment[] }): Promise<{ id: string }>;
165
+ openDiff(diff: { title: string; subtitle?: string; /** The repo or folder the diff is of, so Send to agent offers the agent working there first. */ cwd?: string; patch?: string; comments?: WingsComment[] }): Promise<{ id: string }>;
158
166
  /** Updates a viewer opened with `openDiff`; ignored once it's closed. `{ comments }` alone keeps the patch, like after a reply. */
159
167
  updateDiff(id: string, update: { patch: string; comments?: WingsComment[] } | { comments: WingsComment[] } | { error: string }): Promise<void>;
160
168
  /**
package/ui-css.d.ts DELETED
@@ -1,4 +0,0 @@
1
- /** The version of the classes wings-plugin-sdk/ui renders. Wings' plugin-ui.css sets it as `--wings-ui`. */
2
- export const UI_VERSION: number;
3
- /** The wings-plugin-sdk/ui styles, for Wings versions whose plugin-ui.css doesn't have them yet. */
4
- export const fallbackCss: string;