myrlin-workbook 0.8.10 → 0.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myrlin-workbook",
3
- "version": "0.8.10",
3
+ "version": "0.9.0",
4
4
  "description": "Browser-based project manager for Claude Code sessions - session discovery, multi-terminal, cost tracking, docs, and kanban board",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -3571,6 +3571,7 @@ class CWMApp {
3571
3571
  { key: 'maxConcurrentTasks', label: 'Max Concurrent Tasks', description: 'Maximum number of worktree tasks that can run simultaneously (1-8)', category: 'Advanced', type: 'number', min: 1, max: 8 },
3572
3572
  { key: 'defaultModelPlanning', label: 'Default Model (Planning)', description: 'Auto-assign when tasks enter Planning. Haiku is fast/cheap for exploration. Only applies to tasks without a model set.', category: 'Advanced', type: 'select', options: [{ value: '', label: 'None' }, { value: 'claude-haiku-4-5-20251001', label: 'Haiku (fast, cheap)' }, { value: 'claude-sonnet-4-6', label: 'Sonnet (balanced)' }, { value: 'claude-opus-4-6', label: 'Opus (thorough)' }] },
3573
3573
  { key: 'defaultModelRunning', label: 'Default Model (Running)', description: 'Auto-assign when tasks enter Running. Sonnet balances speed and quality for implementation. Only applies to tasks without a model set.', category: 'Advanced', type: 'select', options: [{ value: '', label: 'None' }, { value: 'claude-haiku-4-5-20251001', label: 'Haiku (fast, cheap)' }, { value: 'claude-sonnet-4-6', label: 'Sonnet (balanced)' }, { value: 'claude-opus-4-6', label: 'Opus (thorough)' }] },
3574
+ { key: 'anthropicApiKey', label: 'Anthropic API Key', description: 'Required for AI-powered session finder. Uses Claude Haiku for fast, low-cost semantic search across your projects and sessions. Get a key at console.anthropic.com.', category: 'AI', type: 'server-text', placeholder: 'sk-ant-...', apiEndpoint: '/api/keys/anthropic', apiField: 'key' },
3574
3575
  { key: 'cfNamedTunnel', label: 'Cloudflare Named Tunnel', description: 'Expose Myrlin on the internet via your own domain. Go to one.dash.cloudflare.com → Networks → Tunnels → Create a tunnel, then copy the token from the install command (the long eyJ… string).', category: 'Remote Access', type: 'tunnel' },
3575
3576
  ];
3576
3577
  }
@@ -8489,92 +8490,170 @@ class CWMApp {
8489
8490
 
8490
8491
 
8491
8492
  /* ═══════════════════════════════════════════════════════════
8492
- FIND A CONVERSATION
8493
+ FIND A SESSION (AI-POWERED)
8493
8494
  ═══════════════════════════════════════════════════════════ */
8494
8495
 
8496
+ /**
8497
+ * Open the AI-powered session finder overlay.
8498
+ * Uses Claude Haiku to semantically match a natural language description
8499
+ * against all known projects and sessions. Falls back to keyword matching
8500
+ * when no Anthropic API key is configured.
8501
+ */
8495
8502
  openFindConversation() {
8496
8503
  const overlay = document.getElementById('find-convo-overlay');
8497
8504
  const input = document.getElementById('find-convo-input');
8498
8505
  const results = document.getElementById('find-convo-results');
8499
8506
  const closeBtn = document.getElementById('find-convo-close');
8507
+ const searchBtn = document.getElementById('find-convo-search-btn');
8508
+ const modeIndicator = document.getElementById('find-convo-mode');
8500
8509
 
8501
8510
  if (!overlay || !input || !results) return;
8502
8511
 
8503
8512
  overlay.hidden = false;
8504
8513
  input.value = '';
8505
- results.innerHTML = '<div class="find-convo-empty">Enter keywords to search across all conversations</div>';
8514
+ results.innerHTML = '<div class="find-convo-empty">Describe the session or project you\'re looking for</div>';
8506
8515
  setTimeout(() => input.focus(), 50);
8507
8516
 
8508
- // Debounced search
8509
- let searchTimer = null;
8517
+ // Check if AI mode is available (API key configured)
8518
+ this.api('GET', '/api/keys/anthropic')
8519
+ .then(data => {
8520
+ if (modeIndicator) {
8521
+ if (data.configured) {
8522
+ modeIndicator.innerHTML = '<span class="find-convo-mode-ai">&#10024; AI search</span>';
8523
+ } else {
8524
+ modeIndicator.innerHTML = '<span class="find-convo-mode-keyword">Keyword search <a href="#" class="find-convo-setup-link">(add API key for AI)</a></span>';
8525
+ }
8526
+ // Wire up the "add API key" link to open settings
8527
+ const setupLink = modeIndicator.querySelector('.find-convo-setup-link');
8528
+ if (setupLink) {
8529
+ setupLink.addEventListener('click', (e) => {
8530
+ e.preventDefault();
8531
+ this.closeFindConversation();
8532
+ this.openSettings();
8533
+ });
8534
+ }
8535
+ }
8536
+ })
8537
+ .catch(() => {});
8538
+
8539
+ /** Execute the AI find search */
8510
8540
  const doSearch = () => {
8511
8541
  const query = input.value.trim();
8512
- if (query.length < 2) {
8513
- results.innerHTML = '<div class="find-convo-empty">Enter at least 2 characters to search</div>';
8542
+ if (query.length < 3) {
8543
+ results.innerHTML = '<div class="find-convo-empty">Enter at least 3 characters to search</div>';
8514
8544
  return;
8515
8545
  }
8516
- results.innerHTML = '<div class="find-convo-loading">Searching conversations...</div>';
8517
- this.api('POST', '/api/search-conversations', { query })
8546
+
8547
+ // Show skeleton loading cards
8548
+ results.innerHTML = Array.from({ length: 3 }, () => `
8549
+ <div class="ai-find-card ai-find-card-skeleton">
8550
+ <div class="ai-find-card-header">
8551
+ <span class="skeleton-line" style="width: 60%"></span>
8552
+ <span class="skeleton-line" style="width: 30px"></span>
8553
+ </div>
8554
+ <div class="ai-find-card-summary">
8555
+ <span class="skeleton-line" style="width: 90%"></span>
8556
+ <span class="skeleton-line" style="width: 70%"></span>
8557
+ </div>
8558
+ <div class="ai-find-card-meta">
8559
+ <span class="skeleton-line" style="width: 50%"></span>
8560
+ </div>
8561
+ </div>
8562
+ `).join('');
8563
+
8564
+ if (searchBtn) {
8565
+ searchBtn.disabled = true;
8566
+ searchBtn.textContent = 'Searching...';
8567
+ }
8568
+
8569
+ this.api('POST', '/api/ai/find-session', { query })
8518
8570
  .then(data => {
8519
8571
  const items = data.results || [];
8572
+
8520
8573
  if (items.length === 0) {
8521
- results.innerHTML = '<div class="find-convo-empty">No conversations matched your search</div>';
8574
+ results.innerHTML = '<div class="find-convo-empty">No matching sessions or projects found</div>';
8522
8575
  return;
8523
8576
  }
8524
- results.innerHTML = items.map(r => `
8525
- <div class="find-convo-result" data-session-id="${this.escapeHtml(r.sessionId)}" data-project-path="${this.escapeHtml(r.projectPath)}" data-project-encoded="${this.escapeHtml(r.projectEncoded)}">
8526
- <div class="find-convo-result-header">
8527
- <span class="find-convo-result-project">${this.escapeHtml(r.projectName)}</span>
8528
- <span class="find-convo-result-meta">${this.formatSize(r.size)} &middot; ${this.relativeTime(r.modified)}</span>
8577
+
8578
+ // Show fallback hint when AI is not configured
8579
+ const fallbackHint = data.fallback
8580
+ ? '<div class="find-convo-fallback-hint">Showing keyword matches. Add an Anthropic API key in Settings for smarter AI-powered search.</div>'
8581
+ : '';
8582
+
8583
+ results.innerHTML = fallbackHint + items.map(r => {
8584
+ const confidence = Math.round((r.confidence || 0) * 100);
8585
+ const lastActive = r.lastActive ? this.relativeTime(r.lastActive) : 'unknown';
8586
+ const statusBadge = r.status === 'running'
8587
+ ? '<span class="ai-find-card-status ai-find-card-status-running">running</span>'
8588
+ : '';
8589
+ const sessionCount = r.sessionCount != null ? `${r.sessionCount} sessions` : '';
8590
+ const typeBadge = r.type === 'workspace' ? 'project' : r.type === 'project' ? 'discovered' : 'session';
8591
+
8592
+ return `
8593
+ <div class="ai-find-card" data-type="${this.escapeHtml(r.type)}" data-id="${this.escapeHtml(r.id)}" data-path="${this.escapeHtml(r.path || '')}" data-workspace-id="${this.escapeHtml(r.workspaceId || '')}">
8594
+ <div class="ai-find-card-header">
8595
+ <span class="ai-find-card-name">${this.escapeHtml(r.name || r.id)}</span>
8596
+ <span class="ai-find-card-badge">${typeBadge}</span>
8597
+ <span class="ai-find-card-confidence">${confidence}%</span>
8598
+ </div>
8599
+ <div class="ai-find-card-summary">${this.escapeHtml(r.summary || '')}</div>
8600
+ <div class="ai-find-card-meta">
8601
+ ${r.path ? `<span class="ai-find-card-path">${this.escapeHtml(r.path)}</span>` : ''}
8602
+ <span class="ai-find-card-time">${lastActive}</span>
8603
+ ${sessionCount ? `<span class="ai-find-card-sessions">${sessionCount}</span>` : ''}
8604
+ ${statusBadge}
8605
+ </div>
8606
+ <button class="ai-find-card-open" title="Open in terminal pane">Open &#8594;</button>
8529
8607
  </div>
8530
- <div class="find-convo-result-topic">${this.escapeHtml(r.topic)}</div>
8531
- <div class="find-convo-result-preview">${this.escapeHtml(r.preview)}</div>
8532
- <div class="find-convo-result-id">${r.sessionId}</div>
8533
- </div>
8534
- `).join('');
8535
-
8536
- // Bind click on results
8537
- results.querySelectorAll('.find-convo-result').forEach(el => {
8538
- el.addEventListener('click', () => {
8539
- const sessionId = el.dataset.sessionId;
8540
- const projectPath = el.dataset.projectPath;
8541
- this.openConversationResult(sessionId, projectPath);
8542
- this.closeFindConversation();
8608
+ `;
8609
+ }).join('');
8610
+
8611
+ // Bind click handlers on cards (open button only, not the whole card)
8612
+ results.querySelectorAll('.ai-find-card-open').forEach(btn => {
8613
+ btn.addEventListener('click', (e) => {
8614
+ e.stopPropagation();
8615
+ const card = btn.closest('.ai-find-card');
8616
+ if (!card) return;
8617
+ this._openFindResult(card);
8543
8618
  });
8544
8619
  });
8620
+
8621
+ // Also allow clicking the whole card
8622
+ results.querySelectorAll('.ai-find-card').forEach(card => {
8623
+ card.addEventListener('click', () => this._openFindResult(card));
8624
+ });
8545
8625
  })
8546
8626
  .catch(err => {
8547
8627
  results.innerHTML = `<div class="find-convo-empty" style="color: var(--red);">Search failed: ${this.escapeHtml(err.message || 'Unknown error')}</div>`;
8628
+ })
8629
+ .finally(() => {
8630
+ if (searchBtn) {
8631
+ searchBtn.disabled = false;
8632
+ searchBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><line x1="16.5" y1="16.5" x2="21" y2="21"/></svg>`;
8633
+ }
8548
8634
  });
8549
8635
  };
8550
8636
 
8551
- // Remove old listener if any
8552
- if (this._findConvoInputHandler) {
8553
- input.removeEventListener('input', this._findConvoInputHandler);
8554
- }
8555
- this._findConvoInputHandler = () => {
8556
- clearTimeout(searchTimer);
8557
- searchTimer = setTimeout(doSearch, 400);
8558
- };
8559
- input.addEventListener('input', this._findConvoInputHandler);
8560
-
8561
- // Enter key triggers immediate search
8637
+ // Remove old listeners
8562
8638
  if (this._findConvoKeyHandler) {
8563
8639
  input.removeEventListener('keydown', this._findConvoKeyHandler);
8564
8640
  }
8565
8641
  this._findConvoKeyHandler = (e) => {
8566
- if (e.key === 'Enter') {
8567
- clearTimeout(searchTimer);
8568
- doSearch();
8569
- } else if (e.key === 'Escape') {
8570
- this.closeFindConversation();
8571
- }
8642
+ if (e.key === 'Enter') doSearch();
8643
+ else if (e.key === 'Escape') this.closeFindConversation();
8572
8644
  };
8573
8645
  input.addEventListener('keydown', this._findConvoKeyHandler);
8574
8646
 
8647
+ // Search button click
8648
+ if (this._findConvoSearchHandler && searchBtn) {
8649
+ searchBtn.removeEventListener('click', this._findConvoSearchHandler);
8650
+ }
8651
+ this._findConvoSearchHandler = doSearch;
8652
+ if (searchBtn) searchBtn.addEventListener('click', this._findConvoSearchHandler);
8653
+
8575
8654
  // Close handlers
8576
8655
  if (this._findConvoCloseHandler) {
8577
- closeBtn.removeEventListener('click', this._findConvoCloseHandler);
8656
+ if (closeBtn) closeBtn.removeEventListener('click', this._findConvoCloseHandler);
8578
8657
  overlay.removeEventListener('click', this._findConvoCloseHandler);
8579
8658
  }
8580
8659
  this._findConvoCloseHandler = (e) => {
@@ -8582,29 +8661,54 @@ class CWMApp {
8582
8661
  this.closeFindConversation();
8583
8662
  }
8584
8663
  };
8585
- closeBtn.addEventListener('click', this._findConvoCloseHandler);
8664
+ if (closeBtn) closeBtn.addEventListener('click', this._findConvoCloseHandler);
8586
8665
  overlay.addEventListener('click', this._findConvoCloseHandler);
8587
8666
  }
8588
8667
 
8589
- closeFindConversation() {
8590
- const overlay = document.getElementById('find-convo-overlay');
8591
- if (overlay) overlay.hidden = true;
8592
- }
8668
+ /**
8669
+ * Open a find result in a terminal pane.
8670
+ * Does NOT close the overlay so the user can open multiple results.
8671
+ */
8672
+ _openFindResult(card) {
8673
+ const type = card.dataset.type;
8674
+ const id = card.dataset.id;
8675
+ const cardPath = card.dataset.path;
8676
+ const wsId = card.dataset.workspaceId;
8677
+
8678
+ // Mark this card as opened
8679
+ card.classList.add('ai-find-card-opened');
8593
8680
 
8594
- openConversationResult(sessionId, projectPath) {
8595
- // Open the session in a terminal pane - not added to any workspace
8596
8681
  const emptySlot = this.terminalPanes.findIndex(p => p === null);
8597
8682
  if (emptySlot === -1) {
8598
8683
  this.showToast('All terminal panes full. Close one first.', 'warning');
8599
8684
  return;
8600
8685
  }
8686
+
8601
8687
  this.setViewMode('terminal');
8602
- this.openTerminalInPane(emptySlot, sessionId, sessionId, {
8603
- cwd: projectPath,
8604
- resumeSessionId: sessionId,
8605
- command: 'claude',
8606
- });
8607
- this.showToast('Opening conversation in terminal', 'info');
8688
+
8689
+ if (type === 'session') {
8690
+ // Resume existing session
8691
+ this.openTerminalInPane(emptySlot, id, id, {
8692
+ cwd: cardPath,
8693
+ resumeSessionId: id,
8694
+ command: 'claude',
8695
+ });
8696
+ this.showToast('Opening session in terminal', 'info');
8697
+ } else {
8698
+ // Workspace or discovered project: open a new session in the project directory
8699
+ const name = card.querySelector('.ai-find-card-name')?.textContent || 'new-session';
8700
+ this.openTerminalInPane(emptySlot, null, name, {
8701
+ cwd: cardPath,
8702
+ command: 'claude',
8703
+ });
8704
+ this.showToast(`Opening ${name} in terminal`, 'info');
8705
+ }
8706
+ }
8707
+
8708
+ /** Close the find conversation overlay and clean up listeners. */
8709
+ closeFindConversation() {
8710
+ const overlay = document.getElementById('find-convo-overlay');
8711
+ if (overlay) overlay.hidden = true;
8608
8712
  }
8609
8713
 
8610
8714
 
@@ -1192,7 +1192,7 @@
1192
1192
  <div class="modal-overlay" id="find-convo-overlay" hidden>
1193
1193
  <div class="modal find-convo-modal" role="dialog" aria-modal="true">
1194
1194
  <div class="modal-header">
1195
- <h3 class="modal-title">Find a Conversation</h3>
1195
+ <h3 class="modal-title">Find a Session</h3>
1196
1196
  <button class="btn btn-ghost btn-icon btn-sm" id="find-convo-close" aria-label="Close">
1197
1197
  <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
1198
1198
  <path d="M3 3l8 8M11 3l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
@@ -1200,8 +1200,13 @@
1200
1200
  </button>
1201
1201
  </div>
1202
1202
  <div class="modal-body find-convo-body">
1203
- <input type="text" id="find-convo-input" class="find-convo-input" placeholder="Describe the conversation you're looking for..." spellcheck="false" autocomplete="off" />
1204
- <div class="find-convo-hint">Search across all session content. Try topics, file names, or keywords.</div>
1203
+ <div class="find-convo-search-row">
1204
+ <input type="text" id="find-convo-input" class="find-convo-input" placeholder="Describe the session you're looking for..." spellcheck="false" autocomplete="off" />
1205
+ <button class="btn btn-primary btn-icon find-convo-search-btn" id="find-convo-search-btn" title="Search">
1206
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><line x1="16.5" y1="16.5" x2="21" y2="21"/></svg>
1207
+ </button>
1208
+ </div>
1209
+ <div class="find-convo-hint" id="find-convo-mode">Press Enter to search</div>
1205
1210
  <div class="find-convo-results" id="find-convo-results"></div>
1206
1211
  </div>
1207
1212
  </div>
@@ -3604,9 +3604,9 @@ textarea.input {
3604
3604
  background: rgba(203, 166, 247, 0.05);
3605
3605
  }
3606
3606
 
3607
- /* Find Conversation Modal */
3607
+ /* Find Session Modal (AI-powered) */
3608
3608
  .find-convo-modal {
3609
- width: min(600px, 90vw);
3609
+ width: min(640px, 90vw);
3610
3610
  max-height: 80vh;
3611
3611
  display: flex;
3612
3612
  flex-direction: column;
@@ -3617,8 +3617,13 @@ textarea.input {
3617
3617
  gap: 8px;
3618
3618
  overflow: hidden;
3619
3619
  }
3620
+ .find-convo-search-row {
3621
+ display: flex;
3622
+ gap: 6px;
3623
+ align-items: stretch;
3624
+ }
3620
3625
  .find-convo-input {
3621
- width: 100%;
3626
+ flex: 1;
3622
3627
  padding: 10px 12px;
3623
3628
  font-size: 14px;
3624
3629
  font-family: var(--font-mono);
@@ -3635,83 +3640,198 @@ textarea.input {
3635
3640
  .find-convo-input::placeholder {
3636
3641
  color: var(--overlay0);
3637
3642
  }
3643
+ .find-convo-search-btn {
3644
+ width: 38px;
3645
+ min-width: 38px;
3646
+ display: flex;
3647
+ align-items: center;
3648
+ justify-content: center;
3649
+ border-radius: var(--radius-md);
3650
+ font-size: 12px;
3651
+ }
3638
3652
  .find-convo-hint {
3639
3653
  font-size: 11px;
3640
3654
  color: var(--overlay0);
3641
3655
  padding: 0 2px;
3656
+ display: flex;
3657
+ align-items: center;
3658
+ gap: 4px;
3659
+ }
3660
+ .find-convo-mode-ai {
3661
+ color: var(--mauve);
3662
+ font-weight: 500;
3663
+ }
3664
+ .find-convo-mode-keyword {
3665
+ color: var(--overlay0);
3666
+ }
3667
+ .find-convo-setup-link {
3668
+ color: var(--accent);
3669
+ text-decoration: none;
3670
+ font-size: 11px;
3671
+ }
3672
+ .find-convo-setup-link:hover {
3673
+ text-decoration: underline;
3674
+ }
3675
+ .find-convo-fallback-hint {
3676
+ font-size: 11px;
3677
+ color: var(--overlay0);
3678
+ background: var(--surface0);
3679
+ padding: 8px 12px;
3680
+ border-radius: var(--radius-md);
3681
+ margin-bottom: 4px;
3642
3682
  }
3643
3683
  .find-convo-results {
3644
3684
  overflow-y: auto;
3645
- max-height: calc(80vh - 160px);
3685
+ max-height: calc(80vh - 180px);
3646
3686
  display: flex;
3647
3687
  flex-direction: column;
3648
- gap: 4px;
3688
+ gap: 6px;
3689
+ }
3690
+ .find-convo-loading {
3691
+ text-align: center;
3692
+ padding: 24px;
3693
+ color: var(--overlay0);
3694
+ font-size: 12px;
3695
+ }
3696
+ .find-convo-empty {
3697
+ text-align: center;
3698
+ padding: 24px;
3699
+ color: var(--overlay0);
3700
+ font-size: 12px;
3649
3701
  }
3650
- .find-convo-result {
3702
+
3703
+ /* AI Find Result Cards */
3704
+ .ai-find-card {
3705
+ position: relative;
3651
3706
  display: flex;
3652
3707
  flex-direction: column;
3653
- gap: 4px;
3654
- padding: 10px 12px;
3708
+ gap: 6px;
3709
+ padding: 12px 14px;
3655
3710
  background: var(--surface0);
3656
- border: 1px solid transparent;
3711
+ border: 1px solid var(--surface1);
3657
3712
  border-radius: var(--radius-md);
3658
3713
  cursor: pointer;
3659
- transition: all var(--transition-fast);
3714
+ transition: all 150ms ease;
3660
3715
  }
3661
- .find-convo-result:hover {
3716
+ .ai-find-card:hover {
3662
3717
  background: var(--surface1);
3663
3718
  border-color: var(--accent);
3664
3719
  }
3665
- .find-convo-result-header {
3720
+ .ai-find-card.ai-find-card-opened {
3721
+ border-color: var(--green);
3722
+ opacity: 0.7;
3723
+ }
3724
+ .ai-find-card.ai-find-card-opened::after {
3725
+ content: 'opened';
3726
+ position: absolute;
3727
+ top: 8px;
3728
+ right: 8px;
3729
+ font-size: 10px;
3730
+ color: var(--green);
3731
+ font-weight: 500;
3732
+ text-transform: uppercase;
3733
+ letter-spacing: 0.05em;
3734
+ }
3735
+ .ai-find-card-header {
3666
3736
  display: flex;
3667
3737
  align-items: center;
3668
- justify-content: space-between;
3669
3738
  gap: 8px;
3670
3739
  }
3671
- .find-convo-result-project {
3672
- font-size: 11px;
3740
+ .ai-find-card-name {
3741
+ font-size: 14px;
3673
3742
  font-weight: 600;
3674
- color: var(--accent);
3743
+ color: var(--text-primary);
3744
+ flex: 1;
3675
3745
  white-space: nowrap;
3676
3746
  overflow: hidden;
3677
3747
  text-overflow: ellipsis;
3678
3748
  }
3679
- .find-convo-result-meta {
3749
+ .ai-find-card-badge {
3680
3750
  font-size: 10px;
3681
- color: var(--overlay0);
3682
- white-space: nowrap;
3751
+ font-weight: 500;
3752
+ text-transform: uppercase;
3753
+ letter-spacing: 0.04em;
3754
+ padding: 1px 6px;
3755
+ border-radius: 3px;
3756
+ background: var(--surface1);
3757
+ color: var(--subtext0);
3758
+ }
3759
+ .ai-find-card-confidence {
3760
+ font-size: 12px;
3761
+ font-weight: 600;
3762
+ color: var(--green);
3683
3763
  font-family: var(--font-mono);
3764
+ white-space: nowrap;
3684
3765
  }
3685
- .find-convo-result-topic {
3766
+ .ai-find-card-summary {
3686
3767
  font-size: 12px;
3687
- color: var(--text-primary);
3688
- line-height: 1.4;
3768
+ color: var(--subtext0);
3769
+ line-height: 1.45;
3689
3770
  }
3690
- .find-convo-result-preview {
3771
+ .ai-find-card-meta {
3772
+ display: flex;
3773
+ flex-wrap: wrap;
3774
+ gap: 6px 12px;
3691
3775
  font-size: 11px;
3692
- color: var(--subtext0);
3693
- line-height: 1.3;
3694
- display: -webkit-box;
3695
- -webkit-line-clamp: 2;
3696
- -webkit-box-orient: vertical;
3776
+ color: var(--overlay0);
3777
+ font-family: var(--font-mono);
3778
+ }
3779
+ .ai-find-card-path {
3780
+ max-width: 100%;
3697
3781
  overflow: hidden;
3782
+ text-overflow: ellipsis;
3783
+ white-space: nowrap;
3698
3784
  }
3699
- .find-convo-result-id {
3785
+ .ai-find-card-status {
3700
3786
  font-size: 10px;
3701
- color: var(--overlay0);
3702
- font-family: var(--font-mono);
3787
+ font-weight: 500;
3788
+ padding: 0 5px;
3789
+ border-radius: 3px;
3703
3790
  }
3704
- .find-convo-loading {
3705
- text-align: center;
3706
- padding: 24px;
3707
- color: var(--overlay0);
3708
- font-size: 12px;
3791
+ .ai-find-card-status-running {
3792
+ color: var(--green);
3793
+ background: rgba(166, 218, 149, 0.1);
3709
3794
  }
3710
- .find-convo-empty {
3711
- text-align: center;
3712
- padding: 24px;
3713
- color: var(--overlay0);
3714
- font-size: 12px;
3795
+ .ai-find-card-open {
3796
+ position: absolute;
3797
+ bottom: 10px;
3798
+ right: 10px;
3799
+ background: var(--accent);
3800
+ color: var(--base);
3801
+ border: none;
3802
+ padding: 4px 12px;
3803
+ border-radius: var(--radius-sm);
3804
+ font-size: 11px;
3805
+ font-weight: 600;
3806
+ cursor: pointer;
3807
+ opacity: 0;
3808
+ transition: opacity 150ms ease;
3809
+ }
3810
+ .ai-find-card:hover .ai-find-card-open {
3811
+ opacity: 1;
3812
+ }
3813
+ .ai-find-card-open:hover {
3814
+ filter: brightness(1.15);
3815
+ }
3816
+ .ai-find-card.ai-find-card-opened .ai-find-card-open {
3817
+ display: none;
3818
+ }
3819
+
3820
+ /* Skeleton loading animation for AI find cards */
3821
+ .ai-find-card-skeleton {
3822
+ pointer-events: none;
3823
+ }
3824
+ .skeleton-line {
3825
+ display: inline-block;
3826
+ height: 12px;
3827
+ background: linear-gradient(90deg, var(--surface1) 25%, var(--surface2) 50%, var(--surface1) 75%);
3828
+ background-size: 200% 100%;
3829
+ animation: skeleton-shimmer 1.5s ease-in-out infinite;
3830
+ border-radius: 3px;
3831
+ }
3832
+ @keyframes skeleton-shimmer {
3833
+ 0% { background-position: 200% 0; }
3834
+ 100% { background-position: -200% 0; }
3715
3835
  }
3716
3836
 
3717
3837
  /* Project accordion */