<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Minseok Song</title>
    <description>The latest articles on DEV Community by Minseok Song (@minseoksong).</description>
    <link>https://dev.to/minseoksong</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4022207%2F7a4b303d-5c32-4af9-ba8c-fa6fd1a25cf3.jpg</url>
      <title>DEV Community: Minseok Song</title>
      <link>https://dev.to/minseoksong</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/minseoksong"/>
    <language>en</language>
    <item>
      <title>A lecture is not a collection of knowledge</title>
      <dc:creator>Minseok Song</dc:creator>
      <pubDate>Sat, 11 Jul 2026 02:10:50 +0000</pubDate>
      <link>https://dev.to/minseoksong/a-lecture-is-not-a-collection-of-knowledge-32eb</link>
      <guid>https://dev.to/minseoksong/a-lecture-is-not-a-collection-of-knowledge-32eb</guid>
      <description>&lt;p&gt;Before I think about how to give a lecture, I need to define what a lecture means to me.&lt;/p&gt;

&lt;p&gt;A lecture isn't a collection of knowledge that the audience has to memorize. The people in front of me don't need to remember everything I say. Delivering more information doesn't necessarily mean that I've taught them something useful.&lt;/p&gt;

&lt;p&gt;For me, a lecture is a chance to share something I've actually tried. I want to talk about where I struggled, what didn't work, and how I dealt with it. The audience doesn't need to repeat my experience exactly. I want them to recognize part of their own problem in it.&lt;/p&gt;

&lt;p&gt;This changes how I prepare.&lt;/p&gt;

&lt;p&gt;Before building the lecture, I need to understand what the participants are struggling with right now. If I don't know their problem, even correct and useful knowledge can miss them completely.&lt;/p&gt;

&lt;p&gt;Preparing a lecture feels like aiming at a target. It doesn't matter how much knowledge I put into the lecture if I'm aiming at the wrong place. I need to aim at a problem the audience actually has.&lt;/p&gt;

&lt;p&gt;I want participants to leave thinking, "I can apply that approach to the problem I'm working on." If they feel that way, the lecture has done more than deliver information. It has given them a possible way forward.&lt;/p&gt;

&lt;p&gt;I don't think I do this perfectly yet. But this is the kind of lecture I want to give.&lt;/p&gt;

</description>
      <category>career</category>
      <category>teaching</category>
      <category>community</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Progress Bar Is Not an API</title>
      <dc:creator>Minseok Song</dc:creator>
      <pubDate>Fri, 10 Jul 2026 00:41:09 +0000</pubDate>
      <link>https://dev.to/minseoksong/progress-bar-is-not-an-api-235b</link>
      <guid>https://dev.to/minseoksong/progress-bar-is-not-an-api-235b</guid>
      <description>&lt;p&gt;When a CLI becomes useful, it usually stops being something a person runs only by hand.&lt;/p&gt;

&lt;p&gt;Someone puts it in a script. Then someone wraps it in a product workflow. Then another system starts asking a simple question: "What is happening right now?"&lt;/p&gt;

&lt;p&gt;That is where a progress bar can become a problem.&lt;/p&gt;

&lt;p&gt;For a person, this output is enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Translating markdown files  12/40  30%  docs/intro.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can see that the command is alive. I can see how far it has moved. I can see the file it is working on.&lt;/p&gt;

&lt;p&gt;But another program cannot safely build product state from that sentence. The moment it tries, the progress bar stops being just a UI. It becomes an accidental API.&lt;/p&gt;

&lt;p&gt;That boundary was the real lesson behind one part of Co-op Translator v0.20.0. The release added a Rich-based progress UI for people, but it also added structured translation events for systems.&lt;/p&gt;

&lt;p&gt;The practical problem was not how to print progress. It was how to keep human output and system state from collapsing into one unstable surface.&lt;/p&gt;

&lt;p&gt;This post walks through the design in three steps:&lt;/p&gt;

&lt;p&gt;First, why console output is so tempting to parse.&lt;/p&gt;

&lt;p&gt;Second, why a better human UI can still break automation.&lt;/p&gt;

&lt;p&gt;Third, how a shared event contract can serve the CLI, Python API, MCP, and product integrations such as Localizeflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the simple version
&lt;/h2&gt;

&lt;p&gt;Console output is the easiest progress interface to build.&lt;/p&gt;

&lt;p&gt;When I run a translation command, I want to answer a few practical questions quickly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the command still running?&lt;/li&gt;
&lt;li&gt;Which stage is active?&lt;/li&gt;
&lt;li&gt;Which file is being processed?&lt;/li&gt;
&lt;li&gt;How much work is left?&lt;/li&gt;
&lt;li&gt;Did anything fail?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A progress bar is good at this job. It compresses the current state of the run into something a person can scan.&lt;/p&gt;

&lt;p&gt;That is the right first step. A CLI that gives no feedback during a long translation job feels broken, even when it is working.&lt;/p&gt;

&lt;p&gt;The problem starts when the same text becomes the only place where state exists.&lt;/p&gt;

&lt;p&gt;Imagine Localizeflow running Co-op Translator inside a larger workflow. It needs more than a readable line in the terminal. It needs durable facts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which translation job started&lt;/li&gt;
&lt;li&gt;Which target language is active&lt;/li&gt;
&lt;li&gt;Which stage is running&lt;/li&gt;
&lt;li&gt;Which file completed&lt;/li&gt;
&lt;li&gt;Which file failed&lt;/li&gt;
&lt;li&gt;How many items are done&lt;/li&gt;
&lt;li&gt;Whether the run succeeded&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If those facts only exist inside display text, Localizeflow has to parse human language to recover machine state.&lt;/p&gt;

&lt;p&gt;That is not a stable contract. It is a guess, and guesses become expensive once another product depends on them.&lt;/p&gt;

&lt;h2&gt;
  
  
  A better UI can still break automation
&lt;/h2&gt;

&lt;p&gt;The awkward part is that the CLI can improve and still break the thing built around it.&lt;/p&gt;

&lt;p&gt;Suppose an integration looks for this text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Translating markdown files: 12/40
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later, the CLI gets nicer. The same information moves into a Rich table, a progress bar, a status panel, or a shorter label.&lt;/p&gt;

&lt;p&gt;For the person watching the command, that may be a clear improvement.&lt;/p&gt;

&lt;p&gt;For a parser, it may be a failure.&lt;/p&gt;

&lt;p&gt;Even a small wording change can be enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Retranslating outdated markdowns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the label becomes clearer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Retranslating outdated markdown files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That change should be harmless. It is display text. It should be allowed to get better.&lt;/p&gt;

&lt;p&gt;But if another system depends on the exact phrase, a UI edit becomes an integration breaking change.&lt;/p&gt;

&lt;p&gt;Once you see that, the fix is not to make the progress bar easier to parse. The fix is to stop asking the progress bar to do two jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build state once, expose it twice
&lt;/h2&gt;

&lt;p&gt;In v0.20.0, progress information is created once and then exposed through two different surfaces.&lt;/p&gt;

&lt;p&gt;The first surface is the human renderer.&lt;/p&gt;

&lt;p&gt;In the CLI, Rich can turn translation state into headers, tables, progress bars, and status lines. That output should be pleasant to read. It should also be free to change when a clearer display helps the person using the tool.&lt;/p&gt;

&lt;p&gt;The second surface is the system event stream.&lt;/p&gt;

&lt;p&gt;External integrations should not scrape console output. They should consume events with a stable schema.&lt;/p&gt;

&lt;p&gt;The same progress can be represented as data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"co-op.translation.event.v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stage_progress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stage_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"translating_markdown_files"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stage_label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Translating markdown files"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ko"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"current_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"docs/intro.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"completed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"progress"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The progress bar and the event can come from the same internal state. They are just shaped for different readers.&lt;/p&gt;

&lt;p&gt;The UI gets friendly labels and layout. The event gets stable fields that another system can store, compare, and replay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Labels explain; keys promise
&lt;/h2&gt;

&lt;p&gt;One small design choice matters a lot here: &lt;code&gt;stage_label&lt;/code&gt; and &lt;code&gt;stage_key&lt;/code&gt; are separate.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;stage_label&lt;/code&gt; is for people:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stage_label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Translating markdown files"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That phrase can change if the CLI becomes clearer.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;stage_key&lt;/code&gt; is for integrations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stage_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"translating_markdown_files"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That value is the one another system should depend on.&lt;/p&gt;

&lt;p&gt;This separation protects both sides. The CLI can keep improving its language, and integrations do not have to treat every wording change as a risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Progress is a workflow, not only a percentage
&lt;/h2&gt;

&lt;p&gt;A useful event stream should not only report a number.&lt;/p&gt;

&lt;p&gt;Progress is a workflow moving through states.&lt;/p&gt;

&lt;p&gt;For Co-op Translator, that workflow can be expressed with events such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;run_started
estimate_ready
stage_started
stage_progress
file_completed
run_completed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Warnings and failures can use their own event types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;warning
file_failed
run_failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the integration code simpler.&lt;/p&gt;

&lt;p&gt;A dashboard can create a job when it receives &lt;code&gt;run_started&lt;/code&gt;. It can show token estimates after &lt;code&gt;estimate_ready&lt;/code&gt;. It can update visible progress after &lt;code&gt;stage_progress&lt;/code&gt;. It can mark the job complete after &lt;code&gt;run_completed&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The dashboard does not need to understand Co-op Translator's terminal sentences.&lt;/p&gt;

&lt;p&gt;It needs to understand the event contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  One contract can serve several interfaces
&lt;/h2&gt;

&lt;p&gt;Co-op Translator is not only a CLI.&lt;/p&gt;

&lt;p&gt;It also has a Python API and an MCP server. That means the progress contract should not belong to one terminal renderer.&lt;/p&gt;

&lt;p&gt;In the CLI, a person can watch the Rich progress UI. If another system needs machine-readable output, the CLI can write NDJSON events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;translate &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="s2"&gt;"ko ja"&lt;/span&gt; &lt;span class="nt"&gt;-md&lt;/span&gt; &lt;span class="nt"&gt;--json-events&lt;/span&gt; progress.ndjson
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the Python API, callers can receive the same kind of events through a callback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;co_op_translator.api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;run_translation&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;record_translation_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_dict&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;


&lt;span class="nf"&gt;run_translation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;language_codes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ko ja&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;root_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;progress_callback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;on_event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In MCP, &lt;code&gt;run_translation&lt;/code&gt; can return events in the tool result payload. Agents and host applications can then understand what happened without scraping terminal output.&lt;/p&gt;

&lt;p&gt;The interfaces differ, but the contract is shared:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CLI: Rich renderer and NDJSON event file&lt;/li&gt;
&lt;li&gt;Python API: &lt;code&gt;progress_callback&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;MCP: event payload in the tool result&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the practical value of the split. The display can evolve without destabilizing API and MCP integrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes for Localizeflow
&lt;/h2&gt;

&lt;p&gt;From the perspective of Localizeflow, the difference is concrete.&lt;/p&gt;

&lt;p&gt;In the fragile version, Localizeflow receives a sentence and tries to extract meaning from it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Done: Translated README.md to Korean.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From that sentence, it has to infer the file name, language, and completion state.&lt;/p&gt;

&lt;p&gt;With structured events, it can store the fact directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"file_completed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ko"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"current_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"README.md"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That event is easy to append to a database. Current job status can be materialized from the event stream. Logs can still exist, but they become supporting context rather than the source of truth for product state.&lt;/p&gt;

&lt;p&gt;That is a healthier boundary. A person can read logs while investigating a run. The product can rely on events while managing the workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rich still matters
&lt;/h2&gt;

&lt;p&gt;Separating events from the UI does not make the UI less important.&lt;/p&gt;

&lt;p&gt;It makes the UI easier to improve.&lt;/p&gt;

&lt;p&gt;A human-facing CLI should be clear. It should show the command, target language, estimate, current stage, current file, and failures in a form that is easy to scan.&lt;/p&gt;

&lt;p&gt;Rich is useful for that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Headers can group run information&lt;/li&gt;
&lt;li&gt;Tables can organize estimates and stage progress&lt;/li&gt;
&lt;li&gt;Progress bars can make long jobs easier to follow&lt;/li&gt;
&lt;li&gt;GitHub Actions logs can remain reasonably readable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But Rich output should be allowed to change when a better display helps the user.&lt;/p&gt;

&lt;p&gt;In this design, Rich renders the human-facing view. The versioned event schema carries the machine-readable state.&lt;/p&gt;

&lt;p&gt;The two surfaces come from the same run, but they are accountable to different readers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The principle
&lt;/h2&gt;

&lt;p&gt;The lesson is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Logs are for humans. Events are for systems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;People can read sentences and infer context. A small wording change is usually fine.&lt;/p&gt;

&lt;p&gt;Systems need stable keys, schema versions, event types, and typed fields.&lt;/p&gt;

&lt;p&gt;So when designing progress output, the first question should not be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do I make this text easier to parse?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The better question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Who is going to depend on this output?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If a person will read it, make the UI clear.&lt;/p&gt;

&lt;p&gt;If another system will depend on it, provide versioned structured events.&lt;/p&gt;

&lt;p&gt;A progress bar still matters. It makes a long-running CLI feel alive.&lt;/p&gt;

&lt;p&gt;It just should not be the API.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>cli</category>
      <category>api</category>
      <category>python</category>
    </item>
  </channel>
</rss>
