Skip to content

feat: /export with markdown and JSON format#14

Merged
kienbui1995 merged 1 commit intomainfrom
feat/conversation-export
Apr 11, 2026
Merged

feat: /export with markdown and JSON format#14
kienbui1995 merged 1 commit intomainfrom
feat/conversation-export

Conversation

@kienbui1995
Copy link
Copy Markdown
Owner

@kienbui1995 kienbui1995 commented Apr 11, 2026

What

Enhanced /export to export real session data (not raw TUI lines).

  • /export — readable markdown with user/assistant/tool messages
  • /export json — raw session JSON for programmatic use

Changes

  • mc-core/src/session.rs: Add Session::to_markdown()
  • mc-tui/src/app.rs: Export now carries format string
  • mc-tui/src/commands.rs: Parse /export [json]
  • mc-cli/src/main.rs: Use session data for export

152 tests pass.

Summary by CodeRabbit

  • New Features
    • Export functionality now supports multiple formats: JSON and Markdown. Users can specify the desired format when exporting (e.g., json for JSON export, defaulting to Markdown). Export now captures the complete session history with improved formatting, including message roles, token counts, and structured content blocks.

/export — exports session as readable markdown (default)
/export json — exports raw session as JSON

Markdown format includes user/assistant/tool messages with
proper formatting, tool call details, and token summary.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 11, 2026

📝 Walkthrough

Walkthrough

Export functionality is enhanced to support multiple formats (JSON and Markdown). A format parameter is added to the PendingCommand::Export enum variant, the /export command handler now accepts an optional format argument, and a new to_markdown() method serializes session message history as Markdown.

Changes

Cohort / File(s) Summary
Export Command Interface
mc/crates/mc-tui/src/app.rs, mc/crates/mc-tui/src/commands.rs
Updated PendingCommand::Export to carry a String format parameter. Command handler now parses optional format argument ("json" or defaults to "md").
Export Implementation
mc/crates/mc-cli/src/main.rs
Implements format-aware export: acquires non-blocking runtime lock, writes pretty-printed JSON to export.json when fmt == "json", otherwise writes Markdown via to_markdown() to export.md.
Session Serialization
mc/crates/mc-core/src/session.rs
Added to_markdown() public method that converts message history into readable Markdown with role headers, truncated content blocks, token counts, and special formatting for tool calls, results, and thinking blocks.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI/TUI
    participant Command Handler
    participant Session
    participant FileSystem

    User->>CLI/TUI: Trigger /export command with optional format
    CLI/TUI->>Command Handler: Parse format argument ("json" or "md")
    Command Handler->>CLI/TUI: Queue PendingCommand::Export(format)
    CLI/TUI->>CLI/TUI: Acquire runtime lock (non-blocking)
    alt format == "json"
        CLI/TUI->>Session: Serialize to JSON
        Session-->>CLI/TUI: JSON string
        CLI/TUI->>FileSystem: Write to export.json
    else format == "md"
        CLI/TUI->>Session: Call to_markdown()
        Session->>Session: Iterate messages, format per role
        Session-->>CLI/TUI: Markdown string
        CLI/TUI->>FileSystem: Write to export.md
    end
    FileSystem-->>User: Export complete
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Exports flow in formats new,
JSON gleams in morning dew,
Markdown streams so clean and bright,
Sessions dance from dark to light!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding markdown and JSON format options to the /export command.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/conversation-export

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@mc/crates/mc-cli/src/main.rs`:
- Around line 748-750: The code currently calls
serde_json::to_string_pretty(&rt.session).unwrap_or_default(), which can
silently produce an empty JSON string on serialization failure; instead handle
the Result explicitly (e.g., use match or the ? operator) so serialization
errors are returned or logged rather than written as empty content—find the
serde_json::to_string_pretty call around rt.session in main.rs and replace
unwrap_or_default with proper error propagation or a logged error path that
returns Err (or a non-success exit) so callers do not get an empty JSON file on
failure.
- Around line 746-753: The export path ends up with double extensions because
session_path("export.json") always appends ".json"; update the export branch
around the let (path, content) block so you pass a base name to session_path
(e.g., session_path("export")) and then append the desired extension per branch
(".json" for JSON, ".md" for Markdown), or change session_path to detect an
existing extension and not append ".json"; modify the code in the JSON and
Markdown branches accordingly (referencing the let (path, content) assignment
and the session_path function).

In `@mc/crates/mc-tui/src/commands.rs`:
- Around line 41-43: The format selection uses arg directly so inputs with extra
whitespace or different casing (e.g., "  JSON") fall through to markdown;
normalize arg by trimming whitespace and comparing case-insensitively before
deciding format. In the "/export" branch update the logic that sets fmt (where
PendingCommand::Export is used and app.pending_command is assigned) to compute a
normalized value (e.g., let normalized = arg.trim().to_ascii_lowercase() or use
eq_ignore_ascii_case on arg.trim()) and then choose "json" when it matches
"json", otherwise "md".
🪄 Autofix (Beta)

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

Run ID: f2b5ef44-8567-4540-91a0-24eaef341b0a

📥 Commits

Reviewing files that changed from the base of the PR and between 64a0298 and ed0befb.

📒 Files selected for processing (4)
  • mc/crates/mc-cli/src/main.rs
  • mc/crates/mc-core/src/session.rs
  • mc/crates/mc-tui/src/app.rs
  • mc/crates/mc-tui/src/commands.rs

Comment on lines +746 to +753
let (path, content) = if fmt == "json" {
let p = session_path("export.json");
let c = serde_json::to_string_pretty(&rt.session)
.unwrap_or_default();
(p, c)
} else {
let p = session_path("export.md");
(p, rt.session.to_markdown())
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Export path generation is wrong (.json gets appended twice).

Because session_path() always appends .json, Line 747 creates export.json.json and Line 752 creates export.md.json.

Suggested fix
-    let (path, content) = if fmt == "json" {
-        let p = session_path("export.json");
+    let session_dir = session_path("")
+        .parent()
+        .unwrap_or(std::path::Path::new("."))
+        .to_path_buf();
+    let (path, content) = if fmt == "json" {
+        let p = session_dir.join("export.json");
         let c = serde_json::to_string_pretty(&rt.session)
             .unwrap_or_default();
         (p, c)
     } else {
-        let p = session_path("export.md");
+        let p = session_dir.join("export.md");
         (p, rt.session.to_markdown())
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mc/crates/mc-cli/src/main.rs` around lines 746 - 753, The export path ends up
with double extensions because session_path("export.json") always appends
".json"; update the export branch around the let (path, content) block so you
pass a base name to session_path (e.g., session_path("export")) and then append
the desired extension per branch (".json" for JSON, ".md" for Markdown), or
change session_path to detect an existing extension and not append ".json";
modify the code in the JSON and Markdown branches accordingly (referencing the
let (path, content) assignment and the session_path function).

Comment on lines +748 to +750
let c = serde_json::to_string_pretty(&rt.session)
.unwrap_or_default();
(p, c)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid silently exporting empty JSON on serialization failure.

Line 748-749 uses unwrap_or_default(), which can write an empty file and still report success.

Suggested fix
-    let c = serde_json::to_string_pretty(&rt.session)
-        .unwrap_or_default();
-    (p, c)
+    match serde_json::to_string_pretty(&rt.session) {
+        Ok(c) => (p, c),
+        Err(e) => {
+            app.output_lines.push(format!("Export failed: {e}"));
+            continue;
+        }
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let c = serde_json::to_string_pretty(&rt.session)
.unwrap_or_default();
(p, c)
match serde_json::to_string_pretty(&rt.session) {
Ok(c) => (p, c),
Err(e) => {
app.output_lines.push(format!("Export failed: {e}"));
continue;
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mc/crates/mc-cli/src/main.rs` around lines 748 - 750, The code currently
calls serde_json::to_string_pretty(&rt.session).unwrap_or_default(), which can
silently produce an empty JSON string on serialization failure; instead handle
the Result explicitly (e.g., use match or the ? operator) so serialization
errors are returned or logged rather than written as empty content—find the
serde_json::to_string_pretty call around rt.session in main.rs and replace
unwrap_or_default with proper error propagation or a logged error path that
returns Err (or a non-success exit) so callers do not get an empty JSON file on
failure.

Comment on lines +41 to +43
"/export" => {
let fmt = if arg == "json" { "json" } else { "md" };
app.pending_command = Some(PendingCommand::Export(fmt.into()));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Normalize /export argument before format selection.

Line 42 only matches exact "json", so inputs like /export json or /export JSON unexpectedly export markdown.

Suggested fix
 "/export" => {
-    let fmt = if arg == "json" { "json" } else { "md" };
+    let fmt = if arg.trim().eq_ignore_ascii_case("json") {
+        "json"
+    } else {
+        "md"
+    };
     app.pending_command = Some(PendingCommand::Export(fmt.into()));
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mc/crates/mc-tui/src/commands.rs` around lines 41 - 43, The format selection
uses arg directly so inputs with extra whitespace or different casing (e.g., " 
JSON") fall through to markdown; normalize arg by trimming whitespace and
comparing case-insensitively before deciding format. In the "/export" branch
update the logic that sets fmt (where PendingCommand::Export is used and
app.pending_command is assigned) to compute a normalized value (e.g., let
normalized = arg.trim().to_ascii_lowercase() or use eq_ignore_ascii_case on
arg.trim()) and then choose "json" when it matches "json", otherwise "md".

@kienbui1995 kienbui1995 merged commit 9fc3bba into main Apr 11, 2026
6 of 7 checks passed
@kienbui1995 kienbui1995 deleted the feat/conversation-export branch April 11, 2026 09:25
@kienbui1995 kienbui1995 mentioned this pull request Apr 11, 2026
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.

1 participant