Skip to content

Conversation

dingfeli
Copy link
Contributor

@dingfeli dingfeli commented Oct 7, 2025

Issue #, if available:

Description of changes:
This PR aims to achieve the following:

  • Abstracting write destination for our existing event loop (This is stage B of the diagram below)
  • Scaffolding for new UI (This is the prerequisite of stage E in the diagram below)
  • Instrument old loop to conditionally emit events (This is stage C of the diagram below)

Background:
image

Stage A:
This is the current status quo. The event loop writes directly to stdout and stderr. What gets written are the ANSI code that gets interpreted by the terminal.

Stage B:
An abstraction is implemented that substitutes stdout and stderr. This abstraction also implements std::io::Write, which enables it be a drop in replacement for stdout and stderr where they are used as output in crossterm::queue! and crossterm::execute!. In this mode, the ANSI bytes written to this abstraction is then logged to stderr and stdout accordingly, thus preserving the UX.

Stage C:
In this stage, the current event loop is instrumented to send structured events instead of ANSI bytes. To preserve the ability to go back to stage B (should we choose to), we shall gate this behind a flag:

if should_send_structured_message(os) {
    self.stderr.send(Event::SomeType { ... })?;
} else {
    // The same old calls to crossterm::queue! and crossterm::execute!
}

The purpose of this stage is really to prepare us for the next stage when we swap out the old event loop with its successor (which is only capable to sending structured messages and does not have the option to write directly to stdout or stderr). Aside from the aforementioned instrumentation, we would also need to add enough logic in the UI abstraction layer to properly handle these events:

impl ViewEnd {
    /// Method to facilitate in the interim
    /// It takes possible messages from the old even loop and queues write to the output provided
    /// This blocks the current thread and consumes the [ViewEnd]
    pub fn into_legacy_mode(
        self,
        mut stderr: std::io::Stderr,
        mut stdout: std::io::Stdout,
    ) -> Result<(), ConduitError> {
        while let Ok(event) = self.receiver.recv() {
            match event {
                Event::LegacyPassThrough(content) => match content {
                    LegacyPassThroughOutput::Stderr(content) => {
                        stderr.write_all(&content)?;
                        stderr.flush()?;
                    },
                    LegacyPassThroughOutput::Stdout(content) => {
                        stdout.write_all(&content)?;
                        stdout.flush()?;
                    },
                },
// and the rest of the match arms for different events...

Stage D:
After validating / verifying the UI layer works as expected with the old event loop, at this stage we shall swap out the old event loop with the new event loop.

Stage E:
At this stage we would write another UI layer that speaks the same protocol and is more suitable for relevant use cases.


To facilitate a smooth transition that could remain as a two way door for as long as possible while also allowing us to keep shipping into production, the app would be able to switch freely (via config / settings) between B and E. 

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@dingfeli dingfeli force-pushed the dingfeli/chat-cli-ui-revamp branch from 7db7092 to e49e1a4 Compare October 7, 2025 23:55
@dingfeli dingfeli force-pushed the dingfeli/chat-cli-ui-revamp branch from e49e1a4 to 47ec19d Compare October 9, 2025 22:03
@dingfeli dingfeli changed the title Dingfeli/chat cli UI revamp feat: chat cli UI revamp Oct 10, 2025
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