@enfocussw/switch-scripting-context 25.11.1-beta.2 → 25.11.1-beta.4

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/CHANGELOG.md CHANGED
@@ -5,6 +5,47 @@ All notable changes to this package are documented here. Format follows
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [25.11.1-beta.4] - 2026-09-20
9
+
10
+ ### Added
11
+
12
+ - Guidance in `docs/switch-project/property-editors.md` for a property whose shape varies by a
13
+ type selector. Declare an enum master and one dependent per shape instead of encoding JSON in a
14
+ single property, so each shape gets a native editor and most parsing disappears. Includes the
15
+ two constraints: the master must be static, and a hidden dependent throws when read.
16
+ - A rule in `docs/switch-api/logging.md` against adding a script-level verbosity property. Switch
17
+ already gates `Debug` behind a preference, so the property duplicates a control the user has and
18
+ costs a pane row and translated strings.
19
+
20
+ ### Changed
21
+
22
+ - `init` and `remove` now write to a docs folder named `docs-for-agents` instead of
23
+ `switch-docs`. Re-running `init` after an upgrade deletes the old folder, drops its gitignore
24
+ line, and repoints the generated AI config files, so no stale copy of the docs is left for an
25
+ agent to load. Pass `--docs-dir switch-docs` to keep the old name.
26
+ - The `DetailedInfo` item in `docs/switch-appstore/app-guidelines.md` no longer reads as requiring
27
+ it on every property and connection. It is optional, matching what the property documentation
28
+ and app manual guidance already said, and points at the app manual as the surface users read.
29
+
30
+ ### Fixed
31
+
32
+ - The secret property row in `docs/switch-project/property-editors.md` listed `password` as an
33
+ editor chain. It is a `Type`, and there is no such editor token, so following the row produced an
34
+ invalid declaration.
35
+ - The array property row in `docs/switch-project/property-editors.md` claimed the chain always
36
+ returns a `string[]`, which the modal editor table contradicts. Both files now say the behaviour
37
+ is unverified and that code should handle a bare string too.
38
+
39
+ ## [25.11.1-beta.3] - 2026-09-17
40
+
41
+ ### Added
42
+
43
+ - `init` now records the version that produced the docs on the first line of
44
+ `switch-scripting.md`, as an HTML comment reading `switch-scripting-context <version>`. A tool
45
+ that installs the docs can read it to tell which version a project has, including a checkout
46
+ someone else set up. Re-running `init` rewrites the line, and `--dry-run` prints it without
47
+ writing.
48
+
8
49
  ## [25.11.1-beta.2] - 2026-09-17
9
50
 
10
51
  ### Added
package/README.md CHANGED
@@ -4,7 +4,7 @@ AI coding assistant context for [Enfocus Switch](https://www.enfocus.com/en/swit
4
4
 
5
5
  Installs curated API reference docs and generates config files for 8 AI coding agents (Claude Code, GitHub Copilot, Cursor, Codex CLI/OpenCode, Gemini CLI, Windsurf, Zed, and Cline) so AI assistants understand the Switch scripting API out of the box.
6
6
 
7
- See [CHANGELOG.md](https://cdn.jsdelivr.net/npm/@enfocussw/switch-scripting-context@25.11.1-beta.2/CHANGELOG.md) (also included in this package) for what's changed between versions.
7
+ See [CHANGELOG.md](https://cdn.jsdelivr.net/npm/@enfocussw/switch-scripting-context@25.11.1-beta.4/CHANGELOG.md) (also included in this package) for what's changed between versions.
8
8
 
9
9
  ## Usage
10
10
 
@@ -45,20 +45,25 @@ so the beta line precedes GA and leaves `25.11.0` free for the first published r
45
45
 
46
46
  ## What it does
47
47
 
48
- 1. Copies the Switch API reference docs to `switch-docs/` in your project
48
+ 1. Copies the Switch API reference docs to `docs-for-agents/` in your project
49
49
  2. Generates AI config files for each tool (see below)
50
- 3. Appends `switch-docs/` to `.gitignore`
50
+ 3. Appends `docs-for-agents/` to `.gitignore`
51
+
52
+ Versions before this one used `switch-docs/` as the folder name. Re-running `init` deletes that
53
+ folder and its `.gitignore` line, so your agents do not find a second, stale copy of the docs. It
54
+ only deletes a `switch-docs/` folder that `init` itself wrote. Pass `--docs-dir switch-docs` to
55
+ stay on the old name.
51
56
 
52
57
  ### Generated files
53
58
 
54
59
  | Tool | File | How context is loaded |
55
60
  |---|---|---|
56
- | Claude Code / ClawCode | `CLAUDE.md` | `@switch-docs/switch-scripting.md` import; hub file routes to specific API docs on demand |
61
+ | Claude Code / ClawCode | `CLAUDE.md` | `@docs-for-agents/switch-scripting.md` import; hub file routes to specific API docs on demand |
57
62
  | GitHub Copilot | `.github/copilot-instructions.md` | `#file:` reference to hub |
58
63
  | GitHub Copilot (scoped) | `.github/instructions/switch-scripting.instructions.md` | `applyTo: "**/*.ts"`; auto-attaches to every TypeScript file edit |
59
64
  | Cursor | `.cursor/rules/switch-scripting.mdc` | Inline key rules + full API file path list |
60
65
  | Codex CLI / OpenCode | `AGENTS.md` | Inline key rules + full API file path list |
61
- | Gemini CLI | `GEMINI.md` | `@switch-docs/switch-scripting.md` import |
66
+ | Gemini CLI | `GEMINI.md` | `@docs-for-agents/switch-scripting.md` import |
62
67
  | Windsurf | `.windsurfrules` | Inline key rules + full API file path list |
63
68
  | Zed | `.rules` | Inline key rules + full API file path list |
64
69
  | Cline | `.clinerules` | Inline key rules + full API file path list |
@@ -84,7 +89,7 @@ A few things this gets you without asking for them by name:
84
89
  properties (app path/licence) are off-limits and left to SwitchScripter's GUI instead.
85
90
 
86
91
  Re-run `init` after upgrading this package so the copied docs and generated config files catch up.
87
- See [CHANGELOG.md](https://cdn.jsdelivr.net/npm/@enfocussw/switch-scripting-context@25.11.1-beta.2/CHANGELOG.md) for what changed.
92
+ See [CHANGELOG.md](https://cdn.jsdelivr.net/npm/@enfocussw/switch-scripting-context@25.11.1-beta.4/CHANGELOG.md) for what changed.
88
93
 
89
94
  ## Options
90
95
 
@@ -94,7 +99,7 @@ npx switch-scripting-context init [options]
94
99
  --tools <list> Tools to configure. Default: all
95
100
  IDs: claude, copilot, cursor, codex, gemini, windsurf, zed, cline
96
101
  Aliases: opencode (→ codex), clawcode (→ claude)
97
- --docs-dir <dir> Destination folder for docs. Default: switch-docs
102
+ --docs-dir <dir> Destination folder for docs. Default: docs-for-agents
98
103
  --force Overwrite existing AI config files instead of merging
99
104
  --dry-run Print what would happen without writing any files
100
105
  ```
@@ -124,7 +129,7 @@ To undo `init`, run `remove`. By default it deletes the docs folder, the Switch
124
129
  npx switch-scripting-context remove [options]
125
130
 
126
131
  --tools <list> Only remove these tools' config. Default: remove everything
127
- --docs-dir <dir> Docs folder to remove. Default: switch-docs
132
+ --docs-dir <dir> Docs folder to remove. Default: docs-for-agents
128
133
  --dry-run Print what would happen without writing any files
129
134
  ```
130
135
 
@@ -143,7 +148,7 @@ npm install --save-dev "https://github.com/enfocus-switch/types-switch-scripting
143
148
 
144
149
  ## Included docs
145
150
 
146
- The `switch-docs/` folder contains:
151
+ The `docs-for-agents/` folder contains:
147
152
 
148
153
  - `switch-scripting.md`: master index with execution environment rules and "load when" routing table
149
154
  <!-- docs-index:readme begin -->
package/dist/init.d.ts CHANGED
@@ -31,4 +31,15 @@ export declare function generateZedRules(docsDir: string): string;
31
31
  export declare function generateClineRules(docsDir: string): string;
32
32
  export declare function buildIdMap(registry: ToolDefinition[]): Map<string, string>;
33
33
  export declare function findMarkerRange(content: string): [number, number] | null;
34
+ /** The running package's own version, so a git-clone install stamps what actually ran. */
35
+ export declare function packageVersion(root?: string): string;
36
+ /** The comment init writes as the first line of the copied hub file. */
37
+ export declare function versionStamp(version: string): string;
38
+ /**
39
+ * Drop the `docsDir/` line init added, together with the comment line directly above it.
40
+ * The comment and the path are matched as one unit so that a project carrying entries for
41
+ * two docs folders (one left by an older version, one current) keeps the comment belonging
42
+ * to the entry that stays.
43
+ */
44
+ export declare function stripGitignoreEntry(content: string, docsDir: string): string;
34
45
  export declare function run(argv?: string[]): Promise<void>;
package/dist/init.js CHANGED
@@ -42,6 +42,9 @@ exports.generateZedRules = generateZedRules;
42
42
  exports.generateClineRules = generateClineRules;
43
43
  exports.buildIdMap = buildIdMap;
44
44
  exports.findMarkerRange = findMarkerRange;
45
+ exports.packageVersion = packageVersion;
46
+ exports.versionStamp = versionStamp;
47
+ exports.stripGitignoreEntry = stripGitignoreEntry;
45
48
  exports.run = run;
46
49
  const fs = __importStar(require("fs"));
47
50
  const path = __importStar(require("path"));
@@ -90,6 +93,13 @@ const INLINE_CORE_RULES = `## Core rules
90
93
  - After editing \`main.ts\`, transpile with \`SwitchScriptTool --transpile <folder>\` — do not use \`tsc\` directly.
91
94
  - Never use a script folder in production — pack with \`SwitchScriptTool --pack\` first; folders are excluded from flow exports.`;
92
95
  // ─── Content generators ──────────────────────────────────────────────────────
96
+ // Claude Code and Gemini CLI resolve @file imports themselves, so their generated
97
+ // block is just a pointer to switch-scripting.md; the routing table and key rules
98
+ // living there are the actual instructions. Codex, Windsurf, Zed, and Cline don't
99
+ // reliably follow that import syntax, so their generators (generateInlineBlock)
100
+ // paste INLINE_CORE_RULES and the routing table directly into the generated file
101
+ // instead. Don't collapse these into one shared generator; the split is deliberate,
102
+ // not duplication.
93
103
  function generateClaudeMd(docsDir) {
94
104
  return `${MARKER_BEGIN}
95
105
  ## Switch Scripting (Enfocus Switch)
@@ -365,6 +375,61 @@ function copyDocs(packageRoot, destDir, dryRun) {
365
375
  });
366
376
  console.log(` docs → ${path.relative(process.cwd(), destDir)}/`);
367
377
  }
378
+ // ─── Version stamp ───────────────────────────────────────────────────────────
379
+ /** The hub file in the copied docs folder that carries the version stamp. */
380
+ const HUB_FILE = 'switch-scripting.md';
381
+ /** The running package's own version, so a git-clone install stamps what actually ran. */
382
+ function packageVersion(root = packageRoot()) {
383
+ return JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).version;
384
+ }
385
+ /** The comment init writes as the first line of the copied hub file. */
386
+ function versionStamp(version) {
387
+ return `<!-- switch-scripting-context ${version} -->`;
388
+ }
389
+ /**
390
+ * Records which version produced the copied docs, so a consumer can tell what is installed
391
+ * without having recorded it at install time. Only the copy is stamped: the repo's own
392
+ * docs/switch-scripting.md is generated output and a stamp there would fight the generator.
393
+ * copyDocs wipes destDir on every run, so there is never a stale stamp to remove.
394
+ */
395
+ function stampDocsVersion(packageRoot, destDir, dryRun) {
396
+ const stamp = versionStamp(packageVersion(packageRoot));
397
+ const hubPath = path.join(destDir, HUB_FILE);
398
+ const rel = path.relative(process.cwd(), hubPath);
399
+ if (dryRun) {
400
+ console.log(` [dry-run] ${rel} → first line ${stamp}`);
401
+ return;
402
+ }
403
+ fs.writeFileSync(hubPath, `${stamp}\n${fs.readFileSync(hubPath, 'utf8')}`, 'utf8');
404
+ console.log(` ${rel} → stamped ${stamp}`);
405
+ }
406
+ // ─── Legacy docs folder ──────────────────────────────────────────────────────
407
+ /** Docs folder name written by versions before the rename to docs-for-agents. */
408
+ const LEGACY_DOCS_DIR = 'switch-docs';
409
+ /**
410
+ * Delete the docs folder an older version wrote under its old name.
411
+ *
412
+ * init regenerates every AI config block in place, so after an upgrade the configs point at
413
+ * the new folder and nothing reads the old one. It is gitignored, so nothing surfaces it
414
+ * either, but an agent grepping the project still finds and loads the stale copy. The folder
415
+ * is only removed when it holds the hub file init writes, so a folder of the same name that
416
+ * this tool did not create is left alone.
417
+ */
418
+ function removeLegacyDocsDir(targetRoot, docsDir, dryRun) {
419
+ if (docsDir === LEGACY_DOCS_DIR)
420
+ return;
421
+ const legacyDir = path.join(targetRoot, LEGACY_DOCS_DIR);
422
+ if (!fs.existsSync(path.join(legacyDir, HUB_FILE)))
423
+ return;
424
+ if (dryRun) {
425
+ console.log(` [dry-run] ${LEGACY_DOCS_DIR}/ → deleted (docs folder from an earlier version)`);
426
+ }
427
+ else {
428
+ fs.rmSync(legacyDir, { recursive: true, force: true });
429
+ console.log(` ${LEGACY_DOCS_DIR}/ → deleted (docs folder from an earlier version)`);
430
+ }
431
+ removeGitignoreEntry(targetRoot, LEGACY_DOCS_DIR, dryRun);
432
+ }
368
433
  // ─── .gitignore update ───────────────────────────────────────────────────────
369
434
  function updateGitignore(targetRoot, docsDir, dryRun) {
370
435
  const gitignorePath = path.join(targetRoot, '.gitignore');
@@ -378,7 +443,10 @@ function updateGitignore(targetRoot, docsDir, dryRun) {
378
443
  console.log(` [dry-run] .gitignore → append ${docsDir}/ entry`);
379
444
  return;
380
445
  }
381
- fs.appendFileSync(gitignorePath, entry, 'utf8');
446
+ // entry opens with its own blank-line separator, so normalise what the file already
447
+ // ends with. Without this, appending after an entry that was just removed leaves a
448
+ // widening gap in the file on every upgrade.
449
+ fs.writeFileSync(gitignorePath, content.replace(/\n*$/, '\n') + entry, 'utf8');
382
450
  console.log(` .gitignore → appended ${docsDir}/ entry`);
383
451
  }
384
452
  /** AI config files that init may have created, for all tools or only the given tool ids. */
@@ -442,15 +510,25 @@ function removeAiConfig(filePath, file, docsDir, targetRoot, dryRun) {
442
510
  console.log(` ${rel} → removed Switch section`);
443
511
  }
444
512
  }
513
+ function escapeRegExp(s) {
514
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
515
+ }
516
+ /**
517
+ * Drop the `docsDir/` line init added, together with the comment line directly above it.
518
+ * The comment and the path are matched as one unit so that a project carrying entries for
519
+ * two docs folders (one left by an older version, one current) keeps the comment belonging
520
+ * to the entry that stays.
521
+ */
522
+ function stripGitignoreEntry(content, docsDir) {
523
+ const pattern = new RegExp(`(\n# switch-scripting-context \\(AI context docs[^\n]*\\))?\n${escapeRegExp(docsDir)}/\n`, 'g');
524
+ return content.replace(pattern, '\n');
525
+ }
445
526
  function removeGitignoreEntry(targetRoot, docsDir, dryRun) {
446
527
  const gitignorePath = path.join(targetRoot, '.gitignore');
447
528
  if (!fs.existsSync(gitignorePath))
448
529
  return;
449
530
  const content = fs.readFileSync(gitignorePath, 'utf8');
450
- // Remove the comment line and the docsDir/ line added by init
451
- const updated = content
452
- .replace(/\n# switch-scripting-context \(AI context docs[^\n]*\)\n/g, '\n')
453
- .replace(new RegExp(`\n${docsDir}/\n`, 'g'), '\n');
531
+ const updated = stripGitignoreEntry(content, docsDir);
454
532
  if (updated === content)
455
533
  return;
456
534
  if (dryRun) {
@@ -491,6 +569,7 @@ function remove(options) {
491
569
  else {
492
570
  console.log(` ${options.docsDir}/ → not found, skipping`);
493
571
  }
572
+ removeLegacyDocsDir(targetRoot, options.docsDir, options.dryRun);
494
573
  // 2. Remove AI config sections / files
495
574
  console.log('\nCleaning AI config files...');
496
575
  for (const { filePath, file } of aiConfigFiles(targetRoot, options.docsDir)) {
@@ -510,7 +589,9 @@ function init(options) {
510
589
  console.log('\nDry run — no files will be written.\n');
511
590
  // 1. Copy docs
512
591
  console.log('Copying docs...');
592
+ removeLegacyDocsDir(targetRoot, options.docsDir, options.dryRun);
513
593
  copyDocs(packageRoot, docsDestDir, options.dryRun);
594
+ stampDocsVersion(packageRoot, docsDestDir, options.dryRun);
514
595
  // 2. Generate AI config files
515
596
  const toolActions = [];
516
597
  for (const toolDef of TOOL_REGISTRY) {
@@ -542,12 +623,12 @@ function parseArgs(argv) {
542
623
  let toolsExplicit = false;
543
624
  const initOptions = {
544
625
  tools: TOOL_REGISTRY.map(t => t.id),
545
- docsDir: 'switch-docs',
626
+ docsDir: 'docs-for-agents',
546
627
  force: false,
547
628
  dryRun: false,
548
629
  };
549
630
  const removeOptions = {
550
- docsDir: 'switch-docs',
631
+ docsDir: 'docs-for-agents',
551
632
  dryRun: false,
552
633
  };
553
634
  for (let i = 1; i < args.length; i++) {
@@ -627,7 +708,7 @@ Options (init):
627
708
  IDs: claude, copilot, cursor, codex, gemini, windsurf, zed, cline
628
709
  Aliases: opencode (→ codex), clawcode (→ claude)
629
710
  Default: all tools
630
- --docs-dir <dir> Destination folder for docs in target project (default: switch-docs)
711
+ --docs-dir <dir> Destination folder for docs in target project (default: docs-for-agents)
631
712
  --force Overwrite existing AI config files rather than merging
632
713
  --dry-run Print what would happen without writing any files
633
714
  --help Show this help message
@@ -635,7 +716,7 @@ Options (init):
635
716
  Options (remove):
636
717
  --tools <list> Only remove these tools' config; keeps the docs folder and other tools
637
718
  Default: remove everything, same as listing every tool
638
- --docs-dir <dir> Docs folder to remove (default: switch-docs)
719
+ --docs-dir <dir> Docs folder to remove (default: docs-for-agents)
639
720
  --dry-run Print what would happen without writing any files
640
721
 
641
722
  Examples:
@@ -24,6 +24,13 @@ and how often to log is a deliberate choice, not a default.
24
24
  | `Info` | Normal, expected checkpoints worth surfacing to a flow operator without them needing debug mode (e.g. "processed 40 records", "output written to X"). |
25
25
  | `Debug` | Verbose detail for diagnosing execution. Many users leave debug logging enabled in production to self-diagnose issues and to help app developers and Enfocus Support — leave meaningful checkpoints in, but trim noisy or no-longer-useful debug logs before shipping. **`Debug` messages only reach the log database if the Switch preference "Log debug messages" is turned on** (off by default) — see [logs-and-dataroot.md](../switch-project/logs-and-dataroot.md#retention-and-the-debug-level-gate). |
26
26
 
27
+ ## Don't add a verbosity property
28
+
29
+ Switch already has the switch: `Debug` messages only reach the log database when the preference
30
+ "Log debug messages" is on. A script-level "log detail" or "verbose logging" property duplicates a
31
+ control the user already has, adds a row to the property pane, and (for an app) adds strings to
32
+ translate. Put verbose detail behind `LogLevel.Debug` and let the preference govern it.
33
+
27
34
  ## Logging in loops
28
35
 
29
36
  Don't categorically avoid logging inside loops — per-item logs can matter (e.g. a per-job error).
@@ -162,11 +162,15 @@ Unlike the sections above, these are documented in full here rather than deferri
162
162
  approval. Note that an unsigned app always loads under the **Custom** subcategory regardless of
163
163
  what it declares — the declared subcategory only takes effect once Enfocus has signed the app, and
164
164
  review may change it.
165
- - [ ] **`DetailedInfo` is present on every property and every connection**, not just `Tooltip`.
166
- Switch Designer can export a *flow's* HTML documentation from every element in it, so an empty
167
- `DetailedInfo` produces a blank entry there if a flow builder ever generates one — see
165
+ - [ ] **`DetailedInfo` is written wherever it adds something a `Tooltip` can't carry** — not
166
+ mechanically on every property. It is optional: Switch Designer can export a *flow's* HTML
167
+ documentation from every element in it, so an empty `DetailedInfo` produces a blank entry there
168
+ if a flow builder ever generates one, which is a reason to fill it in where the property warrants
169
+ it, not a reason to pad out every trivial one. See
168
170
  [property-documentation.md](../switch-project/property-documentation.md) for what to write in it, and when
169
- it's fine to leave blank.
171
+ it's fine to leave blank. The [app manual](app-manual.md#flow-elements-properties) is the surface
172
+ users are far more likely to read, and it needs real per-property content regardless of what
173
+ `DetailedInfo` holds.
170
174
  - [ ] **`Description`, `Compatibility`, `SupportInfo` and `AppDiscovery` are filled in.** These are
171
175
  required to create the `.enfpack` at all, and `SupportInfo` must point at *your* support channel,
172
176
  never Enfocus Support — see [app-store-listing.md](app-store-listing.md) for what to write
@@ -158,9 +158,9 @@ offer it as the property's only editor — pair it with a module-free option (e.
158
158
 
159
159
  | Property kind | Recommended `Editor` chain | Why |
160
160
  |---|---|---|
161
- | Secret (password, token, API key) | `password` only | Masks the value and avoids leaking it via logs or variable inspection if a var/expression editor were combined. |
161
+ | Secret (password, token, API key) | `inline` only, with `Type="password"` | Masks the value and avoids leaking it via logs or variable inspection if a var/expression editor were combined. `password` is a `Type`, not an editor token — there is no `password` entry in the [modal editor](#modal-editors) list, so `Editor="password"` is wrong. |
162
162
  | Scalar job-specific value (job ID, copy count, company name, date, etc.) | `inline` (`Type` matching the value) + `sltextwithvar` + `scriptexp` | Hard-coded default, plus dynamic substitution and full expression evaluation. |
163
- | Array/list job-specific value | `stringlist` + `mltextwithvar` + `scriptexp` | One item per line; keeps both the hard-coded and dynamic paths returning a `string[]`. |
163
+ | Array/list job-specific value | `stringlist` + `mltextwithvar` + `scriptexp` | One item per line. **Caveat:** the intent is that every path returns a `string[]`, but the [modal editor table](#modal-editors) lists `mltextwithvar` as returning a single string, and which one holds for this chain is unverified. Handle both — split on newlines when a bare string comes back. |
164
164
  | Structured blob text (XML/JSON/HTML, email body) | `description` + `mltextwithvar` + `scriptexp` | Free multi-line text suits a single blob of content better than a line-per-item list. |
165
165
  | Boolean used as a `Dependency` master | `inline` only | Other properties' visibility depends on a value known at design time. |
166
166
  | Boolean consumed directly by script logic (not a `Dependency` master) | `inline` + `conditionwithvar` + `scriptexp` | Safe to toggle dynamically since nothing else depends on it for visibility. Also covers booleans that are themselves a `Dependency` *dependent* (not a master). |
@@ -174,6 +174,50 @@ offer it as the property's only editor — pair it with a module-free option (e.
174
174
  combined with `sltextwithvar`/`scriptexp` too, but this is uncommon in practice and not covered by a
175
175
  dedicated row above.
176
176
 
177
+ ### A property whose shape varies by a type selector
178
+
179
+ When what a property must hold depends on another property's value — a map for one type, an
180
+ ordered list for another, a pair of labelled strings for a third — don't reach for one property
181
+ holding JSON or a hand-rolled line format. Declare an enum master and one dependent per shape, each
182
+ with the editor that natively fits it, and read only the dependent the master currently shows.
183
+
184
+ The shapes then need no parsing at all in the common cases, and the property pane shows one
185
+ type-appropriate editor with a label that explains itself instead of a text box whose format lives
186
+ in the documentation:
187
+
188
+ ```xml
189
+ <!-- master: enum, inline only, so the value is known at flow-configuration time -->
190
+ <QType UserDefined="true" Type="enum:(not used);List;Ordered;Pair" Editor="inline" Subtype="inline"
191
+ LocalizedTagName="Type" Tooltip="" DetailedInfo="" Validation="Standard"
192
+ Default="(not used)">(not used)</QType>
193
+
194
+ <!-- one dependent per shape; each is hidden unless its condition holds -->
195
+ <QList UserDefined="true" Type="stringlist" Editor="stringlist" Subtype="stringlist"
196
+ Dependency="QType" DependencyCondition="Equals" Dependencyvalue="List"
197
+ LocalizedTagName="Items" Tooltip="" DetailedInfo="" Validation="Standard"></QList>
198
+ <QLow UserDefined="true" Type="string" Editor="description" Subtype=""
199
+ Dependency="QType" DependencyCondition="Equals" Dependencyvalue="Pair"
200
+ LocalizedTagName="What a low value means" Tooltip="" DetailedInfo=""
201
+ Validation="Standard" Default=""></QLow>
202
+ ```
203
+
204
+ Adding `(not used)` to the master's items makes it double as the group's on/off switch, so the
205
+ group needs no separate boolean.
206
+
207
+ Two constraints come with the pattern:
208
+
209
+ - The master must be an enum or a boolean with `inline` only, per the rows above. A dynamic master
210
+ makes the visible set differ per job, which the dependents' own editors cannot account for.
211
+ - Reading a hidden dependent throws `"Invalid tag: <tag>"`. Check the master's value, or call
212
+ `hasProperty(tag)`, before every read — see
213
+ [script-declaration.md § Dependency](script-declaration.md#custom-property-elements-elementfields).
214
+ This bites hardest in a loop over jobs or connections, where the throw repeats on every
215
+ invocation.
216
+
217
+ A shape the master cannot enumerate, or one that must vary per job, is the case this pattern does
218
+ not cover; that is where a single `description` property holding a structured blob is the honest
219
+ answer.
220
+
177
221
  ---
178
222
 
179
223
  ## Notes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enfocussw/switch-scripting-context",
3
- "version": "25.11.1-beta.2",
3
+ "version": "25.11.1-beta.4",
4
4
  "description": "AI coding assistant context for Enfocus Switch scripting (Node.js/TypeScript)",
5
5
  "keywords": [
6
6
  "switch",