@portel/photon 1.10.0 → 1.12.0

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 (56) hide show
  1. package/README.md +81 -72
  2. package/dist/auto-ui/beam/photon-management.d.ts.map +1 -1
  3. package/dist/auto-ui/beam/photon-management.js +5 -0
  4. package/dist/auto-ui/beam/photon-management.js.map +1 -1
  5. package/dist/auto-ui/beam/routes/api-browse.d.ts +1 -2
  6. package/dist/auto-ui/beam/routes/api-browse.d.ts.map +1 -1
  7. package/dist/auto-ui/beam/routes/api-browse.js +140 -191
  8. package/dist/auto-ui/beam/routes/api-browse.js.map +1 -1
  9. package/dist/auto-ui/beam/routes/api-config.d.ts.map +1 -1
  10. package/dist/auto-ui/beam/routes/api-config.js +44 -1
  11. package/dist/auto-ui/beam/routes/api-config.js.map +1 -1
  12. package/dist/auto-ui/beam.d.ts.map +1 -1
  13. package/dist/auto-ui/beam.js +874 -20
  14. package/dist/auto-ui/beam.js.map +1 -1
  15. package/dist/auto-ui/frontend/index.html +83 -60
  16. package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -1
  17. package/dist/auto-ui/streamable-http-transport.js +16 -2
  18. package/dist/auto-ui/streamable-http-transport.js.map +1 -1
  19. package/dist/auto-ui/types.d.ts +1 -1
  20. package/dist/auto-ui/types.d.ts.map +1 -1
  21. package/dist/auto-ui/types.js.map +1 -1
  22. package/dist/beam.bundle.js +2836 -357
  23. package/dist/beam.bundle.js.map +4 -4
  24. package/dist/cli/commands/package-app.d.ts.map +1 -1
  25. package/dist/cli/commands/package-app.js +116 -35
  26. package/dist/cli/commands/package-app.js.map +1 -1
  27. package/dist/context-store.d.ts +5 -0
  28. package/dist/context-store.d.ts.map +1 -1
  29. package/dist/context-store.js +9 -0
  30. package/dist/context-store.js.map +1 -1
  31. package/dist/daemon/server.js +303 -6
  32. package/dist/daemon/server.js.map +1 -1
  33. package/dist/loader.d.ts +21 -0
  34. package/dist/loader.d.ts.map +1 -1
  35. package/dist/loader.js +277 -0
  36. package/dist/loader.js.map +1 -1
  37. package/dist/photon-cli-runner.d.ts.map +1 -1
  38. package/dist/photon-cli-runner.js +21 -1
  39. package/dist/photon-cli-runner.js.map +1 -1
  40. package/dist/photon-doc-extractor.d.ts +6 -0
  41. package/dist/photon-doc-extractor.d.ts.map +1 -1
  42. package/dist/photon-doc-extractor.js +22 -0
  43. package/dist/photon-doc-extractor.js.map +1 -1
  44. package/dist/photons/tunnel.photon.d.ts +5 -9
  45. package/dist/photons/tunnel.photon.d.ts.map +1 -1
  46. package/dist/photons/tunnel.photon.js +36 -96
  47. package/dist/photons/tunnel.photon.js.map +1 -1
  48. package/dist/photons/tunnel.photon.ts +40 -112
  49. package/dist/server.d.ts.map +1 -1
  50. package/dist/server.js +27 -2
  51. package/dist/server.js.map +1 -1
  52. package/dist/test-runner.d.ts +13 -1
  53. package/dist/test-runner.d.ts.map +1 -1
  54. package/dist/test-runner.js +529 -122
  55. package/dist/test-runner.js.map +1 -1
  56. package/package.json +22 -6
@@ -15357,10 +15357,14 @@ var theme = i`
15357
15357
  bottom: 0;
15358
15358
  background-color: var(--bg-glass);
15359
15359
  border: 1px solid var(--border-glass);
15360
- transition: 0.4s;
15360
+ transition: 0.2s;
15361
15361
  border-radius: var(--radius-full);
15362
15362
  }
15363
15363
 
15364
+ .slider:hover {
15365
+ border-color: var(--accent-secondary);
15366
+ }
15367
+
15364
15368
  .slider:before {
15365
15369
  position: absolute;
15366
15370
  content: '';
@@ -15369,7 +15373,7 @@ var theme = i`
15369
15373
  left: 2px;
15370
15374
  bottom: 2px;
15371
15375
  background-color: var(--t-muted);
15372
- transition: 0.4s;
15376
+ transition: 0.2s;
15373
15377
  border-radius: 50%;
15374
15378
  }
15375
15379
 
@@ -15544,7 +15548,7 @@ ToastManager.styles = [
15544
15548
  pointer-events: auto;
15545
15549
  animation: slideIn 0.3s ease-out;
15546
15550
  max-width: 350px;
15547
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
15551
+ box-shadow: var(--shadow-lg);
15548
15552
  }
15549
15553
 
15550
15554
  .toast.exiting {
@@ -15622,7 +15626,7 @@ ToastManager.styles = [
15622
15626
 
15623
15627
  .close:hover {
15624
15628
  color: var(--t-primary);
15625
- background: hsla(220, 10%, 80%, 0.1);
15629
+ background: var(--bg-glass);
15626
15630
  }
15627
15631
 
15628
15632
  .toast-action {
@@ -15822,45 +15826,10 @@ var iconPaths = {
15822
15826
  docs: `<path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/>`,
15823
15827
  expand: `<path d="M15 3h6v6"/><path d="m21 3-7 7"/><path d="m3 21 7-7"/><path d="M9 21H3v-6"/>`,
15824
15828
  plus: `<path d="M5 12h14"/><path d="M12 5v14"/>`,
15825
- clone: `<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>`
15829
+ clone: `<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>`,
15830
+ installApp: `<path d="M12 3v12"/><path d="m17 11-5 5-5-5"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>`
15826
15831
  };
15827
15832
 
15828
- // src/auto-ui/frontend/utils/format-label.ts
15829
- function formatLabel(name2) {
15830
- if (!name2) return name2;
15831
- let cleaned = name2.replace(/^_+/, "");
15832
- let spaced = cleaned.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2");
15833
- spaced = spaced.replace(/[_-]+/g, " ");
15834
- let result = spaced.split(/\s+/).map((word) => {
15835
- if (!word) return "";
15836
- const upper = word.toUpperCase();
15837
- if ([
15838
- "AI",
15839
- "ID",
15840
- "IDS",
15841
- "URL",
15842
- "API",
15843
- "UI",
15844
- "IP",
15845
- "HTTP",
15846
- "HTTPS",
15847
- "JSON",
15848
- "XML",
15849
- "HTML",
15850
- "CSS",
15851
- "SQL",
15852
- "MCP",
15853
- "SSH",
15854
- "CLI"
15855
- ].includes(upper)) {
15856
- if (upper === "IDS") return "IDs";
15857
- return upper;
15858
- }
15859
- return word.charAt(0).toUpperCase() + word.slice(1);
15860
- }).join(" ");
15861
- return result;
15862
- }
15863
-
15864
15833
  // node_modules/@portel/photon-core/dist/design-system/oklch.js
15865
15834
  function oklchToSRGB(L3, C3, H3) {
15866
15835
  const hRad = H3 * Math.PI / 180;
@@ -16545,7 +16514,8 @@ var buttons = i`
16545
16514
  color: var(--t-primary);
16546
16515
  }
16547
16516
 
16548
- .btn-primary:disabled {
16517
+ .btn-primary:disabled,
16518
+ .btn-secondary:disabled {
16549
16519
  opacity: 0.6;
16550
16520
  cursor: not-allowed;
16551
16521
  transform: none;
@@ -16585,7 +16555,21 @@ var forms = i`
16585
16555
  padding: var(--space-sm);
16586
16556
  border-radius: var(--radius-sm);
16587
16557
  font-family: var(--font-sans);
16558
+ font-size: var(--text-md);
16588
16559
  box-sizing: border-box;
16560
+ transition:
16561
+ border-color 0.15s ease,
16562
+ box-shadow 0.15s ease;
16563
+ }
16564
+
16565
+ input:hover,
16566
+ textarea:hover,
16567
+ select:hover {
16568
+ border-color: var(--accent-secondary);
16569
+ }
16570
+
16571
+ textarea {
16572
+ resize: vertical;
16589
16573
  }
16590
16574
 
16591
16575
  ::placeholder {
@@ -16601,12 +16585,12 @@ var forms = i`
16601
16585
  display: block;
16602
16586
  margin-bottom: var(--space-xs);
16603
16587
  font-weight: 500;
16604
- font-size: 0.9rem;
16588
+ font-size: var(--text-md);
16605
16589
  }
16606
16590
 
16607
16591
  .error-text {
16608
16592
  color: var(--color-error);
16609
- font-size: 0.75rem;
16593
+ font-size: var(--text-xs);
16610
16594
  margin-top: var(--space-xs);
16611
16595
  }
16612
16596
 
@@ -16626,7 +16610,7 @@ var forms = i`
16626
16610
  var badges = i`
16627
16611
  .type-badge {
16628
16612
  display: inline-block;
16629
- font-size: 0.65rem;
16613
+ font-size: var(--text-2xs);
16630
16614
  font-weight: 600;
16631
16615
  text-transform: uppercase;
16632
16616
  letter-spacing: 0.04em;
@@ -16660,7 +16644,7 @@ var badges = i`
16660
16644
  }
16661
16645
 
16662
16646
  .param-tag {
16663
- font-size: 0.65rem;
16647
+ font-size: var(--text-2xs);
16664
16648
  padding: 1px 6px;
16665
16649
  border-radius: var(--radius-xs);
16666
16650
  background: var(--param-tag-bg, hsla(220, 10%, 80%, 0.08));
@@ -17236,6 +17220,307 @@ function loadSavedThemeConfig() {
17236
17220
  return null;
17237
17221
  }
17238
17222
 
17223
+ // src/auto-ui/frontend/services/photon-instance-manager.ts
17224
+ var SimpleEventEmitter = class {
17225
+ constructor() {
17226
+ this._listeners = /* @__PURE__ */ new Map();
17227
+ }
17228
+ on(event, callback2) {
17229
+ if (!this._listeners.has(event)) {
17230
+ this._listeners.set(event, /* @__PURE__ */ new Set());
17231
+ }
17232
+ this._listeners.get(event).add(callback2);
17233
+ }
17234
+ off(event, callback2) {
17235
+ const listeners = this._listeners.get(event);
17236
+ if (listeners) {
17237
+ listeners.delete(callback2);
17238
+ }
17239
+ }
17240
+ emit(event, ...args) {
17241
+ const listeners = this._listeners.get(event);
17242
+ if (listeners) {
17243
+ for (const callback2 of listeners) {
17244
+ callback2(...args);
17245
+ }
17246
+ }
17247
+ }
17248
+ };
17249
+ var PhotonSessionProxy = class extends SimpleEventEmitter {
17250
+ constructor(options) {
17251
+ super();
17252
+ this._paginationState = /* @__PURE__ */ new Map();
17253
+ this._pendingPatches = [];
17254
+ this._isApplyingPatches = false;
17255
+ this._name = options.name;
17256
+ this._state = { ...options.initialState };
17257
+ }
17258
+ /**
17259
+ * Get the photon name
17260
+ */
17261
+ get name() {
17262
+ return this._name;
17263
+ }
17264
+ /**
17265
+ * Get current state
17266
+ */
17267
+ get state() {
17268
+ return this._state;
17269
+ }
17270
+ /**
17271
+ * Apply JSON Patch array to instance state
17272
+ * Patches from: https://tools.ietf.org/html/rfc6902
17273
+ */
17274
+ applyPatches(patches) {
17275
+ if (!Array.isArray(patches) || patches.length === 0) {
17276
+ return;
17277
+ }
17278
+ this._pendingPatches.push(...patches);
17279
+ if (!this._isApplyingPatches) {
17280
+ this._isApplyingPatches = true;
17281
+ queueMicrotask(() => this._processPendingPatches());
17282
+ }
17283
+ }
17284
+ /**
17285
+ * Update pagination state for a property (e.g., 'items')
17286
+ */
17287
+ setPaginationState(property2, state) {
17288
+ const current = this._paginationState.get(property2) || {
17289
+ pageSize: 20,
17290
+ currentPage: 0,
17291
+ hasMore: true,
17292
+ isLoading: false
17293
+ };
17294
+ this._paginationState.set(property2, { ...current, ...state });
17295
+ }
17296
+ /**
17297
+ * Get pagination state for a property
17298
+ */
17299
+ getPaginationState(property2) {
17300
+ return this._paginationState.get(property2) || {
17301
+ pageSize: 20,
17302
+ currentPage: 0,
17303
+ hasMore: true,
17304
+ isLoading: false
17305
+ };
17306
+ }
17307
+ /**
17308
+ * Create a getter/setter for property access
17309
+ */
17310
+ _createPropertyDescriptor(key) {
17311
+ return {
17312
+ configurable: true,
17313
+ enumerable: true,
17314
+ get: () => this._state[key],
17315
+ set: (value) => {
17316
+ this._state[key] = value;
17317
+ this.emit("propertyChanged", { property: key, value });
17318
+ }
17319
+ };
17320
+ }
17321
+ /**
17322
+ * Make a property accessible on the proxy
17323
+ */
17324
+ makeProperty(key) {
17325
+ Object.defineProperty(this, key, this._createPropertyDescriptor(key));
17326
+ }
17327
+ /**
17328
+ * Get all top-level properties (useful for initialization)
17329
+ */
17330
+ getProperties() {
17331
+ return Object.keys(this._state);
17332
+ }
17333
+ /**
17334
+ * Process pending patches
17335
+ */
17336
+ _processPendingPatches() {
17337
+ const patches = this._pendingPatches.splice(0);
17338
+ try {
17339
+ for (const patch of patches) {
17340
+ this._applyPatch(patch);
17341
+ }
17342
+ this.emit("state-changed", patches);
17343
+ } finally {
17344
+ this._isApplyingPatches = false;
17345
+ if (this._pendingPatches.length > 0) {
17346
+ queueMicrotask(() => this._processPendingPatches());
17347
+ }
17348
+ }
17349
+ }
17350
+ /**
17351
+ * Apply a single JSON Patch operation
17352
+ */
17353
+ _applyPatch(patch) {
17354
+ const { op, path, value } = patch;
17355
+ if (!path || typeof path !== "string") {
17356
+ console.warn("Invalid patch: missing or invalid path", patch);
17357
+ return;
17358
+ }
17359
+ const parts = path.split("/").filter((p5) => p5 !== "");
17360
+ let current = this._state;
17361
+ try {
17362
+ for (let i7 = 0; i7 < parts.length - 1; i7++) {
17363
+ const part = this._unescapePath(parts[i7]);
17364
+ if (!(part in current)) {
17365
+ current[part] = {};
17366
+ }
17367
+ current = current[part];
17368
+ }
17369
+ const lastPart = this._unescapePath(parts[parts.length - 1]);
17370
+ switch (op) {
17371
+ case "add":
17372
+ if (Array.isArray(current) && !isNaN(Number(lastPart))) {
17373
+ const index2 = Number(lastPart);
17374
+ current.splice(index2, 0, value);
17375
+ } else {
17376
+ current[lastPart] = value;
17377
+ }
17378
+ break;
17379
+ case "remove":
17380
+ if (Array.isArray(current) && !isNaN(Number(lastPart))) {
17381
+ const index2 = Number(lastPart);
17382
+ current.splice(index2, 1);
17383
+ } else {
17384
+ delete current[lastPart];
17385
+ }
17386
+ break;
17387
+ case "replace":
17388
+ current[lastPart] = value;
17389
+ break;
17390
+ case "move":
17391
+ const fromParts = patch.from.split("/").filter((p5) => p5 !== "");
17392
+ let fromCurrent = this._state;
17393
+ for (let i7 = 0; i7 < fromParts.length - 1; i7++) {
17394
+ fromCurrent = fromCurrent[this._unescapePath(fromParts[i7])];
17395
+ }
17396
+ const fromLast = this._unescapePath(fromParts[fromParts.length - 1]);
17397
+ const movedValue = fromCurrent[fromLast];
17398
+ if (Array.isArray(fromCurrent)) {
17399
+ fromCurrent.splice(Number(fromLast), 1);
17400
+ } else {
17401
+ delete fromCurrent[fromLast];
17402
+ }
17403
+ if (Array.isArray(current)) {
17404
+ current.splice(Number(lastPart), 0, movedValue);
17405
+ } else {
17406
+ current[lastPart] = movedValue;
17407
+ }
17408
+ break;
17409
+ case "copy":
17410
+ const copyFromParts = patch.from.split("/").filter((p5) => p5 !== "");
17411
+ let copyCurrent = this._state;
17412
+ for (let i7 = 0; i7 < copyFromParts.length - 1; i7++) {
17413
+ copyCurrent = copyCurrent[this._unescapePath(copyFromParts[i7])];
17414
+ }
17415
+ const copyFromLast = this._unescapePath(copyFromParts[copyFromParts.length - 1]);
17416
+ const copiedValue = JSON.parse(JSON.stringify(copyCurrent[copyFromLast]));
17417
+ current[lastPart] = copiedValue;
17418
+ break;
17419
+ case "test":
17420
+ if (current[lastPart] !== value) {
17421
+ throw new Error(`Test failed at ${path}: expected ${value}, got ${current[lastPart]}`);
17422
+ }
17423
+ break;
17424
+ default:
17425
+ console.warn(`Unknown patch operation: ${op}`);
17426
+ }
17427
+ } catch (error2) {
17428
+ console.error("Failed to apply patch", { patch, error: error2 });
17429
+ throw error2;
17430
+ }
17431
+ }
17432
+ /**
17433
+ * Unescape JSON Pointer path component
17434
+ * See: https://tools.ietf.org/html/rfc6901
17435
+ */
17436
+ _unescapePath(part) {
17437
+ return part.replace(/~1/g, "/").replace(/~0/g, "~");
17438
+ }
17439
+ /**
17440
+ * Clear all state
17441
+ */
17442
+ reset(newState) {
17443
+ this._state = { ...newState };
17444
+ this._paginationState.clear();
17445
+ this.emit("reset");
17446
+ }
17447
+ };
17448
+ var GlobalSessionManager = class {
17449
+ constructor() {
17450
+ this._sessions = /* @__PURE__ */ new Map();
17451
+ }
17452
+ /**
17453
+ * Create or get a photon session
17454
+ */
17455
+ createOrGetSession(name2, initialState) {
17456
+ if (this._sessions.has(name2)) {
17457
+ return this._sessions.get(name2);
17458
+ }
17459
+ const session = new PhotonSessionProxy({
17460
+ name: name2,
17461
+ initialState
17462
+ });
17463
+ Object.keys(initialState).forEach((key) => {
17464
+ session.makeProperty(key);
17465
+ });
17466
+ this._sessions.set(name2, session);
17467
+ return session;
17468
+ }
17469
+ /**
17470
+ * Get existing session
17471
+ */
17472
+ getSession(name2) {
17473
+ return this._sessions.get(name2);
17474
+ }
17475
+ /**
17476
+ * Apply patches to a session
17477
+ */
17478
+ applyPatches(name2, patches) {
17479
+ const session = this._sessions.get(name2);
17480
+ if (!session) {
17481
+ console.warn(`No session found for: ${name2}`);
17482
+ return false;
17483
+ }
17484
+ session.applyPatches(patches);
17485
+ return true;
17486
+ }
17487
+ /**
17488
+ * Remove session
17489
+ */
17490
+ removeSession(name2) {
17491
+ this._sessions.delete(name2);
17492
+ }
17493
+ /**
17494
+ * Get all session names
17495
+ */
17496
+ getSessionNames() {
17497
+ return Array.from(this._sessions.keys());
17498
+ }
17499
+ };
17500
+ var globalSessionManager = null;
17501
+ function getGlobalSessionManager() {
17502
+ if (!globalSessionManager) {
17503
+ globalSessionManager = new GlobalSessionManager();
17504
+ if (typeof window !== "undefined") {
17505
+ window.__photonSessionManager = globalSessionManager;
17506
+ }
17507
+ }
17508
+ return globalSessionManager;
17509
+ }
17510
+ function initializeGlobalPhotonSession(photonName, initialState) {
17511
+ const manager = getGlobalSessionManager();
17512
+ const session = manager.createOrGetSession(photonName, initialState);
17513
+ if (typeof window !== "undefined") {
17514
+ const varName = photonName.charAt(0).toLocaleLowerCase() + photonName.slice(1);
17515
+ window[varName] = session;
17516
+ if (photonName === "Boards") {
17517
+ window.boards = session;
17518
+ window.boardsSession = session;
17519
+ }
17520
+ }
17521
+ return session;
17522
+ }
17523
+
17239
17524
  // src/auto-ui/frontend/services/mcp-client.ts
17240
17525
  var MCPClientService = class {
17241
17526
  // Discard operations older than 30s
@@ -17940,9 +18225,21 @@ var MCPClientService = class {
17940
18225
  case "photon/refresh-needed":
17941
18226
  this.emit("refresh-needed", notification.params);
17942
18227
  break;
17943
- case "photon/state-changed":
18228
+ case "state-changed":
18229
+ const params = notification.params;
18230
+ if (params?.instance && params?.patches) {
18231
+ try {
18232
+ const manager = getGlobalSessionManager();
18233
+ manager.applyPatches(params.instance, params.patches);
18234
+ } catch (error2) {
18235
+ console.error("Failed to apply patches to session", error2);
18236
+ }
18237
+ }
17944
18238
  this.emit("state-changed", notification.params);
17945
18239
  break;
18240
+ case "photon/notification":
18241
+ this.emit("photon-notification", notification.params);
18242
+ break;
17946
18243
  // MCP Apps standard notifications
17947
18244
  case "ui/notifications/tool-result":
17948
18245
  this.emit("ui-tool-result", notification.params);
@@ -17967,6 +18264,439 @@ var MCPClientService = class {
17967
18264
  };
17968
18265
  var mcpClient = new MCPClientService();
17969
18266
 
18267
+ // src/auto-ui/frontend/services/viewport-aware-proxy.ts
18268
+ var ViewportAwareProxy = class {
18269
+ constructor(photonName, methodName, mcpClient2, options = {}) {
18270
+ // Cache management
18271
+ this._cache = /* @__PURE__ */ new Map();
18272
+ this._viewport = { start: 0, end: 20 };
18273
+ this._pendingRanges = /* @__PURE__ */ new Set();
18274
+ this._pagination = {
18275
+ totalCount: 0,
18276
+ start: 0,
18277
+ end: 0,
18278
+ hasMore: false
18279
+ };
18280
+ // Event listeners
18281
+ this._listeners = /* @__PURE__ */ new Map();
18282
+ this._photonName = photonName;
18283
+ this._methodName = methodName;
18284
+ this._mcpClient = mcpClient2;
18285
+ this._fetchOptions = {
18286
+ pageSize: options.pageSize ?? 20,
18287
+ bufferSize: options.bufferSize ?? 5,
18288
+ maxCacheSize: options.maxCacheSize ?? 1e3
18289
+ };
18290
+ }
18291
+ /**
18292
+ * Initialize with paginated response from server
18293
+ * Server should return: { items: [...], _pagination: {...} }
18294
+ */
18295
+ initializeWithResponse(response) {
18296
+ if (!response || typeof response !== "object") {
18297
+ return;
18298
+ }
18299
+ const { items = [], _pagination } = response;
18300
+ if (_pagination) {
18301
+ this._pagination = _pagination;
18302
+ items.forEach((item, offset) => {
18303
+ this._cache.set(_pagination.start + offset, item);
18304
+ });
18305
+ this._emit("initialized", { pagination: this._pagination });
18306
+ }
18307
+ }
18308
+ /**
18309
+ * Set visible viewport (what user sees on screen)
18310
+ * Automatically fetches data for this range + buffer
18311
+ */
18312
+ async setViewport(start, end) {
18313
+ this._viewport = { start, end };
18314
+ const bufferedStart = Math.max(0, start - this._fetchOptions.bufferSize);
18315
+ const bufferedEnd = Math.min(this._pagination.totalCount, end + this._fetchOptions.bufferSize);
18316
+ const missingRanges = this._findMissingRanges(bufferedStart, bufferedEnd);
18317
+ await Promise.all(
18318
+ missingRanges.map(
18319
+ (range) => this._fetchRange(range.start, range.end).catch((err) => {
18320
+ console.error(`Failed to fetch range [${range.start}, ${range.end}]`, err);
18321
+ })
18322
+ )
18323
+ );
18324
+ }
18325
+ /**
18326
+ * Get items in current viewport (array-like interface)
18327
+ */
18328
+ get items() {
18329
+ const items = [];
18330
+ for (let i7 = this._viewport.start; i7 < this._viewport.end; i7++) {
18331
+ if (this._cache.has(i7)) {
18332
+ items.push(this._cache.get(i7));
18333
+ }
18334
+ }
18335
+ return items;
18336
+ }
18337
+ /**
18338
+ * Get item by index
18339
+ */
18340
+ getItem(index2) {
18341
+ return this._cache.get(index2);
18342
+ }
18343
+ /**
18344
+ * Get total count of items (if available)
18345
+ */
18346
+ get totalCount() {
18347
+ return this._pagination.totalCount;
18348
+ }
18349
+ /**
18350
+ * Get current pagination state
18351
+ */
18352
+ get pagination() {
18353
+ return { ...this._pagination };
18354
+ }
18355
+ /**
18356
+ * Get current viewport
18357
+ */
18358
+ get viewport() {
18359
+ return { ...this._viewport };
18360
+ }
18361
+ /**
18362
+ * Get cache size (for debugging)
18363
+ */
18364
+ get cacheSize() {
18365
+ return this._cache.size;
18366
+ }
18367
+ /**
18368
+ * Check if a range is cached
18369
+ */
18370
+ isCached(start, end) {
18371
+ for (let i7 = start; i7 < end; i7++) {
18372
+ if (!this._cache.has(i7)) {
18373
+ return false;
18374
+ }
18375
+ }
18376
+ return true;
18377
+ }
18378
+ /**
18379
+ * Subscribe to events
18380
+ */
18381
+ on(event, callback2) {
18382
+ if (!this._listeners.has(event)) {
18383
+ this._listeners.set(event, /* @__PURE__ */ new Set());
18384
+ }
18385
+ this._listeners.get(event).add(callback2);
18386
+ }
18387
+ /**
18388
+ * Unsubscribe from events
18389
+ */
18390
+ off(event, callback2) {
18391
+ const listeners = this._listeners.get(event);
18392
+ if (listeners) {
18393
+ listeners.delete(callback2);
18394
+ }
18395
+ }
18396
+ /**
18397
+ * Clear cache
18398
+ */
18399
+ clearCache() {
18400
+ this._cache.clear();
18401
+ this._emit("cache-cleared");
18402
+ }
18403
+ /**
18404
+ * Apply patches from state-changed events
18405
+ * Updates cache with new/modified items
18406
+ */
18407
+ applyPatches(patches) {
18408
+ for (const patch of patches) {
18409
+ this._applyPatch(patch);
18410
+ }
18411
+ this._emit("patched", { patches });
18412
+ }
18413
+ /**
18414
+ * Find which ranges need to be fetched
18415
+ */
18416
+ _findMissingRanges(start, end) {
18417
+ const missing = [];
18418
+ for (let i7 = start; i7 < end; i7++) {
18419
+ if (!this._cache.has(i7)) {
18420
+ missing.push(i7);
18421
+ }
18422
+ }
18423
+ const ranges = [];
18424
+ if (missing.length === 0) return ranges;
18425
+ let rangeStart = missing[0];
18426
+ let rangeEnd2 = missing[0];
18427
+ for (let i7 = 1; i7 < missing.length; i7++) {
18428
+ if (missing[i7] === rangeEnd2 + 1) {
18429
+ rangeEnd2 = missing[i7];
18430
+ } else {
18431
+ ranges.push({ start: rangeStart, end: rangeEnd2 + 1 });
18432
+ rangeStart = missing[i7];
18433
+ rangeEnd2 = missing[i7];
18434
+ }
18435
+ }
18436
+ ranges.push({ start: rangeStart, end: rangeEnd2 + 1 });
18437
+ return ranges;
18438
+ }
18439
+ /**
18440
+ * Fetch a range of items via MCP
18441
+ */
18442
+ async _fetchRange(start, end) {
18443
+ const rangeKey = `${start}-${end}`;
18444
+ if (this._pendingRanges.has(rangeKey)) {
18445
+ return;
18446
+ }
18447
+ this._pendingRanges.add(rangeKey);
18448
+ try {
18449
+ const toolName = `${this._photonName}/${this._methodName}`;
18450
+ const result = await this._mcpClient.callTool(toolName, {
18451
+ start,
18452
+ limit: Math.min(end - start, this._fetchOptions.pageSize)
18453
+ });
18454
+ if (result.isError) {
18455
+ console.error(`Failed to fetch ${toolName}[${start}:${end}]`, result);
18456
+ return;
18457
+ }
18458
+ const parsed = this._mcpClient.parseToolResult(result);
18459
+ if (parsed && typeof parsed === "object") {
18460
+ const { items = [], _pagination } = parsed;
18461
+ if (_pagination) {
18462
+ this._pagination = _pagination;
18463
+ }
18464
+ items.forEach((item, offset) => {
18465
+ this._cache.set(start + offset, item);
18466
+ });
18467
+ if (this._cache.size > this._fetchOptions.maxCacheSize) {
18468
+ this._pruneCache();
18469
+ }
18470
+ this._emit("fetched", { start, end, itemCount: items.length });
18471
+ }
18472
+ } finally {
18473
+ this._pendingRanges.delete(rangeKey);
18474
+ }
18475
+ }
18476
+ /**
18477
+ * Apply a single JSON Patch to cache
18478
+ */
18479
+ _applyPatch(patch) {
18480
+ const { op, path } = patch;
18481
+ const match = path.match(/\/items\/(\d+)/);
18482
+ if (!match) return;
18483
+ const index2 = parseInt(match[1], 10);
18484
+ switch (op) {
18485
+ case "add": {
18486
+ const newCache = /* @__PURE__ */ new Map();
18487
+ for (const [key, value] of this._cache) {
18488
+ if (key >= index2) {
18489
+ newCache.set(key + 1, value);
18490
+ } else {
18491
+ newCache.set(key, value);
18492
+ }
18493
+ }
18494
+ newCache.set(index2, patch.value);
18495
+ this._cache = newCache;
18496
+ this._pagination.totalCount++;
18497
+ break;
18498
+ }
18499
+ case "remove": {
18500
+ const newCache = /* @__PURE__ */ new Map();
18501
+ for (const [key, value] of this._cache) {
18502
+ if (key > index2) {
18503
+ newCache.set(key - 1, value);
18504
+ } else if (key < index2) {
18505
+ newCache.set(key, value);
18506
+ }
18507
+ }
18508
+ this._cache = newCache;
18509
+ this._pagination.totalCount--;
18510
+ break;
18511
+ }
18512
+ case "replace": {
18513
+ this._cache.set(index2, patch.value);
18514
+ break;
18515
+ }
18516
+ }
18517
+ }
18518
+ /**
18519
+ * Remove least-recently-used items from cache when it gets too large
18520
+ */
18521
+ _pruneCache() {
18522
+ const toRemove = this._cache.size - this._fetchOptions.maxCacheSize;
18523
+ if (toRemove <= 0) return;
18524
+ const viewportCenter = this._viewport.start + (this._viewport.end - this._viewport.start) / 2;
18525
+ const sortedKeys = Array.from(this._cache.keys()).sort(
18526
+ (a5, b3) => Math.abs(a5 - viewportCenter) - Math.abs(b3 - viewportCenter)
18527
+ );
18528
+ for (let i7 = 0; i7 < toRemove && i7 < sortedKeys.length; i7++) {
18529
+ this._cache.delete(sortedKeys[i7]);
18530
+ }
18531
+ }
18532
+ /**
18533
+ * Emit event to listeners
18534
+ */
18535
+ _emit(event, data) {
18536
+ const listeners = this._listeners.get(event);
18537
+ if (listeners) {
18538
+ for (const callback2 of listeners) {
18539
+ try {
18540
+ callback2(data);
18541
+ } catch (err) {
18542
+ console.error(`Error in ${event} listener:`, err);
18543
+ }
18544
+ }
18545
+ }
18546
+ }
18547
+ };
18548
+
18549
+ // src/auto-ui/frontend/services/viewport-manager.ts
18550
+ function getPageSizeForClient() {
18551
+ if (typeof window !== "undefined" && window.innerWidth < 600) {
18552
+ return 10;
18553
+ }
18554
+ if (typeof window !== "undefined" && window.innerWidth < 1024) {
18555
+ return 25;
18556
+ }
18557
+ return 50;
18558
+ }
18559
+ var ViewportManager = class {
18560
+ constructor(config3) {
18561
+ this.observer = null;
18562
+ this.visibleSentinels = /* @__PURE__ */ new Set();
18563
+ this.lastVisibleRange = { start: 0, end: 0 };
18564
+ this.lastScrollDirection = "none";
18565
+ this.changeCallbacks = [];
18566
+ this.element = config3.element;
18567
+ this.pageSize = config3.pageSize;
18568
+ this.paddingAbove = config3.paddingAbove ?? this.pageSize;
18569
+ this.paddingBelow = config3.paddingBelow ?? this.pageSize * 2;
18570
+ this.debug = config3.debug ?? false;
18571
+ this.log("ViewportManager initialized", {
18572
+ pageSize: this.pageSize,
18573
+ paddingAbove: this.paddingAbove,
18574
+ paddingBelow: this.paddingBelow
18575
+ });
18576
+ this.initializeObserver();
18577
+ }
18578
+ initializeObserver() {
18579
+ const options = {
18580
+ root: null,
18581
+ rootMargin: "100px",
18582
+ threshold: 0
18583
+ };
18584
+ this.observer = new IntersectionObserver((entries) => {
18585
+ for (const entry of entries) {
18586
+ const index2 = this.getSentinelIndex(entry.target);
18587
+ if (index2 !== null) {
18588
+ if (entry.isIntersecting) {
18589
+ this.visibleSentinels.add(index2);
18590
+ } else {
18591
+ this.visibleSentinels.delete(index2);
18592
+ }
18593
+ }
18594
+ }
18595
+ this.updateVisibleRange();
18596
+ }, options);
18597
+ }
18598
+ createSentinel(index2) {
18599
+ const sentinel = document.createElement("div");
18600
+ sentinel.setAttribute("data-viewport-sentinel", String(index2));
18601
+ sentinel.style.height = "0px";
18602
+ sentinel.style.overflow = "hidden";
18603
+ return sentinel;
18604
+ }
18605
+ observeSentinel(sentinel) {
18606
+ if (this.observer) {
18607
+ this.observer.observe(sentinel);
18608
+ }
18609
+ }
18610
+ unobserveSentinel(sentinel) {
18611
+ if (this.observer) {
18612
+ this.observer.unobserve(sentinel);
18613
+ }
18614
+ }
18615
+ getSentinelIndex(element) {
18616
+ const attr = element.getAttribute("data-viewport-sentinel");
18617
+ return attr !== null ? parseInt(attr, 10) : null;
18618
+ }
18619
+ updateVisibleRange() {
18620
+ if (this.visibleSentinels.size === 0) {
18621
+ return;
18622
+ }
18623
+ const indices = Array.from(this.visibleSentinels).sort((a5, b3) => a5 - b3);
18624
+ const visibleStart = indices[0];
18625
+ const visibleEnd = indices[indices.length - 1] + 1;
18626
+ const direction = this.determineScrollDirection(visibleStart);
18627
+ const bufferStart = Math.max(
18628
+ 0,
18629
+ visibleStart - Math.ceil(this.paddingAbove / this.pageSize) * this.pageSize
18630
+ );
18631
+ const bufferEnd = visibleEnd + Math.ceil(this.paddingBelow / this.pageSize) * this.pageSize;
18632
+ const rangeChanged = visibleStart !== this.lastVisibleRange.start || visibleEnd !== this.lastVisibleRange.end;
18633
+ if (rangeChanged) {
18634
+ this.lastVisibleRange = { start: visibleStart, end: visibleEnd };
18635
+ this.lastScrollDirection = direction;
18636
+ const event = {
18637
+ visibleRange: { start: visibleStart, end: visibleEnd },
18638
+ bufferRange: { start: bufferStart, end: bufferEnd },
18639
+ scrollDirection: direction,
18640
+ timestamp: Date.now()
18641
+ };
18642
+ this.log("Viewport changed", event);
18643
+ for (const callback2 of this.changeCallbacks) {
18644
+ callback2(event);
18645
+ }
18646
+ }
18647
+ }
18648
+ determineScrollDirection(newStart) {
18649
+ if (newStart < this.lastVisibleRange.start) {
18650
+ return "up";
18651
+ } else if (newStart > this.lastVisibleRange.start) {
18652
+ return "down";
18653
+ }
18654
+ return "none";
18655
+ }
18656
+ getVisibleRange() {
18657
+ return this.lastVisibleRange;
18658
+ }
18659
+ getBufferRange(totalItems) {
18660
+ const { start, end } = this.lastVisibleRange;
18661
+ const bufferStart = Math.max(0, start - this.paddingAbove);
18662
+ const bufferEnd = Math.min(totalItems, end + this.paddingBelow);
18663
+ return { start: bufferStart, end: bufferEnd };
18664
+ }
18665
+ getScrollDirection() {
18666
+ return this.lastScrollDirection;
18667
+ }
18668
+ getPageSize() {
18669
+ return this.pageSize;
18670
+ }
18671
+ onChange(callback2) {
18672
+ this.changeCallbacks.push(callback2);
18673
+ }
18674
+ offChange(callback2) {
18675
+ this.changeCallbacks = this.changeCallbacks.filter((cb) => cb !== callback2);
18676
+ }
18677
+ setPageSize(newPageSize) {
18678
+ if (newPageSize !== this.pageSize) {
18679
+ this.pageSize = newPageSize;
18680
+ this.log("Page size updated", { newPageSize });
18681
+ this.updateVisibleRange();
18682
+ }
18683
+ }
18684
+ destroy() {
18685
+ if (this.observer) {
18686
+ this.observer.disconnect();
18687
+ this.observer = null;
18688
+ }
18689
+ this.changeCallbacks = [];
18690
+ this.visibleSentinels.clear();
18691
+ this.log("ViewportManager destroyed");
18692
+ }
18693
+ log(message, data) {
18694
+ if (this.debug) {
18695
+ console.log(`[ViewportManager] ${message}`, data);
18696
+ }
18697
+ }
18698
+ };
18699
+
17970
18700
  // src/auto-ui/frontend/components/beam-app.ts
17971
18701
  var THEME_STORAGE_KEY = "beam-theme";
17972
18702
  var PROTOCOL_STORAGE_KEY = "beam-protocol";
@@ -18030,6 +18760,10 @@ var BeamApp = class extends i4 {
18030
18760
  this._promptArguments = {};
18031
18761
  this._renderedPrompt = "";
18032
18762
  this._resourceContent = "";
18763
+ this._splitPanels = [];
18764
+ this._methodPickerOpen = false;
18765
+ this._methodPickerPanelId = null;
18766
+ this._nextPanelId = 0;
18033
18767
  // Collection auto-subscription for ReactiveArray/Map/Set events
18034
18768
  this._collectionUnsubscribes = [];
18035
18769
  this._currentCollectionName = null;
@@ -18040,6 +18774,10 @@ var BeamApp = class extends i4 {
18040
18774
  this._fileIdCounter = 0;
18041
18775
  // Deep link URL for setOpenInAppUrl
18042
18776
  this._openInAppUrl = null;
18777
+ // PWA install state
18778
+ this._pwaInstallPrompt = null;
18779
+ this._pwaIsStandalone = false;
18780
+ this._pwaCurrentPhoton = null;
18043
18781
  this._handleDocumentClick = (e8) => {
18044
18782
  const path = e8.composedPath();
18045
18783
  if (this._showSettingsMenu) {
@@ -18057,11 +18795,11 @@ var BeamApp = class extends i4 {
18057
18795
  }
18058
18796
  };
18059
18797
  this._initialConnectDone = false;
18060
- this._handleHashChange = () => {
18798
+ this._handleRouteChange = () => {
18061
18799
  void (async () => {
18062
- const fullHash = window.location.hash.slice(1);
18063
- const [pathPart, queryPart] = fullHash.split("?");
18064
- const [photonName, methodName] = pathPart.split("/");
18800
+ const fullPath = window.location.pathname.slice(1);
18801
+ const queryPart = window.location.search.slice(1);
18802
+ const [photonName, methodName] = fullPath.split("/");
18065
18803
  let sharedParams = {};
18066
18804
  if (queryPart) {
18067
18805
  const params = new URLSearchParams(queryPart);
@@ -18078,6 +18816,9 @@ var BeamApp = class extends i4 {
18078
18816
  }
18079
18817
  }
18080
18818
  }
18819
+ this._methodPickerOpen = false;
18820
+ this._methodPickerPanelId = null;
18821
+ this._splitPanels = [];
18081
18822
  if (!photonName || photonName === "home") {
18082
18823
  this._selectedPhoton = null;
18083
18824
  this._selectedMethod = null;
@@ -18104,7 +18845,8 @@ var BeamApp = class extends i4 {
18104
18845
  return;
18105
18846
  }
18106
18847
  if (methodName && photon.methods) {
18107
- const method = photon.methods.find((m3) => m3.name === methodName);
18848
+ const [firstMethodName, secondMethodName] = methodName.split("+");
18849
+ const method = photon.methods.find((m3) => m3.name === firstMethodName);
18108
18850
  if (method) {
18109
18851
  if (Object.keys(sharedParams).length > 0) {
18110
18852
  this._sharedFormParams = sharedParams;
@@ -18115,6 +18857,22 @@ var BeamApp = class extends i4 {
18115
18857
  }
18116
18858
  this._selectedMethod = method;
18117
18859
  this._view = "form";
18860
+ if (secondMethodName) {
18861
+ const urlPath = location.pathname;
18862
+ const methodPart = urlPath.split("/").pop() || "";
18863
+ const methodNames = methodPart.split("+");
18864
+ for (let i7 = 1; i7 < methodNames.length; i7++) {
18865
+ const name2 = methodNames[i7];
18866
+ if (name2 === "source") {
18867
+ this._addPanel("source");
18868
+ } else {
18869
+ const panelMethod = photon.methods.find((m3) => m3.name === name2);
18870
+ if (panelMethod) {
18871
+ this._addPanel("method", panelMethod);
18872
+ }
18873
+ }
18874
+ }
18875
+ }
18118
18876
  if (Object.keys(sharedParams).length === 0) {
18119
18877
  this._maybeAutoInvoke(method);
18120
18878
  }
@@ -18134,18 +18892,15 @@ var BeamApp = class extends i4 {
18134
18892
  }
18135
18893
  })();
18136
18894
  };
18137
- this._handlePopState = () => {
18138
- void this._handleHashChange();
18139
- };
18140
18895
  this._goHome = () => {
18141
18896
  this._selectedPhoton = null;
18142
18897
  this._selectedMethod = null;
18143
18898
  this._lastResult = null;
18144
- this._updateHash();
18899
+ this._updateRoute();
18145
18900
  };
18146
18901
  this._runTests = async () => {
18147
18902
  if (!this._selectedPhoton || this._runningTests) return;
18148
- const testMethods = this._getTestMethods();
18903
+ const testMethods = await this._fetchTestList(this._selectedPhoton.name);
18149
18904
  if (testMethods.length === 0) return;
18150
18905
  this._runningTests = true;
18151
18906
  this._testResults = [];
@@ -18231,6 +18986,7 @@ var BeamApp = class extends i4 {
18231
18986
  `All photon tests: ${passed}/${total} passed`
18232
18987
  );
18233
18988
  };
18989
+ this._pickerDismissHandler = null;
18234
18990
  this._toggleSettingsMenu = () => {
18235
18991
  this._showSettingsMenu = !this._showSettingsMenu;
18236
18992
  };
@@ -18249,8 +19005,45 @@ var BeamApp = class extends i4 {
18249
19005
  };
18250
19006
  this._launchAsApp = () => {
18251
19007
  this._closeSettingsMenu();
18252
- if (this._selectedPhoton) {
18253
- window.open(`/api/pwa/app?photon=${encodeURIComponent(this._selectedPhoton.name)}`, "_blank");
19008
+ if (!this._selectedPhoton) return;
19009
+ if (this._pwaInstallPrompt) {
19010
+ void (async () => {
19011
+ try {
19012
+ await fetch("/api/pwa/configure", {
19013
+ method: "POST",
19014
+ headers: { "Content-Type": "application/json" },
19015
+ body: JSON.stringify({
19016
+ photon: this._selectedPhoton.name,
19017
+ port: location.port || "4100"
19018
+ }),
19019
+ signal: AbortSignal.timeout(5e3)
19020
+ });
19021
+ } catch {
19022
+ }
19023
+ const photonName = this._selectedPhoton.name;
19024
+ const onInstalled = () => {
19025
+ window.open(`/app/${encodeURIComponent(photonName)}`, "_blank");
19026
+ };
19027
+ window.addEventListener("appinstalled", onInstalled, { once: true });
19028
+ const result = await this._pwaInstallPrompt.prompt();
19029
+ this._log("info", `PWA install prompt result: ${result?.outcome}`);
19030
+ if (result?.outcome !== "accepted") {
19031
+ window.removeEventListener("appinstalled", onInstalled);
19032
+ }
19033
+ this._pwaInstallPrompt = null;
19034
+ })();
19035
+ } else {
19036
+ const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
19037
+ const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) || navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1;
19038
+ if (isSafari || isIOS) {
19039
+ const hint = isIOS ? 'Tap the Share button, then "Add to Home Screen"' : "Use File > Add to Dock to install this app";
19040
+ showToast(hint, "info");
19041
+ } else {
19042
+ showToast(
19043
+ "Install not ready yet \u2014 the service worker needs a moment to generate icons. Try again in a few seconds.",
19044
+ "info"
19045
+ );
19046
+ }
18254
19047
  }
18255
19048
  };
18256
19049
  this._handleRemove = async () => {
@@ -18416,7 +19209,7 @@ var BeamApp = class extends i4 {
18416
19209
  }
18417
19210
  }
18418
19211
  this._view = "config";
18419
- this._updateHash();
19212
+ this._updateRoute();
18420
19213
  };
18421
19214
  this._toggleRememberValues = () => {
18422
19215
  this._rememberFormValues = !this._rememberFormValues;
@@ -19001,7 +19794,7 @@ var BeamApp = class extends i4 {
19001
19794
  }
19002
19795
  if (e8.key === "Enter" && this._selectedMethod && this._view === "list") {
19003
19796
  this._view = "form";
19004
- this._updateHash();
19797
+ this._updateRoute();
19005
19798
  return;
19006
19799
  }
19007
19800
  if (e8.key === "r") {
@@ -19037,12 +19830,12 @@ var BeamApp = class extends i4 {
19037
19830
  this._focusMode = !this._focusMode;
19038
19831
  if (this._focusMode) {
19039
19832
  this.classList.add("focus-mode");
19040
- const hash2 = window.location.hash.replace(/\?focus=1/, "");
19041
- history.replaceState(null, "", hash2 + "?focus=1");
19833
+ const path = window.location.pathname;
19834
+ history.replaceState(null, "", path + "?focus=1");
19042
19835
  } else {
19043
19836
  this.classList.remove("focus-mode");
19044
- const hash2 = window.location.hash.replace(/\?focus=1/, "");
19045
- history.replaceState(null, "", hash2 || "#");
19837
+ const path = window.location.pathname;
19838
+ history.replaceState(null, "", path);
19046
19839
  }
19047
19840
  };
19048
19841
  this._handleFullscreen = () => {
@@ -19141,9 +19934,25 @@ var BeamApp = class extends i4 {
19141
19934
  case "fullscreen":
19142
19935
  this._handleFullscreen();
19143
19936
  break;
19937
+ case "install-app":
19938
+ this._launchAsApp();
19939
+ break;
19940
+ }
19941
+ break;
19942
+ }
19943
+ case "split-view:add":
19944
+ this._showMethodPicker();
19945
+ break;
19946
+ case "split-view:change": {
19947
+ const method = this._selectedPhoton?.methods?.find((m3) => m3.name === e8.detail.method);
19948
+ if (method && this._splitPanels.length > 0) {
19949
+ this._changePanelMethod(this._splitPanels[0].id, method);
19144
19950
  }
19145
19951
  break;
19146
19952
  }
19953
+ case "split-view:remove":
19954
+ this._closeSecondPanel();
19955
+ break;
19147
19956
  }
19148
19957
  };
19149
19958
  this._startEditingDescription = () => {
@@ -19343,7 +20152,7 @@ var BeamApp = class extends i4 {
19343
20152
  if (method) {
19344
20153
  this._selectedMethod = method;
19345
20154
  this._view = "form";
19346
- this._updateHash();
20155
+ this._updateRoute();
19347
20156
  this._log("info", `Starting ${targetName}/${action}...`);
19348
20157
  showToast(`Starting ${action}...`, "info");
19349
20158
  } else {
@@ -19370,15 +20179,21 @@ var BeamApp = class extends i4 {
19370
20179
  */
19371
20180
  _handleGlobalMethodSelect(photon, method) {
19372
20181
  this._teardownActiveCustomUI();
20182
+ if (this._selectedPhoton?.name !== photon.name) {
20183
+ this._closeSecondPanel();
20184
+ }
19373
20185
  this._selectedPhoton = photon;
19374
20186
  if (this._willAutoInvoke(method)) {
19375
20187
  this._isExecuting = true;
19376
20188
  }
19377
20189
  this._selectedMethod = method;
19378
20190
  this._view = "form";
19379
- this._updateHash();
20191
+ this._updateRoute();
19380
20192
  this._maybeAutoInvoke(method);
19381
20193
  }
20194
+ get _splitViewEnabled() {
20195
+ return this._splitPanels.length > 0;
20196
+ }
19382
20197
  connectedCallback() {
19383
20198
  super.connectedCallback();
19384
20199
  const savedTheme = localStorage.getItem(THEME_STORAGE_KEY);
@@ -19410,20 +20225,114 @@ var BeamApp = class extends i4 {
19410
20225
  }
19411
20226
  document.addEventListener("click", this._handleDocumentClick);
19412
20227
  void this._connectMCP();
19413
- window.addEventListener("hashchange", this._handleHashChange);
19414
- window.addEventListener("popstate", this._handlePopState);
20228
+ this._setupNotificationHandlers();
20229
+ window.addEventListener("popstate", this._handleRouteChange);
19415
20230
  window.addEventListener("message", this._handleBridgeMessage);
19416
20231
  window.addEventListener("keydown", this._handleKeydown);
20232
+ this._initPWA();
19417
20233
  }
19418
20234
  disconnectedCallback() {
19419
20235
  super.disconnectedCallback();
19420
- window.removeEventListener("hashchange", this._handleHashChange);
19421
- window.removeEventListener("popstate", this._handlePopState);
20236
+ window.removeEventListener("popstate", this._handleRouteChange);
19422
20237
  window.removeEventListener("message", this._handleBridgeMessage);
19423
20238
  window.removeEventListener("keydown", this._handleKeydown);
19424
20239
  document.removeEventListener("click", this._handleDocumentClick);
19425
20240
  this._cleanupCollectionSubscriptions();
19426
20241
  }
20242
+ willUpdate(changedProperties) {
20243
+ super.willUpdate(changedProperties);
20244
+ if (changedProperties.has("_selectedPhoton")) {
20245
+ const name2 = this._selectedPhoton?.name || null;
20246
+ if (name2 !== this._pwaCurrentPhoton) {
20247
+ this._pwaCurrentPhoton = name2;
20248
+ if (name2) {
20249
+ this._setupPWAManifest(name2);
20250
+ }
20251
+ }
20252
+ }
20253
+ }
20254
+ // ---------------------------------------------------------------------------
20255
+ // PWA — Progressive Web App support
20256
+ // ---------------------------------------------------------------------------
20257
+ _initPWA() {
20258
+ this._pwaIsStandalone = window.matchMedia("(display-mode: standalone)").matches || window.navigator.standalone === true;
20259
+ if ("serviceWorker" in navigator) {
20260
+ navigator.serviceWorker.register("/sw.js", { scope: "/" }).then((reg) => this._log("info", `PWA SW registered: ${reg.scope}`, true)).catch((err) => this._log("warn", `PWA SW registration failed: ${err}`, true));
20261
+ }
20262
+ window.addEventListener("beforeinstallprompt", (e8) => {
20263
+ this._pwaInstallPrompt = e8;
20264
+ });
20265
+ window.addEventListener("appinstalled", () => {
20266
+ this._pwaInstallPrompt = null;
20267
+ this._log("info", "PWA app installed");
20268
+ });
20269
+ }
20270
+ /**
20271
+ * Set up notification handlers for photon notifications.
20272
+ * When a notification arrives for a photon that cares about that event type,
20273
+ * bring the window to focus (especially important for PWAs/background tabs).
20274
+ */
20275
+ _setupNotificationHandlers() {
20276
+ mcpClient.on("photon-notification", (notification) => {
20277
+ const { photon, type, priority } = notification;
20278
+ if (!photon || !type) return;
20279
+ const sidebarElement = this.querySelector("beam-sidebar");
20280
+ if (sidebarElement && sidebarElement.isNotificationWatched) {
20281
+ const isWatched = sidebarElement.isNotificationWatched(photon, type);
20282
+ if (isWatched) {
20283
+ window.focus();
20284
+ console.log(`\u{1F4E1} Window focused for ${photon} notification: ${type}`);
20285
+ sidebarElement.updatePhotonWarmth?.(photon);
20286
+ }
20287
+ }
20288
+ });
20289
+ }
20290
+ /**
20291
+ * Set up the PWA manifest & icons for the currently selected photon.
20292
+ * Called whenever the selected photon changes. The manifest points to
20293
+ * /api/pwa/icon-png URLs which the service worker intercepts and renders
20294
+ * as real PNGs via OffscreenCanvas — no client-side blob URL needed.
20295
+ */
20296
+ _setupPWAManifest(photonName) {
20297
+ let manifestLink = document.head.querySelector('link[rel="manifest"]');
20298
+ if (!manifestLink) {
20299
+ manifestLink = document.createElement("link");
20300
+ manifestLink.rel = "manifest";
20301
+ document.head.appendChild(manifestLink);
20302
+ }
20303
+ manifestLink.href = `/api/pwa/manifest.json?photon=${encodeURIComponent(photonName)}`;
20304
+ let appleIcon = document.head.querySelector('link[rel="apple-touch-icon"]');
20305
+ if (!appleIcon) {
20306
+ appleIcon = document.createElement("link");
20307
+ appleIcon.rel = "apple-touch-icon";
20308
+ document.head.appendChild(appleIcon);
20309
+ }
20310
+ appleIcon.href = `/api/pwa/icon?photon=${encodeURIComponent(photonName)}`;
20311
+ let appleTitleMeta = document.head.querySelector(
20312
+ 'meta[name="apple-mobile-web-app-title"]'
20313
+ );
20314
+ if (!appleTitleMeta) {
20315
+ appleTitleMeta = document.createElement("meta");
20316
+ appleTitleMeta.name = "apple-mobile-web-app-title";
20317
+ document.head.appendChild(appleTitleMeta);
20318
+ }
20319
+ appleTitleMeta.content = photonName;
20320
+ this._prewarmIconCache(photonName);
20321
+ this._log("info", `PWA manifest set for "${photonName}"`, true);
20322
+ }
20323
+ /**
20324
+ * Pre-warm the service worker icon cache by fetching the PNG icon URLs.
20325
+ * This ensures the rendered emoji icons are cached before the user
20326
+ * attempts to install the PWA, improving the install dialog appearance.
20327
+ */
20328
+ _prewarmIconCache(photonName) {
20329
+ const sizes = [192, 512];
20330
+ sizes.forEach((size) => {
20331
+ const url2 = `/api/pwa/icon-png?photon=${encodeURIComponent(photonName)}&size=${size}`;
20332
+ fetch(url2, { mode: "no-cors" }).catch(() => {
20333
+ });
20334
+ });
20335
+ }
19427
20336
  async _connectMCP() {
19428
20337
  try {
19429
20338
  mcpClient.on("connect", () => {
@@ -19451,12 +20360,24 @@ var BeamApp = class extends i4 {
19451
20360
  }
19452
20361
  this._initialConnectDone = true;
19453
20362
  void this._checkForUpdates();
19454
- if (window.location.hash) {
19455
- void this._handleHashChange();
20363
+ if (window.location.pathname !== "/") {
20364
+ void this._handleRouteChange();
19456
20365
  } else if (!this._selectedPhoton && this._photons.length > 0) {
19457
20366
  const firstUserPhoton = this._photons.find((p5) => !p5.internal);
19458
- if (firstUserPhoton) this._selectedPhoton = firstUserPhoton;
19459
- this._updateHash(true);
20367
+ if (firstUserPhoton) {
20368
+ this._selectedPhoton = firstUserPhoton;
20369
+ if (firstUserPhoton.isApp && firstUserPhoton.appEntry) {
20370
+ if (this._willAutoInvoke(firstUserPhoton.appEntry)) {
20371
+ this._isExecuting = true;
20372
+ }
20373
+ this._selectedMethod = firstUserPhoton.appEntry;
20374
+ this._view = "form";
20375
+ this._maybeAutoInvoke(firstUserPhoton.appEntry);
20376
+ } else {
20377
+ this._view = "list";
20378
+ }
20379
+ }
20380
+ this._updateRoute(true);
19460
20381
  }
19461
20382
  })();
19462
20383
  });
@@ -19475,15 +20396,37 @@ var BeamApp = class extends i4 {
19475
20396
  this._photons = photons;
19476
20397
  this._externalMCPs = externalMCPs;
19477
20398
  this._addUnconfiguredPhotons();
19478
- if (!this._selectedPhoton && window.location.hash !== "#home") {
19479
- const newUserPhoton = this._photons.find(
19480
- (p5) => !p5.internal && p5.configured && !prevNames.has(p5.name)
19481
- );
19482
- if (newUserPhoton) {
19483
- this._selectedPhoton = newUserPhoton;
19484
- this._welcomePhase = "welcome";
19485
- this._view = "list";
19486
- this._updateHash(true);
20399
+ if (this._selectedPhoton) {
20400
+ const updated = this._photons.find((p5) => p5.name === this._selectedPhoton?.name);
20401
+ if (updated) {
20402
+ const wasApp = this._selectedPhoton.isApp;
20403
+ this._selectedPhoton = updated;
20404
+ if (!wasApp && updated.isApp && updated.appEntry && this._view === "list") {
20405
+ if (this._willAutoInvoke(updated.appEntry)) {
20406
+ this._isExecuting = true;
20407
+ }
20408
+ this._selectedMethod = updated.appEntry;
20409
+ this._view = "form";
20410
+ this._updateRoute(true);
20411
+ this._maybeAutoInvoke(updated.appEntry);
20412
+ }
20413
+ }
20414
+ }
20415
+ if (!this._selectedPhoton && window.location.pathname !== "/") {
20416
+ const pathPhotonName = window.location.pathname.slice(1).split("/")[0];
20417
+ const routePhoton = pathPhotonName ? this._photons.find((p5) => p5.name === pathPhotonName) : null;
20418
+ if (routePhoton) {
20419
+ void this._handleRouteChange();
20420
+ } else {
20421
+ const newUserPhoton = this._photons.find(
20422
+ (p5) => !p5.internal && p5.configured && !prevNames.has(p5.name)
20423
+ );
20424
+ if (newUserPhoton) {
20425
+ this._selectedPhoton = newUserPhoton;
20426
+ this._welcomePhase = "welcome";
20427
+ this._view = "list";
20428
+ this._updateRoute(true);
20429
+ }
19487
20430
  }
19488
20431
  }
19489
20432
  })();
@@ -19547,7 +20490,7 @@ var BeamApp = class extends i4 {
19547
20490
  this._selectedPhoton = updated;
19548
20491
  }
19549
20492
  } else {
19550
- if (window.location.hash === "#home") return;
20493
+ if (window.location.pathname === "/") return;
19551
20494
  const newUserPhoton = this._photons.find(
19552
20495
  (p5) => !p5.internal && p5.configured && !prevNames.has(p5.name)
19553
20496
  );
@@ -19576,7 +20519,7 @@ var BeamApp = class extends i4 {
19576
20519
  } else {
19577
20520
  this._view = "list";
19578
20521
  }
19579
- this._updateHash(true);
20522
+ this._updateRoute(true);
19580
20523
  }
19581
20524
  }
19582
20525
  }
@@ -19628,7 +20571,7 @@ var BeamApp = class extends i4 {
19628
20571
  } else {
19629
20572
  this._view = "list";
19630
20573
  }
19631
- this._updateHash(true);
20574
+ this._updateRoute(true);
19632
20575
  }
19633
20576
  }
19634
20577
  });
@@ -19739,20 +20682,27 @@ var BeamApp = class extends i4 {
19739
20682
  }
19740
20683
  }
19741
20684
  }
19742
- _updateHash(replace2 = false) {
19743
- let hash2;
20685
+ _updateRoute(replace2 = false) {
20686
+ let path;
19744
20687
  if (!this._selectedPhoton) {
19745
- hash2 = "home";
20688
+ path = "/";
19746
20689
  } else {
19747
- hash2 = this._selectedPhoton.name;
20690
+ path = "/" + this._selectedPhoton.name;
19748
20691
  if (this._selectedMethod) {
19749
- hash2 += `/${this._selectedMethod.name}`;
20692
+ path += `/${this._selectedMethod.name}`;
20693
+ for (const panel of this._splitPanels) {
20694
+ if (panel.type === "method" && panel.method) {
20695
+ path += `+${panel.method.name}`;
20696
+ } else if (panel.type === "source") {
20697
+ path += `+source`;
20698
+ }
20699
+ }
19750
20700
  }
19751
20701
  }
19752
20702
  if (replace2) {
19753
- history.replaceState(null, "", `#${hash2}`);
20703
+ history.replaceState(null, "", path);
19754
20704
  } else {
19755
- history.pushState(null, "", `#${hash2}`);
20705
+ history.pushState(null, "", path);
19756
20706
  }
19757
20707
  }
19758
20708
  /**
@@ -19803,6 +20753,21 @@ var BeamApp = class extends i4 {
19803
20753
  if (!this._selectedPhoton?.methods) return [];
19804
20754
  return this._selectedPhoton.methods.filter((m3) => m3.name.startsWith("test_") || m3.name.startsWith("test")).map((m3) => m3.name);
19805
20755
  }
20756
+ /** Fetch all tests (external .test.ts + inline) from the server */
20757
+ async _fetchTestList(photonName) {
20758
+ try {
20759
+ const res = await fetch(`/api/test/list?photon=${encodeURIComponent(photonName)}`, {
20760
+ signal: AbortSignal.timeout(5e3)
20761
+ });
20762
+ if (!res.ok) return this._getTestMethods();
20763
+ const data = await res.json();
20764
+ if (data.tests && data.tests.length > 0) {
20765
+ return data.tests.map((t8) => t8.name);
20766
+ }
20767
+ } catch {
20768
+ }
20769
+ return this._getTestMethods();
20770
+ }
19806
20771
  _getAllTestMethods() {
19807
20772
  const results = [];
19808
20773
  for (const p5 of this._photons) {
@@ -20126,7 +21091,7 @@ var BeamApp = class extends i4 {
20126
21091
  }}
20127
21092
  @diagnostics=${() => {
20128
21093
  this._view = "diagnostics";
20129
- this._updateHash();
21094
+ this._updateRoute();
20130
21095
  }}
20131
21096
  @open-studio=${(e8) => {
20132
21097
  const photon = this._photons.find((p5) => p5.name === e8.detail.photonName);
@@ -20146,7 +21111,39 @@ var BeamApp = class extends i4 {
20146
21111
  ></beam-sidebar>
20147
21112
  </nav>
20148
21113
 
20149
- <main class="main-area" id="main-content" tabindex="-1" aria-label="Main content">
21114
+ <main
21115
+ class="main-area"
21116
+ id="main-content"
21117
+ tabindex="-1"
21118
+ aria-label="Main content"
21119
+ style="${this._splitViewEnabled ? "overflow: hidden !important;" : ""}"
21120
+ >
21121
+ ${this._selectedPhoton && this._selectedMethod ? b2`<button
21122
+ class="beam-back-btn"
21123
+ @click=${() => this._handleBackFromMethod()}
21124
+ @mouseenter=${(e8) => {
21125
+ e8.target.style.color = "var(--t-primary)";
21126
+ e8.target.style.borderColor = "var(--accent-primary)";
21127
+ }}
21128
+ @mouseleave=${(e8) => {
21129
+ e8.target.style.color = "var(--t-muted)";
21130
+ e8.target.style.borderColor = "var(--border-glass)";
21131
+ }}
21132
+ title="Back to ${this._selectedPhoton.name}"
21133
+ >
21134
+ <svg
21135
+ width="16"
21136
+ height="16"
21137
+ viewBox="0 0 24 24"
21138
+ fill="none"
21139
+ stroke="currentColor"
21140
+ stroke-width="2"
21141
+ stroke-linecap="round"
21142
+ stroke-linejoin="round"
21143
+ >
21144
+ <path d="m15 18-6-6 6-6" />
21145
+ </svg>
21146
+ </button>` : ""}
20150
21147
  ${this._selectedPhoton ? b2`<button
20151
21148
  class="beam-fullscreen-btn"
20152
21149
  @click=${this._toggleFocusMode}
@@ -20582,7 +21579,7 @@ var BeamApp = class extends i4 {
20582
21579
  }}
20583
21580
  @click=${() => {
20584
21581
  this._view = "diagnostics";
20585
- this._updateHash();
21582
+ this._updateRoute();
20586
21583
  }}
20587
21584
  >
20588
21585
  <span style="font-size: 1rem; width: 20px; text-align: center;">🔍</span>
@@ -20791,7 +21788,7 @@ ${photon.errorMessage || "Unknown error"}</pre
20791
21788
  @select=${(e8) => {
20792
21789
  this._selectedMethod = e8.detail.method;
20793
21790
  this._view = "form";
20794
- this._updateHash();
21791
+ this._updateRoute();
20795
21792
  }}
20796
21793
  ></method-card>
20797
21794
  `
@@ -20808,7 +21805,7 @@ ${photon.errorMessage || "Unknown error"}</pre
20808
21805
  if (this._selectedMethod.linkedUi) {
20809
21806
  const isAppMain = this._selectedPhoton.isApp && this._selectedMethod.name === "main";
20810
21807
  const otherMethods = isAppMain ? this._getVisibleMethods().filter((m3) => m3.name !== "main") : [];
20811
- const isExternalMCP2 = this._selectedPhoton.isExternalMCP;
21808
+ const isExternalMCP = this._selectedPhoton.isExternalMCP;
20812
21809
  const appRenderer = this._isExecuting ? b2`
20813
21810
  <div
20814
21811
  style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; height: calc(100vh - 140px);"
@@ -20818,7 +21815,7 @@ ${photon.errorMessage || "Unknown error"}</pre
20818
21815
  ${this._progress?.message || "Starting up\u2026"}
20819
21816
  </span>
20820
21817
  </div>
20821
- ` : isExternalMCP2 ? b2`
21818
+ ` : isExternalMCP ? b2`
20822
21819
  <mcp-app-renderer
20823
21820
  .mcpName=${this._selectedPhoton.name}
20824
21821
  .appUri=${`ui://${this._selectedPhoton.name}/${this._selectedMethod.linkedUi}`}
@@ -20837,7 +21834,48 @@ ${photon.errorMessage || "Unknown error"}</pre
20837
21834
  ></custom-ui-renderer>
20838
21835
  `;
20839
21836
  if (isAppMain) {
21837
+ if (this._splitPanels.length > 0) {
21838
+ return b2`
21839
+ <div style="display: flex; gap: 1px; height: calc(100vh - 60px); overflow: hidden;">
21840
+ <!-- App Panel (primary) — no title bar, app has its own chrome -->
21841
+ <div style="flex: 1; min-height: 0; overflow: hidden;">${appRenderer}</div>
21842
+
21843
+ <!-- Additional Panels -->
21844
+ ${this._splitPanels.map(
21845
+ (panel) => b2`
21846
+ <div style="flex: 1; min-height: 0; background: var(--bg-panel);">
21847
+ ${panel.type === "method" ? this._renderSinglePanel(this._buildAdditionalPanelOpts(panel)) : this._renderSourcePanel(panel.id)}
21848
+ </div>
21849
+ `
21850
+ )}
21851
+ </div>
21852
+ `;
21853
+ }
20840
21854
  return b2`
21855
+ <!-- Floating add-panel button for app view — sits next to focus button -->
21856
+ <div
21857
+ style="position: sticky; top: calc(-1 * var(--space-lg)); float: right; z-index: 100; margin-top: calc(-1 * var(--space-lg)); margin-right: 6px;"
21858
+ >
21859
+ <button
21860
+ @click=${() => {
21861
+ this._methodPickerOpen = !this._methodPickerOpen;
21862
+ this._methodPickerPanelId = null;
21863
+ }}
21864
+ style="width: 28px; height: 28px; border-radius: var(--radius-sm); background: var(--bg-glass); border: 1px solid var(--border-glass); color: var(--t-muted); cursor: pointer; font-size: 14px; font-weight: 700; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; backdrop-filter: blur(8px);"
21865
+ @mouseenter=${(e8) => {
21866
+ e8.target.style.color = "var(--accent-secondary)";
21867
+ e8.target.style.borderColor = "var(--accent-secondary)";
21868
+ }}
21869
+ @mouseleave=${(e8) => {
21870
+ e8.target.style.color = "var(--t-muted)";
21871
+ e8.target.style.borderColor = "var(--border-glass)";
21872
+ }}
21873
+ title="Add panel"
21874
+ >
21875
+ +
21876
+ </button>
21877
+ ${this._methodPickerOpen && this._methodPickerPanelId === null ? this._renderMethodPickerPopover() : ""}
21878
+ </div>
20841
21879
  <app-layout
20842
21880
  .photonName=${this._selectedPhoton.name}
20843
21881
  .photonIcon=${this._selectedPhoton.appEntry?.icon || "\u{1F4F1}"}
@@ -20880,61 +21918,72 @@ ${photon.errorMessage || "Unknown error"}</pre
20880
21918
  </app-layout>
20881
21919
  `;
20882
21920
  }
21921
+ if (this._splitPanels.length > 0) {
21922
+ return b2`
21923
+ <div style="display: flex; gap: 1px; height: calc(100vh - 60px); overflow: hidden;">
21924
+ <!-- Linked UI Panel (primary) -->
21925
+ <div
21926
+ style="flex: 1; min-height: 0; display: flex; flex-direction: column; position: relative;"
21927
+ >
21928
+ <div
21929
+ style="display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; border-bottom: 1px solid var(--border-glass); background: var(--bg-glass); flex-shrink: 0;"
21930
+ >
21931
+ <span style="font-size: 12px; font-weight: 500; color: var(--t-primary);"
21932
+ >${this._selectedMethod.name}</span
21933
+ >
21934
+ <div style="position: relative; flex-shrink: 0;">
21935
+ <button
21936
+ @click=${() => {
21937
+ this._methodPickerOpen = !this._methodPickerOpen;
21938
+ this._methodPickerPanelId = null;
21939
+ }}
21940
+ style="padding: 4px 8px; background: none; color: var(--accent-secondary); border: 1px solid var(--accent-secondary); border-radius: 3px; cursor: pointer; font-size: 14px; font-weight: 700; transition: all 0.2s ease;"
21941
+ title="Add panel"
21942
+ >
21943
+ +
21944
+ </button>
21945
+ ${this._methodPickerOpen && this._methodPickerPanelId === null ? this._renderMethodPickerPopover() : ""}
21946
+ </div>
21947
+ </div>
21948
+ <div style="flex: 1; min-height: 0; overflow: hidden;">${appRenderer}</div>
21949
+ </div>
21950
+
21951
+ <!-- Additional Panels -->
21952
+ ${this._splitPanels.map(
21953
+ (panel) => b2`
21954
+ <div style="flex: 1; min-height: 0; background: var(--bg-panel);">
21955
+ ${panel.type === "method" ? this._renderSinglePanel(this._buildAdditionalPanelOpts(panel)) : this._renderSourcePanel(panel.id)}
21956
+ </div>
21957
+ `
21958
+ )}
21959
+ </div>
21960
+ `;
21961
+ }
20883
21962
  return b2`
20884
- <context-bar
20885
- .photon=${this._selectedPhoton}
20886
- .breadcrumbs=${[
20887
- {
20888
- label: this._currentInstance !== "default" ? `${this._selectedPhoton.name}:${this._currentInstance}` : this._selectedPhoton.name,
20889
- action: "back"
20890
- },
20891
- { label: formatLabel(this._selectedMethod.name) }
20892
- ]}
20893
- .live=${this._currentCollectionName !== null}
20894
- .showEdit=${false}
20895
- .showConfigure=${false}
20896
- .showCopyConfig=${false}
20897
- .overflowItems=${[]}
20898
- .instanceSelectorMode=${this._instanceSelectorMode}
20899
- .autoInstance=${this._autoInstance}
20900
- @context-action=${this._handleContextAction}
20901
- ></context-bar>
20902
- <div
20903
- class="glass-panel"
20904
- style="padding: 0; overflow: hidden; min-height: calc(100vh - 80px); margin-top: var(--space-md);"
20905
- >
20906
- ${appRenderer}
21963
+ <div style="position: relative;">
21964
+ <div style="position: absolute; top: 16px; right: 16px; z-index: 50;">
21965
+ <button
21966
+ @click=${() => {
21967
+ this._methodPickerOpen = !this._methodPickerOpen;
21968
+ this._methodPickerPanelId = null;
21969
+ }}
21970
+ style="padding: 4px 8px; background: var(--bg-glass); color: var(--accent-secondary); border: 1px solid var(--accent-secondary); border-radius: 3px; cursor: pointer; font-size: 14px; font-weight: 700; transition: all 0.2s ease; backdrop-filter: blur(8px);"
21971
+ title="Add panel"
21972
+ >
21973
+ +
21974
+ </button>
21975
+ ${this._methodPickerOpen && this._methodPickerPanelId === null ? this._renderMethodPickerPopover() : ""}
21976
+ </div>
21977
+ <div
21978
+ class="glass-panel"
21979
+ style="padding: 0; overflow: hidden; min-height: calc(100vh - 80px); margin-top: var(--space-md);"
21980
+ >
21981
+ ${appRenderer}
21982
+ </div>
20907
21983
  </div>
20908
21984
  `;
20909
21985
  }
20910
- const isExternalMCP = this._selectedPhoton.isExternalMCP;
20911
- return b2`
20912
- <context-bar
20913
- .photon=${this._selectedPhoton}
20914
- .breadcrumbs=${[
20915
- {
20916
- label: this._currentInstance !== "default" ? `${this._selectedPhoton.name}:${this._currentInstance}` : this._selectedPhoton.name,
20917
- action: "back"
20918
- },
20919
- { label: formatLabel(this._selectedMethod.name) }
20920
- ]}
20921
- .live=${this._currentCollectionName !== null}
20922
- .showEdit=${false}
20923
- .showConfigure=${false}
20924
- .showCopyConfig=${false}
20925
- .overflowItems=${this._buildOverflowItems({
20926
- showRefresh: !isExternalMCP,
20927
- showRename: false,
20928
- showViewSource: false,
20929
- showDelete: false,
20930
- showHelp: !isExternalMCP
20931
- })}
20932
- .instanceSelectorMode=${this._instanceSelectorMode}
20933
- .autoInstance=${this._autoInstance}
20934
- @context-action=${this._handleContextAction}
20935
- ></context-bar>
20936
- ${this._renderMethodContent()}
20937
- `;
21986
+ return b2` ${this._renderMethodContent()} `;
20938
21987
  }
20939
21988
  return b2`
20940
21989
  ${this._renderPhotonToolbar()} ${this._editingIcon ? this._renderEmojiPicker() : ""}
@@ -21062,6 +22111,7 @@ ${photon.errorMessage || "Unknown error"}</pre
21062
22111
  }
21063
22112
  }
21064
22113
  async _handlePhotonSelect(e8) {
22114
+ this._closeSecondPanel();
21065
22115
  this._selectedPhoton = e8.detail.photon;
21066
22116
  this._selectedMethod = null;
21067
22117
  this._lastResult = null;
@@ -21069,12 +22119,12 @@ ${photon.errorMessage || "Unknown error"}</pre
21069
22119
  this._currentInstance = "default";
21070
22120
  if (this._selectedPhoton.configured === false) {
21071
22121
  this._view = "config";
21072
- this._updateHash();
22122
+ this._updateRoute();
21073
22123
  return;
21074
22124
  }
21075
22125
  if (this._selectedPhoton.isExternalMCP && this._selectedPhoton.hasMcpApp) {
21076
22126
  this._view = "mcp-app";
21077
- this._updateHash();
22127
+ this._updateRoute();
21078
22128
  return;
21079
22129
  }
21080
22130
  if (this._selectedPhoton.stateful && this._selectedPhoton.configured) {
@@ -21101,13 +22151,13 @@ ${photon.errorMessage || "Unknown error"}</pre
21101
22151
  }
21102
22152
  this._selectedMethod = this._selectedPhoton.appEntry;
21103
22153
  this._view = "form";
21104
- this._updateHash();
22154
+ this._updateRoute();
21105
22155
  this._maybeAutoInvoke(this._selectedPhoton.appEntry);
21106
22156
  return;
21107
22157
  } else {
21108
22158
  this._view = "list";
21109
22159
  }
21110
- this._updateHash();
22160
+ this._updateRoute();
21111
22161
  }
21112
22162
  /** Fetch available instances for a stateful photon from the server */
21113
22163
  async _fetchInstances(photonName) {
@@ -21150,7 +22200,7 @@ ${photon.errorMessage || "Unknown error"}</pre
21150
22200
  /**
21151
22201
  * Trigger instance switch via MCP elicitation (calls _use without name).
21152
22202
  * The transport layer shows an elicitation modal with available instances.
21153
- * After _use succeeds, the transport broadcasts photon/state-changed which
22203
+ * After _use succeeds, the transport broadcasts state-changed which
21154
22204
  * triggers _silentRefresh() to update the result and notifies custom UIs.
21155
22205
  */
21156
22206
  async _switchInstance() {
@@ -21186,7 +22236,7 @@ ${photon.errorMessage || "Unknown error"}</pre
21186
22236
  this._selectedMethod = e8.detail.method;
21187
22237
  this._lastResult = null;
21188
22238
  this._view = "form";
21189
- this._updateHash();
22239
+ this._updateRoute();
21190
22240
  this._maybeAutoInvoke(e8.detail.method);
21191
22241
  }
21192
22242
  /**
@@ -21248,57 +22298,507 @@ ${photon.errorMessage || "Unknown error"}</pre
21248
22298
  </div>
21249
22299
  `;
21250
22300
  }
22301
+ if (this._splitPanels.length > 0) {
22302
+ return b2`
22303
+ <div style="display: flex; gap: 1px; height: 100%; overflow: hidden;">
22304
+ <!-- Primary Panel -->
22305
+ <div style="flex: 1; min-height: 0; background: var(--bg-panel);">
22306
+ ${this._renderSinglePanel({
22307
+ photon: this._selectedPhoton,
22308
+ method: this._selectedMethod,
22309
+ result: this._lastResult,
22310
+ executing: this._isExecuting,
22311
+ progress: this._progress,
22312
+ formParams: this._lastFormParams,
22313
+ onSubmit: (e8) => void this._handleExecute(e8),
22314
+ onCancel: () => this._handleBackFromMethod(),
22315
+ panelLabel: "Primary",
22316
+ instance: this._currentInstance,
22317
+ instances: this._instances,
22318
+ onInstanceChange: (instance) => this._handleLeftPanelInstanceChange(instance),
22319
+ allMethods: this._selectedPhoton?.methods || [],
22320
+ onMethodChange: (method) => this._handleLeftPanelMethodChange(method),
22321
+ panelSide: "primary",
22322
+ onPanelAction: (action) => this._handlePrimaryPanelAction(action),
22323
+ onInstanceAction: (detail) => void this._handleInstanceAction(detail),
22324
+ isStateful: !!this._selectedPhoton?.stateful,
22325
+ isLive: this._currentCollectionName !== null,
22326
+ overflowItems: this._buildOverflowItems({
22327
+ showRefresh: !this._selectedPhoton?.isExternalMCP,
22328
+ showRename: false,
22329
+ showViewSource: false,
22330
+ showDelete: false,
22331
+ showHelp: !this._selectedPhoton?.isExternalMCP
22332
+ }),
22333
+ onOverflowSelect: (id2) => this._handleOverflowAction(id2)
22334
+ })}
22335
+ </div>
22336
+
22337
+ <!-- Additional Panels -->
22338
+ ${this._splitPanels.map(
22339
+ (panel) => b2`
22340
+ <div style="flex: 1; min-height: 0; background: var(--bg-panel);">
22341
+ ${panel.type === "method" ? this._renderSinglePanel(this._buildAdditionalPanelOpts(panel)) : this._renderSourcePanel(panel.id)}
22342
+ </div>
22343
+ `
22344
+ )}
22345
+ </div>
22346
+ `;
22347
+ }
22348
+ return this._renderSinglePanel({
22349
+ photon: this._selectedPhoton,
22350
+ method: this._selectedMethod,
22351
+ result: this._lastResult,
22352
+ executing: this._isExecuting,
22353
+ progress: this._progress,
22354
+ formParams: this._lastFormParams,
22355
+ onSubmit: (e8) => void this._handleExecute(e8),
22356
+ onCancel: () => this._handleBackFromMethod(),
22357
+ panelLabel: "Primary",
22358
+ instance: this._currentInstance,
22359
+ instances: this._instances,
22360
+ allMethods: this._selectedPhoton?.methods || [],
22361
+ onMethodChange: (method) => {
22362
+ this._selectedMethod = method;
22363
+ this._lastResult = null;
22364
+ this._lastFormParams = {};
22365
+ if (this._willAutoInvoke(method)) {
22366
+ void this._handleExecute(new CustomEvent("execute", { detail: { args: {} } }));
22367
+ }
22368
+ this._updateRoute();
22369
+ },
22370
+ panelSide: "primary",
22371
+ onPanelAction: (action) => this._handlePrimaryPanelAction(action),
22372
+ onInstanceAction: (detail) => void this._handleInstanceAction(detail),
22373
+ isStateful: !!this._selectedPhoton?.stateful,
22374
+ isLive: this._currentCollectionName !== null,
22375
+ overflowItems: this._buildOverflowItems({
22376
+ showRefresh: !this._selectedPhoton?.isExternalMCP,
22377
+ showRename: false,
22378
+ showViewSource: false,
22379
+ showDelete: false,
22380
+ showHelp: !this._selectedPhoton?.isExternalMCP
22381
+ }),
22382
+ onOverflowSelect: (id2) => this._handleOverflowAction(id2)
22383
+ });
22384
+ }
22385
+ /** Render a single panel with self-contained header */
22386
+ _renderSinglePanel(opts) {
22387
+ const isSplit = this._splitPanels.length > 0;
21251
22388
  return b2`
21252
- <div class="glass-panel method-detail">
21253
- <h2>${formatLabel(this._selectedMethod.name)}</h2>
21254
- ${this._renderDescription(this._selectedMethod.description)}
21255
- <invoke-form
21256
- .params=${this._selectedMethod.params}
21257
- .loading=${this._isExecuting}
21258
- .photonName=${this._selectedPhoton.name}
21259
- .methodName=${this._selectedMethod.name}
21260
- .rememberValues=${this._rememberFormValues}
21261
- .sharedValues=${this._sharedFormParams}
21262
- @submit=${(e8) => {
21263
- void this._handleExecute(e8);
22389
+ <div
22390
+ class="glass-panel method-detail"
22391
+ style="${isSplit ? "border-radius: 0; height: 100%;" : ""} display: flex; flex-direction: column;"
22392
+ >
22393
+ <!-- Panel Header: LED + Method ▼ + instance-panel + [+] + [⋯] + [×] -->
22394
+ <div
22395
+ style="display: flex; align-items: center; gap: 8px; padding-bottom: 12px; margin-bottom: 12px; border-bottom: 1px solid var(--border-glass); flex-shrink: 0; position: relative;"
22396
+ >
22397
+ <!-- LED dot (stateful/live indicator) -->
22398
+ ${opts.isStateful ? b2`<span
22399
+ style="width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; background: ${opts.isLive ? "var(--color-success, #22c55e)" : "var(--t-tertiary, #666)"}; box-shadow: ${opts.isLive ? "0 0 6px var(--color-success, #22c55e)" : "none"};"
22400
+ title="${opts.isLive ? "Live \u2014 stateful photon with active subscription" : "Stateful photon"}"
22401
+ ></span>` : ""}
22402
+
22403
+ <!-- Method Selector (name as dropdown) -->
22404
+ <select
22405
+ .value=${opts.method.name}
22406
+ @change=${(e8) => {
22407
+ const methodName = e8.target.value;
22408
+ const method = opts.allMethods?.find((m3) => m3.name === methodName);
22409
+ if (method && opts.onMethodChange) {
22410
+ opts.onMethodChange(method);
22411
+ }
21264
22412
  }}
21265
- @cancel=${() => this._handleBackFromMethod()}
21266
- ></invoke-form>
22413
+ style="padding: 6px 8px; border-radius: 4px; border: 1px solid var(--border-glass); background: var(--bg-glass); color: var(--t-primary); font-size: 13px; font-weight: 600; flex-shrink: 0;"
22414
+ >
22415
+ ${opts.allMethods?.map(
22416
+ (m3) => b2`<option .selected=${m3.name === opts.method.name} value=${m3.name}>
22417
+ ${m3.name}
22418
+ </option>`
22419
+ ) || b2`<option value=${opts.method.name}>${opts.method.name}</option>`}
22420
+ </select>
21267
22421
 
21268
- ${this._progress ? b2`
21269
- <div class="progress-container">
21270
- <div class="progress-bar-wrapper">
21271
- <div
21272
- class="progress-bar ${this._progress.value < 0 ? "indeterminate" : ""}"
21273
- style="width: ${this._progress.value < 0 ? "30%" : Math.round(this._progress.value * 100) + "%"}"
21274
- ></div>
22422
+ <div style="flex: 1;"></div>
22423
+
22424
+ <!-- Rich Instance Selector -->
22425
+ ${opts.isStateful && opts.instances && opts.instances.length > 0 && opts.onInstanceAction ? b2`
22426
+ <instance-panel
22427
+ .instanceName=${opts.instance || "default"}
22428
+ .instances=${opts.instances}
22429
+ .photonName=${opts.photon.name}
22430
+ .selectorMode=${this._instanceSelectorMode}
22431
+ .autoInstance=${this._autoInstance}
22432
+ @instance-action=${(e8) => opts.onInstanceAction?.(e8.detail)}
22433
+ ></instance-panel>
22434
+ ` : ""}
22435
+
22436
+ <!-- Add panel button (primary only) -->
22437
+ ${opts.panelSide === "primary" ? b2`
22438
+ <div style="position: relative; flex-shrink: 0;">
22439
+ <button
22440
+ @click=${() => opts.onPanelAction?.("toggle-picker")}
22441
+ style="padding: 4px 8px; background: none; color: var(--accent-secondary); border: 1px solid var(--accent-secondary); border-radius: 3px; cursor: pointer; font-size: 14px; font-weight: 700; transition: all 0.2s ease;"
22442
+ title="Add panel"
22443
+ >
22444
+ +
22445
+ </button>
22446
+ ${this._methodPickerOpen && this._methodPickerPanelId === null ? this._renderMethodPickerPopover() : ""}
21275
22447
  </div>
21276
- <div class="progress-text">
21277
- <span>${this._progress.message}</span>
21278
- ${this._progress.value >= 0 ? b2`
21279
- <span class="progress-percentage"
21280
- >${Math.round(this._progress.value * 100)}%</span
21281
- >
21282
- ` : ""}
22448
+ ` : ""}
22449
+
22450
+ <!-- Overflow menu -->
22451
+ ${opts.overflowItems && opts.overflowItems.length > 0 ? b2`
22452
+ <overflow-menu
22453
+ .items=${opts.overflowItems}
22454
+ @menu-select=${(e8) => opts.onOverflowSelect?.(e8.detail.id)}
22455
+ ></overflow-menu>
22456
+ ` : ""}
22457
+
22458
+ <!-- Close button (additional panels only) -->
22459
+ ${opts.panelSide === "additional" ? b2`
22460
+ <button
22461
+ @click=${() => opts.onPanelAction?.("close")}
22462
+ style="padding: 4px 8px; background: none; color: var(--color-error); border: 1px solid var(--color-error); border-radius: 3px; cursor: pointer; font-size: 14px; font-weight: 700; flex-shrink: 0; transition: all 0.2s ease;"
22463
+ title="Close panel"
22464
+ >
22465
+ ×
22466
+ </button>
22467
+ ` : ""}
22468
+ </div>
22469
+ <!-- Panel Content (scrollable) -->
22470
+ <div
22471
+ style="display: flex; flex-direction: column; flex: 1; min-height: 0; overflow-y: auto;"
22472
+ >
22473
+ ${this._renderDescription(opts.method.description)}
22474
+ <invoke-form
22475
+ .params=${opts.method.params}
22476
+ .loading=${opts.executing}
22477
+ .photonName=${opts.photon.name}
22478
+ .methodName=${opts.method.name}
22479
+ .rememberValues=${this._rememberFormValues}
22480
+ .sharedValues=${opts.formParams}
22481
+ @submit=${opts.onSubmit}
22482
+ @cancel=${opts.onCancel}
22483
+ ></invoke-form>
22484
+
22485
+ ${opts.progress ? b2`
22486
+ <div class="progress-container">
22487
+ <div class="progress-bar-wrapper">
22488
+ <div
22489
+ class="progress-bar ${opts.progress.value < 0 ? "indeterminate" : ""}"
22490
+ style="width: ${opts.progress.value < 0 ? "30%" : Math.round(opts.progress.value * 100) + "%"}"
22491
+ ></div>
22492
+ </div>
22493
+ <div class="progress-text">
22494
+ <span>${opts.progress.message}</span>
22495
+ </div>
22496
+ </div>
22497
+ ` : ""}
22498
+ ${opts.result !== null ? b2`
22499
+ <result-viewer
22500
+ .result=${opts.result}
22501
+ .outputFormat=${opts.method?.outputFormat}
22502
+ .layoutHints=${opts.method?.layoutHints}
22503
+ .theme=${this._theme}
22504
+ .live=${this._currentCollectionName !== null}
22505
+ .resultKey=${opts.photon && opts.method ? `${opts.photon.name}/${opts.method.name}` : void 0}
22506
+ @share=${() => this._handleShareResult()}
22507
+ ></result-viewer>
22508
+ ` : b2`
22509
+ <div class="empty-state-inline result-empty">
22510
+ <span class="empty-state-icon">${play}</span>
22511
+ <span>Run the method to see results here</span>
22512
+ </div>
22513
+ `}
22514
+ </div>
22515
+ </div>
22516
+ `;
22517
+ }
22518
+ /** Close all split panels and return to single-panel mode */
22519
+ _closeSecondPanel() {
22520
+ this._splitPanels = [];
22521
+ this._methodPickerOpen = false;
22522
+ this._methodPickerPanelId = null;
22523
+ this._updateRoute();
22524
+ }
22525
+ /** Handle instance change for primary panel */
22526
+ _handleLeftPanelInstanceChange(instance) {
22527
+ this._currentInstance = instance;
22528
+ sessionStorage.setItem(`photon-instance:${this._selectedPhoton.name}`, instance);
22529
+ this._lastResult = null;
22530
+ }
22531
+ /** Add a new split panel */
22532
+ _addPanel(type, method) {
22533
+ if (this._splitPanels.length >= 2) return;
22534
+ const panel = {
22535
+ id: `panel-${++this._nextPanelId}`,
22536
+ type,
22537
+ method,
22538
+ result: null,
22539
+ executing: false,
22540
+ progress: null,
22541
+ formParams: {},
22542
+ instance: this._currentInstance || "default"
22543
+ };
22544
+ this._splitPanels = [...this._splitPanels, panel];
22545
+ if (type === "method" && method && this._willAutoInvoke(method)) {
22546
+ void this._executePanelMethod(panel.id, {});
22547
+ }
22548
+ this._methodPickerOpen = false;
22549
+ this._methodPickerPanelId = null;
22550
+ this._cleanupPickerDismiss();
22551
+ this._updateRoute();
22552
+ }
22553
+ /** Remove a split panel by id */
22554
+ _removePanel(panelId) {
22555
+ this._splitPanels = this._splitPanels.filter((p5) => p5.id !== panelId);
22556
+ this._updateRoute();
22557
+ }
22558
+ /** Update a split panel's state */
22559
+ _updatePanel(panelId, updates) {
22560
+ this._splitPanels = this._splitPanels.map((p5) => p5.id === panelId ? { ...p5, ...updates } : p5);
22561
+ }
22562
+ /** Execute a method in a split panel */
22563
+ async _executePanelMethod(panelId, args) {
22564
+ const panel = this._splitPanels.find((p5) => p5.id === panelId);
22565
+ if (!panel || panel.type !== "method" || !panel.method) return;
22566
+ this._updatePanel(panelId, { executing: true, result: null, progress: null, formParams: args });
22567
+ try {
22568
+ const toolName = `${this._selectedPhoton.name}/${panel.method.name}`;
22569
+ const result = await mcpClient.callTool(
22570
+ toolName,
22571
+ args,
22572
+ void 0,
22573
+ panel.instance || this._currentInstance,
22574
+ (progress) => {
22575
+ this._updatePanel(panelId, { progress });
22576
+ }
22577
+ );
22578
+ this._updatePanel(panelId, {
22579
+ result: mcpClient.parseToolResult(result),
22580
+ executing: false,
22581
+ progress: null
22582
+ });
22583
+ } catch (error2) {
22584
+ showToast(`Error: ${error2.message}`, { type: "error" });
22585
+ this._updatePanel(panelId, {
22586
+ result: { error: error2.message },
22587
+ executing: false,
22588
+ progress: null
22589
+ });
22590
+ }
22591
+ }
22592
+ /** Change the method in a split panel */
22593
+ _changePanelMethod(panelId, method) {
22594
+ this._updatePanel(panelId, { method, result: null, formParams: {} });
22595
+ if (this._willAutoInvoke(method)) {
22596
+ void this._executePanelMethod(panelId, {});
22597
+ }
22598
+ this._updateRoute();
22599
+ }
22600
+ /** Change the instance in a split panel */
22601
+ _changePanelInstance(panelId, instance) {
22602
+ this._updatePanel(panelId, { instance, result: null });
22603
+ }
22604
+ /** Open a method in a new split panel (backwards-compatible entry point) */
22605
+ _openInSecondPanel(photon, method) {
22606
+ this._addPanel("method", method);
22607
+ }
22608
+ /** Show method picker popover */
22609
+ _showMethodPicker() {
22610
+ this._methodPickerOpen = !this._methodPickerOpen;
22611
+ this._methodPickerPanelId = null;
22612
+ }
22613
+ /** Clean up picker dismiss listener */
22614
+ _cleanupPickerDismiss() {
22615
+ if (this._pickerDismissHandler) {
22616
+ window.removeEventListener("mousedown", this._pickerDismissHandler, true);
22617
+ this._pickerDismissHandler = null;
22618
+ }
22619
+ }
22620
+ /** Handle method change in primary panel */
22621
+ _handleLeftPanelMethodChange(method) {
22622
+ this._selectedMethod = method;
22623
+ this._lastResult = null;
22624
+ this._lastFormParams = {};
22625
+ this._updateRoute();
22626
+ }
22627
+ /** Handle panel action for primary panel */
22628
+ _handlePrimaryPanelAction(action) {
22629
+ if (action === "toggle-picker") {
22630
+ this._methodPickerOpen = !this._methodPickerOpen;
22631
+ this._methodPickerPanelId = null;
22632
+ }
22633
+ }
22634
+ /** Build opts for an additional (non-primary) split panel */
22635
+ _buildAdditionalPanelOpts(panel) {
22636
+ return {
22637
+ photon: this._selectedPhoton,
22638
+ method: panel.method,
22639
+ result: panel.result,
22640
+ executing: panel.executing || false,
22641
+ progress: panel.progress,
22642
+ formParams: panel.formParams || {},
22643
+ onSubmit: (e8) => {
22644
+ const args = e8.detail?.args || {};
22645
+ void this._executePanelMethod(panel.id, args);
22646
+ },
22647
+ onCancel: () => this._removePanel(panel.id),
22648
+ panelLabel: panel.id,
22649
+ instance: panel.instance,
22650
+ instances: this._instances,
22651
+ onInstanceChange: (inst) => this._changePanelInstance(panel.id, inst),
22652
+ allMethods: this._selectedPhoton?.methods || [],
22653
+ onMethodChange: (m3) => this._changePanelMethod(panel.id, m3),
22654
+ panelSide: "additional",
22655
+ onPanelAction: (action) => {
22656
+ if (action === "close") this._removePanel(panel.id);
22657
+ },
22658
+ onInstanceAction: (detail) => {
22659
+ if (detail.action === "switch") {
22660
+ this._changePanelInstance(panel.id, detail.instance);
22661
+ }
22662
+ },
22663
+ isStateful: !!this._selectedPhoton?.stateful,
22664
+ isLive: this._currentCollectionName !== null,
22665
+ overflowItems: this._buildOverflowItems({
22666
+ showRefresh: false,
22667
+ showRename: false,
22668
+ showViewSource: false,
22669
+ showDelete: false,
22670
+ showHelp: false,
22671
+ showRunTests: false
22672
+ }),
22673
+ onOverflowSelect: (id2) => this._handleOverflowAction(id2)
22674
+ };
22675
+ }
22676
+ /** Handle overflow menu actions from panel headers */
22677
+ _handleOverflowAction(id2) {
22678
+ switch (id2) {
22679
+ case "refresh":
22680
+ if (this._selectedMethod && this._willAutoInvoke(this._selectedMethod)) {
22681
+ void this._handleExecute(new CustomEvent("execute", { detail: { args: {} } }));
22682
+ }
22683
+ break;
22684
+ case "remember-values":
22685
+ this._rememberFormValues = !this._rememberFormValues;
22686
+ break;
22687
+ case "verbose-logging":
22688
+ this._verboseLogging = !this._verboseLogging;
22689
+ break;
22690
+ case "run-tests":
22691
+ void this._runTests();
22692
+ break;
22693
+ case "help":
22694
+ this._showPhotonHelp = true;
22695
+ break;
22696
+ default:
22697
+ this._handleContextAction(new CustomEvent("context-action", { detail: { action: id2 } }));
22698
+ break;
22699
+ }
22700
+ }
22701
+ /** Render source panel for split view */
22702
+ _renderSourcePanel(panelId) {
22703
+ return b2`
22704
+ <div style="height: 100%; overflow: hidden;">
22705
+ <photon-studio
22706
+ .photonName=${this._selectedPhoton?.name}
22707
+ .theme=${this._theme}
22708
+ @studio-saved=${() => this._handleStudioSaved?.()}
22709
+ @studio-close=${() => this._removePanel(panelId)}
22710
+ ></photon-studio>
22711
+ </div>
22712
+ `;
22713
+ }
22714
+ /** Render the method picker popover for adding new panels */
22715
+ _renderMethodPickerPopover() {
22716
+ const methods = this._getVisibleMethods();
22717
+ const openMethodNames = /* @__PURE__ */ new Set();
22718
+ if (this._selectedMethod) openMethodNames.add(this._selectedMethod.name);
22719
+ for (const p5 of this._splitPanels) {
22720
+ if (p5.type === "method" && p5.method) openMethodNames.add(p5.method.name);
22721
+ }
22722
+ const availableMethods = methods.filter((m3) => !openMethodNames.has(m3.name));
22723
+ const hasSourcePanel = this._splitPanels.some((p5) => p5.type === "source");
22724
+ const canAddMore = this._splitPanels.length < 2;
22725
+ if (!canAddMore) return "";
22726
+ if (!this._pickerDismissHandler) {
22727
+ const handler = (e8) => {
22728
+ const path = e8.composedPath();
22729
+ const popover = this.renderRoot.querySelector(".method-picker-popover");
22730
+ if (popover && path.includes(popover)) return;
22731
+ this._methodPickerOpen = false;
22732
+ this._pickerDismissHandler = null;
22733
+ window.removeEventListener("mousedown", handler, true);
22734
+ };
22735
+ this._pickerDismissHandler = handler;
22736
+ setTimeout(() => window.addEventListener("mousedown", handler, true), 0);
22737
+ }
22738
+ return b2`
22739
+ <div
22740
+ class="method-picker-popover"
22741
+ @click=${(e8) => e8.stopPropagation()}
22742
+ style="position: absolute; top: 100%; right: 0; z-index: 9999; min-width: 200px; max-height: 360px; overflow-y: auto; background: var(--bg-glass); border: 1px solid var(--border-glass); border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,0.3); backdrop-filter: blur(20px); margin-top: 4px;"
22743
+ >
22744
+ <!-- Header -->
22745
+ <div style="padding: 10px 12px 8px; border-bottom: 1px solid var(--border-glass);">
22746
+ <div
22747
+ style="font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--t-tertiary);"
22748
+ >
22749
+ Split Pane
22750
+ </div>
22751
+ </div>
22752
+
22753
+ <!-- Methods section -->
22754
+ ${availableMethods.length > 0 ? b2`
22755
+ <div style="padding: 4px 0;">
22756
+ <div
22757
+ style="padding: 4px 12px 2px; font-size: 10px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; color: var(--t-tertiary);"
22758
+ >
22759
+ Methods
21283
22760
  </div>
22761
+ ${availableMethods.map(
22762
+ (m3) => b2`
22763
+ <div
22764
+ @click=${() => this._addPanel("method", m3)}
22765
+ style="padding: 6px 12px 6px 16px; cursor: pointer; font-size: 12px; color: var(--t-primary); transition: background 0.15s ease; display: flex; align-items: center; gap: 6px;"
22766
+ @mouseenter=${(e8) => e8.target.style.background = "var(--bg-hover)"}
22767
+ @mouseleave=${(e8) => e8.target.style.background = "transparent"}
22768
+ >
22769
+ <span style="color: var(--t-tertiary); font-size: 10px;">&#9656;</span>
22770
+ ${m3.name}
22771
+ </div>
22772
+ `
22773
+ )}
21284
22774
  </div>
21285
- ` : ""}
21286
- ${this._lastResult !== null ? b2`
21287
- <result-viewer
21288
- .result=${this._lastResult}
21289
- .outputFormat=${this._selectedMethod?.outputFormat}
21290
- .layoutHints=${this._selectedMethod?.layoutHints}
21291
- .theme=${this._theme}
21292
- .live=${this._currentCollectionName !== null}
21293
- .resultKey=${this._selectedPhoton && this._selectedMethod ? `${this._selectedPhoton.name}/${this._selectedMethod.name}` : void 0}
21294
- @share=${() => this._handleShareResult()}
21295
- ></result-viewer>
21296
22775
  ` : b2`
21297
- <div class="empty-state-inline result-empty">
21298
- <span class="empty-state-icon">${play}</span>
21299
- <span>Run the method to see results here</span>
22776
+ <div
22777
+ style="padding: 8px 12px; font-size: 11px; color: var(--t-tertiary); font-style: italic;"
22778
+ >
22779
+ All methods already open
21300
22780
  </div>
21301
22781
  `}
22782
+
22783
+ <!-- Tools section -->
22784
+ ${!hasSourcePanel ? b2`
22785
+ <div style="border-top: 1px solid var(--border-glass); padding: 4px 0;">
22786
+ <div
22787
+ style="padding: 4px 12px 2px; font-size: 10px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; color: var(--t-tertiary);"
22788
+ >
22789
+ Tools
22790
+ </div>
22791
+ <div
22792
+ @click=${() => this._addPanel("source")}
22793
+ style="padding: 6px 12px 6px 16px; cursor: pointer; font-size: 12px; color: var(--t-primary); transition: background 0.15s ease; display: flex; align-items: center; gap: 6px;"
22794
+ @mouseenter=${(e8) => e8.target.style.background = "var(--bg-hover)"}
22795
+ @mouseleave=${(e8) => e8.target.style.background = "transparent"}
22796
+ >
22797
+ <span style="font-size: 11px; color: var(--accent-primary);">&lt;/&gt;</span>
22798
+ Source Editor
22799
+ </div>
22800
+ </div>
22801
+ ` : ""}
21302
22802
  </div>
21303
22803
  `;
21304
22804
  }
@@ -21327,10 +22827,11 @@ ${photon.errorMessage || "Unknown error"}</pre
21327
22827
  if (form?.isDirty && !confirm("You have unsaved changes. Discard them?")) {
21328
22828
  return;
21329
22829
  }
22830
+ this._closeSecondPanel();
21330
22831
  if (this._selectedPhoton.isApp && this._selectedPhoton.appEntry) {
21331
22832
  this._selectedMethod = this._selectedPhoton.appEntry;
21332
22833
  this._view = "form";
21333
- this._updateHash(true);
22834
+ this._updateRoute(true);
21334
22835
  void this.updateComplete.then(() => {
21335
22836
  setTimeout(() => {
21336
22837
  const mainArea = this.shadowRoot?.querySelector(".main-area");
@@ -21344,11 +22845,11 @@ ${photon.errorMessage || "Unknown error"}</pre
21344
22845
  this._selectedPhoton = null;
21345
22846
  this._welcomePhase = "welcome";
21346
22847
  this._view = "list";
21347
- this._updateHash(true);
22848
+ this._updateRoute(true);
21348
22849
  } else {
21349
22850
  this._view = "list";
21350
22851
  this._selectedMethod = null;
21351
- this._updateHash(true);
22852
+ this._updateRoute(true);
21352
22853
  }
21353
22854
  }
21354
22855
  async _openForkDialog() {
@@ -21422,7 +22923,17 @@ ${photon.errorMessage || "Unknown error"}</pre
21422
22923
  }
21423
22924
  } else {
21424
22925
  this._lastResult = mcpClient.parseToolResult(result);
22926
+ if (this._selectedPhoton?.stateful && Array.isArray(this._lastResult) && this._selectedMethod) {
22927
+ this._lastResult = this._autoWrapPaginationIfNeeded(
22928
+ this._lastResult,
22929
+ this._selectedPhoton.name,
22930
+ this._selectedMethod
22931
+ );
22932
+ }
21425
22933
  this._log("success", "Execution completed", false, execDuration);
22934
+ if (this._selectedPhoton?.stateful && this._lastResult != null && typeof this._lastResult === "object") {
22935
+ this._initializeGlobalInstance(this._selectedPhoton.name, this._lastResult);
22936
+ }
21426
22937
  void this.updateComplete.then(() => {
21427
22938
  const rv2 = this.shadowRoot?.querySelector("result-viewer");
21428
22939
  if (rv2 && window.innerWidth <= 768) {
@@ -21457,6 +22968,16 @@ ${photon.errorMessage || "Unknown error"}</pre
21457
22968
  const result = await mcpClient.callTool(toolName, this._lastFormParams || {});
21458
22969
  if (!result.isError) {
21459
22970
  this._lastResult = mcpClient.parseToolResult(result);
22971
+ if (this._selectedPhoton.stateful && Array.isArray(this._lastResult) && this._selectedMethod) {
22972
+ this._lastResult = this._autoWrapPaginationIfNeeded(
22973
+ this._lastResult,
22974
+ this._selectedPhoton.name,
22975
+ this._selectedMethod
22976
+ );
22977
+ }
22978
+ if (this._selectedPhoton.stateful && this._lastResult != null && typeof this._lastResult === "object") {
22979
+ this._initializeGlobalInstance(this._selectedPhoton.name, this._lastResult);
22980
+ }
21460
22981
  }
21461
22982
  } catch {
21462
22983
  } finally {
@@ -21507,6 +23028,162 @@ ${photon.errorMessage || "Unknown error"}</pre
21507
23028
  this._collectionUnsubscribes = [];
21508
23029
  this._currentCollectionName = null;
21509
23030
  }
23031
+ /**
23032
+ * Initialize global photon instance for @stateful photons
23033
+ * Makes the photon instance available as window.{photonName}
23034
+ * and keeps it in sync with server state via state-changed patches
23035
+ * Wraps paginated array properties with ViewportAwareProxy for smart fetching
23036
+ */
23037
+ /**
23038
+ * Auto-wrap array results with pagination metadata for @stateful photons
23039
+ * If method has (start, limit) parameters, detect them and use global instance
23040
+ * to calculate pagination metadata from the full array
23041
+ */
23042
+ _autoWrapPaginationIfNeeded(items, photonName, method) {
23043
+ const hasStartLimitParams = method.parameters?.some(
23044
+ (p5) => (p5.name === "start" || p5.name === "limit") && p5.required !== true
23045
+ );
23046
+ if (!hasStartLimitParams || items.length === 0) {
23047
+ return items;
23048
+ }
23049
+ try {
23050
+ const manager = getGlobalInstanceManager();
23051
+ const instance = manager.getInstance(photonName);
23052
+ if (!instance) {
23053
+ return items;
23054
+ }
23055
+ const arrayProp = Object.keys(instance).find((key) => {
23056
+ const value = instance[key];
23057
+ return Array.isArray(value) && value.length > 0;
23058
+ });
23059
+ if (!arrayProp) {
23060
+ return items;
23061
+ }
23062
+ const fullArray = instance[arrayProp];
23063
+ const totalCount = fullArray.length;
23064
+ const start = 0;
23065
+ const end = items.length;
23066
+ return {
23067
+ items,
23068
+ _pagination: {
23069
+ totalCount,
23070
+ start,
23071
+ end,
23072
+ hasMore: end < totalCount
23073
+ }
23074
+ };
23075
+ } catch (error2) {
23076
+ if (this._verboseLogging) {
23077
+ const msg = error2 instanceof Error ? error2.message : "Unknown error";
23078
+ this._log("warn", `Could not auto-wrap pagination: ${msg}`);
23079
+ }
23080
+ return items;
23081
+ }
23082
+ }
23083
+ _initializeGlobalInstance(photonName, initialState) {
23084
+ try {
23085
+ const paginatedProps = this._detectPaginatedProperties(initialState);
23086
+ const instance = initializeGlobalPhotonSession(photonName, initialState);
23087
+ for (const [propName, paginationMeta] of Object.entries(paginatedProps)) {
23088
+ this._wrapWithViewportProxy(instance, propName, paginationMeta);
23089
+ }
23090
+ if (this._verboseLogging) {
23091
+ const message = paginatedProps.size > 0 ? `\u{1F4E1} Global instance initialized with ${paginatedProps.size} paginated array(s): window.${photonName.toLowerCase()}` : `\u{1F4E1} Global instance initialized: window.${photonName.toLowerCase()}`;
23092
+ this._log("info", message);
23093
+ }
23094
+ } catch (error2) {
23095
+ console.error("Failed to initialize global photon instance", error2);
23096
+ }
23097
+ }
23098
+ /**
23099
+ * Detect which properties have pagination metadata
23100
+ */
23101
+ _detectPaginatedProperties(initialState) {
23102
+ const paginated = /* @__PURE__ */ new Map();
23103
+ if (!initialState || typeof initialState !== "object") {
23104
+ return paginated;
23105
+ }
23106
+ for (const [key, value] of Object.entries(initialState)) {
23107
+ if (value && typeof value === "object" && value._pagination) {
23108
+ paginated.set(key, value._pagination);
23109
+ }
23110
+ }
23111
+ return paginated;
23112
+ }
23113
+ /**
23114
+ * Wrap a property with ViewportAwareProxy for smart pagination
23115
+ */
23116
+ _wrapWithViewportProxy(instance, propertyName2, paginationMeta) {
23117
+ try {
23118
+ const pageSize = getPageSizeForClient();
23119
+ const proxy = new ViewportAwareProxy(
23120
+ this._selectedPhoton?.name || "unknown",
23121
+ this._selectedMethod?.name || propertyName2,
23122
+ mcpClient,
23123
+ {
23124
+ pageSize,
23125
+ bufferSize: 5,
23126
+ // Items to buffer above/below viewport
23127
+ maxCacheSize: 500
23128
+ // Max items to keep in cache
23129
+ }
23130
+ );
23131
+ proxy.initializeWithResponse({
23132
+ items: instance[propertyName2] || [],
23133
+ _pagination: paginationMeta
23134
+ });
23135
+ instance.makeProperty(propertyName2);
23136
+ Object.defineProperty(instance, propertyName2, {
23137
+ configurable: true,
23138
+ enumerable: true,
23139
+ get: () => proxy.items,
23140
+ set: (value) => {
23141
+ proxy.clearCache();
23142
+ proxy.initializeWithResponse({
23143
+ items: value || [],
23144
+ _pagination: paginationMeta
23145
+ });
23146
+ }
23147
+ });
23148
+ const patchHandler = (data) => {
23149
+ if (data?.patches) {
23150
+ proxy.applyPatches(data.patches);
23151
+ }
23152
+ };
23153
+ instance.on("state-changed", patchHandler);
23154
+ queueMicrotask(() => {
23155
+ try {
23156
+ const resultViewer = this._resultViewer;
23157
+ if (resultViewer?.shadowRoot) {
23158
+ const scrollContainer = resultViewer.shadowRoot.querySelector(
23159
+ ".result-content"
23160
+ );
23161
+ if (scrollContainer) {
23162
+ const manager = new ViewportManager(proxy, {
23163
+ container: scrollContainer,
23164
+ itemSelector: "[data-index]",
23165
+ pageSize,
23166
+ bufferSize: 5
23167
+ });
23168
+ manager.start();
23169
+ }
23170
+ }
23171
+ } catch (error2) {
23172
+ if (this._verboseLogging) {
23173
+ this._log("warn", `Could not set up automatic viewport tracking for ${propertyName2}`);
23174
+ }
23175
+ }
23176
+ });
23177
+ if (this._verboseLogging) {
23178
+ this._log(
23179
+ "info",
23180
+ `\u2728 Paginated proxy enabled for ${propertyName2} (page size: ${pageSize})`
23181
+ );
23182
+ }
23183
+ } catch (error2) {
23184
+ console.error(`Failed to wrap ${propertyName2} with ViewportAwareProxy`, error2);
23185
+ }
23186
+ }
21510
23187
  async _handleConfigure(e8) {
21511
23188
  const { photon, config: config3 } = e8.detail;
21512
23189
  this._log("info", `Configuring ${photon}...`);
@@ -21541,8 +23218,7 @@ ${photon.errorMessage || "Unknown error"}</pre
21541
23218
  showToast("No method selected to share", "error");
21542
23219
  return;
21543
23220
  }
21544
- const baseUrl = window.location.origin + window.location.pathname;
21545
- const hash2 = `${this._selectedPhoton.name}/${this._selectedMethod.name}`;
23221
+ const pathSegment2 = `${this._selectedPhoton.name}/${this._selectedMethod.name}`;
21546
23222
  const params = new URLSearchParams();
21547
23223
  for (const [key, value] of Object.entries(this._lastFormParams)) {
21548
23224
  if (value !== void 0 && value !== null && value !== "") {
@@ -21553,7 +23229,7 @@ ${photon.errorMessage || "Unknown error"}</pre
21553
23229
  }
21554
23230
  }
21555
23231
  }
21556
- let shareUrl = `${baseUrl}#${hash2}`;
23232
+ let shareUrl = `${window.location.origin}/${pathSegment2}`;
21557
23233
  if (params.toString()) {
21558
23234
  shareUrl += `?${params.toString()}`;
21559
23235
  }
@@ -21787,7 +23463,8 @@ ${photon.errorMessage || "Unknown error"}</pre
21787
23463
  showHelp = true,
21788
23464
  showRunTests = this._getTestMethods().length > 0,
21789
23465
  showRemove = false,
21790
- showFullscreen = false
23466
+ showFullscreen = false,
23467
+ showInstallApp = !this._pwaIsStandalone && !!this._selectedPhoton?.isApp
21791
23468
  } = opts;
21792
23469
  const items = [];
21793
23470
  if (showFullscreen) {
@@ -21833,6 +23510,13 @@ ${photon.errorMessage || "Unknown error"}</pre
21833
23510
  toggle: true,
21834
23511
  toggleActive: this._verboseLogging
21835
23512
  });
23513
+ if (showInstallApp) {
23514
+ items.push({
23515
+ id: "install-app",
23516
+ label: "Install as App",
23517
+ iconSvg: iconSvgString(iconPaths.installApp)
23518
+ });
23519
+ }
21836
23520
  if (showRename || showViewSource || showFork || showContribute || showDelete || showRemove) {
21837
23521
  const first = [
21838
23522
  showRename,
@@ -22758,6 +24442,10 @@ BeamApp.styles = [
22758
24442
  display: none;
22759
24443
  }
22760
24444
 
24445
+ :host(.focus-mode) .main-area {
24446
+ padding: var(--space-sm);
24447
+ }
24448
+
22761
24449
  .main-area {
22762
24450
  flex: 1;
22763
24451
  position: relative;
@@ -22766,10 +24454,10 @@ BeamApp.styles = [
22766
24454
  padding: var(--space-lg);
22767
24455
  }
22768
24456
 
24457
+ .beam-back-btn,
22769
24458
  .beam-fullscreen-btn {
22770
24459
  position: sticky;
22771
24460
  top: calc(-1 * var(--space-lg));
22772
- float: right;
22773
24461
  z-index: 100;
22774
24462
  width: 28px;
22775
24463
  height: 28px;
@@ -22783,6 +24471,18 @@ BeamApp.styles = [
22783
24471
  align-items: center;
22784
24472
  justify-content: center;
22785
24473
  transition: all 0.2s ease;
24474
+ backdrop-filter: blur(8px);
24475
+ }
24476
+
24477
+ .beam-back-btn {
24478
+ float: left;
24479
+ margin-top: calc(-1 * var(--space-lg));
24480
+ margin-left: calc(-1 * var(--space-lg) + 1px);
24481
+ margin-bottom: calc(-28px + var(--space-lg));
24482
+ }
24483
+
24484
+ .beam-fullscreen-btn {
24485
+ float: right;
22786
24486
  margin-top: calc(-1 * var(--space-lg));
22787
24487
  margin-right: calc(-1 * var(--space-lg) + 1px);
22788
24488
  margin-bottom: calc(-28px + var(--space-lg));
@@ -23813,7 +25513,7 @@ BeamApp.styles = [
23813
25513
  min-width: 120px;
23814
25514
  font-family: var(--font-mono);
23815
25515
  font-size: var(--text-md);
23816
- color: hsl(45, 80%, 60%);
25516
+ color: var(--color-warning);
23817
25517
  }
23818
25518
 
23819
25519
  .variable-input input {
@@ -24002,7 +25702,7 @@ BeamApp.styles = [
24002
25702
 
24003
25703
  .progress-bar-wrapper {
24004
25704
  height: 8px;
24005
- background: rgba(0, 0, 0, 0.2);
25705
+ background: var(--bg-glass);
24006
25706
  border-radius: var(--radius-xs);
24007
25707
  overflow: hidden;
24008
25708
  }
@@ -24120,6 +25820,25 @@ BeamApp.styles = [
24120
25820
  padding-top: calc(var(--space-md) + 60px); /* Space for mobile menu button */
24121
25821
  }
24122
25822
 
25823
+ .beam-back-btn,
25824
+ .beam-fullscreen-btn {
25825
+ position: fixed;
25826
+ top: var(--space-md);
25827
+ float: none;
25828
+ margin: 0;
25829
+ width: 44px;
25830
+ height: 44px;
25831
+ box-shadow: var(--shadow-md);
25832
+ }
25833
+
25834
+ .beam-back-btn {
25835
+ left: calc(var(--space-md) + 44px + var(--space-sm));
25836
+ }
25837
+
25838
+ .beam-fullscreen-btn {
25839
+ right: var(--space-md);
25840
+ }
25841
+
24123
25842
  .photon-header {
24124
25843
  flex-direction: column;
24125
25844
  align-items: flex-start;
@@ -24134,10 +25853,9 @@ BeamApp.styles = [
24134
25853
  grid-template-columns: 1fr;
24135
25854
  }
24136
25855
 
24137
- /* Touch-friendly targets - min 44px */
25856
+ /* Touch-friendly targets - min 44px (exclude small inline buttons) */
24138
25857
  .asset-card,
24139
25858
  .method-card,
24140
- button,
24141
25859
  .filter-btn {
24142
25860
  min-height: 44px;
24143
25861
  }
@@ -24418,6 +26136,15 @@ __decorateClass([
24418
26136
  __decorateClass([
24419
26137
  r5()
24420
26138
  ], BeamApp.prototype, "_resourceContent", 2);
26139
+ __decorateClass([
26140
+ r5()
26141
+ ], BeamApp.prototype, "_splitPanels", 2);
26142
+ __decorateClass([
26143
+ r5()
26144
+ ], BeamApp.prototype, "_methodPickerOpen", 2);
26145
+ __decorateClass([
26146
+ r5()
26147
+ ], BeamApp.prototype, "_methodPickerPanelId", 2);
24421
26148
  __decorateClass([
24422
26149
  e7("beam-sidebar")
24423
26150
  ], BeamApp.prototype, "_sidebar", 2);
@@ -24456,6 +26183,7 @@ var BeamSidebar = class extends i4 {
24456
26183
  this._favorites = /* @__PURE__ */ new Set();
24457
26184
  this._collapsedSections = /* @__PURE__ */ new Set();
24458
26185
  this._recentPhotons = [];
26186
+ this._notificationWarmth = /* @__PURE__ */ new Map();
24459
26187
  }
24460
26188
  connectedCallback() {
24461
26189
  super.connectedCallback();
@@ -24826,7 +26554,7 @@ var BeamSidebar = class extends i4 {
24826
26554
  }
24827
26555
  return b2`
24828
26556
  <li
24829
- class="photon-item ${this.selectedPhoton === photon.name ? "active" : ""} ${photon.internal ? "internal" : ""}"
26557
+ class="photon-item ${this.selectedPhoton === photon.name ? "active" : ""} ${photon.internal ? "internal" : ""} ${this._isPhotonWarm(photon.name) ? "warmth" : ""}"
24830
26558
  role="option"
24831
26559
  aria-selected="${this.selectedPhoton === photon.name}"
24832
26560
  tabindex="0"
@@ -25023,6 +26751,28 @@ ${photon.path}` : ""}"
25023
26751
  }
25024
26752
  });
25025
26753
  }
26754
+ /**
26755
+ * Check if a photon currently has a warmth indicator (notification received within last 5 seconds)
26756
+ */
26757
+ _isPhotonWarm(photonName) {
26758
+ const lastNotificationTime = this._notificationWarmth.get(photonName);
26759
+ if (!lastNotificationTime) return false;
26760
+ return Date.now() - lastNotificationTime < 5e3;
26761
+ }
26762
+ /**
26763
+ * Update the warmth indicator for a photon when a notification arrives
26764
+ */
26765
+ updatePhotonWarmth(photonName) {
26766
+ this._notificationWarmth.set(photonName, Date.now());
26767
+ this.requestUpdate();
26768
+ }
26769
+ /**
26770
+ * Check if a notification type is watched by a photon
26771
+ * This will be called by beam-app to determine if window.focus() should be triggered
26772
+ */
26773
+ isNotificationWatched(photonName, notificationType) {
26774
+ return true;
26775
+ }
25026
26776
  };
25027
26777
  BeamSidebar.styles = [
25028
26778
  theme,
@@ -25257,7 +27007,7 @@ BeamSidebar.styles = [
25257
27007
  }
25258
27008
 
25259
27009
  .section-header:hover {
25260
- color: var(--t-secondary);
27010
+ color: var(--t-primary);
25261
27011
  background: var(--bg-panel);
25262
27012
  }
25263
27013
 
@@ -25319,7 +27069,7 @@ BeamSidebar.styles = [
25319
27069
  }
25320
27070
 
25321
27071
  .photon-item:hover {
25322
- background: hsla(220, 10%, 80%, 0.1);
27072
+ background: var(--bg-glass);
25323
27073
  }
25324
27074
 
25325
27075
  .photon-item.active {
@@ -25333,13 +27083,28 @@ BeamSidebar.styles = [
25333
27083
 
25334
27084
  @keyframes flash-highlight {
25335
27085
  0% {
25336
- background: hsla(260, 100%, 65%, 0.2);
27086
+ background: var(--glow-primary);
25337
27087
  }
25338
27088
  100% {
25339
27089
  background: transparent;
25340
27090
  }
25341
27091
  }
25342
27092
 
27093
+ .photon-item.warmth {
27094
+ animation: warmth-fade 5s ease-out forwards;
27095
+ }
27096
+
27097
+ @keyframes warmth-fade {
27098
+ 0% {
27099
+ background: var(--color-warning);
27100
+ opacity: 0.15;
27101
+ }
27102
+ 100% {
27103
+ background: var(--color-warning);
27104
+ opacity: 0;
27105
+ }
27106
+ }
27107
+
25343
27108
  .photon-icon {
25344
27109
  width: 28px;
25345
27110
  height: 28px;
@@ -25430,7 +27195,7 @@ BeamSidebar.styles = [
25430
27195
  gap: 3px;
25431
27196
  font-size: var(--text-2xs);
25432
27197
  padding: 2px 5px;
25433
- background: rgba(255, 255, 255, 0.05);
27198
+ background: var(--bg-glass);
25434
27199
  border: 1px solid var(--border-glass);
25435
27200
  border-radius: var(--radius-full);
25436
27201
  font-weight: 500;
@@ -25513,7 +27278,7 @@ BeamSidebar.styles = [
25513
27278
  .marketplace-btn:hover {
25514
27279
  background: var(--bg-panel);
25515
27280
  border-color: var(--accent-secondary);
25516
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
27281
+ box-shadow: var(--shadow-sm);
25517
27282
  transform: translateY(-1px);
25518
27283
  }
25519
27284
 
@@ -25745,6 +27510,9 @@ __decorateClass([
25745
27510
  __decorateClass([
25746
27511
  r5()
25747
27512
  ], BeamSidebar.prototype, "_recentPhotons", 2);
27513
+ __decorateClass([
27514
+ r5()
27515
+ ], BeamSidebar.prototype, "_notificationWarmth", 2);
25748
27516
  BeamSidebar = __decorateClass([
25749
27517
  t4("beam-sidebar")
25750
27518
  ], BeamSidebar);
@@ -25791,6 +27559,7 @@ var MethodCard = class extends i4 {
25791
27559
  const isThrottled = !!this.method.throttled;
25792
27560
  const isDebounced = !!this.method.debounced;
25793
27561
  const isQueued = !!this.method.queued;
27562
+ const emitsEvent = !!this.method.emitsEvent;
25794
27563
  const isTyped = isAutorun || isWebhook || isCron || isLocked || isDeprecated;
25795
27564
  const typeAccent = isDeprecated ? "hsl(0, 0%, 50%)" : isWebhook ? "hsl(45, 80%, 50%)" : isCron ? "hsl(215, 80%, 60%)" : isLocked ? "hsl(0, 65%, 55%)" : isAutorun ? "hsl(160, 60%, 45%)" : "";
25796
27565
  return b2`
@@ -25817,8 +27586,7 @@ var MethodCard = class extends i4 {
25817
27586
  ` : ""}
25818
27587
  <span class="editable">
25819
27588
  <h3 class="title">
25820
- <span class="title-name">${formatLabel(this.method.name)}</span
25821
- >${this._renderParamSignature()}
27589
+ <span class="title-name">${this.method.name}</span>${this._renderParamSignature()}
25822
27590
  </h3>
25823
27591
  <span
25824
27592
  class="edit-pencil"
@@ -25834,30 +27602,18 @@ var MethodCard = class extends i4 {
25834
27602
  </div>
25835
27603
  ${this.method.isTemplate ? b2`<span class="badge prompt">Prompt</span>` : ""}
25836
27604
  ${isCron ? b2`<span
25837
- class="badge"
25838
- style="background:hsla(215,80%,60%,0.15);color:hsl(215,80%,65%)"
27605
+ class="badge scheduled"
25839
27606
  title="Runs automatically on schedule: ${this.method.scheduled}"
25840
27607
  >⏱ Scheduled</span
25841
27608
  >` : ""}
25842
- ${isDeprecated ? b2`<span
25843
- class="badge"
25844
- style="background:hsla(0,0%,50%,0.15);color:hsl(0,0%,60%);text-decoration:line-through"
25845
- >Deprecated</span
25846
- >` : ""}
25847
- ${isCached ? b2`<span
25848
- class="badge"
25849
- style="background:hsla(280,60%,50%,0.15);color:hsl(280,60%,65%)"
25850
- >Cached</span
25851
- >` : ""}
25852
- ${isThrottled ? b2`<span
25853
- class="badge"
25854
- style="background:hsla(30,80%,50%,0.15);color:hsl(30,80%,60%)"
25855
- >Throttled</span
25856
- >` : ""}
25857
- ${isQueued ? b2`<span
25858
- class="badge"
25859
- style="background:hsla(200,70%,50%,0.15);color:hsl(200,70%,60%)"
25860
- >Queued</span
27609
+ ${isDeprecated ? b2`<span class="badge deprecated">Deprecated</span>` : ""}
27610
+ ${isCached ? b2`<span class="badge cached">Cached</span>` : ""}
27611
+ ${isThrottled ? b2`<span class="badge throttled">Throttled</span>` : ""}
27612
+ ${isQueued ? b2`<span class="badge queued">Queued</span>` : ""}
27613
+ ${emitsEvent ? b2`<span
27614
+ class="badge event"
27615
+ title="Automatically emits event: ${this.method.eventName}"
27616
+ >📡 Event</span
25861
27617
  >` : ""}
25862
27618
  </div>
25863
27619
  ${this._editingDescription ? b2`
@@ -26101,7 +27857,7 @@ MethodCard.styles = [
26101
27857
 
26102
27858
  .card:hover {
26103
27859
  transform: translateY(-2px);
26104
- box-shadow: 0 8px 32px -4px rgba(0, 0, 0, 0.3);
27860
+ box-shadow: var(--shadow-lg);
26105
27861
  border-left-color: var(--accent-primary);
26106
27862
  }
26107
27863
 
@@ -26279,7 +28035,7 @@ MethodCard.styles = [
26279
28035
  font-size: var(--text-xs);
26280
28036
  padding: 2px 8px;
26281
28037
  border-radius: var(--radius-md);
26282
- background: hsla(220, 10%, 80%, 0.1);
28038
+ background: var(--bg-glass);
26283
28039
  color: var(--t-muted);
26284
28040
  flex-shrink: 0;
26285
28041
  }
@@ -26289,6 +28045,37 @@ MethodCard.styles = [
26289
28045
  color: hsl(45, 80%, 60%);
26290
28046
  }
26291
28047
 
28048
+ .badge.scheduled {
28049
+ background: hsla(215, 80%, 60%, 0.15);
28050
+ color: hsl(215, 80%, 65%);
28051
+ }
28052
+
28053
+ .badge.deprecated {
28054
+ background: hsla(0, 0%, 50%, 0.15);
28055
+ color: hsl(0, 0%, 60%);
28056
+ text-decoration: line-through;
28057
+ }
28058
+
28059
+ .badge.cached {
28060
+ background: hsla(280, 60%, 50%, 0.15);
28061
+ color: hsl(280, 60%, 65%);
28062
+ }
28063
+
28064
+ .badge.throttled {
28065
+ background: hsla(30, 80%, 50%, 0.15);
28066
+ color: hsl(30, 80%, 60%);
28067
+ }
28068
+
28069
+ .badge.queued {
28070
+ background: hsla(200, 70%, 50%, 0.15);
28071
+ color: hsl(200, 70%, 60%);
28072
+ }
28073
+
28074
+ .badge.event {
28075
+ background: hsla(100, 70%, 50%, 0.15);
28076
+ color: hsl(100, 70%, 60%);
28077
+ }
28078
+
26292
28079
  .param-tags {
26293
28080
  display: flex;
26294
28081
  flex-wrap: wrap;
@@ -26302,9 +28089,9 @@ MethodCard.styles = [
26302
28089
  font-size: var(--text-xs);
26303
28090
  padding: 4px 10px;
26304
28091
  border-radius: var(--radius-xs);
26305
- background: hsla(260, 60%, 50%, 0.12);
26306
- color: var(--t-primary);
26307
- border: 1px solid hsla(260, 60%, 50%, 0.2);
28092
+ background: var(--param-tag-bg, hsla(260, 60%, 50%, 0.12));
28093
+ color: var(--param-tag-color, var(--t-primary));
28094
+ border: 1px solid var(--param-tag-border, hsla(260, 60%, 50%, 0.2));
26308
28095
  font-weight: 500;
26309
28096
  }
26310
28097
 
@@ -26313,8 +28100,8 @@ MethodCard.styles = [
26313
28100
  min-width: 20px;
26314
28101
  height: 20px;
26315
28102
  border-radius: var(--radius-sm);
26316
- background: hsla(45, 80%, 50%, 0.2);
26317
- color: hsl(45, 80%, 60%);
28103
+ background: var(--color-warning-bg, hsla(45, 80%, 50%, 0.2));
28104
+ color: var(--color-warning, hsl(45, 80%, 60%));
26318
28105
  display: inline-flex;
26319
28106
  align-items: center;
26320
28107
  justify-content: center;
@@ -26344,7 +28131,7 @@ MethodCard.styles = [
26344
28131
  align-items: center;
26345
28132
  justify-content: center;
26346
28133
  border-radius: var(--radius-sm);
26347
- background: hsla(220, 10%, 80%, 0.1);
28134
+ background: var(--bg-glass);
26348
28135
  }
26349
28136
 
26350
28137
  .card:hover .action-icon {
@@ -26359,7 +28146,7 @@ MethodCard.styles = [
26359
28146
  border: 1px solid var(--border-glass);
26360
28147
  border-radius: var(--radius-md);
26361
28148
  padding: var(--space-sm);
26362
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
28149
+ box-shadow: var(--shadow-lg);
26363
28150
  z-index: 100;
26364
28151
  display: grid;
26365
28152
  grid-template-columns: repeat(6, 1fr);
@@ -26466,6 +28253,42 @@ MethodCard = __decorateClass([
26466
28253
  // node_modules/lit-html/directives/if-defined.js
26467
28254
  var o8 = (o9) => o9 ?? A;
26468
28255
 
28256
+ // src/auto-ui/frontend/utils/format-label.ts
28257
+ function formatLabel(name2) {
28258
+ if (!name2) return name2;
28259
+ let cleaned = name2.replace(/^_+/, "");
28260
+ let spaced = cleaned.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2");
28261
+ spaced = spaced.replace(/[_-]+/g, " ");
28262
+ let result = spaced.split(/\s+/).map((word) => {
28263
+ if (!word) return "";
28264
+ const upper = word.toUpperCase();
28265
+ if ([
28266
+ "AI",
28267
+ "ID",
28268
+ "IDS",
28269
+ "URL",
28270
+ "API",
28271
+ "UI",
28272
+ "IP",
28273
+ "HTTP",
28274
+ "HTTPS",
28275
+ "JSON",
28276
+ "XML",
28277
+ "HTML",
28278
+ "CSS",
28279
+ "SQL",
28280
+ "MCP",
28281
+ "SSH",
28282
+ "CLI"
28283
+ ].includes(upper)) {
28284
+ if (upper === "IDS") return "IDs";
28285
+ return upper;
28286
+ }
28287
+ return word.charAt(0).toUpperCase() + word.slice(1);
28288
+ }).join(" ");
28289
+ return result;
28290
+ }
28291
+
26469
28292
  // src/auto-ui/frontend/components/invoke-form.ts
26470
28293
  function capitalizeEnumValue(val) {
26471
28294
  return formatLabel(val);
@@ -26492,6 +28315,30 @@ function singularize(word) {
26492
28315
  if (lower.endsWith("s") && !lower.endsWith("ss")) return word.slice(0, -1);
26493
28316
  return word;
26494
28317
  }
28318
+ function isValidCharForFormat(char, format, position) {
28319
+ if (!format) return true;
28320
+ switch (format.toLowerCase()) {
28321
+ case "email":
28322
+ return /[a-zA-Z0-9@.\-_+]/.test(char);
28323
+ case "url":
28324
+ case "uri":
28325
+ return /[a-zA-Z0-9:\/\-_.?=&#%@+~;,!]/.test(char);
28326
+ case "uuid":
28327
+ return /[0-9a-fA-F\-]/.test(char);
28328
+ case "ipv4":
28329
+ return /[0-9.]/.test(char);
28330
+ case "ipv6":
28331
+ return /[0-9a-fA-F:]/.test(char);
28332
+ case "slug":
28333
+ return /[a-z0-9\-]/.test(char);
28334
+ case "hex":
28335
+ return position === 0 && char === "#" ? true : /[0-9a-fA-F]/.test(char);
28336
+ case "phone":
28337
+ return /[0-9+\-() ]/.test(char);
28338
+ default:
28339
+ return true;
28340
+ }
28341
+ }
26495
28342
  var InvokeForm = class extends i4 {
26496
28343
  constructor() {
26497
28344
  super(...arguments);
@@ -26746,9 +28593,7 @@ var InvokeForm = class extends i4 {
26746
28593
  this._handleChange(key, arr.length > 0 ? arr : raw);
26747
28594
  }}
26748
28595
  />
26749
- <div class="hint" style="font-size:0.75rem;color:var(--t-muted);margin-top:2px;">
26750
- Comma-separated values
26751
- </div>
28596
+ <div class="hint">Comma-separated values</div>
26752
28597
  </div>
26753
28598
  `;
26754
28599
  }
@@ -26799,9 +28644,12 @@ var InvokeForm = class extends i4 {
26799
28644
  min="${min}"
26800
28645
  max="${max}"
26801
28646
  step="${step2}"
28647
+ style="${this._sliderFillStyle(Number(currentValue2), min, max)}"
26802
28648
  .value=${String(currentValue2)}
26803
28649
  @input=${(e8) => {
26804
- const v2 = Number(e8.target.value);
28650
+ const el2 = e8.target;
28651
+ const v2 = this._sanitizeSliderValue(Number(el2.value), min, max, step2);
28652
+ el2.style.cssText = this._sliderFillStyle(v2, min, max);
26805
28653
  this._handleChange(key, v2);
26806
28654
  }}
26807
28655
  />
@@ -26814,7 +28662,14 @@ var InvokeForm = class extends i4 {
26814
28662
  step="${step2}"
26815
28663
  .value=${displayValue2}
26816
28664
  @input=${(e8) => {
26817
- const v2 = Number(e8.target.value);
28665
+ const raw = e8.target.value;
28666
+ if (raw === "" || raw === "-") return;
28667
+ this._handleChange(key, Number(raw));
28668
+ }}
28669
+ @change=${(e8) => {
28670
+ const el2 = e8.target;
28671
+ const v2 = this._sanitizeSliderValue(Number(el2.value), min, max, step2);
28672
+ el2.value = String(v2);
26818
28673
  this._handleChange(key, v2);
26819
28674
  }}
26820
28675
  />
@@ -26833,14 +28688,31 @@ var InvokeForm = class extends i4 {
26833
28688
  return b2`
26834
28689
  <input
26835
28690
  id=${o8(inputId)}
26836
- type="number"
26837
- class="${errorClass}"
28691
+ type="text"
28692
+ class="number-input-clean ${errorClass}"
28693
+ inputmode=${isInteger ? "numeric" : "decimal"}
26838
28694
  ${hasMin ? `min="${schema.minimum}"` : ""}
26839
- step="${step}"
26840
28695
  placeholder="${defaultVal2}"
26841
28696
  .value=${displayValue}
28697
+ @keypress=${(e8) => {
28698
+ const char = e8.key;
28699
+ const isDigit = /\d/.test(char);
28700
+ const isMinus = char === "-";
28701
+ const isDecimal = char === "." && !isInteger;
28702
+ if (!isDigit && !isMinus && !isDecimal) {
28703
+ e8.preventDefault();
28704
+ }
28705
+ }}
26842
28706
  @input=${(e8) => {
26843
- const v2 = Number(e8.target.value);
28707
+ let text = e8.target.value;
28708
+ if (isInteger) {
28709
+ const cleaned = text.replace(/[^\d-]/g, "");
28710
+ text = cleaned.match(/-.*-/) ? cleaned.replace(/-/g, "").replace(/^/, "-") : cleaned;
28711
+ }
28712
+ let v2 = text === "" || text === "-" ? defaultVal2 : Number(text);
28713
+ if (isNaN(v2)) {
28714
+ v2 = defaultVal2;
28715
+ }
26844
28716
  this._handleChange(key, v2);
26845
28717
  }}
26846
28718
  />
@@ -26928,6 +28800,8 @@ var InvokeForm = class extends i4 {
26928
28800
  }
26929
28801
  const defaultVal = schema.default;
26930
28802
  const placeholder = defaultVal != null ? String(defaultVal) : "";
28803
+ const format = schema.format;
28804
+ const pattern = schema.pattern;
26931
28805
  return b2`
26932
28806
  <input
26933
28807
  id=${o8(inputId)}
@@ -26935,6 +28809,23 @@ var InvokeForm = class extends i4 {
26935
28809
  class="${errorClass}"
26936
28810
  placeholder="${placeholder}"
26937
28811
  .value=${this._values[key] || ""}
28812
+ @keypress=${(e8) => {
28813
+ if (!isValidCharForFormat(e8.key, format)) {
28814
+ e8.preventDefault();
28815
+ return;
28816
+ }
28817
+ if (pattern) {
28818
+ try {
28819
+ const currentValue = e8.target.value;
28820
+ const newValue = currentValue + e8.key;
28821
+ const regex = new RegExp(pattern);
28822
+ if (!regex.test(newValue)) {
28823
+ e8.preventDefault();
28824
+ }
28825
+ } catch (err) {
28826
+ }
28827
+ }
28828
+ }}
26938
28829
  @input=${(e8) => this._handleChange(key, e8.target.value)}
26939
28830
  />
26940
28831
  `;
@@ -27059,7 +28950,11 @@ var InvokeForm = class extends i4 {
27059
28950
  */
27060
28951
  _cleanDescription(desc, schema) {
27061
28952
  if (!desc) return desc;
27062
- let cleaned = desc.replace(
28953
+ let cleaned = desc;
28954
+ const lines = cleaned.split("\n").filter((line) => !line.trim().startsWith("@"));
28955
+ cleaned = lines.join("\n").trim();
28956
+ cleaned = cleaned.replace(/\s*@\w+[\s\S]*?(?=[.!?\n]|$)/g, "").trim();
28957
+ cleaned = cleaned.replace(
27063
28958
  /['"][\w-]+['"]\s*(?:\([^)]*\)\s*)?(?:\|\s*['"][\w-]+['"]\s*(?:\([^)]*\)\s*)?)+/g,
27064
28959
  ""
27065
28960
  );
@@ -27095,6 +28990,23 @@ var InvokeForm = class extends i4 {
27095
28990
  this._savePersistedValues();
27096
28991
  }
27097
28992
  }
28993
+ _sliderFillStyle(value, min, max) {
28994
+ const pct = max > min ? (value - min) / (max - min) * 100 : 0;
28995
+ return `background: linear-gradient(to right, var(--accent-primary) ${pct}%, var(--border-glass) ${pct}%)`;
28996
+ }
28997
+ /** Clamp to [min, max] and round to nearest step (integer-safe). */
28998
+ _sanitizeSliderValue(raw, min, max, step) {
28999
+ let v2 = Number.isFinite(raw) ? raw : min;
29000
+ v2 = Math.min(max, Math.max(min, v2));
29001
+ if (step > 0) {
29002
+ v2 = min + Math.round((v2 - min) / step) * step;
29003
+ if (v2 > max) v2 = max;
29004
+ }
29005
+ if (Number.isInteger(step) && step >= 1) {
29006
+ v2 = Math.round(v2);
29007
+ }
29008
+ return v2;
29009
+ }
27098
29010
  _handleChange(key, value) {
27099
29011
  this._values = { ...this._values, [key]: value };
27100
29012
  if (this.rememberValues) {
@@ -27102,7 +29014,7 @@ var InvokeForm = class extends i4 {
27102
29014
  }
27103
29015
  }
27104
29016
  _buildCliCommand() {
27105
- const parts = ["photon", "cli", this.photonName, this.methodName];
29017
+ const parts = [this.photonName, this.methodName];
27106
29018
  for (const [key, value] of Object.entries(this._values)) {
27107
29019
  if (value === void 0 || value === null || value === "") continue;
27108
29020
  const strVal = typeof value === "object" ? JSON.stringify(value) : String(value);
@@ -27226,7 +29138,9 @@ var InvokeForm = class extends i4 {
27226
29138
  >
27227
29139
  <option value="">Select...</option>
27228
29140
  ${schema.enum.map(
27229
- (opt) => b2` <option value=${opt} ?selected=${opt === value}>${opt}</option> `
29141
+ (opt) => b2`
29142
+ <option value=${opt} ?selected=${opt === value}>${capitalizeEnumValue(opt)}</option>
29143
+ `
27230
29144
  )}
27231
29145
  </select>
27232
29146
  `;
@@ -27262,8 +29176,14 @@ var InvokeForm = class extends i4 {
27262
29176
  min="${min}"
27263
29177
  max="${max}"
27264
29178
  step="${step2}"
29179
+ style="${this._sliderFillStyle(Number(currentVal2), min, max)}"
27265
29180
  .value=${String(currentVal2)}
27266
- @input=${(e8) => onChange(propKey, Number(e8.target.value))}
29181
+ @input=${(e8) => {
29182
+ const el2 = e8.target;
29183
+ const v2 = this._sanitizeSliderValue(Number(el2.value), min, max, step2);
29184
+ el2.style.cssText = this._sliderFillStyle(v2, min, max);
29185
+ onChange(propKey, v2);
29186
+ }}
27267
29187
  />
27268
29188
  <input
27269
29189
  type="number"
@@ -27272,7 +29192,17 @@ var InvokeForm = class extends i4 {
27272
29192
  max="${max}"
27273
29193
  step="${step2}"
27274
29194
  .value=${displayVal2}
27275
- @input=${(e8) => onChange(propKey, Number(e8.target.value))}
29195
+ @input=${(e8) => {
29196
+ const raw = e8.target.value;
29197
+ if (raw === "" || raw === "-") return;
29198
+ onChange(propKey, Number(raw));
29199
+ }}
29200
+ @change=${(e8) => {
29201
+ const el2 = e8.target;
29202
+ const v2 = this._sanitizeSliderValue(Number(el2.value), min, max, step2);
29203
+ el2.value = String(v2);
29204
+ onChange(propKey, v2);
29205
+ }}
27276
29206
  />
27277
29207
  </div>
27278
29208
  <div class="range-labels">
@@ -27439,18 +29369,23 @@ var InvokeForm = class extends i4 {
27439
29369
  >
27440
29370
  <option value="">Select...</option>
27441
29371
  ${schema.enum.map(
27442
- (opt) => b2` <option value=${opt} ?selected=${opt === value}>${opt}</option> `
29372
+ (opt) => b2`
29373
+ <option value=${opt} ?selected=${opt === value}>${capitalizeEnumValue(opt)}</option>
29374
+ `
27443
29375
  )}
27444
29376
  </select>
27445
29377
  `;
27446
29378
  }
27447
29379
  if (schema.type === "boolean") {
27448
29380
  return b2`
27449
- <input
27450
- type="checkbox"
27451
- .checked=${!!value}
27452
- @change=${(e8) => handleNestedChange(e8.target.checked)}
27453
- />
29381
+ <label class="switch">
29382
+ <input
29383
+ type="checkbox"
29384
+ .checked=${!!value}
29385
+ @change=${(e8) => handleNestedChange(e8.target.checked)}
29386
+ />
29387
+ <span class="slider"></span>
29388
+ </label>
27454
29389
  `;
27455
29390
  }
27456
29391
  if (schema.type === "number" || schema.type === "integer") {
@@ -27472,8 +29407,14 @@ var InvokeForm = class extends i4 {
27472
29407
  min="${min}"
27473
29408
  max="${max}"
27474
29409
  step="${step2}"
29410
+ style="${this._sliderFillStyle(Number(currentVal2), min, max)}"
27475
29411
  .value=${String(currentVal2)}
27476
- @input=${(e8) => handleNestedChange(Number(e8.target.value))}
29412
+ @input=${(e8) => {
29413
+ const el2 = e8.target;
29414
+ const v2 = this._sanitizeSliderValue(Number(el2.value), min, max, step2);
29415
+ el2.style.cssText = this._sliderFillStyle(v2, min, max);
29416
+ handleNestedChange(v2);
29417
+ }}
27477
29418
  />
27478
29419
  <input
27479
29420
  type="number"
@@ -27482,7 +29423,17 @@ var InvokeForm = class extends i4 {
27482
29423
  max="${max}"
27483
29424
  step="${step2}"
27484
29425
  .value=${displayVal2}
27485
- @input=${(e8) => handleNestedChange(Number(e8.target.value))}
29426
+ @input=${(e8) => {
29427
+ const raw = e8.target.value;
29428
+ if (raw === "" || raw === "-") return;
29429
+ handleNestedChange(Number(raw));
29430
+ }}
29431
+ @change=${(e8) => {
29432
+ const el2 = e8.target;
29433
+ const v2 = this._sanitizeSliderValue(Number(el2.value), min, max, step2);
29434
+ el2.value = String(v2);
29435
+ handleNestedChange(v2);
29436
+ }}
27486
29437
  />
27487
29438
  </div>
27488
29439
  <div class="range-labels">
@@ -27833,6 +29784,7 @@ InvokeForm.styles = [
27833
29784
  font-size: var(--text-xs);
27834
29785
  padding: 2px 6px;
27835
29786
  border-radius: var(--radius-xs);
29787
+ transition: background 0.15s ease;
27836
29788
  }
27837
29789
 
27838
29790
  .array-item-remove:hover {
@@ -27909,11 +29861,17 @@ InvokeForm.styles = [
27909
29861
  margin-top: var(--space-xs);
27910
29862
  }
27911
29863
 
29864
+ .hint {
29865
+ font-size: var(--text-xs);
29866
+ color: var(--t-muted);
29867
+ margin-top: 2px;
29868
+ }
29869
+
27912
29870
  /* Slider-First Numeric Input */
27913
29871
  .slider-group {
27914
29872
  display: flex;
27915
29873
  flex-direction: column;
27916
- gap: 6px;
29874
+ gap: 4px;
27917
29875
  }
27918
29876
 
27919
29877
  .slider-row {
@@ -27924,56 +29882,140 @@ InvokeForm.styles = [
27924
29882
 
27925
29883
  .slider-row input[type='range'] {
27926
29884
  flex: 1;
27927
- height: 6px;
27928
- background: rgba(255, 255, 255, 0.12);
27929
- border: 1px solid rgba(255, 255, 255, 0.08);
27930
- border-radius: 3px;
29885
+ height: 4px;
29886
+ border-radius: var(--radius-full, 9999px);
27931
29887
  -webkit-appearance: none;
27932
29888
  cursor: pointer;
27933
29889
  margin: 8px 0;
29890
+ border: none;
29891
+ outline: none;
29892
+ /* Default unfilled track — overridden by inline style for fill effect */
29893
+ background: var(--border-glass);
27934
29894
  }
27935
29895
 
27936
29896
  .slider-row input[type='range']::-webkit-slider-thumb {
27937
29897
  -webkit-appearance: none;
27938
- width: 20px;
27939
- height: 20px;
29898
+ width: 16px;
29899
+ height: 16px;
27940
29900
  background: var(--accent-primary);
29901
+ border: 2px solid var(--bg-panel, var(--bg-glass));
27941
29902
  border-radius: 50%;
27942
29903
  cursor: pointer;
27943
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
29904
+ box-shadow:
29905
+ 0 0 0 2px var(--accent-primary),
29906
+ 0 1px 4px rgba(0, 0, 0, 0.3);
29907
+ transition:
29908
+ box-shadow 0.15s ease,
29909
+ transform 0.15s ease;
29910
+ }
29911
+
29912
+ .slider-row input[type='range']::-webkit-slider-thumb:hover {
29913
+ box-shadow:
29914
+ 0 0 0 3px var(--accent-primary),
29915
+ 0 0 8px var(--glow-primary);
29916
+ transform: scale(1.1);
29917
+ }
29918
+
29919
+ .slider-row input[type='range']:active::-webkit-slider-thumb {
29920
+ transform: scale(0.95);
27944
29921
  }
27945
29922
 
27946
29923
  .slider-row input[type='range']::-moz-range-track {
27947
- height: 6px;
27948
- background: rgba(255, 255, 255, 0.12);
27949
- border-radius: 3px;
27950
- border: 1px solid rgba(255, 255, 255, 0.08);
29924
+ height: 4px;
29925
+ background: var(--border-glass);
29926
+ border-radius: var(--radius-full, 9999px);
29927
+ border: none;
29928
+ }
29929
+
29930
+ .slider-row input[type='range']::-moz-range-progress {
29931
+ height: 4px;
29932
+ background: var(--accent-primary);
29933
+ border-radius: var(--radius-full, 9999px);
27951
29934
  }
27952
29935
 
27953
29936
  .slider-row input[type='range']::-moz-range-thumb {
27954
- width: 20px;
27955
- height: 20px;
29937
+ width: 12px;
29938
+ height: 12px;
27956
29939
  background: var(--accent-primary);
29940
+ border: 2px solid var(--bg-panel, var(--bg-glass));
27957
29941
  border-radius: 50%;
27958
29942
  cursor: pointer;
27959
- border: none;
27960
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
29943
+ box-shadow:
29944
+ 0 0 0 2px var(--accent-primary),
29945
+ 0 1px 4px rgba(0, 0, 0, 0.3);
29946
+ transition:
29947
+ box-shadow 0.15s ease,
29948
+ transform 0.15s ease;
27961
29949
  }
27962
29950
 
27963
- .slider-value {
27964
- min-width: 60px;
29951
+ .slider-row input[type='range']::-moz-range-thumb:hover {
29952
+ box-shadow:
29953
+ 0 0 0 3px var(--accent-primary),
29954
+ 0 0 8px var(--glow-primary);
29955
+ }
29956
+
29957
+ .slider-number-input {
29958
+ width: 64px;
27965
29959
  text-align: center;
29960
+ font-size: var(--text-sm);
29961
+ font-weight: 500;
29962
+ font-variant-numeric: tabular-nums;
29963
+ padding: 4px 6px;
29964
+ background: var(--bg-glass);
29965
+ border: 1px solid var(--border-glass) !important;
29966
+ border-radius: var(--radius-sm);
29967
+ color: var(--t-primary);
29968
+ -moz-appearance: textfield;
29969
+ transition:
29970
+ border-color 0.15s ease,
29971
+ box-shadow 0.15s ease;
29972
+ }
29973
+
29974
+ .slider-number-input::-webkit-outer-spin-button,
29975
+ .slider-number-input::-webkit-inner-spin-button {
29976
+ -webkit-appearance: none;
29977
+ margin: 0;
29978
+ }
29979
+
29980
+ .slider-number-input:focus-visible {
29981
+ outline: none;
29982
+ border-color: var(--accent-primary) !important;
29983
+ box-shadow: 0 0 0 2px var(--glow-primary);
29984
+ }
29985
+
29986
+ /* Clean numeric input — no spinner, mouse wheel support */
29987
+ .number-input-clean {
29988
+ width: 100%;
29989
+ padding: 8px 12px;
27966
29990
  font-size: var(--text-md);
27967
- font-weight: 600;
29991
+ background: var(--bg-glass);
29992
+ border: 1px solid var(--border-glass);
29993
+ border-radius: var(--radius-sm);
27968
29994
  color: var(--t-primary);
27969
- font-variant-numeric: tabular-nums;
29995
+ appearance: none;
29996
+ -moz-appearance: textfield;
29997
+ -webkit-appearance: none;
29998
+ transition:
29999
+ border-color 0.15s ease,
30000
+ box-shadow 0.15s ease;
27970
30001
  }
27971
30002
 
27972
- .slider-number-input {
27973
- width: 80px;
27974
- text-align: right;
27975
- font-size: var(--text-sm);
27976
- padding: 4px 8px;
30003
+ .number-input-clean::-webkit-outer-spin-button,
30004
+ .number-input-clean::-webkit-inner-spin-button {
30005
+ -webkit-appearance: none;
30006
+ appearance: none;
30007
+ display: none;
30008
+ margin: 0;
30009
+ }
30010
+
30011
+ .number-input-clean:hover {
30012
+ border-color: var(--accent-primary);
30013
+ }
30014
+
30015
+ .number-input-clean:focus-visible {
30016
+ outline: none;
30017
+ border-color: var(--accent-primary);
30018
+ box-shadow: 0 0 0 2px var(--glow-primary);
27977
30019
  }
27978
30020
 
27979
30021
  .range-labels {
@@ -28022,6 +30064,15 @@ InvokeForm.styles = [
28022
30064
  flex-shrink: 0;
28023
30065
  }
28024
30066
 
30067
+ select {
30068
+ appearance: none;
30069
+ -webkit-appearance: none;
30070
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23888'/%3E%3C/svg%3E");
30071
+ background-repeat: no-repeat;
30072
+ background-position: right 12px center;
30073
+ padding-right: 32px;
30074
+ }
30075
+
28025
30076
  /* ===== Responsive Design ===== */
28026
30077
  @media (max-width: 768px) {
28027
30078
  .form-container {
@@ -28224,7 +30275,7 @@ ActivityLog.styles = [
28224
30275
  font-size: var(--text-sm);
28225
30276
  text-transform: uppercase;
28226
30277
  letter-spacing: 0.1em;
28227
- color: var(--t-secondary); /* Brighter */
30278
+ color: var(--t-primary);
28228
30279
  font-weight: 600;
28229
30280
  }
28230
30281
 
@@ -28239,7 +30290,7 @@ ActivityLog.styles = [
28239
30290
  }
28240
30291
 
28241
30292
  .clear-btn:hover {
28242
- background: hsla(220, 10%, 80%, 0.1);
30293
+ background: var(--bg-glass);
28243
30294
  color: var(--t-primary);
28244
30295
  }
28245
30296
 
@@ -28258,14 +30309,14 @@ ActivityLog.styles = [
28258
30309
  }
28259
30310
 
28260
30311
  .filter-btn:hover {
28261
- background: hsla(220, 10%, 80%, 0.1);
30312
+ background: var(--bg-glass);
28262
30313
  color: var(--t-primary);
28263
30314
  }
28264
30315
 
28265
30316
  .filter-btn.active {
28266
30317
  border-color: var(--accent-secondary);
28267
30318
  color: var(--accent-secondary);
28268
- background: hsla(210, 80%, 65%, 0.08);
30319
+ background: var(--bg-glass);
28269
30320
  }
28270
30321
 
28271
30322
  .visually-hidden {
@@ -28325,7 +30376,7 @@ ActivityLog.styles = [
28325
30376
  display: inline-block;
28326
30377
  font-size: var(--text-xs);
28327
30378
  color: var(--t-muted);
28328
- background: hsla(220, 10%, 80%, 0.08);
30379
+ background: var(--bg-glass);
28329
30380
  padding: 1px 6px;
28330
30381
  border-radius: var(--radius-full);
28331
30382
  margin-left: var(--space-sm);
@@ -28484,6 +30535,8 @@ var ResultViewer = class extends i4 {
28484
30535
  this._previousResult = null;
28485
30536
  // Recency heat: track when items were last added/updated
28486
30537
  this._itemHeatTimestamps = /* @__PURE__ */ new Map();
30538
+ // Audit trail expansion state: track which items have expanded audit trails
30539
+ this._expandedAuditTrails = /* @__PURE__ */ new Set();
28487
30540
  // The detected ID field for the current result (shared across diff, animation, warmth)
28488
30541
  this._activeIdField = "id";
28489
30542
  // Chart.js instance for reactive updates
@@ -28786,19 +30839,32 @@ var ResultViewer = class extends i4 {
28786
30839
  /**
28787
30840
  * Get the warmth class based on recency heat.
28788
30841
  * Reads timestamp from item data first (survives refresh), falls back to in-memory map.
30842
+ * Prioritizes __meta timestamps (most recent), then standard timestamp fields.
28789
30843
  */
28790
30844
  _getItemWarmthClass(item) {
28791
30845
  const idField = this._activeIdField;
28792
30846
  let timestamp;
28793
30847
  if (item && typeof item === "object") {
28794
30848
  const rec = item;
28795
- for (const field of ResultViewer._TIMESTAMP_FIELDS) {
28796
- const val = rec[field];
28797
- if (val !== void 0 && val !== null) {
28798
- const parsed = typeof val === "number" ? val : new Date(typeof val === "string" ? val : String(val)).getTime();
28799
- if (!isNaN(parsed)) {
28800
- timestamp = parsed;
28801
- break;
30849
+ const meta4 = rec.__meta;
30850
+ if (meta4 && typeof meta4 === "object") {
30851
+ if (meta4.modifiedAt) {
30852
+ const parsed = new Date(meta4.modifiedAt).getTime();
30853
+ if (!isNaN(parsed)) timestamp = parsed;
30854
+ } else if (meta4.createdAt) {
30855
+ const parsed = new Date(meta4.createdAt).getTime();
30856
+ if (!isNaN(parsed)) timestamp = parsed;
30857
+ }
30858
+ }
30859
+ if (timestamp === void 0) {
30860
+ for (const field of ResultViewer._TIMESTAMP_FIELDS) {
30861
+ const val = rec[field];
30862
+ if (val !== void 0 && val !== null) {
30863
+ const parsed = typeof val === "number" ? val : new Date(typeof val === "string" ? val : String(val)).getTime();
30864
+ if (!isNaN(parsed)) {
30865
+ timestamp = parsed;
30866
+ break;
30867
+ }
28802
30868
  }
28803
30869
  }
28804
30870
  }
@@ -29307,7 +31373,8 @@ var ResultViewer = class extends i4 {
29307
31373
  "tabs",
29308
31374
  "accordion",
29309
31375
  "stack",
29310
- "columns"
31376
+ "columns",
31377
+ "qr"
29311
31378
  ].includes(format)) {
29312
31379
  return format;
29313
31380
  }
@@ -29474,6 +31541,8 @@ var ResultViewer = class extends i4 {
29474
31541
  return this._renderStack(filteredData);
29475
31542
  case "columns":
29476
31543
  return this._renderColumns(filteredData);
31544
+ case "qr":
31545
+ return this._renderQR(filteredData);
29477
31546
  case "mermaid":
29478
31547
  return this._renderMermaid(filteredData);
29479
31548
  case "json":
@@ -29716,6 +31785,136 @@ var ResultViewer = class extends i4 {
29716
31785
  </div>
29717
31786
  `
29718
31787
  )}
31788
+ ${this._renderAuditTrail(data)}
31789
+ `;
31790
+ }
31791
+ /**
31792
+ * Render audit trail from __meta object if present
31793
+ */
31794
+ _renderAuditTrail(data) {
31795
+ if (!data || typeof data !== "object") return "";
31796
+ const meta4 = data.__meta;
31797
+ if (!meta4 || typeof meta4 !== "object") return "";
31798
+ const idField = this._activeIdField;
31799
+ const itemId = data[idField] ? String(data[idField]) : Math.random().toString(36);
31800
+ const auditKey = `audit-${itemId}`;
31801
+ const isExpanded = this._expandedAuditTrails.has(auditKey);
31802
+ const formatTime = (isoString) => {
31803
+ if (!isoString) return "N/A";
31804
+ const date4 = new Date(isoString);
31805
+ return date4.toLocaleString();
31806
+ };
31807
+ const createdAt = formatTime(meta4.createdAt);
31808
+ const modifiedAt = meta4.modifiedAt ? formatTime(meta4.modifiedAt) : null;
31809
+ return b2`
31810
+ <div
31811
+ style="margin-top: var(--space-md); border-top: 1px solid var(--border-glass); padding-top: var(--space-md);"
31812
+ >
31813
+ <details
31814
+ ?open="${isExpanded}"
31815
+ @toggle="${(e8) => {
31816
+ const detail = e8.target;
31817
+ if (detail.open) {
31818
+ this._expandedAuditTrails.add(auditKey);
31819
+ } else {
31820
+ this._expandedAuditTrails.delete(auditKey);
31821
+ }
31822
+ }}"
31823
+ >
31824
+ <summary
31825
+ style="cursor: pointer; font-weight: 500; display: flex; align-items: center; gap: var(--space-sm);"
31826
+ >
31827
+ <span
31828
+ style="display: inline-block; width: 0.5em; height: 0.5em; border-radius: 50%; background: var(--text-secondary); margin-right: var(--space-xs);"
31829
+ ></span>
31830
+ Audit Trail
31831
+ ${meta4.modifications?.length ? b2`<span style="font-size: 0.85em; color: var(--text-secondary);"
31832
+ >(${meta4.modifications.length} changes)</span
31833
+ >` : ""}
31834
+ </summary>
31835
+
31836
+ <div
31837
+ style="margin-top: var(--space-md); padding: var(--space-sm) var(--space-md); background: var(--bg-secondary); border-radius: var(--radius-sm);"
31838
+ >
31839
+ <table style="width: 100%; font-size: 0.9em;">
31840
+ <tbody>
31841
+ <tr style="border-bottom: 1px solid var(--border-subtle);">
31842
+ <td
31843
+ style="padding: var(--space-xs); color: var(--text-secondary); font-weight: 500;"
31844
+ >
31845
+ Created
31846
+ </td>
31847
+ <td style="padding: var(--space-xs);">${createdAt}</td>
31848
+ ${meta4.createdBy ? b2`<td
31849
+ style="padding: var(--space-xs); color: var(--text-secondary); font-size: 0.85em;"
31850
+ >
31851
+ (by: ${meta4.createdBy})
31852
+ </td>` : ""}
31853
+ </tr>
31854
+ ${modifiedAt ? b2`
31855
+ <tr style="border-bottom: 1px solid var(--border-subtle);">
31856
+ <td
31857
+ style="padding: var(--space-xs); color: var(--text-secondary); font-weight: 500;"
31858
+ >
31859
+ Modified
31860
+ </td>
31861
+ <td style="padding: var(--space-xs);">${modifiedAt}</td>
31862
+ ${meta4.modifiedBy ? b2`<td
31863
+ style="padding: var(--space-xs); color: var(--text-secondary); font-size: 0.85em;"
31864
+ >
31865
+ (by: ${meta4.modifiedBy})
31866
+ </td>` : ""}
31867
+ </tr>
31868
+ ` : ""}
31869
+ </tbody>
31870
+ </table>
31871
+
31872
+ ${meta4.modifications && Array.isArray(meta4.modifications) && meta4.modifications.length > 0 ? b2`
31873
+ <div style="margin-top: var(--space-md);">
31874
+ <div
31875
+ style="font-weight: 500; margin-bottom: var(--space-sm); color: var(--text-secondary);"
31876
+ >
31877
+ Changes
31878
+ </div>
31879
+ <ul style="list-style: none; padding: 0; margin: 0;">
31880
+ ${meta4.modifications.map(
31881
+ (mod, idx) => b2`
31882
+ <li
31883
+ key="${idx}"
31884
+ style="padding: var(--space-xs) var(--space-sm); margin-bottom: var(--space-xs); background: var(--bg-hover); border-left: 3px solid var(--primary); border-radius: 2px; font-size: 0.9em;"
31885
+ >
31886
+ <div style="margin-bottom: 2px;">
31887
+ <span style="font-weight: 500; color: var(--text-primary);"
31888
+ >${mod.field}</span
31889
+ >
31890
+ <span style="color: var(--text-secondary); margin: 0 var(--space-xs);"
31891
+ >→</span
31892
+ >
31893
+ <span style="color: var(--text-secondary); font-size: 0.85em;"
31894
+ >${formatTime(mod.timestamp || (/* @__PURE__ */ new Date()).toISOString())}</span
31895
+ >
31896
+ </div>
31897
+ <div
31898
+ style="font-family: monospace; font-size: 0.85em; color: var(--text-secondary); margin-left: var(--space-sm);"
31899
+ >
31900
+ <span style="color: #d87070;">${JSON.stringify(mod.oldValue)}</span>
31901
+ <span style="margin: 0 4px;">→</span>
31902
+ <span style="color: #7cb342;">${JSON.stringify(mod.newValue)}</span>
31903
+ </div>
31904
+ ${mod.modifiedBy ? b2`<div
31905
+ style="font-size: 0.8em; color: var(--text-secondary); margin-top: 2px;"
31906
+ >
31907
+ <em>by: ${mod.modifiedBy}</em>
31908
+ </div>` : ""}
31909
+ </li>
31910
+ `
31911
+ )}
31912
+ </ul>
31913
+ </div>
31914
+ ` : ""}
31915
+ </div>
31916
+ </details>
31917
+ </div>
29719
31918
  `;
29720
31919
  }
29721
31920
  /** Returns true for arrays of objects or large nested objects that deserve their own section */
@@ -30174,6 +32373,163 @@ ${code}</pre>`;
30174
32373
  style="position: relative; background: ${bgColor}; border-radius: var(--radius-sm); padding: 16px; margin: 16px 0; min-height: 120px;"
30175
32374
  ></div>`;
30176
32375
  }
32376
+ _renderQR(data) {
32377
+ const text = typeof data === "object" && data !== null ? String(data.url || data.link || data.value || JSON.stringify(data)) : String(data);
32378
+ const isUrl = /^https?:\/\//i.test(text);
32379
+ const isEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(text);
32380
+ const isPhone = /^[+]?[\d\s\-().]{7,}$/.test(text.trim());
32381
+ let href = "";
32382
+ let linkLabel = text;
32383
+ if (isUrl) {
32384
+ href = text;
32385
+ try {
32386
+ linkLabel = new URL(text).hostname + new URL(text).pathname;
32387
+ } catch {
32388
+ }
32389
+ } else if (isEmail) {
32390
+ href = `mailto:${text}`;
32391
+ } else if (isPhone) {
32392
+ href = `tel:${text.replace(/[\s\-().]/g, "")}`;
32393
+ }
32394
+ setTimeout(() => {
32395
+ if (this._qrContainer) {
32396
+ this._qrContainer.innerHTML = "";
32397
+ try {
32398
+ const containerWidth = this._qrContainer.clientWidth;
32399
+ const qrSize = Math.max(200, Math.min(containerWidth - 48, 400));
32400
+ new window.QRCode(this._qrContainer, {
32401
+ text,
32402
+ width: qrSize,
32403
+ height: qrSize,
32404
+ correctLevel: window.QRCode?.CorrectLevel?.H,
32405
+ colorDark: "#000000",
32406
+ colorLight: "#ffffff"
32407
+ });
32408
+ } catch (error2) {
32409
+ console.error("Failed to generate QR code:", error2);
32410
+ }
32411
+ }
32412
+ }, 0);
32413
+ const message = typeof data === "object" && data !== null ? data.message : void 0;
32414
+ const extraFields = [];
32415
+ if (typeof data === "object" && data !== null) {
32416
+ for (const [k3, v2] of Object.entries(data)) {
32417
+ if (["url", "link", "value", "message", "port"].includes(k3) || v2 == null || typeof v2 === "object")
32418
+ continue;
32419
+ extraFields.push({ label: k3, value: `${v2}` });
32420
+ }
32421
+ }
32422
+ return b2`<div
32423
+ style="
32424
+ display: flex; flex-direction: column; align-items: center; gap: 0;
32425
+ padding: 0; border-radius: var(--radius-md);
32426
+ border: 1px solid var(--border-glass);
32427
+ background: var(--bg-subtle); overflow: hidden;
32428
+ width: 40%; min-width: 280px; max-width: 480px;
32429
+ margin: 16px auto;
32430
+ "
32431
+ >
32432
+ <div
32433
+ id="qr-container"
32434
+ style="
32435
+ width: 100%; padding: 24px;
32436
+ display: flex; justify-content: center; align-items: center;
32437
+ background: #ffffff; border-radius: var(--radius-md) var(--radius-md) 0 0;
32438
+ "
32439
+ ></div>
32440
+
32441
+ <div
32442
+ style="
32443
+ width: 100%; padding: 16px 20px;
32444
+ display: flex; flex-direction: column; gap: 10px;
32445
+ border-top: 1px solid var(--border-glass);
32446
+ "
32447
+ >
32448
+ ${message ? b2`<div
32449
+ style="
32450
+ font-size: 0.8rem; color: var(--t-secondary, #a0a0a0);
32451
+ text-align: center; font-weight: 500;
32452
+ "
32453
+ >
32454
+ ${message}
32455
+ </div>` : ""}
32456
+ ${href ? b2`<div
32457
+ style="display: flex; align-items: center; gap: 10px; justify-content: center;"
32458
+ >
32459
+ <a
32460
+ href="${href}"
32461
+ target="_blank"
32462
+ rel="noopener noreferrer"
32463
+ style="
32464
+ font-size: 0.85rem; color: var(--accent, #3b82f6);
32465
+ text-decoration: none; font-weight: 500;
32466
+ text-align: center; word-break: break-word;
32467
+ "
32468
+ @mouseenter=${(e8) => e8.target.style.textDecoration = "underline"}
32469
+ @mouseleave=${(e8) => e8.target.style.textDecoration = "none"}
32470
+ >${linkLabel}</a
32471
+ >
32472
+ <button
32473
+ title="Copy to clipboard"
32474
+ style="
32475
+ background: var(--bg-subtle, rgba(255,255,255,0.06));
32476
+ border: 1px solid var(--border-glass);
32477
+ border-radius: 6px; padding: 6px 10px; cursor: pointer;
32478
+ color: var(--t-muted); font-size: 0.85rem; flex-shrink: 0;
32479
+ transition: all 0.15s; line-height: 1;
32480
+ "
32481
+ @mouseenter=${(e8) => {
32482
+ e8.target.style.color = "var(--accent, #3b82f6)";
32483
+ e8.target.style.borderColor = "var(--accent, #3b82f6)";
32484
+ }}
32485
+ @mouseleave=${(e8) => {
32486
+ e8.target.style.color = "var(--t-muted)";
32487
+ e8.target.style.borderColor = "var(--border-glass)";
32488
+ }}
32489
+ @click=${(e8) => {
32490
+ void navigator.clipboard.writeText(text);
32491
+ const btn = e8.target;
32492
+ const orig = btn.textContent;
32493
+ btn.textContent = "\u2713 Copied";
32494
+ btn.style.color = "var(--accent, #3b82f6)";
32495
+ setTimeout(() => {
32496
+ btn.textContent = orig;
32497
+ btn.style.color = "var(--t-muted)";
32498
+ }, 1500);
32499
+ }}
32500
+ >
32501
+ Copy
32502
+ </button>
32503
+ </div>` : b2`<div
32504
+ style="
32505
+ font-size: 0.875rem; color: var(--t-muted);
32506
+ text-align: center; word-break: break-all;
32507
+ "
32508
+ >
32509
+ ${text}
32510
+ </div>`}
32511
+ ${extraFields.length > 0 ? b2`<div
32512
+ style="
32513
+ display: flex; flex-direction: column; gap: 6px;
32514
+ padding-top: 10px; border-top: 1px solid var(--border-glass);
32515
+ font-size: 0.8rem;
32516
+ "
32517
+ >
32518
+ ${extraFields.map(
32519
+ (f5) => b2`<div style="display: flex; justify-content: space-between; gap: 12px;">
32520
+ <span style="color: var(--t-muted); text-transform: capitalize;"
32521
+ >${f5.label}</span
32522
+ >
32523
+ <span
32524
+ style="color: var(--t-primary); font-family: var(--font-mono, monospace); word-break: break-all; text-align: right;"
32525
+ >${f5.value}</span
32526
+ >
32527
+ </div>`
32528
+ )}
32529
+ </div>` : ""}
32530
+ </div>
32531
+ </div>`;
32532
+ }
30177
32533
  _renderText(data) {
30178
32534
  const text = String(data);
30179
32535
  if (this._isMermaidString(text)) {
@@ -31433,7 +33789,7 @@ ResultViewer.styles = [
31433
33789
  }
31434
33790
 
31435
33791
  button:hover {
31436
- background: hsla(220, 10%, 80%, 0.1);
33792
+ background: var(--bg-glass);
31437
33793
  color: var(--t-primary);
31438
33794
  }
31439
33795
 
@@ -31584,7 +33940,7 @@ ResultViewer.styles = [
31584
33940
  }
31585
33941
 
31586
33942
  .smart-table tr:hover td {
31587
- background: hsla(220, 10%, 80%, 0.05);
33943
+ background: var(--bg-glass);
31588
33944
  }
31589
33945
 
31590
33946
  /* Key-Value Table (single object) */
@@ -32271,7 +34627,7 @@ ResultViewer.styles = [
32271
34627
  }
32272
34628
 
32273
34629
  .tree-item:hover {
32274
- background: hsla(220, 10%, 80%, 0.1);
34630
+ background: var(--bg-glass);
32275
34631
  }
32276
34632
 
32277
34633
  .tree-toggle {
@@ -32300,16 +34656,16 @@ ResultViewer.styles = [
32300
34656
  }
32301
34657
 
32302
34658
  .tree-value.string {
32303
- color: #a5d6ff;
34659
+ color: var(--syntax-string);
32304
34660
  }
32305
34661
  .tree-value.number {
32306
- color: #ff9e64;
34662
+ color: var(--syntax-number);
32307
34663
  }
32308
34664
  .tree-value.boolean {
32309
- color: #ff007c;
34665
+ color: var(--syntax-boolean);
32310
34666
  }
32311
34667
  .tree-value.null {
32312
- color: #79c0ff;
34668
+ color: var(--syntax-null);
32313
34669
  }
32314
34670
 
32315
34671
  .tree-type {
@@ -32571,9 +34927,9 @@ ResultViewer.styles = [
32571
34927
  }
32572
34928
 
32573
34929
  .expand-btn:hover {
32574
- background: var(--primary);
34930
+ background: var(--accent-primary);
32575
34931
  color: white;
32576
- border-color: var(--primary);
34932
+ border-color: var(--accent-primary);
32577
34933
  }
32578
34934
 
32579
34935
  /* Markdown items (array rendering with filter transitions) */
@@ -32811,13 +35167,13 @@ ResultViewer.styles = [
32811
35167
  }
32812
35168
 
32813
35169
  .metric-delta.up {
32814
- color: #16a34a;
32815
- background: rgba(22, 163, 74, 0.12);
35170
+ color: var(--color-success);
35171
+ background: var(--color-success-bg);
32816
35172
  }
32817
35173
 
32818
35174
  .metric-delta.down {
32819
- color: #dc2626;
32820
- background: rgba(220, 38, 38, 0.12);
35175
+ color: var(--color-error);
35176
+ background: var(--color-error-bg);
32821
35177
  }
32822
35178
 
32823
35179
  .metric-delta.neutral {
@@ -32952,7 +35308,7 @@ ResultViewer.styles = [
32952
35308
 
32953
35309
  .timeline-description {
32954
35310
  font-size: var(--text-md);
32955
- color: var(--t-secondary);
35311
+ color: var(--t-muted);
32956
35312
  margin-top: 2px;
32957
35313
  line-height: 1.4;
32958
35314
  }
@@ -33099,7 +35455,7 @@ ResultViewer.styles = [
33099
35455
  justify-content: space-between;
33100
35456
  padding: 4px 0;
33101
35457
  font-size: var(--text-md);
33102
- color: var(--t-secondary);
35458
+ color: var(--t-muted);
33103
35459
  }
33104
35460
 
33105
35461
  .cart-summary-row.total {
@@ -33377,6 +35733,9 @@ __decorateClass([
33377
35733
  __decorateClass([
33378
35734
  r5()
33379
35735
  ], ResultViewer.prototype, "_internalResult", 2);
35736
+ __decorateClass([
35737
+ e7("#qr-container")
35738
+ ], ResultViewer.prototype, "_qrContainer", 2);
33380
35739
  __decorateClass([
33381
35740
  n4({ type: String })
33382
35741
  ], ResultViewer.prototype, "collectionProperty", 2);
@@ -83446,9 +85805,13 @@ var ElicitationModal = class extends i4 {
83446
85805
  min="${min}"
83447
85806
  max="${max}"
83448
85807
  step="${step}"
85808
+ style="${this._sliderFillStyle(Number(currentValue), min, max)}"
83449
85809
  .value=${String(currentValue)}
83450
85810
  @input=${(e8) => {
83451
- this._inputValue = Number(e8.target.value);
85811
+ const el2 = e8.target;
85812
+ const v2 = this._sanitizeSliderValue(Number(el2.value), min, max, step);
85813
+ el2.style.cssText = this._sliderFillStyle(v2, min, max);
85814
+ this._inputValue = v2;
83452
85815
  }}
83453
85816
  />
83454
85817
  <input
@@ -83459,7 +85822,15 @@ var ElicitationModal = class extends i4 {
83459
85822
  step="${step}"
83460
85823
  .value=${String(currentValue)}
83461
85824
  @input=${(e8) => {
83462
- this._inputValue = Number(e8.target.value);
85825
+ const raw = e8.target.value;
85826
+ if (raw === "" || raw === "-") return;
85827
+ this._inputValue = Number(raw);
85828
+ }}
85829
+ @change=${(e8) => {
85830
+ const el2 = e8.target;
85831
+ const v2 = this._sanitizeSliderValue(Number(el2.value), min, max, step);
85832
+ el2.value = String(v2);
85833
+ this._inputValue = v2;
83463
85834
  }}
83464
85835
  @keydown=${(e8) => this._handleKeydown(e8)}
83465
85836
  autofocus
@@ -83703,9 +86074,13 @@ var ElicitationModal = class extends i4 {
83703
86074
  min="${min}"
83704
86075
  max="${max}"
83705
86076
  step="${step}"
86077
+ style="${this._sliderFillStyle(Number(currentValue), min, max)}"
83706
86078
  .value=${String(currentValue)}
83707
86079
  @input=${(e8) => {
83708
- this._updateFormValue(field.name, Number(e8.target.value));
86080
+ const el2 = e8.target;
86081
+ const v2 = this._sanitizeSliderValue(Number(el2.value), min, max, step);
86082
+ el2.style.cssText = this._sliderFillStyle(v2, min, max);
86083
+ this._updateFormValue(field.name, v2);
83709
86084
  }}
83710
86085
  />
83711
86086
  <input
@@ -83716,7 +86091,15 @@ var ElicitationModal = class extends i4 {
83716
86091
  step="${step}"
83717
86092
  .value=${String(currentValue)}
83718
86093
  @input=${(e8) => {
83719
- this._updateFormValue(field.name, Number(e8.target.value));
86094
+ const raw = e8.target.value;
86095
+ if (raw === "" || raw === "-") return;
86096
+ this._updateFormValue(field.name, Number(raw));
86097
+ }}
86098
+ @change=${(e8) => {
86099
+ const el2 = e8.target;
86100
+ const v2 = this._sanitizeSliderValue(Number(el2.value), min, max, step);
86101
+ el2.value = String(v2);
86102
+ this._updateFormValue(field.name, v2);
83720
86103
  }}
83721
86104
  />
83722
86105
  </div>
@@ -83737,6 +86120,23 @@ var ElicitationModal = class extends i4 {
83737
86120
  />
83738
86121
  `;
83739
86122
  }
86123
+ _sliderFillStyle(value, min, max) {
86124
+ const pct = max > min ? (value - min) / (max - min) * 100 : 0;
86125
+ return `background: linear-gradient(to right, var(--accent-primary) ${pct}%, var(--border-glass) ${pct}%)`;
86126
+ }
86127
+ /** Clamp to [min, max] and round to nearest step (integer-safe). */
86128
+ _sanitizeSliderValue(raw, min, max, step) {
86129
+ let v2 = Number.isFinite(raw) ? raw : min;
86130
+ v2 = Math.min(max, Math.max(min, v2));
86131
+ if (step > 0) {
86132
+ v2 = min + Math.round((v2 - min) / step) * step;
86133
+ if (v2 > max) v2 = max;
86134
+ }
86135
+ if (Number.isInteger(step) && step >= 1) {
86136
+ v2 = Math.round(v2);
86137
+ }
86138
+ return v2;
86139
+ }
83740
86140
  _updateFormValue(name2, value) {
83741
86141
  this._formValues = { ...this._formValues, [name2]: value };
83742
86142
  }
@@ -83861,11 +86261,19 @@ ElicitationModal.styles = [
83861
86261
  color: white;
83862
86262
  }
83863
86263
 
86264
+ .btn-success:hover {
86265
+ filter: brightness(1.1);
86266
+ }
86267
+
83864
86268
  .btn-danger {
83865
86269
  background: var(--color-error);
83866
86270
  color: white;
83867
86271
  }
83868
86272
 
86273
+ .btn-danger:hover {
86274
+ filter: brightness(1.1);
86275
+ }
86276
+
83869
86277
  /* Confirm buttons */
83870
86278
  .confirm-actions {
83871
86279
  display: flex;
@@ -83975,7 +86383,7 @@ ElicitationModal.styles = [
83975
86383
  .slider-group {
83976
86384
  display: flex;
83977
86385
  flex-direction: column;
83978
- gap: 6px;
86386
+ gap: 4px;
83979
86387
  }
83980
86388
 
83981
86389
  .slider-row {
@@ -83986,47 +86394,104 @@ ElicitationModal.styles = [
83986
86394
 
83987
86395
  .slider-row input[type='range'] {
83988
86396
  flex: 1;
83989
- height: 6px;
83990
- background: rgba(255, 255, 255, 0.12);
83991
- border: 1px solid rgba(255, 255, 255, 0.08);
83992
- border-radius: 3px;
86397
+ height: 4px;
86398
+ border-radius: var(--radius-full, 9999px);
83993
86399
  -webkit-appearance: none;
83994
86400
  cursor: pointer;
83995
86401
  margin: 8px 0;
86402
+ border: none;
86403
+ outline: none;
86404
+ background: var(--border-glass);
83996
86405
  }
83997
86406
 
83998
86407
  .slider-row input[type='range']::-webkit-slider-thumb {
83999
86408
  -webkit-appearance: none;
84000
- width: 20px;
84001
- height: 20px;
86409
+ width: 16px;
86410
+ height: 16px;
84002
86411
  background: var(--accent-primary);
86412
+ border: 2px solid var(--bg-panel, var(--bg-glass));
84003
86413
  border-radius: 50%;
84004
86414
  cursor: pointer;
84005
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
86415
+ box-shadow:
86416
+ 0 0 0 2px var(--accent-primary),
86417
+ 0 1px 4px rgba(0, 0, 0, 0.3);
86418
+ transition:
86419
+ box-shadow 0.15s ease,
86420
+ transform 0.15s ease;
86421
+ }
86422
+
86423
+ .slider-row input[type='range']::-webkit-slider-thumb:hover {
86424
+ box-shadow:
86425
+ 0 0 0 3px var(--accent-primary),
86426
+ 0 0 8px var(--glow-primary);
86427
+ transform: scale(1.1);
86428
+ }
86429
+
86430
+ .slider-row input[type='range']:active::-webkit-slider-thumb {
86431
+ transform: scale(0.95);
84006
86432
  }
84007
86433
 
84008
86434
  .slider-row input[type='range']::-moz-range-track {
84009
- height: 6px;
84010
- background: rgba(255, 255, 255, 0.12);
84011
- border-radius: 3px;
84012
- border: 1px solid rgba(255, 255, 255, 0.08);
86435
+ height: 4px;
86436
+ background: var(--border-glass);
86437
+ border-radius: var(--radius-full, 9999px);
86438
+ border: none;
86439
+ }
86440
+
86441
+ .slider-row input[type='range']::-moz-range-progress {
86442
+ height: 4px;
86443
+ background: var(--accent-primary);
86444
+ border-radius: var(--radius-full, 9999px);
84013
86445
  }
84014
86446
 
84015
86447
  .slider-row input[type='range']::-moz-range-thumb {
84016
- width: 20px;
84017
- height: 20px;
86448
+ width: 12px;
86449
+ height: 12px;
84018
86450
  background: var(--accent-primary);
86451
+ border: 2px solid var(--bg-panel, var(--bg-glass));
84019
86452
  border-radius: 50%;
84020
86453
  cursor: pointer;
84021
- border: none;
84022
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
86454
+ box-shadow:
86455
+ 0 0 0 2px var(--accent-primary),
86456
+ 0 1px 4px rgba(0, 0, 0, 0.3);
86457
+ transition:
86458
+ box-shadow 0.15s ease,
86459
+ transform 0.15s ease;
86460
+ }
86461
+
86462
+ .slider-row input[type='range']::-moz-range-thumb:hover {
86463
+ box-shadow:
86464
+ 0 0 0 3px var(--accent-primary),
86465
+ 0 0 8px var(--glow-primary);
84023
86466
  }
84024
86467
 
84025
86468
  .slider-number-input {
84026
- width: 80px;
84027
- text-align: right;
86469
+ width: 64px;
86470
+ text-align: center;
84028
86471
  font-size: var(--text-sm);
84029
- padding: 4px 8px;
86472
+ font-weight: 500;
86473
+ font-variant-numeric: tabular-nums;
86474
+ padding: 4px 6px;
86475
+ background: var(--bg-glass);
86476
+ border: 1px solid var(--border-glass) !important;
86477
+ border-radius: var(--radius-sm);
86478
+ color: var(--t-primary);
86479
+ -moz-appearance: textfield;
86480
+ transition:
86481
+ border-color 0.15s ease,
86482
+ box-shadow 0.15s ease;
86483
+ }
86484
+
86485
+ .slider-number-input::-webkit-outer-spin-button,
86486
+ .slider-number-input::-webkit-inner-spin-button {
86487
+ -webkit-appearance: none;
86488
+ margin: 0;
86489
+ }
86490
+
86491
+ .slider-number-input:focus-visible {
86492
+ outline: none;
86493
+ border-color: var(--accent-primary) !important;
86494
+ box-shadow: 0 0 0 2px var(--glow-primary);
84030
86495
  }
84031
86496
 
84032
86497
  .range-labels {
@@ -84085,7 +86550,13 @@ ElicitationModal.styles = [
84085
86550
  background: var(--bg-glass-strong);
84086
86551
  border-radius: 12px;
84087
86552
  cursor: pointer;
84088
- transition: background 0.2s;
86553
+ transition:
86554
+ background 0.2s,
86555
+ box-shadow 0.2s;
86556
+ }
86557
+
86558
+ .toggle-track:hover {
86559
+ background: var(--bg-glass);
84089
86560
  }
84090
86561
 
84091
86562
  .toggle-track::after {
@@ -84104,6 +86575,14 @@ ElicitationModal.styles = [
84104
86575
  background: var(--accent-primary);
84105
86576
  }
84106
86577
 
86578
+ .toggle-switch input:checked + .toggle-track:hover {
86579
+ background: var(--accent-secondary);
86580
+ }
86581
+
86582
+ .toggle-switch input:focus-visible + .toggle-track {
86583
+ box-shadow: 0 0 0 2px var(--glow-primary);
86584
+ }
86585
+
84107
86586
  .toggle-switch input:checked + .toggle-track::after {
84108
86587
  transform: translateX(20px);
84109
86588
  }
@@ -84239,7 +86718,7 @@ var OverflowMenu = class extends i4 {
84239
86718
  return b2`
84240
86719
  <button
84241
86720
  class="trigger"
84242
- @click=${() => this._toggle()}
86721
+ @click=${(e8) => this._toggle(e8)}
84243
86722
  title="More actions"
84244
86723
  aria-label="More actions"
84245
86724
  >
@@ -84519,7 +86998,7 @@ var InstancePanel = class extends i4 {
84519
86998
  return b2`
84520
86999
  <button
84521
87000
  class="instance-pill"
84522
- @click=${() => this._toggle()}
87001
+ @click=${(e8) => this._toggle(e8)}
84523
87002
  title="Instances are separate data containers — like multiple accounts in the same app. Click to switch."
84524
87003
  aria-label="Current instance: ${display}"
84525
87004
  aria-haspopup="true"
@@ -85746,7 +88225,7 @@ ContextBar.styles = [
85746
88225
  backdrop-filter: blur(20px);
85747
88226
  border: 1px solid var(--border-glass);
85748
88227
  border-radius: var(--radius-md);
85749
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
88228
+ box-shadow: var(--shadow-lg);
85750
88229
  padding: 4px;
85751
88230
  z-index: 100;
85752
88231
  }
@@ -85757,7 +88236,7 @@ ContextBar.styles = [
85757
88236
  padding: 6px 10px;
85758
88237
  border: none;
85759
88238
  background: none;
85760
- color: var(--t-secondary);
88239
+ color: var(--t-muted);
85761
88240
  font-size: var(--text-sm);
85762
88241
  font-family: inherit;
85763
88242
  text-align: left;
@@ -85783,7 +88262,7 @@ ContextBar.styles = [
85783
88262
  font-size: var(--text-2xs);
85784
88263
  font-weight: 700;
85785
88264
  letter-spacing: 0.08em;
85786
- color: #4ade80;
88265
+ color: var(--color-success);
85787
88266
  text-transform: uppercase;
85788
88267
  margin-left: auto;
85789
88268
  }
@@ -85792,8 +88271,8 @@ ContextBar.styles = [
85792
88271
  width: 6px;
85793
88272
  height: 6px;
85794
88273
  border-radius: 50%;
85795
- background: #4ade80;
85796
- box-shadow: 0 0 6px 2px rgba(74, 222, 128, 0.6);
88274
+ background: var(--color-success);
88275
+ box-shadow: 0 0 6px 2px var(--color-success-glow);
85797
88276
  animation: live-pulse 2s ease-in-out infinite;
85798
88277
  }
85799
88278
 
@@ -85801,11 +88280,11 @@ ContextBar.styles = [
85801
88280
  0%,
85802
88281
  100% {
85803
88282
  opacity: 1;
85804
- box-shadow: 0 0 6px 2px rgba(74, 222, 128, 0.6);
88283
+ box-shadow: 0 0 6px 2px var(--color-success-glow);
85805
88284
  }
85806
88285
  50% {
85807
88286
  opacity: 0.5;
85808
- box-shadow: 0 0 3px 1px rgba(74, 222, 128, 0.3);
88287
+ box-shadow: 0 0 3px 1px var(--color-success-glow);
85809
88288
  }
85810
88289
  }
85811
88290