# Clipper Plugin - Comprehensive Changelog ## Version 2.4.4 (2026-04-26) ### Critical data-loss fix: notecards never reached disk If you lost a notecard between v2.4.3 and v2.4.4 — I'm sorry. This was my fault. The "atomic write" hotfix shipped in v2.4.3 turned out to be **silently broken** on noctalia-qs and similar Quickshell builds: `Quickshell.execDetached(["sh", "-c", script, ...])` with the multi-arg shell pipeline never actually executed the script. Every `saveNoteCard()` call returned successfully, the note appeared in the panel UI, and a "Note created" toast was shown — but the file never landed in `~/.config/noctalia/plugins/clipper/notecards/`. On the next panel open, `loadNoteCards` saw nothing and the note was gone. The same path was used for `pinned.json` and notecard `.txt` exports, so those could vanish too under the same conditions. #### Root cause - `Quickshell.execDetached` with `["sh", "-c", script, "$0", "$1", "$2", "$3"]` (where `$3` was a long base64 string) silently no-ops in noctalia-qs 0.0.x. The simpler 2–3 argv calls (`mkdir -p`, `find ... -delete`) work; the long-argv save path does not. There is no error, no log, no partial file — the script never starts. - `loadNoteCards` used `jq -s '.' *.json 2>/dev/null || echo '[]'`, which is **all-or-nothing**: a single 0-byte or malformed file in the directory made jq exit non-zero, the fallback returned an empty array, and the in-memory `noteCards` was wiped — making every other note invisible in the UI even though it was intact on disk. #### Fix - **New atomic-write engine.** Replaced `Quickshell.execDetached` + base64 with a queued `Process` that pipes the raw payload to `sh -c 'cat > "$1.tmp" && [ -s "$1.tmp" ] && mv -f "$1.tmp" "$1" && { [ -z "$2" ] || [ "$2" = "$1" ] || rm -f "$2"; } || { rm -f "$1.tmp"; exit 1; }'` via stdin. Process gives us a real exit code and stderr, so genuine failures are now logged via `Logger.w` instead of vanishing. The queue serializes saves because Process is single-instance. - **Resilient loader.** `loadNoteCards` now validates each `*.json` file individually with `jq -e .` and concatenates only the valid ones into the slurp. A single corrupt or 0-byte file no longer wipes the rest. The `onExited` handler also stops blanking `noteCards` on shell errors or empty results — when the disk read gives nothing useful, in-memory state is preserved instead of cleared. - **No more base64.** Payload travels through stdin, so Polish / Cyrillic / emoji titles and content survive verbatim without escaping concerns. The `stringToBase64` helper and the `atomicWriteBase64` shim are gone. Applied to `savePinnedFile`, `saveNoteCard`, `updateNoteCard`, and `exportNoteCard`. Tested by reproducing the disappearing-notecard case (rename + reopen panel) on noctalia-qs 0.0.12 — pre-fix: file vanished; post-fix: file persists across restarts. If you can still reproduce a disappearance, please open an issue with `grep Clipper /run/user/$(id -u)/quickshell/by-id/*/log.log | tail -40`. The `atomicWrite FAIL` lines now carry exit code and stderr. ## Version 2.4.3 (2026-04-22) ### Data-loss hotfix: atomic writes for pinned items and notecards Fixes a data-loss bug that could silently destroy pinned items (`pinned.json`) or individual notecards (`notecards/*.json`) under rare timing conditions. A 0-byte ghost file left on disk was traced back to three compounding issues in the save path: 1. **Non-atomic shell redirection.** All writes used `base64 -d > "$file"`, which opens the target with `O_TRUNC` **before** decoding and writing. If the process was interrupted between truncate and write (shell restart, kill, OOM, base64 failure), the file was left at 0 bytes with no recovery possible. 2. **Rename race in `updateNoteCard`.** When a note's title changed, the old file was deleted via a parallel `rm` `execDetached` call issued alongside the new `saveNoteCard`. If the `rm` completed but the save failed, the note vanished entirely. 3. **Empty-base64 edge case.** If `stringToBase64()` returned an empty string (e.g. from a malformed note object), the shell pipeline still truncated the target, producing a guaranteed 0-byte file. **Fix:** new `atomicWriteBase64(filePath, base64, oldFilePath)` helper in `Main.qml`: - Writes to a temp file (`.tmp`) first, verifies it is non-empty (`[ -s "$t" ]`), then atomically renames over the target with `mv -f`. - Only removes the old file **after** the new file is safely in place (and only when the old path differs from the new path). - Passes `filePath`, `oldFilePath`, and `base64` via argv (not string interpolation) — shell-injection safe and robust against filenames with spaces/quotes. - Refuses empty writes up front (`base64.length === 0`) and logs a warning instead of truncating the target. - `saveNoteCard` now validates `note.id` and `JSON.stringify` length ≥ 10 before writing. Applied to `savePinnedFile`, `saveNoteCard`, `updateNoteCard`, and `exportNoteCard`. Also: `Component.onCompleted` now sweeps any stale `*.json.tmp` files left over from previous interrupted writes, so a crash mid-hotfix cannot leak temp files indefinitely. ## Version 2.4.2 (2026-04-22) ### Fix ReferenceError on plugin teardown - Removed a dead `wlCopyProc` reference in `Component.onDestruction` (`Main.qml`) that threw `ReferenceError: wlCopyProc is not defined` when the panel or shell was torn down. The id was left over from an earlier refactor; wl-copy usage already lives in `copyToClipboardProc` and direct `Quickshell.execDetached(["wl-copy", ...])` calls, so no replacement logic is needed. ## Version 2.4.1 (2026-04-21) ### Fix Qt.btoa deprecation warnings - Replaced three deprecated `Qt.btoa(string)` calls in `Main.qml` (`savePinnedFile`, `exportNoteCard`, `saveNoteCard`) with a new `stringToBase64(str)` helper that encodes the string to UTF-8 bytes as a `Uint8Array` and passes it to the non-deprecated `Qt.btoa(array-like)` overload. - Simplified the matching `Qt.atob()` call: the array-like overload returns a `Uint8Array` directly, so the intermediate string + `charCodeAt` loop was removed. - Side benefit: `exportNoteCard()` now writes properly encoded UTF-8 `.txt` files — previously, notes containing non-ASCII characters (Polish, emoji, Cyrillic, etc.) were written with Latin-1 byte encoding. - No behavior change for existing `pinned.json` / `notecards/*.json` files: `JSON.stringify` already escapes non-ASCII to `\uNNNN`, so the base64 output is effectively identical for ASCII-safe JSON. ## Version 2.4.0 (2026-04-20) ### Live-Preview Settings, Panel Size Controls, i18n Cleanup **Settings live-preview with revert-on-cancel:** - All settings changes apply immediately as a live preview via `_applyPreview()` - Snapshot taken on `Component.onCompleted`; reverted on `Component.onDestruction` if user cancels (Apply not clicked) - `saveSettings()` sets `_applied = true` to suppress revert **New panel size controls (Appearance tab):** - `NSpinBox` for panel width (400–3840 px, step 50) — hidden in fullscreen mode - `NSpinBox` for panel height (0–2160 px, step 50, 0 = auto) — hidden in fullscreen mode - `Panel.qml` now reads `panelWidth`/`panelHeight` from settings instead of hardcoded 1450 **Hide Panel Background gating:** - "Hide Panel Background" toggle and its divider are now hidden when NoteCards are enabled **i18n cleanup:** - Removed all `|| "fallback"` suffixes from `pluginApi?.tr()` calls across all 11 QML files - Added 4 new translation keys (`panel-width`, `panel-width-desc`, `panel-height`, `panel-height-desc`) to all 17 language files **manifest.json:** - Version bumped to `2.4.0` - Added `defaultSettings`: `fullscreenMode`, `hidePanelBackground`, `autoPaste`, `autoPasteOnRightClick`, `autoPasteDelay`, `panelWidth`, `panelHeight`, `cardColors`, `customColors` ## Version 1.4.0 (2026-02-04) ### NoteCards / Sticky Notes Feature 🎉 **Brand new notecards panel with draggable sticky note cards:** - Create, edit, move, and manage multiple note cards in the middle space - Persistent storage in `notecards.json` file - 5 color themes: Yellow, Pink, Blue, Green, Purple - Auto-save with 500ms debounce after typing stops - Export individual notes to .txt files in ~/Documents - Drag-and-drop positioning with boundary enforcement - Z-index management for overlapping cards - Maximum 20 notes limit with visual indicators **New Files:** - `NoteCardsCard.qml` (290 lines) - Draggable note card component - `NoteCardsPanel.qml` (159 lines) - Container for note cards **Files Modified:** - `Main.qml` (+150 lines) - Data model, CRUD functions, IPC handlers - `Panel.qml` (+20 lines) - Added notecards panel in middle space - `Settings.qml` (+110 lines) - NoteCards settings section - `manifest.json` - Version bump to 1.4.0, updated description - `i18n/*.json` (16 files) - Added notecards translation keys **New Functions in Main.qml:** - `createNoteCardsNote(initialText)` - Create note with cascade positioning - `updateNoteCardsNote(noteId, updates)` - Update content/position/color - `deleteNoteCardsNote(noteId)` - Remove note - `exportNoteCardsNote(noteId)` - Export to ~/Documents/*.txt - `saveNoteCardsNotes()` - Persist to JSON with base64 encoding - `bringNoteToFront(noteId)` - Z-index management **New IPC Commands:** ```bash qs -c noctalia-shell ipc call plugin:clipper addNoteCardsNote "Quick note" qs -c noctalia-shell ipc call plugin:clipper exportNoteCardsNote "note_123_abc" ``` **NoteCardsCard Features:** - Draggable header with grip icon - Inline text editing with TextArea - Color picker button (cycles through 5 colors) - Export button (saves to .txt) - Delete button - Automatic boundary enforcement - Click-to-bring-to-front **NoteCardsPanel Features:** - Empty state UI with "Create Note" button - Note counter (X/20) with color coding - Floating "Add Note" button - Repeater for dynamic note card rendering **Settings UI:** - Enable/disable toggle - Default color selector - Current notes count display (X/20) - Clear all notes button **Technical Details:** - Absolute positioning (x, y coordinates in JSON) - Fixed size 250x200px (MVP - resizable in future) - Cascade positioning: 30px offset for new notes - Storage: `~/.config/noctalia/plugins/clipper/notecards.json` - Immutable array updates throughout - Component.onDestruction cleanup for timers **notecards.json Schema:** ```json { "notes": [ { "id": "note_1709123456789_abc123", "content": "Note text here...", "x": 450, "y": 150, "width": 250, "height": 200, "zIndex": 1, "color": "yellow", "createdAt": "2026-02-04T10:30:00Z", "lastModified": "2026-02-04T11:45:00Z" } ] } ``` **Bug Fixes During Implementation:** - Fixed `qs.Style` import (moved to `qs.Commons`) - Removed `NToolTip` component (use `tooltipText` property) - Added missing `qs.Widgets` import - Changed `size` to `pointSize` for `NIcon` components - Removed `iconSize` and `applyUiScale` from `NIconButton` - Replaced spread operator (`...`) with `Object.assign()` and `.slice()` - Removed `NDropShadow` (not available) - Replaced optional chaining (`?.`) with explicit conditional checks - Changed arrow functions `(mouse) =>` to traditional syntax - Fixed `fontSize`/`fontWeight` to `font.pointSize`/`font.bold` **Translation Keys Added (16 languages):** ```json { "notecards": { "empty-state": "No notes yet", "empty-hint": "Click the button below to create your first note", "create-note": "Create Note", "change-color": "Change Color", "export": "Export to .txt", "delete": "Delete Note" }, "settings": { "notecards": "NoteCards / Sticky Notes", "notecards-enabled": "Enable NoteCards", "notecards-desc": "Show notecards panel for quick notes", "default-note-color": "Default Note Color", "default-note-color-desc": "Color for newly created notes", "notecards-notes-count": "Current Notes", "clear-all-notes": "Clear All Notes" } } ``` **Known Limitations:** - Fixed size cards (250x200px) - resizing planned for v1.5.0 - No rich text formatting - plain text only - No markdown support - planned for future - Maximum 20 notes - performance limitation --- ## Version 1.3.2 (2026-02-04) ### Translation System Overhaul ✅ **Fixed i18n structure to comply with Noctalia standards:** - Removed plugin name prefix from JSON structure - Changed from `{"clipper": {...}}` to `{"bar": {...}, "panel": {...}}` - Updated all 16 language files to new structure (de, en, es, fr, hu, ja, ko-KR, nl, pl, pt, ru, sv, tr, uk-UA, zh-CN, zh-TW) - Updated 37 translation calls across 4 QML files - All keys now use kebab-case format (e.g., `"panel.search-placeholder"`) - 100% consistency across all language files (43 keys each) **Files modified:** - `i18n/*.json` (16 files) - Structure update - `BarWidget.qml` (3 keys) - `ClipboardCard.qml` (3 keys) - `Panel.qml` (15 keys) - `Settings.qml` (16 keys) ### ToDo Context Menu Fix ✅ **Fixed non-functional ToDo button on clipboard cards:** - Added `screen` property passing from Panel.qml to ClipboardCard delegates - Fixed context menu positioning to appear at button location - Implemented proper menu lifecycle management - Added automatic closing of previous menu when opening new one - Removed Keys.onPressed handler (NPopupContextMenu is not an Item) **Implementation details:** - Added `property var currentScreen: screen` in Panel.qml - Added `property var activeContextMenu: null` for tracking open menus - Changed PanelService.showContextMenu to manual positioning with anchorItem - Menu now positioned using `mapToItem()` for accurate placement - MouseArea for click-outside-to-close functionality **Files modified:** - `ClipboardCard.qml` - Added screen property, menuAnchor, activeContextMenu tracking - `Panel.qml` - Added currentScreen and activeContextMenu properties, passed to delegates ### Documentation Update ✅ **Updated QML development guidelines:** - Added comprehensive i18n section to `~/.claude/rules/QML-code-reviewer.md` - Documented standard i18n structure patterns from official Noctalia plugins - Added 400+ lines of translation best practices and examples - Included anti-patterns and common mistakes to avoid **Changes summary:** - Total files changed: 21 - i18n files: 16 updated - QML files: 4 updated - Documentation: 1 updated - Lines changed: ~500 across all files --- ## Version Comparison: GitHub v1.1.0 → Local v1.3.1 --- ## Executive Summary **Total Changes:** - **Main.qml**: +29/-672 lines (Major refactoring - removed ClipboardService.qml integration) - **Panel.qml**: +190/-193 lines (Architecture overhaul) - **Settings.qml**: +467/-254 lines (Complete redesign with color customization) - **BarWidget.qml**: +15/-33 lines (Translation system update) - **ClipboardCard.qml**: +13/-103 lines (Simplified + ToDo integration) - **manifest.json**: Version bump + metadata updates **New Files:** - `TodoPageSelector.qml` - Full-screen overlay for ToDo page selection - `CHANGELOG.md` - Version history documentation - `pinned.json` - Persistent storage for pinned clipboard items **Removed Files:** - `ClipboardService.qml` - Functionality integrated into Main.qml - `.gitignore` - Accidentally deleted (should be restored) --- ## Major Feature Additions ### 1. Pinned Items System (v1.2.0+) **Impact:** High - Completely new feature **Implementation:** - Persistent pinned clipboard items stored in `pinned.json` - Support for both text and images (base64 encoded) - Dedicated vertical panel on left side of screen - Security improvements: Input validation, size limits - LRU cache for image previews (max 50 items) **Limits:** - Max 20 pinned items - Max 5MB per image - Max 1MB per text item - Max 10MB for image preview cache **New IPC Commands:** ```bash qs ipc call plugin:clipper pinClipboardItem qs ipc call plugin:clipper unpinItem qs ipc call plugin:clipper copyPinned ``` **Files Modified:** - `Main.qml`: Added pinItem(), unpinItem(), copyPinnedToClipboard(), savePinnedFile() - `Panel.qml`: Added pinnedPanel with ListView for pinned items - `ClipboardCard.qml`: Added pin button and isPinned state --- ### 2. ToDo Plugin Integration (v1.2.0+) **Impact:** High - Major new integration **Features:** - Add clipboard items directly to ToDo lists - Page selection via context menu on clipboard cards - Primary selection support (add selected text via keybind) - Full-screen ToDo page selector overlay **Implementation:** - `TodoPageSelector.qml`: New component with cursor-following context menu - Direct API access via `PluginService.getPluginAPI("todo")` - No IPC overhead for internal operations - Support for multiple ToDo pages **New IPC Commands:** ```bash qs ipc call plugin:clipper addSelectionToTodo ``` **Removed IPC Commands (replaced by context menu):** ```bash # These were removed as they're now handled via UI context menu qs ipc call plugin:clipper addToTodo1..9 qs ipc call plugin:clipper addToTodo qs ipc call plugin:clipper addTextToTodo ``` **Settings:** - New toggle: "ToDo Plugin Integration" (auto-detects if ToDo plugin is installed) - FileView monitoring of `~/.config/noctalia/plugins.json` for availability --- ### 3. Advanced Card Color Customization (v1.3.0+) **Impact:** High - Complete UI customization system **Features:** - Per-card-type color schemes (Text, Image, Link, Code, Color, Emoji, File) - Live preview of color changes - Material Design semantic colors + custom RGB picker - Separate colors for: background, separator, foreground - Reset to defaults button **Color Options:** - 15 Material Design colors (mPrimary, mSecondary, mTertiary, etc.) - Custom RGB color picker for unlimited options - Colors saved per-card-type in plugin settings **UI Components:** - Live preview card (250x220px) showing real-time changes - 3 NComboBox selectors (bg, separator, fg) - 3 conditional NColorPicker widgets for custom colors - Card type selector dropdown **Default Color Schemes:** ```javascript Text: { bg: "mOutline", separator: "mSurface", fg: "mOnSurface" } Image: { bg: "mTertiary", separator: "mSurface", fg: "mOnTertiary" } Link: { bg: "mPrimary", separator: "mSurface", fg: "mOnPrimary" } Code: { bg: "mSecondary", separator: "mSurface", fg: "mOnSecondary" } Color: { bg: "mSecondary", separator: "mSurface", fg: "mOnSecondary" } Emoji: { bg: "mHover", separator: "mSurface", fg: "mOnHover" } File: { bg: "mError", separator: "mSurface", fg: "mOnError" } ``` --- ### 4. Translation System Overhaul (v1.3.0+) **Impact:** Medium - Better i18n support **Changes:** - Migrated from `I18n.tr()` to `pluginApi?.tr()` system - All hardcoded strings replaced with translation keys - 16 language files added (es, hu, ja, ko-KR, nl, pl, pt, ru, sv, tr, uk-UA, zh-CN, zh-TW) - Consistent key naming: `clipper.component.key` **New Translation Keys Added:** ```json { "clipper": { "bar": { "tooltip": "..." }, "context": { "toggle": "...", "settings": "..." }, "panel": { 30+ keys for all UI elements }, "card": { "add-todo": "...", "pin": "...", "delete": "..." }, "settings": { 15+ keys for settings UI } } } ``` **Files Updated:** - `BarWidget.qml`: Changed I18n.tr() to pluginApi?.tr() - `Panel.qml`: All user-facing strings now translatable - `Settings.qml`: All labels, descriptions, buttons translated - `ClipboardCard.qml`: Tooltip translations added --- ## Architecture Improvements ### 1. Removal of ClipboardService.qml **Impact:** High - Simplified architecture **Rationale:** - Eliminated unnecessary abstraction layer - Reduced IPC overhead - Direct function calls from Panel.qml to Main.qml - Better memory management **Migration:** - All ClipboardService functions moved to Main.qml - Panel uses `pluginApi?.mainInstance?.functionName()` pattern - Removed internal IPC calls (anti-pattern per guidelines) **Functions Integrated:** - list(), copyToClipboard(), deleteById(), wipeAll() - pinItem(), unpinItem(), copyPinnedToClipboard() - decodeToDataUrl(), getItemType() - addTodoWithText(), showTodoPageSelector() --- ### 2. Security Enhancements **Impact:** Critical - Prevented command injection vulnerabilities **Fixes:** - Input validation with regex `/^\d+$/` for all clipboard IDs - Shell command parameters properly escaped - Qt.btoa() for safe base64 encoding (no shell metacharacters) - Process stdin used instead of shell interpolation - File paths are constant, not user-controlled **Vulnerable Patterns Fixed:** ```qml // BEFORE (v1.1.0 - VULNERABLE) Quickshell.execDetached(["sh", "-c", "cliphist decode " + id + " | wl-copy"]) // AFTER (v1.3.1 - SECURE) if (!/^\d+$/.test(String(id))) { Logger.e("clipper", "Invalid clipboard ID: " + id); return; } copyToClipboardProc.command = ["cliphist", "decode", String(id)]; copyToClipboardProc.running = true; ``` **Protected Functions:** - pinItem(), deleteById(), copyToClipboard(), decodeToDataUrl() --- ### 3. Memory Management **Impact:** Medium - Leak prevention **Improvements:** - Main.qml: Comprehensive cleanup of 11 Process objects - TodoPageSelector.qml: Timer cleanup on destruction - LRU cache with automatic eviction (maxImageCacheSize: 50) - clearCaches() function for wipe operations **Component.onDestruction Added:** ```qml // Main.qml Component.onDestruction: { if (listProc.running) listProc.terminate(); if (decodeProc.running) decodeProc.terminate(); // ... 9 more processes } // TodoPageSelector.qml Component.onDestruction: { showMenuTimer.stop(); close(); } ``` --- ### 4. IPC Interface Cleanup **Impact:** Medium - Cleaner external API **Simplified IPC:** - Removed 9 numbered functions: `addToTodo1()` through `addToTodo9()` - Removed generic `addToTodo(pageId: int)` - Removed `addTextToTodo(text, pageId)` (now internal-only) - Renamed `addToTodoWithSelector()` → `addSelectionToTodo()` **Retained Essential IPC:** ```qml function toggle() // Required for keybind compatibility function openPanel() function closePanel() function togglePanel() function pinClipboardItem(cliphistId: string) function unpinItem(pinnedId: string) function copyPinned(pinnedId: string) function addSelectionToTodo() // NEW - replaced 9+ functions ``` --- ## UI/UX Improvements ### 1. Panel Layout Redesign **Impact:** High - Better space utilization **Changes:** - Fullscreen transparent overlay (replaces anchored panel) - Two sub-panels: Pinned (left, vertical) + Clipboard (bottom, horizontal) - Pinned panel: 300px width (max 20% screen width) - Clipboard panel: 300px height (max 30% screen height) - Click outside panels to close **Removed Animations:** - Eliminated `populate`, `add`, `remove`, `displaced`, `move` transitions - Instant card appearance/disappearance - No sliding animations on delete - Improved perceived performance **Benefits:** - Always-visible pinned items - More horizontal space for clipboard cards - Better multi-monitor support (fullscreen overlay) --- ### 2. ClipboardCard Enhancements **Impact:** Medium - Better usability **Visual Improvements:** - Pin indicator icon in top-right corner (for pinned items) - Unified color scheme (background = header = body) - Gradient separator between header and content - Hover effects with color lightening/darkening **Interactive Elements:** - ToDo button now shows context menu (page selection) - Pin button hidden for already-pinned items - Delete button always visible - Tooltips added for all action buttons **Type Detection:** - Enhanced regex patterns for better content classification - Support for: Text, Image, Link, Code, Color, Emoji, File paths --- ### 3. Settings UI Redesign **Impact:** High - Professional appearance **Layout:** - Section headers with bold large font - NDivider separators between sections - Logical grouping: Integrations, Pinned Items, Appearance **New Components:** - Card type selector dropdown - Live preview rectangle with sample card - 3-level color customization (bg, separator, fg) - RGB color pickers with Material Design palette - Reset to defaults button **Removed:** - "Auto-open pinned panel" toggle (always-on design) - "Clear All Pinned Items" button (use individual delete) --- ## Performance Optimizations ### 1. Image Caching Strategy **Implementation:** - LRU (Least Recently Used) cache - Max 50 images in memory - Automatic eviction of oldest entries - Reactive binding via `imageCacheRevision` counter **Cache Operations:** ```qml function addToImageCache(cliphistId, dataUrl) { // Remove if exists (will re-add at end) // Evict oldest if at capacity while (imageCacheOrder.length >= maxImageCacheSize) { const oldestKey = imageCacheOrder[0]; // Remove from cache } // Add new entry imageCacheRevision++; // Trigger reactive updates } ``` **Benefits:** - Reduced cliphist decode calls (expensive operation) - Faster card rendering for previously-seen images - Bounded memory usage --- ### 2. Removed Animations **Impact:** Medium - Faster perceived performance **Removed Transitions:** - `populate`: Initial list population - `add`: New item insertion - `remove`: Item deletion (was sliding left) - `displaced`: Item repositioning - `move`: Item movement **Result:** - Instant feedback on delete operations - No visual lag during filtering - Cleaner appearance during rapid changes --- ## Bug Fixes ### 1. Command Injection Vulnerabilities (CRITICAL) **GitHub v1.1.0 Issue:** ```qml // Vulnerable to shell injection Quickshell.execDetached(["sh", "-c", "cliphist decode " + id + " | wl-copy"]) ``` **Local v1.3.1 Fix:** ```qml // Input validation + proper command array if (!/^\d+$/.test(String(id))) { Logger.e("clipper", "Invalid clipboard ID"); return; } copyToClipboardProc.command = ["cliphist", "decode", String(id)]; ``` --- ### 2. Internal IPC Anti-Pattern (HIGH) **GitHub v1.1.0 Issue:** ```qml // Panel.qml calling its own plugin via IPC (anti-pattern) Process { command: ["qs", "ipc", "call", "plugin:clipper", "addTextToTodo", ...] } ``` **Local v1.3.1 Fix:** ```qml // Direct function call pluginApi?.mainInstance?.addTodoWithText(text, pageId); ``` --- ### 3. Memory Leaks in Process Objects **GitHub v1.1.0 Issue:** - No cleanup handlers for Process objects - Processes continue running after component destruction **Local v1.3.1 Fix:** ```qml Component.onDestruction: { if (listProc.running) listProc.terminate(); if (decodeProc.running) decodeProc.terminate(); // ... all 11 processes terminated } ``` --- ### 4. Multi-Monitor Support Issues **Remaining Issue in v1.3.1:** ```qml // Still uses Quickshell.screens[0] - should use withCurrentScreen() function toggle() { const screens = Quickshell.screens; if (screens && screens.length > 0) { pluginApi.togglePanel(screens[0]); // Always first screen } } ``` **Recommended Fix:** ```qml function toggle() { if (pluginApi) { pluginApi.withCurrentScreen(screen => { pluginApi.togglePanel(screen); }); } } ``` --- ## Configuration Changes ### manifest.json Updates ```diff { "id": "clipper", "name": "Clipper", - "version": "1.1.0", + "version": "1.3.1", - "minNoctaliaVersion": "4.1.1", + "minNoctaliaVersion": "4.1.2", "author": "blackbartblues", + "contributors": ["rscipher001"], "license": "MIT", + "repository": "https://github.com/blackbartblues/noctalia-clipper", - "description": "Clipboard manager with history and search", + "description": "Advanced clipboard manager with history, search, keyboard navigation, ToDo integration, and pinned items. Secure and modernized.", - "tags": ["Clipboard", "Utility"], + "tags": ["Clipboard", "Utility", "Bar", "Panel"], "entryPoints": { "main": "Main.qml", "barWidget": "BarWidget.qml", "panel": "Panel.qml", "settings": "Settings.qml" } } ``` --- ### New Settings Schema ```javascript pluginSettings: { // Existing (v1.1.0) position: "Bottom", // New in v1.2.0+ enableTodoIntegration: false, // New in v1.3.0+ cardColors: { "Text": { bg: "mOutline", separator: "mSurface", fg: "mOnSurface" }, "Image": { bg: "mTertiary", separator: "mSurface", fg: "mOnTertiary" }, // ... 5 more types }, customColors: { "Text": { bg: "#555555", separator: "#000000", fg: "#e9e4f0" }, "Image": { bg: "#e0b7c9", separator: "#000000", fg: "#20161f" }, // ... 5 more types } } ``` --- ## Testing Recommendations ### Critical Test Cases: 1. **Security:** Attempt clipboard ID injection: `12345; rm -rf /` 2. **Multi-Monitor:** Test toggle on non-primary screen 3. **Memory:** Monitor RSS after 100+ clipboard operations 4. **Pinned Items:** Test 20-item limit enforcement 5. **ToDo Integration:** Test with ToDo plugin disabled 6. **Image Caching:** Verify LRU eviction after 51 images 7. **Translation:** Test all 16 language files load correctly 8. **Color Customization:** Test custom RGB → save → reload ### Performance Benchmarks: - Initial load time: < 500ms - Image decode & cache: < 200ms per image - Panel open latency: < 100ms - Search filter response: < 50ms --- ## Migration Guide (v1.1.0 → v1.3.1) ### For Users: 1. **Backup existing clipboard data** (cliphist database) 2. Update plugin files 3. Restart Noctalia Shell 4. Configure ToDo integration in Settings (if using ToDo plugin) 5. Customize card colors if desired 6. Update keybinds (if using removed `addToTodo1-9` functions) ### For Developers: 1. **Remove ClipboardService.qml** - functionality in Main.qml 2. **Update IPC calls** - use new `addSelectionToTodo` instead of numbered functions 3. **Test pinned items** - new persistent storage in `pinned.json` 4. **Verify translations** - all strings now use `pluginApi?.tr()` 5. **Check multi-monitor** - potential issue with screen detection ### Breaking Changes: - ClipboardService.qml removed (use Main.qml functions) - IPC functions `addToTodo1-9` removed (use context menu or `addSelectionToTodo`) - Settings structure changed (new color customization fields) - Panel layout changed (fullscreen overlay vs anchored panel) --- ## Known Issues & Future Work ### Current Issues (v1.3.1): 1. **[HIGH]** Multi-monitor: IpcHandler uses `screens[0]` instead of `withCurrentScreen()` 2. **[MEDIUM]** Duplicated code: getTodoPages() in TodoPageSelector.qml and ClipboardCard.qml 3. **[MEDIUM]** Magic numbers: Hardcoded dimensions (250px, 300px, etc.) 4. **[LOW]** Panel.qml.backup tracked in git (should be in .gitignore) 5. **[LOW]** .gitignore file deleted (should be restored) ### Planned Features (v1.4.0+): - [ ] Configurable history limit (50-500 items) - [ ] Image preview size limit setting - [ ] Export/import pinned items - [ ] Clipboard sync across devices - [ ] Smart categorization using AI - [ ] Clipboard statistics dashboard --- ## Detailed File Changes ### Main.qml (+29/-672) **Major Refactoring:** - Integrated ClipboardService.qml functionality (removed service layer) - Added pinned items system with JSON persistence - Added ToDo integration functions - Added TodoPageSelector component integration - Simplified IPC interface (removed 12 functions, added 1) - Enhanced security with input validation - Comprehensive Process cleanup in Component.onDestruction **New Functions:** - `pinItem(cliphistId)` - Pin clipboard item to persistent storage - `unpinItem(pinnedId)` - Remove from pinned items - `copyPinnedToClipboard(pinnedId)` - Copy pinned item (text or image) - `savePinnedFile()` - Persist pinned items to JSON - `showTodoPageSelector(text)` - Show full-screen page selector - `handleTodoPageSelected(pageId, pageName)` - Handle page selection - `getSelectionAndShowSelector()` - Get primary selection → show selector **Removed Functions:** - All ClipboardService integration code - `addToTodo1()` through `addToTodo9()` - `addToTodo(pageId: int)` - `addTextToTodo(text: string, pageId: int)` **New Properties:** - `pinnedItems: []` - Array of pinned item objects - `pinnedRevision: 0` - Reactive update counter - `imageCache: {}` - LRU cache object - `imageCacheOrder: []` - LRU eviction tracking - `imageCacheRevision: 0` - Cache update counter - `maxPinnedItems: 20` - Hard limit constant - `maxImageCacheSize: 50` - Cache size limit **New Components:** - `FileView { id: pinnedFile }` - Monitor pinned.json changes - `Process { id: decodeProc }` - Decode content for pinning - `Process { id: copyPinnedImageProc }` - Copy pinned images - `Process { id: copyPinnedTextProc }` - Copy pinned text - `Process { id: getSelectionForSelectorProcess }` - Get selection for ToDo - `Variants { TodoPageSelector }` - Todo page selector instances --- ### Panel.qml (+190/-193) **Architecture Overhaul:** - Changed from anchored panel to fullscreen transparent overlay - Added pinned panel (left side, vertical, 300px width) - Redesigned clipboard panel (bottom, horizontal, 300px height) - Removed all ListView animations (populate, add, remove, displaced, move) - Removed internal IPC call to `addTextToTodo` (now direct function call) - Added click-outside-to-close functionality **Layout Changes:** ```qml // BEFORE (v1.1.0) NPanel { anchors.bottom: true // Single panel, bottom-anchored } // AFTER (v1.3.1) Item { // Fullscreen transparent MouseArea { /* click to close */ } Rectangle { id: clipboardPanel /* bottom */ } Rectangle { id: pinnedPanel /* left */ } } ``` **New Features:** - Pinned items ListView with delete functionality - ToDo context menu integration (via ClipboardCard) - Keyboard navigation enhancements - Search input with escape-to-clear **Removed:** - `todoIpcProcess` - no longer needed (direct calls) - Button to toggle pinned panel (always visible) - All transition animations **Translation Updates:** - All UI strings use `pluginApi?.tr()` with fallbacks - 30+ translation keys added --- ### Settings.qml (+467/-254) **Complete Redesign:** - Added card color customization system - Added live preview component - Restructured into logical sections - All strings translated with `pluginApi?.tr()` **New Sections:** 1. **Integrations** (unchanged structure, added translations) 2. **Pinned Items** (title only - removed controls) 3. **Appearance** (NEW - 200+ lines) - Card type selector - Live preview (250x220px rectangle) - Background color selector + RGB picker - Separator color selector + RGB picker - Foreground color selector + RGB picker - Reset to defaults button **New Properties:** - `selectedCardType: "Text"` - Currently editing type - `cardColors: {}` - Per-type color schemes (Material Design keys) - `customColors: {}` - Per-type RGB values for custom colors - `defaultCardColors: {}` - Reset reference - `colorOptions: []` - Dropdown options (15 colors + custom) **New Functions:** - `getColorValue(colorKey, cardType, colorType)` - Resolve color - `getPreviewBg()`, `getPreviewSeparator()`, `getPreviewFg()` - Preview helpers **Removed:** - `autoOpenPinnedPanel` property and toggle - "Clear All Pinned Items" button --- ### BarWidget.qml (+15/-33) **Translation System Migration:** - Replaced `I18n.tr()` with `pluginApi?.tr()` - Updated translation key structure - Maintained context menu functionality **Changes:** ```diff - tooltipText: I18n.tr("clipboard.tooltip-title", "Clipboard History") + tooltipText: pluginApi?.tr("clipper.bar.tooltip") || "Clipboard History" - "label": I18n.tr("clipboard.toggle-clipper", "Toggle Clipper"), + "label": pluginApi?.tr("clipper.context.toggle") || "Toggle Clipper", - "label": I18n.tr("clipboard.open-settings", "Open Settings"), + "label": pluginApi?.tr("clipper.context.settings") || "Open Settings", ``` --- ### ClipboardCard.qml (+13/-103) **Simplified & Enhanced:** - Added ToDo page selector context menu - Added tooltip translations - Removed ClipboardService dependency - Simplified signal handlers **New Imports:** ```diff + import qs.Services.Noctalia + import qs.Services.UI ``` **New Features:** - `getTodoPages()` - Fetch ToDo pages via PluginService - `buildTodoMenuModel()` - Generate context menu model - `NPopupContextMenu { id: todoContextMenu }` - Page selection menu - ToDo button now shows context menu instead of direct IPC call **Changes:** ```diff // BEFORE onClicked: root.addToTodoClicked() // Signal to parent // AFTER onClicked: { todoContextMenu.model = root.buildTodoMenuModel(); PanelService.showContextMenu(todoContextMenu, todoButton, null); } ``` **Translations:** - Added `pluginApi?.tr()` for tooltips: add-todo, pin, delete --- ### TodoPageSelector.qml (NEW FILE - 134 lines) **Full-Screen ToDo Page Selector:** - Transparent fullscreen overlay with cursor tracking - Context menu positioned at cursor location - Fetches ToDo pages dynamically from ToDo plugin - Keyboard support (ESC to cancel) - Click-outside-to-close functionality **Key Features:** ```qml WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand function show(text) { selectedText = text; contextMenu.model = buildMenuModel(); visible = true; showMenuTimer.start(); // Wait for compositor hover events } function getTodoPages() { const todoApi = PluginService.getPluginAPI("todo"); return todoApi?.pluginSettings?.pages || [{ id: 0, name: "General" }]; } ``` **Signals:** - `pageSelected(int pageId, string pageName)` - User selected a page - `cancelled()` - User cancelled selection --- ## Statistics ### Code Metrics: - **Total Lines Changed:** 1,151 (709 additions, 442 deletions) - **Files Modified:** 6 - **Files Added:** 3 (TodoPageSelector.qml, CHANGELOG.md, pinned.json) - **Files Removed:** 1 (ClipboardService.qml) - **Functions Added:** 15+ - **Functions Removed:** 20+ - **Translation Keys Added:** 50+ - **IPC Functions Removed:** 12 - **IPC Functions Added:** 1 ### Security Improvements: - **Command Injection Fixes:** 6 functions - **Input Validation Added:** 4 functions - **Memory Leak Fixes:** 11 Process objects - **Size Limit Enforcements:** 4 limits ### Feature Additions: - **Major Features:** 3 (Pinned Items, ToDo Integration, Color Customization) - **UI Components:** 8 (TodoPageSelector, PinnedPanel, ColorPickers, etc.) - **Settings Options:** 15+ (color schemes, integration toggles) --- ## Compatibility Matrix | Feature | v1.1.0 | v1.3.1 | Breaking? | |---------|--------|--------|-----------| | Basic clipboard history | ✅ | ✅ | No | | Search & filter | ✅ | ✅ | No | | Keyboard navigation | ✅ | ✅ | No | | Pinned items | ❌ | ✅ | No | | ToDo integration | ❌ | ✅ | No | | Color customization | ❌ | ✅ | No | | ClipboardService.qml | ✅ | ❌ | **Yes** | | IPC addToTodo1-9 | ❌ | ❌ | No (never existed) | | I18n.tr() translations | ✅ | ❌ | **Yes** (use pluginApi?.tr()) | | Bottom-anchored panel | ✅ | ❌ | **Yes** (fullscreen overlay) | | Animation transitions | ✅ | ❌ | **Yes** (instant updates) | --- ## Recommendations ### For Immediate Deployment (v1.3.1): ✅ **Safe to deploy** with the following caveats: 1. Test multi-monitor setup thoroughly (potential screen detection issue) 2. Document IPC changes for users with custom keybinds 3. Backup existing clipboard data before migration 4. Restore `.gitignore` file to prevent tracking backup files ### Priority Fixes for v1.3.2: 1. **[HIGH]** Fix IpcHandler screen detection (use `withCurrentScreen()`) 2. **[HIGH]** Add Component.onDestruction to Panel, Settings, ClipboardCard, BarWidget 3. **[MEDIUM]** Refactor duplicated getTodoPages() function 4. **[MEDIUM]** Replace magic numbers with Style constants 5. **[LOW]** Remove Panel.qml.backup from repository 6. **[LOW]** Restore .gitignore file ### Long-term Improvements (v1.4.0+): - History limit configuration (requested feature) - Pinned items export/import - Clipboard statistics dashboard - AI-powered categorization - Cross-device sync --- ## Conclusion The Clipper plugin has evolved significantly from v1.1.0 to v1.3.1: **Strengths:** - ✅ Massive feature additions (pinned items, ToDo integration, color customization) - ✅ Critical security vulnerabilities fixed (command injection prevention) - ✅ Better architecture (removed unnecessary service layer) - ✅ Comprehensive translation support (16 languages) - ✅ Improved UX (context menus, live previews, instant updates) **Weaknesses:** - ⚠️ Multi-monitor support regression (fixable) - ⚠️ Code duplication in some areas - ⚠️ Missing cleanup handlers in some components - ⚠️ Backup files tracked in version control **Overall Assessment:** **RECOMMEND DEPLOYMENT** with priority fixes scheduled for v1.3.2. The new features significantly enhance usability, and the security fixes are critical. The identified issues are minor and can be addressed in a follow-up release. --- **Generated:** 2026-02-04 **Comparison:** GitHub noctalia-plugins/clipper v1.1.0 ↔ Local v1.3.1 **Total Analysis Time:** ~15 minutes **Review Status:** ✅ APPROVED with recommendations --- ## Translation Files Update (v1.3.1) **Complete i18n Implementation:** - All 16 language files updated to new structure - Unified `clipper.section.key` format - 42 translation keys per language - All files synchronized with en.json structure **Supported Languages:** 1. English (en.json) 2. German (de.json) 3. Spanish (es.json) 4. French (fr.json) 5. Hungarian (hu.json) 6. Japanese (ja.json) 7. Korean (ko-KR.json) 8. Dutch (nl.json) 9. Polish (pl.json) 10. Portuguese (pt.json) 11. Russian (ru.json) 12. Swedish (sv.json) 13. Turkish (tr.json) 14. Ukrainian (uk-UA.json) 15. Chinese Simplified (zh-CN.json) 16. Chinese Traditional (zh-TW.json) **Migration from old structure:** - Old: `panel.test` → New: `clipper.panel.title` - Old: `bar_widget.tooltip` → New: `clipper.bar.tooltip` - Consistent hyphenated keys (no underscores)