Skip to content

UX: Implement undo/recovery for delete operations #76

@sfloess

Description

@sfloess

Overview

Add ability to undo accidental deletions within the same session, or at minimum show a "Recently Deleted" view for reference.

Current State

  • ✅ Dry-run mode for preview
  • ✅ Confirmation prompts
  • ❌ No undo after deletion
  • ❌ No recovery mechanism
  • ❌ No deletion history

UX Score Impact

Current: UX A (94/100)
With this + #76 + #77: UX A+ (99/100)

Problem

Current deletion workflow:

1. User searches and filters
2. User deletes components
3. [POINT OF NO RETURN]
4. User realizes mistake → TOO LATE

Even with dry-run and confirmation, mistakes happen:

  • Typo in regex after dry-run was correct
  • Wrong repository selected
  • Confirmation prompt muscle memory

Proposed Solutions

Option 1: Session-Based Undo (Realistic)

Limitation: Can only track deletions, NOT restore artifacts (would require re-uploading to Nexus)

Implementation:

Track deletions in-session:

public class DeletionHistory {
    private final Stack<DeletedComponent> history = new Stack<>();
    private static final int MAX_HISTORY = 1000;
    
    public record DeletedComponent(
        String id,
        String path,
        long fileSize,
        String repository,
        Instant deletedAt
    ) {}
    
    public void recordDeletion(String id, String path, long fileSize, String repository) {
        if (history.size() >= MAX_HISTORY) {
            history.remove(0);  // Remove oldest
        }
        history.push(new DeletedComponent(id, path, fileSize, repository, Instant.now()));
    }
    
    public List<DeletedComponent> getRecentDeletions(int limit) {
        return history.stream()
            .sorted(Comparator.comparing(DeletedComponent::deletedAt).reversed())
            .limit(limit)
            .toList();
    }
    
    public void clear() {
        history.clear();
    }
}

CLI Usage:

# After deleting components
jnexus delete my-repo --regex '.*SNAPSHOT.*'
# Deleted 150 components

# Oh no, wrong regex!
jnexus history
# Shows list of deleted components from this session

# Copy-paste for re-upload (manual)

GUI Usage (Swing):

JMenu editMenu = new JMenu("Edit");
JMenuItem historyItem = new JMenuItem("Recent Deletions...");
historyItem.addActionListener(e -> showDeletionHistory());

void showDeletionHistory() {
    List<DeletedComponent> recent = deletionHistory.getRecentDeletions(100);
    
    JDialog dialog = new JDialog(this, "Recently Deleted Components");
    JTable table = new JTable(new DeletionHistoryTableModel(recent));
    
    // Show: ID, Path, Size, Repository, Deleted At
    dialog.add(new JScrollPane(table));
    dialog.setSize(800, 600);
    dialog.setVisible(true);
}

Benefits:

  • Helps identify what was deleted
  • Provides reference for manual re-upload
  • Session-scoped (no persistence complexity)

Limitations:

  • NOT a true undo (can't restore)
  • Lost on program exit
  • Informational only

Option 2: Soft Delete with Undo Window (Advanced)

Concept: Don't actually delete from Nexus immediately. Instead:

  1. Mark for deletion
  2. Wait 5 minutes (configurable)
  3. Undo available during window
  4. Auto-delete after window expires

Problem: Nexus API doesn't support this. Would require:

  • Custom Nexus plugin
  • Database to track "pending deletions"
  • Background job to execute deletions

Verdict: Too complex, not worth it.

Option 3: Export Deletion List for Recovery (Practical)

Before deleting, export list to file:

jnexus delete my-repo --regex '.*SNAPSHOT.*' --export-before-delete deleted-2026-05-28.json

# File contains:
# {
#   "deletedAt": "2026-05-28T10:30:00Z",
#   "repository": "my-repo",
#   "criteria": {"regex": ".*SNAPSHOT.*"},
#   "components": [
#     {"id": "abc123", "path": "com/example/artifact/1.0-SNAPSHOT/...", "fileSize": 12345},
#     ...
#   ]
# }

User can review file later and re-upload if needed.

Recommended Implementation

Combine Option 1 + Option 3:

  1. Session History (Always On):

    • Track all deletions in memory
    • Show via "Recent Deletions" dialog/command
    • Lost on exit (acceptable)
  2. Export Before Delete (Opt-In):

    • Add flag
    • Save JSON before any deletion
    • User can reference later
  3. Auto-Export Large Deletions (Safety):

    • If deleting >100 components, auto-export
    • Prompt: "Deleting 150 components. Export list to file? [Y/n]"

Configuration

# Auto-export threshold (default: 100)
nexus.delete.auto.export.threshold=100

# Deletion history size (default: 1000)
nexus.delete.history.max=1000

# Auto-export directory (default: ~/.flossware/nexus/deleted/)
nexus.delete.export.directory=~/.flossware/nexus/deleted/

UI Changes

CLI

# New commands
jnexus history                          # Show recent deletions
jnexus history --export history.json    # Export to file

# Enhanced delete
jnexus delete my-repo --regex '.*' --export-before-delete backup.json

Swing GUI

Menu Bar:
  Edit
    → Recent Deletions...     [Shows dialog]
    → Clear Deletion History  [Clears session history]

Delete Confirmation Dialog:
  "Delete 150 components?"
  [✓] Export list before deleting
  [ Export Location: ~/.flossware/nexus/deleted/2026-05-28-150-components.json ]
  
  [Cancel] [Delete]

Android

Settings Tab:
  [x] Auto-export large deletions (>100 components)
  
Delete Confirmation:
  "Delete 150 components?"
  [✓] Save deletion list
  
  [Cancel] [Delete]
  
After deletion:
  Snackbar: "150 components deleted. List saved to Downloads/jnexus-deleted.json" [UNDO - opens file]

Export File Format

{
  "exportedAt": "2026-05-28T10:30:00Z",
  "repository": "maven-releases",
  "criteria": {
    "regex": ".*SNAPSHOT.*",
    "minSize": 1000000,
    "createdBefore": "2025-01-01"
  },
  "summary": {
    "totalComponents": 150,
    "totalSize": 1234567890
  },
  "components": [
    {
      "id": "abc123",
      "path": "com/example/artifact/1.0-SNAPSHOT/artifact-1.0-20250101.jar",
      "fileSize": 12345,
      "contentType": "application/java-archive",
      "createdDate": "2025-01-01T00:00:00Z",
      "checksum": "sha1:abcdef123456"
    }
  ]
}

Testing

@Test
void testDeletionHistoryRecordsDeletes() {
    DeletionHistory history = new DeletionHistory();
    history.recordDeletion("id1", "path1.jar", 1000, "repo1");
    history.recordDeletion("id2", "path2.jar", 2000, "repo1");
    
    List<DeletedComponent> recent = history.getRecentDeletions(10);
    assertEquals(2, recent.size());
    assertEquals("id2", recent.get(0).id());  // Most recent first
}

@Test
void testAutoExportLargeDeletions() {
    // Mock user confirms export
    // Verify file created
    // Verify JSON contains all components
}

Documentation

Add section to user guides:

## Recovering from Accidental Deletions

JNexus cannot undo deletions from Nexus (artifacts are permanently removed), but it provides tools to help:

### Recent Deletions History
View components deleted in current session:
- **CLI:** 
- **GUI:** Edit → Recent Deletions

### Export Before Delete
Save a list of deleted components for reference:
```bash
jnexus delete my-repo --regex '.*' --export-before-delete backup.json

Auto-Export Large Deletions

When deleting >100 components, JNexus prompts to export the list automatically.

Re-Uploading

To restore deleted artifacts:

  1. Review exported JSON file
  2. Locate artifact files on disk or in backups
  3. Re-upload to Nexus manually or via script

## Acceptance Criteria

- [ ] DeletionHistory class implemented
- [ ] CLI  command added
- [ ] GUI "Recent Deletions" dialog added
- [ ]  flag added
- [ ] Auto-export threshold configurable
- [ ] JSON export format documented
- [ ] Tests for history tracking
- [ ] Tests for export functionality
- [ ] User guide section added

## Priority
Low - Nice safety feature but dry-run already prevents most accidents

## Related
- Dry-run mode (existing - primary safety mechanism)
- #76: Bulk operations (would benefit from export)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions