Skip to content

feat(terminal): redact copied command blocks - #222

Merged
shihuili1218 merged 2 commits into
mainfrom
feat/command-block-copy-redaction
Aug 6, 2026
Merged

shihuili1218 merged 2 commits into
mainfrom
feat/command-block-copy-redaction

Conversation

@shihuili1218

@shihuili1218 shihuili1218 commented Aug 6, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • keep Enter-defined command block boundaries and recognize common shell prompts before copying
  • apply one independent redaction policy to command-block text and image copies, with editable Prompt replacement and regex rules
  • seed command-block rules from the existing AI redaction rules, persist them independently, and sync them with legacy payload compatibility

Compatibility

  • Desktop GUI: copy-as-text, copy-as-image, and command-block redaction settings are supported
  • Mobile GUI: settings remain available; no new copy entry is added because the existing command-block action depends on desktop right-click
  • CLI: copy rendering is N/A; shared local/remote config export and import include the new category
  • Headless / JetBrains: uses the shared frontend behavior and includes command-block rule CRUD in the server dispatcher

New exports write command_block_redaction; imports also accept the legacy command_block_image_redaction field. AI handoff keeps its existing independent redaction path.

Tests

  • npm test — 621 passed
  • npm run build
  • cargo fmt --manifest-path src-tauri/Cargo.toml --check
  • cargo test --manifest-path src-tauri/Cargo.toml — 702 passed
  • cargo check --manifest-path src-tauri/Cargo.toml --features cli --bin rssh-cli
  • cargo check --manifest-path src-tauri/Cargo.toml --features server --bin rssh-server

Summary by CodeRabbit

  • New Features
    • Added command-block redaction settings with customizable prompt replacement text and regex-based rules.
    • Redaction now applies when copying command-block text or images.
    • Added rule creation, editing, deletion, validation, and support for multiple shell prompt formats.
    • Added synchronization support for command-block redaction preferences and rules.
  • Bug Fixes
    • Invalid or zero-width patterns are rejected before saving and fail safely during processing.
  • Localization
    • Added English and Chinese translations for redaction and synchronization settings.

Copilot AI lite review requested due to automatic review settings August 6, 2026 05:53
@coderabbitai

coderabbitai Bot commented Aug 6, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e57d15ec-24c1-4be0-840f-b9e7fe43a1be

📥 Commits

Reviewing files that changed from the base of the PR and between 5241cd6 and c98dbf8.

📒 Files selected for processing (6)
  • src/lib/stores/app.svelte.ts
  • src/lib/terminal/block-to-image.test.ts
  • src/lib/terminal/block-to-image.ts
  • src/lib/terminal/command-block-redaction.test.ts
  • src/lib/terminal/prompt.test.ts
  • src/lib/terminal/prompt.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/lib/terminal/command-block-redaction.test.ts
  • src/lib/stores/app.svelte.ts
  • src/lib/terminal/prompt.ts
  • src/lib/terminal/prompt.test.ts
  • src/lib/terminal/block-to-image.ts

📝 Walkthrough

Walkthrough

This change adds command-block redaction storage, validation, APIs, synchronization, settings, and clipboard processing for text and images. It also adds prompt detection, soft-wrap handling, migration coverage, and localized configuration messages.

Changes

Command-block redaction

Layer / File(s) Summary
Storage and pattern validation
src-tauri/src/db/..., src-tauri/src/db/schema.rs, src-tauri/src/redaction.rs, src-tauri/src/ai/redact_rules.rs
Schema migration 26 adds command-block redaction storage and seeds existing settings. Database helpers manage rules. Shared validation rejects invalid and zero-width patterns.
Backend commands and synchronization
src-tauri/src/commands/..., src-tauri/src/lib.rs, src-tauri/src/server.rs, src-tauri/src/sync/config.rs
Tauri and WebSocket handlers expose rule CRUD. Sync export and import support command-block settings and rules, including legacy-key compatibility and validation errors.
Settings state and configuration UI
src/lib/stores/app.svelte.ts, src/lib/components/CommandBlockSettings.svelte, src/lib/components/SyncScreen.svelte, src/lib/i18n/locales/*
The store manages settings and rules. The UI supports prompt redaction, rule editing, validation, deletion confirmation, synchronization preferences, and localized messages.
Terminal copy redaction
src/lib/terminal/*, src/lib/components/TerminalPane.svelte
Text extraction preserves block boundaries. Prompt detection and regex rules redact text. Image rendering applies redaction while preserving soft-wrap state, styling, and character widths. Clipboard paths load the policy before copying.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TerminalPane
  participant appStore
  participant commandBlockRedaction
  participant ImageRenderer
  participant Clipboard
  TerminalPane->>appStore: load command-block redaction settings
  appStore-->>TerminalPane: return settings and rules
  TerminalPane->>commandBlockRedaction: redact block text
  commandBlockRedaction-->>TerminalPane: return redacted text
  TerminalPane->>ImageRenderer: render blocks with redaction
  ImageRenderer-->>TerminalPane: return redacted image blob
  TerminalPane->>Clipboard: write redacted output
Loading

Possibly related PRs

  • shihuili1218/rssh#5: Introduced command-block functionality extended here with redaction state and behavior.
  • shihuili1218/rssh#24: Added command-block copy extraction and rendering flows modified here for redaction.
  • shihuili1218/rssh#84: Added redaction-rule infrastructure extended here for command-block redaction.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.72% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: redaction of copied terminal command blocks.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/command-block-copy-redaction

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new “command-block copy redaction” feature for terminal copy-as-text and copy-as-image flows, including prompt recognition, per-block redaction rules, UI controls, persistence/migration, and sync import/export (with legacy payload compatibility).

Changes:

  • Introduce shell prompt detection and apply prompt replacement + regex-based redaction per command block for text and image copying.
  • Add UI and frontend store plumbing to manage prompt/redaction settings and CRUD redaction rules.
  • Persist/sync the new redaction category via DB schema v26, new command APIs (Tauri + headless server dispatcher), and sync payload import/export (including legacy import key support).

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/lib/terminal/prompt.ts Add prompt-recognition heuristics for common shells/prompts.
src/lib/terminal/prompt.test.ts Add unit tests for prompt detection.
src/lib/terminal/command-block-redaction.ts Implement rule compilation and per-block text redaction (prompt + regex rules).
src/lib/terminal/command-block-redaction.test.ts Add unit tests for command-block text redaction behavior and fail-closed semantics.
src/lib/terminal/block-to-image.ts Add optional command-block redaction to image rendering path; implement row/cell-level redaction.
src/lib/terminal/block-to-image.test.ts Add tests for image-row redaction behavior.
src/lib/terminal/block-content.ts Add extractBlockTexts to preserve per-block boundaries for copy-time policies.
src/lib/terminal/block-content.test.ts Add coverage for multi-block boundary extraction.
src/lib/stores/app.svelte.ts Add store state + loading/saving APIs for command-block copy redaction settings and rule CRUD.
src/lib/i18n/locales/zh.ts Add Chinese strings for the new settings UI and sync category label.
src/lib/i18n/locales/en.ts Add English strings for the new settings UI and sync category label.
src/lib/components/TerminalPane.svelte Apply command-block redaction during “copy as text” and “copy as image”.
src/lib/components/SyncScreen.svelte Add sync category toggle for command-block redaction rules.
src/lib/components/CommandBlockSettings.svelte Add settings UI to edit prompt redaction + redaction rules with CRUD actions.
src-tauri/src/sync/config.rs Add import/export + sync prefs support for command_block_redaction (and legacy import key).
src-tauri/src/server.rs Expose command-block rule CRUD in the headless/JetBrains dispatcher.
src-tauri/src/redaction.rs Introduce shared regex validation (invalid + zero-width) for user-managed redaction rules.
src-tauri/src/lib.rs Wire up new module + register new Tauri commands.
src-tauri/src/db/schema.rs Bump schema to v26; add command_block_redact_rules + seed/snapshot migration logic.
src-tauri/src/db/mod.rs Export the new command_block_redact_rule DB module.
src-tauri/src/db/command_block_redact_rule.rs Add DB persistence layer for command-block redaction rules.
src-tauri/src/commands/sync.rs Ensure sync command tests accept the new payload category.
src-tauri/src/commands/mod.rs Register the new command-block command module.
src-tauri/src/commands/command_block.rs Add list/save/delete APIs + Tauri commands for command-block rule CRUD.
src-tauri/src/ai/redact_rules.rs Reuse shared regex validation for AI redaction rule saving.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +3 to +6
export interface CommandBlockRedactionRule {
pattern: string;
replacement: string;
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified. The redaction engine intentionally accepts the minimal pattern/replacement shape; persisted rules extend it with id. I removed persistence-only ids from the engine test literals and also pass explicit fields to the Tauri invoke so the feature adds no TypeScript errors.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (6)
src/lib/terminal/block-to-image.ts (2)

175-180: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

applyRules returns the caller's cell objects when the rule list is empty.

reduce over an empty rules array returns the seed. The seed [...cells] is a new array, but it holds the original ImageCell references. Every other path in this file returns fresh objects. A caller that mutates a returned row then mutates the input row.

No current caller does this, so this is defensive only.

♻️ Proposed change
-  return rules.reduce<ImageCell[]>((current, rule) => applyRule(current, rule), [...cells]);
+  return rules.reduce<ImageCell[]>(
+    (current, rule) => applyRule(current, rule),
+    cells.map((cell) => ({ ...cell })),
+  );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/terminal/block-to-image.ts` around lines 175 - 180, Update applyRules
to return fresh ImageCell objects even when rules is empty, not merely a
shallow-copied array containing the original references. Preserve the existing
rule-application behavior while ensuring every returned cell is cloned
independently from the input cells.

91-113: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

cellsFromText splits grapheme clusters into separate cells.

Array.from iterates code points, not grapheme clusters. A replacement string that contains a combining mark, a variation selector, or a ZWJ emoji sequence becomes several cells. graphemeWidth then assigns a width to each part, so a ZWJ family emoji renders as several wide cells instead of one. xterm stores such a cluster in a single cell.

This affects only the replacement text, which the user controls. If you want to keep it simple, document the limitation next to graphemeWidth. Otherwise use Intl.Segmenter with granularity: "grapheme".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/terminal/block-to-image.ts` around lines 91 - 113, Update
cellsFromText to segment replacement text by grapheme clusters using
Intl.Segmenter with granularity "grapheme" rather than Array.from code-point
iteration, then assign one cell and one graphemeWidth result per segment. Keep
the existing style and character storage behavior, and preserve the current
width fallback or document the limitation beside graphemeWidth if segmentation
cannot be supported.
src/lib/components/CommandBlockSettings.svelte (1)

116-134: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider validating the pattern as a regular expression before saving.

saveRule rejects only empty strings. A syntactically invalid pattern is accepted here and then rejected later. Because compileCommandBlockRedaction fails closed, one bad rule blocks every command-block copy until the user finds and fixes it. A local new RegExp(pattern) check gives immediate feedback at the point of entry.

♻️ Proposed change
     if (editingRule.pattern === "" || editingRule.replacement === "") {
       redactNote = t("settings.shell.command_block_redact_error_empty");
       return;
     }
+    try {
+      new RegExp(editingRule.pattern.replace(/^\(\?[ims]+\)/, ""));
+    } catch (error) {
+      redactNote = t("settings.shell.command_block_redact_error_save", { error: errMsg(error) });
+      return;
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/CommandBlockSettings.svelte` around lines 116 - 134,
Update saveRule to validate editingRule.pattern with new RegExp before setting
savingRule or calling app.saveCommandBlockRedactRule; catch syntax errors, set
the existing redactNote using the appropriate validation message, and return
while preserving the current empty-value and successful-save flows.
src/lib/components/TerminalPane.svelte (1)

411-419: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Confirm the silent return on an empty redacted result is intended.

If a rule redacts a block to an empty string, copyBlocksAsText returns before writeClipboard and before clearBlockSelection. The user sees no clipboard change and no message. The old clipboard content stays in place, which is the safe outcome, but the copy action appears to do nothing.

Consider a short toast so the user knows the copy was suppressed rather than failed silently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/TerminalPane.svelte` around lines 411 - 419, Update
copyBlocksAsText around the empty redacted text check so suppressed copies
provide user feedback before returning. Add a brief toast indicating that
copying was skipped because the selected content was redacted, while preserving
the existing behavior of avoiding writeClipboard and clearBlockSelection.
src/lib/terminal/command-block-redaction.test.ts (1)

48-54: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for the zero-width rejection contract.

redactionRuleMatches throws redact_zero_width_pattern for a pattern that matches an empty string. That guard prevents an infinite loop, and no test covers it. A pattern such as a* is valid JavaScript regex, so it passes compilation and reaches the guard.

💚 Proposed test
+  it("rejects a zero-width pattern instead of looping", () => {
+    expect(() => redactCommandBlockTexts(["$ id"], {
+      promptEnabled: true,
+      promptReplacement: "anonymous@rssh",
+      rules: [{ pattern: "x*", replacement: "<X>" }],
+    })).toThrow(/redact_zero_width_pattern/);
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/terminal/command-block-redaction.test.ts` around lines 48 - 54, Add a
test alongside the existing invalid-rule test in command-block-redaction.test.ts
that calls redactCommandBlockTexts with a valid zero-width-capable rule such as
pattern “a*”, then asserts it throws the redact_zero_width_pattern error. Keep
the test focused on the zero-width rejection contract and preserve the current
rule configuration style.
src/lib/terminal/command-block-redaction.ts (1)

30-44: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Inline-flag translation accepts only a prefix group and drops nothing silently.

The translation handles the common (?ims) prefix. Two Rust forms are not handled: a negated group such as (?-i) and a mid-pattern group such as foo(?i)bar. Both remain in source and then fail new RegExp, which fails closed. That behavior is correct, but the failure message reaches the user as a generic regex error at copy time.

Consider rejecting these forms in the shared backend validator so the user sees the error while editing the rule, not while copying.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/terminal/command-block-redaction.ts` around lines 30 - 44, Update the
shared backend validator for CommandBlockRedactionRule patterns to reject
unsupported Rust inline-flag forms, including negated groups such as (?-i) and
non-prefix groups such as foo(?i)bar, before compileRule is called. Reuse the
validator’s existing validation-error mechanism so users receive the actionable
error while editing, while preserving compileRule’s current supported-prefix
translation and fail-closed behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/stores/app.svelte.ts`:
- Around line 825-841: Remove the `_commandBlockRedactionLoaded = true`
assignments from `setCommandBlockPromptRedactEnabled` and
`setCommandBlockPromptReplacement`; these setters should update persisted and
in-memory values without claiming the cache was loaded. Set
`_commandBlockRedactionLoaded` only after `loadCommandBlockRedaction()`
successfully reads the backend, unless the setters explicitly merge into an
already loaded snapshot.

In `@src/lib/terminal/block-to-image.test.ts`:
- Around line 345-348: Remove the id properties from the redaction rule objects
in the test rules array, keeping only pattern and replacement so they conform to
CommandBlockRedactionRule and remain compatible with compileRule.

In `@src/lib/terminal/block-to-image.ts`:
- Around line 182-197: Update ImageRow and extractImageRows to preserve each
buffer line’s isWrapped state, then revise redactImageRows to combine
continuation rows into logical text before calling commandBlockPromptEnd. Apply
the prompt replacement to the originating prompt row while preserving the
remaining cells and row structure, and add a regression test covering a prompt
split across an isWrapped continuation row.

In `@src/lib/terminal/prompt.ts`:
- Around line 28-31: Update the prompt-detection regexes in the terminal prompt
patterns so their leading prefix quantifiers are lazy, ensuring matching stops
at the first prompt marker rather than the last. Decide and preserve the
intended behavior for multi-segment Powerline prompts, then add coverage for
that case and for command text containing prompt markers.

---

Nitpick comments:
In `@src/lib/components/CommandBlockSettings.svelte`:
- Around line 116-134: Update saveRule to validate editingRule.pattern with new
RegExp before setting savingRule or calling app.saveCommandBlockRedactRule;
catch syntax errors, set the existing redactNote using the appropriate
validation message, and return while preserving the current empty-value and
successful-save flows.

In `@src/lib/components/TerminalPane.svelte`:
- Around line 411-419: Update copyBlocksAsText around the empty redacted text
check so suppressed copies provide user feedback before returning. Add a brief
toast indicating that copying was skipped because the selected content was
redacted, while preserving the existing behavior of avoiding writeClipboard and
clearBlockSelection.

In `@src/lib/terminal/block-to-image.ts`:
- Around line 175-180: Update applyRules to return fresh ImageCell objects even
when rules is empty, not merely a shallow-copied array containing the original
references. Preserve the existing rule-application behavior while ensuring every
returned cell is cloned independently from the input cells.
- Around line 91-113: Update cellsFromText to segment replacement text by
grapheme clusters using Intl.Segmenter with granularity "grapheme" rather than
Array.from code-point iteration, then assign one cell and one graphemeWidth
result per segment. Keep the existing style and character storage behavior, and
preserve the current width fallback or document the limitation beside
graphemeWidth if segmentation cannot be supported.

In `@src/lib/terminal/command-block-redaction.test.ts`:
- Around line 48-54: Add a test alongside the existing invalid-rule test in
command-block-redaction.test.ts that calls redactCommandBlockTexts with a valid
zero-width-capable rule such as pattern “a*”, then asserts it throws the
redact_zero_width_pattern error. Keep the test focused on the zero-width
rejection contract and preserve the current rule configuration style.

In `@src/lib/terminal/command-block-redaction.ts`:
- Around line 30-44: Update the shared backend validator for
CommandBlockRedactionRule patterns to reject unsupported Rust inline-flag forms,
including negated groups such as (?-i) and non-prefix groups such as foo(?i)bar,
before compileRule is called. Reuse the validator’s existing validation-error
mechanism so users receive the actionable error while editing, while preserving
compileRule’s current supported-prefix translation and fail-closed behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1dd65558-3642-4b68-bb57-381b106a6ae3

📥 Commits

Reviewing files that changed from the base of the PR and between 53c55da and 5241cd6.

📒 Files selected for processing (25)
  • src-tauri/src/ai/redact_rules.rs
  • src-tauri/src/commands/command_block.rs
  • src-tauri/src/commands/mod.rs
  • src-tauri/src/commands/sync.rs
  • src-tauri/src/db/command_block_redact_rule.rs
  • src-tauri/src/db/mod.rs
  • src-tauri/src/db/schema.rs
  • src-tauri/src/lib.rs
  • src-tauri/src/redaction.rs
  • src-tauri/src/server.rs
  • src-tauri/src/sync/config.rs
  • src/lib/components/CommandBlockSettings.svelte
  • src/lib/components/SyncScreen.svelte
  • src/lib/components/TerminalPane.svelte
  • src/lib/i18n/locales/en.ts
  • src/lib/i18n/locales/zh.ts
  • src/lib/stores/app.svelte.ts
  • src/lib/terminal/block-content.test.ts
  • src/lib/terminal/block-content.ts
  • src/lib/terminal/block-to-image.test.ts
  • src/lib/terminal/block-to-image.ts
  • src/lib/terminal/command-block-redaction.test.ts
  • src/lib/terminal/command-block-redaction.ts
  • src/lib/terminal/prompt.test.ts
  • src/lib/terminal/prompt.ts

Comment thread src/lib/stores/app.svelte.ts
Comment thread src/lib/terminal/block-to-image.test.ts
Comment thread src/lib/terminal/block-to-image.ts Outdated
Comment thread src/lib/terminal/prompt.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated no new comments.

@shihuili1218
shihuili1218 merged commit b048e14 into main Aug 6, 2026
1 check passed
@shihuili1218
shihuili1218 deleted the feat/command-block-copy-redaction branch August 6, 2026 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants