@schandlergarcia/sf-web-components 1.9.86 → 1.9.88

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.
@@ -398,19 +398,27 @@ Define schemas outside component body. Always `required: true` on mandatory fiel
398
398
 
399
399
  ## AI Chat & Agent
400
400
 
401
- Two components for agent integration:
401
+ Two pieces for agent integration — a **ChatBar** component and the **`useEvaAgent` hook** (Agent REST API):
402
402
 
403
403
  - **`ChatBar`** — command-palette strip for suggested prompts. Place between the hero map and data panels (full-width, `px-4 pt-4`), NOT in the header. Always provide 3–5 `suggestions`.
404
- - **`AgentforceConversationClient`** — the real Salesforce agent. Import from `@/components/AgentforceConversationClient` and pass the agent ID. Renders as a floating widget.
404
+ - **`useEvaAgent`** — pre-installed hook at `src/hooks/useEvaAgent.ts`. Handles OAuth session messages → cleanup via the Agentforce Agent REST API. Config is in `src/config/agentApi.ts`. Vite proxy rules in `vite.config.ts` forward requests.
405
+
406
+ > **Do NOT use `AgentforceConversationClient`** (the iframe widget). We use the Agent REST API via `useEvaAgent`.
407
+ > **Do NOT create wrapper hooks** (useEvaChatAdapter, useDisruptionAlerts, etc.). Use `useEvaAgent` directly.
408
+ > **Do NOT create `.env` or `.env.local` files** — config is hardcoded in `src/config/agentApi.ts`.
405
409
 
406
410
  ```tsx
407
- import { AgentforceConversationClient } from "@/components/AgentforceConversationClient";
411
+ import useEvaAgent from "@/hooks/useEvaAgent";
412
+
413
+ const { messages, isReady, isSending, connect, sendMessage } = useEvaAgent();
414
+ useEffect(() => { connect(); }, [connect]);
408
415
 
409
- <AgentforceConversationClient agentId="0Xxa5000000relhCAA" agentLabel="Eva" />
416
+ const handleChat = (text: string) => {
417
+ sendMessage(text);
418
+ return { text: `Looking into: "${text}"…` };
419
+ };
410
420
  ```
411
421
 
412
- - Return `components` for structured data — never markdown tables in text
413
- - Always set height on `ChatPanel`
414
422
  - Extract `onSend` handlers to module scope when shared
415
423
 
416
424
  ## Tables
@@ -672,7 +672,7 @@ Dropdown select. Value `"all"` skips filtering.
672
672
  ## Data Utilities
673
673
 
674
674
  ### DataModeToggle
675
- **When:** Place in the app header to let users switch between sample and live data.
675
+ > ⚠️ **Do NOT use in the Engine Command Center dashboard.** The Engine dashboard controls data mode via `ENABLE_SAMPLE_DATA_CACHE` in `src/lib/dataStrategy.ts` — there is no UI toggle. Only use `DataModeToggle` in non-Engine dashboards that explicitly need a manual toggle.
676
676
 
677
677
  ```jsx
678
678
  <DataModeToggle className="ml-4" />
@@ -54,7 +54,7 @@ Wraps app. Provides `useDataMode()` → `{ mode, isSample, isLive, toggle, setMo
54
54
  ## Data Utilities
55
55
 
56
56
  ### DataModeToggle
57
- **When:** Place in the app header to let users switch between sample and live data.
57
+ > ⚠️ **Do NOT use in the Engine Command Center dashboard.** The Engine dashboard controls data mode via `ENABLE_SAMPLE_DATA_CACHE` in `src/lib/dataStrategy.ts` — there is no UI toggle. Only use `DataModeToggle` in non-Engine dashboards that explicitly need a manual toggle.
58
58
 
59
59
  ```jsx
60
60
  <DataModeToggle className="ml-4" />
package/CHANGELOG.md CHANGED
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.9.88] - 2026-04-07
9
+
10
+ ### Fixed
11
+ - **Postinstall `.a4drules` copy** — now copies all root-level `.md` files (`webapp-ui.md`, `webapp-data.md`, `validation-requirements.md`, `RULES.md`) and all subdirectories (`skills/`, `features/`, `troubleshooting/`), not just `skills/` and `features/`. Ensures the full build guide reaches consuming projects.
12
+ - **Postinstall vite proxy injection** — when `vite.config.ts` already exists, the postinstall now checks for and injects the Agent API proxy rules (`/sf-oauth`, `/sf-agent`) if they're missing, rather than silently skipping.
13
+ - **Reset script `.a4drules` restore** — the reset script now force-copies every `.md` file from the package's `.a4drules` to the project root, preventing stale stubs from persisting across resets.
14
+
15
+ ## [1.9.87] - 2026-04-07
16
+
17
+ ### Changed
18
+ - **SKILL.md AI Chat section** — replaced `AgentforceConversationClient` instructions with `useEvaAgent` hook usage and explicit prohibitions against iframe widget, wrapper hooks, and .env files.
19
+ - **Component library DataModeToggle docs** — added warning that DataModeToggle must NOT be used in the Engine Command Center dashboard (both SKILL.md and chat-data.md).
20
+
8
21
  ## [1.9.86] - 2026-04-07
9
22
 
10
23
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schandlergarcia/sf-web-components",
3
- "version": "1.9.86",
3
+ "version": "1.9.88",
4
4
  "description": "Reusable Salesforce web components library with Tailwind CSS v4 and shadcn/ui",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -429,7 +429,46 @@ if (fs.existsSync(viteConfigTemplatePath) && !fs.existsSync(viteConfigPath)) {
429
429
  console.error(` ✗ Failed to install vite.config.ts: ${error.message}`);
430
430
  }
431
431
  } else if (fs.existsSync(viteConfigPath)) {
432
- console.log(' ℹ vite.config.ts already exists (not overwriting)');
432
+ // Ensure Agent API proxy rules exist even in pre-existing vite.config.ts
433
+ try {
434
+ const existing = fs.readFileSync(viteConfigPath, 'utf-8');
435
+ if (!existing.includes('/sf-oauth') || !existing.includes("api.salesforce.com")) {
436
+ const proxyBlock = `
437
+ // Proxy Agentforce Agent API calls through the dev server to avoid CORS.
438
+ // /sf-oauth/* → Salesforce org (for OAuth client-credentials token)
439
+ // /sf-agent/* → Agent API base (for session + message calls)
440
+ server: {
441
+ proxy: {
442
+ '/sf-oauth': {
443
+ target: 'https://tdx26-keynote-org-1com.my.salesforce.com',
444
+ changeOrigin: true,
445
+ rewrite: (p: string) => p.replace(/^\\/sf-oauth/, ''),
446
+ },
447
+ '/sf-agent': {
448
+ target: 'https://api.salesforce.com',
449
+ changeOrigin: true,
450
+ rewrite: (p: string) => p.replace(/^\\/sf-agent/, ''),
451
+ },
452
+ },
453
+ },
454
+ `;
455
+ // Insert before the build: { section if it exists
456
+ if (existing.includes('build: {') && !existing.includes('server: {')) {
457
+ const updated = existing.replace(
458
+ /(\s*)(\/\/.*Build|build:\s*\{)/,
459
+ proxyBlock + '\n$1$2'
460
+ );
461
+ if (updated !== existing) {
462
+ fs.writeFileSync(viteConfigPath, updated, 'utf-8');
463
+ console.log(' ✓ Added Agent API proxy rules to existing vite.config.ts');
464
+ }
465
+ }
466
+ } else {
467
+ console.log(' ℹ vite.config.ts already has proxy rules');
468
+ }
469
+ } catch {
470
+ // Non-critical — proxy can be added manually
471
+ }
433
472
  }
434
473
 
435
474
  // Copy dataStrategy.ts template
@@ -501,35 +540,48 @@ if (fs.existsSync(packageJsonPath)) {
501
540
 
502
541
  // Copy .a4drules from package to project root (so AI assistants can discover them)
503
542
  const packageA4dRules = path.join(packageRoot, '.a4drules');
504
- const projectRootA4dRules = path.join(cwd, '../../../../../.a4drules'); // Go up from webapp to project root
505
543
 
506
544
  if (fs.existsSync(packageA4dRules)) {
507
545
  console.log('\n📋 Copying AI assistant rules to project root...\n');
508
546
 
509
- // Resolve to absolute path
510
547
  const projectRootPath = path.resolve(cwd, '../../../../../');
511
548
  const targetA4dRules = path.join(projectRootPath, '.a4drules');
512
549
 
513
- // Create .a4drules if it doesn't exist
514
550
  if (!fs.existsSync(targetA4dRules)) {
515
551
  fs.mkdirSync(targetA4dRules, { recursive: true });
516
552
  }
517
553
 
518
- // Copy skills directory
519
- const skillsSource = path.join(packageA4dRules, 'skills');
520
- const skillsTarget = path.join(targetA4dRules, 'skills');
521
- if (fs.existsSync(skillsSource)) {
522
- const skillsCopied = copyDirectoryRecursive(skillsSource, skillsTarget);
523
- console.log(` ✓ Copied ${skillsCopied} skill files`);
554
+ let totalA4dCopied = 0;
555
+
556
+ // Force-copy root-level .md files first
557
+ const rootFiles = fs.readdirSync(packageA4dRules).filter(f => f.endsWith('.md'));
558
+ for (const file of rootFiles) {
559
+ try {
560
+ fs.copyFileSync(path.join(packageA4dRules, file), path.join(targetA4dRules, file));
561
+ totalA4dCopied++;
562
+ } catch (error) {
563
+ console.error(` ✗ Failed to copy ${file}: ${error.message}`);
564
+ }
565
+ }
566
+ if (rootFiles.length > 0) {
567
+ console.log(` ✓ Copied ${rootFiles.length} root rule files`);
524
568
  }
525
569
 
526
- // Copy features directory
527
- const featuresSource = path.join(packageA4dRules, 'features');
528
- const featuresTarget = path.join(targetA4dRules, 'features');
529
- if (fs.existsSync(featuresSource)) {
530
- const featuresCopied = copyDirectoryRecursive(featuresSource, featuresTarget);
531
- console.log(` ✓ Copied ${featuresCopied} feature rule files`);
570
+ // Copy all subdirectories (skills, features, troubleshooting)
571
+ const subdirs = fs.readdirSync(packageA4dRules).filter(d => {
572
+ const full = path.join(packageA4dRules, d);
573
+ return fs.statSync(full).isDirectory();
574
+ });
575
+
576
+ for (const subdir of subdirs) {
577
+ const src = path.join(packageA4dRules, subdir);
578
+ const dst = path.join(targetA4dRules, subdir);
579
+ const copied = copyDirectoryRecursive(src, dst);
580
+ totalA4dCopied += copied;
581
+ console.log(` ✓ Copied ${copied} files from ${subdir}/`);
532
582
  }
583
+
584
+ console.log(` 📋 Total: ${totalA4dCopied} rule files installed`);
533
585
  }
534
586
 
535
587
  // Migrate any dashboards from old location (src/components/pages/) to new location (src/pages/)
@@ -27,6 +27,7 @@ else
27
27
  fi
28
28
 
29
29
  cd "$ROOT"
30
+ ROOT_ABS="$(cd "$ROOT" && pwd)"
30
31
 
31
32
  echo ""
32
33
  echo "╔════════════════════════════════════════════════╗"
@@ -799,15 +800,53 @@ else
799
800
  echo " ⚠ Skipped logo (package logo not found)"
800
801
  fi
801
802
 
803
+ # ── Restore .a4drules to project root ─────────────────────────────────────────
804
+
805
+ echo "→ Restoring AI assistant rules (.a4drules)..."
806
+
807
+ # Detect package .a4drules location
808
+ PACKAGE_A4D=""
809
+ if [ -d "node_modules/@schandlergarcia/sf-web-components/.a4drules" ]; then
810
+ PACKAGE_A4D="node_modules/@schandlergarcia/sf-web-components/.a4drules"
811
+ elif [ -d "$SCRIPT_DIR/../.a4drules" ]; then
812
+ PACKAGE_A4D="$SCRIPT_DIR/../.a4drules"
813
+ fi
814
+
815
+ if [ -n "$PACKAGE_A4D" ]; then
816
+ # Resolve project root (5 levels up from webapp, or 1 level up from package source)
817
+ PROJECT_ROOT=""
818
+ if [[ "$ROOT_ABS" == */force-app/main/default/* ]]; then
819
+ PROJECT_ROOT="${ROOT_ABS%%/force-app/main/default/*}"
820
+ else
821
+ PROJECT_ROOT="$ROOT/.."
822
+ fi
823
+
824
+ if [ -n "$PROJECT_ROOT" ] && [ -d "$PROJECT_ROOT" ]; then
825
+ TARGET_A4D="$PROJECT_ROOT/.a4drules"
826
+ a4d_copied=0
827
+
828
+ # Force-copy every .md file individually (cp -R doesn't always overwrite)
829
+ while IFS= read -r src_file; do
830
+ rel="${src_file#$PACKAGE_A4D/}"
831
+ dst="$TARGET_A4D/$rel"
832
+ mkdir -p "$(dirname "$dst")"
833
+ cp -f "$src_file" "$dst"
834
+ a4d_copied=$((a4d_copied + 1))
835
+ done < <(find "$PACKAGE_A4D" -name "*.md" -type f)
836
+
837
+ echo " ✓ Restored $a4d_copied rule files to $TARGET_A4D"
838
+ fi
839
+ else
840
+ echo " ⚠ Skipped .a4drules (package rules not found)"
841
+ fi
842
+ echo ""
843
+
802
844
  # ── Clean Salesforce metadata created during the demo ────────────────────────
803
845
 
804
846
  SFDX_DEFAULT=""
805
847
 
806
848
  # The webapp lives inside force-app/main/default/uiBundles/..., so extract
807
- # the SFDX default directory from our own path first. Only fall back to
808
- # searching child directories if the pattern doesn't match (standalone app).
809
- ROOT_ABS="$(cd "$ROOT" && pwd)"
810
-
849
+ # the SFDX default directory from our own path first.
811
850
  if [[ "$ROOT_ABS" == */force-app/main/default/* ]]; then
812
851
  SFDX_DEFAULT="${ROOT_ABS%%/force-app/main/default/*}/force-app/main/default"
813
852
  fi
@@ -872,6 +911,7 @@ echo "║ • Agent API config (src/config/agentApi.ts)║"
872
911
  echo "║ • Data strategy (ENABLE_SAMPLE_DATA_CACHE) ║"
873
912
  echo "║ • GraphQL schema + PRD + logo ║"
874
913
  echo "║ • Component library + theme providers ║"
914
+ echo "║ • AI assistant rules (.a4drules) ║"
875
915
  echo "║ ║"
876
916
  echo "║ Cleaned: ║"
877
917
  echo "║ • Apex classes (force-app classes/) ║"