Files
dotfiles/noctalia/plugins/clipper/CHANGELOG.md
T
Domipoke 955801c666 noctalia
2026-06-25 19:58:23 +02:00

41 KiB
Raw Blame History

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 23 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 (<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 (4003840 px, step 50) — hidden in fullscreen mode
  • NSpinBox for panel height (02160 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:

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:

{
  "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):

{
  "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:

qs ipc call plugin:clipper pinClipboardItem <id>
qs ipc call plugin:clipper unpinItem <pinnedId>
qs ipc call plugin:clipper copyPinned <pinnedId>

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:

qs ipc call plugin:clipper addSelectionToTodo

Removed IPC Commands (replaced by context menu):

# 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 <pageId>
qs ipc call plugin:clipper addTextToTodo <text> <pageId>

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:

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:

{
  "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:

// 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:

// 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:

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:

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:

// Vulnerable to shell injection
Quickshell.execDetached(["sh", "-c", "cliphist decode " + id + " | wl-copy"])

Local v1.3.1 Fix:

// 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:

// Panel.qml calling its own plugin via IPC (anti-pattern)
Process {
    command: ["qs", "ipc", "call", "plugin:clipper", "addTextToTodo", ...]
}

Local v1.3.1 Fix:

// 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:

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:

// 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:

function toggle() {
    if (pluginApi) {
        pluginApi.withCurrentScreen(screen => {
            pluginApi.togglePanel(screen);
        });
    }
}

Configuration Changes

manifest.json Updates

{
  "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

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:

// 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:

- 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:

+ 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:

// 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:

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)