@enfocussw/switch-scripting-context 25.11.1-beta.3 → 25.11.1-beta.5

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,52 @@ All notable changes to this package are documented here. Format follows
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [25.11.1-beta.5] - 2026-09-21
9
+
10
+ ### Fixed
11
+
12
+ - The property editor and declaration docs listed a `rational` (decimal) inline editor type,
13
+ which Node.js scripts do not allow. The docs no longer offer it and now say it is not allowed.
14
+ - `createJob()` was documented as valid only from `jobArrived` and `timerFired`. It also works from
15
+ `httpRequestTriggeredAsync`, the only webhook entry point that receives `flowElement`.
16
+ - Documented that `Type="password"` needs `Subtype=""`, not `"inline"`. The latter fails at script
17
+ load with an unsupported-editor error, even though it is the usual `Subtype` for a single inline
18
+ editor.
19
+ - Documented that `IncomingConnections="Yes"` with `RequireAtLeastOne="No"` makes the incoming
20
+ connection optional per flow element instance, letting one script support both a flow-starting
21
+ role and a mid-flow role.
22
+
23
+ ## [25.11.1-beta.4] - 2026-09-20
24
+
25
+ ### Added
26
+
27
+ - Guidance in `docs/switch-project/property-editors.md` for a property whose shape varies by a
28
+ type selector. Declare an enum master and one dependent per shape instead of encoding JSON in a
29
+ single property, so each shape gets a native editor and most parsing disappears. Includes the
30
+ two constraints: the master must be static, and a hidden dependent throws when read.
31
+ - A rule in `docs/switch-api/logging.md` against adding a script-level verbosity property. Switch
32
+ already gates `Debug` behind a preference, so the property duplicates a control the user has and
33
+ costs a pane row and translated strings.
34
+
35
+ ### Changed
36
+
37
+ - `init` and `remove` now write to a docs folder named `docs-for-agents` instead of
38
+ `switch-docs`. Re-running `init` after an upgrade deletes the old folder, drops its gitignore
39
+ line, and repoints the generated AI config files, so no stale copy of the docs is left for an
40
+ agent to load. Pass `--docs-dir switch-docs` to keep the old name.
41
+ - The `DetailedInfo` item in `docs/switch-appstore/app-guidelines.md` no longer reads as requiring
42
+ it on every property and connection. It is optional, matching what the property documentation
43
+ and app manual guidance already said, and points at the app manual as the surface users read.
44
+
45
+ ### Fixed
46
+
47
+ - The secret property row in `docs/switch-project/property-editors.md` listed `password` as an
48
+ editor chain. It is a `Type`, and there is no such editor token, so following the row produced an
49
+ invalid declaration.
50
+ - The array property row in `docs/switch-project/property-editors.md` claimed the chain always
51
+ returns a `string[]`, which the modal editor table contradicts. Both files now say the behaviour
52
+ is unverified and that code should handle a bare string too.
53
+
8
54
  ## [25.11.1-beta.3] - 2026-09-17
9
55
 
10
56
  ### 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.3/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.5/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.3/CHANGELOG.md) for what changed.
92
+ See [CHANGELOG.md](https://cdn.jsdelivr.net/npm/@enfocussw/switch-scripting-context@25.11.1-beta.5/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
@@ -35,4 +35,11 @@ export declare function findMarkerRange(content: string): [number, number] | nul
35
35
  export declare function packageVersion(root?: string): string;
36
36
  /** The comment init writes as the first line of the copied hub file. */
37
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;
38
45
  export declare function run(argv?: string[]): Promise<void>;
package/dist/init.js CHANGED
@@ -44,6 +44,7 @@ exports.buildIdMap = buildIdMap;
44
44
  exports.findMarkerRange = findMarkerRange;
45
45
  exports.packageVersion = packageVersion;
46
46
  exports.versionStamp = versionStamp;
47
+ exports.stripGitignoreEntry = stripGitignoreEntry;
47
48
  exports.run = run;
48
49
  const fs = __importStar(require("fs"));
49
50
  const path = __importStar(require("path"));
@@ -92,6 +93,13 @@ const INLINE_CORE_RULES = `## Core rules
92
93
  - After editing \`main.ts\`, transpile with \`SwitchScriptTool --transpile <folder>\` — do not use \`tsc\` directly.
93
94
  - Never use a script folder in production — pack with \`SwitchScriptTool --pack\` first; folders are excluded from flow exports.`;
94
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.
95
103
  function generateClaudeMd(docsDir) {
96
104
  return `${MARKER_BEGIN}
97
105
  ## Switch Scripting (Enfocus Switch)
@@ -395,6 +403,33 @@ function stampDocsVersion(packageRoot, destDir, dryRun) {
395
403
  fs.writeFileSync(hubPath, `${stamp}\n${fs.readFileSync(hubPath, 'utf8')}`, 'utf8');
396
404
  console.log(` ${rel} → stamped ${stamp}`);
397
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
+ }
398
433
  // ─── .gitignore update ───────────────────────────────────────────────────────
399
434
  function updateGitignore(targetRoot, docsDir, dryRun) {
400
435
  const gitignorePath = path.join(targetRoot, '.gitignore');
@@ -408,7 +443,10 @@ function updateGitignore(targetRoot, docsDir, dryRun) {
408
443
  console.log(` [dry-run] .gitignore → append ${docsDir}/ entry`);
409
444
  return;
410
445
  }
411
- 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');
412
450
  console.log(` .gitignore → appended ${docsDir}/ entry`);
413
451
  }
414
452
  /** AI config files that init may have created, for all tools or only the given tool ids. */
@@ -472,15 +510,25 @@ function removeAiConfig(filePath, file, docsDir, targetRoot, dryRun) {
472
510
  console.log(` ${rel} → removed Switch section`);
473
511
  }
474
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
+ }
475
526
  function removeGitignoreEntry(targetRoot, docsDir, dryRun) {
476
527
  const gitignorePath = path.join(targetRoot, '.gitignore');
477
528
  if (!fs.existsSync(gitignorePath))
478
529
  return;
479
530
  const content = fs.readFileSync(gitignorePath, 'utf8');
480
- // Remove the comment line and the docsDir/ line added by init
481
- const updated = content
482
- .replace(/\n# switch-scripting-context \(AI context docs[^\n]*\)\n/g, '\n')
483
- .replace(new RegExp(`\n${docsDir}/\n`, 'g'), '\n');
531
+ const updated = stripGitignoreEntry(content, docsDir);
484
532
  if (updated === content)
485
533
  return;
486
534
  if (dryRun) {
@@ -521,6 +569,7 @@ function remove(options) {
521
569
  else {
522
570
  console.log(` ${options.docsDir}/ → not found, skipping`);
523
571
  }
572
+ removeLegacyDocsDir(targetRoot, options.docsDir, options.dryRun);
524
573
  // 2. Remove AI config sections / files
525
574
  console.log('\nCleaning AI config files...');
526
575
  for (const { filePath, file } of aiConfigFiles(targetRoot, options.docsDir)) {
@@ -540,6 +589,7 @@ function init(options) {
540
589
  console.log('\nDry run — no files will be written.\n');
541
590
  // 1. Copy docs
542
591
  console.log('Copying docs...');
592
+ removeLegacyDocsDir(targetRoot, options.docsDir, options.dryRun);
543
593
  copyDocs(packageRoot, docsDestDir, options.dryRun);
544
594
  stampDocsVersion(packageRoot, docsDestDir, options.dryRun);
545
595
  // 2. Generate AI config files
@@ -573,12 +623,12 @@ function parseArgs(argv) {
573
623
  let toolsExplicit = false;
574
624
  const initOptions = {
575
625
  tools: TOOL_REGISTRY.map(t => t.id),
576
- docsDir: 'switch-docs',
626
+ docsDir: 'docs-for-agents',
577
627
  force: false,
578
628
  dryRun: false,
579
629
  };
580
630
  const removeOptions = {
581
- docsDir: 'switch-docs',
631
+ docsDir: 'docs-for-agents',
582
632
  dryRun: false,
583
633
  };
584
634
  for (let i = 1; i < args.length; i++) {
@@ -658,7 +708,7 @@ Options (init):
658
708
  IDs: claude, copilot, cursor, codex, gemini, windsurf, zed, cline
659
709
  Aliases: opencode (→ codex), clawcode (→ claude)
660
710
  Default: all tools
661
- --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)
662
712
  --force Overwrite existing AI config files rather than merging
663
713
  --dry-run Print what would happen without writing any files
664
714
  --help Show this help message
@@ -666,7 +716,7 @@ Options (init):
666
716
  Options (remove):
667
717
  --tools <list> Only remove these tools' config; keeps the docs folder and other tools
668
718
  Default: remove everything, same as listing every tool
669
- --docs-dir <dir> Docs folder to remove (default: switch-docs)
719
+ --docs-dir <dir> Docs folder to remove (default: docs-for-agents)
670
720
  --dry-run Print what would happen without writing any files
671
721
 
672
722
  Examples:
@@ -81,12 +81,12 @@ flowElement.failProcess(message: string, messageParam?: string | number | boolea
81
81
  ```
82
82
  Logs a fatal error and puts the element into the "problem process" state. `messageParam` substitutes `%1`; before Switch 22.1, always pass it, as a string (see [api-versions.md](api-versions.md#declaration-changes-to-existing-methods)). Throws `"Job routing is not allowed in this entry point."` if called from an entry point where routing isn't permitted.
83
83
 
84
- ## Job creation (jobArrived / timerFired)
84
+ ## Job creation (jobArrived / timerFired / httpRequestTriggeredAsync)
85
85
 
86
86
  ```ts
87
87
  flowElement.createJob(path: string): Promise<Job>
88
88
  ```
89
- Creates a new job from an existing file/folder path. Valid only in `jobArrived` and `timerFired`. The new job has default properties (no parent). The caller is responsible for cleaning up the source file after routing. See [job-patterns.md](job-patterns.md#temp-file-cleanup) for temp file cleanup rules and [job-patterns.md](job-patterns.md#sending-jobs) for routing constraints.
89
+ Creates a new job from an existing file/folder path. Valid in `jobArrived`, `timerFired`, and `httpRequestTriggeredAsync` (confirmed working there; the only webhook entry point that receives `flowElement`). The new job has default properties (no parent). The caller is responsible for cleaning up the source file after routing. See [job-patterns.md](job-patterns.md#temp-file-cleanup) for temp file cleanup rules and [job-patterns.md](job-patterns.md#sending-jobs) for routing constraints.
90
90
 
91
91
  ```ts
92
92
  // Switch 21.0+
@@ -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
@@ -36,13 +36,15 @@ The inline editor slot is always the single token `inline` in `Editor` — what
36
36
  | `Editor="inline" Type="string"` | Single-line text as entered |
37
37
  | `Editor="inline" Type="password"` | Text as entered (displayed masked) |
38
38
  | `Editor="inline" Type="number"` | Integer as a string |
39
- | `Editor="inline" Type="rational"` | Decimal as a string |
40
39
  | `Editor="inline" Type="time"` | `"hh:mm"` (zero-padded, e.g. `"09:05"`) |
41
40
  | `Editor="inline" Type="date"` | Date as entered |
42
41
  | `Editor="inline" Type="datetime"` | Date and time as entered |
43
42
  | `Editor="inline" Type="bool"` | `"No"` or `"Yes"` |
44
43
  | `Editor="inline" Type="enum:Item1;Item2;..."` | The selected item string (one of the declared values) |
45
44
 
45
+ > The `rational` (decimal) inline editor is not allowed in Node.js scripts. Don't declare
46
+ > `Type="rational"`.
47
+
46
48
  ---
47
49
 
48
50
  ## Modal editors
@@ -158,9 +160,9 @@ offer it as the property's only editor — pair it with a module-free option (e.
158
160
 
159
161
  | Property kind | Recommended `Editor` chain | Why |
160
162
  |---|---|---|
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. |
163
+ | Secret (password, token, API key) | `inline` only, with `Type="password"` and **`Subtype=""`** (not `"inline"`) | 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. `Subtype="inline"` (the usual value for a single inline editor, per [script-declaration.md](script-declaration.md#custom-property-elements-elementfields)) is rejected at script load with "editor that is not supported in Node.js scripting"; Switch Designer itself saves `Type="password"` with `Subtype=""`. |
162
164
  | 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[]`. |
165
+ | 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
166
  | 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
167
  | Boolean used as a `Dependency` master | `inline` only | Other properties' visibility depends on a value known at design time. |
166
168
  | 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 +176,50 @@ offer it as the property's only editor — pair it with a module-free option (e.
174
176
  combined with `sltextwithvar`/`scriptexp` too, but this is uncommon in practice and not covered by a
175
177
  dedicated row above.
176
178
 
179
+ ### A property whose shape varies by a type selector
180
+
181
+ When what a property must hold depends on another property's value — a map for one type, an
182
+ ordered list for another, a pair of labelled strings for a third — don't reach for one property
183
+ holding JSON or a hand-rolled line format. Declare an enum master and one dependent per shape, each
184
+ with the editor that natively fits it, and read only the dependent the master currently shows.
185
+
186
+ The shapes then need no parsing at all in the common cases, and the property pane shows one
187
+ type-appropriate editor with a label that explains itself instead of a text box whose format lives
188
+ in the documentation:
189
+
190
+ ```xml
191
+ <!-- master: enum, inline only, so the value is known at flow-configuration time -->
192
+ <QType UserDefined="true" Type="enum:(not used);List;Ordered;Pair" Editor="inline" Subtype="inline"
193
+ LocalizedTagName="Type" Tooltip="" DetailedInfo="" Validation="Standard"
194
+ Default="(not used)">(not used)</QType>
195
+
196
+ <!-- one dependent per shape; each is hidden unless its condition holds -->
197
+ <QList UserDefined="true" Type="stringlist" Editor="stringlist" Subtype="stringlist"
198
+ Dependency="QType" DependencyCondition="Equals" Dependencyvalue="List"
199
+ LocalizedTagName="Items" Tooltip="" DetailedInfo="" Validation="Standard"></QList>
200
+ <QLow UserDefined="true" Type="string" Editor="description" Subtype=""
201
+ Dependency="QType" DependencyCondition="Equals" Dependencyvalue="Pair"
202
+ LocalizedTagName="What a low value means" Tooltip="" DetailedInfo=""
203
+ Validation="Standard" Default=""></QLow>
204
+ ```
205
+
206
+ Adding `(not used)` to the master's items makes it double as the group's on/off switch, so the
207
+ group needs no separate boolean.
208
+
209
+ Two constraints come with the pattern:
210
+
211
+ - The master must be an enum or a boolean with `inline` only, per the rows above. A dynamic master
212
+ makes the visible set differ per job, which the dependents' own editors cannot account for.
213
+ - Reading a hidden dependent throws `"Invalid tag: <tag>"`. Check the master's value, or call
214
+ `hasProperty(tag)`, before every read — see
215
+ [script-declaration.md § Dependency](script-declaration.md#custom-property-elements-elementfields).
216
+ This bites hardest in a loop over jobs or connections, where the throw repeats on every
217
+ invocation.
218
+
219
+ A shape the master cannot enumerate, or one that must vary per job, is the case this pattern does
220
+ not cover; that is where a single `description` property holding a structured blob is the honest
221
+ answer.
222
+
177
223
  ---
178
224
 
179
225
  ## Notes
@@ -68,7 +68,7 @@ value is non-string (see the table); an empty string field can omit `Type`.
68
68
  | `Version` | number | Format `[1-9][0-9]*(\.(0\|[1-9][0-9]*))*`, e.g. `1`, `2.1`. SwitchScripter accepts a whole number only for a script and at most one minor level for an app (`2.1`); it resets anything else. Bump once per release, not per edit (see [Agent editing policy](#agent-editing-policy)). Each flow records the version of the element it uses; Switch compares that recorded value with `UpgradeMaximumVersion` to decide whether to show `FlowUpgradeWarning`. |
69
69
  | `Keywords` | string | Space/comma/semicolon-separated. Elements pane search. |
70
70
  | `Tooltip` | string | Elements pane tooltip. |
71
- | `IncomingConnections` | `Yes` \| `No` | Add `RequireAtLeastOne="Yes"` when `Yes`. When `Yes`, the script must implement `jobArrived` (optionally `timerFired` too); when `No`, it must implement `timerFired` instead — see [entry-points.md](../switch-api/entry-points.md#job-processing). |
71
+ | `IncomingConnections` | `Yes` \| `No` | Add `RequireAtLeastOne="Yes"` when `Yes`. When `Yes`, the script must implement `jobArrived` (optionally `timerFired` too); when `No`, it must implement `timerFired` instead — see [entry-points.md](../switch-api/entry-points.md#job-processing). Setting `IncomingConnections="Yes" RequireAtLeastOne="No"` makes the incoming connection optional per instance: some instances can have zero incoming connections (e.g. a flow-starting webhook trigger) and others one wired in (e.g. a mid-flow processing step), with the script branching on both cases at runtime, typically gated by a property such as a mode enum. Confirmed working in Switch Designer. |
72
72
  | `OutgoingConnections` | `No` \| `One` \| `Unlimited` | Add `RequireAtLeastOne="Yes"` when `One`/`Unlimited`. Always carries `DetailedInfo=""` (or a prose description of the connections, for generated docs). When not `No`, the script must call a `sendTo*()` method at least once for any job it actually routes (a purely timer/event-driven script that never touches a job — polling an API, rotating logs, etc. — is a legitimate exception); when `Unlimited`, never use `job.sendToSingle()` — see [job-patterns.md § Sending jobs](../switch-api/job-patterns.md#sending-jobs). |
73
73
  | `ConnectionType` | `Move` \| `Filter` \| `TrafficLight` | Governs which `ConnectionFields` children are legal — see [ConnectionFields](#connectionfields). |
74
74
  | `FunctionsNodeJSScript` | `;`-separated names | Entry point function names found in the script. Omit the element entirely if there is no Node.js program. |
@@ -120,7 +120,7 @@ Key attributes:
120
120
  | `UserDefined` | Always `"true"` for custom properties. |
121
121
  | `Type` | Value type discriminator — see [Type reference](#type-reference). |
122
122
  | `Editor` | `;`-joined chain of editor tokens (inline editor first, then modal editors) — see [property-editors.md](property-editors.md). |
123
- | `Subtype` | Which editor in the `Editor` chain is currently active for the stored value. For a single-editor property this equals that editor's token (e.g. `"inline"`); for a bare modal editor with no inline component, leave it `""`. This attribute is read by Switch's validator at runtime, not just bookkeeping — keep it consistent with `Editor`. |
123
+ | `Subtype` | Which editor in the `Editor` chain is currently active for the stored value. For a single-editor property this equals that editor's token (e.g. `"inline"`); for a bare modal editor with no inline component, leave it `""`. Exception: `Type="password"` takes `Subtype=""` even though `Editor="inline"` alone — see [property-editors.md](property-editors.md#common-practices). This attribute is read by Switch's validator at runtime, not just bookkeeping — keep it consistent with `Editor`. |
124
124
  | `LocalizedTagName` | Display name shown in the Properties pane. **App guideline (mandatory for apps, recommended for scripts):** 30 characters max, sentence-style capitalization (`"Customer name"`, not `"Customer Name"`). |
125
125
  | `Tooltip` | Tooltip shown in the Properties pane. **App guideline (mandatory for apps, recommended for scripts):** give every property a tooltip. The only exception is a property that's fully self-explanatory from its name alone with no extra constraints (e.g. "Number of copies" with no min/max) — if there's anything a user would need to know (units, valid range, format, an example value), put it in the tooltip. |
126
126
  | `DetailedInfo` | Long-form description for generated docs. Always include the attribute (empty string `""` is fine) — omitting it makes the property non-editable in Switch Designer. |
@@ -258,7 +258,6 @@ If no OAuth property exists on the script, omit `ExtraProperties` entirely or le
258
258
  |---|---|
259
259
  | `string` | Text |
260
260
  | `number` | Integer |
261
- | `rational` | Decimal |
262
261
  | `password` | Masked text |
263
262
  | `bool` | `Yes`/`No` |
264
263
  | `date`, `datetime`, `time` | `time` is hours-and-minutes |
@@ -272,11 +271,13 @@ This is the *authoring* vocabulary written into the XML `Type` attribute. It doe
272
271
  onto the runtime `PropertyType` returned by `getPropertyType()` (see
273
272
  [property-editors.md](property-editors.md) and [enums.md](../switch-api/enums.md)) — e.g. XML
274
273
  `bool` becomes runtime `boolean`, XML `path` splits into runtime `filepath`/`folderpath`, and several
275
- XML types (`rational`, `stringlist`, `accountlist`, `enum:...`, `datetime`, `time`) have no direct
274
+ XML types (`stringlist`, `accountlist`, `enum:...`, `datetime`, `time`) have no direct
276
275
  `PropertyType` counterpart while several runtime values (`filetype`, `folderpattern`, `regex`,
277
276
  `oauthtoken`, `literal`) have no direct XML `Type` counterpart. Don't assume the two vocabularies are
278
277
  interchangeable.
279
278
 
279
+ `rational` (decimal) is not a valid `Type` for Node.js scripts.
280
+
280
281
  `Type` is not independently authored on properties with a modal editor chain — it follows from
281
282
  which editor(s) you choose, per [property-editors.md](property-editors.md). When combining
282
283
  an inline editor with modal editors (e.g. `Editor="inline;sltextwithvar;scriptexp"`), the inline
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enfocussw/switch-scripting-context",
3
- "version": "25.11.1-beta.3",
3
+ "version": "25.11.1-beta.5",
4
4
  "description": "AI coding assistant context for Enfocus Switch scripting (Node.js/TypeScript)",
5
5
  "keywords": [
6
6
  "switch",