@zhama/tego-plugin-sdk 1.37.1 → 1.37.2

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.
@@ -0,0 +1,500 @@
1
+ {
2
+ "catalogVersion": 1,
3
+ "rules": [
4
+ {
5
+ "id": "TP-MAN-001",
6
+ "severity": "error",
7
+ "guide": "manifest.md",
8
+ "title": "tego.plugin.json is valid",
9
+ "appliesTo": [
10
+ "tego.plugin.json",
11
+ "package.json"
12
+ ],
13
+ "enforcedBy": "doctor"
14
+ },
15
+ {
16
+ "id": "TP-MAN-002",
17
+ "severity": "error",
18
+ "guide": "host-api-versions.md",
19
+ "title": "hostApiRange admits every host capability the code uses",
20
+ "appliesTo": [
21
+ "tego.plugin.json"
22
+ ],
23
+ "enforcedBy": "doctor"
24
+ },
25
+ {
26
+ "id": "TP-PKG-001",
27
+ "severity": "error",
28
+ "guide": "manifest.md",
29
+ "title": "SDK packages come from npm aliases or workspace links, never a plain @tego/* name",
30
+ "appliesTo": [
31
+ "tego.plugin.json",
32
+ "package.json"
33
+ ],
34
+ "enforcedBy": "doctor"
35
+ },
36
+ {
37
+ "id": "TP-PKG-002",
38
+ "severity": "warning",
39
+ "guide": "testing.md",
40
+ "title": "package.json has the lint, type-check, test and build gates",
41
+ "appliesTo": [
42
+ "**/*.test.ts"
43
+ ],
44
+ "enforcedBy": "doctor"
45
+ },
46
+ {
47
+ "id": "TP-PKG-003",
48
+ "severity": "error",
49
+ "guide": "manifest.md",
50
+ "title": "No imports of @tego/shared or host app code; only published packages",
51
+ "appliesTo": [
52
+ "tego.plugin.json",
53
+ "package.json"
54
+ ],
55
+ "enforcedBy": "doctor"
56
+ },
57
+ {
58
+ "id": "TP-CLAW-002",
59
+ "severity": "warning",
60
+ "guide": "claw.md",
61
+ "title": "Each claw skill's frontmatter name matches its folder",
62
+ "appliesTo": [
63
+ "claw/**"
64
+ ],
65
+ "enforcedBy": "doctor"
66
+ },
67
+ {
68
+ "id": "TP-DESK-001",
69
+ "severity": "error",
70
+ "guide": "desktop-web.md",
71
+ "title": "No eval or new Function (the desktop CSP refuses them)",
72
+ "appliesTo": [
73
+ "frontend/src/**"
74
+ ],
75
+ "enforcedBy": "doctor"
76
+ },
77
+ {
78
+ "id": "TP-DESK-002",
79
+ "severity": "error",
80
+ "guide": "desktop-web.md",
81
+ "title": "No addresses built from location.origin (tauri://localhost on desktop)",
82
+ "appliesTo": [
83
+ "frontend/src/**"
84
+ ],
85
+ "enforcedBy": "doctor"
86
+ },
87
+ {
88
+ "id": "TP-DESK-003",
89
+ "severity": "error",
90
+ "guide": "desktop-web.md",
91
+ "title": "External links open with ctx.openExternal, not window.open",
92
+ "appliesTo": [
93
+ "frontend/src/**"
94
+ ],
95
+ "enforcedBy": "doctor"
96
+ },
97
+ {
98
+ "id": "TP-DESK-004",
99
+ "severity": "error",
100
+ "guide": "desktop-web.md",
101
+ "title": "Files are saved with ctx.download, not <a download> or a.download",
102
+ "appliesTo": [
103
+ "frontend/src/**"
104
+ ],
105
+ "enforcedBy": "doctor"
106
+ },
107
+ {
108
+ "id": "TP-DESK-005",
109
+ "severity": "warning",
110
+ "guide": "desktop-web.md",
111
+ "title": "No streamed responses or EventSource from the frontend (the desktop proxy buffers; use ctx.events)",
112
+ "appliesTo": [
113
+ "frontend/src/**"
114
+ ],
115
+ "enforcedBy": "doctor"
116
+ },
117
+ {
118
+ "id": "TP-DESK-006",
119
+ "severity": "warning",
120
+ "guide": "desktop-web.md",
121
+ "title": "Clipboard writes go through ctx.copyText",
122
+ "appliesTo": [
123
+ "frontend/src/**"
124
+ ],
125
+ "enforcedBy": "doctor"
126
+ },
127
+ {
128
+ "id": "TP-WEB-001",
129
+ "severity": "error",
130
+ "guide": "desktop-web.md",
131
+ "title": "The frontend calls no third-party URL (the web CSP blocks it); call the backend, which declares egress",
132
+ "appliesTo": [
133
+ "frontend/src/**"
134
+ ],
135
+ "enforcedBy": "doctor"
136
+ },
137
+ {
138
+ "id": "TP-WEB-002",
139
+ "severity": "error",
140
+ "guide": "desktop-web.md",
141
+ "title": "No remote or injected scripts; everything ships in the bundle",
142
+ "appliesTo": [
143
+ "frontend/src/**"
144
+ ],
145
+ "enforcedBy": "doctor"
146
+ },
147
+ {
148
+ "id": "TP-WEB-003",
149
+ "severity": "error",
150
+ "guide": "frontend.md",
151
+ "title": "The frontend calls its backend through ctx.api (createApiClient), never a raw fetch of a path",
152
+ "appliesTo": [
153
+ "frontend/src/**"
154
+ ],
155
+ "enforcedBy": "doctor"
156
+ },
157
+ {
158
+ "id": "TP-WEB-004",
159
+ "severity": "error",
160
+ "guide": "desktop-web.md",
161
+ "title": "No cookie-based auth (document.cookie, hono/cookie); ctx.api carries the credentials",
162
+ "appliesTo": [
163
+ "frontend/src/**"
164
+ ],
165
+ "enforcedBy": "doctor"
166
+ },
167
+ {
168
+ "id": "TP-UI-001",
169
+ "severity": "error",
170
+ "guide": "ui.md",
171
+ "title": "Colors come from token classes: no hex, rgb(), palette classes or dark: overrides",
172
+ "appliesTo": [
173
+ "frontend/src/**/*.tsx",
174
+ "frontend/src/**/*.css"
175
+ ],
176
+ "enforcedBy": "doctor"
177
+ },
178
+ {
179
+ "id": "TP-UI-002",
180
+ "severity": "error",
181
+ "guide": "ui.md",
182
+ "title": "No direct @radix-ui/* imports; use @tego/ui",
183
+ "appliesTo": [
184
+ "frontend/src/**/*.tsx",
185
+ "frontend/src/**/*.css"
186
+ ],
187
+ "enforcedBy": "doctor"
188
+ },
189
+ {
190
+ "id": "TP-UI-003",
191
+ "severity": "error",
192
+ "guide": "ui.md",
193
+ "title": "The plugin's root element carries its scope class (.<pluginId>) when it compiles its own Tailwind",
194
+ "appliesTo": [
195
+ "frontend/src/**/*.tsx",
196
+ "frontend/src/**/*.css"
197
+ ],
198
+ "enforcedBy": "doctor"
199
+ },
200
+ {
201
+ "id": "TP-UI-004",
202
+ "severity": "error",
203
+ "guide": "frontend.md",
204
+ "title": "The frontend has its own error boundary",
205
+ "appliesTo": [
206
+ "frontend/src/**"
207
+ ],
208
+ "enforcedBy": "doctor"
209
+ },
210
+ {
211
+ "id": "TP-UI-005",
212
+ "severity": "warning",
213
+ "guide": "ui.md",
214
+ "title": "Interface copy stays within budget: explanations ≤ 24 characters, any string ≤ 40 (zh-CN)",
215
+ "appliesTo": [
216
+ "frontend/src/**/*.tsx",
217
+ "frontend/src/**/*.css"
218
+ ],
219
+ "enforcedBy": "doctor"
220
+ },
221
+ {
222
+ "id": "TP-I18N-001",
223
+ "severity": "error",
224
+ "guide": "i18n.md",
225
+ "title": "All ten locales, with the same keys as zh-CN, and every nav label defined",
226
+ "appliesTo": [
227
+ "frontend/i18n/**"
228
+ ],
229
+ "enforcedBy": "doctor"
230
+ },
231
+ {
232
+ "id": "TP-I18N-002",
233
+ "severity": "error",
234
+ "guide": "i18n.md",
235
+ "title": "Every locale value keeps the {{placeholders}} of zh-CN and is not empty",
236
+ "appliesTo": [
237
+ "frontend/i18n/**"
238
+ ],
239
+ "enforcedBy": "doctor"
240
+ },
241
+ {
242
+ "id": "TP-I18N-003",
243
+ "severity": "error",
244
+ "guide": "i18n.md",
245
+ "title": "Every literal t('…') key exists in zh-CN",
246
+ "appliesTo": [
247
+ "frontend/i18n/**"
248
+ ],
249
+ "enforcedBy": "doctor"
250
+ },
251
+ {
252
+ "id": "TP-I18N-004",
253
+ "severity": "warning",
254
+ "guide": "i18n.md",
255
+ "title": "Current terminology: 数字员工 / Employee, never 数字分身 / digital avatar",
256
+ "appliesTo": [
257
+ "frontend/i18n/**"
258
+ ],
259
+ "enforcedBy": "doctor"
260
+ },
261
+ {
262
+ "id": "TP-I18N-005",
263
+ "severity": "error",
264
+ "guide": "i18n.md",
265
+ "title": "tegoAccess.* strings are merged by pack; the plugin locales do not copy them",
266
+ "appliesTo": [
267
+ "frontend/i18n/**"
268
+ ],
269
+ "enforcedBy": "doctor"
270
+ },
271
+ {
272
+ "id": "TP-BE-001",
273
+ "severity": "error",
274
+ "guide": "backend.md",
275
+ "title": "Identity comes from the verified ctx, never from the raw x-tego-plugin-* headers",
276
+ "appliesTo": [
277
+ "backend/src/**"
278
+ ],
279
+ "enforcedBy": "doctor"
280
+ },
281
+ {
282
+ "id": "TP-BE-002",
283
+ "severity": "error",
284
+ "guide": "access.md",
285
+ "title": "No host role names in business code; ask the access engine",
286
+ "appliesTo": [
287
+ "shared/access.json",
288
+ "backend/src/access*.ts",
289
+ "backend/src/**/access/**"
290
+ ],
291
+ "enforcedBy": "doctor"
292
+ },
293
+ {
294
+ "id": "TP-BE-003",
295
+ "severity": "error",
296
+ "guide": "migrations.md",
297
+ "title": "Migrations are <ordinal>_<name>.sql with unique ordinals",
298
+ "appliesTo": [
299
+ "backend/migrations/**",
300
+ "backend/src/**/schema*.ts",
301
+ "backend/drizzle.config.ts"
302
+ ],
303
+ "enforcedBy": "doctor"
304
+ },
305
+ {
306
+ "id": "TP-BE-004",
307
+ "severity": "error",
308
+ "guide": "migrations.md",
309
+ "title": "Committed migrations are never edited or deleted",
310
+ "appliesTo": [
311
+ "backend/migrations/**",
312
+ "backend/src/**/schema*.ts",
313
+ "backend/drizzle.config.ts"
314
+ ],
315
+ "enforcedBy": "doctor"
316
+ },
317
+ {
318
+ "id": "TP-BE-005",
319
+ "severity": "warning",
320
+ "guide": "backend.md",
321
+ "title": "Backend errors are logged through host.log, not console",
322
+ "appliesTo": [
323
+ "backend/src/**"
324
+ ],
325
+ "enforcedBy": "doctor"
326
+ },
327
+ {
328
+ "id": "TP-BE-006",
329
+ "severity": "error",
330
+ "guide": "backend.md",
331
+ "title": "Configuration comes from host.config, never process.env",
332
+ "appliesTo": [
333
+ "backend/src/**"
334
+ ],
335
+ "enforcedBy": "doctor"
336
+ },
337
+ {
338
+ "id": "TP-BE-007",
339
+ "severity": "warning",
340
+ "guide": "backend.md",
341
+ "title": "The backend app turns every error into JSON with pluginErrorHandler",
342
+ "appliesTo": [
343
+ "backend/src/**"
344
+ ],
345
+ "enforcedBy": "doctor"
346
+ },
347
+ {
348
+ "id": "TP-ACC-001",
349
+ "severity": "error",
350
+ "guide": "access.md",
351
+ "title": "With an access catalog, a test asserts every route has a policy (assertRouteCoverage)",
352
+ "appliesTo": [
353
+ "shared/access.json",
354
+ "backend/src/access*.ts",
355
+ "backend/src/**/access/**"
356
+ ],
357
+ "enforcedBy": "doctor"
358
+ },
359
+ {
360
+ "id": "TP-ACC-002",
361
+ "severity": "error",
362
+ "guide": "access.md",
363
+ "title": "With an access catalog, start() reconciles it (reconcile(host))",
364
+ "appliesTo": [
365
+ "shared/access.json",
366
+ "backend/src/access*.ts",
367
+ "backend/src/**/access/**"
368
+ ],
369
+ "enforcedBy": "doctor"
370
+ },
371
+ {
372
+ "id": "TP-TEST-001",
373
+ "severity": "warning",
374
+ "guide": "testing.md",
375
+ "title": "The plugin has tests",
376
+ "appliesTo": [
377
+ "**/*.test.ts"
378
+ ],
379
+ "enforcedBy": "doctor"
380
+ },
381
+ {
382
+ "id": "TP-TEST-002",
383
+ "severity": "warning",
384
+ "guide": "testing.md",
385
+ "title": "Each test file starts with a Business Cases list",
386
+ "appliesTo": [
387
+ "**/*.test.ts"
388
+ ],
389
+ "enforcedBy": "doctor"
390
+ },
391
+ {
392
+ "id": "TP-TPL-001",
393
+ "severity": "error",
394
+ "guide": "agent-templates.md",
395
+ "title": "A template whose content changed raises its version (existing employees only see updates by version)",
396
+ "appliesTo": [
397
+ "templates/**"
398
+ ],
399
+ "enforcedBy": "doctor"
400
+ },
401
+ {
402
+ "id": "TP-TPL-002",
403
+ "severity": "warning",
404
+ "guide": "agent-templates.md",
405
+ "title": "usageGuide.summary is at most 30 characters and quickPrompts lists 3–4 prompts",
406
+ "appliesTo": [
407
+ "templates/**"
408
+ ],
409
+ "enforcedBy": "doctor"
410
+ }
411
+ ],
412
+ "guides": [
413
+ {
414
+ "file": "manifest.md",
415
+ "appliesTo": [
416
+ "tego.plugin.json",
417
+ "package.json"
418
+ ]
419
+ },
420
+ {
421
+ "file": "host-api-versions.md",
422
+ "appliesTo": [
423
+ "tego.plugin.json"
424
+ ]
425
+ },
426
+ {
427
+ "file": "desktop-web.md",
428
+ "appliesTo": [
429
+ "frontend/src/**"
430
+ ]
431
+ },
432
+ {
433
+ "file": "frontend.md",
434
+ "appliesTo": [
435
+ "frontend/src/**"
436
+ ]
437
+ },
438
+ {
439
+ "file": "ui.md",
440
+ "appliesTo": [
441
+ "frontend/src/**/*.tsx",
442
+ "frontend/src/**/*.css"
443
+ ]
444
+ },
445
+ {
446
+ "file": "i18n.md",
447
+ "appliesTo": [
448
+ "frontend/i18n/**"
449
+ ]
450
+ },
451
+ {
452
+ "file": "backend.md",
453
+ "appliesTo": [
454
+ "backend/src/**"
455
+ ]
456
+ },
457
+ {
458
+ "file": "storage.md",
459
+ "appliesTo": [
460
+ "backend/src/**/storage*",
461
+ "backend/src/**/upload*",
462
+ "backend/src/**/files*"
463
+ ]
464
+ },
465
+ {
466
+ "file": "migrations.md",
467
+ "appliesTo": [
468
+ "backend/migrations/**",
469
+ "backend/src/**/schema*.ts",
470
+ "backend/drizzle.config.ts"
471
+ ]
472
+ },
473
+ {
474
+ "file": "access.md",
475
+ "appliesTo": [
476
+ "shared/access.json",
477
+ "backend/src/access*.ts",
478
+ "backend/src/**/access/**"
479
+ ]
480
+ },
481
+ {
482
+ "file": "claw.md",
483
+ "appliesTo": [
484
+ "claw/**"
485
+ ]
486
+ },
487
+ {
488
+ "file": "testing.md",
489
+ "appliesTo": [
490
+ "**/*.test.ts"
491
+ ]
492
+ },
493
+ {
494
+ "file": "agent-templates.md",
495
+ "appliesTo": [
496
+ "templates/**"
497
+ ]
498
+ }
499
+ ]
500
+ }
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: add-agent-template
3
+ description: Add or update a 数字员工 template shipped with a Tego plugin (template.config.json, workspace files, usage guide, quick prompts, version). Use when the plugin should offer a ready-made employee or an existing template changes.
4
+ ---
5
+
6
+ # Add or update an agent template
7
+
8
+ Read first: `guides/agent-templates.md`, `guides/claw.md`.
9
+
10
+ 1. Create `templates/<key>/template.config.json`, `workspace/*.md` (IDENTITY, SOUL, AGENTS,
11
+ TOOLS…) and `avatar.png`; experts go in `experts/<id>/` matching `agents` (main first).
12
+ 2. Declare it in `tego.plugin.json` → `agentTemplates: [{ key, entry: "templates/<key>/template.json", icon }]`.
13
+ 3. Fill `content.usageGuide` (`summary` ≤ 30 characters, `setup` with host-checkable items only,
14
+ `outputs`, `boundaries`) and 3–4 `quickPrompts` with unique keys (TP-TPL-002). Plugin business
15
+ readiness goes through the manifest's `agentReadiness`, not the template.
16
+ 4. When changing an existing template, raise its `version` (TP-TPL-001), or existing employees
17
+ will not see the update.
18
+ 5. Rebuild `template.json` with `tego-plugin-pack templates` (never your own script) and commit it.
19
+ 6. Run `tego-plugin check`.
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: add-claw-tool
3
+ description: Add a tool (and the skill that teaches the employee to use it) to a Tego plugin's claw part. Use when the plugin's 数字员工 needs a new capability or an existing tool changes.
4
+ ---
5
+
6
+ # Add a claw tool
7
+
8
+ Read first: `guides/claw.md`, `guides/access.md`, `guides/testing.md`.
9
+
10
+ 1. Register the tool in `claw/src/index.ts`; `register()` is idempotent and tolerates
11
+ early-phase config. Log with `api.logger` (one string).
12
+ 2. The tool calls your backend through the Tego API request
13
+ (`/api/v1/tego-os/plugin-api/<id>/…`) with the delegation the runtime provides, so the backend
14
+ applies the user's permissions. If the employee needs a permission, declare `agent.requires`
15
+ in the catalog; never widen it silently.
16
+ 3. List the tool in `contracts.tools` in `tego.plugin.json`.
17
+ 4. Teach it in a skill: `claw/skills/<name>/SKILL.md` whose frontmatter `name` equals the folder
18
+ (TP-CLAW-002) and whose `description` says when to use it; every tool it mentions exists.
19
+ 5. Test with `assertClawSkills(skillsDir, toolNames)` and check the registered tools equal
20
+ `contracts.tools`.
21
+ 6. Run `tego-plugin check`.
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: add-i18n-strings
3
+ description: Add or change user-visible text in a Tego plugin across all ten locales with matching keys and placeholders. Use whenever a change adds, renames or rewords any string the user sees.
4
+ ---
5
+
6
+ # Add i18n strings
7
+
8
+ Read first: `guides/i18n.md`, the copy rules in `guides/ui.md`.
9
+
10
+ 1. Add the key to `frontend/i18n/zh-CN.json` first, under the plugin's root key
11
+ (`notes.home.title`). Keep it short: explanations ≤ 24 characters, any string ≤ 40 (TP-UI-005).
12
+ 2. Add the same key to every other locale: `zh-TW en ja ko de es fr it pt` (TP-I18N-001), with a
13
+ real translation (not empty, not the zh-CN text) and exactly the same `{{placeholders}}`
14
+ (TP-I18N-002).
15
+ 3. Use the key literally (`t('notes.home.title')`) so doctor can verify it exists (TP-I18N-003);
16
+ for keys built at runtime, list every variant in a test.
17
+ 4. Say 数字员工 / Employee through `{{avatarTerm}}`, never 数字分身 / digital avatar (TP-I18N-004).
18
+ Never copy `tegoAccess.*` strings into the plugin (TP-I18N-005).
19
+ 5. Run `tego-plugin check` (`assertLocaleParity` in the i18n test keeps the files in step).
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: add-migration
3
+ description: Change a Tego plugin's database schema with a new append-only migration generated from the drizzle schema. Use when adding or changing tables, columns or indexes.
4
+ ---
5
+
6
+ # Add a database migration
7
+
8
+ Read first: `guides/migrations.md`, `guides/storage.md`.
9
+
10
+ 1. **Never edit or delete a committed migration** (TP-BE-004): an install may have applied it.
11
+ Every change is a new file.
12
+ 2. Change the drizzle schema (`backend/src/**/schema*.ts`), then generate SQL with drizzle-kit and
13
+ copy it into place with `tego-plugin migrations sync`. Hand-write only what drizzle cannot
14
+ express (functions, triggers, RLS), in a new `<ordinal>_<name>.sql` with the next ordinal
15
+ (TP-BE-003).
16
+ 3. Keep data in your own schema (`host.db`); never reference another plugin's or the host's
17
+ tables.
18
+ 4. With an access catalog, the `tego_access_*` tables come from your own migration (re-export
19
+ `@tego/plugin-sdk/access/drizzle` in the schema before generating).
20
+ 5. **Test** the new SQL against a real database: `createTestPluginHost({ pluginId, migrationsDir })`
21
+ (`TEST_DATABASE_URL`) applies every migration from scratch.
22
+ 6. Run `tego-plugin migrations check` and `tego-plugin check`.
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: add-route-with-access
3
+ description: Add a backend route (with its permission, policy, validation and tests) to a Tego plugin. Use when the plugin needs a new API endpoint, a new action users can take, or data some users may see and others may not.
4
+ ---
5
+
6
+ # Add a backend route with access control
7
+
8
+ Read first: `guides/backend.md`, `guides/access.md`, `guides/testing.md`.
9
+
10
+ 1. **Decide who may call it.** Find or add the permission in the access catalog
11
+ (`shared/access.json`): a permission id, its scope, which built-in roles get it, and the host
12
+ role bindings. Raise the catalog `version` if the meaning of an existing permission changes.
13
+ Never check host role names in code (TP-BE-002).
14
+ 2. **Validate the input** with zod (`readBody` / `readQuery` from `@tego/plugin-sdk/hono`).
15
+ 3. **Write the route** in the backend Hono app with exactly one policy:
16
+ `policy.authenticated()`, `policy.permission('<id>')` or `policy.resource(...)` for a single
17
+ record; lists use `session.filter('<permission>', { alias })` in the SQL. `policy.public()` only
18
+ for routes listed in the manifest's `publicRoutes`.
19
+ 4. **Data and config:** only your schema through `host.db`; configuration from
20
+ `host.config.current()`, never `process.env` (TP-BE-006). Schema changes → the
21
+ `add-migration` skill.
22
+ 5. **Errors:** throw coded errors; the app's `app.onError(pluginErrorHandler())` turns them into
23
+ JSON (TP-BE-007). Log with `host.log`, never `console.error` (TP-BE-005).
24
+ 6. **Tests** (`backend/src/*.test.ts`, starting with a Business Cases list): an allow case and a
25
+ deny case for the permission, the happy path against `createTestPluginHost`, and keep
26
+ `assertRouteCoverage(app)` passing (TP-ACC-001).
27
+ 7. **Frontend:** call it through the `createApiClient` client (`ctx.api`), never a raw `fetch`
28
+ (TP-WEB-003); hide what the user may not do with `useAccess()` / `<Can>` (presentation only).
29
+ 8. **New strings** for permissions or errors go into all ten locales (`add-i18n-strings`).
30
+ 9. Run `tego-plugin check` and fix everything it reports.
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: add-screen
3
+ description: Add a page or screen to a Tego plugin's frontend (route, navigation, data loading, UI with @tego/ui, i18n). Use when the user wants a new page, view, form, list or dashboard in the plugin.
4
+ ---
5
+
6
+ # Add a frontend screen
7
+
8
+ Read first: `guides/frontend.md`, `guides/ui.md`, `guides/desktop-web.md`, `guides/i18n.md`.
9
+
10
+ 1. **Route:** add the path to the pure `lib/route.ts` parser (`parseRoute(path, search)` returns a
11
+ union) and test it. Navigate with `ctx.navigate`; paths live under `ctx.basePath` and have no
12
+ dotted segment. If it is a top-level page, add a nav entry in `tego.plugin.json` with a
13
+ `labelKey` that exists in every locale.
14
+ 2. **Data:** load through the `createApiClient` client with TanStack Query; show errors with
15
+ `describeApiError(error, '<ns>')`. Live changes: `useLiveUpdates(...)` (ctx.events), never
16
+ `EventSource` (TP-DESK-005).
17
+ 3. **UI:** compose `@tego/ui` components, never `@radix-ui/*` (TP-UI-002). Colors only through
18
+ token classes, no hex or `dark:` (TP-UI-001). Status with text or an icon, not color alone;
19
+ ids, numbers and times in `font-mono tabular-nums`. No card inside a card.
20
+ 4. **Copy:** short labels (≤ 40 characters, explanations ≤ 24 in zh-CN, TP-UI-005); longer help
21
+ goes into an `InfoTip`; an empty state is one sentence and one action. Every string in all ten
22
+ locales (`add-i18n-strings`).
23
+ 5. **Shell differences:** links with `ctx.openExternal`, files with `ctx.download`, copying with
24
+ `ctx.copyText` (hostApiRange ^1.37.0 or feature-detect), never `location.origin`.
25
+ 6. **Access:** gate actions with `useAccess()` / `<Can>`; the backend still enforces them.
26
+ 7. Run `tego-plugin check`; open the toolbar's Dev preview to look at the page in the web and the
27
+ desktop shell.