From 523cc4b355ef464b86d86d1e7129ce116f2566c7 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 05:37:11 +0000 Subject: [PATCH] Create plan for issue #6 This plan outlines the implementation of remote prompt URL fetching to enable automatic prompt distribution and sharing across devices and teams. Key features: - RemotePromptsManager for fetching and caching prompts - Settings UI for managing remote sources - Automatic fetching on page load - Graceful error handling and caching Co-Authored-By: Claude Opus 4.5 --- plans/6.md | 611 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 611 insertions(+) create mode 100644 plans/6.md diff --git a/plans/6.md b/plans/6.md new file mode 100644 index 0000000..fbd95ee --- /dev/null +++ b/plans/6.md @@ -0,0 +1,611 @@ +# Implementation Plan for Issue #6 + +## Issue Summary + +**Title**: Import prompts from remote url on page load + +**Description**: Allow users to set a remote URL that returns exported prompts so the app can automatically fetch them on page load. This enables prompt sharing and automatic distribution from a centralized source. + +**Author**: tomzx + +## Feasibility Assessment + +- **Status**: ✅ Feasible +- **Complexity**: Medium +- **Estimated Effort**: 4-6 hours + +**Assessment Rationale**: +- The codebase already has a robust `PromptsManager` with import/export functionality +- Settings system exists with localStorage persistence (`SettingsManager`) +- Manager pattern is well-established for adding new functionality +- Main architectural challenge is async fetching on initialization without blocking app load +- CORS must be handled (server must support CORS or be same-origin) + +## Proposed Solution + +### Overview + +Create a new `RemotePromptsManager` that: +1. Fetches prompts from configured remote URLs on page load +2. Caches fetched prompts with a configurable timeout (default: 1 hour) +3. Merges remote prompts with existing prompts using existing `PromptsManager.importPrompts()` with `replace=false` +4. Provides UI in Settings for managing remote sources (add/remove/enable/disable) +5. Handles network errors gracefully without blocking app initialization + +### Expected Remote Data Format + +The remote URL should return JSON with the following structure: + +```json +{ + "version": "1.0", + "name": "Optional collection name", + "description": "Optional description", + "prompts": [ + { + "name": "Prompt Name", + "prompt": "The prompt text with {placeholders}", + "triggerTiming": "custom", + "customDelay": "1s", + "keyboardShortcut": "", + "llmService": "", + "llmModel": "", + "actionType": "feedback" + } + ] +} +``` + +**Validation Rules**: +- Must be valid JSON +- Must have `prompts` array +- Each prompt must have `name` and `prompt` (required) +- Other fields will use defaults if missing +- Existing `PromptsManager.importPrompts()` handles validation + +### Configuration + +Users will configure remote sources in Settings with: +- **URL**: The remote endpoint to fetch +- **Name**: Human-readable label (e.g., "Team Prompts") +- **Enabled**: Whether to auto-fetch from this source + +Stored in `SettingsManager` as: +```javascript +remotePromptUrls: [ + { + url: "https://example.com/prompts.json", + name: "Team Prompts", + enabled: true, + lastFetch: "2025-12-08T12:00:00Z", + lastError: null + } +] +``` + +### Implementation Steps + +#### Step 1: Create RemotePromptsManager + +**New File**: `components/RemotePromptsManager.js` + +**Responsibilities**: +- Fetch prompts from remote URLs +- Cache fetched prompts with timeout +- Coordinate with PromptsManager for importing +- Track fetch history and errors + +**Key Methods**: +```javascript +class RemotePromptsManager { + constructor(promptsManager, settingsManager) { + this.promptsManager = promptsManager; + this.settingsManager = settingsManager; + this.cacheTimeout = 3600000; // 1 hour default + this.storageKey = 'ai_editor_remote_prompts_cache'; + } + + async initialize() { + // Auto-fetch from all enabled sources + await this.fetchAllRemotePrompts(); + } + + async fetchAllRemotePrompts() { + const sources = this.getEnabledSources(); + const results = await Promise.allSettled( + sources.map(s => this.fetchFromSource(s)) + ); + // Handle results and show notifications + } + + async fetchFromSource(source) { + // Check cache first + const cached = this.getCachedData(source); + if (cached) return cached; + + // Fetch from URL + const response = await fetch(source.url); + if (!response.ok) throw new Error(...); + + const data = await response.json(); + // Validate structure + + // Import via PromptsManager + const result = await this.importFetchedPrompts(data.prompts, source); + + // Cache the result + this.setCachedData(source, data); + + // Update source metadata + this.updateSourceMetadata(source, { lastFetch: Date.now(), lastError: null }); + + return result; + } + + getCachedData(source) { + const cache = JSON.parse(localStorage.getItem(this.storageKey) || '{}'); + const cached = cache[source.url]; + if (!cached) return null; + + const age = Date.now() - cached.timestamp; + if (age > this.cacheTimeout) { + delete cache[source.url]; + localStorage.setItem(this.storageKey, JSON.stringify(cache)); + return null; + } + + return cached.data; + } + + setCachedData(source, data) { + const cache = JSON.parse(localStorage.getItem(this.storageKey) || '{}'); + cache[source.url] = { + data, + timestamp: Date.now(), + source: source.name + }; + localStorage.setItem(this.storageKey, JSON.stringify(cache)); + } + + clearCache() { + localStorage.removeItem(this.storageKey); + } + + getSources() { + return this.settingsManager.getSetting('remotePromptUrls') || []; + } + + getEnabledSources() { + return this.getSources().filter(s => s.enabled); + } +} +``` + +**Files to create**: `components/RemotePromptsManager.js` + +#### Step 2: Update SettingsManager + +**File**: `components/SettingsManager.js` + +**Changes**: +1. Add `remotePromptUrls` to `defaultSettings` (empty array) +2. Add methods for managing remote sources: + - `getRemotePromptUrls()` + - `addRemotePromptUrl(url, name)` + - `removeRemotePromptUrl(url)` + - `updateRemotePromptUrl(url, updates)` +3. Add UI setup methods in `setupUI()` or new `setupRemotePromptsUI()` + +**Code additions**: +```javascript +// In defaultSettings +remotePromptUrls: [] + +// New methods +getRemotePromptUrls() { + return this.settings.remotePromptUrls || []; +} + +addRemotePromptUrl(url, name) { + const sources = this.getRemotePromptUrls(); + if (sources.find(s => s.url === url)) { + throw new Error('Source with this URL already exists'); + } + const newSource = { + url, + name: name || url, + enabled: true, + lastFetch: null, + lastError: null, + addedAt: new Date().toISOString() + }; + this.settings.remotePromptUrls = [...sources, newSource]; + this.saveSettings(); + this.notifyChange('remotePromptUrls', this.settings.remotePromptUrls); + return newSource; +} + +removeRemotePromptUrl(url) { + const sources = this.getRemotePromptUrls().filter(s => s.url !== url); + this.settings.remotePromptUrls = sources; + this.saveSettings(); + this.notifyChange('remotePromptUrls', this.settings.remotePromptUrls); +} + +updateRemotePromptUrl(url, updates) { + const sources = this.getRemotePromptUrls(); + const index = sources.findIndex(s => s.url === url); + if (index === -1) throw new Error('Source not found'); + sources[index] = { ...sources[index], ...updates }; + this.settings.remotePromptUrls = sources; + this.saveSettings(); + this.notifyChange('remotePromptUrls', this.settings.remotePromptUrls); +} +``` + +**Files to modify**: `components/SettingsManager.js` + +#### Step 3: Update Main App Initialization + +**File**: `script.js` + +**Changes**: Initialize `RemotePromptsManager` after other managers + +**Location**: In `initializeManagers()`, after `importExportManager` (line ~110) + +```javascript +initializeManagers() { + // ... existing managers ... + this.importExportManager = new ImportExportManager(); + + // NEW: Initialize remote prompts manager + this.remotePromptsManager = new RemotePromptsManager( + this.promptsManager, + this.settingsManager + ); + + // ... rest of initialization ... +} + +// In the setTimeout block (after other managers initialize) +setTimeout(async () => { + this.settingsManager.setupUI(); + await this.usageTracker.initialize(); + await this.historyManager.initialize(); + await this.importExportManager.initialize(); + this.setupImportExportUI(); + + // NEW: Initialize remote prompts + await this.remotePromptsManager.initialize(); + this.setupRemotePromptsUI(); +}, 0); +``` + +**Add setup method**: +```javascript +setupRemotePromptsUI() { + const addBtn = document.getElementById('addRemotePromptSourceBtn'); + const fetchBtn = document.getElementById('fetchRemotePromptsBtn'); + const clearCacheBtn = document.getElementById('clearRemotePromptsCacheBtn'); + + if (addBtn) { + addBtn.addEventListener('click', () => this.showAddRemoteSourceDialog()); + } + if (fetchBtn) { + fetchBtn.addEventListener('click', () => this.remotePromptsManager.fetchAllRemotePrompts()); + } + if (clearCacheBtn) { + clearCacheBtn.addEventListener('click', () => { + this.remotePromptsManager.clearCache(); + if (window.app?.notificationManager) { + window.app.notificationManager.success('Remote prompts cache cleared'); + } + }); + } + + this.renderRemoteSourcesList(); +} + +renderRemoteSourcesList() { + const sources = this.remotePromptsManager.getSources(); + const container = document.getElementById('remotePromptsList'); + if (!container) return; + + // Render list of sources with status indicators + // Similar to renderCustomServicesList() in SettingsManager +} +``` + +**Files to modify**: `script.js` + +#### Step 4: Add UI to index.html + +**File**: `index.html` + +**Location**: In Settings tab content, after Custom LLM Services section (after line ~400) + +**Add new section**: +```html +
+

Remote Prompt Sources

+
+ + +
+
+
+
Automatically import prompts from remote URLs on page load. This enables sharing prompts across devices and teams.
+
+
+

No remote sources configured.

+
+
+ + +
+
+

+ Expected format: JSON with a prompts array. + Each prompt needs name and prompt fields. +

+
+
+
+ + + +``` + +**Add script tag** (before closing body tag): +```html + +``` + +**Files to modify**: `index.html` + +#### Step 5: Add CSS Styling + +**File**: `styles.css` + +**Add styles** for: +- Remote sources list items +- Status indicators (success/error/pending) +- Remote source modal + +```css +.remote-prompts-list { + max-height: 300px; + overflow-y: auto; +} + +.remote-source-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px; + background: var(--card-bg); + border-radius: 8px; + margin-bottom: 8px; +} + +.remote-source-info { + flex: 1; +} + +.remote-source-name { + font-weight: 600; + margin-bottom: 4px; +} + +.remote-source-url { + font-size: 0.85em; + color: var(--muted); + font-family: monospace; +} + +.remote-source-status { + display: flex; + align-items: center; + gap: 12px; +} + +.status-indicator { + width: 10px; + height: 10px; + border-radius: 50%; +} + +.status-indicator.success { background: #4caf50; } +.status-indicator.error { background: #f44336; } +.status-indicator.pending { background: #ff9800; } + +.remote-source-actions { + display: flex; + gap: 8px; +} + +.remote-prompts-actions { + display: flex; + gap: 8px; + margin-top: 16px; +} + +/* Modal styles if not already present */ +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + z-index: 1000; + align-items: center; + justify-content: center; +} + +.modal.active { + display: flex; +} + +.modal-content { + background: var(--card-bg); + padding: 24px; + border-radius: 12px; + width: 90%; + max-width: 500px; +} + +.form-group { + margin-bottom: 16px; +} + +.form-group label { + display: block; + margin-bottom: 8px; +} + +.form-group input { + width: 100%; + padding: 10px; + border: 1px solid var(--border); + border-radius: 6px; +} + +.modal-actions { + display: flex; + justify-content: flex-end; + gap: 12px; + margin-top: 24px; +} +``` + +**Files to modify**: `styles.css` + +### Dependencies + +**External dependencies**: None (uses standard `fetch` API) + +**Internal dependencies**: +- `PromptsManager` - for importing fetched prompts +- `SettingsManager` - for storing configuration +- `NotificationManager` - for showing fetch status + +### Data Flow + +``` +Page Load + ↓ +RemotePromptsManager.initialize() + ↓ +Get enabled sources from SettingsManager + ↓ +For each source: + 1. Check cache (if recent, skip) + 2. Fetch from URL + 3. Validate JSON structure + 4. Call PromptsManager.importPrompts(prompts, replace=false) + 5. Cache result + 6. Update source metadata (lastFetch, lastError) + ↓ +Show notification with results +``` + +### Risk Assessment + +| Risk | Impact | Mitigation | +|------|--------|------------| +| CORS errors | High - prompts won't load | Document CORS requirement; show helpful error message | +| Network failures | Medium - some sources fail | Use Promise.allSettled; show per-source status | +| Invalid JSON | Low - single source fails | Validate before importing; show specific error | +| Cache stale data | Low | Configurable timeout; manual "Fetch Now" button | +| Naming conflicts | Low | Existing importPrompts() skips duplicates by name | +| Malicious content | Medium | Validate structure; sanitize via JSON parsing | + +### Testing Considerations + +1. **Functional tests**: + - Add/remove remote sources + - Enable/disable sources + - Fetch on page load + - Manual fetch button + - Cache clearing + +2. **Error scenarios**: + - Invalid URL + - Network timeout + - CORS error + - Invalid JSON + - Empty response + +3. **Integration tests**: + - Verify prompts are imported correctly + - Check merging with existing prompts + - Verify cache timeout behavior + +4. **Test server setup**: + - Create a test JSON file with prompts + - Ensure CORS headers are set (Access-Control-Allow-Origin: *) + +## Alternatives Considered + +### Alternative 1: Browser Extension +**Pros**: Can bypass CORS, more powerful +**Cons**: Requires installation, more complex distribution +**Decision**: Not chosen - web app is simpler to use + +### Alternative 2: Webhook-based push +**Pros**: Real-time updates +**Cons**: Requires backend, more complex +**Decision**: Not chosen - polling on page load is simpler + +### Alternative 3: GitHub Gist integration +**Pros**: No server needed +**Cons**: GitHub-specific, rate limits +**Decision**: Not chosen - generic URLs are more flexible + +### Alternative 4: Local file import only +**Pros**: No network dependency +**Cons**: Manual process, can't auto-update +**Decision**: Already supported; this feature adds automation + +## References + +- **Related issues**: None +- **Related PRs**: None +- **Documentation**: + - `CLAUDE.md` - Project architecture and patterns + - `PromptsManager.js` - Import/export functionality + - `SettingsManager.js` - Settings management pattern + +## Open Questions for Review + +1. **Cache timeout**: Should 1 hour be the default, or make it configurable? +2. **Prompt conflicts**: When a remote prompt has the same name but different content, should we: + - Skip (current behavior via importPrompts) + - Overwrite + - Create with suffix (e.g., "Prompt Name (remote)") +3. **Source attribution**: Should we track which source a prompt came from? Useful for bulk removal. +4. **Auth**: Should we support authenticated endpoints? (Not in scope initially)