@eclipse-lyra/extension-howto-system 0.7.56 → 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.
@@ -0,0 +1,1335 @@
1
+ import { File, LyraWidget, TOOLBAR_BOTTOM_END, TOPIC_CONTRIBUTEIONS_CHANGED, TOPIC_WORKSPACE_CHANGED, TOPIC_WORKSPACE_CONNECTED, activeEditorSignal, appLoaderService, appSettings, commandRegistry, contributionRegistry, createLogger, partDirtySignal, publish, registerAll, rootContext, subscribe, toastError, watchSignal, workspaceService } from "@eclipse-lyra/core";
2
+ import { KEY_AI_CONFIG, TOPIC_AICONFIG_CHANGED } from "@eclipse-lyra/extension-ai-system/api";
3
+ import { css, html, nothing } from "lit";
4
+ import { customElement, state } from "lit/decorators.js";
5
+ import { styleMap } from "lit/directives/style-map.js";
6
+ import { createRef, ref } from "lit/directives/ref.js";
7
+ import _decorate from "@oxc-project/runtime/helpers/decorate";
8
+ //#region src/howto-service.ts
9
+ var logger$1 = createLogger("HowToService");
10
+ var HOWTO_CONTRIBUTION_TARGET = "system.howtos";
11
+ /**
12
+ * HowTo Service
13
+ *
14
+ * Manages HowTo contributions by reading from the contribution registry.
15
+ * Provides methods to retrieve and manage workflows.
16
+ */
17
+ var HowToService = class {
18
+ constructor() {
19
+ this.contributions = /* @__PURE__ */ new Map();
20
+ this.loadContributions();
21
+ subscribe(TOPIC_CONTRIBUTEIONS_CHANGED, (event) => {
22
+ if (event.target === "system.howtos") this.loadContributions();
23
+ });
24
+ }
25
+ /**
26
+ * Load contributions from the contribution registry
27
+ */
28
+ loadContributions() {
29
+ const contributions = contributionRegistry.getContributions(HOWTO_CONTRIBUTION_TARGET);
30
+ this.contributions.clear();
31
+ for (const contribution of contributions) {
32
+ if (!contribution.id) {
33
+ logger$1.warn("HowTo contribution missing id, skipping");
34
+ continue;
35
+ }
36
+ if (!contribution.label) contribution.label = typeof contribution.title === "function" ? contribution.title() : contribution.title;
37
+ if (!contribution.steps || contribution.steps.length === 0) {
38
+ logger$1.warn(`HowTo contribution "${contribution.id}" has no steps, skipping`);
39
+ continue;
40
+ }
41
+ const stepIds = /* @__PURE__ */ new Set();
42
+ for (const step of contribution.steps) {
43
+ if (stepIds.has(step.id)) {
44
+ logger$1.warn(`HowTo contribution "${contribution.id}" has duplicate step ID: "${step.id}"`);
45
+ continue;
46
+ }
47
+ stepIds.add(step.id);
48
+ }
49
+ this.contributions.set(contribution.id, contribution);
50
+ const title = typeof contribution.title === "function" ? contribution.title() : contribution.title;
51
+ logger$1.debug(`Loaded HowTo contribution: ${title} (${contribution.id})`);
52
+ }
53
+ logger$1.info(`Loaded ${this.contributions.size} HowTo contributions`);
54
+ }
55
+ /**
56
+ * Get a HowTo Contribution by ID
57
+ *
58
+ * @param contributionId The ID of the contribution
59
+ * @returns The contribution or undefined if not found
60
+ */
61
+ getContribution(contributionId) {
62
+ return this.contributions.get(contributionId);
63
+ }
64
+ /**
65
+ * Get all registered HowTo Contributions
66
+ *
67
+ * @returns Array of all contributions
68
+ */
69
+ getAllContributions() {
70
+ return Array.from(this.contributions.values());
71
+ }
72
+ /**
73
+ * Get HowTo Contributions by category
74
+ *
75
+ * @param category The category to filter by
76
+ * @returns Array of contributions in the category
77
+ */
78
+ getContributionsByCategory(category) {
79
+ return Array.from(this.contributions.values()).filter((contrib) => contrib.category === category);
80
+ }
81
+ /**
82
+ * Check if a contribution exists
83
+ *
84
+ * @param contributionId The ID of the contribution
85
+ * @returns True if the contribution exists
86
+ */
87
+ hasContribution(contributionId) {
88
+ return this.contributions.has(contributionId);
89
+ }
90
+ };
91
+ var howToService = new HowToService();
92
+ //#endregion
93
+ //#region src/contributions/onboarding-howto-contributions.ts
94
+ var ONBOARDING_FILE_NAME = "welcome.txt";
95
+ async function getOnboardingFilePath() {
96
+ if (!await workspaceService.getWorkspace()) return null;
97
+ const folders = await workspaceService.getFolders();
98
+ if (folders.length === 0) return null;
99
+ return `${folders[0].name}/${ONBOARDING_FILE_NAME}`;
100
+ }
101
+ /**
102
+ * Type guard to check if an editor implements EditorContentProvider
103
+ */
104
+ function isEditorContentProvider$1(editor) {
105
+ return editor && typeof editor.getFilePath === "function";
106
+ }
107
+ /**
108
+ * Checks if a workspace is selected
109
+ */
110
+ async function isWorkspaceSelected() {
111
+ return workspaceService.isConnected();
112
+ }
113
+ /**
114
+ * Checks if the onboarding file exists
115
+ */
116
+ async function onboardingFileExists() {
117
+ const path = await getOnboardingFilePath();
118
+ if (!path) return false;
119
+ const workspace = await workspaceService.getWorkspace();
120
+ if (!workspace) return false;
121
+ try {
122
+ return await workspace.getResource(path) instanceof File;
123
+ } catch {
124
+ return false;
125
+ }
126
+ }
127
+ /**
128
+ * Checks if the onboarding file is open in an editor
129
+ */
130
+ function isOnboardingFileOpen() {
131
+ const activeEditor = activeEditorSignal.get();
132
+ if (!activeEditor || !isEditorContentProvider$1(activeEditor)) return false;
133
+ const filePath = activeEditor.getFilePath();
134
+ return filePath === ONBOARDING_FILE_NAME || filePath?.endsWith("/" + ONBOARDING_FILE_NAME) === true;
135
+ }
136
+ /**
137
+ * Checks if the active editor is clean (no unsaved changes)
138
+ * Returns true only if the onboarding file is open AND not dirty
139
+ */
140
+ function isActiveEditorClean() {
141
+ if (!isOnboardingFileOpen()) return false;
142
+ const activeEditor = activeEditorSignal.get();
143
+ if (!activeEditor) return false;
144
+ return activeEditor.isDirty() === false;
145
+ }
146
+ /**
147
+ * Checks if the onboarding file is closed (not open in any editor)
148
+ */
149
+ function isOnboardingFileClosed() {
150
+ return !isOnboardingFileOpen();
151
+ }
152
+ function getAppName$1() {
153
+ return appLoaderService.getCurrentApp()?.name || "AppSpace";
154
+ }
155
+ contributionRegistry.registerContribution(HOWTO_CONTRIBUTION_TARGET, {
156
+ id: "appspace.onboarding",
157
+ title: () => `Welcome to ${getAppName$1()}`,
158
+ description: () => `Get started with ${getAppName$1()} by learning the basics of workspace and file management`,
159
+ icon: "graduation-cap",
160
+ label: "",
161
+ category: "Getting Started",
162
+ initialize: (context) => {
163
+ const cleanups = [];
164
+ subscribe(TOPIC_WORKSPACE_CHANGED, () => {
165
+ context.requestUpdate();
166
+ });
167
+ subscribe(TOPIC_WORKSPACE_CONNECTED, () => {
168
+ context.requestUpdate();
169
+ });
170
+ cleanups.push(watchSignal(activeEditorSignal, () => {
171
+ context.requestUpdate();
172
+ }));
173
+ cleanups.push(watchSignal(partDirtySignal, () => {
174
+ context.requestUpdate();
175
+ }));
176
+ return () => {
177
+ cleanups.forEach((cleanup) => cleanup());
178
+ };
179
+ },
180
+ steps: [
181
+ {
182
+ id: "create-text-file",
183
+ title: "Create welcome.txt",
184
+ description: "Create a new text file called \"welcome.txt\" in your workspace. If you don't have a workspace selected, choose one first.",
185
+ preCondition: async () => {
186
+ return await isWorkspaceSelected();
187
+ },
188
+ postCondition: async () => {
189
+ return await onboardingFileExists();
190
+ },
191
+ command: "touch",
192
+ commandParams: async () => {
193
+ const path = await getOnboardingFilePath();
194
+ return path ? {
195
+ path,
196
+ contents: "Welcome to AppSpace!\n\nThis is your first file. You can edit it and save your changes."
197
+ } : {};
198
+ }
199
+ },
200
+ {
201
+ id: "open-text-file",
202
+ title: "Open welcome.txt",
203
+ description: "Open the \"welcome.txt\" file in the editor.",
204
+ preCondition: async () => {
205
+ return await onboardingFileExists();
206
+ },
207
+ postCondition: () => {
208
+ return isOnboardingFileOpen();
209
+ },
210
+ command: "open_editor",
211
+ commandParams: async () => {
212
+ const path = await getOnboardingFilePath();
213
+ return path ? { path } : {};
214
+ }
215
+ },
216
+ {
217
+ id: "edit-and-save",
218
+ title: "Type something and save",
219
+ description: "Type some text in the editor to modify the file, then save it using Ctrl+S or the save button.",
220
+ preCondition: () => {
221
+ return isOnboardingFileOpen();
222
+ },
223
+ postCondition: () => {
224
+ return isActiveEditorClean();
225
+ }
226
+ },
227
+ {
228
+ id: "close-text-file",
229
+ title: "Close the file",
230
+ description: "Close the editor tab by clicking the X button on the tab.",
231
+ preCondition: () => {
232
+ return isOnboardingFileOpen();
233
+ },
234
+ postCondition: () => {
235
+ return isOnboardingFileClosed();
236
+ }
237
+ }
238
+ ]
239
+ });
240
+ //#endregion
241
+ //#region src/contributions/ai-setup-howto-contributions.ts
242
+ var AI_CONFIG_EDITOR_KEY = ".system.ai-config";
243
+ /**
244
+ * Type guard to check if an editor implements EditorContentProvider
245
+ */
246
+ function isEditorContentProvider(editor) {
247
+ return editor && typeof editor.getFilePath === "function";
248
+ }
249
+ /**
250
+ * Checks if the AI config editor is open
251
+ */
252
+ function isAIConfigEditorOpen() {
253
+ const activeEditor = activeEditorSignal.get();
254
+ if (!activeEditor || !isEditorContentProvider(activeEditor)) return false;
255
+ return activeEditor.getFilePath() === AI_CONFIG_EDITOR_KEY;
256
+ }
257
+ /**
258
+ * Checks if an LLM provider is configured (has default provider with API key)
259
+ */
260
+ async function isLLMProviderConfigured() {
261
+ try {
262
+ const config = await appSettings.get(KEY_AI_CONFIG);
263
+ if (!config || !config.defaultProvider) return false;
264
+ const defaultProvider = config.providers?.find((p) => p.name === config.defaultProvider);
265
+ if (!defaultProvider) return false;
266
+ return !!defaultProvider.apiKey && defaultProvider.apiKey.trim() !== "";
267
+ } catch {
268
+ return false;
269
+ }
270
+ }
271
+ /**
272
+ * Checks if the AI config editor has unsaved changes
273
+ */
274
+ function isAIConfigEditorDirty() {
275
+ const activeEditor = activeEditorSignal.get();
276
+ if (!activeEditor || !isEditorContentProvider(activeEditor)) return false;
277
+ if (activeEditor.getFilePath() !== AI_CONFIG_EDITOR_KEY) return false;
278
+ return activeEditor.isDirty() === true;
279
+ }
280
+ /**
281
+ * Checks if the AI config editor is saved (not dirty)
282
+ */
283
+ function isAIConfigEditorSaved() {
284
+ if (!isAIConfigEditorOpen()) return false;
285
+ return !isAIConfigEditorDirty();
286
+ }
287
+ /**
288
+ * Checks if the AI config editor is closed
289
+ */
290
+ function isAIConfigEditorClosed() {
291
+ return !isAIConfigEditorOpen();
292
+ }
293
+ /**
294
+ * Checks if user has typed something in the AI chat
295
+ * This checks if there are any chat sessions with messages
296
+ */
297
+ async function hasTypedInChat() {
298
+ try {
299
+ const sessions = await appSettings.get("aiChatSessions");
300
+ if (!sessions || typeof sessions !== "object") return false;
301
+ for (const sessionId in sessions) {
302
+ const session = sessions[sessionId];
303
+ if (session?.history && Array.isArray(session.history)) {
304
+ if (session.history.some((msg) => msg.role === "user" && msg.content && msg.content.trim() !== "")) return true;
305
+ }
306
+ }
307
+ return false;
308
+ } catch {
309
+ return false;
310
+ }
311
+ }
312
+ function getAppName() {
313
+ return appLoaderService.getCurrentApp()?.name || "AppSpace";
314
+ }
315
+ contributionRegistry.registerContribution(HOWTO_CONTRIBUTION_TARGET, {
316
+ id: "appspace.ai-setup",
317
+ title: () => `Set up AI in ${getAppName()}`,
318
+ description: () => `Configure an LLM provider to enable AI chat features in ${getAppName()}`,
319
+ icon: "robot",
320
+ label: "",
321
+ category: "Getting Started",
322
+ initialize: (context) => {
323
+ const cleanups = [];
324
+ cleanups.push(watchSignal(activeEditorSignal, () => {
325
+ context.requestUpdate();
326
+ }));
327
+ cleanups.push(watchSignal(partDirtySignal, () => {
328
+ context.requestUpdate();
329
+ }));
330
+ subscribe(TOPIC_AICONFIG_CHANGED, () => {
331
+ context.requestUpdate();
332
+ });
333
+ return () => {
334
+ cleanups.forEach((cleanup) => cleanup());
335
+ };
336
+ },
337
+ steps: [
338
+ {
339
+ id: "open-ai-settings",
340
+ title: "Open AI Settings",
341
+ description: "Open the AI settings editor by clicking the robot icon in the toolbar or using the command palette.",
342
+ preCondition: () => true,
343
+ postCondition: () => isAIConfigEditorOpen(),
344
+ command: "open_ai_config"
345
+ },
346
+ {
347
+ id: "configure-llm-provider",
348
+ title: "Configure LLM Provider",
349
+ description: "Select a provider as default and enter an API key. Make sure to save your changes using Ctrl+S or the save button.",
350
+ preCondition: () => isAIConfigEditorOpen(),
351
+ postCondition: async () => {
352
+ const configured = await isLLMProviderConfigured();
353
+ const saved = isAIConfigEditorSaved();
354
+ return configured && saved;
355
+ }
356
+ },
357
+ {
358
+ id: "save-and-close",
359
+ title: "Save and Close",
360
+ description: "Save your changes (if not already saved) and close the AI settings editor tab.",
361
+ preCondition: () => isAIConfigEditorOpen(),
362
+ postCondition: () => isAIConfigEditorClosed()
363
+ },
364
+ {
365
+ id: "type-in-chat",
366
+ title: "Type in Chat",
367
+ description: "Open the AI chat view (if not already open) and type a message to test your AI configuration.",
368
+ preCondition: async () => await isLLMProviderConfigured(),
369
+ postCondition: async () => await hasTypedInChat()
370
+ }
371
+ ]
372
+ });
373
+ //#endregion
374
+ //#region src/howto-panel.ts
375
+ var TOPIC_SHOW_HOWTO_PANEL = "howto/show-panel";
376
+ var TOPIC_TOGGLE_HOWTO_PANEL = "howto/toggle-panel";
377
+ var SETTINGS_KEY = "howto-panel";
378
+ var DEFAULT_POSITION = {
379
+ x: 100,
380
+ y: 100
381
+ };
382
+ var DEFAULT_PANEL_SIZE = {
383
+ width: 400,
384
+ height: 300
385
+ };
386
+ var LyraHowToPanel = class LyraHowToPanel extends LyraWidget {
387
+ constructor(..._args) {
388
+ super(..._args);
389
+ this.contributions = [];
390
+ this.activeContributionId = null;
391
+ this.stepStates = /* @__PURE__ */ new Map();
392
+ this.isMinimized = false;
393
+ this.isVisible = false;
394
+ this.positionX = DEFAULT_POSITION.x;
395
+ this.positionY = DEFAULT_POSITION.y;
396
+ this.isDragging = false;
397
+ this.dragPreviewPosition = null;
398
+ this.dragStartPosition = {
399
+ x: 0,
400
+ y: 0
401
+ };
402
+ this.panelRef = createRef();
403
+ this.handleDragStart = (e) => {
404
+ const target = e.target;
405
+ if (this.isDragTarget(target) || !this.panelRef.value) return;
406
+ const rect = this.panelRef.value.getBoundingClientRect();
407
+ this.dragStartPosition = {
408
+ x: e.clientX - rect.left,
409
+ y: e.clientY - rect.top
410
+ };
411
+ this.isDragging = true;
412
+ this.dragPreviewPosition = {
413
+ x: this.positionX,
414
+ y: this.positionY
415
+ };
416
+ this.requestUpdate();
417
+ e.preventDefault();
418
+ e.stopPropagation();
419
+ };
420
+ this.handleDragMove = (e) => {
421
+ if (!this.isDragging || !this.dragPreviewPosition) return;
422
+ const bounds = this.getViewportBounds();
423
+ const newX = Math.max(0, Math.min(e.clientX - this.dragStartPosition.x, bounds.maxX));
424
+ const newY = Math.max(0, Math.min(e.clientY - this.dragStartPosition.y, bounds.maxY));
425
+ if (this.dragPreviewPosition.x !== newX || this.dragPreviewPosition.y !== newY) {
426
+ this.dragPreviewPosition = {
427
+ x: newX,
428
+ y: newY
429
+ };
430
+ this.requestUpdate();
431
+ }
432
+ };
433
+ this.handleDragEnd = async () => {
434
+ if (!this.isDragging || !this.dragPreviewPosition) return;
435
+ this.isDragging = false;
436
+ const previewPos = this.dragPreviewPosition;
437
+ this.dragPreviewPosition = null;
438
+ this.positionX = previewPos.x;
439
+ this.positionY = previewPos.y;
440
+ await this.saveSettings();
441
+ this.requestUpdate();
442
+ };
443
+ }
444
+ async doBeforeUI() {
445
+ this.loadContributions();
446
+ subscribe(TOPIC_CONTRIBUTEIONS_CHANGED, (event) => {
447
+ if (event.target === "system.howtos") {
448
+ this.loadContributions();
449
+ this.requestUpdate();
450
+ }
451
+ });
452
+ subscribe(TOPIC_SHOW_HOWTO_PANEL, () => this.showPanel());
453
+ subscribe(TOPIC_TOGGLE_HOWTO_PANEL, () => this.toggleVisibility());
454
+ await this.loadSettings();
455
+ if (!workspaceService.isConnected() && this.isVisible === false) {
456
+ const settings = await appSettings.getDialogSetting(SETTINGS_KEY);
457
+ if (!settings || settings.visible === void 0) {
458
+ this.isVisible = true;
459
+ await this.saveSettings();
460
+ }
461
+ }
462
+ subscribe(TOPIC_WORKSPACE_CONNECTED, () => {
463
+ const checkAutoHide = async () => {
464
+ const settings = await appSettings.getDialogSetting(SETTINGS_KEY);
465
+ if (!settings || settings.visible === void 0) {}
466
+ };
467
+ checkAutoHide();
468
+ });
469
+ }
470
+ doInitUI() {
471
+ this.boundHandleDragMove = this.handleDragMove.bind(this);
472
+ this.boundHandleDragEnd = this.handleDragEnd.bind(this);
473
+ document.addEventListener("mousemove", this.boundHandleDragMove);
474
+ document.addEventListener("mouseup", this.boundHandleDragEnd);
475
+ }
476
+ firstUpdated(_changedProperties) {
477
+ super.firstUpdated(_changedProperties);
478
+ }
479
+ loadContributions() {
480
+ this.contributions = howToService.getAllContributions();
481
+ }
482
+ async loadSettings() {
483
+ const settings = await appSettings.getDialogSetting(SETTINGS_KEY);
484
+ if (settings) {
485
+ if (settings.position) {
486
+ this.positionX = settings.position.x || DEFAULT_POSITION.x;
487
+ this.positionY = settings.position.y || DEFAULT_POSITION.y;
488
+ }
489
+ if (settings.visible !== void 0) this.isVisible = settings.visible;
490
+ }
491
+ }
492
+ async saveSettings() {
493
+ await appSettings.setDialogSetting(SETTINGS_KEY, {
494
+ position: {
495
+ x: this.positionX,
496
+ y: this.positionY
497
+ },
498
+ visible: this.isVisible
499
+ });
500
+ }
501
+ isDragTarget(target) {
502
+ return !!(target.closest(".header-actions") || target.closest("wa-button"));
503
+ }
504
+ getViewportBounds() {
505
+ const panelWidth = this.panelRef.value?.offsetWidth || DEFAULT_PANEL_SIZE.width;
506
+ const panelHeight = this.panelRef.value?.offsetHeight || DEFAULT_PANEL_SIZE.height;
507
+ return {
508
+ maxX: window.innerWidth - panelWidth,
509
+ maxY: window.innerHeight - panelHeight
510
+ };
511
+ }
512
+ async startHowTo(contributionId) {
513
+ const contribution = howToService.getContribution(contributionId);
514
+ if (!contribution) {
515
+ toastError(`HowTo "${contributionId}" not found`);
516
+ return;
517
+ }
518
+ this.cleanupHowTo();
519
+ this.activeContributionId = contributionId;
520
+ this.isMinimized = false;
521
+ const states = contribution.steps.map((step) => ({
522
+ step,
523
+ status: "pending"
524
+ }));
525
+ this.stepStates.set(contributionId, states);
526
+ if (contribution.initialize) {
527
+ const context = {
528
+ requestUpdate: () => this.recheckActiveStepConditions(),
529
+ contributionId
530
+ };
531
+ this.howtoCleanup = contribution.initialize(context) || void 0;
532
+ }
533
+ this.requestUpdate();
534
+ await this.checkPreConditions(contributionId, 0);
535
+ }
536
+ cleanupHowTo() {
537
+ if (this.howtoCleanup) {
538
+ this.howtoCleanup();
539
+ this.howtoCleanup = void 0;
540
+ }
541
+ }
542
+ getStepState(contributionId, stepIndex) {
543
+ const states = this.stepStates.get(contributionId);
544
+ return states && stepIndex < states.length ? states[stepIndex] : null;
545
+ }
546
+ async checkPreConditions(contributionId, stepIndex) {
547
+ const state = this.getStepState(contributionId, stepIndex);
548
+ if (!state) return;
549
+ if (!state.step.preCondition) {
550
+ state.preConditionMet = true;
551
+ this.requestUpdate();
552
+ return;
553
+ }
554
+ try {
555
+ state.preConditionMet = await state.step.preCondition();
556
+ this.requestUpdate();
557
+ } catch (error) {
558
+ console.error(`Pre-condition check failed for step ${state.step.id}:`, error);
559
+ state.preConditionMet = false;
560
+ this.requestUpdate();
561
+ }
562
+ }
563
+ async checkPostConditions(contributionId, stepIndex) {
564
+ const state = this.getStepState(contributionId, stepIndex);
565
+ if (!state) return;
566
+ if (!state.step.postCondition) {
567
+ this.completeStep(contributionId, stepIndex);
568
+ return;
569
+ }
570
+ try {
571
+ const result = await state.step.postCondition();
572
+ state.postConditionMet = result;
573
+ state.status = result ? "completed" : "failed";
574
+ if (result) this.activateNextStep(contributionId, stepIndex);
575
+ this.requestUpdate();
576
+ } catch (error) {
577
+ console.error(`Post-condition check failed for step ${state.step.id}:`, error);
578
+ state.postConditionMet = false;
579
+ state.status = "failed";
580
+ this.requestUpdate();
581
+ }
582
+ }
583
+ completeStep(contributionId, stepIndex) {
584
+ const state = this.getStepState(contributionId, stepIndex);
585
+ if (!state) return;
586
+ state.status = "completed";
587
+ this.activateNextStep(contributionId, stepIndex);
588
+ this.requestUpdate();
589
+ }
590
+ async activateNextStep(contributionId, stepIndex) {
591
+ const states = this.stepStates.get(contributionId);
592
+ if (!states || stepIndex + 1 >= states.length) return;
593
+ const nextState = states[stepIndex + 1];
594
+ nextState.status = "active";
595
+ await this.checkPreConditions(contributionId, stepIndex + 1);
596
+ }
597
+ /**
598
+ * Re-checks conditions for active and pending steps when workspace or editor state changes
599
+ */
600
+ async recheckActiveStepConditions() {
601
+ if (!this.activeContributionId) return;
602
+ const states = this.stepStates.get(this.activeContributionId);
603
+ if (!states) return;
604
+ const activeStepIndex = states.findIndex((state) => state.status === "active");
605
+ if (activeStepIndex !== -1) {
606
+ const activeState = states[activeStepIndex];
607
+ const step = activeState.step;
608
+ if (step.postCondition) try {
609
+ if (await step.postCondition() && activeState.status === "active") {
610
+ await this.checkPostConditions(this.activeContributionId, activeStepIndex);
611
+ return;
612
+ }
613
+ } catch (error) {}
614
+ }
615
+ for (let i = 0; i < states.length; i++) {
616
+ const state = states[i];
617
+ if (state.status === "pending" && state.step.preCondition) await this.checkPreConditions(this.activeContributionId, i);
618
+ }
619
+ this.requestUpdate();
620
+ }
621
+ async executeStep(contributionId, stepIndex) {
622
+ const state = this.getStepState(contributionId, stepIndex);
623
+ if (!state) return;
624
+ if (!await this.validatePreConditions(state, contributionId, stepIndex)) return;
625
+ state.status = "active";
626
+ this.requestUpdate();
627
+ if (state.step.command && !await this.executeStepCommand(state)) return;
628
+ await this.checkPostConditions(contributionId, stepIndex);
629
+ }
630
+ async validatePreConditions(state, contributionId, stepIndex) {
631
+ if (!state.step.preCondition) return true;
632
+ if (state.preConditionMet === void 0 || state.preConditionMet === false) await this.checkPreConditions(contributionId, stepIndex);
633
+ if (state.preConditionMet !== true) {
634
+ toastError(`Pre-conditions not met for step: ${state.step.title}`);
635
+ return false;
636
+ }
637
+ return true;
638
+ }
639
+ async executeStepCommand(state) {
640
+ if (!state.step.command) return true;
641
+ try {
642
+ const rawParams = state.step.commandParams;
643
+ const params = typeof rawParams === "function" ? await rawParams() : rawParams || {};
644
+ const execContext = commandRegistry.createExecutionContext(params);
645
+ await commandRegistry.execute(state.step.command, execContext);
646
+ return true;
647
+ } catch (error) {
648
+ console.error(`Failed to execute command for step ${state.step.id}:`, error);
649
+ toastError(`Failed to execute step: ${state.step.title}`);
650
+ state.status = "failed";
651
+ this.requestUpdate();
652
+ return false;
653
+ }
654
+ }
655
+ async runStepCommand(contributionId, stepIndex) {
656
+ const state = this.getStepState(contributionId, stepIndex);
657
+ if (!state || !state.step.command) return;
658
+ if (state.step.preCondition) {
659
+ if (!await state.step.preCondition()) {
660
+ toastError(`Pre-conditions not met for step: ${state.step.title}`);
661
+ return;
662
+ }
663
+ }
664
+ if (await this.executeStepCommand(state)) await this.checkPostConditions(contributionId, stepIndex);
665
+ }
666
+ skipStep(contributionId, stepIndex) {
667
+ const state = this.getStepState(contributionId, stepIndex);
668
+ if (!state || !state.step.optional) return;
669
+ state.status = "skipped";
670
+ this.activateNextStep(contributionId, stepIndex);
671
+ this.requestUpdate();
672
+ }
673
+ closeHowTo() {
674
+ this.cleanupHowTo();
675
+ this.activeContributionId = null;
676
+ this.stepStates.clear();
677
+ this.requestUpdate();
678
+ }
679
+ toggleMinimize() {
680
+ this.isMinimized = !this.isMinimized;
681
+ this.requestUpdate();
682
+ }
683
+ async showPanel() {
684
+ this.isVisible = true;
685
+ this.isMinimized = false;
686
+ await this.saveSettings();
687
+ this.requestUpdate();
688
+ }
689
+ async hidePanel() {
690
+ this.isVisible = false;
691
+ await this.saveSettings();
692
+ this.requestUpdate();
693
+ }
694
+ async toggleVisibility() {
695
+ if (this.isVisible) await this.hidePanel();
696
+ else await this.showPanel();
697
+ }
698
+ renderStep(state, index, contributionId) {
699
+ const { step, status, preConditionMet, postConditionMet } = state;
700
+ const isActive = status === "active";
701
+ const isCompleted = status === "completed";
702
+ const isFailed = status === "failed";
703
+ const isPending = status === "pending";
704
+ const isSkipped = status === "skipped";
705
+ return html`
706
+ <div class="step ${status}" ?data-active=${isActive}>
707
+ <div class="step-header">
708
+ <div class="step-number">${index + 1}</div>
709
+ <div class="step-title">${step.title}</div>
710
+ <div class="step-status">
711
+ ${step.command ? html`
712
+ <wa-button
713
+ size="small"
714
+ appearance="plain"
715
+ @click=${(e) => {
716
+ e.stopPropagation();
717
+ this.runStepCommand(contributionId, index);
718
+ }}
719
+ title="Run step command"
720
+ >
721
+ <wa-icon name="play"></wa-icon>
722
+ </wa-button>
723
+ ` : nothing}
724
+ ${isCompleted ? html`<wa-icon name="check-circle" class="status-icon completed"></wa-icon>` : nothing}
725
+ ${isFailed ? html`<wa-icon name="xmark-circle" class="status-icon failed"></wa-icon>` : nothing}
726
+ ${isSkipped ? html`<wa-icon name="minus-circle" class="status-icon skipped"></wa-icon>` : nothing}
727
+ ${isPending ? html`<wa-icon name="circle" class="status-icon pending"></wa-icon>` : nothing}
728
+ ${isActive ? html`<wa-icon name="play-circle" class="status-icon active"></wa-icon>` : nothing}
729
+ </div>
730
+ </div>
731
+ <div class="step-description">${step.description}</div>
732
+ ${step.preCondition && preConditionMet !== void 0 ? html`
733
+ <div class="condition pre-condition ${preConditionMet ? "met" : "not-met"}">
734
+ <wa-icon name="${preConditionMet ? "check" : "xmark"}"></wa-icon>
735
+ <span>Pre-condition: ${preConditionMet ? "Met" : "Not met"}</span>
736
+ </div>
737
+ ` : nothing}
738
+ ${step.postCondition && postConditionMet !== void 0 ? html`
739
+ <div class="condition post-condition ${postConditionMet ? "met" : "not-met"}">
740
+ <wa-icon name="${postConditionMet ? "check" : "xmark"}"></wa-icon>
741
+ <span>Post-condition: ${postConditionMet ? "Met" : "Not met"}</span>
742
+ </div>
743
+ ` : nothing}
744
+ ${isActive && step.optional ? html`
745
+ <div class="step-actions">
746
+ <wa-button size="small" appearance="outline" @click=${() => this.skipStep(contributionId, index)}>
747
+ <wa-icon name="forward"></wa-icon>
748
+ Skip
749
+ </wa-button>
750
+ </div>
751
+ ` : nothing}
752
+ </div>
753
+ `;
754
+ }
755
+ render() {
756
+ if (!this.isVisible) return nothing;
757
+ const activeContribution = this.activeContributionId ? howToService.getContribution(this.activeContributionId) : null;
758
+ const activeStepStates = this.activeContributionId ? this.stepStates.get(this.activeContributionId) || [] : [];
759
+ return html`
760
+ ${this.dragPreviewPosition ? html`
761
+ <div
762
+ class="howto-panel-drag-preview"
763
+ style=${styleMap({
764
+ left: `${this.dragPreviewPosition.x}px`,
765
+ top: `${this.dragPreviewPosition.y}px`,
766
+ width: `${this.panelRef.value?.offsetWidth || DEFAULT_PANEL_SIZE.width}px`,
767
+ height: `${this.panelRef.value?.offsetHeight || DEFAULT_PANEL_SIZE.height}px`,
768
+ display: "block",
769
+ visibility: "visible"
770
+ })}
771
+ ></div>
772
+ ` : nothing}
773
+ <div
774
+ class="howto-panel ${this.isMinimized ? "minimized" : ""} ${this.dragPreviewPosition ? "dragging" : ""}"
775
+ style=${styleMap({
776
+ left: `${this.positionX}px`,
777
+ top: `${this.positionY}px`,
778
+ transform: "translateZ(0)"
779
+ })}
780
+ ${ref(this.panelRef)}
781
+ >
782
+ <div class="panel-header" @mousedown=${this.handleDragStart}>
783
+ <div class="header-title">
784
+ <wa-icon name="list-check"></wa-icon>
785
+ <span>HowTo Workflows</span>
786
+ </div>
787
+ <div class="header-actions" @mousedown=${(e) => e.stopPropagation()}>
788
+ <wa-button
789
+ size="small"
790
+ appearance="plain"
791
+ @click=${this.toggleMinimize}
792
+ title="${this.isMinimized ? "Expand" : "Minimize"}"
793
+ >
794
+ <wa-icon name="${this.isMinimized ? "chevron-up" : "chevron-down"}"></wa-icon>
795
+ </wa-button>
796
+ <wa-button
797
+ size="small"
798
+ appearance="plain"
799
+ @click=${this.hidePanel}
800
+ title="Hide Panel"
801
+ >
802
+ <wa-icon name="xmark"></wa-icon>
803
+ </wa-button>
804
+ </div>
805
+ </div>
806
+
807
+ ${!this.isMinimized ? html`
808
+ <div class="panel-content">
809
+ ${activeContribution ? html`
810
+ <div class="active-workflow">
811
+ <div class="workflow-header">
812
+ <div class="workflow-title-section">
813
+ <h3>${typeof activeContribution.title === "function" ? activeContribution.title() : activeContribution.title}</h3>
814
+ ${activeContribution.description ? html`
815
+ <p class="workflow-description">${typeof activeContribution.description === "function" ? activeContribution.description() : activeContribution.description}</p>
816
+ ` : nothing}
817
+ </div>
818
+ <wa-button
819
+ size="small"
820
+ appearance="plain"
821
+ @click=${this.closeHowTo}
822
+ title="Close HowTo"
823
+ >
824
+ <wa-icon name="xmark"></wa-icon>
825
+ </wa-button>
826
+ </div>
827
+ <div class="steps-list">
828
+ ${activeStepStates.map((state, index) => this.renderStep(state, index, this.activeContributionId))}
829
+ </div>
830
+ </div>
831
+ ` : html`
832
+ <div class="workflows-list">
833
+ <h3>Available Workflows</h3>
834
+ ${this.contributions.length === 0 ? html`
835
+ <div class="empty-state">
836
+ <wa-icon name="list-check" style="font-size: 2em; opacity: 0.5; margin-bottom: 12px;"></wa-icon>
837
+ <p>No HowTo workflows available yet.</p>
838
+ <p style="font-size: 0.9em; opacity: 0.7;">Extensions can register workflows via the contribution registry.</p>
839
+ </div>
840
+ ` : this.contributions.map((contrib) => {
841
+ const title = typeof contrib.title === "function" ? contrib.title() : contrib.title;
842
+ const description = contrib.description ? typeof contrib.description === "function" ? contrib.description() : contrib.description : null;
843
+ return html`
844
+ <div class="workflow-item" @click=${() => this.startHowTo(contrib.id)}>
845
+ ${contrib.icon ? html`
846
+ <wa-icon name="${contrib.icon}"></wa-icon>
847
+ ` : html`
848
+ <wa-icon name="list-check"></wa-icon>
849
+ `}
850
+ <div class="workflow-info">
851
+ <div class="workflow-title">${title}</div>
852
+ ${description ? html`
853
+ <div class="workflow-desc">${description}</div>
854
+ ` : nothing}
855
+ <div class="workflow-meta">${contrib.steps.length} step${contrib.steps.length !== 1 ? "s" : ""}</div>
856
+ </div>
857
+ </div>
858
+ `;
859
+ })}
860
+ </div>
861
+ `}
862
+ </div>
863
+ ` : nothing}
864
+ </div>
865
+ `;
866
+ }
867
+ static {
868
+ this.styles = css`
869
+ :host {
870
+ display: block;
871
+ position: fixed;
872
+ z-index: 10000;
873
+ pointer-events: none;
874
+ }
875
+
876
+ .howto-panel-drag-preview {
877
+ position: fixed !important;
878
+ border: 3px dashed var(--wa-color-primary-50, #0066cc) !important;
879
+ background: var(--wa-color-primary-05, rgba(0, 102, 204, 0.05)) !important;
880
+ border-radius: var(--wa-border-radius-medium, 8px);
881
+ z-index: 10001 !important;
882
+ pointer-events: none !important;
883
+ opacity: 0.8 !important;
884
+ box-sizing: border-box;
885
+ display: block !important;
886
+ visibility: visible !important;
887
+ min-width: 100px;
888
+ min-height: 100px;
889
+ }
890
+
891
+ :host-context(.wa-light) .howto-panel-drag-preview {
892
+ background: var(--wa-color-primary-95);
893
+ border-color: var(--wa-color-primary-50);
894
+ }
895
+
896
+ .howto-panel {
897
+ position: fixed !important;
898
+ width: 400px;
899
+ max-height: 600px;
900
+ background: var(--wa-color-surface-raised, var(--wa-color-neutral-05));
901
+ border: var(--wa-border-width-s, 1px) solid var(--wa-color-neutral-border-loud, var(--wa-color-neutral-25));
902
+ border-radius: var(--wa-border-radius-medium, 8px);
903
+ box-shadow: var(--wa-shadow-large, 0 8px 24px rgba(0, 0, 0, 0.8));
904
+ pointer-events: all;
905
+ display: flex;
906
+ flex-direction: column;
907
+ overflow: hidden;
908
+ }
909
+
910
+ :host-context(.wa-light) .howto-panel {
911
+ background: var(--wa-color-surface-raised, var(--wa-color-neutral-95));
912
+ border-color: var(--wa-color-neutral-border-loud, var(--wa-color-neutral-75));
913
+ box-shadow: var(--wa-shadow-large, 0 8px 24px rgba(0, 0, 0, 0.2));
914
+ }
915
+
916
+ .howto-panel.minimized {
917
+ max-height: auto;
918
+ }
919
+
920
+ .howto-panel.dragging {
921
+ opacity: 0.5;
922
+ }
923
+
924
+ .panel-header {
925
+ display: flex;
926
+ align-items: center;
927
+ justify-content: space-between;
928
+ padding: var(--wa-spacing-medium, 12px) var(--wa-spacing-large, 16px);
929
+ background: var(--wa-color-surface-lowered, var(--wa-color-neutral-10));
930
+ border-bottom: var(--wa-border-width-s, 1px) solid var(--wa-color-neutral-border-loud, var(--wa-color-neutral-25));
931
+ cursor: move;
932
+ user-select: none;
933
+ }
934
+
935
+ :host-context(.wa-light) .panel-header {
936
+ background: var(--wa-color-surface-lowered, var(--wa-color-neutral-90));
937
+ border-bottom-color: var(--wa-color-neutral-border-loud, var(--wa-color-neutral-75));
938
+ }
939
+
940
+ .header-title {
941
+ display: flex;
942
+ align-items: center;
943
+ gap: var(--wa-spacing-small, 8px);
944
+ font-weight: 600;
945
+ color: var(--wa-color-text-normal, var(--wa-color-neutral-90));
946
+ }
947
+
948
+ :host-context(.wa-light) .header-title {
949
+ color: var(--wa-color-text-normal, var(--wa-color-neutral-10));
950
+ }
951
+
952
+ .header-actions {
953
+ display: flex;
954
+ gap: var(--wa-spacing-x-small, 4px);
955
+ }
956
+
957
+ .panel-content {
958
+ flex: 1;
959
+ overflow-y: auto;
960
+ padding: var(--wa-spacing-large, 16px);
961
+ }
962
+
963
+ .workflows-list h3 {
964
+ margin: 0 0 var(--wa-spacing-medium, 12px) 0;
965
+ font-size: var(--wa-font-size-medium, 14px);
966
+ font-weight: 600;
967
+ color: var(--wa-color-text-normal, var(--wa-color-neutral-80));
968
+ }
969
+
970
+ :host-context(.wa-light) .workflows-list h3 {
971
+ color: var(--wa-color-text-normal, var(--wa-color-neutral-20));
972
+ }
973
+
974
+ .workflow-item {
975
+ display: flex;
976
+ align-items: flex-start;
977
+ gap: var(--wa-spacing-medium, 12px);
978
+ padding: var(--wa-spacing-medium, 12px);
979
+ margin-bottom: var(--wa-spacing-small, 8px);
980
+ background: var(--wa-color-surface-lowered, var(--wa-color-neutral-10));
981
+ border: var(--wa-border-width-s, 1px) solid var(--wa-color-neutral-border-subtle, var(--wa-color-neutral-20));
982
+ border-radius: var(--wa-border-radius-small, 6px);
983
+ cursor: pointer;
984
+ transition: all var(--wa-transition-medium, 0.2s);
985
+ }
986
+
987
+ :host-context(.wa-light) .workflow-item {
988
+ background: var(--wa-color-surface-lowered, var(--wa-color-neutral-90));
989
+ border-color: var(--wa-color-neutral-border-subtle, var(--wa-color-neutral-80));
990
+ }
991
+
992
+ .workflow-item:hover {
993
+ background: var(--wa-color-mix-hover, var(--wa-color-neutral-15));
994
+ border-color: var(--wa-color-neutral-border-loud, var(--wa-color-neutral-30));
995
+ }
996
+
997
+ :host-context(.wa-light) .workflow-item:hover {
998
+ background: var(--wa-color-mix-hover, var(--wa-color-neutral-85));
999
+ border-color: var(--wa-color-neutral-border-loud, var(--wa-color-neutral-70));
1000
+ }
1001
+
1002
+ .workflow-info {
1003
+ flex: 1;
1004
+ }
1005
+
1006
+ .workflow-title {
1007
+ font-weight: 600;
1008
+ margin-bottom: var(--wa-spacing-x-small, 4px);
1009
+ color: var(--wa-color-text-normal, var(--wa-color-neutral-90));
1010
+ }
1011
+
1012
+ :host-context(.wa-light) .workflow-title {
1013
+ color: var(--wa-color-text-normal, var(--wa-color-neutral-10));
1014
+ }
1015
+
1016
+ .workflow-desc {
1017
+ font-size: var(--wa-font-size-small, 12px);
1018
+ color: var(--wa-color-text-subtle, var(--wa-color-neutral-70));
1019
+ margin-bottom: var(--wa-spacing-x-small, 4px);
1020
+ }
1021
+
1022
+ :host-context(.wa-light) .workflow-desc {
1023
+ color: var(--wa-color-text-subtle, var(--wa-color-neutral-30));
1024
+ }
1025
+
1026
+ .workflow-meta {
1027
+ font-size: var(--wa-font-size-x-small, 11px);
1028
+ color: var(--wa-color-text-quiet, var(--wa-color-neutral-60));
1029
+ }
1030
+
1031
+ :host-context(.wa-light) .workflow-meta {
1032
+ color: var(--wa-color-text-quiet, var(--wa-color-neutral-40));
1033
+ }
1034
+
1035
+ .active-workflow {
1036
+ display: flex;
1037
+ flex-direction: column;
1038
+ gap: var(--wa-spacing-large, 16px);
1039
+ }
1040
+
1041
+ .workflow-header {
1042
+ display: flex;
1043
+ align-items: flex-start;
1044
+ justify-content: space-between;
1045
+ gap: var(--wa-spacing-medium, 12px);
1046
+ margin-bottom: var(--wa-spacing-medium, 12px);
1047
+ }
1048
+
1049
+ .workflow-title-section {
1050
+ flex: 1;
1051
+ }
1052
+
1053
+ .workflow-header h3 {
1054
+ margin: 0 0 var(--wa-spacing-small, 8px) 0;
1055
+ font-size: var(--wa-font-size-large, 16px);
1056
+ font-weight: 600;
1057
+ color: var(--wa-color-text-normal, var(--wa-color-neutral-90));
1058
+ }
1059
+
1060
+ :host-context(.wa-light) .workflow-header h3 {
1061
+ color: var(--wa-color-text-normal, var(--wa-color-neutral-10));
1062
+ }
1063
+
1064
+ .workflow-description {
1065
+ margin: 0;
1066
+ font-size: var(--wa-font-size-medium, 13px);
1067
+ color: var(--wa-color-text-subtle, var(--wa-color-neutral-70));
1068
+ }
1069
+
1070
+ :host-context(.wa-light) .workflow-description {
1071
+ color: var(--wa-color-text-subtle, var(--wa-color-neutral-30));
1072
+ }
1073
+
1074
+ .steps-list {
1075
+ display: flex;
1076
+ flex-direction: column;
1077
+ gap: var(--wa-spacing-medium, 12px);
1078
+ }
1079
+
1080
+ .step {
1081
+ padding: var(--wa-spacing-medium, 12px);
1082
+ background: var(--wa-color-surface-lowered, var(--wa-color-neutral-10));
1083
+ border: var(--wa-border-width-s, 1px) solid var(--wa-color-neutral-border-subtle, var(--wa-color-neutral-20));
1084
+ border-radius: var(--wa-border-radius-small, 6px);
1085
+ transition: all var(--wa-transition-medium, 0.2s);
1086
+ }
1087
+
1088
+ :host-context(.wa-light) .step {
1089
+ background: var(--wa-color-surface-lowered, var(--wa-color-neutral-90));
1090
+ border-color: var(--wa-color-neutral-border-subtle, var(--wa-color-neutral-80));
1091
+ }
1092
+
1093
+ .step[data-active="true"] {
1094
+ border-color: var(--wa-color-primary-50);
1095
+ background: var(--wa-color-primary-05);
1096
+ }
1097
+
1098
+ :host-context(.wa-light) .step[data-active="true"] {
1099
+ background: var(--wa-color-primary-95);
1100
+ border-color: var(--wa-color-primary-50);
1101
+ }
1102
+
1103
+ .step.completed {
1104
+ border-color: var(--wa-color-success-50);
1105
+ background: var(--wa-color-success-05);
1106
+ }
1107
+
1108
+ :host-context(.wa-light) .step.completed {
1109
+ background: var(--wa-color-success-95);
1110
+ border-color: var(--wa-color-success-50);
1111
+ }
1112
+
1113
+ .step.failed {
1114
+ border-color: var(--wa-color-danger-50);
1115
+ background: var(--wa-color-danger-05);
1116
+ }
1117
+
1118
+ :host-context(.wa-light) .step.failed {
1119
+ background: var(--wa-color-danger-95);
1120
+ border-color: var(--wa-color-danger-50);
1121
+ }
1122
+
1123
+ .step-header {
1124
+ display: flex;
1125
+ align-items: center;
1126
+ gap: var(--wa-spacing-medium, 12px);
1127
+ margin-bottom: var(--wa-spacing-small, 8px);
1128
+ }
1129
+
1130
+ .step-number {
1131
+ width: 24px;
1132
+ height: 24px;
1133
+ display: flex;
1134
+ align-items: center;
1135
+ justify-content: center;
1136
+ background: var(--wa-color-surface-lowered, var(--wa-color-neutral-20));
1137
+ border-radius: 50%;
1138
+ font-size: var(--wa-font-size-small, 12px);
1139
+ font-weight: 600;
1140
+ color: var(--wa-color-text-normal, var(--wa-color-neutral-80));
1141
+ }
1142
+
1143
+ :host-context(.wa-light) .step-number {
1144
+ background: var(--wa-color-surface-lowered, var(--wa-color-neutral-80));
1145
+ color: var(--wa-color-text-normal, var(--wa-color-neutral-20));
1146
+ }
1147
+
1148
+ .step-title {
1149
+ flex: 1;
1150
+ font-weight: 600;
1151
+ color: var(--wa-color-text-normal, var(--wa-color-neutral-90));
1152
+ }
1153
+
1154
+ :host-context(.wa-light) .step-title {
1155
+ color: var(--wa-color-text-normal, var(--wa-color-neutral-10));
1156
+ }
1157
+
1158
+ .step-status {
1159
+ display: flex;
1160
+ align-items: center;
1161
+ gap: var(--wa-spacing-small, 8px);
1162
+ }
1163
+
1164
+ .status-icon {
1165
+ width: 20px;
1166
+ height: 20px;
1167
+ }
1168
+
1169
+ .status-icon.completed {
1170
+ color: var(--wa-color-success-50);
1171
+ }
1172
+
1173
+ .status-icon.failed {
1174
+ color: var(--wa-color-danger-50);
1175
+ }
1176
+
1177
+ .status-icon.skipped {
1178
+ color: var(--wa-color-neutral-50);
1179
+ }
1180
+
1181
+ .status-icon.active {
1182
+ color: var(--wa-color-primary-50);
1183
+ }
1184
+
1185
+ .status-icon.pending {
1186
+ color: var(--wa-color-neutral-50);
1187
+ }
1188
+
1189
+ .step-description {
1190
+ font-size: var(--wa-font-size-medium, 13px);
1191
+ color: var(--wa-color-text-subtle, var(--wa-color-neutral-70));
1192
+ margin-bottom: var(--wa-spacing-small, 8px);
1193
+ line-height: 1.4;
1194
+ }
1195
+
1196
+ :host-context(.wa-light) .step-description {
1197
+ color: var(--wa-color-text-subtle, var(--wa-color-neutral-30));
1198
+ }
1199
+
1200
+ .condition {
1201
+ display: flex;
1202
+ align-items: center;
1203
+ gap: var(--wa-spacing-x-small, 6px);
1204
+ font-size: var(--wa-font-size-small, 12px);
1205
+ padding: var(--wa-spacing-x-small, 6px) var(--wa-spacing-small, 8px);
1206
+ border-radius: var(--wa-border-radius-x-small, 4px);
1207
+ margin-bottom: var(--wa-spacing-small, 8px);
1208
+ }
1209
+
1210
+ .condition.met {
1211
+ background: var(--wa-color-success-10);
1212
+ color: var(--wa-color-success-70);
1213
+ }
1214
+
1215
+ :host-context(.wa-light) .condition.met {
1216
+ background: var(--wa-color-success-90);
1217
+ color: var(--wa-color-success-30);
1218
+ }
1219
+
1220
+ .condition.not-met {
1221
+ background: var(--wa-color-danger-10);
1222
+ color: var(--wa-color-danger-70);
1223
+ }
1224
+
1225
+ :host-context(.wa-light) .condition.not-met {
1226
+ background: var(--wa-color-danger-90);
1227
+ color: var(--wa-color-danger-30);
1228
+ }
1229
+
1230
+ .step-actions {
1231
+ display: flex;
1232
+ gap: var(--wa-spacing-small, 8px);
1233
+ margin-top: var(--wa-spacing-small, 8px);
1234
+ }
1235
+
1236
+ .empty-state {
1237
+ display: flex;
1238
+ flex-direction: column;
1239
+ align-items: center;
1240
+ justify-content: center;
1241
+ padding: var(--wa-spacing-x-large, 40px) var(--wa-spacing-large, 20px);
1242
+ text-align: center;
1243
+ color: var(--wa-color-text-subtle, var(--wa-color-neutral-70));
1244
+ }
1245
+
1246
+ :host-context(.wa-light) .empty-state {
1247
+ color: var(--wa-color-text-subtle, var(--wa-color-neutral-30));
1248
+ }
1249
+
1250
+ .empty-state p {
1251
+ margin: var(--wa-spacing-small, 8px) 0;
1252
+ }
1253
+ `;
1254
+ }
1255
+ };
1256
+ _decorate([state()], LyraHowToPanel.prototype, "contributions", void 0);
1257
+ _decorate([state()], LyraHowToPanel.prototype, "activeContributionId", void 0);
1258
+ _decorate([state()], LyraHowToPanel.prototype, "stepStates", void 0);
1259
+ _decorate([state()], LyraHowToPanel.prototype, "isMinimized", void 0);
1260
+ _decorate([state()], LyraHowToPanel.prototype, "isVisible", void 0);
1261
+ _decorate([state()], LyraHowToPanel.prototype, "positionX", void 0);
1262
+ _decorate([state()], LyraHowToPanel.prototype, "positionY", void 0);
1263
+ _decorate([state()], LyraHowToPanel.prototype, "dragPreviewPosition", void 0);
1264
+ LyraHowToPanel = _decorate([customElement("lyra-howto-panel")], LyraHowToPanel);
1265
+ //#endregion
1266
+ //#region src/howto-extension.ts
1267
+ var logger = createLogger("HowToExtension");
1268
+ /**
1269
+ * HowTo System Extension
1270
+ *
1271
+ * Provides a system for registering and displaying step-by-step workflows
1272
+ * that guide users through specific processes with pre and post condition checks.
1273
+ *
1274
+ * Features:
1275
+ * - Register HowToContributions via contribution registry
1276
+ * - Floating, draggable UI panel
1277
+ * - Sequential step execution
1278
+ * - Pre and post condition validation
1279
+ * - Step status tracking
1280
+ */
1281
+ function howToExtension(context) {
1282
+ logger.info("HowTo system extension loaded");
1283
+ rootContext.put("howToService", howToService);
1284
+ const ensurePanelInDOM = () => {
1285
+ if (document.querySelector("lyra-howto-panel")) return;
1286
+ const panel = document.createElement("lyra-howto-panel");
1287
+ document.body.appendChild(panel);
1288
+ logger.info("HowTo panel added to DOM");
1289
+ };
1290
+ if (document.body) requestAnimationFrame(() => {
1291
+ ensurePanelInDOM();
1292
+ });
1293
+ else {
1294
+ const checkDOM = () => {
1295
+ if (document.body) ensurePanelInDOM();
1296
+ else requestAnimationFrame(checkDOM);
1297
+ };
1298
+ requestAnimationFrame(checkDOM);
1299
+ }
1300
+ registerAll({
1301
+ command: {
1302
+ id: "howto.show-panel",
1303
+ name: "Show HowTo Panel",
1304
+ description: "Shows the HowTo workflows panel",
1305
+ icon: "list-check",
1306
+ parameters: []
1307
+ },
1308
+ handler: { execute: () => {
1309
+ publish(TOPIC_SHOW_HOWTO_PANEL, null);
1310
+ } }
1311
+ });
1312
+ registerAll({
1313
+ command: {
1314
+ id: "howto.toggle-panel",
1315
+ name: "Toggle HowTo Panel",
1316
+ description: "Toggles the visibility of the HowTo workflows panel",
1317
+ icon: "list-check",
1318
+ keyBinding: "CTRL+SHIFT+H",
1319
+ parameters: []
1320
+ },
1321
+ handler: { execute: () => {
1322
+ publish(TOPIC_TOGGLE_HOWTO_PANEL, null);
1323
+ } },
1324
+ contribution: {
1325
+ target: TOOLBAR_BOTTOM_END,
1326
+ icon: "list-check",
1327
+ label: "HowTo"
1328
+ }
1329
+ });
1330
+ logger.info("HowTo system extension initialized");
1331
+ }
1332
+ //#endregion
1333
+ export { HOWTO_CONTRIBUTION_TARGET, howToExtension as default, howToService };
1334
+
1335
+ //# sourceMappingURL=howto-extension-BEQ-0XJ7.js.map