deck-plugin-sdk 1.4.0 → 1.6.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.
Files changed (40) hide show
  1. package/README.md +96 -77
  2. package/bin/deck-plugin.mjs +149 -116
  3. package/build-plugin.mjs +48 -26
  4. package/package.json +6 -1
  5. package/packages.mjs +107 -0
  6. package/shim/bridge.js +73 -90
  7. package/shim/event.js +6 -1
  8. package/shim/scope.js +6 -0
  9. package/shim/terminals.js +1 -0
  10. package/shim/ui.d.ts +1 -0
  11. package/shim/ui.js +7 -1
  12. package/template/AGENTS.md +77 -0
  13. package/template/CLAUDE.md +1 -0
  14. package/template/README.md +20 -17
  15. package/template/gitignore +4 -4
  16. package/template/plugin.json +9 -9
  17. package/template/src/assets.d.ts +16 -0
  18. package/template/src/plugin.tsx +65 -65
  19. package/types/components/ActivityBar.d.ts +3 -1
  20. package/types/components/ContextMenu.d.ts +3 -0
  21. package/types/components/TabStrip.d.ts +6 -1
  22. package/types/components/ui.d.ts +48 -1
  23. package/types/components/useResize.d.ts +8 -1
  24. package/types/core/bookmarks.d.ts +79 -0
  25. package/types/core/bridge.d.ts +294 -156
  26. package/types/core/browser.d.ts +189 -0
  27. package/types/core/browserurl.d.ts +64 -0
  28. package/types/core/editorsession.d.ts +55 -0
  29. package/types/core/editorsettings.d.ts +9 -0
  30. package/types/core/fileview.d.ts +35 -0
  31. package/types/core/notifications.d.ts +84 -0
  32. package/types/core/notifyapi.d.ts +39 -0
  33. package/types/core/terminals.d.ts +7 -0
  34. package/types/modules/editor/DiffPeek.d.ts +19 -0
  35. package/types/modules/editor/MonacoHosts.d.ts +19 -6
  36. package/types/modules/editor/detectlang.d.ts +3 -0
  37. package/types/modules/editor/editorstatus.d.ts +2 -0
  38. package/types/modules/editor/hunks.d.ts +54 -0
  39. package/types/modules/editor/models.d.ts +11 -0
  40. package/types/modules/editor/tree.d.ts +0 -6
@@ -1,65 +1,65 @@
1
- // __NAME__ — a Deck plugin.
2
- //
3
- // The default export is the tab. A plugin can export more than that — a footer readout, a
4
- // settings panel, palette commands, a pane view — and `plugins/API.md` in the Deck repository
5
- // lists every slot. One export is enough to start.
6
- import { useState } from "react";
7
- import { Sparkles } from "lucide-react";
8
- import { configRead, configWrite } from "../shim/bridge.js";
9
-
10
- /** Where this plugin's own settings live. The `plugin-` prefix keeps them on this machine. */
11
- const CONFIG = "plugin-__ID__";
12
-
13
- export default function __COMPONENT__() {
14
- const [note, setNote] = useState("");
15
- const [saved, setSaved] = useState(false);
16
-
17
- // Deck's config store, the same one its own modules use. Reading a key that has never been
18
- // written resolves to an empty string rather than throwing.
19
- const load = async () => {
20
- const raw = await configRead(CONFIG).catch(() => "");
21
- setNote(raw.trim() ? (JSON.parse(raw) as { note?: string }).note ?? "" : "");
22
- };
23
-
24
- const save = async () => {
25
- await configWrite(CONFIG, JSON.stringify({ note }));
26
- setSaved(true);
27
- setTimeout(() => setSaved(false), 1500);
28
- };
29
-
30
- return (
31
- <div className="flex h-full flex-col gap-4 p-6">
32
- <header className="flex items-center gap-3">
33
- <Sparkles size={18} style={{ color: "var(--accent)" }} />
34
- <h1 className="text-base font-semibold text-text-primary">__NAME__</h1>
35
- </header>
36
-
37
- <p className="max-w-[60ch] text-sm text-text-secondary">
38
- Edit <code className="rounded bg-elev px-1.5 py-0.5 text-text-primary">src/plugin.tsx</code>,
39
- run <code className="rounded bg-elev px-1.5 py-0.5 text-text-primary">npm run dev</code>,
40
- and Deck reloads the plugin as you save.
41
- </p>
42
-
43
- <div className="flex max-w-md items-center gap-2">
44
- <input
45
- value={note}
46
- onChange={(e) => setNote(e.target.value)}
47
- onFocus={() => void load()}
48
- placeholder="Type something, then save it"
49
- className="min-w-0 flex-1 rounded-lg border border-subtle bg-elev px-3 py-2 text-sm text-text-primary outline-none focus:border-accent"
50
- />
51
- <button
52
- onClick={() => void save()}
53
- className="shrink-0 rounded-lg border border-subtle bg-elev px-3 py-2 text-sm text-text-secondary hover:border-accent hover:text-text-primary"
54
- >
55
- {saved ? "Saved" : "Save"}
56
- </button>
57
- </div>
58
-
59
- <p className="text-xs text-text-muted">
60
- That writes to Deck's config store under <code>{CONFIG}</code>, which stays on this
61
- machine.
62
- </p>
63
- </div>
64
- );
65
- }
1
+ // __NAME__ — a Deck plugin.
2
+ //
3
+ // The default export is the tab. A plugin can export more than that — a footer readout, a
4
+ // settings panel, palette commands, a pane view — and `docs/guide/api.md` in the Deck repository
5
+ // lists every slot. One export is enough to start.
6
+ import { useState } from "react";
7
+ import { Sparkles } from "lucide-react";
8
+ import { configRead, configWrite } from "deck-plugin-sdk/bridge";
9
+
10
+ /** This plugin's own settings. Deck keeps a plugin's configs in its own folder, so a plain name. */
11
+ const CONFIG = "settings";
12
+
13
+ export default function __COMPONENT__() {
14
+ const [note, setNote] = useState("");
15
+ const [saved, setSaved] = useState(false);
16
+
17
+ // Deck's config store, the same one its own modules use. Reading a key that has never been
18
+ // written resolves to an empty string rather than throwing.
19
+ const load = async () => {
20
+ const raw = await configRead(CONFIG).catch(() => "");
21
+ setNote(raw.trim() ? (JSON.parse(raw) as { note?: string }).note ?? "" : "");
22
+ };
23
+
24
+ const save = async () => {
25
+ await configWrite(CONFIG, JSON.stringify({ note }));
26
+ setSaved(true);
27
+ setTimeout(() => setSaved(false), 1500);
28
+ };
29
+
30
+ return (
31
+ <div className="flex h-full flex-col gap-4 p-6">
32
+ <header className="flex items-center gap-3">
33
+ <Sparkles size={18} style={{ color: "var(--accent)" }} />
34
+ <h1 className="text-base font-semibold text-text-primary">__NAME__</h1>
35
+ </header>
36
+
37
+ <p className="max-w-[60ch] text-sm text-text-secondary">
38
+ Edit <code className="rounded bg-elev px-1.5 py-0.5 text-text-primary">src/plugin.tsx</code>,
39
+ run <code className="rounded bg-elev px-1.5 py-0.5 text-text-primary">npm run dev</code>,
40
+ and Deck reloads the plugin as you save.
41
+ </p>
42
+
43
+ <div className="flex max-w-md items-center gap-2">
44
+ <input
45
+ value={note}
46
+ onChange={(e) => setNote(e.target.value)}
47
+ onFocus={() => void load()}
48
+ placeholder="Type something, then save it"
49
+ className="min-w-0 flex-1 rounded-lg border border-subtle bg-elev px-3 py-2 text-sm text-text-primary outline-none focus:border-accent"
50
+ />
51
+ <button
52
+ onClick={() => void save()}
53
+ className="shrink-0 rounded-lg border border-subtle bg-elev px-3 py-2 text-sm text-text-secondary hover:border-accent hover:text-text-primary"
54
+ >
55
+ {saved ? "Saved" : "Save"}
56
+ </button>
57
+ </div>
58
+
59
+ <p className="text-xs text-text-muted">
60
+ That writes to Deck's config store under <code>{CONFIG}</code>, which stays on this
61
+ machine.
62
+ </p>
63
+ </div>
64
+ );
65
+ }
@@ -11,10 +11,12 @@ export interface ActivityView<Id extends string> {
11
11
  onClick?: (el: HTMLElement) => void;
12
12
  }
13
13
  export default ActivityBar;
14
- export declare function ActivityBar<Id extends string>({ views, active, onSelect, trailing, }: {
14
+ export declare function ActivityBar<Id extends string>({ views, active, onSelect, trailing, grouped, }: {
15
15
  views: readonly ActivityView<Id>[];
16
16
  active: Id;
17
17
  onSelect: (id: Id) => void;
18
18
  /** Fixed controls pinned to the right — present for every view, or the icons shift. */
19
19
  trailing?: React.ReactNode;
20
+ /** The icons together in one rounded group, smaller, rather than loose pills. */
21
+ grouped?: boolean;
20
22
  }): import("react").JSX.Element;
@@ -26,6 +26,9 @@ export interface MenuItem {
26
26
  /** A setting rather than an action: renders a checkbox and does NOT close the menu, so several
27
27
  * can be flipped in one visit. The caller re-renders with the new value. */
28
28
  checked?: boolean;
29
+ /** Controls of its own at the row's right (a zoom's − and +, a slider): the row is not
30
+ * clicked itself, and using them leaves the menu open. */
31
+ controls?: React.ReactNode;
29
32
  }
30
33
  export type MenuEntry = MenuItem | "sep";
31
34
  export declare function ContextMenu({ x, y, items, alignRight, onClose, onReorder, flipAt, up }: {
@@ -12,7 +12,7 @@ export interface TabItem {
12
12
  /** Tooltip for the close button; defaults to "Close". */
13
13
  closeTitle?: string;
14
14
  }
15
- export declare function TabStrip({ tabs, activeId, onSelect, onClose, onReorder, onDoubleClick, trailing, pill, onAdd, addTitle, }: {
15
+ export declare function TabStrip({ tabs, activeId, onSelect, onClose, onReorder, onDoubleClick, leading, trailing, pill: pillProp, chip, onAdd, addTitle, }: {
16
16
  tabs: readonly TabItem[];
17
17
  activeId: string | null;
18
18
  onSelect: (id: string) => void;
@@ -20,11 +20,16 @@ export declare function TabStrip({ tabs, activeId, onSelect, onClose, onReorder,
20
20
  /** Moves `from` to `to`'s position. Omit to make the strip non-reorderable. */
21
21
  onReorder?: (from: string, to: string) => void;
22
22
  onDoubleClick?: (id: string) => void;
23
+ /** Controls before the first tab. */
24
+ leading?: ReactNode;
23
25
  /** Right-aligned controls after the tabs — view toggles, save state. */
24
26
  trailing?: ReactNode;
25
27
  /** Rounded tabs with the active one lit in the accent, the look of the workspace tabs in the
26
28
  * title bar, instead of the editor-style boxed row. */
27
29
  pill?: boolean;
30
+ /** Rounded like `pill`, with the open tab raised on the card colour rather than lit in the
31
+ * accent: the editor's tabs, where the accent already marks the open file in the tree. */
32
+ chip?: boolean;
28
33
  /** A + right after the last tab, as the workspace tabs have. */
29
34
  onAdd?: () => void;
30
35
  addTitle?: string;
@@ -58,10 +58,14 @@ export declare function DayPicker({ value, onChange, }: {
58
58
  onChange: (days: string[]) => void;
59
59
  }): import("react").JSX.Element;
60
60
  /** One readout in the footer strip: an icon, a short label, and an optional click. */
61
- export declare function StatusItem({ icon, color, label, title, onClick, }: {
61
+ export declare function StatusItem({ icon, color, label, detail, meter, title, onClick, }: {
62
62
  icon: React.ReactNode;
63
63
  color: string;
64
64
  label: string;
65
+ /** A second, quieter part after the label: a branch, a commit. */
66
+ detail?: string;
67
+ /** 0 to 100: a small bar before the label, in the item's colour (a limit's use). */
68
+ meter?: number;
65
69
  title?: string;
66
70
  onClick?: () => void;
67
71
  }): import("react").JSX.Element;
@@ -100,3 +104,46 @@ export declare function Toggle({ on, onChange }: {
100
104
  on: boolean;
101
105
  onChange: (v: boolean) => void;
102
106
  }): import("react").JSX.Element;
107
+ /** A state in its colour: Ready, Running, Paused, Failed. `color` is any CSS colour; a `--tone-*`
108
+ * one (`tone("green")`) follows a light theme. */
109
+ export declare function Chip({ color, title, children }: {
110
+ color: string;
111
+ title?: string;
112
+ children: ReactNode;
113
+ }): import("react").JSX.Element;
114
+ /** A `--tone-*` colour as CSS, for a Chip, a dot or text: `tone("amber")`. */
115
+ export declare function tone(name: "red" | "emerald" | "amber" | "blue" | "green" | "cyan" | "sky" | "violet" | "pink" | "orange", shade?: 300 | 400): string;
116
+ /** How many, after a heading or a row. */
117
+ export declare function CountChip({ n, title }: {
118
+ n: number;
119
+ title?: string;
120
+ }): import("react").JSX.Element;
121
+ /**
122
+ * What a space is for and the way in, when it has nothing to show: a sentence and, usually, a
123
+ * button. `card` puts it in a quiet card, for when it is the only thing on screen.
124
+ */
125
+ export declare function EmptyState({ icon, title, children, action, card }: {
126
+ icon?: ReactNode;
127
+ /** A short line above the sentence, when the space needs naming. */
128
+ title?: string;
129
+ children: ReactNode;
130
+ action?: {
131
+ label: string;
132
+ onClick: () => void;
133
+ };
134
+ card?: boolean;
135
+ }): import("react").JSX.Element;
136
+ /**
137
+ * An icon-only button. `toolbar` (size-7) for a panel's own actions, `row` (size-5) for one inside
138
+ * a list row, shown on the row's hover by the caller. `danger` turns it red on hover only; `active`
139
+ * marks a toggle that is on. `title` is required: it is the only label the button has.
140
+ */
141
+ export declare function IconButton({ title, onClick, children, size, danger, active, disabled }: {
142
+ title: string;
143
+ onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
144
+ children: ReactNode;
145
+ size?: "toolbar" | "row";
146
+ danger?: boolean;
147
+ active?: boolean;
148
+ disabled?: boolean;
149
+ }): import("react").JSX.Element;
@@ -19,7 +19,7 @@ export declare function useNarrow(px: number): [boolean, (el: HTMLElement | null
19
19
  * Listeners go on `window`, not the divider, so a fast drag that outruns the pointer keeps
20
20
  * resizing instead of stopping the moment the cursor leaves the 4px handle.
21
21
  */
22
- export declare function useResize({ initial, min, max, direction, invert, }: {
22
+ export declare function useResize({ initial, min, max, direction, invert, collapseBelow, onCollapse, }: {
23
23
  initial: number;
24
24
  min: number;
25
25
  max: number;
@@ -31,6 +31,13 @@ export declare function useResize({ initial, min, max, direction, invert, }: {
31
31
  * would grow a pane that is actually being squeezed from above.
32
32
  */
33
33
  invert?: boolean;
34
+ /**
35
+ * Dragged this far down (below `min`), the drag ends and `onCollapse` runs: the pane snaps shut
36
+ * rather than stopping at its minimum. Its size goes back to what it was when the drag began, so
37
+ * opening it again gives the width it had.
38
+ */
39
+ collapseBelow?: number;
40
+ onCollapse?: () => void;
34
41
  }): readonly [number, {
35
42
  readonly onMouseDown: (e: React.MouseEvent) => void;
36
43
  }, boolean];
@@ -0,0 +1,79 @@
1
+ // GENERATED by sdk/gen-types.mjs from Deck's source — do not edit.
2
+ export interface BookmarkNode {
3
+ id: string;
4
+ title: string;
5
+ /** A page has a URL; a folder has children instead. */
6
+ url?: string;
7
+ children?: BookmarkNode[];
8
+ }
9
+ export interface Bookmarks {
10
+ bar: BookmarkNode[];
11
+ other: BookmarkNode[];
12
+ }
13
+ export declare const EMPTY: Bookmarks;
14
+ export declare const isFolder: (n: BookmarkNode) => boolean;
15
+ /** An id unique within this run and across runs: time, then a counter. */
16
+ export declare const newId: () => string;
17
+ /** Every page in the tree, depth first, with the folder titles above it. */
18
+ export declare function pages(b: Bookmarks): {
19
+ node: BookmarkNode;
20
+ path: string[];
21
+ }[];
22
+ /** The bookmark for `url`, if there is one (the first, when it was saved twice). */
23
+ export declare const findByUrl: (b: Bookmarks, url: string) => BookmarkNode | undefined;
24
+ /** Every folder, for a "save in" list: the two roots first, then each folder by its path. */
25
+ export declare function folders(b: Bookmarks): {
26
+ id: string;
27
+ path: string;
28
+ }[];
29
+ /** Add `node` at the end of `folder`. */
30
+ export declare const add: (b: Bookmarks, folder: string, node: BookmarkNode) => Bookmarks;
31
+ /** Remove the node with `id`, wherever it is (a folder goes with what is in it). */
32
+ export declare function remove(b: Bookmarks, id: string): Bookmarks;
33
+ /** Change a node's title or URL. */
34
+ export declare function update(b: Bookmarks, id: string, patch: Partial<Pick<BookmarkNode, "title" | "url">>): Bookmarks;
35
+ /** The node with `id`, and the id of the folder it is in. */
36
+ export declare function locate(b: Bookmarks, id: string): {
37
+ node: BookmarkNode;
38
+ folder: string;
39
+ } | null;
40
+ /** Move a node to the end of another folder. A folder cannot go inside itself. */
41
+ export declare function move(b: Bookmarks, id: string, folder: string): Bookmarks;
42
+ /**
43
+ * A Chromium browser's Bookmarks file (Chrome, Edge, Brave all keep the same JSON) as Deck's
44
+ * nodes: its bar, its other bookmarks, and on Chrome its mobile ones, each as given. Anything that
45
+ * is not a page or a folder (a separator, an app link) is left out.
46
+ */
47
+ export declare function fromChrome(json: string): {
48
+ bar: BookmarkNode[];
49
+ other: BookmarkNode[];
50
+ };
51
+ /** An import, as one folder in each root named after where it came from ("From Chrome"), left out
52
+ * of a root it has nothing for. */
53
+ export declare function addImport(b: Bookmarks, from: string, got: {
54
+ bar: BookmarkNode[];
55
+ other: BookmarkNode[];
56
+ }): Bookmarks;
57
+ /** How many pages a set of nodes holds. */
58
+ export declare const countPages: (ns: readonly BookmarkNode[]) => number;
59
+ /** Deck's bookmarks as a bookmarks HTML file. */
60
+ export declare function toHtml(b: Bookmarks): string;
61
+ /** A bookmarks HTML file as Deck's two roots. Read by its tags, not as a page: folders (H3) open
62
+ * the list (DL) that follows them, pages are links (A HREF). */
63
+ export declare function fromHtml(html: string): {
64
+ bar: BookmarkNode[];
65
+ other: BookmarkNode[];
66
+ };
67
+ /**
68
+ * An import merged in: a folder joins the folder of the same name where it lands (made if there
69
+ * is none), and a page already bookmarked anywhere is left out. So a file exported from Deck
70
+ * restores into the same places, and importing one twice adds nothing the second time.
71
+ * Returns the bookmarks and how many pages were added.
72
+ */
73
+ export declare function mergeImport(b: Bookmarks, got: {
74
+ bar: BookmarkNode[];
75
+ other: BookmarkNode[];
76
+ }): {
77
+ bookmarks: Bookmarks;
78
+ added: number;
79
+ };