@standardagents/code-plugin-sdk 1.0.0-alpha.7-theme.0 → 1.0.0-alpha.9-icons.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
@@ -11,8 +11,8 @@ Its `package.json` includes a static `standardPlugin` manifest.
11
11
  {
12
12
  "name": "example-status",
13
13
  "type": "module",
14
- "peerDependencies": { "@standardagents/code-plugin-sdk": "^1.0.0-alpha.7-theme.0" },
15
- "devDependencies": { "@standardagents/code-plugin-sdk": "^1.0.0-alpha.7-theme.0" },
14
+ "peerDependencies": { "@standardagents/code-plugin-sdk": "^1.0.0-alpha.9-icons.0" },
15
+ "devDependencies": { "@standardagents/code-plugin-sdk": "^1.0.0-alpha.9-icons.0" },
16
16
  "standardPlugin": {
17
17
  "apiVersion": 1,
18
18
  "id": "example-status",
@@ -195,6 +195,27 @@ The Standard Code build workflow publishes this package when the version in
195
195
  under `latest`. Feature branches publish under their product branch tag. Authors
196
196
  can install a published prerelease by its exact version.
197
197
 
198
+ ## Sidebar card titles
199
+
200
+ Sidebar card titles use muted, regular-weight text. A card declaration may
201
+ supply an `icon` containing one Nerd Font private-use glyph, for example
202
+ `"icon": "\uDB81\uDCC5"` (a gauge). The author chooses the card's topic icon.
203
+ The host shows it only on the viewer's Nerd Font tier. Other tiers omit the
204
+ icon and its spacing. Omitted icons have no fallback. This applies to bordered
205
+ and borderless cards. Empty titles remain empty.
206
+
207
+ A card view may replace its declared title through
208
+ `ui.view(root, { title: [{ text: 'Builds ' }, { text: '●', foreground: 'green' }] })`.
209
+ The optional `title` uses native `TextSpan` fields, including `#RRGGBB` and the terminal colors `black`, `red`, `green`,
210
+ `yellow`, `blue`, `magenta`, `cyan`, and `white`;
211
+ `ui.span()` is a body-view builder with a different shape. Titles accept 1–32
212
+ passive spans and at most 512 UTF-8 bytes of combined, nonblank single-line text.
213
+ Actions, control characters, and bidi formatting are rejected. Each replacement
214
+ updates the body and title together. Omitting `title` restores the manifest title.
215
+ This option belongs to card views. Plugins own the meaning and color of any
216
+ status light; the SDK adds no health indicator. Host title styling and supported
217
+ author-selected Nerd Font icons apply around the author-supplied spans.
218
+
198
219
  ## License
199
220
 
200
221
  MIT. See `LICENSE`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@standardagents/code-plugin-sdk",
3
- "version": "1.0.0-alpha.7-theme.0",
3
+ "version": "1.0.0-alpha.9-icons.0",
4
4
  "type": "module",
5
5
  "description": "Standard Code plugin authoring SDK",
6
6
  "license": "MIT",
package/src/index.d.ts CHANGED
@@ -16,6 +16,8 @@ export interface ContributionDeclaration {
16
16
  anchor: Anchor;
17
17
  /** Sidebar card border. Defaults to true; false retains the title and content padding. */
18
18
  border?: boolean;
19
+ /** One author-selected Nerd Font glyph; omitted on non-Nerd viewer tiers. */
20
+ icon?: string;
19
21
  title?: string;
20
22
  merge?: 'by-machine' | 'by-identity';
21
23
  width?: 'full' | 'half';
@@ -157,7 +159,7 @@ export interface ButtonNode { type: 'button'; label: string; action: ViewAction
157
159
  export type ViewNode = StackNode | RowNode | DividerNode | TextNode | BadgeNode | DotNode | ProgressNode |
158
160
  SegmentsNode | CardNode | StatNode | KvNode | TabsNode | SelectNode | TableNode | LogNode | ButtonNode;
159
161
  /** A host-rendered view tree. Cards, panels, and sections accept it. */
160
- export interface ViewContent { kind: 'view'; root: ViewNode }
162
+ export interface ViewContent { kind: 'view'; root: ViewNode; /** Passive styled title for card views. */ title?: TextSpan[] }
161
163
 
162
164
  /** Slots and overlays accept rows, text, or canvas. */
163
165
  export type DrawnContent = RowsContent | TextContent | CanvasContent;
@@ -410,7 +412,7 @@ export function validateView(root: unknown, options?: { kind?: SurfaceKind; mani
410
412
  type Options<T> = Omit<T, 'type' | 'children'>;
411
413
  /** Optional builders. Each returns the plain protocol JSON for one node; hand-written JSON is equivalent. */
412
414
  export const ui: {
413
- view(root: ViewNode): ViewContent;
415
+ view(root: ViewNode, options?: { title?: TextSpan[] }): ViewContent;
414
416
  action(actionId: string, options?: { value?: string; opens?: string }): ViewAction;
415
417
  span(text: string, options?: Omit<ViewSpan, 'text'>): ViewSpan;
416
418
  stack(children: ViewNode[], options?: Options<StackNode>): StackNode;
package/src/manifest.mjs CHANGED
@@ -76,6 +76,9 @@ export function validateManifest(value) {
76
76
  ensure(object(declaration) && identifier(declaration.id) && !ids.has(declaration.id) &&
77
77
  SURFACE_KINDS.includes(declaration.kind) && ANCHORS.includes(declaration.anchor), 'invalid_manifest', 'Invalid contribution declaration')
78
78
  ids.add(declaration.id)
79
+ ensure(declaration.icon === undefined || (declaration.kind === 'card' && typeof declaration.icon === 'string' &&
80
+ /^[\uE000-\uF8FF\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]$/u.test(declaration.icon)),
81
+ 'invalid_manifest', 'Card icon must be one Nerd Font private-use glyph')
79
82
  ensure(declaration.border === undefined || (typeof declaration.border === 'boolean' && declaration.kind === 'card'),
80
83
  'invalid_manifest', 'Only sidebar cards declare a boolean border preference')
81
84
  for (const [key, choices] of Object.entries({ merge: ['by-machine', 'by-identity'], width: ['full', 'half'],
package/src/runtime.mjs CHANGED
@@ -11,41 +11,41 @@ function boundedText(value, label, limit = 512) {
11
11
  return value
12
12
  }
13
13
  const HOVER_FORBIDDEN = /[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/
14
- function validateCanvasHover(value) {
14
+ function validatePassiveText(value, limit = LIMITS.canvasHoverBytes, label = 'canvas hover') {
15
15
  if (typeof value === 'string') {
16
16
  ensure(value.trim().length > 0 && !HOVER_FORBIDDEN.test(value),
17
- 'invalid_payload', 'Invalid canvas hover text')
18
- ensure(Buffer.byteLength(value) <= LIMITS.canvasHoverBytes,
19
- 'payload_too_large', `Canvas hover text exceeds ${LIMITS.canvasHoverBytes} bytes`)
17
+ 'invalid_payload', `Invalid ${label} text`)
18
+ ensure(Buffer.byteLength(value) <= limit,
19
+ 'payload_too_large', `${label} text exceeds ${limit} bytes`)
20
20
  return
21
21
  }
22
- ensure(Array.isArray(value), 'invalid_payload', 'Invalid canvas hover spans')
23
- ensure(value.length > 0, 'invalid_payload', 'Canvas hover spans must contain text')
24
- ensure(value.length <= 32, 'payload_too_large', 'Canvas hover spans exceed 32 spans')
22
+ ensure(Array.isArray(value), 'invalid_payload', `Invalid ${label} spans`)
23
+ ensure(value.length > 0, 'invalid_payload', `${label} spans must contain text`)
24
+ ensure(value.length <= 32, 'payload_too_large', `${label} spans exceed 32 spans`)
25
25
  let text = ''
26
26
  for (const span of value) {
27
- ensure(object(span), 'invalid_payload', 'Invalid canvas hover span')
27
+ ensure(object(span), 'invalid_payload', `Invalid ${label} span`)
28
28
  ensure(typeof span.text === 'string' && !HOVER_FORBIDDEN.test(span.text),
29
- 'invalid_payload', 'Invalid canvas hover span text')
29
+ 'invalid_payload', `Invalid ${label} span text`)
30
30
  for (const field of ['foreground', 'background']) {
31
31
  if (span[field] !== undefined) {
32
32
  ensure(typeof span[field] === 'string' && span[field].trim().length > 0 &&
33
- !HOVER_FORBIDDEN.test(span[field]), 'invalid_payload', `Invalid canvas hover span ${field}`)
33
+ !HOVER_FORBIDDEN.test(span[field]), 'invalid_payload', `Invalid ${label} span ${field}`)
34
34
  ensure(Buffer.byteLength(span[field]) <= 64,
35
- 'payload_too_large', `Canvas hover span ${field} exceeds 64 bytes`)
35
+ 'payload_too_large', `${label} span ${field} exceeds 64 bytes`)
36
36
  }
37
37
  }
38
38
  for (const field of ['bold', 'italic', 'underline']) {
39
39
  ensure(span[field] === undefined || typeof span[field] === 'boolean',
40
- 'invalid_payload', `Invalid canvas hover span ${field}`)
40
+ 'invalid_payload', `Invalid ${label} span ${field}`)
41
41
  }
42
- ensure(span.actionId === undefined, 'invalid_payload', 'Canvas hover spans cannot contain actionId')
42
+ ensure(span.actionId === undefined, 'invalid_payload', `${label} spans cannot contain actionId`)
43
43
  text += span.text
44
44
  }
45
45
  ensure(text.trim().length > 0 && !HOVER_FORBIDDEN.test(text),
46
- 'invalid_payload', 'Invalid canvas hover text')
47
- ensure(Buffer.byteLength(text) <= LIMITS.canvasHoverBytes,
48
- 'payload_too_large', `Canvas hover text exceeds ${LIMITS.canvasHoverBytes} bytes`)
46
+ 'invalid_payload', `Invalid ${label} text`)
47
+ ensure(Buffer.byteLength(text) <= limit,
48
+ 'payload_too_large', `${label} text exceeds ${limit} bytes`)
49
49
  }
50
50
  function validateEntity(entity) {
51
51
  ensure(object(entity) && entityKinds.includes(entity.kind), 'invalid_payload', 'Invalid registration entity')
@@ -91,7 +91,13 @@ function validateContent(content, declaration, manifest) {
91
91
  ensure(object(content) && CONTENT_KINDS.includes(content.kind), 'invalid_payload', 'Invalid surface content')
92
92
  ensure(acceptsContent(kind, content), 'invalid_payload', `A ${kind} contribution cannot show ${content.kind} content`)
93
93
  // The view walk bounds depth before serialization visits the tree.
94
- if (content.kind === 'view') validateView(content.root, { kind: declaration.kind, manifest })
94
+ if (content.kind === 'view') {
95
+ validateView(content.root, { kind: declaration.kind, manifest })
96
+ if (content.title !== undefined) {
97
+ ensure(kind === 'card' && Array.isArray(content.title), 'invalid_payload', 'Styled titles belong to card views')
98
+ validatePassiveText(content.title, 512, 'card title')
99
+ }
100
+ }
95
101
  jsonBytes(content)
96
102
  if (content.kind === 'canvas') {
97
103
  const { columns, rows, shade = 0, hover, themeColors } = content.canvas ?? {}
@@ -108,7 +114,7 @@ function validateContent(content, declaration, manifest) {
108
114
  'invalid_payload', 'Invalid canvas theme color recipe')
109
115
  }
110
116
  }
111
- if (hover !== undefined) validateCanvasHover(hover)
117
+ if (hover !== undefined) validatePassiveText(hover)
112
118
  }
113
119
  }
114
120
 
package/src/view.mjs CHANGED
@@ -231,7 +231,7 @@ const node = (type, fields) => compact({ type, ...fields })
231
231
 
232
232
  /** Optional builders. Each returns the plain protocol JSON for one node. */
233
233
  export const ui = Object.freeze({
234
- view: root => ({ kind: 'view', root }),
234
+ view: (root, { title } = {}) => compact({ kind: 'view', root, title }),
235
235
  action: (actionId, { value, opens } = {}) => compact({ actionId, value, opens }),
236
236
  span: (value, { tone, weight, mono } = {}) => compact({ text: value, tone, weight, mono }),
237
237
  stack: (items, { gap } = {}) => node('stack', { gap, children: items }),