@michaelmishin/speclens 0.3.0 → 0.4.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.
package/dist/speclens.js CHANGED
@@ -17995,6 +17995,144 @@ const objectToNumericMapAsync = async (object) => {
17995
17995
  };
17996
17996
  const wait = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
17997
17997
  const SPACE_OR_PUNCTUATION = /[\n\r\p{Z}\p{P}]+/u;
17998
+ const STOP_WORDS = /* @__PURE__ */ new Set([
17999
+ "a",
18000
+ "an",
18001
+ "the",
18002
+ "is",
18003
+ "are",
18004
+ "was",
18005
+ "were",
18006
+ "be",
18007
+ "been",
18008
+ "being",
18009
+ "have",
18010
+ "has",
18011
+ "had",
18012
+ "do",
18013
+ "does",
18014
+ "did",
18015
+ "will",
18016
+ "would",
18017
+ "shall",
18018
+ "should",
18019
+ "may",
18020
+ "might",
18021
+ "must",
18022
+ "can",
18023
+ "could",
18024
+ "of",
18025
+ "in",
18026
+ "to",
18027
+ "for",
18028
+ "with",
18029
+ "on",
18030
+ "at",
18031
+ "from",
18032
+ "by",
18033
+ "about",
18034
+ "as",
18035
+ "into",
18036
+ "through",
18037
+ "during",
18038
+ "before",
18039
+ "after",
18040
+ "above",
18041
+ "below",
18042
+ "between",
18043
+ "and",
18044
+ "but",
18045
+ "or",
18046
+ "nor",
18047
+ "not",
18048
+ "so",
18049
+ "yet",
18050
+ "both",
18051
+ "either",
18052
+ "neither",
18053
+ "each",
18054
+ "every",
18055
+ "all",
18056
+ "any",
18057
+ "few",
18058
+ "more",
18059
+ "most",
18060
+ "other",
18061
+ "some",
18062
+ "such",
18063
+ "no",
18064
+ "only",
18065
+ "own",
18066
+ "same",
18067
+ "than",
18068
+ "too",
18069
+ "very",
18070
+ "just",
18071
+ "that",
18072
+ "this",
18073
+ "these",
18074
+ "those",
18075
+ "it",
18076
+ "its",
18077
+ "if",
18078
+ "then",
18079
+ "else",
18080
+ "when",
18081
+ "up",
18082
+ "out",
18083
+ "off",
18084
+ "over",
18085
+ "under",
18086
+ "again",
18087
+ "further",
18088
+ "once",
18089
+ "here",
18090
+ "there",
18091
+ "where",
18092
+ "why",
18093
+ "how",
18094
+ "which",
18095
+ "who",
18096
+ "whom",
18097
+ "what",
18098
+ "while",
18099
+ "also",
18100
+ "use",
18101
+ "used",
18102
+ "using",
18103
+ "return",
18104
+ "returns",
18105
+ "returned",
18106
+ "get",
18107
+ "set",
18108
+ "true",
18109
+ "false",
18110
+ "null",
18111
+ "undefined",
18112
+ "string",
18113
+ "number",
18114
+ "boolean",
18115
+ "object",
18116
+ "array",
18117
+ "type",
18118
+ "value",
18119
+ "values",
18120
+ "field",
18121
+ "fields",
18122
+ "example",
18123
+ "default"
18124
+ ]);
18125
+ function extractKeywords(texts, max) {
18126
+ const freq = /* @__PURE__ */ new Map();
18127
+ for (const text2 of texts) {
18128
+ const words = text2.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/);
18129
+ for (const w2 of words) {
18130
+ if (w2.length < 3 || STOP_WORDS.has(w2)) continue;
18131
+ freq.set(w2, (freq.get(w2) || 0) + 1);
18132
+ }
18133
+ }
18134
+ return [...freq.entries()].filter(([, count]) => count >= 2).sort((a2, b2) => b2[1] - a2[1]).slice(0, max).map(([word]) => word);
18135
+ }
17998
18136
  function buildSearchIndex(operations, guides) {
17999
18137
  const opSearch = new MiniSearch({
18000
18138
  fields: ["operationId", "summary", "description", "path", "method", "tags"],
@@ -18044,10 +18182,13 @@ function buildSearchIndex(operations, guides) {
18044
18182
  }));
18045
18183
  guideSearch.addAll(guideDocs);
18046
18184
  }
18185
+ const apiTexts = operations.flatMap((op) => [op.summary, op.description, ...op.tags]);
18186
+ const guideTexts = (guides || []).flatMap((g2) => [g2.title, g2.category || "", g2.content || ""]);
18187
+ const keywords = extractKeywords([...apiTexts, ...guideTexts], 12);
18047
18188
  return {
18048
- search(query) {
18189
+ search(query, scope2 = "all") {
18049
18190
  if (!query.trim()) return [];
18050
- const opResults = opSearch.search(query).map((result) => ({
18191
+ const opResults = scope2 !== "guides" ? opSearch.search(query).map((result) => ({
18051
18192
  type: "operation",
18052
18193
  operationId: result.operationId,
18053
18194
  path: result.path,
@@ -18055,8 +18196,8 @@ function buildSearchIndex(operations, guides) {
18055
18196
  summary: result.summary,
18056
18197
  tags: result.tags.split(" ").filter(Boolean),
18057
18198
  score: result.score
18058
- }));
18059
- const guideResults = guideSearch ? guideSearch.search(query).map((result) => ({
18199
+ })) : [];
18200
+ const guideResults = scope2 !== "api" && guideSearch ? guideSearch.search(query).map((result) => ({
18060
18201
  type: "guide",
18061
18202
  slug: result.slug,
18062
18203
  title: result.title,
@@ -18070,6 +18211,12 @@ function buildSearchIndex(operations, guides) {
18070
18211
  const opSuggestions = opSearch.autoSuggest(query).map((s2) => s2.suggestion);
18071
18212
  const guideSuggestions = guideSearch ? guideSearch.autoSuggest(query).map((s2) => s2.suggestion) : [];
18072
18213
  return [.../* @__PURE__ */ new Set([...opSuggestions, ...guideSuggestions])];
18214
+ },
18215
+ getKeywords() {
18216
+ return keywords;
18217
+ },
18218
+ hasGuides() {
18219
+ return guideSearch !== null;
18073
18220
  }
18074
18221
  };
18075
18222
  }
@@ -20363,7 +20510,7 @@ class ThemeManager {
20363
20510
  }
20364
20511
  }
20365
20512
  }
20366
- const MAX_PROMPT_LENGTH = 6e3;
20513
+ const SAFE_ENCODED_LENGTH = 8e3;
20367
20514
  function generateAiPrompt(op) {
20368
20515
  const lines = [];
20369
20516
  lines.push(`I'm working with the following REST API endpoint and need help understanding how to use it correctly.
@@ -20381,8 +20528,9 @@ ${op.description}`);
20381
20528
  const flags = [];
20382
20529
  if (p2.required) flags.push("required");
20383
20530
  if (p2.deprecated) flags.push("deprecated");
20384
- const type2 = p2.schema ? p2.schema.type ?? "any" : "any";
20385
- const enumVals = p2.schema ? p2.schema.enum : void 0;
20531
+ const schema2 = p2.schema;
20532
+ const type2 = (schema2 == null ? void 0 : schema2.type) ?? "any";
20533
+ const enumVals = schema2 == null ? void 0 : schema2.enum;
20386
20534
  let line = `- **${p2.name}** (${p2.in}, ${type2}${flags.length ? ", " + flags.join(", ") : ""})`;
20387
20535
  if (p2.description) line += `: ${p2.description}`;
20388
20536
  if (enumVals == null ? void 0 : enumVals.length) line += ` — Allowed values: ${enumVals.join(", ")}`;
@@ -20399,20 +20547,9 @@ ${op.description}`);
20399
20547
  Content-Type: \`${content.mediaType}\``);
20400
20548
  if (content.schema) {
20401
20549
  const schemaStr = JSON.stringify(content.schema, null, 2);
20402
- if (schemaStr.length < 2e3) {
20403
- lines.push("```json");
20404
- lines.push(schemaStr);
20405
- lines.push("```");
20406
- } else {
20407
- lines.push("Schema: (large schema, key properties shown)");
20408
- const props = content.schema.properties;
20409
- if (props) {
20410
- for (const [key, val] of Object.entries(props)) {
20411
- const t2 = (val == null ? void 0 : val.type) ?? "object";
20412
- lines.push(`- ${key}: ${t2}`);
20413
- }
20414
- }
20415
- }
20550
+ lines.push("```json");
20551
+ lines.push(schemaStr);
20552
+ lines.push("```");
20416
20553
  }
20417
20554
  if (content.example !== void 0) {
20418
20555
  lines.push(`
@@ -20432,14 +20569,10 @@ ${JSON.stringify(content.example, null, 2)}
20432
20569
  for (const content of r2.content) {
20433
20570
  if (content.schema) {
20434
20571
  const schemaStr = JSON.stringify(content.schema, null, 2);
20435
- if (schemaStr.length < 1500) {
20436
- lines.push(`\`${content.mediaType}\`
20572
+ lines.push(`\`${content.mediaType}\`
20437
20573
  \`\`\`json
20438
20574
  ${schemaStr}
20439
20575
  \`\`\``);
20440
- } else {
20441
- lines.push(`\`${content.mediaType}\` — (large schema)`);
20442
- }
20443
20576
  }
20444
20577
  }
20445
20578
  }
@@ -20456,20 +20589,20 @@ ${schemaStr}
20456
20589
  }
20457
20590
  lines.push(`
20458
20591
  Please help me understand this endpoint, write an example request, and explain the expected response.`);
20459
- let prompt = lines.join("\n");
20460
- if (prompt.length > MAX_PROMPT_LENGTH) {
20461
- prompt = prompt.slice(0, MAX_PROMPT_LENGTH - 3) + "...";
20462
- }
20463
- return prompt;
20592
+ return lines.join("\n");
20464
20593
  }
20465
- function buildAiUrl(prompt, target) {
20594
+ function _baseUrl(target) {
20595
+ return target === "chatgpt" ? "https://chatgpt.com/" : "https://claude.ai/new";
20596
+ }
20597
+ async function openAiWithPrompt(prompt, target) {
20466
20598
  const encoded = encodeURIComponent(prompt);
20467
- switch (target) {
20468
- case "chatgpt":
20469
- return `https://chatgpt.com/?q=${encoded}`;
20470
- case "claude":
20471
- return `https://claude.ai/new?q=${encoded}`;
20599
+ if (encoded.length <= SAFE_ENCODED_LENGTH) {
20600
+ window.open(`${_baseUrl(target)}?q=${encoded}`, "_blank", "noopener,noreferrer");
20601
+ return "url";
20472
20602
  }
20603
+ await navigator.clipboard.writeText(prompt);
20604
+ window.open(_baseUrl(target), "_blank", "noopener,noreferrer");
20605
+ return "clipboard";
20473
20606
  }
20474
20607
  var __defProp$d = Object.defineProperty;
20475
20608
  var __getOwnPropDesc$d = Object.getOwnPropertyDescriptor;
@@ -21258,6 +21391,7 @@ let SlSearch = class extends i$1 {
21258
21391
  this._query = "";
21259
21392
  this._results = [];
21260
21393
  this._highlightIndex = 0;
21394
+ this._scope = "all";
21261
21395
  }
21262
21396
  firstUpdated() {
21263
21397
  var _a2;
@@ -21266,12 +21400,29 @@ let SlSearch = class extends i$1 {
21266
21400
  _handleInput(e2) {
21267
21401
  this._query = e2.target.value;
21268
21402
  this._highlightIndex = 0;
21403
+ this._runSearch();
21404
+ }
21405
+ _runSearch() {
21269
21406
  if (this.searchEngine && this._query.trim()) {
21270
- this._results = this.searchEngine.search(this._query).slice(0, 20);
21407
+ this._results = this.searchEngine.search(this._query, this._scope).slice(0, 20);
21271
21408
  } else {
21272
21409
  this._results = [];
21273
21410
  }
21274
21411
  }
21412
+ _setScope(scope2) {
21413
+ var _a2;
21414
+ this._scope = scope2;
21415
+ this._runSearch();
21416
+ (_a2 = this._input) == null ? void 0 : _a2.focus();
21417
+ }
21418
+ _searchKeyword(keyword2) {
21419
+ var _a2;
21420
+ this._query = keyword2;
21421
+ this._highlightIndex = 0;
21422
+ this._runSearch();
21423
+ if (this._input) this._input.value = keyword2;
21424
+ (_a2 = this._input) == null ? void 0 : _a2.focus();
21425
+ }
21275
21426
  _handleKeyDown(e2) {
21276
21427
  switch (e2.key) {
21277
21428
  case "Escape":
@@ -21331,6 +21482,9 @@ let SlSearch = class extends i$1 {
21331
21482
  }
21332
21483
  }
21333
21484
  render() {
21485
+ var _a2, _b2;
21486
+ const showScopeBar = (_a2 = this.searchEngine) == null ? void 0 : _a2.hasGuides();
21487
+ const keywords = ((_b2 = this.searchEngine) == null ? void 0 : _b2.getKeywords()) || [];
21334
21488
  return b`
21335
21489
  <div class="overlay" @click=${() => this.dispatchEvent(new CustomEvent("close"))}></div>
21336
21490
  <div class="modal" @keydown=${this._handleKeyDown}>
@@ -21348,9 +21502,28 @@ let SlSearch = class extends i$1 {
21348
21502
  <span class="esc-hint">Esc</span>
21349
21503
  </div>
21350
21504
 
21505
+ ${showScopeBar ? b`
21506
+ <div class="scope-bar">
21507
+ <button class="scope-chip ${this._scope === "all" ? "active" : ""}" @click=${() => this._setScope("all")}>All</button>
21508
+ <button class="scope-chip ${this._scope === "api" ? "active" : ""}" @click=${() => this._setScope("api")}>API Reference</button>
21509
+ <button class="scope-chip ${this._scope === "guides" ? "active" : ""}" @click=${() => this._setScope("guides")}>Guides</button>
21510
+ </div>
21511
+ ` : null}
21512
+
21351
21513
  <div class="results">
21352
21514
  ${this._query.trim() === "" ? b`
21353
- <div class="empty-state">Start typing to search…</div>
21515
+ ${keywords.length > 0 ? b`
21516
+ <div class="keywords-section">
21517
+ <div class="keywords-label">Suggested searches</div>
21518
+ <div class="keywords-list">
21519
+ ${keywords.map((kw) => b`
21520
+ <button class="keyword-chip" @click=${() => this._searchKeyword(kw)}>${kw}</button>
21521
+ `)}
21522
+ </div>
21523
+ </div>
21524
+ ` : b`
21525
+ <div class="empty-state">Start typing to search…</div>
21526
+ `}
21354
21527
  ` : this._results.length === 0 ? b`
21355
21528
  <div class="no-results">No results for "${this._query}"</div>
21356
21529
  ` : this._results.map((result, i3) => this._renderResult(result, i3))}
@@ -21531,6 +21704,70 @@ SlSearch.styles = [
21531
21704
  color: var(--sl-color-text-muted);
21532
21705
  border: 1px solid var(--sl-color-border);
21533
21706
  }
21707
+
21708
+ /* ── Scope filter ────────────────────── */
21709
+ .scope-bar {
21710
+ display: flex;
21711
+ gap: 4px;
21712
+ padding: 4px var(--sl-spacing-lg) var(--sl-spacing-sm);
21713
+ }
21714
+
21715
+ .scope-chip {
21716
+ padding: 3px 10px;
21717
+ border-radius: 999px;
21718
+ font-size: var(--sl-font-size-xs);
21719
+ font-weight: 600;
21720
+ cursor: pointer;
21721
+ transition: all var(--sl-transition-fast);
21722
+ border: 1px solid var(--sl-color-border);
21723
+ background: transparent;
21724
+ color: var(--sl-color-text-muted);
21725
+ }
21726
+
21727
+ .scope-chip:hover {
21728
+ background: var(--sl-color-surface-raised);
21729
+ color: var(--sl-color-text);
21730
+ }
21731
+
21732
+ .scope-chip.active {
21733
+ background: var(--sl-color-primary);
21734
+ color: #fff;
21735
+ border-color: var(--sl-color-primary);
21736
+ }
21737
+
21738
+ /* ── Keyword chips ───────────────────── */
21739
+ .keywords-section {
21740
+ padding: var(--sl-spacing-sm) var(--sl-spacing-lg) var(--sl-spacing-md);
21741
+ }
21742
+
21743
+ .keywords-label {
21744
+ font-size: var(--sl-font-size-xs);
21745
+ color: var(--sl-color-text-muted);
21746
+ margin-bottom: 6px;
21747
+ }
21748
+
21749
+ .keywords-list {
21750
+ display: flex;
21751
+ flex-wrap: wrap;
21752
+ gap: 6px;
21753
+ }
21754
+
21755
+ .keyword-chip {
21756
+ padding: 3px 10px;
21757
+ border-radius: 999px;
21758
+ font-size: var(--sl-font-size-xs);
21759
+ cursor: pointer;
21760
+ transition: all var(--sl-transition-fast);
21761
+ border: 1px solid var(--sl-color-border);
21762
+ background: var(--sl-color-surface-raised);
21763
+ color: var(--sl-color-text-muted);
21764
+ }
21765
+
21766
+ .keyword-chip:hover {
21767
+ background: var(--sl-color-primary);
21768
+ color: #fff;
21769
+ border-color: var(--sl-color-primary);
21770
+ }
21534
21771
  `
21535
21772
  ];
21536
21773
  __decorateClass$b([
@@ -21545,6 +21782,9 @@ __decorateClass$b([
21545
21782
  __decorateClass$b([
21546
21783
  r$1()
21547
21784
  ], SlSearch.prototype, "_highlightIndex", 2);
21785
+ __decorateClass$b([
21786
+ r$1()
21787
+ ], SlSearch.prototype, "_scope", 2);
21548
21788
  __decorateClass$b([
21549
21789
  e$2("input")
21550
21790
  ], SlSearch.prototype, "_input", 2);
@@ -29278,6 +29518,7 @@ let SlOperation = class extends i$1 {
29278
29518
  this._routeCopied = false;
29279
29519
  this._aiMenuOpen = false;
29280
29520
  this._aiMenuRect = null;
29521
+ this._aiToast = false;
29281
29522
  }
29282
29523
  willUpdate(changed) {
29283
29524
  var _a2;
@@ -29330,12 +29571,27 @@ let SlOperation = class extends i$1 {
29330
29571
  requestAnimationFrame(() => document.addEventListener("click", close, true));
29331
29572
  }
29332
29573
  }
29333
- _openAi(target, e2) {
29574
+ async _openAi(target, e2) {
29334
29575
  e2.stopPropagation();
29335
29576
  this._aiMenuOpen = false;
29336
29577
  const prompt = generateAiPrompt(this.operation);
29337
- const url2 = buildAiUrl(prompt, target);
29338
- window.open(url2, "_blank", "noopener,noreferrer");
29578
+ const result = await openAiWithPrompt(prompt, target);
29579
+ if (result === "clipboard") {
29580
+ this._aiToast = true;
29581
+ setTimeout(() => {
29582
+ this._aiToast = false;
29583
+ }, 4e3);
29584
+ }
29585
+ }
29586
+ async _copyPrompt(e2) {
29587
+ e2.stopPropagation();
29588
+ this._aiMenuOpen = false;
29589
+ const prompt = generateAiPrompt(this.operation);
29590
+ await navigator.clipboard.writeText(prompt);
29591
+ this._aiToast = true;
29592
+ setTimeout(() => {
29593
+ this._aiToast = false;
29594
+ }, 4e3);
29339
29595
  }
29340
29596
  render() {
29341
29597
  const op = this.operation;
@@ -29461,6 +29717,23 @@ let SlOperation = class extends i$1 {
29461
29717
  </svg>
29462
29718
  Claude
29463
29719
  </button>
29720
+ <div style="border-top:1px solid var(--sl-color-border);margin:4px 0"></div>
29721
+ <button class="ai-menu-item" @click=${(e2) => this._copyPrompt(e2)}>
29722
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
29723
+ <rect x="5" y="5" width="8" height="8" rx="1"/>
29724
+ <path d="M3 11V3h8"/>
29725
+ </svg>
29726
+ Copy Prompt
29727
+ </button>
29728
+ </div>
29729
+ ` : null}
29730
+ ${this._aiToast ? b`
29731
+ <div class="ai-toast">
29732
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
29733
+ <rect x="5" y="5" width="8" height="8" rx="1"/>
29734
+ <path d="M3 11V3h8"/>
29735
+ </svg>
29736
+ Prompt copied — paste it into the chat
29464
29737
  </div>
29465
29738
  ` : null}
29466
29739
  `;
@@ -29796,6 +30069,32 @@ SlOperation.styles = [
29796
30069
  flex-shrink: 0;
29797
30070
  }
29798
30071
 
30072
+ /* ── AI clipboard toast ──────────────── */
30073
+ .ai-toast {
30074
+ position: fixed;
30075
+ bottom: 24px;
30076
+ left: 50%;
30077
+ transform: translateX(-50%);
30078
+ z-index: 10000;
30079
+ display: flex;
30080
+ align-items: center;
30081
+ gap: 10px;
30082
+ padding: 12px 18px;
30083
+ background: var(--sl-color-text);
30084
+ color: var(--sl-color-bg);
30085
+ border-radius: var(--sl-radius-lg);
30086
+ font-size: var(--sl-font-size-sm);
30087
+ font-weight: 500;
30088
+ box-shadow: var(--sl-shadow-lg);
30089
+ white-space: nowrap;
30090
+ animation: sl-toast-in 200ms ease;
30091
+ }
30092
+
30093
+ @keyframes sl-toast-in {
30094
+ from { opacity: 0; transform: translateX(-50%) translateY(8px); }
30095
+ to { opacity: 1; transform: translateX(-50%) translateY(0); }
30096
+ }
30097
+
29799
30098
  @media (max-width: 900px) {
29800
30099
  .op-body {
29801
30100
  grid-template-columns: 1fr;
@@ -29849,6 +30148,9 @@ __decorateClass$5([
29849
30148
  __decorateClass$5([
29850
30149
  r$1()
29851
30150
  ], SlOperation.prototype, "_aiMenuRect", 2);
30151
+ __decorateClass$5([
30152
+ r$1()
30153
+ ], SlOperation.prototype, "_aiToast", 2);
29852
30154
  SlOperation = __decorateClass$5([
29853
30155
  t$1("sl-operation")
29854
30156
  ], SlOperation);
@@ -31635,6 +31937,7 @@ let SpecLensElement = class extends i$1 {
31635
31937
  this._searchOpen = false;
31636
31938
  this._tryItAiMenuOpen = false;
31637
31939
  this._tryItAiMenuRect = null;
31940
+ this._aiToast = false;
31638
31941
  this._search = null;
31639
31942
  this._router = null;
31640
31943
  this._themeManager = null;
@@ -31773,12 +32076,17 @@ let SpecLensElement = class extends i$1 {
31773
32076
  requestAnimationFrame(() => document.addEventListener("click", close, true));
31774
32077
  }
31775
32078
  }
31776
- _openTryItAi(target) {
32079
+ async _openTryItAi(target) {
31777
32080
  this._tryItAiMenuOpen = false;
31778
32081
  if (!this._tryItOperation) return;
31779
32082
  const prompt = generateAiPrompt(this._tryItOperation);
31780
- const url2 = buildAiUrl(prompt, target);
31781
- window.open(url2, "_blank", "noopener,noreferrer");
32083
+ const result = await openAiWithPrompt(prompt, target);
32084
+ if (result === "clipboard") {
32085
+ this._aiToast = true;
32086
+ setTimeout(() => {
32087
+ this._aiToast = false;
32088
+ }, 4e3);
32089
+ }
31782
32090
  }
31783
32091
  /** Set the color theme programmatically. Useful in embed mode where the header is hidden. */
31784
32092
  setTheme(preference) {
@@ -32042,6 +32350,16 @@ let SpecLensElement = class extends i$1 {
32042
32350
  @select-guide=${this._handleSearchSelectGuide}
32043
32351
  ></sl-search>
32044
32352
  ` : null}
32353
+
32354
+ ${this._aiToast ? b`
32355
+ <div class="ai-toast">
32356
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
32357
+ <rect x="5" y="5" width="8" height="8" rx="1"/>
32358
+ <path d="M3 11V3h8"/>
32359
+ </svg>
32360
+ Prompt copied — paste it into the chat
32361
+ </div>
32362
+ ` : null}
32045
32363
  </div>
32046
32364
  `;
32047
32365
  }
@@ -32639,6 +32957,32 @@ SpecLensElement.styles = [
32639
32957
  --sl-header-height: 0px;
32640
32958
  min-height: unset;
32641
32959
  }
32960
+
32961
+ /* ── AI clipboard toast ──────────────── */
32962
+ .ai-toast {
32963
+ position: fixed;
32964
+ bottom: 24px;
32965
+ left: 50%;
32966
+ transform: translateX(-50%);
32967
+ z-index: 10000;
32968
+ display: flex;
32969
+ align-items: center;
32970
+ gap: 10px;
32971
+ padding: 12px 18px;
32972
+ background: var(--sl-color-text);
32973
+ color: var(--sl-color-bg);
32974
+ border-radius: var(--sl-radius-lg);
32975
+ font-size: var(--sl-font-size-sm);
32976
+ font-weight: 500;
32977
+ box-shadow: var(--sl-shadow-lg);
32978
+ white-space: nowrap;
32979
+ animation: sl-toast-in 200ms ease;
32980
+ }
32981
+
32982
+ @keyframes sl-toast-in {
32983
+ from { opacity: 0; transform: translateX(-50%) translateY(8px); }
32984
+ to { opacity: 1; transform: translateX(-50%) translateY(0); }
32985
+ }
32642
32986
  `
32643
32987
  ];
32644
32988
  __decorateClass([
@@ -32713,6 +33057,9 @@ __decorateClass([
32713
33057
  __decorateClass([
32714
33058
  r$1()
32715
33059
  ], SpecLensElement.prototype, "_tryItAiMenuRect", 2);
33060
+ __decorateClass([
33061
+ r$1()
33062
+ ], SpecLensElement.prototype, "_aiToast", 2);
32716
33063
  SpecLensElement = __decorateClass([
32717
33064
  t$1("spec-lens")
32718
33065
  ], SpecLensElement);