intelligent-system-design-language 0.3.34 → 0.4.1

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.
Files changed (40) hide show
  1. package/.claude/feedback-sweep.json +2 -2
  2. package/out/cli/cli-util.js +9 -3
  3. package/out/cli/cli-util.js.map +1 -1
  4. package/out/cli/components/derived-data-generator.js +45 -7
  5. package/out/cli/components/derived-data-generator.js.map +1 -1
  6. package/out/cli/components/dev-module-generator.js +399 -0
  7. package/out/cli/components/dev-module-generator.js.map +1 -0
  8. package/out/cli/components/method-generator.js +68 -7
  9. package/out/cli/components/method-generator.js.map +1 -1
  10. package/out/cli/components/utils.js +1 -1
  11. package/out/cli/components/utils.js.map +1 -1
  12. package/out/cli/components/vue/base-components/vue-string-choices.js +11 -4
  13. package/out/cli/components/vue/base-components/vue-string-choices.js.map +1 -1
  14. package/out/cli/generator.js +46 -42
  15. package/out/cli/generator.js.map +1 -1
  16. package/out/extension/main.cjs +713 -560
  17. package/out/extension/main.cjs.map +4 -4
  18. package/out/extension/main.js +2 -2
  19. package/out/extension/main.js.map +1 -1
  20. package/out/extension/package.json +1 -1
  21. package/out/language/generated/ast.js +13 -4
  22. package/out/language/generated/ast.js.map +1 -1
  23. package/out/language/generated/grammar.js +106 -32
  24. package/out/language/generated/grammar.js.map +1 -1
  25. package/out/language/intelligent-system-design-language-module.js +3 -1
  26. package/out/language/intelligent-system-design-language-module.js.map +1 -1
  27. package/out/language/intelligent-system-design-language-validator.js +20 -7
  28. package/out/language/intelligent-system-design-language-validator.js.map +1 -1
  29. package/out/language/isdl-completion-provider.js +32 -0
  30. package/out/language/isdl-completion-provider.js.map +1 -0
  31. package/out/language/isdl-docs.js +106 -0
  32. package/out/language/isdl-docs.js.map +1 -0
  33. package/out/language/isdl-hover-provider.js +4 -54
  34. package/out/language/isdl-hover-provider.js.map +1 -1
  35. package/out/language/isdl-parser-error-message-provider.js +6 -1
  36. package/out/language/isdl-parser-error-message-provider.js.map +1 -1
  37. package/out/language/main.cjs +636 -483
  38. package/out/language/main.cjs.map +4 -4
  39. package/out/package.json +1 -1
  40. package/package.json +1 -1
@@ -0,0 +1,399 @@
1
+ import * as path from 'node:path';
2
+ import * as fs from 'node:fs';
3
+ import { expandToNode, toString } from 'langium/generate';
4
+ import { isConfigExpression, isActor, isItem } from '../../language/generated/ast.js';
5
+ export function generateDevModule(entry, id, devDest) {
6
+ var _a, _b, _c, _d;
7
+ const scriptsDir = path.join(devDest, 'scripts');
8
+ if (!fs.existsSync(scriptsDir)) {
9
+ fs.mkdirSync(scriptsDir, { recursive: true });
10
+ }
11
+ const title = (_b = (_a = entry.config.body.find(x => isConfigExpression(x) && x.type === 'label')) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : id;
12
+ const author = (_d = (_c = entry.config.body.find(x => isConfigExpression(x) && x.type === 'author')) === null || _c === void 0 ? void 0 : _c.value) !== null && _d !== void 0 ? _d : '';
13
+ // Foundry fires `render${ClassName}` — for our generated sheets that's `render<Doc>VueSheet`
14
+ // (e.g. renderHeroVueSheet), NOT the generic renderActorSheet. Hook the real class names so
15
+ // the dev overlays actually attach.
16
+ const sheetHooks = entry.documents
17
+ .filter(d => isActor(d) || isItem(d))
18
+ .map(d => `render${d.name}VueSheet`);
19
+ generateModuleJson(id, title, author, devDest);
20
+ generateDevTools(id, title, devDest, sheetHooks);
21
+ }
22
+ function generateModuleJson(id, title, author, devDest) {
23
+ const filePath = path.join(devDest, 'module.json');
24
+ const fileNode = expandToNode `
25
+ {
26
+ "id": "${id}-dev",
27
+ "title": "${title} — Dev Tools",
28
+ "description": "Developer companion module for ${title}. Install locally; never distribute to players.",
29
+ "version": "dev",
30
+ "compatibility": {
31
+ "minimum": 12,
32
+ "verified": 14
33
+ },
34
+ "authors": [
35
+ { "name": "${author}" }
36
+ ],
37
+ "esmodules": [
38
+ "scripts/dev-tools.mjs"
39
+ ],
40
+ "relationships": {
41
+ "systems": [
42
+ {
43
+ "id": "${id}",
44
+ "type": "system",
45
+ "reason": "Dev tools require the ${title} system to be active"
46
+ }
47
+ ]
48
+ }
49
+ }
50
+ `.appendNewLineIfNotEmpty();
51
+ fs.writeFileSync(filePath, toString(fileNode));
52
+ }
53
+ function generateDevTools(id, title, devDest, sheetHooks) {
54
+ const filePath = path.join(devDest, 'scripts', 'dev-tools.mjs');
55
+ const allRenderHooks = ['renderActorSheet', 'renderActorSheetV2', 'renderItemSheet', 'renderItemSheetV2', ...sheetHooks];
56
+ const fileNode = expandToNode `
57
+ // ${id}-dev — developer companion for ${title}
58
+ // Regenerated on each build. Customise ${id}-custom.mjs instead.
59
+
60
+ const MODULE_ID = "${id}-dev";
61
+
62
+ // ─── Vue DevTools ───────────────────────────────────────────────────
63
+ // Only enable if the Vue DevTools extension is already installed —
64
+ // creating a stub hook causes Vue to call emit() on a no-op object and break.
65
+ if (window.__VUE_DEVTOOLS_GLOBAL_HOOK__) {
66
+ window.__VUE_DEVTOOLS_GLOBAL_HOOK__.enabled = true;
67
+ }
68
+
69
+ // ─── Tooltip styles ─────────────────────────────────────────────────
70
+ {
71
+ const style = document.createElement("style");
72
+ style.textContent = \`
73
+ .isdl-dev-tooltip {
74
+ position: fixed;
75
+ background: #1a1a2e;
76
+ border: 1px solid #4a9eff;
77
+ border-radius: 4px;
78
+ padding: 6px 8px;
79
+ font-family: monospace;
80
+ font-size: 11px;
81
+ color: #e0e0e0;
82
+ box-shadow: 0 2px 8px rgba(0,0,0,.6);
83
+ pointer-events: none;
84
+ min-width: 220px;
85
+ z-index: 10000;
86
+ }
87
+ .isdl-dev-section-header {
88
+ font-size: 10px;
89
+ font-weight: bold;
90
+ color: #c0d8f0;
91
+ border-bottom: 1px solid #2a3a5a;
92
+ padding-bottom: 3px;
93
+ margin-bottom: 4px;
94
+ display: flex;
95
+ align-items: center;
96
+ gap: 5px;
97
+ }
98
+ .isdl-dev-section-hint {
99
+ font-weight: normal;
100
+ font-size: 9px;
101
+ color: #6a8aaa;
102
+ margin-left: auto;
103
+ }
104
+ .isdl-dev-label {
105
+ font-size: 9px;
106
+ text-transform: uppercase;
107
+ letter-spacing: 0.05em;
108
+ color: #6a8aaa;
109
+ margin-top: 5px;
110
+ margin-bottom: 1px;
111
+ }
112
+ .isdl-dev-label:first-child { margin-top: 0; }
113
+ .isdl-dev-row {
114
+ display: flex;
115
+ align-items: center;
116
+ gap: 6px;
117
+ padding: 2px 0;
118
+ }
119
+ .isdl-dev-row i { color: #4a9eff; width: 14px; text-align: center; }
120
+ .isdl-dev-row code { flex: 1; font-size: 10px; color: #a0cfff; word-break: break-all; }
121
+ .isdl-dev-copy {
122
+ background: none;
123
+ border: 1px solid #4a9eff;
124
+ border-radius: 2px;
125
+ color: #4a9eff;
126
+ cursor: pointer;
127
+ padding: 1px 5px;
128
+ font-size: 10px;
129
+ line-height: 1.4;
130
+ }
131
+ .isdl-dev-copy:hover { background: #4a9eff; color: #1a1a2e; }
132
+ .isdl-pick-mode-btn { opacity: 0.4; transition: opacity 0.15s, color 0.15s; }
133
+ .isdl-pick-mode-btn.isdl-pick-active { opacity: 1; color: #f6a623 !important; }
134
+ .isdl-dev-tooltip.isdl-dev-locked { border-color: #f6a623; box-shadow: 0 4px 16px rgba(0,0,0,.8); pointer-events: auto; }
135
+ .isdl-dev-tip-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; padding-bottom: 4px; border-bottom: 1px solid #2a3a5a; }
136
+ .isdl-dev-tip-title { font-size: 10px; font-weight: bold; color: #f6a623; letter-spacing: 0.05em; }
137
+ .isdl-dev-dismiss { background: none; border: 1px solid #f6a623; border-radius: 2px; color: #f6a623; cursor: pointer; padding: 0 5px; font-size: 11px; line-height: 1.6; }
138
+ .isdl-dev-dismiss:hover { background: #f6a623; color: #1a1a2e; }
139
+ .isdl-inspector { display: flex; flex-direction: column; gap: 8px; padding: 4px 0; }
140
+ .isdl-inspector-hint { font-size: 12px; color: #888; margin: 0; }
141
+ .isdl-inspector-hint code { font-size: 11px; background: #eee; padding: 1px 3px; border-radius: 2px; }
142
+ .isdl-inspector-copy-all { align-self: flex-end; cursor: pointer; }
143
+ .isdl-inspector-pre {
144
+ max-height: 420px;
145
+ overflow: auto;
146
+ font-size: 11px;
147
+ font-family: monospace;
148
+ background: #1a1a2e;
149
+ color: #a0cfff;
150
+ padding: 10px;
151
+ border-radius: 4px;
152
+ margin: 0;
153
+ white-space: pre;
154
+ line-height: 1.5;
155
+ }
156
+ \`;
157
+ document.head.appendChild(style);
158
+ }
159
+
160
+ // ─── Pick-mode field inspector ──────────────────────────────────────
161
+ let _pickMode = false;
162
+ let _hoverTip = null;
163
+ let _lockedTip = null;
164
+
165
+ function _setPickMode(enabled) {
166
+ _pickMode = enabled;
167
+ if (!enabled) { _clearLockedTip(); _clearHoverTip(); }
168
+ document.querySelectorAll(".isdl-pick-mode-btn")
169
+ .forEach(b => b.classList.toggle("isdl-pick-active", enabled));
170
+ console.log(\`[\${MODULE_ID}] pick mode \${enabled ? "ON — hover a field, click to lock" : "off"}\`);
171
+ }
172
+ function _clearHoverTip() { _hoverTip?.remove(); _hoverTip = null; }
173
+ function _clearLockedTip() { _lockedTip?.remove(); _lockedTip = null; }
174
+
175
+ function _makeRow(iconClass, label, value) {
176
+ return \`
177
+ <div class="isdl-dev-label">\${label}</div>
178
+ <div class="isdl-dev-row">
179
+ <i class="\${iconClass}"></i>
180
+ <code>\${value}</code>
181
+ <button type="button" class="isdl-dev-copy" data-copy="\${value}" title="Copy">
182
+ <i class="fa-solid fa-copy"></i>
183
+ </button>
184
+ </div>
185
+ \`;
186
+ }
187
+
188
+ function _buildTipBody(systemPath, typeSelector, nameSelector) {
189
+ return \`<div class="isdl-dev-section-header"><i class="fa-solid fa-database"></i> Data Path<span class="isdl-dev-section-hint">for macros &amp; scripts</span></div>\`
190
+ + _makeRow("fa-solid fa-folder-open", "system path", systemPath)
191
+ + \`<div class="isdl-dev-section-header" style="margin-top:8px"><i class="fa-solid fa-paintbrush"></i> CSS Selectors<span class="isdl-dev-section-hint">for custom.css</span></div>\`
192
+ + (typeSelector ? _makeRow("fa-solid fa-layer-group", "all fields of this type", typeSelector) : "")
193
+ + (nameSelector ? _makeRow("fa-solid fa-crosshairs", "just this field", nameSelector) : "");
194
+ }
195
+
196
+ function _positionTip(tip, el) {
197
+ const rect = el.getBoundingClientRect();
198
+ tip.style.top = (rect.bottom + 4) + "px";
199
+ tip.style.left = rect.left + "px";
200
+ }
201
+
202
+ function _showHoverTip(el, systemPath, typeSelector, nameSelector) {
203
+ _clearHoverTip();
204
+ if (!_pickMode || _lockedTip) return;
205
+ const tip = document.createElement("div");
206
+ tip.className = "isdl-dev-tooltip";
207
+ tip.innerHTML = _buildTipBody(systemPath, typeSelector, nameSelector);
208
+ _positionTip(tip, el);
209
+ document.body.appendChild(tip);
210
+ _hoverTip = tip;
211
+ }
212
+
213
+ function _lockTip(el, systemPath, typeSelector, nameSelector) {
214
+ _clearHoverTip();
215
+ _clearLockedTip();
216
+ const tip = document.createElement("div");
217
+ tip.className = "isdl-dev-tooltip isdl-dev-locked";
218
+ tip.innerHTML =
219
+ \`<div class="isdl-dev-tip-header"><span class="isdl-dev-tip-title"><i class="fa-solid fa-crosshairs"></i> FIELD INFO</span><button type="button" class="isdl-dev-dismiss" title="Dismiss">&times;</button></div>\`
220
+ + _buildTipBody(systemPath, typeSelector, nameSelector);
221
+ tip.querySelector(".isdl-dev-dismiss").addEventListener("click", e => { e.stopPropagation(); _clearLockedTip(); });
222
+ tip.querySelectorAll(".isdl-dev-copy").forEach(btn => {
223
+ btn.addEventListener("click", e => {
224
+ e.stopPropagation();
225
+ navigator.clipboard.writeText(btn.dataset.copy).then(() => {
226
+ const icon = btn.querySelector("i");
227
+ icon.className = "fa-solid fa-check";
228
+ setTimeout(() => icon.className = "fa-solid fa-copy", 1500);
229
+ });
230
+ });
231
+ });
232
+ _positionTip(tip, el);
233
+ document.body.appendChild(tip);
234
+ _lockedTip = tip;
235
+ }
236
+
237
+ // Delegated field listeners — attached once on document, so they work for Vue-mounted
238
+ // fields regardless of render timing and survive sheet re-renders.
239
+ let _pickWired = false;
240
+ function _wirePickDelegation() {
241
+ if (_pickWired) return;
242
+ _pickWired = true;
243
+ let _curField = null;
244
+ const _fieldAt = t => {
245
+ const el = t?.closest?.("[class*='isdl-']");
246
+ if (!el) return null;
247
+ const input = el.querySelector("input[name^='system.'], select[name^='system.'], textarea[name^='system.']");
248
+ if (!input) return null;
249
+ const { typeSelector, nameSelector } = _buildCssSelectors(el);
250
+ return { el, systemPath: input.getAttribute("name"), typeSelector, nameSelector };
251
+ };
252
+ document.addEventListener("mouseover", e => {
253
+ if (!_pickMode) return;
254
+ const f = _fieldAt(e.target);
255
+ if (f && f.el !== _curField) { _curField = f.el; _showHoverTip(f.el, f.systemPath, f.typeSelector, f.nameSelector); }
256
+ else if (!f && _curField) { _curField = null; _clearHoverTip(); }
257
+ });
258
+ document.addEventListener("click", e => {
259
+ if (!_pickMode) return;
260
+ const f = _fieldAt(e.target);
261
+ if (f) { e.stopPropagation(); _lockTip(f.el, f.systemPath, f.typeSelector, f.nameSelector); }
262
+ }, true);
263
+ }
264
+
265
+ function _buildCssSelectors(el) {
266
+ const classes = [...el.classList];
267
+ // e.g. isdl-field-brawn → "Just this field"
268
+ const nameClass = classes.find(c => /^isdl-field-[a-z]/.test(c));
269
+ // e.g. isdl-number, isdl-attribute — the field type, skip field/visibility/name classes
270
+ const typeClass = classes.find(c =>
271
+ c.startsWith("isdl-") &&
272
+ c !== "isdl-field" &&
273
+ !c.startsWith("isdl-visibility-") &&
274
+ !c.startsWith("isdl-field-")
275
+ );
276
+ return {
277
+ typeSelector: typeClass ? \`.\${typeClass}\` : null,
278
+ nameSelector: nameClass ? \`.\${nameClass}\` : null
279
+ };
280
+ }
281
+
282
+ // ─── System data inspector ──────────────────────────────────────────
283
+ function _openInspector(doc) {
284
+ const data = doc.system?.toObject?.() ?? doc.system ?? {};
285
+ const json = JSON.stringify(data, null, 2);
286
+ new Dialog({
287
+ title: \`System Data — \${doc.name}\`,
288
+ content: \`
289
+ <div class="isdl-inspector">
290
+ <p class="isdl-inspector-hint">
291
+ <i class="fa-solid fa-circle-info"></i>
292
+ Everything stored in <code>actor.system</code> for this document.
293
+ Use these paths in macros, roll formulas, and scripts.
294
+ </p>
295
+ <button type="button" class="isdl-inspector-copy-all">
296
+ <i class="fa-solid fa-copy"></i> Copy all
297
+ </button>
298
+ <pre class="isdl-inspector-pre">\${json}</pre>
299
+ </div>
300
+ \`,
301
+ buttons: { close: { label: "Close" } },
302
+ render: html => {
303
+ const root = html instanceof HTMLElement ? html : html?.[0];
304
+ root?.querySelector(".isdl-inspector-copy-all")?.addEventListener("click", () => {
305
+ navigator.clipboard.writeText(json).then(() => {
306
+ const btn = root.querySelector(".isdl-inspector-copy-all");
307
+ if (btn) {
308
+ btn.innerHTML = \`<i class="fa-solid fa-check"></i> Copied!\`;
309
+ setTimeout(() => { btn.innerHTML = \`<i class="fa-solid fa-copy"></i> Copy all\`; }, 1500);
310
+ }
311
+ });
312
+ });
313
+ }
314
+ }).render(true);
315
+ }
316
+
317
+ // ─── Sheet overlay ──────────────────────────────────────────────────
318
+ function _addHeaderControl(header, btn) {
319
+ const closeBtn = header.querySelector(".close, [data-action='close'], [aria-label='Close']");
320
+ if (closeBtn) closeBtn.before(btn); else header.append(btn);
321
+ }
322
+
323
+ function _activateOverlays(doc, html) {
324
+ const root = html instanceof HTMLElement ? html : html?.[0];
325
+ if (!root) return;
326
+
327
+ // v14: header is a direct child of the form (root IS the sheet element)
328
+ // v12/v13: header is a sibling of root inside .window-app
329
+ const header = root.querySelector(".window-header, .window-controls")
330
+ ?? root.closest(".window-app, .application")?.querySelector(".window-header, .window-controls");
331
+
332
+ // System Data Viewer button — icon as an <i> child (not a class on the button),
333
+ // inserted before the close control so it sits on the right.
334
+ if (header && !header.querySelector(".isdl-inspector-btn")) {
335
+ const btn = document.createElement("button");
336
+ btn.type = "button";
337
+ btn.className = "isdl-inspector-btn header-control";
338
+ btn.innerHTML = '<i class="fa-solid fa-magnifying-glass"></i>';
339
+ btn.setAttribute("aria-label", "Inspect System Data");
340
+ btn.setAttribute("data-tooltip", "Inspect System Data");
341
+ btn.addEventListener("click", e => { e.preventDefault(); _openInspector(doc); });
342
+ _addHeaderControl(header, btn);
343
+ }
344
+
345
+ // Pick Mode button — toggles the field path/CSS picker: hover a field to preview
346
+ // its data path + CSS selectors, click to lock the tooltip.
347
+ if (header && !header.querySelector(".isdl-pick-mode-btn")) {
348
+ const btn = document.createElement("button");
349
+ btn.type = "button";
350
+ btn.className = "isdl-pick-mode-btn header-control";
351
+ btn.innerHTML = '<i class="fa-solid fa-crosshairs"></i>';
352
+ btn.setAttribute("aria-label", "Pick Mode");
353
+ btn.setAttribute("data-tooltip", "Pick Mode — hover a field to preview its data path & CSS, click to lock the tooltip");
354
+ btn.addEventListener("click", e => { e.preventDefault(); _setPickMode(!_pickMode); });
355
+ _addHeaderControl(header, btn);
356
+ }
357
+
358
+ // Field hover/click is handled by a single delegated listener (wired once below),
359
+ // so it works for Vue-mounted fields regardless of render timing.
360
+ _wirePickDelegation();
361
+ }
362
+
363
+ // ─── Verbose logging ────────────────────────────────────────────────
364
+ Hooks.once("init", () => {
365
+ console.group(\`[\${MODULE_ID}] init\`);
366
+ console.log("system:", game.system);
367
+ console.groupEnd();
368
+ });
369
+
370
+ Hooks.once("ready", () => {
371
+ console.group(\`[\${MODULE_ID}] ready\`);
372
+ console.log("actors:", game.actors?.size ?? 0);
373
+ console.log("items:", game.items?.size ?? 0);
374
+ console.groupEnd();
375
+ });
376
+
377
+ // ApplicationV2 (Foundry v13+) fires renderActorSheetV2 / renderItemSheetV2
378
+ // ApplicationV1 fires the legacy renderActorSheet / renderItemSheet
379
+ // Foundry fires render<ClassName>; our sheets are <Doc>VueSheet. The generic
380
+ // ActorSheet/ItemSheet names are kept as a fallback for other Foundry versions.
381
+ for (const hook of ${JSON.stringify(allRenderHooks)}) {
382
+ Hooks.on(hook, (app, html) => {
383
+ const doc = app.actor ?? app.item ?? app.document;
384
+ _activateOverlays(doc, html instanceof HTMLElement ? html : html?.[0] ?? app.element);
385
+ console.log(\`[\${MODULE_ID}] \${hook}\`, doc?.name);
386
+ });
387
+ }
388
+
389
+ Hooks.on("preUpdateActor", (actor, changes) => {
390
+ console.log(\`[\${MODULE_ID}] preUpdateActor\`, actor.name, changes);
391
+ });
392
+
393
+ Hooks.on("preUpdateItem", (item, changes) => {
394
+ console.log(\`[\${MODULE_ID}] preUpdateItem\`, item.name, changes);
395
+ });
396
+ `.appendNewLineIfNotEmpty();
397
+ fs.writeFileSync(filePath, toString(fileNode));
398
+ }
399
+ //# sourceMappingURL=dev-module-generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-module-generator.js","sourceRoot":"","sources":["../../../src/cli/components/dev-module-generator.ts"],"names":[],"mappings":"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"}
@@ -66,6 +66,10 @@ export function translateDiceParts(expression, noLabels = false) {
66
66
  `;
67
67
  }
68
68
  if (isParentAccess(expression)) {
69
+ if (expression.nameAccess) {
70
+ const suffix = noLabels ? "" : "[Name]";
71
+ return expandToNode `"@name${suffix}"`;
72
+ }
69
73
  let path = (_j = (_h = (_g = expression.property) === null || _g === void 0 ? void 0 : _g.ref) === null || _h === void 0 ? void 0 : _h.name) !== null && _j !== void 0 ? _j : "";
70
74
  let label = humanize((_m = (_l = (_k = expression.property) === null || _k === void 0 ? void 0 : _k.ref) === null || _l === void 0 ? void 0 : _l.name) !== null && _m !== void 0 ? _m : "");
71
75
  const document = getParentDocument(expression);
@@ -92,6 +96,10 @@ export function translateDiceParts(expression, noLabels = false) {
92
96
  `;
93
97
  }
94
98
  if (isTargetAccess(expression)) {
99
+ if (expression.nameAccess) {
100
+ const suffix = noLabels ? "" : "[Name]";
101
+ return expandToNode `"@name${suffix}"`;
102
+ }
95
103
  let path = (_w = (_v = (_u = expression.property) === null || _u === void 0 ? void 0 : _u.ref) === null || _v === void 0 ? void 0 : _v.name) !== null && _w !== void 0 ? _w : "";
96
104
  let label = humanize((_z = (_y = (_x = expression.property) === null || _x === void 0 ? void 0 : _x.ref) === null || _y === void 0 ? void 0 : _y.name) !== null && _z !== void 0 ? _z : "");
97
105
  const document = getTargetDocument(expression);
@@ -222,9 +230,13 @@ function translateDisplayFormula(expression) {
222
230
  return label.trim();
223
231
  }
224
232
  if (isParentAccess(expression)) {
233
+ if (expression.nameAccess)
234
+ return "Name";
225
235
  return humanize((_m = (_l = (_k = expression.property) === null || _k === void 0 ? void 0 : _k.ref) === null || _l === void 0 ? void 0 : _l.name) !== null && _m !== void 0 ? _m : "");
226
236
  }
227
237
  if (isTargetAccess(expression)) {
238
+ if (expression.nameAccess)
239
+ return "Name";
228
240
  return humanize((_q = (_p = (_o = expression.property) === null || _o === void 0 ? void 0 : _o.ref) === null || _p === void 0 ? void 0 : _p.name) !== null && _q !== void 0 ? _q : "");
229
241
  }
230
242
  if (isFleetingAccess(expression)) {
@@ -236,6 +248,11 @@ export function translateDiceData(expression, entry, id, preDerived, generatingP
236
248
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61;
237
249
  console.log("Translating Dice Data: ", expression.$type);
238
250
  if (isParentAccess(expression)) {
251
+ if (expression.nameAccess) {
252
+ return expandToNode `
253
+ "name": context.object.parent?.name
254
+ `;
255
+ }
239
256
  let path = "context.object.parent.system";
240
257
  let label = (_c = (_b = (_a = expression.property) === null || _a === void 0 ? void 0 : _a.ref) === null || _b === void 0 ? void 0 : _b.name) !== null && _c !== void 0 ? _c : "";
241
258
  console.log("Parent Access:", (_e = (_d = expression.property) === null || _d === void 0 ? void 0 : _d.ref) === null || _e === void 0 ? void 0 : _e.name);
@@ -257,6 +274,11 @@ export function translateDiceData(expression, entry, id, preDerived, generatingP
257
274
  `;
258
275
  }
259
276
  if (isTargetAccess(expression)) {
277
+ if (expression.nameAccess) {
278
+ return expandToNode `
279
+ "name": context.target?.name
280
+ `;
281
+ }
260
282
  let path = "context.target.system";
261
283
  let label = (_q = (_p = (_o = expression.property) === null || _o === void 0 ? void 0 : _o.ref) === null || _p === void 0 ? void 0 : _p.name) !== null && _q !== void 0 ? _q : "";
262
284
  console.log("Target Access:", (_s = (_r = expression.property) === null || _r === void 0 ? void 0 : _r.ref) === null || _s === void 0 ? void 0 : _s.name);
@@ -823,11 +845,30 @@ export function translateExpression(entry, id, expression, preDerived = false, g
823
845
  throw new Error("Reference expression has no reference");
824
846
  }
825
847
  console.log("Translating Reference Expression: " + accessPath);
826
- // Check if we are in an each loop
827
- const eachExp = AstUtils.getContainerOfType(expression.$container, isEach);
828
- if (eachExp != undefined) {
829
- if (isAccess(eachExp.collection) || isParentAccess(eachExp.collection) || isTargetAccess(eachExp.collection)) {
830
- accessPath = `${accessPath}.system`;
848
+ // If this reference resolves to an `each` loop variable, the loop iterates over a
849
+ // collection of Foundry Documents (embedded items). Custom fields on a Document live
850
+ // under `.system` (e.g. `sk.Expertise` -> `sk.system.expertise`), so member access on
851
+ // the loop variable needs the `.system` hop. Built-in Document props like `.name` are
852
+ // handled separately below and must NOT get `.system`.
853
+ //
854
+ // Only the loop VARIABLE itself gets this treatment. Function/method parameters and
855
+ // other locals that merely appear inside the loop body (e.g. `SkillName`, `Expt`) are
856
+ // plain JS values and must be referenced by their bare name -- appending `.system` to a
857
+ // string param produced `SkillName.system` (undefined at runtime). See bug #123.
858
+ //
859
+ // Walk every enclosing `each` (not just the innermost) so a reference to an outer loop
860
+ // variable from within a nested loop still resolves correctly.
861
+ const refTarget0 = expression.val.ref;
862
+ if (isParameter(refTarget0)) {
863
+ let eachAncestor = AstUtils.getContainerOfType(expression.$container, isEach);
864
+ while (eachAncestor != undefined) {
865
+ if (eachAncestor.var === refTarget0) {
866
+ if (isAccess(eachAncestor.collection) || isParentAccess(eachAncestor.collection) || isTargetAccess(eachAncestor.collection)) {
867
+ accessPath = `${accessPath}.system`;
868
+ }
869
+ break;
870
+ }
871
+ eachAncestor = AstUtils.getContainerOfType(eachAncestor.$container, isEach);
831
872
  }
832
873
  }
833
874
  const refTargetIsRoll = isVariableExpression(expression.val.ref) && isRoll(expression.val.ref.value);
@@ -1173,6 +1214,11 @@ export function translateExpression(entry, id, expression, preDerived = false, g
1173
1214
  }
1174
1215
  if (isParentAccess(expression)) {
1175
1216
  let path = "context.object.parent";
1217
+ if (expression.nameAccess) {
1218
+ return expandToNode `
1219
+ ${path}?.name
1220
+ `;
1221
+ }
1176
1222
  let systemPath = getSystemPath((_a = expression.property) === null || _a === void 0 ? void 0 : _a.ref, expression.subProperties, expression, true);
1177
1223
  path = `${path}?.${systemPath}`;
1178
1224
  console.log("Translating Parent Access Expression: ", path);
@@ -1182,6 +1228,11 @@ export function translateExpression(entry, id, expression, preDerived = false, g
1182
1228
  }
1183
1229
  if (isTargetAccess(expression)) {
1184
1230
  let path = "context.target";
1231
+ if (expression.nameAccess) {
1232
+ return expandToNode `
1233
+ ${path}?.name
1234
+ `;
1235
+ }
1185
1236
  let systemPath = getSystemPath((_b = expression.property) === null || _b === void 0 ? void 0 : _b.ref, expression.subProperties, undefined, true);
1186
1237
  path = `${path}?.${systemPath}`;
1187
1238
  console.log("Translating Target Access Expression: ", path);
@@ -1353,15 +1404,25 @@ export function translateExpression(entry, id, expression, preDerived = false, g
1353
1404
  `;
1354
1405
  }
1355
1406
  if (isParentAccess(expression)) {
1356
- let systemPath = getSystemPath((_3 = expression.property) === null || _3 === void 0 ? void 0 : _3.ref, expression.subProperties, undefined, true);
1357
1407
  const wide = expression.type == "wide" ? true : false;
1408
+ if (expression.nameAccess) {
1409
+ return expandToNode `
1410
+ { isRoll: false, label: "Name", value: context.object.parent?.name, wide: ${wide}, hasValue: context.object.parent?.name != "" },
1411
+ `;
1412
+ }
1413
+ let systemPath = getSystemPath((_3 = expression.property) === null || _3 === void 0 ? void 0 : _3.ref, expression.subProperties, undefined, true);
1358
1414
  return expandToNode `
1359
1415
  { isRoll: false, label: "${humanize((_6 = (_5 = (_4 = expression.property) === null || _4 === void 0 ? void 0 : _4.ref) === null || _5 === void 0 ? void 0 : _5.name) !== null && _6 !== void 0 ? _6 : "")}", value: context.object.parent?.${systemPath}, wide: ${wide}, hasValue: context.object.parent?.${systemPath} != "" },
1360
1416
  `;
1361
1417
  }
1362
1418
  if (isTargetAccess(expression)) {
1363
- let systemPath = getSystemPath((_7 = expression.property) === null || _7 === void 0 ? void 0 : _7.ref, expression.subProperties, undefined, true);
1364
1419
  const wide = expression.type == "wide" ? true : false;
1420
+ if (expression.nameAccess) {
1421
+ return expandToNode `
1422
+ { isRoll: false, label: "Name", value: context.target?.name, wide: ${wide}, hasValue: context.target?.name != "" },
1423
+ `;
1424
+ }
1425
+ let systemPath = getSystemPath((_7 = expression.property) === null || _7 === void 0 ? void 0 : _7.ref, expression.subProperties, undefined, true);
1365
1426
  return expandToNode `
1366
1427
  { isRoll: false, label: "${humanize((_10 = (_9 = (_8 = expression.property) === null || _8 === void 0 ? void 0 : _8.ref) === null || _9 === void 0 ? void 0 : _9.name) !== null && _10 !== void 0 ? _10 : "")}", value: context.target?.${systemPath}, wide: ${wide}, hasValue: context.target?.${systemPath} != "" },
1367
1428
  `;