@pure-ds/core 0.7.57 → 0.7.58
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/.cursorrules +63 -589
- package/.github/copilot-instructions.md +63 -598
- package/custom-elements.json +110 -5
- package/dist/types/pds.d.ts +65 -0
- package/dist/types/src/js/common/common.d.ts +7 -0
- package/dist/types/src/js/common/common.d.ts.map +1 -1
- package/dist/types/src/js/pds-autocomplete.d.ts +74 -1
- package/dist/types/src/js/pds-autocomplete.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-start-helpers.d.ts.map +1 -1
- package/dist/types/src/js/pds-reactive.d.ts +101 -0
- package/dist/types/src/js/pds-reactive.d.ts.map +1 -0
- package/dist/types/src/js/pds.d.ts +4 -1
- package/dist/types/src/js/pds.d.ts.map +1 -1
- package/package.json +1 -2
- package/packages/pds-cli/lib/pds-mcp-core.js +436 -1
- package/public/assets/js/app.js +7 -6
- package/public/assets/js/pds-ask.js +4 -4
- package/public/assets/js/pds-autocomplete.js +7 -7
- package/public/assets/js/pds-manager.js +144 -143
- package/public/assets/js/pds.js +3 -2
- package/public/assets/pds/core/pds-ask.js +4 -4
- package/public/assets/pds/core/pds-autocomplete.js +7 -7
- package/public/assets/pds/core/pds-manager.js +144 -143
- package/public/assets/pds/core.js +3 -2
- package/public/assets/pds/custom-elements.json +382 -28
- package/public/assets/pds/pds-css-complete.json +1 -1
- package/public/assets/pds/vscode-custom-data.json +29 -1
- package/src/js/common/common.js +74 -0
- package/src/js/pds-core/pds-start-helpers.js +17 -2
- package/src/js/pds.d.ts +65 -0
- package/src/js/pds.js +13 -0
|
@@ -70,6 +70,439 @@ function buildSuggestions(items, term, limit = 5) {
|
|
|
70
70
|
return [...new Set([...starts, ...contains])].slice(0, limit);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
function includesAny(text, terms = []) {
|
|
74
|
+
return terms.some((term) => text.includes(term));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ─── Topic-specific guidance note builders ───────────────────────────────────
|
|
78
|
+
|
|
79
|
+
function buildFormWidgetGuidanceNotes(q) {
|
|
80
|
+
const notes = [];
|
|
81
|
+
|
|
82
|
+
if (includesAny(q, ['omnibox', 'ui:widget', 'widget', 'renderer', 'definerenderer', 'uischema', 'ui schema'])) {
|
|
83
|
+
notes.push({
|
|
84
|
+
title: 'Use widget keys, not custom element tag names',
|
|
85
|
+
guidance:
|
|
86
|
+
'In pds-form uiSchema, use the built-in widget key "omnibox", not the HTML tag name "pds-omnibox". The tag name is the rendered element; the widget key is the renderer lookup key.\n' +
|
|
87
|
+
'Built-in widget keys: "omnibox", "upload", "richtext", "input-iso-interval", "input-range", "password", "textarea", "date".',
|
|
88
|
+
evidence: [
|
|
89
|
+
{ source: 'public/assets/pds/components/pds-form.js', detail: 'Built-in renderer is registered under defineRenderer("omnibox", ...).' },
|
|
90
|
+
{ source: 'pds-form-docs.md', detail: 'Available PDS component widgets list.' },
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
notes.push({
|
|
95
|
+
title: 'Pass omnibox settings via ui:options.settings',
|
|
96
|
+
guidance:
|
|
97
|
+
'When using the built-in omnibox renderer, put the PdsOmniboxSettings object under uiSchema[path]["ui:options"].settings.\n' +
|
|
98
|
+
'The renderer reads ui?.["ui:options"]?.settings and assigns it to .settings on <pds-omnibox>.\n\n' +
|
|
99
|
+
'Example:\n' +
|
|
100
|
+
'const omniboxSettings = { categories: { Countries: { trigger: ({ search }) => search.length >= 2, getItems: async ({ search }) => [...] } } };\n' +
|
|
101
|
+
'const uiSchema = { "/country": { "ui:widget": "omnibox", "ui:options": { icon: "globe", settings: omniboxSettings } } };',
|
|
102
|
+
evidence: [
|
|
103
|
+
{ source: 'public/assets/pds/components/pds-form.js', detail: 'Renderer extracts settings from ui?.["ui:options"]?.settings.' },
|
|
104
|
+
{ source: 'pds-form-docs.md', detail: 'Omnibox examples place settings inside ui:options.settings.' },
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (includesAny(q, ['definerenderer', 'renderer', 'custom widget', 'custom renderer', 'omnibox'])) {
|
|
110
|
+
notes.push({
|
|
111
|
+
title: 'Prefer built-in renderers before defineRenderer()',
|
|
112
|
+
guidance:
|
|
113
|
+
'Do not reach for form.defineRenderer() when pds-form already provides the widget.\n' +
|
|
114
|
+
'Use defineRenderer() only for truly custom widgets or intentionally different aliases.',
|
|
115
|
+
evidence: [
|
|
116
|
+
{ source: 'public/assets/pds/components/pds-form.js', detail: 'pds-form registers built-in renderers for omnibox, upload, richtext, input-range, and others.' },
|
|
117
|
+
{ source: 'pds-form-docs.md', detail: 'defineRenderer() is documented as the custom-widget extension point, not the first choice for built-in widgets.' },
|
|
118
|
+
],
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (includesAny(q, ['settings', 'uischema', 'ui schema', 'omnibox'])) {
|
|
123
|
+
notes.push({
|
|
124
|
+
title: 'Declare settings objects before uiSchema uses them',
|
|
125
|
+
guidance:
|
|
126
|
+
'If a const uiSchema references a settings object, declare that settings object first.\n' +
|
|
127
|
+
'Referencing a later const produces a runtime Temporal Dead Zone (TDZ) error.\n\n' +
|
|
128
|
+
'✅ const mySettings = { ... };\n' +
|
|
129
|
+
' const uiSchema = { "/field": { "ui:options": { settings: mySettings } } };\n\n' +
|
|
130
|
+
'❌ const uiSchema = { "/field": { "ui:options": { settings: mySettings } } }; // TDZ error!\n' +
|
|
131
|
+
' const mySettings = { ... };',
|
|
132
|
+
evidence: [{ source: 'pds-form-docs.md', detail: 'Examples define settings objects before uiSchema blocks that reference them.' }],
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (includesAny(q, ['daterange', 'iso-interval', 'input-iso-interval', 'travel date', 'date range'])) {
|
|
137
|
+
notes.push({
|
|
138
|
+
title: 'Use the built-in ISO interval date-range path',
|
|
139
|
+
guidance:
|
|
140
|
+
'For a pds-daterange-backed field in pds-form, prefer format: "iso-interval" or the built-in widget key "input-iso-interval".\n\n' +
|
|
141
|
+
'Schema: { type: "string", format: "iso-interval" }\n' +
|
|
142
|
+
'Or uiSchema: { "/dates": { "ui:widget": "input-iso-interval" } }',
|
|
143
|
+
evidence: [{ source: 'pds-form-docs.md', detail: 'format: "iso-interval" and widget key "input-iso-interval" for pds-daterange rendering.' }],
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return notes;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function buildFormSubmitGuidanceNotes(q) {
|
|
151
|
+
if (!includesAny(q, ['submit', 'pw:submit', 'btn-working', 'onsubmit', 'async', 'handler', 'working'])) return [];
|
|
152
|
+
|
|
153
|
+
return [
|
|
154
|
+
{
|
|
155
|
+
title: 'pds-form: listen to pw:submit (NOT native submit); use btn-working during async',
|
|
156
|
+
guidance:
|
|
157
|
+
'The native "submit" event does NOT fire on pds-form. Always listen to "pw:submit".\n' +
|
|
158
|
+
'e.detail contains: { json, formData, valid, issues }.\n' +
|
|
159
|
+
'Add "btn-working" class to the submit button during async operations — PDS shows a spinner automatically.\n\n' +
|
|
160
|
+
'Complete pattern:\n' +
|
|
161
|
+
'form.addEventListener(\'pw:submit\', async (e) => {\n' +
|
|
162
|
+
' const submitBtn = form.querySelector(\'button[type="submit"]\');\n' +
|
|
163
|
+
' submitBtn?.classList.add(\'btn-working\');\n' +
|
|
164
|
+
' try {\n' +
|
|
165
|
+
' // Simulate or real API call:\n' +
|
|
166
|
+
' await new Promise(resolve => setTimeout(resolve, 2000));\n' +
|
|
167
|
+
' console.log(\'Submitted:\', e.detail.json);\n' +
|
|
168
|
+
' await PDS.toast(\'Saved!\', { type: \'success\' });\n' +
|
|
169
|
+
' form.reset();\n' +
|
|
170
|
+
' } catch (err) {\n' +
|
|
171
|
+
' await PDS.toast(\'Error: \' + err.message, { type: \'error\' });\n' +
|
|
172
|
+
' } finally {\n' +
|
|
173
|
+
' submitBtn?.classList.remove(\'btn-working\');\n' +
|
|
174
|
+
' }\n' +
|
|
175
|
+
'});',
|
|
176
|
+
evidence: [
|
|
177
|
+
{ source: 'pds-form-docs.md', detail: 'pw:submit event documentation and btn-working pattern.' },
|
|
178
|
+
{ source: 'custom-elements.json', detail: 'pds-form events list.' },
|
|
179
|
+
],
|
|
180
|
+
},
|
|
181
|
+
];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function buildFormIconGuidanceNotes(q) {
|
|
185
|
+
if (!includesAny(q, ['icon', 'ui:icon', 'placeholder', 'examples', 'schema property', 'text input', 'email input'])) return [];
|
|
186
|
+
|
|
187
|
+
return [
|
|
188
|
+
{
|
|
189
|
+
title: 'ui:icon is ONLY valid on text-like inputs; use schema examples for placeholders',
|
|
190
|
+
guidance:
|
|
191
|
+
'ui:icon (icon-input rendering) is valid ONLY for: text, email, url, tel, search, password.\n' +
|
|
192
|
+
'NEVER add ui:icon to: date-range, range sliders, textarea, select, checkbox/radio groups, omnibox, or upload.\n\n' +
|
|
193
|
+
'✅ Correct:\n' +
|
|
194
|
+
' "/email": { "ui:icon": "envelope", "ui:autocomplete": "email" }\n' +
|
|
195
|
+
' "/name": { "ui:icon": "user" }\n' +
|
|
196
|
+
' "/phone": { "ui:icon": "phone" }\n\n' +
|
|
197
|
+
'❌ Wrong:\n' +
|
|
198
|
+
' "/notes": { "ui:widget": "textarea", "ui:icon": "message" } // textarea — NO\n' +
|
|
199
|
+
' "/dates": { "ui:widget": "date-range", "ui:icon": "calendar" } // date-range — NO\n' +
|
|
200
|
+
' "/budget": { "ui:widget": "input-range", "ui:icon": "coins" } // range — NO\n\n' +
|
|
201
|
+
'Placeholders: add an "examples" array to schema properties; the first value becomes the placeholder.\n' +
|
|
202
|
+
' name: { type: "string", examples: ["John Doe"] }\n' +
|
|
203
|
+
' email: { type: "string", format: "email", examples: ["user@example.com"] }',
|
|
204
|
+
evidence: [{ source: 'pds-form-docs.md', detail: 'ui:icon scope rules and schema examples for placeholders.' }],
|
|
205
|
+
},
|
|
206
|
+
];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function buildFormConditionalFieldGuidanceNotes(q) {
|
|
210
|
+
if (!includesAny(q, ['other', 'conditional', 'visiblewhen', 'requiredwhen', '"other"', 'please specify'])) return [];
|
|
211
|
+
|
|
212
|
+
return [
|
|
213
|
+
{
|
|
214
|
+
title: 'Auto-generate conditional "Other" text field with ui:visibleWhen + ui:requiredWhen',
|
|
215
|
+
guidance:
|
|
216
|
+
'When a schema has an "other" enum option, always add a companion conditional text field.\n\n' +
|
|
217
|
+
'Schema:\n' +
|
|
218
|
+
' reason: {\n' +
|
|
219
|
+
' type: "string",\n' +
|
|
220
|
+
' oneOf: [\n' +
|
|
221
|
+
' { const: "search", title: "Search Engine" },\n' +
|
|
222
|
+
' { const: "other", title: "Other... (please specify)" }\n' +
|
|
223
|
+
' ]\n' +
|
|
224
|
+
' },\n' +
|
|
225
|
+
' otherReason: { type: "string", examples: ["Tell us more..."] }\n\n' +
|
|
226
|
+
'uiSchema:\n' +
|
|
227
|
+
' "/otherReason": {\n' +
|
|
228
|
+
' "ui:visibleWhen": { "/reason": "other" },\n' +
|
|
229
|
+
' "ui:requiredWhen": { "/reason": "other" }\n' +
|
|
230
|
+
' }',
|
|
231
|
+
evidence: [{ source: 'pds-form-docs.md', detail: 'ui:visibleWhen and ui:requiredWhen documentation.' }],
|
|
232
|
+
},
|
|
233
|
+
];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function buildFormGuidanceNotes(question) {
|
|
237
|
+
const q = normalize(question);
|
|
238
|
+
const isFormQuestion = includesAny(q, [
|
|
239
|
+
'pds-form', 'uischema', 'ui schema', 'ui:widget', 'widget', 'renderer', 'definerenderer',
|
|
240
|
+
'omnibox', 'daterange', 'iso-interval', 'input-iso-interval', 'form', 'pw:submit',
|
|
241
|
+
'btn-working', 'jsonschema', 'json schema',
|
|
242
|
+
]);
|
|
243
|
+
if (!isFormQuestion) return [];
|
|
244
|
+
|
|
245
|
+
return [
|
|
246
|
+
...buildFormWidgetGuidanceNotes(q),
|
|
247
|
+
...buildFormSubmitGuidanceNotes(q),
|
|
248
|
+
...buildFormIconGuidanceNotes(q),
|
|
249
|
+
...buildFormConditionalFieldGuidanceNotes(q),
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function buildLocalizationGuidanceNotes(question) {
|
|
254
|
+
const q = normalize(question);
|
|
255
|
+
const isLocalization = includesAny(q, [
|
|
256
|
+
'locali', 'i18n', 'msg(', ' msg ', 'setlocale', 'loadlocale', 'locale',
|
|
257
|
+
'translat', 'language island', 'getlocalizationstate', 'createjsonlocalization',
|
|
258
|
+
]);
|
|
259
|
+
if (!isLocalization) return [];
|
|
260
|
+
|
|
261
|
+
return [
|
|
262
|
+
{
|
|
263
|
+
title: 'Import localization helpers from #pds — NOT from #pds/lit',
|
|
264
|
+
guidance:
|
|
265
|
+
'msg(), str, setLocale(), loadLocale(), getLocalizationState() are all exported from "#pds".\n' +
|
|
266
|
+
'#pds/lit only re-exports Lit and lazyProps — do NOT use it for localization.\n\n' +
|
|
267
|
+
'Configure in pds.config.js:\n' +
|
|
268
|
+
' import { PDS } from "@pure-ds/core";\n' +
|
|
269
|
+
' const localization = PDS.createJSONLocalization({\n' +
|
|
270
|
+
' locale: "en",\n' +
|
|
271
|
+
' locales: ["en", "nl"],\n' +
|
|
272
|
+
' aliases: { en: ["en", "en-US"], nl: ["nl", "nl-NL"] },\n' +
|
|
273
|
+
' basePath: "/assets/locales",\n' +
|
|
274
|
+
' });\n' +
|
|
275
|
+
' export const config = { mode: "live", preset: "default", localization };\n\n' +
|
|
276
|
+
'Locale file format (e.g. public/assets/locales/nl-NL.json):\n' +
|
|
277
|
+
' { "Hello {0}": { "content": "Hallo {0}" } }\n\n' +
|
|
278
|
+
'Use in modules:\n' +
|
|
279
|
+
' import { PDS, msg } from "#pds";\n' +
|
|
280
|
+
' document.body.innerHTML = msg("Hello world");\n\n' +
|
|
281
|
+
'See LOCALIZATION.md for the full guide.',
|
|
282
|
+
evidence: [
|
|
283
|
+
{ source: 'LOCALIZATION.md', detail: 'Full localization setup and usage guide.' },
|
|
284
|
+
{ source: 'src/js/pds-core/pds-config.js', detail: 'PDS.createJSONLocalization config shape.' },
|
|
285
|
+
],
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
title: 'Localization is DOM-contextual via lang attributes — language islands supported',
|
|
289
|
+
guidance:
|
|
290
|
+
'msg() resolves translation using the nearest ancestor lang attribute on the DOM.\n' +
|
|
291
|
+
'localization.locale is the fallback locale, NOT a forced global language.\n\n' +
|
|
292
|
+
'Language island example:\n' +
|
|
293
|
+
' document.documentElement.lang = "en";\n' +
|
|
294
|
+
' document.body.innerHTML = `\n' +
|
|
295
|
+
' <p>${msg("Hello")}</p> <!-- English -->\n' +
|
|
296
|
+
' <div lang="nl">\n' +
|
|
297
|
+
' <p>${msg("Hello")}</p> <!-- Dutch -->\n' +
|
|
298
|
+
' </div>\n' +
|
|
299
|
+
' `;',
|
|
300
|
+
evidence: [{ source: 'LOCALIZATION.md', detail: 'Contextual translation by DOM lang scope.' }],
|
|
301
|
+
},
|
|
302
|
+
];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function buildDomBuildingGuidanceNotes(question) {
|
|
306
|
+
const q = normalize(question);
|
|
307
|
+
const isDomBuilding = includesAny(q, [
|
|
308
|
+
'parse(', 'pds.parse', 'html(', 'pds.html', 'html`', 'parse`',
|
|
309
|
+
'tagged template', 'event binding', '@click', '.prop=', '?disabled',
|
|
310
|
+
'create element', 'appendchild', 'documentfragment', 'dom build',
|
|
311
|
+
'dom element', 'event handler', ' parse ', 'html function', 'parse function',
|
|
312
|
+
]);
|
|
313
|
+
if (!isDomBuilding) return [];
|
|
314
|
+
|
|
315
|
+
return [
|
|
316
|
+
{
|
|
317
|
+
title: 'parse() → NodeList | html() → DocumentFragment; bindings require tagged template syntax',
|
|
318
|
+
guidance:
|
|
319
|
+
'parse() returns a NodeList (legacy-compatible).\n' +
|
|
320
|
+
'html() returns a DocumentFragment for direct appendChild().\n\n' +
|
|
321
|
+
'CRITICAL: Event handlers and property bindings REQUIRE tagged template syntax (backticks without parens).\n' +
|
|
322
|
+
'Regular string mode (with parens) does NOT support bindings — functions get stringified.\n\n' +
|
|
323
|
+
'── String mode (no bindings): ──\n' +
|
|
324
|
+
" const btn = parse('<button class=\"btn-primary\">Save</button>')[0];\n" +
|
|
325
|
+
' document.body.appendChild(btn);\n\n' +
|
|
326
|
+
'── Tagged template (with bindings): ──\n' +
|
|
327
|
+
' const handler = () => console.log("clicked");\n' +
|
|
328
|
+
' const btn = parse`\n' +
|
|
329
|
+
' <button\n' +
|
|
330
|
+
' class=${"btn-primary"}\n' +
|
|
331
|
+
' ?disabled=${false}\n' +
|
|
332
|
+
' @click=${handler}\n' +
|
|
333
|
+
' >Click</button>\n' +
|
|
334
|
+
' `[0];\n\n' +
|
|
335
|
+
'── html() tagged template: ──\n' +
|
|
336
|
+
' document.body.appendChild(html`\n' +
|
|
337
|
+
' <div @click=${handler}>Content</div>\n' +
|
|
338
|
+
' `);\n\n' +
|
|
339
|
+
'Binding prefixes:\n' +
|
|
340
|
+
' @event=${fn} — addEventListener\n' +
|
|
341
|
+
' .prop=${val} — DOM property assignment\n' +
|
|
342
|
+
' ?boolean=${bool} — boolean attribute toggle\n' +
|
|
343
|
+
' attr=${val} — regular attribute (set/remove)',
|
|
344
|
+
evidence: [{ source: 'src/js/pds.js', detail: 'parse() and html() exports with tagged template engine.' }],
|
|
345
|
+
},
|
|
346
|
+
];
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function buildToastDialogGuidanceNotes(question) {
|
|
350
|
+
const q = normalize(question);
|
|
351
|
+
const isInteraction = includesAny(q, [
|
|
352
|
+
'toast', 'dialog', 'pds.ask', 'pds.toast', 'confirm(', 'alert(', 'prompt(',
|
|
353
|
+
'modal', 'notification', '.ask(', 'beforeclose', 'toaster',
|
|
354
|
+
]);
|
|
355
|
+
if (!isInteraction) return [];
|
|
356
|
+
|
|
357
|
+
return [
|
|
358
|
+
{
|
|
359
|
+
title: 'Use PDS.ask() and PDS.toast() — NEVER browser dialogs',
|
|
360
|
+
guidance:
|
|
361
|
+
'alert() → await PDS.toast("message", { type: "info" })\n' +
|
|
362
|
+
'confirm() → await PDS.ask("message", { type: "confirm" })\n' +
|
|
363
|
+
'prompt() → await PDS.ask("message", { type: "prompt" })\n\n' +
|
|
364
|
+
'PDS.toast(message, options) — non-blocking async notification:\n' +
|
|
365
|
+
' await PDS.toast("Saved!", { type: "success" });\n' +
|
|
366
|
+
' await PDS.toast("Error occurred", { type: "error" });\n' +
|
|
367
|
+
' await PDS.toast("Warning", { type: "warning", persistent: true });\n' +
|
|
368
|
+
' await PDS.toast("Info", { type: "information", duration: 3000 });\n\n' +
|
|
369
|
+
'PDS.ask(message, options) — async modal dialog:\n' +
|
|
370
|
+
' // Confirm:\n' +
|
|
371
|
+
' const confirmed = await PDS.ask("Delete this item?", {\n' +
|
|
372
|
+
' type: "confirm",\n' +
|
|
373
|
+
' buttons: { ok: { name: "Delete", variant: "danger" } }\n' +
|
|
374
|
+
' });\n\n' +
|
|
375
|
+
' // Custom with beforeClose guard:\n' +
|
|
376
|
+
' await PDS.ask("Publish?", {\n' +
|
|
377
|
+
' title: "Final approval",\n' +
|
|
378
|
+
' buttons: { ok: { name: "Publish", primary: true }, cancel: { name: "Cancel", cancel: true } },\n' +
|
|
379
|
+
' beforeClose: async ({ actionKind }) => {\n' +
|
|
380
|
+
' if (actionKind !== "ok") return true;\n' +
|
|
381
|
+
' const ok = await checkCanPublish();\n' +
|
|
382
|
+
' return { allow: ok };\n' +
|
|
383
|
+
' }\n' +
|
|
384
|
+
' });\n\n' +
|
|
385
|
+
'Import PDS: import { PDS } from "#pds";',
|
|
386
|
+
evidence: [
|
|
387
|
+
{ source: 'src/js/common/toast.js', detail: 'PDS.toast() implementation.' },
|
|
388
|
+
{ source: 'custom-elements.json', detail: 'pds-toaster component API.' },
|
|
389
|
+
],
|
|
390
|
+
},
|
|
391
|
+
];
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function buildTreeviewGuidanceNotes(question) {
|
|
395
|
+
const q = normalize(question);
|
|
396
|
+
const isTreeview = includesAny(q, [
|
|
397
|
+
'treeview', 'pds-treeview', 'tree view', 'lazy node', 'haschildren',
|
|
398
|
+
'getchildren', 'node-load', 'expandable node', 'tree node',
|
|
399
|
+
]);
|
|
400
|
+
if (!isTreeview) return [];
|
|
401
|
+
|
|
402
|
+
return [
|
|
403
|
+
{
|
|
404
|
+
title: 'pds-treeview: options.source + options.getChildren for lazy loading',
|
|
405
|
+
guidance:
|
|
406
|
+
'Set tree.options with source (initial payload) and getChildren for deferred fetch.\n' +
|
|
407
|
+
'Mark expandable nodes with hasChildren: true; children need not be pre-loaded.\n' +
|
|
408
|
+
'getChildren fires once per node on first expand.\n\n' +
|
|
409
|
+
'Example:\n' +
|
|
410
|
+
' const tree = document.getElementById("myTree");\n' +
|
|
411
|
+
' tree.options = {\n' +
|
|
412
|
+
' source: [\n' +
|
|
413
|
+
' {\n' +
|
|
414
|
+
' id: "docs",\n' +
|
|
415
|
+
' text: "Documentation",\n' +
|
|
416
|
+
' hasChildren: true,\n' +
|
|
417
|
+
' children: [\n' +
|
|
418
|
+
' { id: "guides", text: "Guides", hasChildren: true },\n' +
|
|
419
|
+
' { id: "api", text: "API", hasChildren: true }\n' +
|
|
420
|
+
' ]\n' +
|
|
421
|
+
' }\n' +
|
|
422
|
+
' ],\n' +
|
|
423
|
+
' getChildren: async ({ nodeId }) => {\n' +
|
|
424
|
+
' const res = await fetch(`/api/tree?parent=${encodeURIComponent(nodeId)}`);\n' +
|
|
425
|
+
' return res.ok ? res.json() : [];\n' +
|
|
426
|
+
' }\n' +
|
|
427
|
+
' };\n\n' +
|
|
428
|
+
'Events for telemetry/retries: "node-load", "node-load-error".\n' +
|
|
429
|
+
'Prefer shallow initial payloads (root + 1-2 levels); fetch deeper on expand.',
|
|
430
|
+
evidence: [{ source: 'custom-elements.json', detail: 'pds-treeview options API and events.' }],
|
|
431
|
+
},
|
|
432
|
+
];
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function buildLitImportMapGuidanceNotes(question) {
|
|
436
|
+
const q = normalize(question);
|
|
437
|
+
const isLit = includesAny(q, [
|
|
438
|
+
'import map', 'importmap', '#pds/lit', 'pds-form', 'lazyprops', 'lazy-props',
|
|
439
|
+
'whendefined', 'lit component', 'lit element',
|
|
440
|
+
]);
|
|
441
|
+
if (!isLit) return [];
|
|
442
|
+
|
|
443
|
+
return [
|
|
444
|
+
{
|
|
445
|
+
title: 'Lit-based components (pds-form) require an import map and customElements.whenDefined()',
|
|
446
|
+
guidance:
|
|
447
|
+
'pds-form uses Lit. Add this import map in the HTML <head> BEFORE any module scripts:\n\n' +
|
|
448
|
+
' <script type="importmap">\n' +
|
|
449
|
+
' {\n' +
|
|
450
|
+
' "imports": {\n' +
|
|
451
|
+
' "#pds": "/assets/pds/core.js",\n' +
|
|
452
|
+
' "#pds/lit": "/assets/pds/external/lit.js"\n' +
|
|
453
|
+
' }\n' +
|
|
454
|
+
' }\n' +
|
|
455
|
+
' </script>\n\n' +
|
|
456
|
+
'Always await definition before setting properties:\n' +
|
|
457
|
+
' await customElements.whenDefined("pds-form");\n' +
|
|
458
|
+
' const form = document.querySelector("pds-form");\n' +
|
|
459
|
+
' form.jsonSchema = schema; // safe\n\n' +
|
|
460
|
+
'lazyProps directive (from #pds/lit) for Lit templates:\n' +
|
|
461
|
+
' import { lazyProps } from "#pds/lit";\n' +
|
|
462
|
+
' html`<pds-fab ${lazyProps({ satellites })}></pds-fab>`\n\n' +
|
|
463
|
+
'Localization helpers (msg, setLocale, etc.) come from "#pds", NOT "#pds/lit".',
|
|
464
|
+
evidence: [
|
|
465
|
+
{ source: 'LIT-REQUIREMENTS.md', detail: 'Lit requirements and import map setup.' },
|
|
466
|
+
{ source: 'custom-elements.json', detail: 'pds-form component API.' },
|
|
467
|
+
],
|
|
468
|
+
},
|
|
469
|
+
];
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function buildEmptyStateGuidanceNotes(question) {
|
|
473
|
+
const q = normalize(question);
|
|
474
|
+
if (!includesAny(q, ['empty state', 'empty-state', 'no results', 'onboarding', 'zero state', 'no data'])) return [];
|
|
475
|
+
|
|
476
|
+
return [
|
|
477
|
+
{
|
|
478
|
+
title: 'Use .empty-state primitive for empty/onboarding states',
|
|
479
|
+
guidance:
|
|
480
|
+
'The .empty-state primitive structures an empty or onboarding UI.\n' +
|
|
481
|
+
'Pattern: heading + supporting text + icon + primary/secondary actions.\n\n' +
|
|
482
|
+
'Example:\n' +
|
|
483
|
+
' <div class="empty-state">\n' +
|
|
484
|
+
' <pds-icon icon="inbox" size="xl"></pds-icon>\n' +
|
|
485
|
+
' <h3>No items yet</h3>\n' +
|
|
486
|
+
' <p class="text-muted">Get started by creating your first item.</p>\n' +
|
|
487
|
+
' <button class="btn-primary">Create item</button>\n' +
|
|
488
|
+
' </div>',
|
|
489
|
+
evidence: [{ source: 'src/js/pds-core/pds-ontology.js', detail: '.empty-state primitive definition.' }],
|
|
490
|
+
},
|
|
491
|
+
];
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function buildAllGuidanceNotes(question) {
|
|
495
|
+
return [
|
|
496
|
+
...buildFormGuidanceNotes(question),
|
|
497
|
+
...buildLocalizationGuidanceNotes(question),
|
|
498
|
+
...buildDomBuildingGuidanceNotes(question),
|
|
499
|
+
...buildToastDialogGuidanceNotes(question),
|
|
500
|
+
...buildTreeviewGuidanceNotes(question),
|
|
501
|
+
...buildLitImportMapGuidanceNotes(question),
|
|
502
|
+
...buildEmptyStateGuidanceNotes(question),
|
|
503
|
+
];
|
|
504
|
+
}
|
|
505
|
+
|
|
73
506
|
function getToolSchema() {
|
|
74
507
|
return [
|
|
75
508
|
{
|
|
@@ -103,7 +536,7 @@ function getToolSchema() {
|
|
|
103
536
|
},
|
|
104
537
|
{
|
|
105
538
|
name: 'query_design_system',
|
|
106
|
-
description: 'Run natural-language PDS design system search against SSoT-backed data. Form truth: ui:icon/icon-input rendering is only for regular text-like inputs (text/email/url/tel/search/password), not date/range/textarea/select/choice widgets.',
|
|
539
|
+
description: 'Run natural-language PDS design system search against SSoT-backed data. Form truth: ui:icon/icon-input rendering is only for regular text-like inputs (text/email/url/tel/search/password), not date/range/textarea/select/choice widgets. For pds-form guidance, prefer built-in widget keys such as "omnibox" over tag names such as "pds-omnibox", and pass omnibox settings through ui:options.settings.',
|
|
107
540
|
inputSchema: {
|
|
108
541
|
type: 'object',
|
|
109
542
|
required: ['question'],
|
|
@@ -398,6 +831,7 @@ async function handleQueryDesignSystem(ctx, args = {}) {
|
|
|
398
831
|
|
|
399
832
|
const engine = new PDSQuery(pseudoPds);
|
|
400
833
|
const results = await engine.search(question);
|
|
834
|
+
const guidanceNotes = buildAllGuidanceNotes(question);
|
|
401
835
|
|
|
402
836
|
return {
|
|
403
837
|
ssoTRoot: normalizePath(ctx.ssoTRoot),
|
|
@@ -408,6 +842,7 @@ async function handleQueryDesignSystem(ctx, args = {}) {
|
|
|
408
842
|
},
|
|
409
843
|
question,
|
|
410
844
|
totalMatches: results.length,
|
|
845
|
+
guidanceNotes,
|
|
411
846
|
results,
|
|
412
847
|
};
|
|
413
848
|
}
|