@portel/photon 1.11.0 → 1.13.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 (87) 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 +994 -34
  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 +53 -12
  18. package/dist/auto-ui/streamable-http-transport.js.map +1 -1
  19. package/dist/auto-ui/types.d.ts +28 -1
  20. package/dist/auto-ui/types.d.ts.map +1 -1
  21. package/dist/auto-ui/types.js +23 -0
  22. package/dist/auto-ui/types.js.map +1 -1
  23. package/dist/beam.bundle.js +2894 -329
  24. package/dist/beam.bundle.js.map +4 -4
  25. package/dist/cli/commands/build.d.ts +3 -0
  26. package/dist/cli/commands/build.d.ts.map +1 -0
  27. package/dist/cli/commands/build.js +339 -0
  28. package/dist/cli/commands/build.js.map +1 -0
  29. package/dist/cli/commands/package-app.d.ts.map +1 -1
  30. package/dist/cli/commands/package-app.js +116 -35
  31. package/dist/cli/commands/package-app.js.map +1 -1
  32. package/dist/cli/commands/run.d.ts.map +1 -1
  33. package/dist/cli/commands/run.js +2 -0
  34. package/dist/cli/commands/run.js.map +1 -1
  35. package/dist/cli/index.d.ts.map +1 -1
  36. package/dist/cli/index.js +2 -0
  37. package/dist/cli/index.js.map +1 -1
  38. package/dist/context-store.d.ts +5 -0
  39. package/dist/context-store.d.ts.map +1 -1
  40. package/dist/context-store.js +9 -0
  41. package/dist/context-store.js.map +1 -1
  42. package/dist/daemon/client.d.ts.map +1 -1
  43. package/dist/daemon/client.js +81 -0
  44. package/dist/daemon/client.js.map +1 -1
  45. package/dist/daemon/protocol.d.ts +3 -1
  46. package/dist/daemon/protocol.d.ts.map +1 -1
  47. package/dist/daemon/protocol.js +1 -1
  48. package/dist/daemon/protocol.js.map +1 -1
  49. package/dist/daemon/server.js +513 -18
  50. package/dist/daemon/server.js.map +1 -1
  51. package/dist/embedded-runtime.d.ts +38 -0
  52. package/dist/embedded-runtime.d.ts.map +1 -0
  53. package/dist/embedded-runtime.js +326 -0
  54. package/dist/embedded-runtime.js.map +1 -0
  55. package/dist/index.d.ts +1 -0
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +1 -0
  58. package/dist/index.js.map +1 -1
  59. package/dist/loader.d.ts +38 -1
  60. package/dist/loader.d.ts.map +1 -1
  61. package/dist/loader.js +455 -15
  62. package/dist/loader.js.map +1 -1
  63. package/dist/photon-cli-runner.d.ts +22 -0
  64. package/dist/photon-cli-runner.d.ts.map +1 -1
  65. package/dist/photon-cli-runner.js +244 -12
  66. package/dist/photon-cli-runner.js.map +1 -1
  67. package/dist/photon-doc-extractor.d.ts +6 -0
  68. package/dist/photon-doc-extractor.d.ts.map +1 -1
  69. package/dist/photon-doc-extractor.js +22 -0
  70. package/dist/photon-doc-extractor.js.map +1 -1
  71. package/dist/photons/tunnel.photon.d.ts +5 -9
  72. package/dist/photons/tunnel.photon.d.ts.map +1 -1
  73. package/dist/photons/tunnel.photon.js +36 -96
  74. package/dist/photons/tunnel.photon.js.map +1 -1
  75. package/dist/photons/tunnel.photon.ts +40 -112
  76. package/dist/server.d.ts +30 -0
  77. package/dist/server.d.ts.map +1 -1
  78. package/dist/server.js +155 -10
  79. package/dist/server.js.map +1 -1
  80. package/dist/test-runner.d.ts +13 -1
  81. package/dist/test-runner.d.ts.map +1 -1
  82. package/dist/test-runner.js +529 -122
  83. package/dist/test-runner.js.map +1 -1
  84. package/dist/version.d.ts.map +1 -1
  85. package/dist/version.js +10 -2
  86. package/dist/version.js.map +1 -1
  87. package/package.json +23 -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 {
@@ -15808,6 +15812,15 @@ var activity = icon(
15808
15812
  var eye = icon(
15809
15813
  `<path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/>`
15810
15814
  );
15815
+ var user = icon(
15816
+ `<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>`
15817
+ );
15818
+ var bot = icon(
15819
+ `<path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/>`
15820
+ );
15821
+ var users = icon(
15822
+ `<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>`
15823
+ );
15811
15824
  var iconPaths = {
15812
15825
  refresh: `<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M8 16H3v5"/>`,
15813
15826
  flask: `<path d="M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2"/><path d="M6.453 15h11.094"/><path d="M8.5 2h7"/>`,
@@ -15822,8 +15835,16 @@ var iconPaths = {
15822
15835
  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
15836
  expand: `<path d="M15 3h6v6"/><path d="m21 3-7 7"/><path d="m3 21 7-7"/><path d="M9 21H3v-6"/>`,
15824
15837
  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"/>`
15838
+ 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"/>`,
15839
+ 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
15840
  };
15841
+ function sizedIcon(iconTpl, size) {
15842
+ return b2`<span
15843
+ class="icon"
15844
+ style="display:inline-flex;align-items:center;justify-content:center;width:${size}px;height:${size}px"
15845
+ >${iconTpl}</span
15846
+ >`;
15847
+ }
15827
15848
 
15828
15849
  // node_modules/@portel/photon-core/dist/design-system/oklch.js
15829
15850
  function oklchToSRGB(L3, C3, H3) {
@@ -16509,7 +16530,8 @@ var buttons = i`
16509
16530
  color: var(--t-primary);
16510
16531
  }
16511
16532
 
16512
- .btn-primary:disabled {
16533
+ .btn-primary:disabled,
16534
+ .btn-secondary:disabled {
16513
16535
  opacity: 0.6;
16514
16536
  cursor: not-allowed;
16515
16537
  transform: none;
@@ -16549,7 +16571,21 @@ var forms = i`
16549
16571
  padding: var(--space-sm);
16550
16572
  border-radius: var(--radius-sm);
16551
16573
  font-family: var(--font-sans);
16574
+ font-size: var(--text-md);
16552
16575
  box-sizing: border-box;
16576
+ transition:
16577
+ border-color 0.15s ease,
16578
+ box-shadow 0.15s ease;
16579
+ }
16580
+
16581
+ input:hover,
16582
+ textarea:hover,
16583
+ select:hover {
16584
+ border-color: var(--accent-secondary);
16585
+ }
16586
+
16587
+ textarea {
16588
+ resize: vertical;
16553
16589
  }
16554
16590
 
16555
16591
  ::placeholder {
@@ -16565,12 +16601,12 @@ var forms = i`
16565
16601
  display: block;
16566
16602
  margin-bottom: var(--space-xs);
16567
16603
  font-weight: 500;
16568
- font-size: 0.9rem;
16604
+ font-size: var(--text-md);
16569
16605
  }
16570
16606
 
16571
16607
  .error-text {
16572
16608
  color: var(--color-error);
16573
- font-size: 0.75rem;
16609
+ font-size: var(--text-xs);
16574
16610
  margin-top: var(--space-xs);
16575
16611
  }
16576
16612
 
@@ -16590,7 +16626,7 @@ var forms = i`
16590
16626
  var badges = i`
16591
16627
  .type-badge {
16592
16628
  display: inline-block;
16593
- font-size: 0.65rem;
16629
+ font-size: var(--text-2xs);
16594
16630
  font-weight: 600;
16595
16631
  text-transform: uppercase;
16596
16632
  letter-spacing: 0.04em;
@@ -16624,7 +16660,7 @@ var badges = i`
16624
16660
  }
16625
16661
 
16626
16662
  .param-tag {
16627
- font-size: 0.65rem;
16663
+ font-size: var(--text-2xs);
16628
16664
  padding: 1px 6px;
16629
16665
  border-radius: var(--radius-xs);
16630
16666
  background: var(--param-tag-bg, hsla(220, 10%, 80%, 0.08));
@@ -17200,6 +17236,307 @@ function loadSavedThemeConfig() {
17200
17236
  return null;
17201
17237
  }
17202
17238
 
17239
+ // src/auto-ui/frontend/services/photon-instance-manager.ts
17240
+ var SimpleEventEmitter = class {
17241
+ constructor() {
17242
+ this._listeners = /* @__PURE__ */ new Map();
17243
+ }
17244
+ on(event, callback2) {
17245
+ if (!this._listeners.has(event)) {
17246
+ this._listeners.set(event, /* @__PURE__ */ new Set());
17247
+ }
17248
+ this._listeners.get(event).add(callback2);
17249
+ }
17250
+ off(event, callback2) {
17251
+ const listeners = this._listeners.get(event);
17252
+ if (listeners) {
17253
+ listeners.delete(callback2);
17254
+ }
17255
+ }
17256
+ emit(event, ...args) {
17257
+ const listeners = this._listeners.get(event);
17258
+ if (listeners) {
17259
+ for (const callback2 of listeners) {
17260
+ callback2(...args);
17261
+ }
17262
+ }
17263
+ }
17264
+ };
17265
+ var PhotonSessionProxy = class extends SimpleEventEmitter {
17266
+ constructor(options) {
17267
+ super();
17268
+ this._paginationState = /* @__PURE__ */ new Map();
17269
+ this._pendingPatches = [];
17270
+ this._isApplyingPatches = false;
17271
+ this._name = options.name;
17272
+ this._state = { ...options.initialState };
17273
+ }
17274
+ /**
17275
+ * Get the photon name
17276
+ */
17277
+ get name() {
17278
+ return this._name;
17279
+ }
17280
+ /**
17281
+ * Get current state
17282
+ */
17283
+ get state() {
17284
+ return this._state;
17285
+ }
17286
+ /**
17287
+ * Apply JSON Patch array to instance state
17288
+ * Patches from: https://tools.ietf.org/html/rfc6902
17289
+ */
17290
+ applyPatches(patches) {
17291
+ if (!Array.isArray(patches) || patches.length === 0) {
17292
+ return;
17293
+ }
17294
+ this._pendingPatches.push(...patches);
17295
+ if (!this._isApplyingPatches) {
17296
+ this._isApplyingPatches = true;
17297
+ queueMicrotask(() => this._processPendingPatches());
17298
+ }
17299
+ }
17300
+ /**
17301
+ * Update pagination state for a property (e.g., 'items')
17302
+ */
17303
+ setPaginationState(property2, state) {
17304
+ const current = this._paginationState.get(property2) || {
17305
+ pageSize: 20,
17306
+ currentPage: 0,
17307
+ hasMore: true,
17308
+ isLoading: false
17309
+ };
17310
+ this._paginationState.set(property2, { ...current, ...state });
17311
+ }
17312
+ /**
17313
+ * Get pagination state for a property
17314
+ */
17315
+ getPaginationState(property2) {
17316
+ return this._paginationState.get(property2) || {
17317
+ pageSize: 20,
17318
+ currentPage: 0,
17319
+ hasMore: true,
17320
+ isLoading: false
17321
+ };
17322
+ }
17323
+ /**
17324
+ * Create a getter/setter for property access
17325
+ */
17326
+ _createPropertyDescriptor(key) {
17327
+ return {
17328
+ configurable: true,
17329
+ enumerable: true,
17330
+ get: () => this._state[key],
17331
+ set: (value) => {
17332
+ this._state[key] = value;
17333
+ this.emit("propertyChanged", { property: key, value });
17334
+ }
17335
+ };
17336
+ }
17337
+ /**
17338
+ * Make a property accessible on the proxy
17339
+ */
17340
+ makeProperty(key) {
17341
+ Object.defineProperty(this, key, this._createPropertyDescriptor(key));
17342
+ }
17343
+ /**
17344
+ * Get all top-level properties (useful for initialization)
17345
+ */
17346
+ getProperties() {
17347
+ return Object.keys(this._state);
17348
+ }
17349
+ /**
17350
+ * Process pending patches
17351
+ */
17352
+ _processPendingPatches() {
17353
+ const patches = this._pendingPatches.splice(0);
17354
+ try {
17355
+ for (const patch of patches) {
17356
+ this._applyPatch(patch);
17357
+ }
17358
+ this.emit("state-changed", patches);
17359
+ } finally {
17360
+ this._isApplyingPatches = false;
17361
+ if (this._pendingPatches.length > 0) {
17362
+ queueMicrotask(() => this._processPendingPatches());
17363
+ }
17364
+ }
17365
+ }
17366
+ /**
17367
+ * Apply a single JSON Patch operation
17368
+ */
17369
+ _applyPatch(patch) {
17370
+ const { op, path, value } = patch;
17371
+ if (!path || typeof path !== "string") {
17372
+ console.warn("Invalid patch: missing or invalid path", patch);
17373
+ return;
17374
+ }
17375
+ const parts = path.split("/").filter((p5) => p5 !== "");
17376
+ let current = this._state;
17377
+ try {
17378
+ for (let i7 = 0; i7 < parts.length - 1; i7++) {
17379
+ const part = this._unescapePath(parts[i7]);
17380
+ if (!(part in current)) {
17381
+ current[part] = {};
17382
+ }
17383
+ current = current[part];
17384
+ }
17385
+ const lastPart = this._unescapePath(parts[parts.length - 1]);
17386
+ switch (op) {
17387
+ case "add":
17388
+ if (Array.isArray(current) && !isNaN(Number(lastPart))) {
17389
+ const index2 = Number(lastPart);
17390
+ current.splice(index2, 0, value);
17391
+ } else {
17392
+ current[lastPart] = value;
17393
+ }
17394
+ break;
17395
+ case "remove":
17396
+ if (Array.isArray(current) && !isNaN(Number(lastPart))) {
17397
+ const index2 = Number(lastPart);
17398
+ current.splice(index2, 1);
17399
+ } else {
17400
+ delete current[lastPart];
17401
+ }
17402
+ break;
17403
+ case "replace":
17404
+ current[lastPart] = value;
17405
+ break;
17406
+ case "move":
17407
+ const fromParts = patch.from.split("/").filter((p5) => p5 !== "");
17408
+ let fromCurrent = this._state;
17409
+ for (let i7 = 0; i7 < fromParts.length - 1; i7++) {
17410
+ fromCurrent = fromCurrent[this._unescapePath(fromParts[i7])];
17411
+ }
17412
+ const fromLast = this._unescapePath(fromParts[fromParts.length - 1]);
17413
+ const movedValue = fromCurrent[fromLast];
17414
+ if (Array.isArray(fromCurrent)) {
17415
+ fromCurrent.splice(Number(fromLast), 1);
17416
+ } else {
17417
+ delete fromCurrent[fromLast];
17418
+ }
17419
+ if (Array.isArray(current)) {
17420
+ current.splice(Number(lastPart), 0, movedValue);
17421
+ } else {
17422
+ current[lastPart] = movedValue;
17423
+ }
17424
+ break;
17425
+ case "copy":
17426
+ const copyFromParts = patch.from.split("/").filter((p5) => p5 !== "");
17427
+ let copyCurrent = this._state;
17428
+ for (let i7 = 0; i7 < copyFromParts.length - 1; i7++) {
17429
+ copyCurrent = copyCurrent[this._unescapePath(copyFromParts[i7])];
17430
+ }
17431
+ const copyFromLast = this._unescapePath(copyFromParts[copyFromParts.length - 1]);
17432
+ const copiedValue = JSON.parse(JSON.stringify(copyCurrent[copyFromLast]));
17433
+ current[lastPart] = copiedValue;
17434
+ break;
17435
+ case "test":
17436
+ if (current[lastPart] !== value) {
17437
+ throw new Error(`Test failed at ${path}: expected ${value}, got ${current[lastPart]}`);
17438
+ }
17439
+ break;
17440
+ default:
17441
+ console.warn(`Unknown patch operation: ${op}`);
17442
+ }
17443
+ } catch (error2) {
17444
+ console.error("Failed to apply patch", { patch, error: error2 });
17445
+ throw error2;
17446
+ }
17447
+ }
17448
+ /**
17449
+ * Unescape JSON Pointer path component
17450
+ * See: https://tools.ietf.org/html/rfc6901
17451
+ */
17452
+ _unescapePath(part) {
17453
+ return part.replace(/~1/g, "/").replace(/~0/g, "~");
17454
+ }
17455
+ /**
17456
+ * Clear all state
17457
+ */
17458
+ reset(newState) {
17459
+ this._state = { ...newState };
17460
+ this._paginationState.clear();
17461
+ this.emit("reset");
17462
+ }
17463
+ };
17464
+ var GlobalSessionManager = class {
17465
+ constructor() {
17466
+ this._sessions = /* @__PURE__ */ new Map();
17467
+ }
17468
+ /**
17469
+ * Create or get a photon session
17470
+ */
17471
+ createOrGetSession(name2, initialState) {
17472
+ if (this._sessions.has(name2)) {
17473
+ return this._sessions.get(name2);
17474
+ }
17475
+ const session = new PhotonSessionProxy({
17476
+ name: name2,
17477
+ initialState
17478
+ });
17479
+ Object.keys(initialState).forEach((key) => {
17480
+ session.makeProperty(key);
17481
+ });
17482
+ this._sessions.set(name2, session);
17483
+ return session;
17484
+ }
17485
+ /**
17486
+ * Get existing session
17487
+ */
17488
+ getSession(name2) {
17489
+ return this._sessions.get(name2);
17490
+ }
17491
+ /**
17492
+ * Apply patches to a session
17493
+ */
17494
+ applyPatches(name2, patches) {
17495
+ const session = this._sessions.get(name2);
17496
+ if (!session) {
17497
+ console.warn(`No session found for: ${name2}`);
17498
+ return false;
17499
+ }
17500
+ session.applyPatches(patches);
17501
+ return true;
17502
+ }
17503
+ /**
17504
+ * Remove session
17505
+ */
17506
+ removeSession(name2) {
17507
+ this._sessions.delete(name2);
17508
+ }
17509
+ /**
17510
+ * Get all session names
17511
+ */
17512
+ getSessionNames() {
17513
+ return Array.from(this._sessions.keys());
17514
+ }
17515
+ };
17516
+ var globalSessionManager = null;
17517
+ function getGlobalSessionManager() {
17518
+ if (!globalSessionManager) {
17519
+ globalSessionManager = new GlobalSessionManager();
17520
+ if (typeof window !== "undefined") {
17521
+ window.__photonSessionManager = globalSessionManager;
17522
+ }
17523
+ }
17524
+ return globalSessionManager;
17525
+ }
17526
+ function initializeGlobalPhotonSession(photonName, initialState) {
17527
+ const manager = getGlobalSessionManager();
17528
+ const session = manager.createOrGetSession(photonName, initialState);
17529
+ if (typeof window !== "undefined") {
17530
+ const varName = photonName.charAt(0).toLocaleLowerCase() + photonName.slice(1);
17531
+ window[varName] = session;
17532
+ if (photonName === "Boards") {
17533
+ window.boards = session;
17534
+ window.boardsSession = session;
17535
+ }
17536
+ }
17537
+ return session;
17538
+ }
17539
+
17203
17540
  // src/auto-ui/frontend/services/mcp-client.ts
17204
17541
  var MCPClientService = class {
17205
17542
  // Discard operations older than 30s
@@ -17904,9 +18241,21 @@ var MCPClientService = class {
17904
18241
  case "photon/refresh-needed":
17905
18242
  this.emit("refresh-needed", notification.params);
17906
18243
  break;
17907
- case "photon/state-changed":
18244
+ case "state-changed":
18245
+ const params = notification.params;
18246
+ if (params?.instance && params?.patches) {
18247
+ try {
18248
+ const manager = getGlobalSessionManager();
18249
+ manager.applyPatches(params.instance, params.patches);
18250
+ } catch (error2) {
18251
+ console.error("Failed to apply patches to session", error2);
18252
+ }
18253
+ }
17908
18254
  this.emit("state-changed", notification.params);
17909
18255
  break;
18256
+ case "photon/notification":
18257
+ this.emit("photon-notification", notification.params);
18258
+ break;
17910
18259
  // MCP Apps standard notifications
17911
18260
  case "ui/notifications/tool-result":
17912
18261
  this.emit("ui-tool-result", notification.params);
@@ -17931,6 +18280,439 @@ var MCPClientService = class {
17931
18280
  };
17932
18281
  var mcpClient = new MCPClientService();
17933
18282
 
18283
+ // src/auto-ui/frontend/services/viewport-aware-proxy.ts
18284
+ var ViewportAwareProxy = class {
18285
+ constructor(photonName, methodName, mcpClient2, options = {}) {
18286
+ // Cache management
18287
+ this._cache = /* @__PURE__ */ new Map();
18288
+ this._viewport = { start: 0, end: 20 };
18289
+ this._pendingRanges = /* @__PURE__ */ new Set();
18290
+ this._pagination = {
18291
+ totalCount: 0,
18292
+ start: 0,
18293
+ end: 0,
18294
+ hasMore: false
18295
+ };
18296
+ // Event listeners
18297
+ this._listeners = /* @__PURE__ */ new Map();
18298
+ this._photonName = photonName;
18299
+ this._methodName = methodName;
18300
+ this._mcpClient = mcpClient2;
18301
+ this._fetchOptions = {
18302
+ pageSize: options.pageSize ?? 20,
18303
+ bufferSize: options.bufferSize ?? 5,
18304
+ maxCacheSize: options.maxCacheSize ?? 1e3
18305
+ };
18306
+ }
18307
+ /**
18308
+ * Initialize with paginated response from server
18309
+ * Server should return: { items: [...], _pagination: {...} }
18310
+ */
18311
+ initializeWithResponse(response) {
18312
+ if (!response || typeof response !== "object") {
18313
+ return;
18314
+ }
18315
+ const { items = [], _pagination } = response;
18316
+ if (_pagination) {
18317
+ this._pagination = _pagination;
18318
+ items.forEach((item, offset) => {
18319
+ this._cache.set(_pagination.start + offset, item);
18320
+ });
18321
+ this._emit("initialized", { pagination: this._pagination });
18322
+ }
18323
+ }
18324
+ /**
18325
+ * Set visible viewport (what user sees on screen)
18326
+ * Automatically fetches data for this range + buffer
18327
+ */
18328
+ async setViewport(start, end) {
18329
+ this._viewport = { start, end };
18330
+ const bufferedStart = Math.max(0, start - this._fetchOptions.bufferSize);
18331
+ const bufferedEnd = Math.min(this._pagination.totalCount, end + this._fetchOptions.bufferSize);
18332
+ const missingRanges = this._findMissingRanges(bufferedStart, bufferedEnd);
18333
+ await Promise.all(
18334
+ missingRanges.map(
18335
+ (range) => this._fetchRange(range.start, range.end).catch((err) => {
18336
+ console.error(`Failed to fetch range [${range.start}, ${range.end}]`, err);
18337
+ })
18338
+ )
18339
+ );
18340
+ }
18341
+ /**
18342
+ * Get items in current viewport (array-like interface)
18343
+ */
18344
+ get items() {
18345
+ const items = [];
18346
+ for (let i7 = this._viewport.start; i7 < this._viewport.end; i7++) {
18347
+ if (this._cache.has(i7)) {
18348
+ items.push(this._cache.get(i7));
18349
+ }
18350
+ }
18351
+ return items;
18352
+ }
18353
+ /**
18354
+ * Get item by index
18355
+ */
18356
+ getItem(index2) {
18357
+ return this._cache.get(index2);
18358
+ }
18359
+ /**
18360
+ * Get total count of items (if available)
18361
+ */
18362
+ get totalCount() {
18363
+ return this._pagination.totalCount;
18364
+ }
18365
+ /**
18366
+ * Get current pagination state
18367
+ */
18368
+ get pagination() {
18369
+ return { ...this._pagination };
18370
+ }
18371
+ /**
18372
+ * Get current viewport
18373
+ */
18374
+ get viewport() {
18375
+ return { ...this._viewport };
18376
+ }
18377
+ /**
18378
+ * Get cache size (for debugging)
18379
+ */
18380
+ get cacheSize() {
18381
+ return this._cache.size;
18382
+ }
18383
+ /**
18384
+ * Check if a range is cached
18385
+ */
18386
+ isCached(start, end) {
18387
+ for (let i7 = start; i7 < end; i7++) {
18388
+ if (!this._cache.has(i7)) {
18389
+ return false;
18390
+ }
18391
+ }
18392
+ return true;
18393
+ }
18394
+ /**
18395
+ * Subscribe to events
18396
+ */
18397
+ on(event, callback2) {
18398
+ if (!this._listeners.has(event)) {
18399
+ this._listeners.set(event, /* @__PURE__ */ new Set());
18400
+ }
18401
+ this._listeners.get(event).add(callback2);
18402
+ }
18403
+ /**
18404
+ * Unsubscribe from events
18405
+ */
18406
+ off(event, callback2) {
18407
+ const listeners = this._listeners.get(event);
18408
+ if (listeners) {
18409
+ listeners.delete(callback2);
18410
+ }
18411
+ }
18412
+ /**
18413
+ * Clear cache
18414
+ */
18415
+ clearCache() {
18416
+ this._cache.clear();
18417
+ this._emit("cache-cleared");
18418
+ }
18419
+ /**
18420
+ * Apply patches from state-changed events
18421
+ * Updates cache with new/modified items
18422
+ */
18423
+ applyPatches(patches) {
18424
+ for (const patch of patches) {
18425
+ this._applyPatch(patch);
18426
+ }
18427
+ this._emit("patched", { patches });
18428
+ }
18429
+ /**
18430
+ * Find which ranges need to be fetched
18431
+ */
18432
+ _findMissingRanges(start, end) {
18433
+ const missing = [];
18434
+ for (let i7 = start; i7 < end; i7++) {
18435
+ if (!this._cache.has(i7)) {
18436
+ missing.push(i7);
18437
+ }
18438
+ }
18439
+ const ranges = [];
18440
+ if (missing.length === 0) return ranges;
18441
+ let rangeStart = missing[0];
18442
+ let rangeEnd2 = missing[0];
18443
+ for (let i7 = 1; i7 < missing.length; i7++) {
18444
+ if (missing[i7] === rangeEnd2 + 1) {
18445
+ rangeEnd2 = missing[i7];
18446
+ } else {
18447
+ ranges.push({ start: rangeStart, end: rangeEnd2 + 1 });
18448
+ rangeStart = missing[i7];
18449
+ rangeEnd2 = missing[i7];
18450
+ }
18451
+ }
18452
+ ranges.push({ start: rangeStart, end: rangeEnd2 + 1 });
18453
+ return ranges;
18454
+ }
18455
+ /**
18456
+ * Fetch a range of items via MCP
18457
+ */
18458
+ async _fetchRange(start, end) {
18459
+ const rangeKey = `${start}-${end}`;
18460
+ if (this._pendingRanges.has(rangeKey)) {
18461
+ return;
18462
+ }
18463
+ this._pendingRanges.add(rangeKey);
18464
+ try {
18465
+ const toolName = `${this._photonName}/${this._methodName}`;
18466
+ const result = await this._mcpClient.callTool(toolName, {
18467
+ start,
18468
+ limit: Math.min(end - start, this._fetchOptions.pageSize)
18469
+ });
18470
+ if (result.isError) {
18471
+ console.error(`Failed to fetch ${toolName}[${start}:${end}]`, result);
18472
+ return;
18473
+ }
18474
+ const parsed = this._mcpClient.parseToolResult(result);
18475
+ if (parsed && typeof parsed === "object") {
18476
+ const { items = [], _pagination } = parsed;
18477
+ if (_pagination) {
18478
+ this._pagination = _pagination;
18479
+ }
18480
+ items.forEach((item, offset) => {
18481
+ this._cache.set(start + offset, item);
18482
+ });
18483
+ if (this._cache.size > this._fetchOptions.maxCacheSize) {
18484
+ this._pruneCache();
18485
+ }
18486
+ this._emit("fetched", { start, end, itemCount: items.length });
18487
+ }
18488
+ } finally {
18489
+ this._pendingRanges.delete(rangeKey);
18490
+ }
18491
+ }
18492
+ /**
18493
+ * Apply a single JSON Patch to cache
18494
+ */
18495
+ _applyPatch(patch) {
18496
+ const { op, path } = patch;
18497
+ const match = path.match(/\/items\/(\d+)/);
18498
+ if (!match) return;
18499
+ const index2 = parseInt(match[1], 10);
18500
+ switch (op) {
18501
+ case "add": {
18502
+ const newCache = /* @__PURE__ */ new Map();
18503
+ for (const [key, value] of this._cache) {
18504
+ if (key >= index2) {
18505
+ newCache.set(key + 1, value);
18506
+ } else {
18507
+ newCache.set(key, value);
18508
+ }
18509
+ }
18510
+ newCache.set(index2, patch.value);
18511
+ this._cache = newCache;
18512
+ this._pagination.totalCount++;
18513
+ break;
18514
+ }
18515
+ case "remove": {
18516
+ const newCache = /* @__PURE__ */ new Map();
18517
+ for (const [key, value] of this._cache) {
18518
+ if (key > index2) {
18519
+ newCache.set(key - 1, value);
18520
+ } else if (key < index2) {
18521
+ newCache.set(key, value);
18522
+ }
18523
+ }
18524
+ this._cache = newCache;
18525
+ this._pagination.totalCount--;
18526
+ break;
18527
+ }
18528
+ case "replace": {
18529
+ this._cache.set(index2, patch.value);
18530
+ break;
18531
+ }
18532
+ }
18533
+ }
18534
+ /**
18535
+ * Remove least-recently-used items from cache when it gets too large
18536
+ */
18537
+ _pruneCache() {
18538
+ const toRemove = this._cache.size - this._fetchOptions.maxCacheSize;
18539
+ if (toRemove <= 0) return;
18540
+ const viewportCenter = this._viewport.start + (this._viewport.end - this._viewport.start) / 2;
18541
+ const sortedKeys = Array.from(this._cache.keys()).sort(
18542
+ (a5, b3) => Math.abs(a5 - viewportCenter) - Math.abs(b3 - viewportCenter)
18543
+ );
18544
+ for (let i7 = 0; i7 < toRemove && i7 < sortedKeys.length; i7++) {
18545
+ this._cache.delete(sortedKeys[i7]);
18546
+ }
18547
+ }
18548
+ /**
18549
+ * Emit event to listeners
18550
+ */
18551
+ _emit(event, data) {
18552
+ const listeners = this._listeners.get(event);
18553
+ if (listeners) {
18554
+ for (const callback2 of listeners) {
18555
+ try {
18556
+ callback2(data);
18557
+ } catch (err) {
18558
+ console.error(`Error in ${event} listener:`, err);
18559
+ }
18560
+ }
18561
+ }
18562
+ }
18563
+ };
18564
+
18565
+ // src/auto-ui/frontend/services/viewport-manager.ts
18566
+ function getPageSizeForClient() {
18567
+ if (typeof window !== "undefined" && window.innerWidth < 600) {
18568
+ return 10;
18569
+ }
18570
+ if (typeof window !== "undefined" && window.innerWidth < 1024) {
18571
+ return 25;
18572
+ }
18573
+ return 50;
18574
+ }
18575
+ var ViewportManager = class {
18576
+ constructor(config3) {
18577
+ this.observer = null;
18578
+ this.visibleSentinels = /* @__PURE__ */ new Set();
18579
+ this.lastVisibleRange = { start: 0, end: 0 };
18580
+ this.lastScrollDirection = "none";
18581
+ this.changeCallbacks = [];
18582
+ this.element = config3.element;
18583
+ this.pageSize = config3.pageSize;
18584
+ this.paddingAbove = config3.paddingAbove ?? this.pageSize;
18585
+ this.paddingBelow = config3.paddingBelow ?? this.pageSize * 2;
18586
+ this.debug = config3.debug ?? false;
18587
+ this.log("ViewportManager initialized", {
18588
+ pageSize: this.pageSize,
18589
+ paddingAbove: this.paddingAbove,
18590
+ paddingBelow: this.paddingBelow
18591
+ });
18592
+ this.initializeObserver();
18593
+ }
18594
+ initializeObserver() {
18595
+ const options = {
18596
+ root: null,
18597
+ rootMargin: "100px",
18598
+ threshold: 0
18599
+ };
18600
+ this.observer = new IntersectionObserver((entries) => {
18601
+ for (const entry of entries) {
18602
+ const index2 = this.getSentinelIndex(entry.target);
18603
+ if (index2 !== null) {
18604
+ if (entry.isIntersecting) {
18605
+ this.visibleSentinels.add(index2);
18606
+ } else {
18607
+ this.visibleSentinels.delete(index2);
18608
+ }
18609
+ }
18610
+ }
18611
+ this.updateVisibleRange();
18612
+ }, options);
18613
+ }
18614
+ createSentinel(index2) {
18615
+ const sentinel = document.createElement("div");
18616
+ sentinel.setAttribute("data-viewport-sentinel", String(index2));
18617
+ sentinel.style.height = "0px";
18618
+ sentinel.style.overflow = "hidden";
18619
+ return sentinel;
18620
+ }
18621
+ observeSentinel(sentinel) {
18622
+ if (this.observer) {
18623
+ this.observer.observe(sentinel);
18624
+ }
18625
+ }
18626
+ unobserveSentinel(sentinel) {
18627
+ if (this.observer) {
18628
+ this.observer.unobserve(sentinel);
18629
+ }
18630
+ }
18631
+ getSentinelIndex(element) {
18632
+ const attr = element.getAttribute("data-viewport-sentinel");
18633
+ return attr !== null ? parseInt(attr, 10) : null;
18634
+ }
18635
+ updateVisibleRange() {
18636
+ if (this.visibleSentinels.size === 0) {
18637
+ return;
18638
+ }
18639
+ const indices = Array.from(this.visibleSentinels).sort((a5, b3) => a5 - b3);
18640
+ const visibleStart = indices[0];
18641
+ const visibleEnd = indices[indices.length - 1] + 1;
18642
+ const direction = this.determineScrollDirection(visibleStart);
18643
+ const bufferStart = Math.max(
18644
+ 0,
18645
+ visibleStart - Math.ceil(this.paddingAbove / this.pageSize) * this.pageSize
18646
+ );
18647
+ const bufferEnd = visibleEnd + Math.ceil(this.paddingBelow / this.pageSize) * this.pageSize;
18648
+ const rangeChanged = visibleStart !== this.lastVisibleRange.start || visibleEnd !== this.lastVisibleRange.end;
18649
+ if (rangeChanged) {
18650
+ this.lastVisibleRange = { start: visibleStart, end: visibleEnd };
18651
+ this.lastScrollDirection = direction;
18652
+ const event = {
18653
+ visibleRange: { start: visibleStart, end: visibleEnd },
18654
+ bufferRange: { start: bufferStart, end: bufferEnd },
18655
+ scrollDirection: direction,
18656
+ timestamp: Date.now()
18657
+ };
18658
+ this.log("Viewport changed", event);
18659
+ for (const callback2 of this.changeCallbacks) {
18660
+ callback2(event);
18661
+ }
18662
+ }
18663
+ }
18664
+ determineScrollDirection(newStart) {
18665
+ if (newStart < this.lastVisibleRange.start) {
18666
+ return "up";
18667
+ } else if (newStart > this.lastVisibleRange.start) {
18668
+ return "down";
18669
+ }
18670
+ return "none";
18671
+ }
18672
+ getVisibleRange() {
18673
+ return this.lastVisibleRange;
18674
+ }
18675
+ getBufferRange(totalItems) {
18676
+ const { start, end } = this.lastVisibleRange;
18677
+ const bufferStart = Math.max(0, start - this.paddingAbove);
18678
+ const bufferEnd = Math.min(totalItems, end + this.paddingBelow);
18679
+ return { start: bufferStart, end: bufferEnd };
18680
+ }
18681
+ getScrollDirection() {
18682
+ return this.lastScrollDirection;
18683
+ }
18684
+ getPageSize() {
18685
+ return this.pageSize;
18686
+ }
18687
+ onChange(callback2) {
18688
+ this.changeCallbacks.push(callback2);
18689
+ }
18690
+ offChange(callback2) {
18691
+ this.changeCallbacks = this.changeCallbacks.filter((cb) => cb !== callback2);
18692
+ }
18693
+ setPageSize(newPageSize) {
18694
+ if (newPageSize !== this.pageSize) {
18695
+ this.pageSize = newPageSize;
18696
+ this.log("Page size updated", { newPageSize });
18697
+ this.updateVisibleRange();
18698
+ }
18699
+ }
18700
+ destroy() {
18701
+ if (this.observer) {
18702
+ this.observer.disconnect();
18703
+ this.observer = null;
18704
+ }
18705
+ this.changeCallbacks = [];
18706
+ this.visibleSentinels.clear();
18707
+ this.log("ViewportManager destroyed");
18708
+ }
18709
+ log(message, data) {
18710
+ if (this.debug) {
18711
+ console.log(`[ViewportManager] ${message}`, data);
18712
+ }
18713
+ }
18714
+ };
18715
+
17934
18716
  // src/auto-ui/frontend/components/beam-app.ts
17935
18717
  var THEME_STORAGE_KEY = "beam-theme";
17936
18718
  var PROTOCOL_STORAGE_KEY = "beam-protocol";
@@ -17994,6 +18776,10 @@ var BeamApp = class extends i4 {
17994
18776
  this._promptArguments = {};
17995
18777
  this._renderedPrompt = "";
17996
18778
  this._resourceContent = "";
18779
+ this._splitPanels = [];
18780
+ this._methodPickerOpen = false;
18781
+ this._methodPickerPanelId = null;
18782
+ this._nextPanelId = 0;
17997
18783
  // Collection auto-subscription for ReactiveArray/Map/Set events
17998
18784
  this._collectionUnsubscribes = [];
17999
18785
  this._currentCollectionName = null;
@@ -18004,6 +18790,10 @@ var BeamApp = class extends i4 {
18004
18790
  this._fileIdCounter = 0;
18005
18791
  // Deep link URL for setOpenInAppUrl
18006
18792
  this._openInAppUrl = null;
18793
+ // PWA install state
18794
+ this._pwaInstallPrompt = null;
18795
+ this._pwaIsStandalone = false;
18796
+ this._pwaCurrentPhoton = null;
18007
18797
  this._handleDocumentClick = (e8) => {
18008
18798
  const path = e8.composedPath();
18009
18799
  if (this._showSettingsMenu) {
@@ -18021,11 +18811,11 @@ var BeamApp = class extends i4 {
18021
18811
  }
18022
18812
  };
18023
18813
  this._initialConnectDone = false;
18024
- this._handleHashChange = () => {
18814
+ this._handleRouteChange = () => {
18025
18815
  void (async () => {
18026
- const fullHash = window.location.hash.slice(1);
18027
- const [pathPart, queryPart] = fullHash.split("?");
18028
- const [photonName, methodName] = pathPart.split("/");
18816
+ const fullPath = window.location.pathname.slice(1);
18817
+ const queryPart = window.location.search.slice(1);
18818
+ const [photonName, methodName] = fullPath.split("/");
18029
18819
  let sharedParams = {};
18030
18820
  if (queryPart) {
18031
18821
  const params = new URLSearchParams(queryPart);
@@ -18042,6 +18832,9 @@ var BeamApp = class extends i4 {
18042
18832
  }
18043
18833
  }
18044
18834
  }
18835
+ this._methodPickerOpen = false;
18836
+ this._methodPickerPanelId = null;
18837
+ this._splitPanels = [];
18045
18838
  if (!photonName || photonName === "home") {
18046
18839
  this._selectedPhoton = null;
18047
18840
  this._selectedMethod = null;
@@ -18068,7 +18861,8 @@ var BeamApp = class extends i4 {
18068
18861
  return;
18069
18862
  }
18070
18863
  if (methodName && photon.methods) {
18071
- const method = photon.methods.find((m3) => m3.name === methodName);
18864
+ const [firstMethodName, secondMethodName] = methodName.split("+");
18865
+ const method = photon.methods.find((m3) => m3.name === firstMethodName);
18072
18866
  if (method) {
18073
18867
  if (Object.keys(sharedParams).length > 0) {
18074
18868
  this._sharedFormParams = sharedParams;
@@ -18079,6 +18873,22 @@ var BeamApp = class extends i4 {
18079
18873
  }
18080
18874
  this._selectedMethod = method;
18081
18875
  this._view = "form";
18876
+ if (secondMethodName) {
18877
+ const urlPath = location.pathname;
18878
+ const methodPart = urlPath.split("/").pop() || "";
18879
+ const methodNames = methodPart.split("+");
18880
+ for (let i7 = 1; i7 < methodNames.length; i7++) {
18881
+ const name2 = methodNames[i7];
18882
+ if (name2 === "source") {
18883
+ this._addPanel("source");
18884
+ } else {
18885
+ const panelMethod = photon.methods.find((m3) => m3.name === name2);
18886
+ if (panelMethod) {
18887
+ this._addPanel("method", panelMethod);
18888
+ }
18889
+ }
18890
+ }
18891
+ }
18082
18892
  if (Object.keys(sharedParams).length === 0) {
18083
18893
  this._maybeAutoInvoke(method);
18084
18894
  }
@@ -18098,18 +18908,15 @@ var BeamApp = class extends i4 {
18098
18908
  }
18099
18909
  })();
18100
18910
  };
18101
- this._handlePopState = () => {
18102
- void this._handleHashChange();
18103
- };
18104
18911
  this._goHome = () => {
18105
18912
  this._selectedPhoton = null;
18106
18913
  this._selectedMethod = null;
18107
18914
  this._lastResult = null;
18108
- this._updateHash();
18915
+ this._updateRoute();
18109
18916
  };
18110
18917
  this._runTests = async () => {
18111
18918
  if (!this._selectedPhoton || this._runningTests) return;
18112
- const testMethods = this._getTestMethods();
18919
+ const testMethods = await this._fetchTestList(this._selectedPhoton.name);
18113
18920
  if (testMethods.length === 0) return;
18114
18921
  this._runningTests = true;
18115
18922
  this._testResults = [];
@@ -18195,6 +19002,7 @@ var BeamApp = class extends i4 {
18195
19002
  `All photon tests: ${passed}/${total} passed`
18196
19003
  );
18197
19004
  };
19005
+ this._pickerDismissHandler = null;
18198
19006
  this._toggleSettingsMenu = () => {
18199
19007
  this._showSettingsMenu = !this._showSettingsMenu;
18200
19008
  };
@@ -18213,8 +19021,45 @@ var BeamApp = class extends i4 {
18213
19021
  };
18214
19022
  this._launchAsApp = () => {
18215
19023
  this._closeSettingsMenu();
18216
- if (this._selectedPhoton) {
18217
- window.open(`/api/pwa/app?photon=${encodeURIComponent(this._selectedPhoton.name)}`, "_blank");
19024
+ if (!this._selectedPhoton) return;
19025
+ if (this._pwaInstallPrompt) {
19026
+ void (async () => {
19027
+ try {
19028
+ await fetch("/api/pwa/configure", {
19029
+ method: "POST",
19030
+ headers: { "Content-Type": "application/json" },
19031
+ body: JSON.stringify({
19032
+ photon: this._selectedPhoton.name,
19033
+ port: location.port || "4100"
19034
+ }),
19035
+ signal: AbortSignal.timeout(5e3)
19036
+ });
19037
+ } catch {
19038
+ }
19039
+ const photonName = this._selectedPhoton.name;
19040
+ const onInstalled = () => {
19041
+ window.open(`/app/${encodeURIComponent(photonName)}`, "_blank");
19042
+ };
19043
+ window.addEventListener("appinstalled", onInstalled, { once: true });
19044
+ const result = await this._pwaInstallPrompt.prompt();
19045
+ this._log("info", `PWA install prompt result: ${result?.outcome}`);
19046
+ if (result?.outcome !== "accepted") {
19047
+ window.removeEventListener("appinstalled", onInstalled);
19048
+ }
19049
+ this._pwaInstallPrompt = null;
19050
+ })();
19051
+ } else {
19052
+ const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
19053
+ const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) || navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1;
19054
+ if (isSafari || isIOS) {
19055
+ const hint = isIOS ? 'Tap the Share button, then "Add to Home Screen"' : "Use File > Add to Dock to install this app";
19056
+ showToast(hint, "info");
19057
+ } else {
19058
+ showToast(
19059
+ "Install not ready yet \u2014 the service worker needs a moment to generate icons. Try again in a few seconds.",
19060
+ "info"
19061
+ );
19062
+ }
18218
19063
  }
18219
19064
  };
18220
19065
  this._handleRemove = async () => {
@@ -18380,7 +19225,7 @@ var BeamApp = class extends i4 {
18380
19225
  }
18381
19226
  }
18382
19227
  this._view = "config";
18383
- this._updateHash();
19228
+ this._updateRoute();
18384
19229
  };
18385
19230
  this._toggleRememberValues = () => {
18386
19231
  this._rememberFormValues = !this._rememberFormValues;
@@ -18965,7 +19810,7 @@ var BeamApp = class extends i4 {
18965
19810
  }
18966
19811
  if (e8.key === "Enter" && this._selectedMethod && this._view === "list") {
18967
19812
  this._view = "form";
18968
- this._updateHash();
19813
+ this._updateRoute();
18969
19814
  return;
18970
19815
  }
18971
19816
  if (e8.key === "r") {
@@ -19001,12 +19846,12 @@ var BeamApp = class extends i4 {
19001
19846
  this._focusMode = !this._focusMode;
19002
19847
  if (this._focusMode) {
19003
19848
  this.classList.add("focus-mode");
19004
- const hash2 = window.location.hash.replace(/\?focus=1/, "");
19005
- history.replaceState(null, "", hash2 + "?focus=1");
19849
+ const path = window.location.pathname;
19850
+ history.replaceState(null, "", path + "?focus=1");
19006
19851
  } else {
19007
19852
  this.classList.remove("focus-mode");
19008
- const hash2 = window.location.hash.replace(/\?focus=1/, "");
19009
- history.replaceState(null, "", hash2 || "#");
19853
+ const path = window.location.pathname;
19854
+ history.replaceState(null, "", path);
19010
19855
  }
19011
19856
  };
19012
19857
  this._handleFullscreen = () => {
@@ -19105,9 +19950,25 @@ var BeamApp = class extends i4 {
19105
19950
  case "fullscreen":
19106
19951
  this._handleFullscreen();
19107
19952
  break;
19953
+ case "install-app":
19954
+ this._launchAsApp();
19955
+ break;
19956
+ }
19957
+ break;
19958
+ }
19959
+ case "split-view:add":
19960
+ this._showMethodPicker();
19961
+ break;
19962
+ case "split-view:change": {
19963
+ const method = this._selectedPhoton?.methods?.find((m3) => m3.name === e8.detail.method);
19964
+ if (method && this._splitPanels.length > 0) {
19965
+ this._changePanelMethod(this._splitPanels[0].id, method);
19108
19966
  }
19109
19967
  break;
19110
19968
  }
19969
+ case "split-view:remove":
19970
+ this._closeSecondPanel();
19971
+ break;
19111
19972
  }
19112
19973
  };
19113
19974
  this._startEditingDescription = () => {
@@ -19307,7 +20168,7 @@ var BeamApp = class extends i4 {
19307
20168
  if (method) {
19308
20169
  this._selectedMethod = method;
19309
20170
  this._view = "form";
19310
- this._updateHash();
20171
+ this._updateRoute();
19311
20172
  this._log("info", `Starting ${targetName}/${action}...`);
19312
20173
  showToast(`Starting ${action}...`, "info");
19313
20174
  } else {
@@ -19334,15 +20195,21 @@ var BeamApp = class extends i4 {
19334
20195
  */
19335
20196
  _handleGlobalMethodSelect(photon, method) {
19336
20197
  this._teardownActiveCustomUI();
20198
+ if (this._selectedPhoton?.name !== photon.name) {
20199
+ this._closeSecondPanel();
20200
+ }
19337
20201
  this._selectedPhoton = photon;
19338
20202
  if (this._willAutoInvoke(method)) {
19339
20203
  this._isExecuting = true;
19340
20204
  }
19341
20205
  this._selectedMethod = method;
19342
20206
  this._view = "form";
19343
- this._updateHash();
20207
+ this._updateRoute();
19344
20208
  this._maybeAutoInvoke(method);
19345
20209
  }
20210
+ get _splitViewEnabled() {
20211
+ return this._splitPanels.length > 0;
20212
+ }
19346
20213
  connectedCallback() {
19347
20214
  super.connectedCallback();
19348
20215
  const savedTheme = localStorage.getItem(THEME_STORAGE_KEY);
@@ -19374,20 +20241,114 @@ var BeamApp = class extends i4 {
19374
20241
  }
19375
20242
  document.addEventListener("click", this._handleDocumentClick);
19376
20243
  void this._connectMCP();
19377
- window.addEventListener("hashchange", this._handleHashChange);
19378
- window.addEventListener("popstate", this._handlePopState);
20244
+ this._setupNotificationHandlers();
20245
+ window.addEventListener("popstate", this._handleRouteChange);
19379
20246
  window.addEventListener("message", this._handleBridgeMessage);
19380
20247
  window.addEventListener("keydown", this._handleKeydown);
20248
+ this._initPWA();
19381
20249
  }
19382
20250
  disconnectedCallback() {
19383
20251
  super.disconnectedCallback();
19384
- window.removeEventListener("hashchange", this._handleHashChange);
19385
- window.removeEventListener("popstate", this._handlePopState);
20252
+ window.removeEventListener("popstate", this._handleRouteChange);
19386
20253
  window.removeEventListener("message", this._handleBridgeMessage);
19387
20254
  window.removeEventListener("keydown", this._handleKeydown);
19388
20255
  document.removeEventListener("click", this._handleDocumentClick);
19389
20256
  this._cleanupCollectionSubscriptions();
19390
20257
  }
20258
+ willUpdate(changedProperties) {
20259
+ super.willUpdate(changedProperties);
20260
+ if (changedProperties.has("_selectedPhoton")) {
20261
+ const name2 = this._selectedPhoton?.name || null;
20262
+ if (name2 !== this._pwaCurrentPhoton) {
20263
+ this._pwaCurrentPhoton = name2;
20264
+ if (name2) {
20265
+ this._setupPWAManifest(name2);
20266
+ }
20267
+ }
20268
+ }
20269
+ }
20270
+ // ---------------------------------------------------------------------------
20271
+ // PWA — Progressive Web App support
20272
+ // ---------------------------------------------------------------------------
20273
+ _initPWA() {
20274
+ this._pwaIsStandalone = window.matchMedia("(display-mode: standalone)").matches || window.navigator.standalone === true;
20275
+ if ("serviceWorker" in navigator) {
20276
+ 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));
20277
+ }
20278
+ window.addEventListener("beforeinstallprompt", (e8) => {
20279
+ this._pwaInstallPrompt = e8;
20280
+ });
20281
+ window.addEventListener("appinstalled", () => {
20282
+ this._pwaInstallPrompt = null;
20283
+ this._log("info", "PWA app installed");
20284
+ });
20285
+ }
20286
+ /**
20287
+ * Set up notification handlers for photon notifications.
20288
+ * When a notification arrives for a photon that cares about that event type,
20289
+ * bring the window to focus (especially important for PWAs/background tabs).
20290
+ */
20291
+ _setupNotificationHandlers() {
20292
+ mcpClient.on("photon-notification", (notification) => {
20293
+ const { photon, type, priority } = notification;
20294
+ if (!photon || !type) return;
20295
+ const sidebarElement = this.querySelector("beam-sidebar");
20296
+ if (sidebarElement && sidebarElement.isNotificationWatched) {
20297
+ const isWatched = sidebarElement.isNotificationWatched(photon, type);
20298
+ if (isWatched) {
20299
+ window.focus();
20300
+ console.log(`\u{1F4E1} Window focused for ${photon} notification: ${type}`);
20301
+ sidebarElement.updatePhotonWarmth?.(photon);
20302
+ }
20303
+ }
20304
+ });
20305
+ }
20306
+ /**
20307
+ * Set up the PWA manifest & icons for the currently selected photon.
20308
+ * Called whenever the selected photon changes. The manifest points to
20309
+ * /api/pwa/icon-png URLs which the service worker intercepts and renders
20310
+ * as real PNGs via OffscreenCanvas — no client-side blob URL needed.
20311
+ */
20312
+ _setupPWAManifest(photonName) {
20313
+ let manifestLink = document.head.querySelector('link[rel="manifest"]');
20314
+ if (!manifestLink) {
20315
+ manifestLink = document.createElement("link");
20316
+ manifestLink.rel = "manifest";
20317
+ document.head.appendChild(manifestLink);
20318
+ }
20319
+ manifestLink.href = `/api/pwa/manifest.json?photon=${encodeURIComponent(photonName)}`;
20320
+ let appleIcon = document.head.querySelector('link[rel="apple-touch-icon"]');
20321
+ if (!appleIcon) {
20322
+ appleIcon = document.createElement("link");
20323
+ appleIcon.rel = "apple-touch-icon";
20324
+ document.head.appendChild(appleIcon);
20325
+ }
20326
+ appleIcon.href = `/api/pwa/icon?photon=${encodeURIComponent(photonName)}`;
20327
+ let appleTitleMeta = document.head.querySelector(
20328
+ 'meta[name="apple-mobile-web-app-title"]'
20329
+ );
20330
+ if (!appleTitleMeta) {
20331
+ appleTitleMeta = document.createElement("meta");
20332
+ appleTitleMeta.name = "apple-mobile-web-app-title";
20333
+ document.head.appendChild(appleTitleMeta);
20334
+ }
20335
+ appleTitleMeta.content = photonName;
20336
+ this._prewarmIconCache(photonName);
20337
+ this._log("info", `PWA manifest set for "${photonName}"`, true);
20338
+ }
20339
+ /**
20340
+ * Pre-warm the service worker icon cache by fetching the PNG icon URLs.
20341
+ * This ensures the rendered emoji icons are cached before the user
20342
+ * attempts to install the PWA, improving the install dialog appearance.
20343
+ */
20344
+ _prewarmIconCache(photonName) {
20345
+ const sizes = [192, 512];
20346
+ sizes.forEach((size) => {
20347
+ const url2 = `/api/pwa/icon-png?photon=${encodeURIComponent(photonName)}&size=${size}`;
20348
+ fetch(url2, { mode: "no-cors" }).catch(() => {
20349
+ });
20350
+ });
20351
+ }
19391
20352
  async _connectMCP() {
19392
20353
  try {
19393
20354
  mcpClient.on("connect", () => {
@@ -19415,12 +20376,24 @@ var BeamApp = class extends i4 {
19415
20376
  }
19416
20377
  this._initialConnectDone = true;
19417
20378
  void this._checkForUpdates();
19418
- if (window.location.hash) {
19419
- void this._handleHashChange();
20379
+ if (window.location.pathname !== "/") {
20380
+ void this._handleRouteChange();
19420
20381
  } else if (!this._selectedPhoton && this._photons.length > 0) {
19421
20382
  const firstUserPhoton = this._photons.find((p5) => !p5.internal);
19422
- if (firstUserPhoton) this._selectedPhoton = firstUserPhoton;
19423
- this._updateHash(true);
20383
+ if (firstUserPhoton) {
20384
+ this._selectedPhoton = firstUserPhoton;
20385
+ if (firstUserPhoton.isApp && firstUserPhoton.appEntry) {
20386
+ if (this._willAutoInvoke(firstUserPhoton.appEntry)) {
20387
+ this._isExecuting = true;
20388
+ }
20389
+ this._selectedMethod = firstUserPhoton.appEntry;
20390
+ this._view = "form";
20391
+ this._maybeAutoInvoke(firstUserPhoton.appEntry);
20392
+ } else {
20393
+ this._view = "list";
20394
+ }
20395
+ }
20396
+ this._updateRoute(true);
19424
20397
  }
19425
20398
  })();
19426
20399
  });
@@ -19439,15 +20412,37 @@ var BeamApp = class extends i4 {
19439
20412
  this._photons = photons;
19440
20413
  this._externalMCPs = externalMCPs;
19441
20414
  this._addUnconfiguredPhotons();
19442
- if (!this._selectedPhoton && window.location.hash !== "#home") {
19443
- const newUserPhoton = this._photons.find(
19444
- (p5) => !p5.internal && p5.configured && !prevNames.has(p5.name)
19445
- );
19446
- if (newUserPhoton) {
19447
- this._selectedPhoton = newUserPhoton;
19448
- this._welcomePhase = "welcome";
19449
- this._view = "list";
19450
- this._updateHash(true);
20415
+ if (this._selectedPhoton) {
20416
+ const updated = this._photons.find((p5) => p5.name === this._selectedPhoton?.name);
20417
+ if (updated) {
20418
+ const wasApp = this._selectedPhoton.isApp;
20419
+ this._selectedPhoton = updated;
20420
+ if (!wasApp && updated.isApp && updated.appEntry && this._view === "list") {
20421
+ if (this._willAutoInvoke(updated.appEntry)) {
20422
+ this._isExecuting = true;
20423
+ }
20424
+ this._selectedMethod = updated.appEntry;
20425
+ this._view = "form";
20426
+ this._updateRoute(true);
20427
+ this._maybeAutoInvoke(updated.appEntry);
20428
+ }
20429
+ }
20430
+ }
20431
+ if (!this._selectedPhoton && window.location.pathname !== "/") {
20432
+ const pathPhotonName = window.location.pathname.slice(1).split("/")[0];
20433
+ const routePhoton = pathPhotonName ? this._photons.find((p5) => p5.name === pathPhotonName) : null;
20434
+ if (routePhoton) {
20435
+ void this._handleRouteChange();
20436
+ } else {
20437
+ const newUserPhoton = this._photons.find(
20438
+ (p5) => !p5.internal && p5.configured && !prevNames.has(p5.name)
20439
+ );
20440
+ if (newUserPhoton) {
20441
+ this._selectedPhoton = newUserPhoton;
20442
+ this._welcomePhase = "welcome";
20443
+ this._view = "list";
20444
+ this._updateRoute(true);
20445
+ }
19451
20446
  }
19452
20447
  }
19453
20448
  })();
@@ -19511,7 +20506,7 @@ var BeamApp = class extends i4 {
19511
20506
  this._selectedPhoton = updated;
19512
20507
  }
19513
20508
  } else {
19514
- if (window.location.hash === "#home") return;
20509
+ if (window.location.pathname === "/") return;
19515
20510
  const newUserPhoton = this._photons.find(
19516
20511
  (p5) => !p5.internal && p5.configured && !prevNames.has(p5.name)
19517
20512
  );
@@ -19540,7 +20535,7 @@ var BeamApp = class extends i4 {
19540
20535
  } else {
19541
20536
  this._view = "list";
19542
20537
  }
19543
- this._updateHash(true);
20538
+ this._updateRoute(true);
19544
20539
  }
19545
20540
  }
19546
20541
  }
@@ -19592,7 +20587,7 @@ var BeamApp = class extends i4 {
19592
20587
  } else {
19593
20588
  this._view = "list";
19594
20589
  }
19595
- this._updateHash(true);
20590
+ this._updateRoute(true);
19596
20591
  }
19597
20592
  }
19598
20593
  });
@@ -19703,20 +20698,27 @@ var BeamApp = class extends i4 {
19703
20698
  }
19704
20699
  }
19705
20700
  }
19706
- _updateHash(replace2 = false) {
19707
- let hash2;
20701
+ _updateRoute(replace2 = false) {
20702
+ let path;
19708
20703
  if (!this._selectedPhoton) {
19709
- hash2 = "home";
20704
+ path = "/";
19710
20705
  } else {
19711
- hash2 = this._selectedPhoton.name;
20706
+ path = "/" + this._selectedPhoton.name;
19712
20707
  if (this._selectedMethod) {
19713
- hash2 += `/${this._selectedMethod.name}`;
20708
+ path += `/${this._selectedMethod.name}`;
20709
+ for (const panel of this._splitPanels) {
20710
+ if (panel.type === "method" && panel.method) {
20711
+ path += `+${panel.method.name}`;
20712
+ } else if (panel.type === "source") {
20713
+ path += `+source`;
20714
+ }
20715
+ }
19714
20716
  }
19715
20717
  }
19716
20718
  if (replace2) {
19717
- history.replaceState(null, "", `#${hash2}`);
20719
+ history.replaceState(null, "", path);
19718
20720
  } else {
19719
- history.pushState(null, "", `#${hash2}`);
20721
+ history.pushState(null, "", path);
19720
20722
  }
19721
20723
  }
19722
20724
  /**
@@ -19767,6 +20769,21 @@ var BeamApp = class extends i4 {
19767
20769
  if (!this._selectedPhoton?.methods) return [];
19768
20770
  return this._selectedPhoton.methods.filter((m3) => m3.name.startsWith("test_") || m3.name.startsWith("test")).map((m3) => m3.name);
19769
20771
  }
20772
+ /** Fetch all tests (external .test.ts + inline) from the server */
20773
+ async _fetchTestList(photonName) {
20774
+ try {
20775
+ const res = await fetch(`/api/test/list?photon=${encodeURIComponent(photonName)}`, {
20776
+ signal: AbortSignal.timeout(5e3)
20777
+ });
20778
+ if (!res.ok) return this._getTestMethods();
20779
+ const data = await res.json();
20780
+ if (data.tests && data.tests.length > 0) {
20781
+ return data.tests.map((t8) => t8.name);
20782
+ }
20783
+ } catch {
20784
+ }
20785
+ return this._getTestMethods();
20786
+ }
19770
20787
  _getAllTestMethods() {
19771
20788
  const results = [];
19772
20789
  for (const p5 of this._photons) {
@@ -20090,7 +21107,7 @@ var BeamApp = class extends i4 {
20090
21107
  }}
20091
21108
  @diagnostics=${() => {
20092
21109
  this._view = "diagnostics";
20093
- this._updateHash();
21110
+ this._updateRoute();
20094
21111
  }}
20095
21112
  @open-studio=${(e8) => {
20096
21113
  const photon = this._photons.find((p5) => p5.name === e8.detail.photonName);
@@ -20110,7 +21127,39 @@ var BeamApp = class extends i4 {
20110
21127
  ></beam-sidebar>
20111
21128
  </nav>
20112
21129
 
20113
- <main class="main-area" id="main-content" tabindex="-1" aria-label="Main content">
21130
+ <main
21131
+ class="main-area"
21132
+ id="main-content"
21133
+ tabindex="-1"
21134
+ aria-label="Main content"
21135
+ style="${this._splitViewEnabled ? "overflow: hidden !important;" : ""}"
21136
+ >
21137
+ ${this._selectedPhoton && this._selectedMethod ? b2`<button
21138
+ class="beam-back-btn"
21139
+ @click=${() => this._handleBackFromMethod()}
21140
+ @mouseenter=${(e8) => {
21141
+ e8.target.style.color = "var(--t-primary)";
21142
+ e8.target.style.borderColor = "var(--accent-primary)";
21143
+ }}
21144
+ @mouseleave=${(e8) => {
21145
+ e8.target.style.color = "var(--t-muted)";
21146
+ e8.target.style.borderColor = "var(--border-glass)";
21147
+ }}
21148
+ title="Back to ${this._selectedPhoton.name}"
21149
+ >
21150
+ <svg
21151
+ width="16"
21152
+ height="16"
21153
+ viewBox="0 0 24 24"
21154
+ fill="none"
21155
+ stroke="currentColor"
21156
+ stroke-width="2"
21157
+ stroke-linecap="round"
21158
+ stroke-linejoin="round"
21159
+ >
21160
+ <path d="m15 18-6-6 6-6" />
21161
+ </svg>
21162
+ </button>` : ""}
20114
21163
  ${this._selectedPhoton ? b2`<button
20115
21164
  class="beam-fullscreen-btn"
20116
21165
  @click=${this._toggleFocusMode}
@@ -20546,7 +21595,7 @@ var BeamApp = class extends i4 {
20546
21595
  }}
20547
21596
  @click=${() => {
20548
21597
  this._view = "diagnostics";
20549
- this._updateHash();
21598
+ this._updateRoute();
20550
21599
  }}
20551
21600
  >
20552
21601
  <span style="font-size: 1rem; width: 20px; text-align: center;">🔍</span>
@@ -20755,7 +21804,7 @@ ${photon.errorMessage || "Unknown error"}</pre
20755
21804
  @select=${(e8) => {
20756
21805
  this._selectedMethod = e8.detail.method;
20757
21806
  this._view = "form";
20758
- this._updateHash();
21807
+ this._updateRoute();
20759
21808
  }}
20760
21809
  ></method-card>
20761
21810
  `
@@ -20772,7 +21821,7 @@ ${photon.errorMessage || "Unknown error"}</pre
20772
21821
  if (this._selectedMethod.linkedUi) {
20773
21822
  const isAppMain = this._selectedPhoton.isApp && this._selectedMethod.name === "main";
20774
21823
  const otherMethods = isAppMain ? this._getVisibleMethods().filter((m3) => m3.name !== "main") : [];
20775
- const isExternalMCP2 = this._selectedPhoton.isExternalMCP;
21824
+ const isExternalMCP = this._selectedPhoton.isExternalMCP;
20776
21825
  const appRenderer = this._isExecuting ? b2`
20777
21826
  <div
20778
21827
  style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; height: calc(100vh - 140px);"
@@ -20782,7 +21831,7 @@ ${photon.errorMessage || "Unknown error"}</pre
20782
21831
  ${this._progress?.message || "Starting up\u2026"}
20783
21832
  </span>
20784
21833
  </div>
20785
- ` : isExternalMCP2 ? b2`
21834
+ ` : isExternalMCP ? b2`
20786
21835
  <mcp-app-renderer
20787
21836
  .mcpName=${this._selectedPhoton.name}
20788
21837
  .appUri=${`ui://${this._selectedPhoton.name}/${this._selectedMethod.linkedUi}`}
@@ -20801,7 +21850,48 @@ ${photon.errorMessage || "Unknown error"}</pre
20801
21850
  ></custom-ui-renderer>
20802
21851
  `;
20803
21852
  if (isAppMain) {
21853
+ if (this._splitPanels.length > 0) {
21854
+ return b2`
21855
+ <div style="display: flex; gap: 1px; height: calc(100vh - 60px); overflow: hidden;">
21856
+ <!-- App Panel (primary) — no title bar, app has its own chrome -->
21857
+ <div style="flex: 1; min-height: 0; overflow: hidden;">${appRenderer}</div>
21858
+
21859
+ <!-- Additional Panels -->
21860
+ ${this._splitPanels.map(
21861
+ (panel) => b2`
21862
+ <div style="flex: 1; min-height: 0; background: var(--bg-panel);">
21863
+ ${panel.type === "method" ? this._renderSinglePanel(this._buildAdditionalPanelOpts(panel)) : this._renderSourcePanel(panel.id)}
21864
+ </div>
21865
+ `
21866
+ )}
21867
+ </div>
21868
+ `;
21869
+ }
20804
21870
  return b2`
21871
+ <!-- Floating add-panel button for app view — sits next to focus button -->
21872
+ <div
21873
+ style="position: sticky; top: calc(-1 * var(--space-lg)); float: right; z-index: 100; margin-top: calc(-1 * var(--space-lg)); margin-right: 6px;"
21874
+ >
21875
+ <button
21876
+ @click=${() => {
21877
+ this._methodPickerOpen = !this._methodPickerOpen;
21878
+ this._methodPickerPanelId = null;
21879
+ }}
21880
+ 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);"
21881
+ @mouseenter=${(e8) => {
21882
+ e8.target.style.color = "var(--accent-secondary)";
21883
+ e8.target.style.borderColor = "var(--accent-secondary)";
21884
+ }}
21885
+ @mouseleave=${(e8) => {
21886
+ e8.target.style.color = "var(--t-muted)";
21887
+ e8.target.style.borderColor = "var(--border-glass)";
21888
+ }}
21889
+ title="Add panel"
21890
+ >
21891
+ +
21892
+ </button>
21893
+ ${this._methodPickerOpen && this._methodPickerPanelId === null ? this._renderMethodPickerPopover() : ""}
21894
+ </div>
20805
21895
  <app-layout
20806
21896
  .photonName=${this._selectedPhoton.name}
20807
21897
  .photonIcon=${this._selectedPhoton.appEntry?.icon || "\u{1F4F1}"}
@@ -20844,61 +21934,72 @@ ${photon.errorMessage || "Unknown error"}</pre
20844
21934
  </app-layout>
20845
21935
  `;
20846
21936
  }
21937
+ if (this._splitPanels.length > 0) {
21938
+ return b2`
21939
+ <div style="display: flex; gap: 1px; height: calc(100vh - 60px); overflow: hidden;">
21940
+ <!-- Linked UI Panel (primary) -->
21941
+ <div
21942
+ style="flex: 1; min-height: 0; display: flex; flex-direction: column; position: relative;"
21943
+ >
21944
+ <div
21945
+ 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;"
21946
+ >
21947
+ <span style="font-size: 12px; font-weight: 500; color: var(--t-primary);"
21948
+ >${this._selectedMethod.name}</span
21949
+ >
21950
+ <div style="position: relative; flex-shrink: 0;">
21951
+ <button
21952
+ @click=${() => {
21953
+ this._methodPickerOpen = !this._methodPickerOpen;
21954
+ this._methodPickerPanelId = null;
21955
+ }}
21956
+ 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;"
21957
+ title="Add panel"
21958
+ >
21959
+ +
21960
+ </button>
21961
+ ${this._methodPickerOpen && this._methodPickerPanelId === null ? this._renderMethodPickerPopover() : ""}
21962
+ </div>
21963
+ </div>
21964
+ <div style="flex: 1; min-height: 0; overflow: hidden;">${appRenderer}</div>
21965
+ </div>
21966
+
21967
+ <!-- Additional Panels -->
21968
+ ${this._splitPanels.map(
21969
+ (panel) => b2`
21970
+ <div style="flex: 1; min-height: 0; background: var(--bg-panel);">
21971
+ ${panel.type === "method" ? this._renderSinglePanel(this._buildAdditionalPanelOpts(panel)) : this._renderSourcePanel(panel.id)}
21972
+ </div>
21973
+ `
21974
+ )}
21975
+ </div>
21976
+ `;
21977
+ }
20847
21978
  return b2`
20848
- <context-bar
20849
- .photon=${this._selectedPhoton}
20850
- .breadcrumbs=${[
20851
- {
20852
- label: this._currentInstance !== "default" ? `${this._selectedPhoton.name}:${this._currentInstance}` : this._selectedPhoton.name,
20853
- action: "back"
20854
- },
20855
- { label: this._selectedMethod.name }
20856
- ]}
20857
- .live=${this._currentCollectionName !== null}
20858
- .showEdit=${false}
20859
- .showConfigure=${false}
20860
- .showCopyConfig=${false}
20861
- .overflowItems=${[]}
20862
- .instanceSelectorMode=${this._instanceSelectorMode}
20863
- .autoInstance=${this._autoInstance}
20864
- @context-action=${this._handleContextAction}
20865
- ></context-bar>
20866
- <div
20867
- class="glass-panel"
20868
- style="padding: 0; overflow: hidden; min-height: calc(100vh - 80px); margin-top: var(--space-md);"
20869
- >
20870
- ${appRenderer}
21979
+ <div style="position: relative;">
21980
+ <div style="position: absolute; top: 16px; right: 16px; z-index: 50;">
21981
+ <button
21982
+ @click=${() => {
21983
+ this._methodPickerOpen = !this._methodPickerOpen;
21984
+ this._methodPickerPanelId = null;
21985
+ }}
21986
+ 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);"
21987
+ title="Add panel"
21988
+ >
21989
+ +
21990
+ </button>
21991
+ ${this._methodPickerOpen && this._methodPickerPanelId === null ? this._renderMethodPickerPopover() : ""}
21992
+ </div>
21993
+ <div
21994
+ class="glass-panel"
21995
+ style="padding: 0; overflow: hidden; min-height: calc(100vh - 80px); margin-top: var(--space-md);"
21996
+ >
21997
+ ${appRenderer}
21998
+ </div>
20871
21999
  </div>
20872
22000
  `;
20873
22001
  }
20874
- const isExternalMCP = this._selectedPhoton.isExternalMCP;
20875
- return b2`
20876
- <context-bar
20877
- .photon=${this._selectedPhoton}
20878
- .breadcrumbs=${[
20879
- {
20880
- label: this._currentInstance !== "default" ? `${this._selectedPhoton.name}:${this._currentInstance}` : this._selectedPhoton.name,
20881
- action: "back"
20882
- },
20883
- { label: this._selectedMethod.name }
20884
- ]}
20885
- .live=${this._currentCollectionName !== null}
20886
- .showEdit=${false}
20887
- .showConfigure=${false}
20888
- .showCopyConfig=${false}
20889
- .overflowItems=${this._buildOverflowItems({
20890
- showRefresh: !isExternalMCP,
20891
- showRename: false,
20892
- showViewSource: false,
20893
- showDelete: false,
20894
- showHelp: !isExternalMCP
20895
- })}
20896
- .instanceSelectorMode=${this._instanceSelectorMode}
20897
- .autoInstance=${this._autoInstance}
20898
- @context-action=${this._handleContextAction}
20899
- ></context-bar>
20900
- ${this._renderMethodContent()}
20901
- `;
22002
+ return b2` ${this._renderMethodContent()} `;
20902
22003
  }
20903
22004
  return b2`
20904
22005
  ${this._renderPhotonToolbar()} ${this._editingIcon ? this._renderEmojiPicker() : ""}
@@ -21026,6 +22127,7 @@ ${photon.errorMessage || "Unknown error"}</pre
21026
22127
  }
21027
22128
  }
21028
22129
  async _handlePhotonSelect(e8) {
22130
+ this._closeSecondPanel();
21029
22131
  this._selectedPhoton = e8.detail.photon;
21030
22132
  this._selectedMethod = null;
21031
22133
  this._lastResult = null;
@@ -21033,12 +22135,12 @@ ${photon.errorMessage || "Unknown error"}</pre
21033
22135
  this._currentInstance = "default";
21034
22136
  if (this._selectedPhoton.configured === false) {
21035
22137
  this._view = "config";
21036
- this._updateHash();
22138
+ this._updateRoute();
21037
22139
  return;
21038
22140
  }
21039
22141
  if (this._selectedPhoton.isExternalMCP && this._selectedPhoton.hasMcpApp) {
21040
22142
  this._view = "mcp-app";
21041
- this._updateHash();
22143
+ this._updateRoute();
21042
22144
  return;
21043
22145
  }
21044
22146
  if (this._selectedPhoton.stateful && this._selectedPhoton.configured) {
@@ -21065,13 +22167,13 @@ ${photon.errorMessage || "Unknown error"}</pre
21065
22167
  }
21066
22168
  this._selectedMethod = this._selectedPhoton.appEntry;
21067
22169
  this._view = "form";
21068
- this._updateHash();
22170
+ this._updateRoute();
21069
22171
  this._maybeAutoInvoke(this._selectedPhoton.appEntry);
21070
22172
  return;
21071
22173
  } else {
21072
22174
  this._view = "list";
21073
22175
  }
21074
- this._updateHash();
22176
+ this._updateRoute();
21075
22177
  }
21076
22178
  /** Fetch available instances for a stateful photon from the server */
21077
22179
  async _fetchInstances(photonName) {
@@ -21114,7 +22216,7 @@ ${photon.errorMessage || "Unknown error"}</pre
21114
22216
  /**
21115
22217
  * Trigger instance switch via MCP elicitation (calls _use without name).
21116
22218
  * The transport layer shows an elicitation modal with available instances.
21117
- * After _use succeeds, the transport broadcasts photon/state-changed which
22219
+ * After _use succeeds, the transport broadcasts state-changed which
21118
22220
  * triggers _silentRefresh() to update the result and notifies custom UIs.
21119
22221
  */
21120
22222
  async _switchInstance() {
@@ -21150,7 +22252,7 @@ ${photon.errorMessage || "Unknown error"}</pre
21150
22252
  this._selectedMethod = e8.detail.method;
21151
22253
  this._lastResult = null;
21152
22254
  this._view = "form";
21153
- this._updateHash();
22255
+ this._updateRoute();
21154
22256
  this._maybeAutoInvoke(e8.detail.method);
21155
22257
  }
21156
22258
  /**
@@ -21212,57 +22314,507 @@ ${photon.errorMessage || "Unknown error"}</pre
21212
22314
  </div>
21213
22315
  `;
21214
22316
  }
22317
+ if (this._splitPanels.length > 0) {
22318
+ return b2`
22319
+ <div style="display: flex; gap: 1px; height: 100%; overflow: hidden;">
22320
+ <!-- Primary Panel -->
22321
+ <div style="flex: 1; min-height: 0; background: var(--bg-panel);">
22322
+ ${this._renderSinglePanel({
22323
+ photon: this._selectedPhoton,
22324
+ method: this._selectedMethod,
22325
+ result: this._lastResult,
22326
+ executing: this._isExecuting,
22327
+ progress: this._progress,
22328
+ formParams: this._lastFormParams,
22329
+ onSubmit: (e8) => void this._handleExecute(e8),
22330
+ onCancel: () => this._handleBackFromMethod(),
22331
+ panelLabel: "Primary",
22332
+ instance: this._currentInstance,
22333
+ instances: this._instances,
22334
+ onInstanceChange: (instance) => this._handleLeftPanelInstanceChange(instance),
22335
+ allMethods: this._selectedPhoton?.methods || [],
22336
+ onMethodChange: (method) => this._handleLeftPanelMethodChange(method),
22337
+ panelSide: "primary",
22338
+ onPanelAction: (action) => this._handlePrimaryPanelAction(action),
22339
+ onInstanceAction: (detail) => void this._handleInstanceAction(detail),
22340
+ isStateful: !!this._selectedPhoton?.stateful,
22341
+ isLive: this._currentCollectionName !== null,
22342
+ overflowItems: this._buildOverflowItems({
22343
+ showRefresh: !this._selectedPhoton?.isExternalMCP,
22344
+ showRename: false,
22345
+ showViewSource: false,
22346
+ showDelete: false,
22347
+ showHelp: !this._selectedPhoton?.isExternalMCP
22348
+ }),
22349
+ onOverflowSelect: (id2) => this._handleOverflowAction(id2)
22350
+ })}
22351
+ </div>
22352
+
22353
+ <!-- Additional Panels -->
22354
+ ${this._splitPanels.map(
22355
+ (panel) => b2`
22356
+ <div style="flex: 1; min-height: 0; background: var(--bg-panel);">
22357
+ ${panel.type === "method" ? this._renderSinglePanel(this._buildAdditionalPanelOpts(panel)) : this._renderSourcePanel(panel.id)}
22358
+ </div>
22359
+ `
22360
+ )}
22361
+ </div>
22362
+ `;
22363
+ }
22364
+ return this._renderSinglePanel({
22365
+ photon: this._selectedPhoton,
22366
+ method: this._selectedMethod,
22367
+ result: this._lastResult,
22368
+ executing: this._isExecuting,
22369
+ progress: this._progress,
22370
+ formParams: this._lastFormParams,
22371
+ onSubmit: (e8) => void this._handleExecute(e8),
22372
+ onCancel: () => this._handleBackFromMethod(),
22373
+ panelLabel: "Primary",
22374
+ instance: this._currentInstance,
22375
+ instances: this._instances,
22376
+ allMethods: this._selectedPhoton?.methods || [],
22377
+ onMethodChange: (method) => {
22378
+ this._selectedMethod = method;
22379
+ this._lastResult = null;
22380
+ this._lastFormParams = {};
22381
+ if (this._willAutoInvoke(method)) {
22382
+ void this._handleExecute(new CustomEvent("execute", { detail: { args: {} } }));
22383
+ }
22384
+ this._updateRoute();
22385
+ },
22386
+ panelSide: "primary",
22387
+ onPanelAction: (action) => this._handlePrimaryPanelAction(action),
22388
+ onInstanceAction: (detail) => void this._handleInstanceAction(detail),
22389
+ isStateful: !!this._selectedPhoton?.stateful,
22390
+ isLive: this._currentCollectionName !== null,
22391
+ overflowItems: this._buildOverflowItems({
22392
+ showRefresh: !this._selectedPhoton?.isExternalMCP,
22393
+ showRename: false,
22394
+ showViewSource: false,
22395
+ showDelete: false,
22396
+ showHelp: !this._selectedPhoton?.isExternalMCP
22397
+ }),
22398
+ onOverflowSelect: (id2) => this._handleOverflowAction(id2)
22399
+ });
22400
+ }
22401
+ /** Render a single panel with self-contained header */
22402
+ _renderSinglePanel(opts) {
22403
+ const isSplit = this._splitPanels.length > 0;
21215
22404
  return b2`
21216
- <div class="glass-panel method-detail">
21217
- <h2>${this._selectedMethod.name}</h2>
21218
- ${this._renderDescription(this._selectedMethod.description)}
21219
- <invoke-form
21220
- .params=${this._selectedMethod.params}
21221
- .loading=${this._isExecuting}
21222
- .photonName=${this._selectedPhoton.name}
21223
- .methodName=${this._selectedMethod.name}
21224
- .rememberValues=${this._rememberFormValues}
21225
- .sharedValues=${this._sharedFormParams}
21226
- @submit=${(e8) => {
21227
- void this._handleExecute(e8);
22405
+ <div
22406
+ class="glass-panel method-detail"
22407
+ style="${isSplit ? "border-radius: 0; height: 100%;" : ""} display: flex; flex-direction: column;"
22408
+ >
22409
+ <!-- Panel Header: LED + Method ▼ + instance-panel + [+] + [⋯] + [×] -->
22410
+ <div
22411
+ 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;"
22412
+ >
22413
+ <!-- LED dot (stateful/live indicator) -->
22414
+ ${opts.isStateful ? b2`<span
22415
+ 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"};"
22416
+ title="${opts.isLive ? "Live \u2014 stateful photon with active subscription" : "Stateful photon"}"
22417
+ ></span>` : ""}
22418
+
22419
+ <!-- Method Selector (name as dropdown) -->
22420
+ <select
22421
+ .value=${opts.method.name}
22422
+ @change=${(e8) => {
22423
+ const methodName = e8.target.value;
22424
+ const method = opts.allMethods?.find((m3) => m3.name === methodName);
22425
+ if (method && opts.onMethodChange) {
22426
+ opts.onMethodChange(method);
22427
+ }
21228
22428
  }}
21229
- @cancel=${() => this._handleBackFromMethod()}
21230
- ></invoke-form>
22429
+ 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;"
22430
+ >
22431
+ ${opts.allMethods?.map(
22432
+ (m3) => b2`<option .selected=${m3.name === opts.method.name} value=${m3.name}>
22433
+ ${m3.name}
22434
+ </option>`
22435
+ ) || b2`<option value=${opts.method.name}>${opts.method.name}</option>`}
22436
+ </select>
21231
22437
 
21232
- ${this._progress ? b2`
21233
- <div class="progress-container">
21234
- <div class="progress-bar-wrapper">
21235
- <div
21236
- class="progress-bar ${this._progress.value < 0 ? "indeterminate" : ""}"
21237
- style="width: ${this._progress.value < 0 ? "30%" : Math.round(this._progress.value * 100) + "%"}"
21238
- ></div>
22438
+ <div style="flex: 1;"></div>
22439
+
22440
+ <!-- Rich Instance Selector -->
22441
+ ${opts.isStateful && opts.instances && opts.instances.length > 0 && opts.onInstanceAction ? b2`
22442
+ <instance-panel
22443
+ .instanceName=${opts.instance || "default"}
22444
+ .instances=${opts.instances}
22445
+ .photonName=${opts.photon.name}
22446
+ .selectorMode=${this._instanceSelectorMode}
22447
+ .autoInstance=${this._autoInstance}
22448
+ @instance-action=${(e8) => opts.onInstanceAction?.(e8.detail)}
22449
+ ></instance-panel>
22450
+ ` : ""}
22451
+
22452
+ <!-- Add panel button (primary only) -->
22453
+ ${opts.panelSide === "primary" ? b2`
22454
+ <div style="position: relative; flex-shrink: 0;">
22455
+ <button
22456
+ @click=${() => opts.onPanelAction?.("toggle-picker")}
22457
+ 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;"
22458
+ title="Add panel"
22459
+ >
22460
+ +
22461
+ </button>
22462
+ ${this._methodPickerOpen && this._methodPickerPanelId === null ? this._renderMethodPickerPopover() : ""}
21239
22463
  </div>
21240
- <div class="progress-text">
21241
- <span>${this._progress.message}</span>
21242
- ${this._progress.value >= 0 ? b2`
21243
- <span class="progress-percentage"
21244
- >${Math.round(this._progress.value * 100)}%</span
21245
- >
21246
- ` : ""}
22464
+ ` : ""}
22465
+
22466
+ <!-- Overflow menu -->
22467
+ ${opts.overflowItems && opts.overflowItems.length > 0 ? b2`
22468
+ <overflow-menu
22469
+ .items=${opts.overflowItems}
22470
+ @menu-select=${(e8) => opts.onOverflowSelect?.(e8.detail.id)}
22471
+ ></overflow-menu>
22472
+ ` : ""}
22473
+
22474
+ <!-- Close button (additional panels only) -->
22475
+ ${opts.panelSide === "additional" ? b2`
22476
+ <button
22477
+ @click=${() => opts.onPanelAction?.("close")}
22478
+ 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;"
22479
+ title="Close panel"
22480
+ >
22481
+ ×
22482
+ </button>
22483
+ ` : ""}
22484
+ </div>
22485
+ <!-- Panel Content (scrollable) -->
22486
+ <div
22487
+ style="display: flex; flex-direction: column; flex: 1; min-height: 0; overflow-y: auto;"
22488
+ >
22489
+ ${this._renderDescription(opts.method.description)}
22490
+ <invoke-form
22491
+ .params=${opts.method.params}
22492
+ .loading=${opts.executing}
22493
+ .photonName=${opts.photon.name}
22494
+ .methodName=${opts.method.name}
22495
+ .rememberValues=${this._rememberFormValues}
22496
+ .sharedValues=${opts.formParams}
22497
+ @submit=${opts.onSubmit}
22498
+ @cancel=${opts.onCancel}
22499
+ ></invoke-form>
22500
+
22501
+ ${opts.progress ? b2`
22502
+ <div class="progress-container">
22503
+ <div class="progress-bar-wrapper">
22504
+ <div
22505
+ class="progress-bar ${opts.progress.value < 0 ? "indeterminate" : ""}"
22506
+ style="width: ${opts.progress.value < 0 ? "30%" : Math.round(opts.progress.value * 100) + "%"}"
22507
+ ></div>
22508
+ </div>
22509
+ <div class="progress-text">
22510
+ <span>${opts.progress.message}</span>
22511
+ </div>
22512
+ </div>
22513
+ ` : ""}
22514
+ ${opts.result !== null ? b2`
22515
+ <result-viewer
22516
+ .result=${opts.result}
22517
+ .outputFormat=${opts.method?.outputFormat}
22518
+ .layoutHints=${opts.method?.layoutHints}
22519
+ .theme=${this._theme}
22520
+ .live=${this._currentCollectionName !== null}
22521
+ .resultKey=${opts.photon && opts.method ? `${opts.photon.name}/${opts.method.name}` : void 0}
22522
+ @share=${() => this._handleShareResult()}
22523
+ ></result-viewer>
22524
+ ` : b2`
22525
+ <div class="empty-state-inline result-empty">
22526
+ <span class="empty-state-icon">${play}</span>
22527
+ <span>Run the method to see results here</span>
21247
22528
  </div>
22529
+ `}
22530
+ </div>
22531
+ </div>
22532
+ `;
22533
+ }
22534
+ /** Close all split panels and return to single-panel mode */
22535
+ _closeSecondPanel() {
22536
+ this._splitPanels = [];
22537
+ this._methodPickerOpen = false;
22538
+ this._methodPickerPanelId = null;
22539
+ this._updateRoute();
22540
+ }
22541
+ /** Handle instance change for primary panel */
22542
+ _handleLeftPanelInstanceChange(instance) {
22543
+ this._currentInstance = instance;
22544
+ sessionStorage.setItem(`photon-instance:${this._selectedPhoton.name}`, instance);
22545
+ this._lastResult = null;
22546
+ }
22547
+ /** Add a new split panel */
22548
+ _addPanel(type, method) {
22549
+ if (this._splitPanels.length >= 2) return;
22550
+ const panel = {
22551
+ id: `panel-${++this._nextPanelId}`,
22552
+ type,
22553
+ method,
22554
+ result: null,
22555
+ executing: false,
22556
+ progress: null,
22557
+ formParams: {},
22558
+ instance: this._currentInstance || "default"
22559
+ };
22560
+ this._splitPanels = [...this._splitPanels, panel];
22561
+ if (type === "method" && method && this._willAutoInvoke(method)) {
22562
+ void this._executePanelMethod(panel.id, {});
22563
+ }
22564
+ this._methodPickerOpen = false;
22565
+ this._methodPickerPanelId = null;
22566
+ this._cleanupPickerDismiss();
22567
+ this._updateRoute();
22568
+ }
22569
+ /** Remove a split panel by id */
22570
+ _removePanel(panelId) {
22571
+ this._splitPanels = this._splitPanels.filter((p5) => p5.id !== panelId);
22572
+ this._updateRoute();
22573
+ }
22574
+ /** Update a split panel's state */
22575
+ _updatePanel(panelId, updates) {
22576
+ this._splitPanels = this._splitPanels.map((p5) => p5.id === panelId ? { ...p5, ...updates } : p5);
22577
+ }
22578
+ /** Execute a method in a split panel */
22579
+ async _executePanelMethod(panelId, args) {
22580
+ const panel = this._splitPanels.find((p5) => p5.id === panelId);
22581
+ if (!panel || panel.type !== "method" || !panel.method) return;
22582
+ this._updatePanel(panelId, { executing: true, result: null, progress: null, formParams: args });
22583
+ try {
22584
+ const toolName = `${this._selectedPhoton.name}/${panel.method.name}`;
22585
+ const result = await mcpClient.callTool(
22586
+ toolName,
22587
+ args,
22588
+ void 0,
22589
+ panel.instance || this._currentInstance,
22590
+ (progress) => {
22591
+ this._updatePanel(panelId, { progress });
22592
+ }
22593
+ );
22594
+ this._updatePanel(panelId, {
22595
+ result: mcpClient.parseToolResult(result),
22596
+ executing: false,
22597
+ progress: null
22598
+ });
22599
+ } catch (error2) {
22600
+ showToast(`Error: ${error2.message}`, { type: "error" });
22601
+ this._updatePanel(panelId, {
22602
+ result: { error: error2.message },
22603
+ executing: false,
22604
+ progress: null
22605
+ });
22606
+ }
22607
+ }
22608
+ /** Change the method in a split panel */
22609
+ _changePanelMethod(panelId, method) {
22610
+ this._updatePanel(panelId, { method, result: null, formParams: {} });
22611
+ if (this._willAutoInvoke(method)) {
22612
+ void this._executePanelMethod(panelId, {});
22613
+ }
22614
+ this._updateRoute();
22615
+ }
22616
+ /** Change the instance in a split panel */
22617
+ _changePanelInstance(panelId, instance) {
22618
+ this._updatePanel(panelId, { instance, result: null });
22619
+ }
22620
+ /** Open a method in a new split panel (backwards-compatible entry point) */
22621
+ _openInSecondPanel(photon, method) {
22622
+ this._addPanel("method", method);
22623
+ }
22624
+ /** Show method picker popover */
22625
+ _showMethodPicker() {
22626
+ this._methodPickerOpen = !this._methodPickerOpen;
22627
+ this._methodPickerPanelId = null;
22628
+ }
22629
+ /** Clean up picker dismiss listener */
22630
+ _cleanupPickerDismiss() {
22631
+ if (this._pickerDismissHandler) {
22632
+ window.removeEventListener("mousedown", this._pickerDismissHandler, true);
22633
+ this._pickerDismissHandler = null;
22634
+ }
22635
+ }
22636
+ /** Handle method change in primary panel */
22637
+ _handleLeftPanelMethodChange(method) {
22638
+ this._selectedMethod = method;
22639
+ this._lastResult = null;
22640
+ this._lastFormParams = {};
22641
+ this._updateRoute();
22642
+ }
22643
+ /** Handle panel action for primary panel */
22644
+ _handlePrimaryPanelAction(action) {
22645
+ if (action === "toggle-picker") {
22646
+ this._methodPickerOpen = !this._methodPickerOpen;
22647
+ this._methodPickerPanelId = null;
22648
+ }
22649
+ }
22650
+ /** Build opts for an additional (non-primary) split panel */
22651
+ _buildAdditionalPanelOpts(panel) {
22652
+ return {
22653
+ photon: this._selectedPhoton,
22654
+ method: panel.method,
22655
+ result: panel.result,
22656
+ executing: panel.executing || false,
22657
+ progress: panel.progress,
22658
+ formParams: panel.formParams || {},
22659
+ onSubmit: (e8) => {
22660
+ const args = e8.detail?.args || {};
22661
+ void this._executePanelMethod(panel.id, args);
22662
+ },
22663
+ onCancel: () => this._removePanel(panel.id),
22664
+ panelLabel: panel.id,
22665
+ instance: panel.instance,
22666
+ instances: this._instances,
22667
+ onInstanceChange: (inst) => this._changePanelInstance(panel.id, inst),
22668
+ allMethods: this._selectedPhoton?.methods || [],
22669
+ onMethodChange: (m3) => this._changePanelMethod(panel.id, m3),
22670
+ panelSide: "additional",
22671
+ onPanelAction: (action) => {
22672
+ if (action === "close") this._removePanel(panel.id);
22673
+ },
22674
+ onInstanceAction: (detail) => {
22675
+ if (detail.action === "switch") {
22676
+ this._changePanelInstance(panel.id, detail.instance);
22677
+ }
22678
+ },
22679
+ isStateful: !!this._selectedPhoton?.stateful,
22680
+ isLive: this._currentCollectionName !== null,
22681
+ overflowItems: this._buildOverflowItems({
22682
+ showRefresh: false,
22683
+ showRename: false,
22684
+ showViewSource: false,
22685
+ showDelete: false,
22686
+ showHelp: false,
22687
+ showRunTests: false
22688
+ }),
22689
+ onOverflowSelect: (id2) => this._handleOverflowAction(id2)
22690
+ };
22691
+ }
22692
+ /** Handle overflow menu actions from panel headers */
22693
+ _handleOverflowAction(id2) {
22694
+ switch (id2) {
22695
+ case "refresh":
22696
+ if (this._selectedMethod && this._willAutoInvoke(this._selectedMethod)) {
22697
+ void this._handleExecute(new CustomEvent("execute", { detail: { args: {} } }));
22698
+ }
22699
+ break;
22700
+ case "remember-values":
22701
+ this._rememberFormValues = !this._rememberFormValues;
22702
+ break;
22703
+ case "verbose-logging":
22704
+ this._verboseLogging = !this._verboseLogging;
22705
+ break;
22706
+ case "run-tests":
22707
+ void this._runTests();
22708
+ break;
22709
+ case "help":
22710
+ this._showPhotonHelp = true;
22711
+ break;
22712
+ default:
22713
+ this._handleContextAction(new CustomEvent("context-action", { detail: { action: id2 } }));
22714
+ break;
22715
+ }
22716
+ }
22717
+ /** Render source panel for split view */
22718
+ _renderSourcePanel(panelId) {
22719
+ return b2`
22720
+ <div style="height: 100%; overflow: hidden;">
22721
+ <photon-studio
22722
+ .photonName=${this._selectedPhoton?.name}
22723
+ .theme=${this._theme}
22724
+ @studio-saved=${() => this._handleStudioSaved?.()}
22725
+ @studio-close=${() => this._removePanel(panelId)}
22726
+ ></photon-studio>
22727
+ </div>
22728
+ `;
22729
+ }
22730
+ /** Render the method picker popover for adding new panels */
22731
+ _renderMethodPickerPopover() {
22732
+ const methods = this._getVisibleMethods();
22733
+ const openMethodNames = /* @__PURE__ */ new Set();
22734
+ if (this._selectedMethod) openMethodNames.add(this._selectedMethod.name);
22735
+ for (const p5 of this._splitPanels) {
22736
+ if (p5.type === "method" && p5.method) openMethodNames.add(p5.method.name);
22737
+ }
22738
+ const availableMethods = methods.filter((m3) => !openMethodNames.has(m3.name));
22739
+ const hasSourcePanel = this._splitPanels.some((p5) => p5.type === "source");
22740
+ const canAddMore = this._splitPanels.length < 2;
22741
+ if (!canAddMore) return "";
22742
+ if (!this._pickerDismissHandler) {
22743
+ const handler = (e8) => {
22744
+ const path = e8.composedPath();
22745
+ const popover = this.renderRoot.querySelector(".method-picker-popover");
22746
+ if (popover && path.includes(popover)) return;
22747
+ this._methodPickerOpen = false;
22748
+ this._pickerDismissHandler = null;
22749
+ window.removeEventListener("mousedown", handler, true);
22750
+ };
22751
+ this._pickerDismissHandler = handler;
22752
+ setTimeout(() => window.addEventListener("mousedown", handler, true), 0);
22753
+ }
22754
+ return b2`
22755
+ <div
22756
+ class="method-picker-popover"
22757
+ @click=${(e8) => e8.stopPropagation()}
22758
+ 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;"
22759
+ >
22760
+ <!-- Header -->
22761
+ <div style="padding: 10px 12px 8px; border-bottom: 1px solid var(--border-glass);">
22762
+ <div
22763
+ style="font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--t-tertiary);"
22764
+ >
22765
+ Split Pane
22766
+ </div>
22767
+ </div>
22768
+
22769
+ <!-- Methods section -->
22770
+ ${availableMethods.length > 0 ? b2`
22771
+ <div style="padding: 4px 0;">
22772
+ <div
22773
+ style="padding: 4px 12px 2px; font-size: 10px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; color: var(--t-tertiary);"
22774
+ >
22775
+ Methods
22776
+ </div>
22777
+ ${availableMethods.map(
22778
+ (m3) => b2`
22779
+ <div
22780
+ @click=${() => this._addPanel("method", m3)}
22781
+ 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;"
22782
+ @mouseenter=${(e8) => e8.target.style.background = "var(--bg-hover)"}
22783
+ @mouseleave=${(e8) => e8.target.style.background = "transparent"}
22784
+ >
22785
+ <span style="color: var(--t-tertiary); font-size: 10px;">&#9656;</span>
22786
+ ${m3.name}
22787
+ </div>
22788
+ `
22789
+ )}
21248
22790
  </div>
21249
- ` : ""}
21250
- ${this._lastResult !== null ? b2`
21251
- <result-viewer
21252
- .result=${this._lastResult}
21253
- .outputFormat=${this._selectedMethod?.outputFormat}
21254
- .layoutHints=${this._selectedMethod?.layoutHints}
21255
- .theme=${this._theme}
21256
- .live=${this._currentCollectionName !== null}
21257
- .resultKey=${this._selectedPhoton && this._selectedMethod ? `${this._selectedPhoton.name}/${this._selectedMethod.name}` : void 0}
21258
- @share=${() => this._handleShareResult()}
21259
- ></result-viewer>
21260
22791
  ` : b2`
21261
- <div class="empty-state-inline result-empty">
21262
- <span class="empty-state-icon">${play}</span>
21263
- <span>Run the method to see results here</span>
22792
+ <div
22793
+ style="padding: 8px 12px; font-size: 11px; color: var(--t-tertiary); font-style: italic;"
22794
+ >
22795
+ All methods already open
21264
22796
  </div>
21265
22797
  `}
22798
+
22799
+ <!-- Tools section -->
22800
+ ${!hasSourcePanel ? b2`
22801
+ <div style="border-top: 1px solid var(--border-glass); padding: 4px 0;">
22802
+ <div
22803
+ style="padding: 4px 12px 2px; font-size: 10px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; color: var(--t-tertiary);"
22804
+ >
22805
+ Tools
22806
+ </div>
22807
+ <div
22808
+ @click=${() => this._addPanel("source")}
22809
+ 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;"
22810
+ @mouseenter=${(e8) => e8.target.style.background = "var(--bg-hover)"}
22811
+ @mouseleave=${(e8) => e8.target.style.background = "transparent"}
22812
+ >
22813
+ <span style="font-size: 11px; color: var(--accent-primary);">&lt;/&gt;</span>
22814
+ Source Editor
22815
+ </div>
22816
+ </div>
22817
+ ` : ""}
21266
22818
  </div>
21267
22819
  `;
21268
22820
  }
@@ -21291,10 +22843,11 @@ ${photon.errorMessage || "Unknown error"}</pre
21291
22843
  if (form?.isDirty && !confirm("You have unsaved changes. Discard them?")) {
21292
22844
  return;
21293
22845
  }
22846
+ this._closeSecondPanel();
21294
22847
  if (this._selectedPhoton.isApp && this._selectedPhoton.appEntry) {
21295
22848
  this._selectedMethod = this._selectedPhoton.appEntry;
21296
22849
  this._view = "form";
21297
- this._updateHash(true);
22850
+ this._updateRoute(true);
21298
22851
  void this.updateComplete.then(() => {
21299
22852
  setTimeout(() => {
21300
22853
  const mainArea = this.shadowRoot?.querySelector(".main-area");
@@ -21308,11 +22861,11 @@ ${photon.errorMessage || "Unknown error"}</pre
21308
22861
  this._selectedPhoton = null;
21309
22862
  this._welcomePhase = "welcome";
21310
22863
  this._view = "list";
21311
- this._updateHash(true);
22864
+ this._updateRoute(true);
21312
22865
  } else {
21313
22866
  this._view = "list";
21314
22867
  this._selectedMethod = null;
21315
- this._updateHash(true);
22868
+ this._updateRoute(true);
21316
22869
  }
21317
22870
  }
21318
22871
  async _openForkDialog() {
@@ -21383,10 +22936,21 @@ ${photon.errorMessage || "Unknown error"}</pre
21383
22936
  if (errorText !== "Elicitation cancelled by user") {
21384
22937
  this._log("error", errorText);
21385
22938
  showToast(errorText, "error", 5e3);
22939
+ this._lastResult = { _error: true, message: errorText };
21386
22940
  }
21387
22941
  } else {
21388
22942
  this._lastResult = mcpClient.parseToolResult(result);
22943
+ if (this._selectedPhoton?.stateful && Array.isArray(this._lastResult) && this._selectedMethod) {
22944
+ this._lastResult = this._autoWrapPaginationIfNeeded(
22945
+ this._lastResult,
22946
+ this._selectedPhoton.name,
22947
+ this._selectedMethod
22948
+ );
22949
+ }
21389
22950
  this._log("success", "Execution completed", false, execDuration);
22951
+ if (this._selectedPhoton?.stateful && this._lastResult != null && typeof this._lastResult === "object") {
22952
+ this._initializeGlobalInstance(this._selectedPhoton.name, this._lastResult);
22953
+ }
21390
22954
  void this.updateComplete.then(() => {
21391
22955
  const rv2 = this.shadowRoot?.querySelector("result-viewer");
21392
22956
  if (rv2 && window.innerWidth <= 768) {
@@ -21421,6 +22985,16 @@ ${photon.errorMessage || "Unknown error"}</pre
21421
22985
  const result = await mcpClient.callTool(toolName, this._lastFormParams || {});
21422
22986
  if (!result.isError) {
21423
22987
  this._lastResult = mcpClient.parseToolResult(result);
22988
+ if (this._selectedPhoton.stateful && Array.isArray(this._lastResult) && this._selectedMethod) {
22989
+ this._lastResult = this._autoWrapPaginationIfNeeded(
22990
+ this._lastResult,
22991
+ this._selectedPhoton.name,
22992
+ this._selectedMethod
22993
+ );
22994
+ }
22995
+ if (this._selectedPhoton.stateful && this._lastResult != null && typeof this._lastResult === "object") {
22996
+ this._initializeGlobalInstance(this._selectedPhoton.name, this._lastResult);
22997
+ }
21424
22998
  }
21425
22999
  } catch {
21426
23000
  } finally {
@@ -21471,6 +23045,162 @@ ${photon.errorMessage || "Unknown error"}</pre
21471
23045
  this._collectionUnsubscribes = [];
21472
23046
  this._currentCollectionName = null;
21473
23047
  }
23048
+ /**
23049
+ * Initialize global photon instance for @stateful photons
23050
+ * Makes the photon instance available as window.{photonName}
23051
+ * and keeps it in sync with server state via state-changed patches
23052
+ * Wraps paginated array properties with ViewportAwareProxy for smart fetching
23053
+ */
23054
+ /**
23055
+ * Auto-wrap array results with pagination metadata for @stateful photons
23056
+ * If method has (start, limit) parameters, detect them and use global instance
23057
+ * to calculate pagination metadata from the full array
23058
+ */
23059
+ _autoWrapPaginationIfNeeded(items, photonName, method) {
23060
+ const hasStartLimitParams = method.parameters?.some(
23061
+ (p5) => (p5.name === "start" || p5.name === "limit") && p5.required !== true
23062
+ );
23063
+ if (!hasStartLimitParams || items.length === 0) {
23064
+ return items;
23065
+ }
23066
+ try {
23067
+ const manager = getGlobalInstanceManager();
23068
+ const instance = manager.getInstance(photonName);
23069
+ if (!instance) {
23070
+ return items;
23071
+ }
23072
+ const arrayProp = Object.keys(instance).find((key) => {
23073
+ const value = instance[key];
23074
+ return Array.isArray(value) && value.length > 0;
23075
+ });
23076
+ if (!arrayProp) {
23077
+ return items;
23078
+ }
23079
+ const fullArray = instance[arrayProp];
23080
+ const totalCount = fullArray.length;
23081
+ const start = 0;
23082
+ const end = items.length;
23083
+ return {
23084
+ items,
23085
+ _pagination: {
23086
+ totalCount,
23087
+ start,
23088
+ end,
23089
+ hasMore: end < totalCount
23090
+ }
23091
+ };
23092
+ } catch (error2) {
23093
+ if (this._verboseLogging) {
23094
+ const msg = error2 instanceof Error ? error2.message : "Unknown error";
23095
+ this._log("warn", `Could not auto-wrap pagination: ${msg}`);
23096
+ }
23097
+ return items;
23098
+ }
23099
+ }
23100
+ _initializeGlobalInstance(photonName, initialState) {
23101
+ try {
23102
+ const paginatedProps = this._detectPaginatedProperties(initialState);
23103
+ const instance = initializeGlobalPhotonSession(photonName, initialState);
23104
+ for (const [propName, paginationMeta] of Object.entries(paginatedProps)) {
23105
+ this._wrapWithViewportProxy(instance, propName, paginationMeta);
23106
+ }
23107
+ if (this._verboseLogging) {
23108
+ 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()}`;
23109
+ this._log("info", message);
23110
+ }
23111
+ } catch (error2) {
23112
+ console.error("Failed to initialize global photon instance", error2);
23113
+ }
23114
+ }
23115
+ /**
23116
+ * Detect which properties have pagination metadata
23117
+ */
23118
+ _detectPaginatedProperties(initialState) {
23119
+ const paginated = /* @__PURE__ */ new Map();
23120
+ if (!initialState || typeof initialState !== "object") {
23121
+ return paginated;
23122
+ }
23123
+ for (const [key, value] of Object.entries(initialState)) {
23124
+ if (value && typeof value === "object" && value._pagination) {
23125
+ paginated.set(key, value._pagination);
23126
+ }
23127
+ }
23128
+ return paginated;
23129
+ }
23130
+ /**
23131
+ * Wrap a property with ViewportAwareProxy for smart pagination
23132
+ */
23133
+ _wrapWithViewportProxy(instance, propertyName2, paginationMeta) {
23134
+ try {
23135
+ const pageSize = getPageSizeForClient();
23136
+ const proxy = new ViewportAwareProxy(
23137
+ this._selectedPhoton?.name || "unknown",
23138
+ this._selectedMethod?.name || propertyName2,
23139
+ mcpClient,
23140
+ {
23141
+ pageSize,
23142
+ bufferSize: 5,
23143
+ // Items to buffer above/below viewport
23144
+ maxCacheSize: 500
23145
+ // Max items to keep in cache
23146
+ }
23147
+ );
23148
+ proxy.initializeWithResponse({
23149
+ items: instance[propertyName2] || [],
23150
+ _pagination: paginationMeta
23151
+ });
23152
+ instance.makeProperty(propertyName2);
23153
+ Object.defineProperty(instance, propertyName2, {
23154
+ configurable: true,
23155
+ enumerable: true,
23156
+ get: () => proxy.items,
23157
+ set: (value) => {
23158
+ proxy.clearCache();
23159
+ proxy.initializeWithResponse({
23160
+ items: value || [],
23161
+ _pagination: paginationMeta
23162
+ });
23163
+ }
23164
+ });
23165
+ const patchHandler = (data) => {
23166
+ if (data?.patches) {
23167
+ proxy.applyPatches(data.patches);
23168
+ }
23169
+ };
23170
+ instance.on("state-changed", patchHandler);
23171
+ queueMicrotask(() => {
23172
+ try {
23173
+ const resultViewer = this._resultViewer;
23174
+ if (resultViewer?.shadowRoot) {
23175
+ const scrollContainer = resultViewer.shadowRoot.querySelector(
23176
+ ".result-content"
23177
+ );
23178
+ if (scrollContainer) {
23179
+ const manager = new ViewportManager(proxy, {
23180
+ container: scrollContainer,
23181
+ itemSelector: "[data-index]",
23182
+ pageSize,
23183
+ bufferSize: 5
23184
+ });
23185
+ manager.start();
23186
+ }
23187
+ }
23188
+ } catch (error2) {
23189
+ if (this._verboseLogging) {
23190
+ this._log("warn", `Could not set up automatic viewport tracking for ${propertyName2}`);
23191
+ }
23192
+ }
23193
+ });
23194
+ if (this._verboseLogging) {
23195
+ this._log(
23196
+ "info",
23197
+ `\u2728 Paginated proxy enabled for ${propertyName2} (page size: ${pageSize})`
23198
+ );
23199
+ }
23200
+ } catch (error2) {
23201
+ console.error(`Failed to wrap ${propertyName2} with ViewportAwareProxy`, error2);
23202
+ }
23203
+ }
21474
23204
  async _handleConfigure(e8) {
21475
23205
  const { photon, config: config3 } = e8.detail;
21476
23206
  this._log("info", `Configuring ${photon}...`);
@@ -21505,8 +23235,7 @@ ${photon.errorMessage || "Unknown error"}</pre
21505
23235
  showToast("No method selected to share", "error");
21506
23236
  return;
21507
23237
  }
21508
- const baseUrl = window.location.origin + window.location.pathname;
21509
- const hash2 = `${this._selectedPhoton.name}/${this._selectedMethod.name}`;
23238
+ const pathSegment2 = `${this._selectedPhoton.name}/${this._selectedMethod.name}`;
21510
23239
  const params = new URLSearchParams();
21511
23240
  for (const [key, value] of Object.entries(this._lastFormParams)) {
21512
23241
  if (value !== void 0 && value !== null && value !== "") {
@@ -21517,7 +23246,7 @@ ${photon.errorMessage || "Unknown error"}</pre
21517
23246
  }
21518
23247
  }
21519
23248
  }
21520
- let shareUrl = `${baseUrl}#${hash2}`;
23249
+ let shareUrl = `${window.location.origin}/${pathSegment2}`;
21521
23250
  if (params.toString()) {
21522
23251
  shareUrl += `?${params.toString()}`;
21523
23252
  }
@@ -21643,9 +23372,8 @@ ${photon.errorMessage || "Unknown error"}</pre
21643
23372
  }
21644
23373
  if (!photon.internal) {
21645
23374
  const paramHints = (method.params || []).filter((p5) => p5.required).map((p5) => `--${p5.name} <${p5.type || "value"}>`).join(" ");
21646
- lines.push(
21647
- `CLI: \`photon cli ${name2} ${method.name}${paramHints ? " " + paramHints : ""}\``
21648
- );
23375
+ const cliPfx = window.__PHOTON_SHELL_INIT ? name2 : `photon cli ${name2}`;
23376
+ lines.push(`CLI: \`${cliPfx} ${method.name}${paramHints ? " " + paramHints : ""}\``);
21649
23377
  lines.push("");
21650
23378
  }
21651
23379
  if (method.returnType) {
@@ -21751,7 +23479,8 @@ ${photon.errorMessage || "Unknown error"}</pre
21751
23479
  showHelp = true,
21752
23480
  showRunTests = this._getTestMethods().length > 0,
21753
23481
  showRemove = false,
21754
- showFullscreen = false
23482
+ showFullscreen = false,
23483
+ showInstallApp = !this._pwaIsStandalone && !!this._selectedPhoton?.isApp
21755
23484
  } = opts;
21756
23485
  const items = [];
21757
23486
  if (showFullscreen) {
@@ -21797,6 +23526,13 @@ ${photon.errorMessage || "Unknown error"}</pre
21797
23526
  toggle: true,
21798
23527
  toggleActive: this._verboseLogging
21799
23528
  });
23529
+ if (showInstallApp) {
23530
+ items.push({
23531
+ id: "install-app",
23532
+ label: "Install as App",
23533
+ iconSvg: iconSvgString(iconPaths.installApp)
23534
+ });
23535
+ }
21800
23536
  if (showRename || showViewSource || showFork || showContribute || showDelete || showRemove) {
21801
23537
  const first = [
21802
23538
  showRename,
@@ -22722,6 +24458,10 @@ BeamApp.styles = [
22722
24458
  display: none;
22723
24459
  }
22724
24460
 
24461
+ :host(.focus-mode) .main-area {
24462
+ padding: var(--space-sm);
24463
+ }
24464
+
22725
24465
  .main-area {
22726
24466
  flex: 1;
22727
24467
  position: relative;
@@ -22730,10 +24470,10 @@ BeamApp.styles = [
22730
24470
  padding: var(--space-lg);
22731
24471
  }
22732
24472
 
24473
+ .beam-back-btn,
22733
24474
  .beam-fullscreen-btn {
22734
24475
  position: sticky;
22735
24476
  top: calc(-1 * var(--space-lg));
22736
- float: right;
22737
24477
  z-index: 100;
22738
24478
  width: 28px;
22739
24479
  height: 28px;
@@ -22747,6 +24487,18 @@ BeamApp.styles = [
22747
24487
  align-items: center;
22748
24488
  justify-content: center;
22749
24489
  transition: all 0.2s ease;
24490
+ backdrop-filter: blur(8px);
24491
+ }
24492
+
24493
+ .beam-back-btn {
24494
+ float: left;
24495
+ margin-top: calc(-1 * var(--space-lg));
24496
+ margin-left: calc(-1 * var(--space-lg) + 1px);
24497
+ margin-bottom: calc(-28px + var(--space-lg));
24498
+ }
24499
+
24500
+ .beam-fullscreen-btn {
24501
+ float: right;
22750
24502
  margin-top: calc(-1 * var(--space-lg));
22751
24503
  margin-right: calc(-1 * var(--space-lg) + 1px);
22752
24504
  margin-bottom: calc(-28px + var(--space-lg));
@@ -23777,7 +25529,7 @@ BeamApp.styles = [
23777
25529
  min-width: 120px;
23778
25530
  font-family: var(--font-mono);
23779
25531
  font-size: var(--text-md);
23780
- color: hsl(45, 80%, 60%);
25532
+ color: var(--color-warning);
23781
25533
  }
23782
25534
 
23783
25535
  .variable-input input {
@@ -23966,7 +25718,7 @@ BeamApp.styles = [
23966
25718
 
23967
25719
  .progress-bar-wrapper {
23968
25720
  height: 8px;
23969
- background: rgba(0, 0, 0, 0.2);
25721
+ background: var(--bg-glass);
23970
25722
  border-radius: var(--radius-xs);
23971
25723
  overflow: hidden;
23972
25724
  }
@@ -24084,6 +25836,25 @@ BeamApp.styles = [
24084
25836
  padding-top: calc(var(--space-md) + 60px); /* Space for mobile menu button */
24085
25837
  }
24086
25838
 
25839
+ .beam-back-btn,
25840
+ .beam-fullscreen-btn {
25841
+ position: fixed;
25842
+ top: var(--space-md);
25843
+ float: none;
25844
+ margin: 0;
25845
+ width: 44px;
25846
+ height: 44px;
25847
+ box-shadow: var(--shadow-md);
25848
+ }
25849
+
25850
+ .beam-back-btn {
25851
+ left: calc(var(--space-md) + 44px + var(--space-sm));
25852
+ }
25853
+
25854
+ .beam-fullscreen-btn {
25855
+ right: var(--space-md);
25856
+ }
25857
+
24087
25858
  .photon-header {
24088
25859
  flex-direction: column;
24089
25860
  align-items: flex-start;
@@ -24098,10 +25869,9 @@ BeamApp.styles = [
24098
25869
  grid-template-columns: 1fr;
24099
25870
  }
24100
25871
 
24101
- /* Touch-friendly targets - min 44px */
25872
+ /* Touch-friendly targets - min 44px (exclude small inline buttons) */
24102
25873
  .asset-card,
24103
25874
  .method-card,
24104
- button,
24105
25875
  .filter-btn {
24106
25876
  min-height: 44px;
24107
25877
  }
@@ -24382,6 +26152,15 @@ __decorateClass([
24382
26152
  __decorateClass([
24383
26153
  r5()
24384
26154
  ], BeamApp.prototype, "_resourceContent", 2);
26155
+ __decorateClass([
26156
+ r5()
26157
+ ], BeamApp.prototype, "_splitPanels", 2);
26158
+ __decorateClass([
26159
+ r5()
26160
+ ], BeamApp.prototype, "_methodPickerOpen", 2);
26161
+ __decorateClass([
26162
+ r5()
26163
+ ], BeamApp.prototype, "_methodPickerPanelId", 2);
24385
26164
  __decorateClass([
24386
26165
  e7("beam-sidebar")
24387
26166
  ], BeamApp.prototype, "_sidebar", 2);
@@ -24420,6 +26199,7 @@ var BeamSidebar = class extends i4 {
24420
26199
  this._favorites = /* @__PURE__ */ new Set();
24421
26200
  this._collapsedSections = /* @__PURE__ */ new Set();
24422
26201
  this._recentPhotons = [];
26202
+ this._notificationWarmth = /* @__PURE__ */ new Map();
24423
26203
  }
24424
26204
  connectedCallback() {
24425
26205
  super.connectedCallback();
@@ -24790,7 +26570,7 @@ var BeamSidebar = class extends i4 {
24790
26570
  }
24791
26571
  return b2`
24792
26572
  <li
24793
- class="photon-item ${this.selectedPhoton === photon.name ? "active" : ""} ${photon.internal ? "internal" : ""}"
26573
+ class="photon-item ${this.selectedPhoton === photon.name ? "active" : ""} ${photon.internal ? "internal" : ""} ${this._isPhotonWarm(photon.name) ? "warmth" : ""}"
24794
26574
  role="option"
24795
26575
  aria-selected="${this.selectedPhoton === photon.name}"
24796
26576
  tabindex="0"
@@ -24987,6 +26767,28 @@ ${photon.path}` : ""}"
24987
26767
  }
24988
26768
  });
24989
26769
  }
26770
+ /**
26771
+ * Check if a photon currently has a warmth indicator (notification received within last 5 seconds)
26772
+ */
26773
+ _isPhotonWarm(photonName) {
26774
+ const lastNotificationTime = this._notificationWarmth.get(photonName);
26775
+ if (!lastNotificationTime) return false;
26776
+ return Date.now() - lastNotificationTime < 5e3;
26777
+ }
26778
+ /**
26779
+ * Update the warmth indicator for a photon when a notification arrives
26780
+ */
26781
+ updatePhotonWarmth(photonName) {
26782
+ this._notificationWarmth.set(photonName, Date.now());
26783
+ this.requestUpdate();
26784
+ }
26785
+ /**
26786
+ * Check if a notification type is watched by a photon
26787
+ * This will be called by beam-app to determine if window.focus() should be triggered
26788
+ */
26789
+ isNotificationWatched(photonName, notificationType) {
26790
+ return true;
26791
+ }
24990
26792
  };
24991
26793
  BeamSidebar.styles = [
24992
26794
  theme,
@@ -25221,7 +27023,7 @@ BeamSidebar.styles = [
25221
27023
  }
25222
27024
 
25223
27025
  .section-header:hover {
25224
- color: var(--t-secondary);
27026
+ color: var(--t-primary);
25225
27027
  background: var(--bg-panel);
25226
27028
  }
25227
27029
 
@@ -25283,7 +27085,7 @@ BeamSidebar.styles = [
25283
27085
  }
25284
27086
 
25285
27087
  .photon-item:hover {
25286
- background: hsla(220, 10%, 80%, 0.1);
27088
+ background: var(--bg-glass);
25287
27089
  }
25288
27090
 
25289
27091
  .photon-item.active {
@@ -25297,13 +27099,28 @@ BeamSidebar.styles = [
25297
27099
 
25298
27100
  @keyframes flash-highlight {
25299
27101
  0% {
25300
- background: hsla(260, 100%, 65%, 0.2);
27102
+ background: var(--glow-primary);
25301
27103
  }
25302
27104
  100% {
25303
27105
  background: transparent;
25304
27106
  }
25305
27107
  }
25306
27108
 
27109
+ .photon-item.warmth {
27110
+ animation: warmth-fade 5s ease-out forwards;
27111
+ }
27112
+
27113
+ @keyframes warmth-fade {
27114
+ 0% {
27115
+ background: var(--color-warning);
27116
+ opacity: 0.15;
27117
+ }
27118
+ 100% {
27119
+ background: var(--color-warning);
27120
+ opacity: 0;
27121
+ }
27122
+ }
27123
+
25307
27124
  .photon-icon {
25308
27125
  width: 28px;
25309
27126
  height: 28px;
@@ -25394,7 +27211,7 @@ BeamSidebar.styles = [
25394
27211
  gap: 3px;
25395
27212
  font-size: var(--text-2xs);
25396
27213
  padding: 2px 5px;
25397
- background: rgba(255, 255, 255, 0.05);
27214
+ background: var(--bg-glass);
25398
27215
  border: 1px solid var(--border-glass);
25399
27216
  border-radius: var(--radius-full);
25400
27217
  font-weight: 500;
@@ -25477,7 +27294,7 @@ BeamSidebar.styles = [
25477
27294
  .marketplace-btn:hover {
25478
27295
  background: var(--bg-panel);
25479
27296
  border-color: var(--accent-secondary);
25480
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
27297
+ box-shadow: var(--shadow-sm);
25481
27298
  transform: translateY(-1px);
25482
27299
  }
25483
27300
 
@@ -25709,6 +27526,9 @@ __decorateClass([
25709
27526
  __decorateClass([
25710
27527
  r5()
25711
27528
  ], BeamSidebar.prototype, "_recentPhotons", 2);
27529
+ __decorateClass([
27530
+ r5()
27531
+ ], BeamSidebar.prototype, "_notificationWarmth", 2);
25712
27532
  BeamSidebar = __decorateClass([
25713
27533
  t4("beam-sidebar")
25714
27534
  ], BeamSidebar);
@@ -25798,37 +27618,24 @@ var MethodCard = class extends i4 {
25798
27618
  </div>
25799
27619
  ${this.method.isTemplate ? b2`<span class="badge prompt">Prompt</span>` : ""}
25800
27620
  ${isCron ? b2`<span
25801
- class="badge"
25802
- style="background:hsla(215,80%,60%,0.15);color:hsl(215,80%,65%)"
27621
+ class="badge scheduled"
25803
27622
  title="Runs automatically on schedule: ${this.method.scheduled}"
25804
27623
  >⏱ Scheduled</span
25805
27624
  >` : ""}
25806
- ${isDeprecated ? b2`<span
25807
- class="badge"
25808
- style="background:hsla(0,0%,50%,0.15);color:hsl(0,0%,60%);text-decoration:line-through"
25809
- >Deprecated</span
25810
- >` : ""}
25811
- ${isCached ? b2`<span
25812
- class="badge"
25813
- style="background:hsla(280,60%,50%,0.15);color:hsl(280,60%,65%)"
25814
- >Cached</span
25815
- >` : ""}
25816
- ${isThrottled ? b2`<span
25817
- class="badge"
25818
- style="background:hsla(30,80%,50%,0.15);color:hsl(30,80%,60%)"
25819
- >Throttled</span
25820
- >` : ""}
25821
- ${isQueued ? b2`<span
25822
- class="badge"
25823
- style="background:hsla(200,70%,50%,0.15);color:hsl(200,70%,60%)"
25824
- >Queued</span
25825
- >` : ""}
27625
+ ${isDeprecated ? b2`<span class="badge deprecated">Deprecated</span>` : ""}
27626
+ ${isCached ? b2`<span class="badge cached">Cached</span>` : ""}
27627
+ ${isThrottled ? b2`<span class="badge throttled">Throttled</span>` : ""}
27628
+ ${isQueued ? b2`<span class="badge queued">Queued</span>` : ""}
25826
27629
  ${emitsEvent ? b2`<span
25827
- class="badge"
25828
- style="background:hsla(100,70%,50%,0.15);color:hsl(100,70%,60%)"
27630
+ class="badge event"
25829
27631
  title="Automatically emits event: ${this.method.eventName}"
25830
27632
  >📡 Event</span
25831
27633
  >` : ""}
27634
+ ${this.method.audience ? b2`<span
27635
+ class="badge audience"
27636
+ title="Target audience: ${this.method.audience.join(", ")}"
27637
+ >${this.method.audience.includes("user") && this.method.audience.includes("assistant") ? sizedIcon(users, 12) : this.method.audience.includes("user") ? sizedIcon(user, 12) : sizedIcon(bot, 12)}</span
27638
+ >` : ""}
25832
27639
  </div>
25833
27640
  ${this._editingDescription ? b2`
25834
27641
  <div class="description editing" @click=${(e8) => e8.stopPropagation()}>
@@ -26071,7 +27878,7 @@ MethodCard.styles = [
26071
27878
 
26072
27879
  .card:hover {
26073
27880
  transform: translateY(-2px);
26074
- box-shadow: 0 8px 32px -4px rgba(0, 0, 0, 0.3);
27881
+ box-shadow: var(--shadow-lg);
26075
27882
  border-left-color: var(--accent-primary);
26076
27883
  }
26077
27884
 
@@ -26249,7 +28056,7 @@ MethodCard.styles = [
26249
28056
  font-size: var(--text-xs);
26250
28057
  padding: 2px 8px;
26251
28058
  border-radius: var(--radius-md);
26252
- background: hsla(220, 10%, 80%, 0.1);
28059
+ background: var(--bg-glass);
26253
28060
  color: var(--t-muted);
26254
28061
  flex-shrink: 0;
26255
28062
  }
@@ -26259,6 +28066,42 @@ MethodCard.styles = [
26259
28066
  color: hsl(45, 80%, 60%);
26260
28067
  }
26261
28068
 
28069
+ .badge.scheduled {
28070
+ background: hsla(215, 80%, 60%, 0.15);
28071
+ color: hsl(215, 80%, 65%);
28072
+ }
28073
+
28074
+ .badge.deprecated {
28075
+ background: hsla(0, 0%, 50%, 0.15);
28076
+ color: hsl(0, 0%, 60%);
28077
+ text-decoration: line-through;
28078
+ }
28079
+
28080
+ .badge.cached {
28081
+ background: hsla(280, 60%, 50%, 0.15);
28082
+ color: hsl(280, 60%, 65%);
28083
+ }
28084
+
28085
+ .badge.throttled {
28086
+ background: hsla(30, 80%, 50%, 0.15);
28087
+ color: hsl(30, 80%, 60%);
28088
+ }
28089
+
28090
+ .badge.queued {
28091
+ background: hsla(200, 70%, 50%, 0.15);
28092
+ color: hsl(200, 70%, 60%);
28093
+ }
28094
+
28095
+ .badge.event {
28096
+ background: hsla(100, 70%, 50%, 0.15);
28097
+ color: hsl(100, 70%, 60%);
28098
+ }
28099
+
28100
+ .badge.audience {
28101
+ background: hsla(180, 60%, 50%, 0.15);
28102
+ color: hsl(180, 60%, 65%);
28103
+ }
28104
+
26262
28105
  .param-tags {
26263
28106
  display: flex;
26264
28107
  flex-wrap: wrap;
@@ -26272,9 +28115,9 @@ MethodCard.styles = [
26272
28115
  font-size: var(--text-xs);
26273
28116
  padding: 4px 10px;
26274
28117
  border-radius: var(--radius-xs);
26275
- background: hsla(260, 60%, 50%, 0.12);
26276
- color: var(--t-primary);
26277
- border: 1px solid hsla(260, 60%, 50%, 0.2);
28118
+ background: var(--param-tag-bg, hsla(260, 60%, 50%, 0.12));
28119
+ color: var(--param-tag-color, var(--t-primary));
28120
+ border: 1px solid var(--param-tag-border, hsla(260, 60%, 50%, 0.2));
26278
28121
  font-weight: 500;
26279
28122
  }
26280
28123
 
@@ -26283,8 +28126,8 @@ MethodCard.styles = [
26283
28126
  min-width: 20px;
26284
28127
  height: 20px;
26285
28128
  border-radius: var(--radius-sm);
26286
- background: hsla(45, 80%, 50%, 0.2);
26287
- color: hsl(45, 80%, 60%);
28129
+ background: var(--color-warning-bg, hsla(45, 80%, 50%, 0.2));
28130
+ color: var(--color-warning, hsl(45, 80%, 60%));
26288
28131
  display: inline-flex;
26289
28132
  align-items: center;
26290
28133
  justify-content: center;
@@ -26314,7 +28157,7 @@ MethodCard.styles = [
26314
28157
  align-items: center;
26315
28158
  justify-content: center;
26316
28159
  border-radius: var(--radius-sm);
26317
- background: hsla(220, 10%, 80%, 0.1);
28160
+ background: var(--bg-glass);
26318
28161
  }
26319
28162
 
26320
28163
  .card:hover .action-icon {
@@ -26329,7 +28172,7 @@ MethodCard.styles = [
26329
28172
  border: 1px solid var(--border-glass);
26330
28173
  border-radius: var(--radius-md);
26331
28174
  padding: var(--space-sm);
26332
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
28175
+ box-shadow: var(--shadow-lg);
26333
28176
  z-index: 100;
26334
28177
  display: grid;
26335
28178
  grid-template-columns: repeat(6, 1fr);
@@ -26498,6 +28341,30 @@ function singularize(word) {
26498
28341
  if (lower.endsWith("s") && !lower.endsWith("ss")) return word.slice(0, -1);
26499
28342
  return word;
26500
28343
  }
28344
+ function isValidCharForFormat(char, format, position) {
28345
+ if (!format) return true;
28346
+ switch (format.toLowerCase()) {
28347
+ case "email":
28348
+ return /[a-zA-Z0-9@.\-_+]/.test(char);
28349
+ case "url":
28350
+ case "uri":
28351
+ return /[a-zA-Z0-9:\/\-_.?=&#%@+~;,!]/.test(char);
28352
+ case "uuid":
28353
+ return /[0-9a-fA-F\-]/.test(char);
28354
+ case "ipv4":
28355
+ return /[0-9.]/.test(char);
28356
+ case "ipv6":
28357
+ return /[0-9a-fA-F:]/.test(char);
28358
+ case "slug":
28359
+ return /[a-z0-9\-]/.test(char);
28360
+ case "hex":
28361
+ return position === 0 && char === "#" ? true : /[0-9a-fA-F]/.test(char);
28362
+ case "phone":
28363
+ return /[0-9+\-() ]/.test(char);
28364
+ default:
28365
+ return true;
28366
+ }
28367
+ }
26501
28368
  var InvokeForm = class extends i4 {
26502
28369
  constructor() {
26503
28370
  super(...arguments);
@@ -26752,9 +28619,7 @@ var InvokeForm = class extends i4 {
26752
28619
  this._handleChange(key, arr.length > 0 ? arr : raw);
26753
28620
  }}
26754
28621
  />
26755
- <div class="hint" style="font-size:0.75rem;color:var(--t-muted);margin-top:2px;">
26756
- Comma-separated values
26757
- </div>
28622
+ <div class="hint">Comma-separated values</div>
26758
28623
  </div>
26759
28624
  `;
26760
28625
  }
@@ -26805,9 +28670,12 @@ var InvokeForm = class extends i4 {
26805
28670
  min="${min}"
26806
28671
  max="${max}"
26807
28672
  step="${step2}"
28673
+ style="${this._sliderFillStyle(Number(currentValue2), min, max)}"
26808
28674
  .value=${String(currentValue2)}
26809
28675
  @input=${(e8) => {
26810
- const v2 = Number(e8.target.value);
28676
+ const el2 = e8.target;
28677
+ const v2 = this._sanitizeSliderValue(Number(el2.value), min, max, step2);
28678
+ el2.style.cssText = this._sliderFillStyle(v2, min, max);
26811
28679
  this._handleChange(key, v2);
26812
28680
  }}
26813
28681
  />
@@ -26820,7 +28688,14 @@ var InvokeForm = class extends i4 {
26820
28688
  step="${step2}"
26821
28689
  .value=${displayValue2}
26822
28690
  @input=${(e8) => {
26823
- const v2 = Number(e8.target.value);
28691
+ const raw = e8.target.value;
28692
+ if (raw === "" || raw === "-") return;
28693
+ this._handleChange(key, Number(raw));
28694
+ }}
28695
+ @change=${(e8) => {
28696
+ const el2 = e8.target;
28697
+ const v2 = this._sanitizeSliderValue(Number(el2.value), min, max, step2);
28698
+ el2.value = String(v2);
26824
28699
  this._handleChange(key, v2);
26825
28700
  }}
26826
28701
  />
@@ -26839,14 +28714,31 @@ var InvokeForm = class extends i4 {
26839
28714
  return b2`
26840
28715
  <input
26841
28716
  id=${o8(inputId)}
26842
- type="number"
26843
- class="${errorClass}"
28717
+ type="text"
28718
+ class="number-input-clean ${errorClass}"
28719
+ inputmode=${isInteger ? "numeric" : "decimal"}
26844
28720
  ${hasMin ? `min="${schema.minimum}"` : ""}
26845
- step="${step}"
26846
28721
  placeholder="${defaultVal2}"
26847
28722
  .value=${displayValue}
28723
+ @keypress=${(e8) => {
28724
+ const char = e8.key;
28725
+ const isDigit = /\d/.test(char);
28726
+ const isMinus = char === "-";
28727
+ const isDecimal = char === "." && !isInteger;
28728
+ if (!isDigit && !isMinus && !isDecimal) {
28729
+ e8.preventDefault();
28730
+ }
28731
+ }}
26848
28732
  @input=${(e8) => {
26849
- const v2 = Number(e8.target.value);
28733
+ let text = e8.target.value;
28734
+ if (isInteger) {
28735
+ const cleaned = text.replace(/[^\d-]/g, "");
28736
+ text = cleaned.match(/-.*-/) ? cleaned.replace(/-/g, "").replace(/^/, "-") : cleaned;
28737
+ }
28738
+ let v2 = text === "" || text === "-" ? defaultVal2 : Number(text);
28739
+ if (isNaN(v2)) {
28740
+ v2 = defaultVal2;
28741
+ }
26850
28742
  this._handleChange(key, v2);
26851
28743
  }}
26852
28744
  />
@@ -26934,6 +28826,8 @@ var InvokeForm = class extends i4 {
26934
28826
  }
26935
28827
  const defaultVal = schema.default;
26936
28828
  const placeholder = defaultVal != null ? String(defaultVal) : "";
28829
+ const format = schema.format;
28830
+ const pattern = schema.pattern;
26937
28831
  return b2`
26938
28832
  <input
26939
28833
  id=${o8(inputId)}
@@ -26941,6 +28835,23 @@ var InvokeForm = class extends i4 {
26941
28835
  class="${errorClass}"
26942
28836
  placeholder="${placeholder}"
26943
28837
  .value=${this._values[key] || ""}
28838
+ @keypress=${(e8) => {
28839
+ if (!isValidCharForFormat(e8.key, format)) {
28840
+ e8.preventDefault();
28841
+ return;
28842
+ }
28843
+ if (pattern) {
28844
+ try {
28845
+ const currentValue = e8.target.value;
28846
+ const newValue = currentValue + e8.key;
28847
+ const regex = new RegExp(pattern);
28848
+ if (!regex.test(newValue)) {
28849
+ e8.preventDefault();
28850
+ }
28851
+ } catch (err) {
28852
+ }
28853
+ }
28854
+ }}
26944
28855
  @input=${(e8) => this._handleChange(key, e8.target.value)}
26945
28856
  />
26946
28857
  `;
@@ -27065,7 +28976,11 @@ var InvokeForm = class extends i4 {
27065
28976
  */
27066
28977
  _cleanDescription(desc, schema) {
27067
28978
  if (!desc) return desc;
27068
- let cleaned = desc.replace(
28979
+ let cleaned = desc;
28980
+ const lines = cleaned.split("\n").filter((line) => !line.trim().startsWith("@"));
28981
+ cleaned = lines.join("\n").trim();
28982
+ cleaned = cleaned.replace(/\s*@\w+[\s\S]*?(?=[.!?\n]|$)/g, "").trim();
28983
+ cleaned = cleaned.replace(
27069
28984
  /['"][\w-]+['"]\s*(?:\([^)]*\)\s*)?(?:\|\s*['"][\w-]+['"]\s*(?:\([^)]*\)\s*)?)+/g,
27070
28985
  ""
27071
28986
  );
@@ -27101,6 +29016,23 @@ var InvokeForm = class extends i4 {
27101
29016
  this._savePersistedValues();
27102
29017
  }
27103
29018
  }
29019
+ _sliderFillStyle(value, min, max) {
29020
+ const pct = max > min ? (value - min) / (max - min) * 100 : 0;
29021
+ return `background: linear-gradient(to right, var(--accent-primary) ${pct}%, var(--border-glass) ${pct}%)`;
29022
+ }
29023
+ /** Clamp to [min, max] and round to nearest step (integer-safe). */
29024
+ _sanitizeSliderValue(raw, min, max, step) {
29025
+ let v2 = Number.isFinite(raw) ? raw : min;
29026
+ v2 = Math.min(max, Math.max(min, v2));
29027
+ if (step > 0) {
29028
+ v2 = min + Math.round((v2 - min) / step) * step;
29029
+ if (v2 > max) v2 = max;
29030
+ }
29031
+ if (Number.isInteger(step) && step >= 1) {
29032
+ v2 = Math.round(v2);
29033
+ }
29034
+ return v2;
29035
+ }
27104
29036
  _handleChange(key, value) {
27105
29037
  this._values = { ...this._values, [key]: value };
27106
29038
  if (this.rememberValues) {
@@ -27108,7 +29040,7 @@ var InvokeForm = class extends i4 {
27108
29040
  }
27109
29041
  }
27110
29042
  _buildCliCommand() {
27111
- const parts = ["photon", "cli", this.photonName, this.methodName];
29043
+ const parts = [this.photonName, this.methodName];
27112
29044
  for (const [key, value] of Object.entries(this._values)) {
27113
29045
  if (value === void 0 || value === null || value === "") continue;
27114
29046
  const strVal = typeof value === "object" ? JSON.stringify(value) : String(value);
@@ -27232,7 +29164,9 @@ var InvokeForm = class extends i4 {
27232
29164
  >
27233
29165
  <option value="">Select...</option>
27234
29166
  ${schema.enum.map(
27235
- (opt) => b2` <option value=${opt} ?selected=${opt === value}>${opt}</option> `
29167
+ (opt) => b2`
29168
+ <option value=${opt} ?selected=${opt === value}>${capitalizeEnumValue(opt)}</option>
29169
+ `
27236
29170
  )}
27237
29171
  </select>
27238
29172
  `;
@@ -27268,8 +29202,14 @@ var InvokeForm = class extends i4 {
27268
29202
  min="${min}"
27269
29203
  max="${max}"
27270
29204
  step="${step2}"
29205
+ style="${this._sliderFillStyle(Number(currentVal2), min, max)}"
27271
29206
  .value=${String(currentVal2)}
27272
- @input=${(e8) => onChange(propKey, Number(e8.target.value))}
29207
+ @input=${(e8) => {
29208
+ const el2 = e8.target;
29209
+ const v2 = this._sanitizeSliderValue(Number(el2.value), min, max, step2);
29210
+ el2.style.cssText = this._sliderFillStyle(v2, min, max);
29211
+ onChange(propKey, v2);
29212
+ }}
27273
29213
  />
27274
29214
  <input
27275
29215
  type="number"
@@ -27278,7 +29218,17 @@ var InvokeForm = class extends i4 {
27278
29218
  max="${max}"
27279
29219
  step="${step2}"
27280
29220
  .value=${displayVal2}
27281
- @input=${(e8) => onChange(propKey, Number(e8.target.value))}
29221
+ @input=${(e8) => {
29222
+ const raw = e8.target.value;
29223
+ if (raw === "" || raw === "-") return;
29224
+ onChange(propKey, Number(raw));
29225
+ }}
29226
+ @change=${(e8) => {
29227
+ const el2 = e8.target;
29228
+ const v2 = this._sanitizeSliderValue(Number(el2.value), min, max, step2);
29229
+ el2.value = String(v2);
29230
+ onChange(propKey, v2);
29231
+ }}
27282
29232
  />
27283
29233
  </div>
27284
29234
  <div class="range-labels">
@@ -27445,18 +29395,23 @@ var InvokeForm = class extends i4 {
27445
29395
  >
27446
29396
  <option value="">Select...</option>
27447
29397
  ${schema.enum.map(
27448
- (opt) => b2` <option value=${opt} ?selected=${opt === value}>${opt}</option> `
29398
+ (opt) => b2`
29399
+ <option value=${opt} ?selected=${opt === value}>${capitalizeEnumValue(opt)}</option>
29400
+ `
27449
29401
  )}
27450
29402
  </select>
27451
29403
  `;
27452
29404
  }
27453
29405
  if (schema.type === "boolean") {
27454
29406
  return b2`
27455
- <input
27456
- type="checkbox"
27457
- .checked=${!!value}
27458
- @change=${(e8) => handleNestedChange(e8.target.checked)}
27459
- />
29407
+ <label class="switch">
29408
+ <input
29409
+ type="checkbox"
29410
+ .checked=${!!value}
29411
+ @change=${(e8) => handleNestedChange(e8.target.checked)}
29412
+ />
29413
+ <span class="slider"></span>
29414
+ </label>
27460
29415
  `;
27461
29416
  }
27462
29417
  if (schema.type === "number" || schema.type === "integer") {
@@ -27478,8 +29433,14 @@ var InvokeForm = class extends i4 {
27478
29433
  min="${min}"
27479
29434
  max="${max}"
27480
29435
  step="${step2}"
29436
+ style="${this._sliderFillStyle(Number(currentVal2), min, max)}"
27481
29437
  .value=${String(currentVal2)}
27482
- @input=${(e8) => handleNestedChange(Number(e8.target.value))}
29438
+ @input=${(e8) => {
29439
+ const el2 = e8.target;
29440
+ const v2 = this._sanitizeSliderValue(Number(el2.value), min, max, step2);
29441
+ el2.style.cssText = this._sliderFillStyle(v2, min, max);
29442
+ handleNestedChange(v2);
29443
+ }}
27483
29444
  />
27484
29445
  <input
27485
29446
  type="number"
@@ -27488,7 +29449,17 @@ var InvokeForm = class extends i4 {
27488
29449
  max="${max}"
27489
29450
  step="${step2}"
27490
29451
  .value=${displayVal2}
27491
- @input=${(e8) => handleNestedChange(Number(e8.target.value))}
29452
+ @input=${(e8) => {
29453
+ const raw = e8.target.value;
29454
+ if (raw === "" || raw === "-") return;
29455
+ handleNestedChange(Number(raw));
29456
+ }}
29457
+ @change=${(e8) => {
29458
+ const el2 = e8.target;
29459
+ const v2 = this._sanitizeSliderValue(Number(el2.value), min, max, step2);
29460
+ el2.value = String(v2);
29461
+ handleNestedChange(v2);
29462
+ }}
27492
29463
  />
27493
29464
  </div>
27494
29465
  <div class="range-labels">
@@ -27839,6 +29810,7 @@ InvokeForm.styles = [
27839
29810
  font-size: var(--text-xs);
27840
29811
  padding: 2px 6px;
27841
29812
  border-radius: var(--radius-xs);
29813
+ transition: background 0.15s ease;
27842
29814
  }
27843
29815
 
27844
29816
  .array-item-remove:hover {
@@ -27915,11 +29887,17 @@ InvokeForm.styles = [
27915
29887
  margin-top: var(--space-xs);
27916
29888
  }
27917
29889
 
29890
+ .hint {
29891
+ font-size: var(--text-xs);
29892
+ color: var(--t-muted);
29893
+ margin-top: 2px;
29894
+ }
29895
+
27918
29896
  /* Slider-First Numeric Input */
27919
29897
  .slider-group {
27920
29898
  display: flex;
27921
29899
  flex-direction: column;
27922
- gap: 6px;
29900
+ gap: 4px;
27923
29901
  }
27924
29902
 
27925
29903
  .slider-row {
@@ -27930,57 +29908,140 @@ InvokeForm.styles = [
27930
29908
 
27931
29909
  .slider-row input[type='range'] {
27932
29910
  flex: 1;
27933
- height: 6px;
27934
- background: rgba(255, 255, 255, 0.12);
27935
- border: 1px solid rgba(255, 255, 255, 0.08);
27936
- border-radius: 3px;
29911
+ height: 4px;
29912
+ border-radius: var(--radius-full, 9999px);
27937
29913
  -webkit-appearance: none;
27938
29914
  cursor: pointer;
27939
29915
  margin: 8px 0;
29916
+ border: none;
29917
+ outline: none;
29918
+ /* Default unfilled track — overridden by inline style for fill effect */
29919
+ background: var(--border-glass);
27940
29920
  }
27941
29921
 
27942
29922
  .slider-row input[type='range']::-webkit-slider-thumb {
27943
29923
  -webkit-appearance: none;
27944
- width: 20px;
27945
- height: 20px;
29924
+ width: 16px;
29925
+ height: 16px;
27946
29926
  background: var(--accent-primary);
29927
+ border: 2px solid var(--bg-panel, var(--bg-glass));
27947
29928
  border-radius: 50%;
27948
29929
  cursor: pointer;
27949
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
29930
+ box-shadow:
29931
+ 0 0 0 2px var(--accent-primary),
29932
+ 0 1px 4px rgba(0, 0, 0, 0.3);
29933
+ transition:
29934
+ box-shadow 0.15s ease,
29935
+ transform 0.15s ease;
29936
+ }
29937
+
29938
+ .slider-row input[type='range']::-webkit-slider-thumb:hover {
29939
+ box-shadow:
29940
+ 0 0 0 3px var(--accent-primary),
29941
+ 0 0 8px var(--glow-primary);
29942
+ transform: scale(1.1);
29943
+ }
29944
+
29945
+ .slider-row input[type='range']:active::-webkit-slider-thumb {
29946
+ transform: scale(0.95);
27950
29947
  }
27951
29948
 
27952
29949
  .slider-row input[type='range']::-moz-range-track {
27953
- height: 6px;
27954
- background: rgba(255, 255, 255, 0.12);
27955
- border-radius: 3px;
27956
- border: 1px solid rgba(255, 255, 255, 0.08);
29950
+ height: 4px;
29951
+ background: var(--border-glass);
29952
+ border-radius: var(--radius-full, 9999px);
29953
+ border: none;
29954
+ }
29955
+
29956
+ .slider-row input[type='range']::-moz-range-progress {
29957
+ height: 4px;
29958
+ background: var(--accent-primary);
29959
+ border-radius: var(--radius-full, 9999px);
27957
29960
  }
27958
29961
 
27959
29962
  .slider-row input[type='range']::-moz-range-thumb {
27960
- width: 20px;
27961
- height: 20px;
29963
+ width: 12px;
29964
+ height: 12px;
27962
29965
  background: var(--accent-primary);
29966
+ border: 2px solid var(--bg-panel, var(--bg-glass));
27963
29967
  border-radius: 50%;
27964
29968
  cursor: pointer;
27965
- border: none;
27966
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
29969
+ box-shadow:
29970
+ 0 0 0 2px var(--accent-primary),
29971
+ 0 1px 4px rgba(0, 0, 0, 0.3);
29972
+ transition:
29973
+ box-shadow 0.15s ease,
29974
+ transform 0.15s ease;
27967
29975
  }
27968
29976
 
27969
- .slider-value {
27970
- min-width: 60px;
29977
+ .slider-row input[type='range']::-moz-range-thumb:hover {
29978
+ box-shadow:
29979
+ 0 0 0 3px var(--accent-primary),
29980
+ 0 0 8px var(--glow-primary);
29981
+ }
29982
+
29983
+ .slider-number-input {
29984
+ width: 64px;
27971
29985
  text-align: center;
29986
+ font-size: var(--text-sm);
29987
+ font-weight: 500;
29988
+ font-variant-numeric: tabular-nums;
29989
+ padding: 4px 6px;
29990
+ background: var(--bg-glass);
29991
+ border: 1px solid var(--border-glass) !important;
29992
+ border-radius: var(--radius-sm);
29993
+ color: var(--t-primary);
29994
+ -moz-appearance: textfield;
29995
+ transition:
29996
+ border-color 0.15s ease,
29997
+ box-shadow 0.15s ease;
29998
+ }
29999
+
30000
+ .slider-number-input::-webkit-outer-spin-button,
30001
+ .slider-number-input::-webkit-inner-spin-button {
30002
+ -webkit-appearance: none;
30003
+ margin: 0;
30004
+ }
30005
+
30006
+ .slider-number-input:focus-visible {
30007
+ outline: none;
30008
+ border-color: var(--accent-primary) !important;
30009
+ box-shadow: 0 0 0 2px var(--glow-primary);
30010
+ }
30011
+
30012
+ /* Clean numeric input — no spinner, mouse wheel support */
30013
+ .number-input-clean {
30014
+ width: 100%;
30015
+ padding: 8px 12px;
27972
30016
  font-size: var(--text-md);
27973
- font-weight: 600;
30017
+ background: var(--bg-glass);
30018
+ border: 1px solid var(--border-glass);
30019
+ border-radius: var(--radius-sm);
27974
30020
  color: var(--t-primary);
27975
- font-variant-numeric: tabular-nums;
30021
+ appearance: none;
30022
+ -moz-appearance: textfield;
30023
+ -webkit-appearance: none;
30024
+ transition:
30025
+ border-color 0.15s ease,
30026
+ box-shadow 0.15s ease;
27976
30027
  }
27977
30028
 
27978
- .slider-number-input {
27979
- width: 80px;
27980
- text-align: right;
27981
- font-size: var(--text-sm);
27982
- padding: 4px 8px;
27983
- border: 1px solid rgba(255, 255, 255, 0.08) !important;
30029
+ .number-input-clean::-webkit-outer-spin-button,
30030
+ .number-input-clean::-webkit-inner-spin-button {
30031
+ -webkit-appearance: none;
30032
+ appearance: none;
30033
+ display: none;
30034
+ margin: 0;
30035
+ }
30036
+
30037
+ .number-input-clean:hover {
30038
+ border-color: var(--accent-primary);
30039
+ }
30040
+
30041
+ .number-input-clean:focus-visible {
30042
+ outline: none;
30043
+ border-color: var(--accent-primary);
30044
+ box-shadow: 0 0 0 2px var(--glow-primary);
27984
30045
  }
27985
30046
 
27986
30047
  .range-labels {
@@ -28029,6 +30090,15 @@ InvokeForm.styles = [
28029
30090
  flex-shrink: 0;
28030
30091
  }
28031
30092
 
30093
+ select {
30094
+ appearance: none;
30095
+ -webkit-appearance: none;
30096
+ 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");
30097
+ background-repeat: no-repeat;
30098
+ background-position: right 12px center;
30099
+ padding-right: 32px;
30100
+ }
30101
+
28032
30102
  /* ===== Responsive Design ===== */
28033
30103
  @media (max-width: 768px) {
28034
30104
  .form-container {
@@ -28231,7 +30301,7 @@ ActivityLog.styles = [
28231
30301
  font-size: var(--text-sm);
28232
30302
  text-transform: uppercase;
28233
30303
  letter-spacing: 0.1em;
28234
- color: var(--t-secondary); /* Brighter */
30304
+ color: var(--t-primary);
28235
30305
  font-weight: 600;
28236
30306
  }
28237
30307
 
@@ -28246,7 +30316,7 @@ ActivityLog.styles = [
28246
30316
  }
28247
30317
 
28248
30318
  .clear-btn:hover {
28249
- background: hsla(220, 10%, 80%, 0.1);
30319
+ background: var(--bg-glass);
28250
30320
  color: var(--t-primary);
28251
30321
  }
28252
30322
 
@@ -28265,14 +30335,14 @@ ActivityLog.styles = [
28265
30335
  }
28266
30336
 
28267
30337
  .filter-btn:hover {
28268
- background: hsla(220, 10%, 80%, 0.1);
30338
+ background: var(--bg-glass);
28269
30339
  color: var(--t-primary);
28270
30340
  }
28271
30341
 
28272
30342
  .filter-btn.active {
28273
30343
  border-color: var(--accent-secondary);
28274
30344
  color: var(--accent-secondary);
28275
- background: hsla(210, 80%, 65%, 0.08);
30345
+ background: var(--bg-glass);
28276
30346
  }
28277
30347
 
28278
30348
  .visually-hidden {
@@ -28332,7 +30402,7 @@ ActivityLog.styles = [
28332
30402
  display: inline-block;
28333
30403
  font-size: var(--text-xs);
28334
30404
  color: var(--t-muted);
28335
- background: hsla(220, 10%, 80%, 0.08);
30405
+ background: var(--bg-glass);
28336
30406
  padding: 1px 6px;
28337
30407
  border-radius: var(--radius-full);
28338
30408
  margin-left: var(--space-sm);
@@ -28491,6 +30561,8 @@ var ResultViewer = class extends i4 {
28491
30561
  this._previousResult = null;
28492
30562
  // Recency heat: track when items were last added/updated
28493
30563
  this._itemHeatTimestamps = /* @__PURE__ */ new Map();
30564
+ // Audit trail expansion state: track which items have expanded audit trails
30565
+ this._expandedAuditTrails = /* @__PURE__ */ new Set();
28494
30566
  // The detected ID field for the current result (shared across diff, animation, warmth)
28495
30567
  this._activeIdField = "id";
28496
30568
  // Chart.js instance for reactive updates
@@ -28793,19 +30865,32 @@ var ResultViewer = class extends i4 {
28793
30865
  /**
28794
30866
  * Get the warmth class based on recency heat.
28795
30867
  * Reads timestamp from item data first (survives refresh), falls back to in-memory map.
30868
+ * Prioritizes __meta timestamps (most recent), then standard timestamp fields.
28796
30869
  */
28797
30870
  _getItemWarmthClass(item) {
28798
30871
  const idField = this._activeIdField;
28799
30872
  let timestamp;
28800
30873
  if (item && typeof item === "object") {
28801
30874
  const rec = item;
28802
- for (const field of ResultViewer._TIMESTAMP_FIELDS) {
28803
- const val = rec[field];
28804
- if (val !== void 0 && val !== null) {
28805
- const parsed = typeof val === "number" ? val : new Date(typeof val === "string" ? val : String(val)).getTime();
28806
- if (!isNaN(parsed)) {
28807
- timestamp = parsed;
28808
- break;
30875
+ const meta4 = rec.__meta;
30876
+ if (meta4 && typeof meta4 === "object") {
30877
+ if (meta4.modifiedAt) {
30878
+ const parsed = new Date(meta4.modifiedAt).getTime();
30879
+ if (!isNaN(parsed)) timestamp = parsed;
30880
+ } else if (meta4.createdAt) {
30881
+ const parsed = new Date(meta4.createdAt).getTime();
30882
+ if (!isNaN(parsed)) timestamp = parsed;
30883
+ }
30884
+ }
30885
+ if (timestamp === void 0) {
30886
+ for (const field of ResultViewer._TIMESTAMP_FIELDS) {
30887
+ const val = rec[field];
30888
+ if (val !== void 0 && val !== null) {
30889
+ const parsed = typeof val === "number" ? val : new Date(typeof val === "string" ? val : String(val)).getTime();
30890
+ if (!isNaN(parsed)) {
30891
+ timestamp = parsed;
30892
+ break;
30893
+ }
28809
30894
  }
28810
30895
  }
28811
30896
  }
@@ -29314,7 +31399,8 @@ var ResultViewer = class extends i4 {
29314
31399
  "tabs",
29315
31400
  "accordion",
29316
31401
  "stack",
29317
- "columns"
31402
+ "columns",
31403
+ "qr"
29318
31404
  ].includes(format)) {
29319
31405
  return format;
29320
31406
  }
@@ -29436,10 +31522,46 @@ var ResultViewer = class extends i4 {
29436
31522
  if (strFields === 1 && numFields >= 2 && keys2.length <= 6) return true;
29437
31523
  return false;
29438
31524
  }
31525
+ /**
31526
+ * Check if data matches the expected shape for a format.
31527
+ * Returns false when the data clearly doesn't fit, so the renderer
31528
+ * should fall through to the default (JSON) view instead of producing
31529
+ * a broken or nonsensical rendering.
31530
+ */
31531
+ _matchesFormat(layout, data) {
31532
+ if (data === null || data === void 0) return true;
31533
+ switch (layout) {
31534
+ case "qr":
31535
+ if (typeof data === "string") return true;
31536
+ if (typeof data === "object") {
31537
+ return !!(data.qr || data.url || data.link || data.value);
31538
+ }
31539
+ return false;
31540
+ case "table":
31541
+ return Array.isArray(data) || typeof data === "object" && data !== null;
31542
+ case "metric":
31543
+ case "gauge":
31544
+ return typeof data === "object" && !Array.isArray(data) && (data.value !== void 0 || data.count !== void 0 || data.total !== void 0 || data.current !== void 0);
31545
+ case "chart":
31546
+ return typeof data !== "string";
31547
+ case "mermaid":
31548
+ return typeof data === "string";
31549
+ case "markdown":
31550
+ return typeof data === "string";
31551
+ default:
31552
+ return true;
31553
+ }
31554
+ }
29439
31555
  _renderContent(layout, filteredData) {
29440
31556
  if (filteredData === null) {
29441
31557
  return b2`<div class="empty-state">No matches found</div>`;
29442
31558
  }
31559
+ if (filteredData && filteredData._error) {
31560
+ return this._renderErrorCard(filteredData.message || "Unknown error");
31561
+ }
31562
+ if (!this._matchesFormat(layout, filteredData)) {
31563
+ return this._renderJson(filteredData);
31564
+ }
29443
31565
  switch (layout) {
29444
31566
  case "table":
29445
31567
  return this._renderTable(filteredData);
@@ -29481,6 +31603,8 @@ var ResultViewer = class extends i4 {
29481
31603
  return this._renderStack(filteredData);
29482
31604
  case "columns":
29483
31605
  return this._renderColumns(filteredData);
31606
+ case "qr":
31607
+ return this._renderQR(filteredData);
29484
31608
  case "mermaid":
29485
31609
  return this._renderMermaid(filteredData);
29486
31610
  case "json":
@@ -29723,6 +31847,136 @@ var ResultViewer = class extends i4 {
29723
31847
  </div>
29724
31848
  `
29725
31849
  )}
31850
+ ${this._renderAuditTrail(data)}
31851
+ `;
31852
+ }
31853
+ /**
31854
+ * Render audit trail from __meta object if present
31855
+ */
31856
+ _renderAuditTrail(data) {
31857
+ if (!data || typeof data !== "object") return "";
31858
+ const meta4 = data.__meta;
31859
+ if (!meta4 || typeof meta4 !== "object") return "";
31860
+ const idField = this._activeIdField;
31861
+ const itemId = data[idField] ? String(data[idField]) : Math.random().toString(36);
31862
+ const auditKey = `audit-${itemId}`;
31863
+ const isExpanded = this._expandedAuditTrails.has(auditKey);
31864
+ const formatTime = (isoString) => {
31865
+ if (!isoString) return "N/A";
31866
+ const date4 = new Date(isoString);
31867
+ return date4.toLocaleString();
31868
+ };
31869
+ const createdAt = formatTime(meta4.createdAt);
31870
+ const modifiedAt = meta4.modifiedAt ? formatTime(meta4.modifiedAt) : null;
31871
+ return b2`
31872
+ <div
31873
+ style="margin-top: var(--space-md); border-top: 1px solid var(--border-glass); padding-top: var(--space-md);"
31874
+ >
31875
+ <details
31876
+ ?open="${isExpanded}"
31877
+ @toggle="${(e8) => {
31878
+ const detail = e8.target;
31879
+ if (detail.open) {
31880
+ this._expandedAuditTrails.add(auditKey);
31881
+ } else {
31882
+ this._expandedAuditTrails.delete(auditKey);
31883
+ }
31884
+ }}"
31885
+ >
31886
+ <summary
31887
+ style="cursor: pointer; font-weight: 500; display: flex; align-items: center; gap: var(--space-sm);"
31888
+ >
31889
+ <span
31890
+ style="display: inline-block; width: 0.5em; height: 0.5em; border-radius: 50%; background: var(--text-secondary); margin-right: var(--space-xs);"
31891
+ ></span>
31892
+ Audit Trail
31893
+ ${meta4.modifications?.length ? b2`<span style="font-size: 0.85em; color: var(--text-secondary);"
31894
+ >(${meta4.modifications.length} changes)</span
31895
+ >` : ""}
31896
+ </summary>
31897
+
31898
+ <div
31899
+ style="margin-top: var(--space-md); padding: var(--space-sm) var(--space-md); background: var(--bg-secondary); border-radius: var(--radius-sm);"
31900
+ >
31901
+ <table style="width: 100%; font-size: 0.9em;">
31902
+ <tbody>
31903
+ <tr style="border-bottom: 1px solid var(--border-subtle);">
31904
+ <td
31905
+ style="padding: var(--space-xs); color: var(--text-secondary); font-weight: 500;"
31906
+ >
31907
+ Created
31908
+ </td>
31909
+ <td style="padding: var(--space-xs);">${createdAt}</td>
31910
+ ${meta4.createdBy ? b2`<td
31911
+ style="padding: var(--space-xs); color: var(--text-secondary); font-size: 0.85em;"
31912
+ >
31913
+ (by: ${meta4.createdBy})
31914
+ </td>` : ""}
31915
+ </tr>
31916
+ ${modifiedAt ? b2`
31917
+ <tr style="border-bottom: 1px solid var(--border-subtle);">
31918
+ <td
31919
+ style="padding: var(--space-xs); color: var(--text-secondary); font-weight: 500;"
31920
+ >
31921
+ Modified
31922
+ </td>
31923
+ <td style="padding: var(--space-xs);">${modifiedAt}</td>
31924
+ ${meta4.modifiedBy ? b2`<td
31925
+ style="padding: var(--space-xs); color: var(--text-secondary); font-size: 0.85em;"
31926
+ >
31927
+ (by: ${meta4.modifiedBy})
31928
+ </td>` : ""}
31929
+ </tr>
31930
+ ` : ""}
31931
+ </tbody>
31932
+ </table>
31933
+
31934
+ ${meta4.modifications && Array.isArray(meta4.modifications) && meta4.modifications.length > 0 ? b2`
31935
+ <div style="margin-top: var(--space-md);">
31936
+ <div
31937
+ style="font-weight: 500; margin-bottom: var(--space-sm); color: var(--text-secondary);"
31938
+ >
31939
+ Changes
31940
+ </div>
31941
+ <ul style="list-style: none; padding: 0; margin: 0;">
31942
+ ${meta4.modifications.map(
31943
+ (mod, idx) => b2`
31944
+ <li
31945
+ key="${idx}"
31946
+ 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;"
31947
+ >
31948
+ <div style="margin-bottom: 2px;">
31949
+ <span style="font-weight: 500; color: var(--text-primary);"
31950
+ >${mod.field}</span
31951
+ >
31952
+ <span style="color: var(--text-secondary); margin: 0 var(--space-xs);"
31953
+ >→</span
31954
+ >
31955
+ <span style="color: var(--text-secondary); font-size: 0.85em;"
31956
+ >${formatTime(mod.timestamp || (/* @__PURE__ */ new Date()).toISOString())}</span
31957
+ >
31958
+ </div>
31959
+ <div
31960
+ style="font-family: monospace; font-size: 0.85em; color: var(--text-secondary); margin-left: var(--space-sm);"
31961
+ >
31962
+ <span style="color: #d87070;">${JSON.stringify(mod.oldValue)}</span>
31963
+ <span style="margin: 0 4px;">→</span>
31964
+ <span style="color: #7cb342;">${JSON.stringify(mod.newValue)}</span>
31965
+ </div>
31966
+ ${mod.modifiedBy ? b2`<div
31967
+ style="font-size: 0.8em; color: var(--text-secondary); margin-top: 2px;"
31968
+ >
31969
+ <em>by: ${mod.modifiedBy}</em>
31970
+ </div>` : ""}
31971
+ </li>
31972
+ `
31973
+ )}
31974
+ </ul>
31975
+ </div>
31976
+ ` : ""}
31977
+ </div>
31978
+ </details>
31979
+ </div>
29726
31980
  `;
29727
31981
  }
29728
31982
  /** Returns true for arrays of objects or large nested objects that deserve their own section */
@@ -30181,6 +32435,163 @@ ${code}</pre>`;
30181
32435
  style="position: relative; background: ${bgColor}; border-radius: var(--radius-sm); padding: 16px; margin: 16px 0; min-height: 120px;"
30182
32436
  ></div>`;
30183
32437
  }
32438
+ _renderQR(data) {
32439
+ const text = typeof data === "object" && data !== null ? String(data.qr || data.url || data.link || data.value) : String(data);
32440
+ const isUrl = /^https?:\/\//i.test(text);
32441
+ const isEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(text);
32442
+ const isPhone = /^[+]?[\d\s\-().]{7,}$/.test(text.trim());
32443
+ let href = "";
32444
+ let linkLabel = text;
32445
+ if (isUrl) {
32446
+ href = text;
32447
+ try {
32448
+ linkLabel = new URL(text).hostname + new URL(text).pathname;
32449
+ } catch {
32450
+ }
32451
+ } else if (isEmail) {
32452
+ href = `mailto:${text}`;
32453
+ } else if (isPhone) {
32454
+ href = `tel:${text.replace(/[\s\-().]/g, "")}`;
32455
+ }
32456
+ setTimeout(() => {
32457
+ if (this._qrContainer) {
32458
+ this._qrContainer.innerHTML = "";
32459
+ try {
32460
+ const containerWidth = this._qrContainer.clientWidth;
32461
+ const qrSize = Math.max(200, Math.min(containerWidth - 48, 400));
32462
+ new window.QRCode(this._qrContainer, {
32463
+ text,
32464
+ width: qrSize,
32465
+ height: qrSize,
32466
+ correctLevel: window.QRCode?.CorrectLevel?.H,
32467
+ colorDark: "#000000",
32468
+ colorLight: "#ffffff"
32469
+ });
32470
+ } catch (error2) {
32471
+ console.error("Failed to generate QR code:", error2);
32472
+ }
32473
+ }
32474
+ }, 0);
32475
+ const message = typeof data === "object" && data !== null ? data.message : void 0;
32476
+ const extraFields = [];
32477
+ if (typeof data === "object" && data !== null) {
32478
+ for (const [k3, v2] of Object.entries(data)) {
32479
+ if (["url", "link", "value", "message", "port"].includes(k3) || v2 == null || typeof v2 === "object")
32480
+ continue;
32481
+ extraFields.push({ label: k3, value: `${v2}` });
32482
+ }
32483
+ }
32484
+ return b2`<div
32485
+ style="
32486
+ display: flex; flex-direction: column; align-items: center; gap: 0;
32487
+ padding: 0; border-radius: var(--radius-md);
32488
+ border: 1px solid var(--border-glass);
32489
+ background: var(--bg-subtle); overflow: hidden;
32490
+ width: 40%; min-width: 280px; max-width: 480px;
32491
+ margin: 16px auto;
32492
+ "
32493
+ >
32494
+ <div
32495
+ id="qr-container"
32496
+ style="
32497
+ width: 100%; padding: 24px;
32498
+ display: flex; justify-content: center; align-items: center;
32499
+ background: #ffffff; border-radius: var(--radius-md) var(--radius-md) 0 0;
32500
+ "
32501
+ ></div>
32502
+
32503
+ <div
32504
+ style="
32505
+ width: 100%; padding: 16px 20px;
32506
+ display: flex; flex-direction: column; gap: 10px;
32507
+ border-top: 1px solid var(--border-glass);
32508
+ "
32509
+ >
32510
+ ${message ? b2`<div
32511
+ style="
32512
+ font-size: 0.8rem; color: var(--t-secondary, #a0a0a0);
32513
+ text-align: center; font-weight: 500;
32514
+ "
32515
+ >
32516
+ ${message}
32517
+ </div>` : ""}
32518
+ ${href ? b2`<div
32519
+ style="display: flex; align-items: center; gap: 10px; justify-content: center;"
32520
+ >
32521
+ <a
32522
+ href="${href}"
32523
+ target="_blank"
32524
+ rel="noopener noreferrer"
32525
+ style="
32526
+ font-size: 0.85rem; color: var(--accent, #3b82f6);
32527
+ text-decoration: none; font-weight: 500;
32528
+ text-align: center; word-break: break-word;
32529
+ "
32530
+ @mouseenter=${(e8) => e8.target.style.textDecoration = "underline"}
32531
+ @mouseleave=${(e8) => e8.target.style.textDecoration = "none"}
32532
+ >${linkLabel}</a
32533
+ >
32534
+ <button
32535
+ title="Copy to clipboard"
32536
+ style="
32537
+ background: var(--bg-subtle, rgba(255,255,255,0.06));
32538
+ border: 1px solid var(--border-glass);
32539
+ border-radius: 6px; padding: 6px 10px; cursor: pointer;
32540
+ color: var(--t-muted); font-size: 0.85rem; flex-shrink: 0;
32541
+ transition: all 0.15s; line-height: 1;
32542
+ "
32543
+ @mouseenter=${(e8) => {
32544
+ e8.target.style.color = "var(--accent, #3b82f6)";
32545
+ e8.target.style.borderColor = "var(--accent, #3b82f6)";
32546
+ }}
32547
+ @mouseleave=${(e8) => {
32548
+ e8.target.style.color = "var(--t-muted)";
32549
+ e8.target.style.borderColor = "var(--border-glass)";
32550
+ }}
32551
+ @click=${(e8) => {
32552
+ void navigator.clipboard.writeText(text);
32553
+ const btn = e8.target;
32554
+ const orig = btn.textContent;
32555
+ btn.textContent = "\u2713 Copied";
32556
+ btn.style.color = "var(--accent, #3b82f6)";
32557
+ setTimeout(() => {
32558
+ btn.textContent = orig;
32559
+ btn.style.color = "var(--t-muted)";
32560
+ }, 1500);
32561
+ }}
32562
+ >
32563
+ Copy
32564
+ </button>
32565
+ </div>` : b2`<div
32566
+ style="
32567
+ font-size: 0.875rem; color: var(--t-muted);
32568
+ text-align: center; word-break: break-all;
32569
+ "
32570
+ >
32571
+ ${text}
32572
+ </div>`}
32573
+ ${extraFields.length > 0 ? b2`<div
32574
+ style="
32575
+ display: flex; flex-direction: column; gap: 6px;
32576
+ padding-top: 10px; border-top: 1px solid var(--border-glass);
32577
+ font-size: 0.8rem;
32578
+ "
32579
+ >
32580
+ ${extraFields.map(
32581
+ (f5) => b2`<div style="display: flex; justify-content: space-between; gap: 12px;">
32582
+ <span style="color: var(--t-muted); text-transform: capitalize;"
32583
+ >${f5.label}</span
32584
+ >
32585
+ <span
32586
+ style="color: var(--t-primary); font-family: var(--font-mono, monospace); word-break: break-all; text-align: right;"
32587
+ >${f5.value}</span
32588
+ >
32589
+ </div>`
32590
+ )}
32591
+ </div>` : ""}
32592
+ </div>
32593
+ </div>`;
32594
+ }
30184
32595
  _renderText(data) {
30185
32596
  const text = String(data);
30186
32597
  if (this._isMermaidString(text)) {
@@ -30188,6 +32599,37 @@ ${code}</pre>`;
30188
32599
  }
30189
32600
  return this._highlightText(text);
30190
32601
  }
32602
+ _renderErrorCard(message) {
32603
+ const lines = message.split("\n").filter((l3) => l3.trim());
32604
+ const messageLine = lines.find((l3) => l3.startsWith("Message: "));
32605
+ const suggestionLine = lines.find((l3) => l3.startsWith("Suggestion: "));
32606
+ const displayMessage = messageLine ? messageLine.replace("Message: ", "") : lines[0]?.replace(/^❌\s*/, "") || message;
32607
+ const suggestion = suggestionLine ? suggestionLine.replace("Suggestion: ", "") : "";
32608
+ return b2`<div
32609
+ style="
32610
+ padding: 20px; border-radius: var(--radius-md);
32611
+ border: 1px solid color-mix(in srgb, #ef4444 30%, transparent);
32612
+ background: color-mix(in srgb, #ef4444 5%, var(--bg-subtle));
32613
+ display: flex; flex-direction: column; gap: 8px;
32614
+ margin: 8px 0;
32615
+ "
32616
+ >
32617
+ <div style="display: flex; align-items: center; gap: 8px;">
32618
+ <span style="font-size: 1.1rem;">⚠️</span>
32619
+ <span style="font-weight: 600; color: var(--t-primary); font-size: 0.95rem;">
32620
+ ${displayMessage}
32621
+ </span>
32622
+ </div>
32623
+ ${suggestion ? b2`<div
32624
+ style="
32625
+ font-size: 0.85rem; color: var(--t-secondary);
32626
+ padding-left: 28px;
32627
+ "
32628
+ >
32629
+ ${suggestion}
32630
+ </div>` : ""}
32631
+ </div>`;
32632
+ }
30191
32633
  _renderJson(data) {
30192
32634
  const jsonStr = JSON.stringify(data, null, 2);
30193
32635
  const highlighted = jsonStr.replace(
@@ -31440,7 +33882,7 @@ ResultViewer.styles = [
31440
33882
  }
31441
33883
 
31442
33884
  button:hover {
31443
- background: hsla(220, 10%, 80%, 0.1);
33885
+ background: var(--bg-glass);
31444
33886
  color: var(--t-primary);
31445
33887
  }
31446
33888
 
@@ -31591,7 +34033,7 @@ ResultViewer.styles = [
31591
34033
  }
31592
34034
 
31593
34035
  .smart-table tr:hover td {
31594
- background: hsla(220, 10%, 80%, 0.05);
34036
+ background: var(--bg-glass);
31595
34037
  }
31596
34038
 
31597
34039
  /* Key-Value Table (single object) */
@@ -32278,7 +34720,7 @@ ResultViewer.styles = [
32278
34720
  }
32279
34721
 
32280
34722
  .tree-item:hover {
32281
- background: hsla(220, 10%, 80%, 0.1);
34723
+ background: var(--bg-glass);
32282
34724
  }
32283
34725
 
32284
34726
  .tree-toggle {
@@ -32307,16 +34749,16 @@ ResultViewer.styles = [
32307
34749
  }
32308
34750
 
32309
34751
  .tree-value.string {
32310
- color: #a5d6ff;
34752
+ color: var(--syntax-string);
32311
34753
  }
32312
34754
  .tree-value.number {
32313
- color: #ff9e64;
34755
+ color: var(--syntax-number);
32314
34756
  }
32315
34757
  .tree-value.boolean {
32316
- color: #ff007c;
34758
+ color: var(--syntax-boolean);
32317
34759
  }
32318
34760
  .tree-value.null {
32319
- color: #79c0ff;
34761
+ color: var(--syntax-null);
32320
34762
  }
32321
34763
 
32322
34764
  .tree-type {
@@ -32578,9 +35020,9 @@ ResultViewer.styles = [
32578
35020
  }
32579
35021
 
32580
35022
  .expand-btn:hover {
32581
- background: var(--primary);
35023
+ background: var(--accent-primary);
32582
35024
  color: white;
32583
- border-color: var(--primary);
35025
+ border-color: var(--accent-primary);
32584
35026
  }
32585
35027
 
32586
35028
  /* Markdown items (array rendering with filter transitions) */
@@ -32818,13 +35260,13 @@ ResultViewer.styles = [
32818
35260
  }
32819
35261
 
32820
35262
  .metric-delta.up {
32821
- color: #16a34a;
32822
- background: rgba(22, 163, 74, 0.12);
35263
+ color: var(--color-success);
35264
+ background: var(--color-success-bg);
32823
35265
  }
32824
35266
 
32825
35267
  .metric-delta.down {
32826
- color: #dc2626;
32827
- background: rgba(220, 38, 38, 0.12);
35268
+ color: var(--color-error);
35269
+ background: var(--color-error-bg);
32828
35270
  }
32829
35271
 
32830
35272
  .metric-delta.neutral {
@@ -32959,7 +35401,7 @@ ResultViewer.styles = [
32959
35401
 
32960
35402
  .timeline-description {
32961
35403
  font-size: var(--text-md);
32962
- color: var(--t-secondary);
35404
+ color: var(--t-muted);
32963
35405
  margin-top: 2px;
32964
35406
  line-height: 1.4;
32965
35407
  }
@@ -33106,7 +35548,7 @@ ResultViewer.styles = [
33106
35548
  justify-content: space-between;
33107
35549
  padding: 4px 0;
33108
35550
  font-size: var(--text-md);
33109
- color: var(--t-secondary);
35551
+ color: var(--t-muted);
33110
35552
  }
33111
35553
 
33112
35554
  .cart-summary-row.total {
@@ -33384,6 +35826,9 @@ __decorateClass([
33384
35826
  __decorateClass([
33385
35827
  r5()
33386
35828
  ], ResultViewer.prototype, "_internalResult", 2);
35829
+ __decorateClass([
35830
+ e7("#qr-container")
35831
+ ], ResultViewer.prototype, "_qrContainer", 2);
33387
35832
  __decorateClass([
33388
35833
  n4({ type: String })
33389
35834
  ], ResultViewer.prototype, "collectionProperty", 2);
@@ -66297,18 +68742,18 @@ var ViewState = class {
66297
68742
  let viewFrom, viewTo;
66298
68743
  if (wrapping) {
66299
68744
  let marginHeight = margin / this.heightOracle.lineLength * this.heightOracle.lineHeight;
66300
- let top2, bot;
68745
+ let top2, bot2;
66301
68746
  if (target != null) {
66302
68747
  let targetFrac = findFraction(structure, target);
66303
68748
  let spaceFrac = ((this.visibleBottom - this.visibleTop) / 2 + marginHeight) / line.height;
66304
68749
  top2 = targetFrac - spaceFrac;
66305
- bot = targetFrac + spaceFrac;
68750
+ bot2 = targetFrac + spaceFrac;
66306
68751
  } else {
66307
68752
  top2 = (this.visibleTop - line.top - marginHeight) / line.height;
66308
- bot = (this.visibleBottom - line.top + marginHeight) / line.height;
68753
+ bot2 = (this.visibleBottom - line.top + marginHeight) / line.height;
66309
68754
  }
66310
68755
  viewFrom = findPosition(structure, top2);
66311
- viewTo = findPosition(structure, bot);
68756
+ viewTo = findPosition(structure, bot2);
66312
68757
  } else {
66313
68758
  let totalWidth = structure.total * this.heightOracle.charWidth;
66314
68759
  let marginWidth = margin * this.heightOracle.charWidth;
@@ -83453,9 +85898,13 @@ var ElicitationModal = class extends i4 {
83453
85898
  min="${min}"
83454
85899
  max="${max}"
83455
85900
  step="${step}"
85901
+ style="${this._sliderFillStyle(Number(currentValue), min, max)}"
83456
85902
  .value=${String(currentValue)}
83457
85903
  @input=${(e8) => {
83458
- this._inputValue = Number(e8.target.value);
85904
+ const el2 = e8.target;
85905
+ const v2 = this._sanitizeSliderValue(Number(el2.value), min, max, step);
85906
+ el2.style.cssText = this._sliderFillStyle(v2, min, max);
85907
+ this._inputValue = v2;
83459
85908
  }}
83460
85909
  />
83461
85910
  <input
@@ -83466,7 +85915,15 @@ var ElicitationModal = class extends i4 {
83466
85915
  step="${step}"
83467
85916
  .value=${String(currentValue)}
83468
85917
  @input=${(e8) => {
83469
- this._inputValue = Number(e8.target.value);
85918
+ const raw = e8.target.value;
85919
+ if (raw === "" || raw === "-") return;
85920
+ this._inputValue = Number(raw);
85921
+ }}
85922
+ @change=${(e8) => {
85923
+ const el2 = e8.target;
85924
+ const v2 = this._sanitizeSliderValue(Number(el2.value), min, max, step);
85925
+ el2.value = String(v2);
85926
+ this._inputValue = v2;
83470
85927
  }}
83471
85928
  @keydown=${(e8) => this._handleKeydown(e8)}
83472
85929
  autofocus
@@ -83710,9 +86167,13 @@ var ElicitationModal = class extends i4 {
83710
86167
  min="${min}"
83711
86168
  max="${max}"
83712
86169
  step="${step}"
86170
+ style="${this._sliderFillStyle(Number(currentValue), min, max)}"
83713
86171
  .value=${String(currentValue)}
83714
86172
  @input=${(e8) => {
83715
- this._updateFormValue(field.name, Number(e8.target.value));
86173
+ const el2 = e8.target;
86174
+ const v2 = this._sanitizeSliderValue(Number(el2.value), min, max, step);
86175
+ el2.style.cssText = this._sliderFillStyle(v2, min, max);
86176
+ this._updateFormValue(field.name, v2);
83716
86177
  }}
83717
86178
  />
83718
86179
  <input
@@ -83723,7 +86184,15 @@ var ElicitationModal = class extends i4 {
83723
86184
  step="${step}"
83724
86185
  .value=${String(currentValue)}
83725
86186
  @input=${(e8) => {
83726
- this._updateFormValue(field.name, Number(e8.target.value));
86187
+ const raw = e8.target.value;
86188
+ if (raw === "" || raw === "-") return;
86189
+ this._updateFormValue(field.name, Number(raw));
86190
+ }}
86191
+ @change=${(e8) => {
86192
+ const el2 = e8.target;
86193
+ const v2 = this._sanitizeSliderValue(Number(el2.value), min, max, step);
86194
+ el2.value = String(v2);
86195
+ this._updateFormValue(field.name, v2);
83727
86196
  }}
83728
86197
  />
83729
86198
  </div>
@@ -83744,6 +86213,23 @@ var ElicitationModal = class extends i4 {
83744
86213
  />
83745
86214
  `;
83746
86215
  }
86216
+ _sliderFillStyle(value, min, max) {
86217
+ const pct = max > min ? (value - min) / (max - min) * 100 : 0;
86218
+ return `background: linear-gradient(to right, var(--accent-primary) ${pct}%, var(--border-glass) ${pct}%)`;
86219
+ }
86220
+ /** Clamp to [min, max] and round to nearest step (integer-safe). */
86221
+ _sanitizeSliderValue(raw, min, max, step) {
86222
+ let v2 = Number.isFinite(raw) ? raw : min;
86223
+ v2 = Math.min(max, Math.max(min, v2));
86224
+ if (step > 0) {
86225
+ v2 = min + Math.round((v2 - min) / step) * step;
86226
+ if (v2 > max) v2 = max;
86227
+ }
86228
+ if (Number.isInteger(step) && step >= 1) {
86229
+ v2 = Math.round(v2);
86230
+ }
86231
+ return v2;
86232
+ }
83747
86233
  _updateFormValue(name2, value) {
83748
86234
  this._formValues = { ...this._formValues, [name2]: value };
83749
86235
  }
@@ -83868,11 +86354,19 @@ ElicitationModal.styles = [
83868
86354
  color: white;
83869
86355
  }
83870
86356
 
86357
+ .btn-success:hover {
86358
+ filter: brightness(1.1);
86359
+ }
86360
+
83871
86361
  .btn-danger {
83872
86362
  background: var(--color-error);
83873
86363
  color: white;
83874
86364
  }
83875
86365
 
86366
+ .btn-danger:hover {
86367
+ filter: brightness(1.1);
86368
+ }
86369
+
83876
86370
  /* Confirm buttons */
83877
86371
  .confirm-actions {
83878
86372
  display: flex;
@@ -83982,7 +86476,7 @@ ElicitationModal.styles = [
83982
86476
  .slider-group {
83983
86477
  display: flex;
83984
86478
  flex-direction: column;
83985
- gap: 6px;
86479
+ gap: 4px;
83986
86480
  }
83987
86481
 
83988
86482
  .slider-row {
@@ -83993,47 +86487,104 @@ ElicitationModal.styles = [
83993
86487
 
83994
86488
  .slider-row input[type='range'] {
83995
86489
  flex: 1;
83996
- height: 6px;
83997
- background: rgba(255, 255, 255, 0.12);
83998
- border: 1px solid rgba(255, 255, 255, 0.08);
83999
- border-radius: 3px;
86490
+ height: 4px;
86491
+ border-radius: var(--radius-full, 9999px);
84000
86492
  -webkit-appearance: none;
84001
86493
  cursor: pointer;
84002
86494
  margin: 8px 0;
86495
+ border: none;
86496
+ outline: none;
86497
+ background: var(--border-glass);
84003
86498
  }
84004
86499
 
84005
86500
  .slider-row input[type='range']::-webkit-slider-thumb {
84006
86501
  -webkit-appearance: none;
84007
- width: 20px;
84008
- height: 20px;
86502
+ width: 16px;
86503
+ height: 16px;
84009
86504
  background: var(--accent-primary);
86505
+ border: 2px solid var(--bg-panel, var(--bg-glass));
84010
86506
  border-radius: 50%;
84011
86507
  cursor: pointer;
84012
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
86508
+ box-shadow:
86509
+ 0 0 0 2px var(--accent-primary),
86510
+ 0 1px 4px rgba(0, 0, 0, 0.3);
86511
+ transition:
86512
+ box-shadow 0.15s ease,
86513
+ transform 0.15s ease;
86514
+ }
86515
+
86516
+ .slider-row input[type='range']::-webkit-slider-thumb:hover {
86517
+ box-shadow:
86518
+ 0 0 0 3px var(--accent-primary),
86519
+ 0 0 8px var(--glow-primary);
86520
+ transform: scale(1.1);
86521
+ }
86522
+
86523
+ .slider-row input[type='range']:active::-webkit-slider-thumb {
86524
+ transform: scale(0.95);
84013
86525
  }
84014
86526
 
84015
86527
  .slider-row input[type='range']::-moz-range-track {
84016
- height: 6px;
84017
- background: rgba(255, 255, 255, 0.12);
84018
- border-radius: 3px;
84019
- border: 1px solid rgba(255, 255, 255, 0.08);
86528
+ height: 4px;
86529
+ background: var(--border-glass);
86530
+ border-radius: var(--radius-full, 9999px);
86531
+ border: none;
86532
+ }
86533
+
86534
+ .slider-row input[type='range']::-moz-range-progress {
86535
+ height: 4px;
86536
+ background: var(--accent-primary);
86537
+ border-radius: var(--radius-full, 9999px);
84020
86538
  }
84021
86539
 
84022
86540
  .slider-row input[type='range']::-moz-range-thumb {
84023
- width: 20px;
84024
- height: 20px;
86541
+ width: 12px;
86542
+ height: 12px;
84025
86543
  background: var(--accent-primary);
86544
+ border: 2px solid var(--bg-panel, var(--bg-glass));
84026
86545
  border-radius: 50%;
84027
86546
  cursor: pointer;
84028
- border: none;
84029
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
86547
+ box-shadow:
86548
+ 0 0 0 2px var(--accent-primary),
86549
+ 0 1px 4px rgba(0, 0, 0, 0.3);
86550
+ transition:
86551
+ box-shadow 0.15s ease,
86552
+ transform 0.15s ease;
86553
+ }
86554
+
86555
+ .slider-row input[type='range']::-moz-range-thumb:hover {
86556
+ box-shadow:
86557
+ 0 0 0 3px var(--accent-primary),
86558
+ 0 0 8px var(--glow-primary);
84030
86559
  }
84031
86560
 
84032
86561
  .slider-number-input {
84033
- width: 80px;
84034
- text-align: right;
86562
+ width: 64px;
86563
+ text-align: center;
84035
86564
  font-size: var(--text-sm);
84036
- padding: 4px 8px;
86565
+ font-weight: 500;
86566
+ font-variant-numeric: tabular-nums;
86567
+ padding: 4px 6px;
86568
+ background: var(--bg-glass);
86569
+ border: 1px solid var(--border-glass) !important;
86570
+ border-radius: var(--radius-sm);
86571
+ color: var(--t-primary);
86572
+ -moz-appearance: textfield;
86573
+ transition:
86574
+ border-color 0.15s ease,
86575
+ box-shadow 0.15s ease;
86576
+ }
86577
+
86578
+ .slider-number-input::-webkit-outer-spin-button,
86579
+ .slider-number-input::-webkit-inner-spin-button {
86580
+ -webkit-appearance: none;
86581
+ margin: 0;
86582
+ }
86583
+
86584
+ .slider-number-input:focus-visible {
86585
+ outline: none;
86586
+ border-color: var(--accent-primary) !important;
86587
+ box-shadow: 0 0 0 2px var(--glow-primary);
84037
86588
  }
84038
86589
 
84039
86590
  .range-labels {
@@ -84092,7 +86643,13 @@ ElicitationModal.styles = [
84092
86643
  background: var(--bg-glass-strong);
84093
86644
  border-radius: 12px;
84094
86645
  cursor: pointer;
84095
- transition: background 0.2s;
86646
+ transition:
86647
+ background 0.2s,
86648
+ box-shadow 0.2s;
86649
+ }
86650
+
86651
+ .toggle-track:hover {
86652
+ background: var(--bg-glass);
84096
86653
  }
84097
86654
 
84098
86655
  .toggle-track::after {
@@ -84111,6 +86668,14 @@ ElicitationModal.styles = [
84111
86668
  background: var(--accent-primary);
84112
86669
  }
84113
86670
 
86671
+ .toggle-switch input:checked + .toggle-track:hover {
86672
+ background: var(--accent-secondary);
86673
+ }
86674
+
86675
+ .toggle-switch input:focus-visible + .toggle-track {
86676
+ box-shadow: 0 0 0 2px var(--glow-primary);
86677
+ }
86678
+
84114
86679
  .toggle-switch input:checked + .toggle-track::after {
84115
86680
  transform: translateX(20px);
84116
86681
  }
@@ -84246,7 +86811,7 @@ var OverflowMenu = class extends i4 {
84246
86811
  return b2`
84247
86812
  <button
84248
86813
  class="trigger"
84249
- @click=${() => this._toggle()}
86814
+ @click=${(e8) => this._toggle(e8)}
84250
86815
  title="More actions"
84251
86816
  aria-label="More actions"
84252
86817
  >
@@ -85753,7 +88318,7 @@ ContextBar.styles = [
85753
88318
  backdrop-filter: blur(20px);
85754
88319
  border: 1px solid var(--border-glass);
85755
88320
  border-radius: var(--radius-md);
85756
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
88321
+ box-shadow: var(--shadow-lg);
85757
88322
  padding: 4px;
85758
88323
  z-index: 100;
85759
88324
  }
@@ -85764,7 +88329,7 @@ ContextBar.styles = [
85764
88329
  padding: 6px 10px;
85765
88330
  border: none;
85766
88331
  background: none;
85767
- color: var(--t-secondary);
88332
+ color: var(--t-muted);
85768
88333
  font-size: var(--text-sm);
85769
88334
  font-family: inherit;
85770
88335
  text-align: left;
@@ -85790,7 +88355,7 @@ ContextBar.styles = [
85790
88355
  font-size: var(--text-2xs);
85791
88356
  font-weight: 700;
85792
88357
  letter-spacing: 0.08em;
85793
- color: #4ade80;
88358
+ color: var(--color-success);
85794
88359
  text-transform: uppercase;
85795
88360
  margin-left: auto;
85796
88361
  }
@@ -85799,8 +88364,8 @@ ContextBar.styles = [
85799
88364
  width: 6px;
85800
88365
  height: 6px;
85801
88366
  border-radius: 50%;
85802
- background: #4ade80;
85803
- box-shadow: 0 0 6px 2px rgba(74, 222, 128, 0.6);
88367
+ background: var(--color-success);
88368
+ box-shadow: 0 0 6px 2px var(--color-success-glow);
85804
88369
  animation: live-pulse 2s ease-in-out infinite;
85805
88370
  }
85806
88371
 
@@ -85808,11 +88373,11 @@ ContextBar.styles = [
85808
88373
  0%,
85809
88374
  100% {
85810
88375
  opacity: 1;
85811
- box-shadow: 0 0 6px 2px rgba(74, 222, 128, 0.6);
88376
+ box-shadow: 0 0 6px 2px var(--color-success-glow);
85812
88377
  }
85813
88378
  50% {
85814
88379
  opacity: 0.5;
85815
- box-shadow: 0 0 3px 1px rgba(74, 222, 128, 0.3);
88380
+ box-shadow: 0 0 3px 1px var(--color-success-glow);
85816
88381
  }
85817
88382
  }
85818
88383