@50arms/plugin-sdk 0.0.0-stage → 0.2.51

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,3 +1,120 @@
1
- # Temporary Holding Version
1
+ # @50arms/plugin-sdk
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Build a 50 Arms **plugin**: a small React bundle that gives a card type its own card view (`CardView`), adds a card-list view (`CardListView`), and/or adds **Widgets** to the Dashboard's Widget Library, loaded into 50 Arms' own page with `import()`.
4
+
5
+ A plugin does not bundle React, `@50arms/ui` or the card features. The app serves them under fixed import specifiers through an import map, so a plugin shares the app's single React and renders the app's own components:
6
+
7
+ | Specifier | What it is |
8
+ |---|---|
9
+ | `react`, `react/jsx-runtime`, `react-dom` | The app's React |
10
+ | `@50arms/ui` | The app's design system |
11
+ | `@danxbot/card-kit@1` | The card features: data hooks, card regions (`StandardCardPage`, `EditableTitle`, `ChecklistsSection`, ...), `CardFace`, `CardBoard` |
12
+
13
+ The live list and each library's major version is `GET /api/plugin-runtime`.
14
+
15
+ ### Plugins built under the earlier names
16
+
17
+ This package was `@danxbot/plugin-sdk` and the design system `@danxbot/ui`. A plugin whose source still depends on `@danxbot/plugin-sdk` and imports `@danxbot/ui` keeps building: those versions stay on npm, and the app still answers the `@danxbot/ui` import (and a manifest recording `"@danxbot/ui": 33`) with `@50arms/ui`. To move a plugin over, depend on `@50arms/plugin-sdk` and `@50arms/ui` and change the imports; this SDK refuses to bundle a copy of `@danxbot/ui`, so a half-moved plugin fails its build instead of shipping a second design system.
18
+
19
+ ## Set up a plugin
20
+
21
+ `package.json`: depend on `@50arms/plugin-sdk` and install its peers: `vite`, `react`, `@types/react`, `@50arms/ui` and `react-router` (types only: the app serves the real ones; the kit's declarations import them).
22
+
23
+ `vite.config.ts`:
24
+
25
+ ```ts
26
+ import { defineConfig } from "vite";
27
+ import { danxPlugin } from "@50arms/plugin-sdk/vite";
28
+
29
+ export default defineConfig({ plugins: [danxPlugin()] });
30
+ ```
31
+
32
+ A plugin whose `CardView` is made for particular card types names them: `danxPlugin({ cardTypes: ["Bug"] })`. Each becomes that type's default view once the plugin's revision is approved and active (a team admin's card-view board shows it as "Default" and reverts a type to it); where two plugins name one type, the one activated first wins. The names go into `danx-plugin.json` as `cardTypes`, and the upload refuses a key that is not a card type key or a plugin that exports no `CardView`.
33
+
34
+ `tsconfig.json`: add `"types": ["@50arms/plugin-sdk/card-kit"]`: the kit's declarations. They are self-contained (they import only `react`, `@50arms/ui` and `react-router`) and the specifier is not a package, so TypeScript reads them as ambient types.
35
+
36
+ `src/index.tsx` exports `CardView` and/or `CardListView`:
37
+
38
+ ```tsx
39
+ import { StandardCardPage, type CardViewProps } from "@danxbot/card-kit@1";
40
+
41
+ export function CardView({ card, mutation }: CardViewProps) {
42
+ return <StandardCardPage card={card} mutation={mutation} />;
43
+ }
44
+ ```
45
+
46
+ `vite build` writes the plugin to `dist/`: `index.js` (the entry), `chunks/` for code splitting, `index.css`, source maps, and `danx-plugin.json`, the manifest listing every file with its size and sha256 and the runtime majors the plugin was built against.
47
+
48
+ A plugin styles itself with its own CSS (import a `.css` file; the build emits it as `index.css`) over the design system's runtime tokens such as `var(--surface)` and `var(--accent)`: the app's stylesheet holds only the utility classes the app's own source uses, so a utility class in a plugin may be missing at run time. A plugin changes cards with the kit's writes (`createCard`, `editCard`, `transitionCard`). A list view about one card type reads its cards with `useCardsOfType(scope, type)`, never by filtering `useBoardLaneState`'s lanes: those hold a window of each status, so cards would go missing.
49
+
50
+ The build refuses to bundle a copy of React, React DOM, `@50arms/ui` or `react-router`: import only the specifiers above. For routing, use the kit's `Link` and `useNavigate`; `StandardCardPage` reads the plan the page was opened from out of the URL, so a view that wraps it passes only `{card, mutation}`.
51
+
52
+ ## Widgets
53
+
54
+ A plugin can add Widgets: tiles a person places on their Dashboard from the Widget Library. A Widget is a component the entry exports plus a declaration, the built-in Widgets' own shape, from `@50arms/plugin-sdk/widgets`:
55
+
56
+ - `key`: the Widget's own key inside the plugin, lowercase words joined by `-` (a placement stores `plugin:<plugin key>:<key>`);
57
+ - `size`: its `default`, `min` and `max` size in cells of the 24-column grid;
58
+ - `config` and `settings`: the fields a placement may set (`config`, fixed by whoever edits the surface) and a viewer may override (`settings`). Each field is a `string`, `number` or `boolean` with a `default` and a `label` (`{ text: "..." }`, the words the Configure form shows); a `string` with `enum` also needs `optionLabels`, a label for every option;
59
+ - `readsSurfaceSettings`: which surface-wide settings it reads (`date_range`, `keyword`);
60
+ - `needsHostContext`: the host facts it cannot draw without (none on the Dashboard).
61
+
62
+ `src/widgets.ts`:
63
+
64
+ ```ts
65
+ import { declareWidget, type PluginWidget } from "@50arms/plugin-sdk/widgets";
66
+
67
+ export const OPEN_BUGS = declareWidget({
68
+ key: "open-bugs",
69
+ size: { default: { w: 6, h: 4 }, min: { w: 3, h: 2 }, max: { w: 24, h: 12 } },
70
+ config: {},
71
+ settings: { board: { type: "string", label: { text: "Board" }, default: "", maxLength: 200 } },
72
+ readsSurfaceSettings: [],
73
+ needsHostContext: [],
74
+ });
75
+
76
+ export const WIDGETS: PluginWidget[] = [
77
+ { export: "OpenBugs", title: "Open bugs", description: "How many bugs are open on each board.", declaration: OPEN_BUGS },
78
+ ];
79
+ ```
80
+
81
+ The entry exports the component, which receives `WidgetProps` (its `config` and `settings` with the defaults filled in, the surface settings it reads, and `saveSettings` for the viewer's own choices) and reads its data with the card kit's hooks:
82
+
83
+ ```tsx
84
+ import type { WidgetProps } from "@50arms/plugin-sdk/widgets";
85
+ import { OPEN_BUGS } from "./widgets";
86
+
87
+ export function OpenBugs({ settings }: WidgetProps<typeof OPEN_BUGS.config, typeof OPEN_BUGS.settings>) {
88
+ return <p>{settings.board === "" ? "Every board" : settings.board}</p>;
89
+ }
90
+ ```
91
+
92
+ `vite.config.ts` names them: `danxPlugin({ widgets: WIDGETS })`. The build records them in `danx-plugin.json` (`widgets`) and refuses a Widget whose declaration cannot work or whose component the entry does not export; the upload checks the declarations again. A plugin may declare only Widgets and export no view; it then publishes without a screenshot (the preview draws views only).
93
+
94
+ Who sees a plugin's Widget is who it runs for: a revision you publish runs for nobody until you try it, so your Widget is listed in your own Widget Library as "Not approved", and choosing it asks before it runs in your session; it is private to you. Once an approver approves the revision and a team admin makes it active, it is the team's.
95
+
96
+ **For an agent asked to make a widget** ("make me a widget that shows ..."): work in the person's own repo, in one of their plugin projects or a new one; add the declaration, the component and the `widgets` option as above; commit; publish with `plugin_publish` from the person's own session (the revision is then theirs); then tell them to open their Widget Library and pick it.
97
+
98
+ ## The card kit is a contract
99
+
100
+ A runtime export of `@danxbot/card-kit@1`, and its parameters, is never removed or changed in place; the keys of the exported data types follow the Versioning section below. A breaking change is `@danxbot/card-kit@2`, served beside `@1` until no approved plugin revision needs it. A plugin's manifest records the majors it was built against, and the app refuses to run a revision built for a major it no longer serves.
101
+
102
+ ## Publish a revision
103
+
104
+ ```
105
+ DANXBOT_DASHBOARD_URL=https://<your dashboard> DANXBOT_API_KEY=<a key from your own account> npx danx-plugin publish --plugin <key> --note "what changed" [--dir <project>] [--name "Display name"]
106
+ ```
107
+
108
+ `publish` runs the project's own `npm run build`, archives the source (inside a git repo: the tracked files plus untracked ones git does not ignore; otherwise what the top-level `.gitignore` leaves, minus `node_modules`, `.git` and a top-level `dist`; a lockfile is required, so the download builds as is) and uploads both as a new revision of the plugin `<key>`, creating the plugin on its first publish. It prints the revision number and the Settings > Plugins link where a team admin approves it. `.env*` files are never archived, in or out of git. Inside git, a git failure (git missing, a dubious-ownership refusal) stops the publish with git's message; only "not a git repository" means outside git, where the top-level `.gitignore` is honoured (`*`, `?`, `**`, anchoring and trailing `/`; a `!` negation is refused). A blank note is refused first, before the build, the archive or any request. The dashboard validates the upload; a refusal prints its named reason.
109
+
110
+ The same flow is exported for tools as `@50arms/plugin-sdk/publish` (`buildPlugin`, `archiveSource`, `uploadRevision`, `fetchTransport`): the dashboard MCP's `plugin_publish` tool runs it for agents.
111
+
112
+ ## The screenshot on a revision
113
+
114
+ An approver sees a screenshot of the plugin next to each revision. There is nothing to draw by hand: the dashboard MCP's `plugin_publish` tool (not `danx-plugin publish`, which uploads no screenshot) takes it from the plugin's own build. A headless Chromium (Playwright, resolved from your project or the MCP server's working directory) opens the dashboard's public `/plugin-preview` page, which imports your built `CardView` on the app's own React, `@50arms/ui` and card kit and draws it with an invented sample card; the build is served from memory, so the revision need not be uploaded first. The capture waits until the page itself reports `ready` (or `failed`, with the reason), then saves a 1280x800 PNG as `screenshot.png`, which joins the revision's files and manifest.
115
+
116
+ So a plugin must export a `CardView` for a screenshot to exist. No browser, a dashboard that predates the page, or a plugin that fails to render all publish the revision WITHOUT a screenshot; `plugin_publish` answers `screenshot: "none: <reason>"` with the cause.
117
+
118
+ ## Versioning
119
+
120
+ The SDK stays 0.x until the shapes behind the card kit's data types settle: phase 1 of DX-4679 (card types as rows) changes `IssueViewV2.type`, and DX-5164 (designs on any card) changes the design fields. Until then the declarations may change between 0.x releases; the kit's runtime exports follow the `@danxbot/card-kit@1` rule above.