@gxp-dev/tools 2.0.76 → 2.0.78
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/bin/lib/lint/schemas/app-manifest.schema.json +4 -0
- package/bin/lib/lint/schemas/configuration.schema.json +5 -0
- package/bin/lib/utils/ai-scaffold.js +1 -0
- package/mcp/lib/config-ops.js +9 -1
- package/mcp/lib/config-tools.js +14 -2
- package/package.json +1 -1
- package/runtime/vite.config.js +15 -9
- package/template/.claude/agents/gxp-developer.md +99 -0
- package/template/AGENTS.md +105 -0
- package/template/GEMINI.md +33 -0
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
"configurationFile": { "type": "string" },
|
|
30
30
|
"appInstructionsFile": { "type": "string" },
|
|
31
31
|
"defaultStylingFile": { "type": "string" },
|
|
32
|
+
"formTemplate": {
|
|
33
|
+
"type": "boolean",
|
|
34
|
+
"description": "Declares this plugin as a form/quiz/survey app. When true, the platform expects `configuration.json` to include a `formTemplate` root key — an array of cards defining the starter question set an admin can customize on install."
|
|
35
|
+
},
|
|
32
36
|
"appInstructions": { "type": "string" },
|
|
33
37
|
"defaultStyling": { "type": "string" },
|
|
34
38
|
"configuration": {
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
"description": "Top-level array of tab definitions. Each item is a card (usually card_list or fields_list) rendered under the plugin's configuration panel.",
|
|
12
12
|
"items": { "$ref": "card.schema.json" }
|
|
13
13
|
},
|
|
14
|
+
"formTemplate": {
|
|
15
|
+
"type": "array",
|
|
16
|
+
"description": "Starter form/quiz/survey question set for plugins that act as a form app. Each item is a card defining a form section (use `fields_list` for question groups or `card_list` to nest further). The admin can copy/customize these when they install the plugin. Only meaningful when `formTemplate` is true in app-manifest.json.",
|
|
17
|
+
"items": { "$ref": "card.schema.json" }
|
|
18
|
+
},
|
|
14
19
|
"title": { "type": ["string", "null"] },
|
|
15
20
|
"description": { "type": ["string", "null"] },
|
|
16
21
|
"version": { "type": ["string", "integer", "null"] }
|
|
@@ -1009,6 +1009,7 @@ function buildInteractiveInitialPrompt(projectName, description, provider) {
|
|
|
1009
1009
|
"- What real-world data it reads/writes — identify the concrete platform operationIds via the MCP, never invent them",
|
|
1010
1010
|
"- Which real-time events matter (use `api_find_events_for_operation` for each planned operationId)",
|
|
1011
1011
|
"- Every piece of admin-editable content: strings, assets, colors/thresholds/settings, feature toggles",
|
|
1012
|
+
'- Whether the plugin uses the platform\'s **form/quiz/survey API** at all. The rule has two independent parts — do not collapse them. (a) **Capability flag.** Any time the plugin calls form/quiz operationIds (creating a form, reading questions, submitting responses, listing form data), set `"formTemplate": true` in `app-manifest.json`. The platform uses this to auto-provision a `ProjectForm` for the install. Required regardless of whether you ship starter questions. (b) **Prepopulated questions.** If you want the admin to install with a starter question set instead of an empty form, also populate `configuration.json`\'s `formTemplate` root array (`config_add_card` with `parent_path: "/formTemplate"` — auto-initializes). Optional payload; the platform seeds the auto-provisioned form from it. At runtime the plugin still declares its form dependencies in `app-manifest.json` (e.g. a `quiz_form` identifier bound to `ProjectForm`) and calls `store.callApi("forms.show", "quiz_form")` / `store.callApi("form_responses.store", "quiz_form", {...})`. End-user questions go in `formTemplate`; admin plugin config stays in `additionalTabs`. Never mix.',
|
|
1012
1013
|
"",
|
|
1013
1014
|
"Then propose a plan — screens/components, data flow, admin configuration form, and the exact keys you'll add to `app-manifest.json` — and get my confirmation before implementing.",
|
|
1014
1015
|
"",
|
package/mcp/lib/config-ops.js
CHANGED
|
@@ -163,7 +163,12 @@ function moveItem(doc, fromPointer, targetArrayPointer, position = "end") {
|
|
|
163
163
|
* cards with their JSON pointer, type, title, and a summary of children.
|
|
164
164
|
*
|
|
165
165
|
* Cards are discovered under:
|
|
166
|
-
* additionalTabs[] (root array of cards)
|
|
166
|
+
* additionalTabs[] (root array of cards — admin form)
|
|
167
|
+
* formTemplate[] (root array of cards — starter
|
|
168
|
+
* form/quiz/survey questions; only
|
|
169
|
+
* present on form apps where
|
|
170
|
+
* app-manifest.json has
|
|
171
|
+
* `formTemplate: true`)
|
|
167
172
|
* <card>.cards[] (card_list)
|
|
168
173
|
* <card>.tabsList[].cards[] (tabs_list)
|
|
169
174
|
*/
|
|
@@ -201,6 +206,9 @@ function listCards(doc) {
|
|
|
201
206
|
if (Array.isArray(doc?.additionalTabs)) {
|
|
202
207
|
doc.additionalTabs.forEach((c, i) => walk(c, `/additionalTabs/${i}`))
|
|
203
208
|
}
|
|
209
|
+
if (Array.isArray(doc?.formTemplate)) {
|
|
210
|
+
doc.formTemplate.forEach((c, i) => walk(c, `/formTemplate/${i}`))
|
|
211
|
+
}
|
|
204
212
|
return out
|
|
205
213
|
}
|
|
206
214
|
|
package/mcp/lib/config-tools.js
CHANGED
|
@@ -224,7 +224,7 @@ const CONFIG_TOOLS = [
|
|
|
224
224
|
{
|
|
225
225
|
name: "config_add_card",
|
|
226
226
|
description:
|
|
227
|
-
"Add a card under a parent container (additionalTabs, a card_list's cards[], or a tabs_list tab).",
|
|
227
|
+
"Add a card under a parent container (additionalTabs, formTemplate, a card_list's cards[], or a tabs_list tab). The `/additionalTabs` and `/formTemplate` root arrays are auto-initialized if missing, so the first add works without seeding.",
|
|
228
228
|
inputSchema: {
|
|
229
229
|
type: "object",
|
|
230
230
|
properties: {
|
|
@@ -232,7 +232,7 @@ const CONFIG_TOOLS = [
|
|
|
232
232
|
parent_path: {
|
|
233
233
|
type: "string",
|
|
234
234
|
description:
|
|
235
|
-
"JSON pointer to the parent array of cards
|
|
235
|
+
"JSON pointer to the parent array of cards. Top-level options: '/additionalTabs' (admin configuration form) or '/formTemplate' (starter form/quiz questions for form apps). Nested options: '/additionalTabs/0/cards', '/formTemplate/0/cards', etc.",
|
|
236
236
|
},
|
|
237
237
|
card: {
|
|
238
238
|
type: "object",
|
|
@@ -448,6 +448,18 @@ async function handleConfigToolCall(name, args = {}) {
|
|
|
448
448
|
case "config_add_card": {
|
|
449
449
|
const abs = resolveProjectPath(args.path)
|
|
450
450
|
const doc = readJson(abs)
|
|
451
|
+
|
|
452
|
+
// Auto-initialize known root-level card arrays if they're missing
|
|
453
|
+
// so the first `config_add_card` against /formTemplate (or
|
|
454
|
+
// /additionalTabs) works without a separate seeding step.
|
|
455
|
+
const KNOWN_ROOT_ARRAYS = ["/formTemplate", "/additionalTabs"]
|
|
456
|
+
if (KNOWN_ROOT_ARRAYS.includes(args.parent_path)) {
|
|
457
|
+
const rootKey = args.parent_path.slice(1)
|
|
458
|
+
if (!Array.isArray(doc[rootKey])) {
|
|
459
|
+
doc[rootKey] = []
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
451
463
|
const parent = getByPointer(doc, args.parent_path)
|
|
452
464
|
if (!Array.isArray(parent)) {
|
|
453
465
|
throw new Error(
|
package/package.json
CHANGED
package/runtime/vite.config.js
CHANGED
|
@@ -313,19 +313,25 @@ export default defineConfig(async (ctx) => {
|
|
|
313
313
|
vue(),
|
|
314
314
|
// GxP Inspector plugin for browser extension integration
|
|
315
315
|
...(useInspector ? [gxpInspectorPlugin()] : []),
|
|
316
|
-
externalGlobals
|
|
317
|
-
|
|
316
|
+
// `externalGlobals` rewrites `import ... from "vue"` → references to
|
|
317
|
+
// the `Vue` global that the GxP platform exposes on `window`. This is
|
|
318
|
+
// only desirable at **build** time — in dev, the toolkit runtime
|
|
319
|
+
// bootstraps its own Vue + Pinia (via main.js → createApp + app.use),
|
|
320
|
+
// and rewriting `from "pinia"` to a non-existent `window.Pinia`
|
|
321
|
+
// crashes `getActivePinia()` at store init.
|
|
322
|
+
//
|
|
323
|
+
// Using `apply: "build"` restricts the transform to production builds,
|
|
324
|
+
// where it rewrites every module in the final bundle (including
|
|
325
|
+
// transitive node_modules deps) so no bare specifiers leak through.
|
|
326
|
+
{
|
|
327
|
+
...externalGlobals({
|
|
318
328
|
vue: "Vue",
|
|
319
329
|
pinia: "Pinia",
|
|
320
330
|
"@/stores/gxpPortalConfigStore":
|
|
321
331
|
"(window.useGxpStore || (() => { console.warn('useGxpStore not found on window, using fallback'); return {}; }))",
|
|
322
|
-
},
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
// from node_modules (component libraries, etc.). Without this,
|
|
326
|
-
// deps' internal `import { h } from "vue"` leak through as bare
|
|
327
|
-
// specifiers and crash at runtime on the platform.
|
|
328
|
-
),
|
|
332
|
+
}),
|
|
333
|
+
apply: "build",
|
|
334
|
+
},
|
|
329
335
|
// Custom request logging and CORS plugin
|
|
330
336
|
{
|
|
331
337
|
name: "request-logger-cors",
|
|
@@ -522,6 +522,105 @@ Each field's `name` must exactly match the manifest key it controls — that's t
|
|
|
522
522
|
|
|
523
523
|
Every mutation is linter-guarded against `bin/lib/lint/schemas/`. If a write is refused, read the validation error and fix the input — do not reach for `force: true`.
|
|
524
524
|
|
|
525
|
+
## Form / Quiz / Survey Apps — `formTemplate`
|
|
526
|
+
|
|
527
|
+
`formTemplate` appears in **two** files and the two meanings are **independent**. Don't collapse them — treat the manifest flag and the configuration array as separate decisions.
|
|
528
|
+
|
|
529
|
+
### The two keys
|
|
530
|
+
|
|
531
|
+
**1. `app-manifest.json` → `"formTemplate": true` — capability flag.**
|
|
532
|
+
|
|
533
|
+
Set this **any time** the plugin calls the platform's form/quiz/survey API: creating a form, reading questions, submitting responses, listing form data. It tells the platform "this plugin uses a form resource" and opts the install into form-specific behavior — auto-provisioning a `ProjectForm` for the install, enabling the question editor and response viewer in the admin UI, etc. **Required whenever form/quiz operationIds are in play, regardless of whether you ship starter questions.**
|
|
534
|
+
|
|
535
|
+
**2. `configuration.json` → `"formTemplate": [ ...cards ]` — prepopulated starter questions.**
|
|
536
|
+
|
|
537
|
+
An array of cards (same shape as `additionalTabs`, typically `fields_list`) that the platform seeds into the auto-provisioned `ProjectForm` at install time. **Optional** — omit it if you want the admin to build the form from scratch.
|
|
538
|
+
|
|
539
|
+
### The rule — three scenarios
|
|
540
|
+
|
|
541
|
+
| Scenario | Manifest `formTemplate` | Configuration `formTemplate` |
|
|
542
|
+
| ----------------------------------------------------------- | ----------------------------------------------------------- | ---------------------------- |
|
|
543
|
+
| Uses form/quiz API, no starter questions | **`true`** (required) | omit |
|
|
544
|
+
| Uses form/quiz API, with starter questions | **`true`** (required) | `[ ...cards ]` |
|
|
545
|
+
| Doesn't touch the form/quiz API | omit / `false` | must not be set |
|
|
546
|
+
| You have starter questions but the manifest flag is missing | — wrong: the array is dead content until the flag is `true` | — |
|
|
547
|
+
|
|
548
|
+
In one line: **uses form API → flag is true.** **Wants to prepopulate questions → array is populated.** These are independent decisions.
|
|
549
|
+
|
|
550
|
+
### How it lands at runtime
|
|
551
|
+
|
|
552
|
+
1. Install-time: platform sees `formTemplate: true` in the manifest → auto-provisions a `ProjectForm` for this plugin install.
|
|
553
|
+
2. If `configuration.json` has a `formTemplate` array → platform seeds that new form with those cards/questions. If absent → the form is empty and the admin fills it in.
|
|
554
|
+
3. The plugin still declares its form dependencies in `app-manifest.json` (e.g. `{ "identifier": "quiz_form", "model": "ProjectForm" }`) and the admin binds that identifier to the auto-provisioned form. Plugin code then calls `store.callApi("forms.show", "quiz_form")`, `store.callApi("form_responses.store", "quiz_form", {...})`, etc.
|
|
555
|
+
4. Discover the right operationIds via MCP: `api_list_tags`, then `api_list_operation_ids --tag Forms` (or `search_api_endpoints quiz`). Never invent them.
|
|
556
|
+
5. After install the admin owns the form — they can add/remove/reorder questions. Your `formTemplate` is a starting point, not a lock.
|
|
557
|
+
|
|
558
|
+
### Minimal example — form app with starter questions and a dependency
|
|
559
|
+
|
|
560
|
+
```json
|
|
561
|
+
// app-manifest.json
|
|
562
|
+
{
|
|
563
|
+
"name": "welcome-quiz",
|
|
564
|
+
"version": "0.1.0",
|
|
565
|
+
"formTemplate": true,
|
|
566
|
+
"settings": {},
|
|
567
|
+
"strings": { "default": { "title": "Welcome Quiz" } },
|
|
568
|
+
"assets": {},
|
|
569
|
+
"dependencies": [{ "identifier": "quiz_form", "model": "ProjectForm" }],
|
|
570
|
+
"permissions": [
|
|
571
|
+
{
|
|
572
|
+
"identifier": "quiz_form",
|
|
573
|
+
"description": "Read questions and submit responses"
|
|
574
|
+
}
|
|
575
|
+
]
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
```json
|
|
580
|
+
// configuration.json
|
|
581
|
+
{
|
|
582
|
+
"additionalTabs": [
|
|
583
|
+
/* admin-facing plugin config (unchanged) */
|
|
584
|
+
],
|
|
585
|
+
"formTemplate": [
|
|
586
|
+
{
|
|
587
|
+
"type": "fields_list",
|
|
588
|
+
"title": "About You",
|
|
589
|
+
"fieldsList": [
|
|
590
|
+
{ "type": "text", "name": "full_name", "label": "Full name" },
|
|
591
|
+
{
|
|
592
|
+
"type": "radio",
|
|
593
|
+
"name": "experience",
|
|
594
|
+
"label": "Experience level",
|
|
595
|
+
"options": [
|
|
596
|
+
{ "label": "Beginner", "value": "beginner" },
|
|
597
|
+
{ "label": "Advanced", "value": "advanced" }
|
|
598
|
+
]
|
|
599
|
+
}
|
|
600
|
+
]
|
|
601
|
+
}
|
|
602
|
+
]
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### Building with the MCP
|
|
607
|
+
|
|
608
|
+
Pointer-based adds work against `/formTemplate` the same way they do against `/additionalTabs`:
|
|
609
|
+
|
|
610
|
+
- `config_add_card` with `parent_path: "/formTemplate"` — the array auto-initializes on first add, no seed step.
|
|
611
|
+
- `config_add_card` with `parent_path: "/formTemplate/0/cards"` — nest under a `card_list` for grouped sections.
|
|
612
|
+
- `config_add_field` with `card_path: "/formTemplate/0"` — add a question to the first section.
|
|
613
|
+
- `config_list_cards` — returns cards from both `/additionalTabs` and `/formTemplate` with their JSON pointers.
|
|
614
|
+
|
|
615
|
+
Use any valid field type (`text`, `textarea`, `number`, `radio`, `checkbox`, `select`, `asyncSelect`, `selectAsset`, etc.). The `name` of each field becomes the response key the platform stores.
|
|
616
|
+
|
|
617
|
+
### Mental model
|
|
618
|
+
|
|
619
|
+
- `additionalTabs` → **admin** configuration (every plugin). Strings, assets, color pickers, dependency binding, feature toggles.
|
|
620
|
+
- `formTemplate` → **end-user** questions (only form apps). Only add here if `app-manifest.json` has `formTemplate: true`.
|
|
621
|
+
|
|
622
|
+
Don't mix. Quiz questions never belong in `additionalTabs`; admin config never belongs in `formTemplate`. Both roots validate against the same card/field schema, so `gxdev lint --all` catches malformed structures in either place.
|
|
623
|
+
|
|
525
624
|
## Component Template
|
|
526
625
|
|
|
527
626
|
When creating new components, use this pattern:
|
package/template/AGENTS.md
CHANGED
|
@@ -321,6 +321,111 @@ Group related fields into `fields_list` cards (`config_add_card` then `config_ad
|
|
|
321
321
|
|
|
322
322
|
If a mutation is refused, read the validation error and fix the input — do not reach for `force: true`.
|
|
323
323
|
|
|
324
|
+
## Form / Quiz / Survey Apps — `formTemplate`
|
|
325
|
+
|
|
326
|
+
`formTemplate` appears in **two** places and they mean **two different things**. Read this section carefully — treat the manifest flag and the configuration array as independent decisions.
|
|
327
|
+
|
|
328
|
+
### The two keys
|
|
329
|
+
|
|
330
|
+
**1. `app-manifest.json` → `"formTemplate": true` — the capability flag.**
|
|
331
|
+
|
|
332
|
+
Set this any time the plugin calls the platform's form/quiz/survey API — creating a form, reading responses, submitting answers, listing questions. It tells the platform "this plugin owns or consumes a form resource" and opts the install into form-specific behavior (question editor in the admin UI, response viewer, auto-provisioned `ProjectForm`, etc.). **Required** whenever you're using form/quiz operationIds, regardless of whether you ship prepopulated questions.
|
|
333
|
+
|
|
334
|
+
**2. `configuration.json` → `"formTemplate": [ ...cards ]` — the prepopulated questions.**
|
|
335
|
+
|
|
336
|
+
A starter question set the platform seeds into the auto-provisioned form at install time, so the admin doesn't start from an empty form. Structured identically to `additionalTabs`: an array of cards (usually `fields_list`) whose `fieldsList` items are the questions. **Optional** — only set it when you actually want to ship starter content. If omitted, the platform provisions an empty form and the admin builds it from scratch.
|
|
337
|
+
|
|
338
|
+
### The rule
|
|
339
|
+
|
|
340
|
+
| Scenario | Manifest `formTemplate` | Configuration `formTemplate` |
|
|
341
|
+
| ----------------------------------------------------------- | -------------------------------------------------------------- | ---------------------------- |
|
|
342
|
+
| Plugin calls form/quiz API, ships no starter questions | **`true`** (required) | omit / leave empty |
|
|
343
|
+
| Plugin calls form/quiz API, ships starter questions | **`true`** (required) | `[ ...cards ]` |
|
|
344
|
+
| Plugin does **not** touch the form/quiz API | omit / `false` | must not be set |
|
|
345
|
+
| You want starter questions but didn't set the manifest flag | — fix this — the array is dead content unless the flag is true | — |
|
|
346
|
+
|
|
347
|
+
Short version:
|
|
348
|
+
|
|
349
|
+
- **Uses form/quiz API → manifest `formTemplate: true`.** Non-negotiable.
|
|
350
|
+
- **Wants to prepopulate questions → configuration `formTemplate: [...]`.** Optional payload.
|
|
351
|
+
|
|
352
|
+
### How the pieces connect at runtime
|
|
353
|
+
|
|
354
|
+
1. At install time the platform sees `formTemplate: true` in the manifest and auto-provisions a `ProjectForm` for this plugin install.
|
|
355
|
+
2. If `configuration.json` includes a `formTemplate` array, the platform seeds the new `ProjectForm` with those questions. If not, it creates an empty form.
|
|
356
|
+
3. The plugin code still declares whichever form/quiz dependencies it needs in `app-manifest.json` (e.g. `quiz_form`, `response_stream`) and calls `store.callApi("forms.show", "quiz_form")` / `store.callApi("form_responses.store", "quiz_form", {...})` etc. Discover the right operationIds via the MCP (`search_api_endpoints quiz`, `api_list_operation_ids --tag Forms`).
|
|
357
|
+
4. The admin can edit, add, or remove questions on the auto-provisioned form after install. Your `formTemplate` array is a starting point, not a lock — the admin owns the form once it's provisioned.
|
|
358
|
+
|
|
359
|
+
So: the manifest flag wires up the form backing store; the configuration array pre-seeds its contents; the dependency bindings scope which form the plugin talks to at runtime. Three independent concerns, all of which you need for a proper form app.
|
|
360
|
+
|
|
361
|
+
### Minimal example — form app with starter questions
|
|
362
|
+
|
|
363
|
+
```json
|
|
364
|
+
// app-manifest.json
|
|
365
|
+
{
|
|
366
|
+
"name": "welcome-quiz",
|
|
367
|
+
"version": "0.1.0",
|
|
368
|
+
"formTemplate": true,
|
|
369
|
+
"settings": {},
|
|
370
|
+
"strings": { "default": { "title": "Welcome Quiz" } },
|
|
371
|
+
"assets": {},
|
|
372
|
+
"dependencies": [{ "identifier": "quiz_form", "model": "ProjectForm" }],
|
|
373
|
+
"permissions": [
|
|
374
|
+
{
|
|
375
|
+
"identifier": "quiz_form",
|
|
376
|
+
"description": "The quiz form — read questions and submit responses"
|
|
377
|
+
}
|
|
378
|
+
]
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
```json
|
|
383
|
+
// configuration.json
|
|
384
|
+
{
|
|
385
|
+
"additionalTabs": [
|
|
386
|
+
/* admin-facing plugin config goes here as always */
|
|
387
|
+
],
|
|
388
|
+
"formTemplate": [
|
|
389
|
+
{
|
|
390
|
+
"type": "fields_list",
|
|
391
|
+
"title": "About You",
|
|
392
|
+
"fieldsList": [
|
|
393
|
+
{ "type": "text", "name": "full_name", "label": "Full name" },
|
|
394
|
+
{
|
|
395
|
+
"type": "radio",
|
|
396
|
+
"name": "experience",
|
|
397
|
+
"label": "Experience level",
|
|
398
|
+
"options": [
|
|
399
|
+
{ "label": "Beginner", "value": "beginner" },
|
|
400
|
+
{ "label": "Advanced", "value": "advanced" }
|
|
401
|
+
]
|
|
402
|
+
}
|
|
403
|
+
]
|
|
404
|
+
}
|
|
405
|
+
]
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Building `formTemplate` with the MCP
|
|
410
|
+
|
|
411
|
+
Use the same `config_*` tools you'd use for `additionalTabs`, pointed at the `/formTemplate` root:
|
|
412
|
+
|
|
413
|
+
- `config_add_card` with `parent_path: "/formTemplate"` — the array is auto-initialized on first add, no seed step needed.
|
|
414
|
+
- `config_add_card` with `parent_path: "/formTemplate/0/cards"` — nest sub-cards if you need a `card_list` grouping.
|
|
415
|
+
- `config_add_field` with `card_path: "/formTemplate/0"` — add questions to a section.
|
|
416
|
+
- `config_list_cards` — lists cards from both `/additionalTabs` and `/formTemplate` with their JSON pointers.
|
|
417
|
+
|
|
418
|
+
Use the same field types as any other form (`text`, `textarea`, `number`, `radio`, `checkbox`, `select`, `asyncSelect`, `selectAsset`, etc.). Question names live under `fieldsList[].name` — these become the response keys the platform stores.
|
|
419
|
+
|
|
420
|
+
### Don't confuse `formTemplate` with `additionalTabs`
|
|
421
|
+
|
|
422
|
+
- `additionalTabs` — **admin** configuration form (every plugin has this). The person installing the plugin fills this in once.
|
|
423
|
+
- `formTemplate` — **end-user** form questions (only form apps). Admins may tweak these before publishing; end users (attendees, staff) answer them at runtime.
|
|
424
|
+
|
|
425
|
+
Strings, assets, dependencies, colors → `additionalTabs`. Quiz/survey questions that end users will answer → `formTemplate`.
|
|
426
|
+
|
|
427
|
+
Finish with `gxdev lint --all`. The linter validates both roots against the same card/field schema, so malformed questions fail in the same way malformed admin fields do.
|
|
428
|
+
|
|
324
429
|
## Component Kit
|
|
325
430
|
|
|
326
431
|
Import UI components from `@gramercytech/gx-componentkit`:
|
package/template/GEMINI.md
CHANGED
|
@@ -231,6 +231,39 @@ Every admin-editable piece of content goes through a directive — that's the br
|
|
|
231
231
|
|
|
232
232
|
Tools: `config_list_card_types`, `config_list_field_types`, `config_get_field_schema`, `config_add_card`, `config_add_field`, `config_move_field`, `config_remove_field`, `config_validate`, `config_extract_strings`. Every mutation is linter-guarded against schemas in `bin/lib/lint/schemas/`.
|
|
233
233
|
|
|
234
|
+
## Form / Quiz / Survey Apps — `formTemplate`
|
|
235
|
+
|
|
236
|
+
`formTemplate` lives in two files and the two meanings are independent — don't treat them as a single toggle.
|
|
237
|
+
|
|
238
|
+
### The two keys
|
|
239
|
+
|
|
240
|
+
- **`app-manifest.json` → `"formTemplate": true` — capability flag.** Set **any time** the plugin uses the platform's form/quiz/survey API (creating a form, reading questions, submitting responses, etc.). Tells the platform to auto-provision a `ProjectForm` for this install and opt into form-specific admin UI. Required whenever form/quiz operationIds are in play, regardless of whether you ship starter questions.
|
|
241
|
+
|
|
242
|
+
- **`configuration.json` → `"formTemplate": [ ...cards ]` — prepopulated questions.** Starter question set the platform seeds into the auto-provisioned form at install time. Structured identically to `additionalTabs`. Optional — omit to let the admin build the form from scratch.
|
|
243
|
+
|
|
244
|
+
### The rule
|
|
245
|
+
|
|
246
|
+
- Uses form/quiz API → manifest `formTemplate: true`. Non-negotiable.
|
|
247
|
+
- Wants to ship starter questions → configuration `formTemplate: [...]`. Optional payload.
|
|
248
|
+
- Doesn't touch the form/quiz API → neither key.
|
|
249
|
+
|
|
250
|
+
### Runtime flow
|
|
251
|
+
|
|
252
|
+
1. `formTemplate: true` in the manifest → platform auto-provisions a `ProjectForm` on install.
|
|
253
|
+
2. `formTemplate` array in configuration.json (if present) → seeds the new form with those starter questions.
|
|
254
|
+
3. Plugin code still declares form dependencies (e.g. `quiz_form`) in `app-manifest.json` and calls `store.callApi("forms.show", "quiz_form")` / `store.callApi("form_responses.store", "quiz_form", {...})` — discover operationIds via the MCP (`search_api_endpoints quiz` / `api_list_operation_ids --tag Forms`).
|
|
255
|
+
4. The admin owns the form after install; your array is a starting point, not a lock.
|
|
256
|
+
|
|
257
|
+
### Building `formTemplate` with the MCP
|
|
258
|
+
|
|
259
|
+
- `config_add_card` with `parent_path: "/formTemplate"` (the array is auto-initialized on first add).
|
|
260
|
+
- `config_add_field` with `card_path: "/formTemplate/0"` to add questions to a section.
|
|
261
|
+
- `config_list_cards` surfaces both `/additionalTabs` and `/formTemplate` cards.
|
|
262
|
+
|
|
263
|
+
### Don't confuse with `additionalTabs`
|
|
264
|
+
|
|
265
|
+
`additionalTabs` = **admin** config (every plugin). `formTemplate` = **end-user** questions (only form apps). Admin strings, assets, colors, dependency bindings → `additionalTabs`. Questions an attendee/user will answer → `formTemplate`.
|
|
266
|
+
|
|
234
267
|
## Component Kit
|
|
235
268
|
|
|
236
269
|
Use `@gramercytech/gx-componentkit` for UI:
|