<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>sglavoie.com – Category: Tools</title><link>https://www.sglavoie.com/categories/tools/</link><description>Posts and writings by Sébastien Lavoie. Customization setups and articles on productivity for developers who like minimalism.</description><language>en</language><atom:link href="https://www.sglavoie.com/categories/tools/feed.xml" rel="self" type="application/rss+xml"/><lastBuildDate>Sun, 01 Oct 2023 19:45:00 -0600</lastBuildDate><item><title>First Steps with RxJS</title><link>https://www.sglavoie.com/posts/first-steps-with-rxjs/</link><description>An introduction to RxJS, a library for reactive programming in JavaScript/TypeScript. We’ll cover core concepts like observables, operators, testing, and tips for managing asynchronous data flows.</description><content:encoded>&lt;hr&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;While building an &lt;a href="https://learn.microsoft.com/en-us/office/dev/add-ins/excel/?view=excel-js-preview"&gt;Excel add-in&lt;/a&gt;, I came across the need for a more reactive style of programming due to the asynchronous nature of the APIs and the unpredictability of some events occurring in the spreadsheet. RxJS is one of those frameworks that can help with that. It&amp;rsquo;s a library for composing asynchronous and event-based programs by using observable sequences. It provides a set of operators to transform and combine streams of data. It&amp;rsquo;s a powerful tool that can be used to manage complex flows of data in a declarative fashion. It&amp;rsquo;s also a bit of a beast to learn, so I thought I&amp;rsquo;d share some of my notes and learnings along the way.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll first go over some basic concepts and terminology, move on to an attempt at depicting those various concepts, present common operators, touch upon reactive testing and then cover some tips and tricks.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="basic-concepts"&gt;Basic concepts&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;This is a push-based approach as opposed to a more common pull-based approach.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Observer&lt;/strong&gt; design pattern is implemented.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Observables&lt;/strong&gt; are the &lt;strong&gt;producers&lt;/strong&gt; of a &lt;strong&gt;stream&lt;/strong&gt; (the &lt;strong&gt;Subject&lt;/strong&gt;) that you can &lt;strong&gt;subscribe&lt;/strong&gt; (listen) to.
&lt;ul&gt;
&lt;li&gt;They are &amp;ldquo;&lt;em&gt;cold&lt;/em&gt;&amp;rdquo; by default, meaning they don&amp;rsquo;t emit values until they are subscribed to.&lt;/li&gt;
&lt;li&gt;They can deliver values &lt;em&gt;synchronously&lt;/em&gt; and &lt;em&gt;asynchronously&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;They can be cancelled by &lt;em&gt;unsubscribing&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Observers&lt;/strong&gt; are the &lt;strong&gt;consumers&lt;/strong&gt; of data produced by observables. They &amp;ldquo;&lt;em&gt;subscribe&lt;/em&gt;&amp;rdquo; via a &lt;strong&gt;subscription&lt;/strong&gt; to the stream of events emitted by the observable.
&lt;ul&gt;
&lt;li&gt;They execute some code when a new value is received.&lt;/li&gt;
&lt;li&gt;They implement &lt;code&gt;next&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt; and/or &lt;code&gt;complete&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;next&lt;/code&gt; method is called whenever a new value is emitted.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;error&lt;/code&gt; method is called whenever an error occurs with an emission from the observable.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;complete&lt;/code&gt; method is called whenever the observable is done, i.e. when no more values will be emitted from the stream in the future.&lt;/li&gt;
&lt;li&gt;By default, this leads to &lt;strong&gt;unicasting&lt;/strong&gt;, where a single subscription gets values from the producer.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;In the context of an Excel spreadsheet, we could think of the values from cells &lt;code&gt;A1&lt;/code&gt; and &lt;code&gt;B1&lt;/code&gt; as the producers of a stream of values that can be subscribed to by a cell &lt;code&gt;C1&lt;/code&gt; &amp;ndash; the consumer &amp;ndash; to perform some calculations based on the values from &lt;code&gt;A1&lt;/code&gt; and &lt;code&gt;B1&lt;/code&gt; which could be updated over time (i.e., emitting new values in the stream of data).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In an imperative programming paradigm, we would write code that executes a series of steps to produce a result. In a reactive programming paradigm, we would write code that reacts to events as they occur. Here&amp;rsquo;s a simple example to illustrate this:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;typescript&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Imperative approach
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 5
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 5: `a` was updated but `c` was not
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 6: `c` was updated after `a` was updated
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 8: `c` was updated after `b` was updated
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Reactive approach
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;combineLatest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rxjs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;combineLatest&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;a$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b$&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;c$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;a$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// logs nothing because b$ has not emitted yet
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;b$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// logs 7 at this point (latest a + b)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;a$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// logs 8 at this point (latest a + b)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="everything-is-a-stream"&gt;Everything is a stream&lt;/h2&gt;
&lt;p&gt;What follows is a metaphorical drawing designed (as best as I could&amp;hellip;) to help you understand the complex world of reactive programming with RxJS. It&amp;rsquo;s not a one-to-one mapping but aims to give a general feel of how things interconnect.&lt;/p&gt;
&lt;p&gt;Think of reactive programming as managing a complex water supply system for a bustling town and a high-tech factory. Water from the glacier (&lt;code&gt;Producer&lt;/code&gt;) flows as a river (&lt;code&gt;Stream&lt;/code&gt;) through a landscape filled with dams, turbines, and filters (&lt;code&gt;Operators&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Town residents and the factory subscribe to this river to fulfill their water needs. A subscription is like turning on a valve at the pumping station to let the water flow into your pipeline.&lt;/p&gt;
&lt;p&gt;Meanwhile, the factory refines the water and feeds it back into the system, acting like a &lt;code&gt;Subject&lt;/code&gt; in RxJS. Natural calamities (or dirty, unfiltered water) disrupting the flow signify errors, while the dam&amp;rsquo;s control room (&lt;code&gt;Schedulers&lt;/code&gt;) regulates the rhythm of the entire system.&lt;/p&gt;
&lt;div class="image"&gt;
&lt;img src="https://www.sglavoie.com/images/posts/0041_first_steps_with_rxjs/everything_is_a_stream.svg" alt="Drawing representing most major concepts found in RxJS" /&gt;
&lt;/div&gt;
&lt;p&gt;A simple drawing like that won&amp;rsquo;t do justice to the complexity of the RxJS ecosystem. Let&amp;rsquo;s nevertheless try to break down a bit more how concepts relate to each other.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;Producer&lt;/strong&gt; (glacier) is the source of the stream. It can be a user action, a network request, a timer, etc. There can be multiple sources of data emitting different types of values. In this example, there is a tributary (a smaller river) that feeds into the main river. By merging the two streams, we can get a single stream of data from both sources.
&lt;ul&gt;
&lt;li&gt;While it may be useful to merge different streams of data and transform them along the way, it is not necessary or always needed. In this drawing, the &lt;strong&gt;ReplaySubject&lt;/strong&gt; merely captures raw data as it arrives from the river and makes it available to late subscribers.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Stream&lt;/strong&gt; is the flow of data. It can be a single value or a sequence of values over time. Here, it may represent the flow of data throughout the whole application, where different events (mouse click, keyboard, etc.) are emitted from different sources (or components) and may be consumed in a modified fashion (e.g., keeping a single property from an object, filtering out some values, etc.).
&lt;ul&gt;
&lt;li&gt;Here, we have the town which subscribes to different streams that have been transformed already (electricity and water subscriptions). The town might be considered a &lt;strong&gt;Consumer&lt;/strong&gt; of the stream and is the data that ends up being displayed in the UI or sent back to a server.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Operators&lt;/strong&gt; are functions that transform the stream. They can be used to filter, combine, or modify the data.
&lt;ul&gt;
&lt;li&gt;Our metaphor contains a couple of them. There&amp;rsquo;s a water turbine &amp;ndash; representing a &lt;strong&gt;transformation operator&lt;/strong&gt; &amp;ndash; that transforms water into electricity (maybe it&amp;rsquo;s a &lt;code&gt;mergeMap&lt;/code&gt; that creates a new observable and maps it to a different structure) or water filters &amp;ndash; representing the &lt;strong&gt;filtering operators&lt;/strong&gt; &amp;ndash; that filter out unwanted particles (maybe it&amp;rsquo;s a &lt;code&gt;filter&lt;/code&gt; that removes some values from the stream).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subscriptions&lt;/strong&gt; are the consumers of the stream. They can be used to listen to the stream and perform actions when new values are emitted.
&lt;ul&gt;
&lt;li&gt;For instance, when the water bottling company receives water via its pipe, it may modify the stream by adding minerals to it (e.g., &lt;code&gt;map&lt;/code&gt; operator) and then bottle it up to deliver water. This creates a dependency chain where the bottling company is a consumer of the stream and a producer of a new stream that can be consumed by the town.&lt;/li&gt;
&lt;li&gt;If we zoom in on the water bottling company section, we can see that the water delivery truck &amp;ldquo;subscribes&amp;rdquo; to the product of the company and will likely want to deliver a truckload of water bottles once it is full (e.g., &lt;code&gt;buffer&lt;/code&gt; operator). If we look at the case where the truck delivers water with a giant container that happens to have a small leak, then we know that once the container starts filling with water, it will start leaking. This is akin to a &lt;strong&gt;hot observable&lt;/strong&gt; where the stream starts emitting values immediately, even if there are no subscribers. We could say this is also a stream that uses the &lt;code&gt;throttleTime&lt;/code&gt; operator to only emit values every 5 seconds, dropping a small amount of water in the process. Whether the container is nearly empty or full, it will still leak water every 5 seconds. This is different from a &lt;strong&gt;cold observable&lt;/strong&gt; where the stream only starts emitting values once a subscriber is listening to it, such as when a well-functioning container is plugged into a destination to deliver water.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subjects&lt;/strong&gt; are both producers and consumers. They can be used to multicast values to multiple subscribers.
&lt;ul&gt;
&lt;li&gt;The water purification factory consumes water from the stream: as such, it is an &lt;strong&gt;observer&lt;/strong&gt; as it reacts to the input stream (&lt;code&gt;next&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;, &lt;code&gt;complete&lt;/code&gt;), but it is also a &lt;strong&gt;producer&lt;/strong&gt; (because it can &lt;code&gt;pipe&lt;/code&gt; and consumers can &lt;code&gt;subscribe&lt;/code&gt; to it) as it emits a new stream of data that can be consumed by the town. This is akin to a &lt;strong&gt;multicast&lt;/strong&gt; where a single producer can emit values to multiple consumers. The factory processes the incoming stream and may re-process values (&lt;code&gt;retryWhen&lt;/code&gt;) or discard them (&lt;code&gt;filter&lt;/code&gt;) before emitting a new stream of purified data.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Schedulers&lt;/strong&gt; are used to control the timing of the stream. They can be used to delay, throttle, or debounce emissions.
&lt;ul&gt;
&lt;li&gt;In this thirsty scenario, we can see that if the dam&amp;rsquo;s control room blocks the flow of water entirely, this will affect the whole system downstream. If the control room were to stop the flow of water at night and schedule it to resume in the morning, this would be akin to a &lt;strong&gt;scheduler&lt;/strong&gt; that controls the timing of the stream. This is useful to avoid flooding the system with too much data at once, such as when a user types in a search box and we want to avoid making too many network requests at once. We can use a scheduler to throttle the stream of data to only emit values every 500 milliseconds, for instance, although in many cases we can directly rely on &lt;a href="#time-based"&gt;time-based operators&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unicast&lt;/strong&gt; is a one-to-one mapping between the producer and the consumer. It&amp;rsquo;s the default behavior of RxJS.
&lt;ul&gt;
&lt;li&gt;Assuming our dam blocks and redirects the entire stream coming from the glacier, then the initial part of the diagram would be a unicast with a single producer emitting values to a single consumer.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multicast&lt;/strong&gt; is a one-to-many mapping between the producer and the consumers. It&amp;rsquo;s the behavior of RxJS when using &lt;strong&gt;Subjects&lt;/strong&gt;.
&lt;ul&gt;
&lt;li&gt;If we take each resident of the town to be a subscriber of the water purification factory, then we have a multicast where a single producer emits values to multiple consumers. Likewise, the power utility company is a consumer of the electricity stream and a producer of a new stream that can be consumed by the town.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cold&lt;/strong&gt; observables are lazy and only start emitting values when subscribed to.
&lt;ul&gt;
&lt;li&gt;In the case of the pond, it is a cold observable that only starts emitting values when a subscriber is listening to it, such as when a pump is plugged into it or someone fills a bucket of water from it.&lt;/li&gt;
&lt;li&gt;In the diagram, a person acts as an &lt;code&gt;AsyncSubject&lt;/code&gt;: it will work for a time to get water from the pond, but once it&amp;rsquo;s done, it will emit a single value (the last value, i.e., the bucket) to all subscribers (let&amp;rsquo;s pretend for the sake of simplicity and for this metaphor to keep working that all subscribers effectively receive the exact same stream/value when it is emitted all at once). This is different from a &lt;code&gt;BehaviorSubject&lt;/code&gt; where the last value is emitted to late subscribers upon subscription. Subscribers will not receive any values from an &lt;code&gt;AsyncSubject&lt;/code&gt; until it completes and, as depicted, can also unsubscribe before the stream emits any value.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hot&lt;/strong&gt; observables are eager and start emitting values immediately, even if there are no subscribers.
&lt;ul&gt;
&lt;li&gt;There is a part of the river that&amp;rsquo;s identified near the bottom-left corner as a &lt;code&gt;hot observable&lt;/code&gt;, because whether there are subscribers or not, it will keep emitting values. A &lt;code&gt;ReplaySubject&lt;/code&gt; &amp;ndash; the cameraman &amp;ndash; was added to capture the values emitted from the river and make them available to late subscribers right upon subscription (e.g., when they watch it live on TV at some point).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My representation of the world may be flawed as I haven&amp;rsquo;t gone out much since the pandemic. Yet, that drawing tried to encapsulate most of the RxJS ecosystem and to some extent, that of reactive programming.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="operators"&gt;Operators&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Operators allow to hook into a stream, operate on it and even combine it with other streams.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="creation"&gt;Creation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;They are standalone functions to create observables, such as &lt;code&gt;of&lt;/code&gt;, &lt;code&gt;from&lt;/code&gt;, &lt;code&gt;fromEvent&lt;/code&gt;, &lt;code&gt;interval&lt;/code&gt;, &lt;code&gt;timer&lt;/code&gt; or &lt;code&gt;range&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You should always clean up subscriptions by unsubscribing when they&amp;rsquo;re no longer needed to avoid memory leaks and unintended consequences.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fromEvent&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Can create observables from DOM events or Node.js streams.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;of&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Values are emitted synchronously, one at a time.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;from&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Values are emitted synchronously, looping through a provided iterable (i.e., object with a &lt;code&gt;length&lt;/code&gt; property). Also works with promises (e.g., &lt;code&gt;from(fetch(url))&lt;/code&gt;) or generators (&lt;code&gt;function* () { yield … }&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;interval&lt;/code&gt; and &lt;code&gt;timer&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;They can emit values over time as a timer, where &lt;code&gt;timer&lt;/code&gt; is useful to specify a different delay on the first value being emitted (e.g., specifying the first argument to &lt;code&gt;timer&lt;/code&gt; to be &lt;code&gt;0&lt;/code&gt; to emit the first value immediately).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mapTo&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;This is a shortcut to remap an input to a different output (e.g., a constant string).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filter&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;It returns values when the evaluate to &lt;code&gt;true&lt;/code&gt; from a function that itself returns a boolean value.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reduce&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;It works exactly like &lt;code&gt;Array.reduce&lt;/code&gt;. It will emit the final value once the observable completes. That means it needs a stop condition in the pipeline, such as reaching the end of a synchronous stream or taking a few values only from an infinite stream (e.g., &lt;code&gt;interval(1000).pipe(take(3), reduce((acc, curr) =&amp;gt; acc + curr) …)&lt;/code&gt;) to take the the sum of the first three values emitted after 3 seconds.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scan&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Just like &lt;code&gt;reduce&lt;/code&gt; but emits every time the stream receives a new value, not just once the stream completes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tap&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Used to &lt;em&gt;spy&lt;/em&gt; on the observable source to perform side effects without affecting the underlying stream, which is useful when debugging to observe output values at different steps of the pipeline.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="pipe"&gt;Pipe&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Operators are comma-separated inside the &lt;code&gt;pipe&lt;/code&gt; method that&amp;rsquo;s called on the observable. Then, the &lt;code&gt;subscribe&lt;/code&gt; method is called on the assembled pipeline with a given observer to start producing the values from the stream.&lt;/li&gt;
&lt;li&gt;The operators create a new observable at each step in a &amp;ldquo;pure&amp;rdquo; fashion, so that the original observable does not change. Each operator in the pipe is akin to a different step in an assembly line.&lt;/li&gt;
&lt;li&gt;When &lt;code&gt;subscribe&lt;/code&gt; is called, each operator in the pipe in turn subscribe to the observable and pass its value(s) along to the next operator in the pipeline.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;map&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;s similar to &lt;code&gt;Array.map&lt;/code&gt; but it works on every element emitted from the stream, not on the stream as a whole.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;finalize&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;It is called once on the completion of the observable, which could be used to perform some cleanup logic after an action completes, such as updating a status field.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="filtering"&gt;Filtering&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;take&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Accepts a given number of items from the source before completing.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;first&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;To &lt;code&gt;take&lt;/code&gt; a single value based on a filtering condition.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;takeWhile&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Emits values from the source observable as long as a predicate condition is met. A value of &lt;code&gt;true&lt;/code&gt; can be passed to the second argument of the function signature of &lt;code&gt;takeWhile&lt;/code&gt; to also emit the value that caused the stream to stop emitting. It is used to limit the lifetime of an observable based on a known condition to apply.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;takeUntil&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;It takes a value until another stream emits a value. It accepts as an argument another observable for this purpose.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;distinctUntilChanged&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;It will emit values only if they are distinct from the previously emitted value from the stream.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;distinctUntilKeyChanged&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Will compare the previous value emitted with the current value to evaluate whether a given key of an object has changed.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="time-based"&gt;Time-based&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;debounceTime&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Useful when you need the last value emitted within a short period of time, discarding all previously emitted values until enough delay has passed.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;debounce&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Does the same thing as &lt;code&gt;debounceTime&lt;/code&gt;, but allows passing a function so that variables can be used to determine the emission of results.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;throttleTime&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Ignore values after the last emission for a specified duration, creating a &amp;ldquo;silence window&amp;rdquo;. It can help ignore spammy events by reacting after a certain time has passed only, such as listening for scroll events (e.g., do not process values instantly but rather every 20-30 ms).&lt;/li&gt;
&lt;li&gt;We can use &lt;code&gt;asyncScheduler&lt;/code&gt; as the second parameter to &lt;code&gt;throttleTime&lt;/code&gt;, and give it a config object as the third argument to specify whether we&amp;rsquo;re interested in emitting the &lt;code&gt;leading&lt;/code&gt; (first) or &lt;code&gt;trailing&lt;/code&gt; (last) event from the stream within the silence window (e.g., for a scroll event, we want to know the last position).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sampleTime&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;It &amp;ldquo;samples&amp;rdquo; a time window to emit only the last value from that window, doing so at precise intervals.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;auditTime&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;This is like &lt;code&gt;sampleTime&lt;/code&gt;, but starting a counter once the stream emits at least one value, keeping the last value emitted within the desired time window. This is the same as &lt;code&gt;throttleTime&lt;/code&gt; where &lt;code&gt;leading&lt;/code&gt; is set to &lt;code&gt;false&lt;/code&gt; and &lt;code&gt;trailing&lt;/code&gt; is set to &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="transformation"&gt;Transformation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;One type of transformation operator is a flattening operator. This type of operator takes an observable that emits an observable to which RxJS internally subscribes to, simplifying the pipeline management. One such operator is &lt;code&gt;mergeAll&lt;/code&gt;. Instead of mapping an emitted value to a new observable (e.g., with &lt;code&gt;Ajax.getJSON&lt;/code&gt;) and then flattening the pipe to get the emitted value from that nested observable with &lt;code&gt;mergeAll&lt;/code&gt;, we can do this in a single step with &lt;code&gt;mergeMap&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mergeMap&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;This is good for &amp;ldquo;fire and forget&amp;rdquo; type of behavior. For example, we may have an observable of click events from which we save the &lt;code&gt;clientX&lt;/code&gt; and &lt;code&gt;clientY&lt;/code&gt; coordinates, using these values to make an Ajax request that can perform an operation in the background which we do not want to cancel. Need to be careful with this as long-running observables may need cleanup.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;switchMap&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;While &lt;code&gt;mergeMap&lt;/code&gt; can maintain any number of inner subscriptions at a time, &lt;code&gt;switchMap&lt;/code&gt; will maintain a single one. Any time a new observable is mapped, the previous one is automatically completed. This is useful if a previous request needs to be cancelled where only the last request makes sense, such as in a type-ahead behavior where we don&amp;rsquo;t want results from previous requests to be shown, but rather only the last one. This is the safest default for flattening because it won&amp;rsquo;t create leaks like &lt;code&gt;mergeMap&lt;/code&gt;, which will leave other inner observables running in the background unless they are explicitly completed.
&lt;ul&gt;
&lt;li&gt;Great for reset, pause and resume functionality.&lt;/li&gt;
&lt;li&gt;Should avoid it when cancelling a request will have negative side effects, like saving a document.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;concatMap&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Like &lt;code&gt;switchMap&lt;/code&gt;, &lt;code&gt;concatMap&lt;/code&gt; works on a single observable at a time, queuing up events (FIFO) from other observables until the first one completes. Should only be used when you have observables with a finite lifespan, otherwise the queue will never empty. This is useful when execution order of requests is important on the client side and when events will have a finite lifespan.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;exhaustMap&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Only maintains one inner subscription at a time. It will ignore values from new events until the first observable completes, discarding those events while there&amp;rsquo;s still an ongoing active subscription. This could be used to prevent further requests when clicking a button such as when submitting a form, effectively &amp;ldquo;disabling&amp;rdquo; the button while the submission is happening to avoid spamming the server with duplicate requests.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;catchError&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;By default, when an observable throws an error, it will stop emitting values as the errors are not handled. It can catch and return the error or return an empty observable to ignore the current value, which prevents the stream from breaking and will continue emitting values.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="combination"&gt;Combination&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Can join multiple observables into a single stream, such as when you need to perform calculations based on multiple stream inputs.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;startWith&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Lets you prepend any value(s) at the beginning of the stream.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;endWith&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Lets you append any value(s) at the end of the stream.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;concat&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Useful to execute multiple observables in order. When the first one finishes, the second one starts. That may come in handy to manage UI animations and to complete network requests in order.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;merge&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Creates a single observable from any other number of observables. This means that it emits all values from all the observables as a single stream as they occur. For example, if we have a countdown related to both a &amp;ldquo;start&amp;rdquo; and a &amp;ldquo;pause&amp;rdquo; button, we might want the same stream to return &amp;ldquo;true&amp;rdquo; values when &amp;ldquo;start&amp;rdquo; is clicked and &amp;ldquo;false&amp;rdquo; values when &amp;ldquo;pause&amp;rdquo; is clicked to determine the next step to take in the pipeline.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;combineLatest&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Takes two or more observables, combining the latest value received from each one only once all the observables have emitted at least one value. This would be useful when all values depend on each other to produce a common result.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;forkJoin&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;It emits the last produced value from each observable subscribed to as an array. This is like combineLatest, but returning values only once all streams have completed. The observables can be wrapped with brackets to emit an object, where a property (key) can be assigned to the result of each observable (set as a value of the object). This is somewhat equivalent to &lt;code&gt;Promise.all&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;share&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;It is used to &amp;ldquo;share&amp;rdquo; the result of a stream to all subscribers. This can be useful to avoid running heavy computations more than once.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="creating-a-custom-operator"&gt;Creating a custom operator&lt;/h2&gt;
&lt;p&gt;We can create a custom operator by creating a function that returns a function that accepts an observable and returns a new observable. While that might be a bit of an abstract explanation to follow, here is a basic example illustrating this concept.&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;typescript&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;rxjs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;multiplyBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;multiplier&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;: &lt;span class="kt"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;complete() {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Usage:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;rxjs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;multiplyBy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;./multiplyBy&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;multiplyBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// logs 2, 4, 6, 8, 10
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Here is another example combining &lt;code&gt;filter&lt;/code&gt;, &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;reduce&lt;/code&gt; to create a custom operator that will filter out values from a stream, map them to a new value and then reduce them to a single value.&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;typescript&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reduce&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;rxjs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;filterMapReduce&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;R&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;predicate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;: &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;mapFn&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;: &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;reduceFn&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;: &lt;span class="kt"&gt;R&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;curr&lt;/span&gt;: &lt;span class="kt"&gt;R&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;: &lt;span class="kt"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;R&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;predicate&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mapFn&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reduceFn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Usage:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rxjs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;filterMapReduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// keep only even numbers (2 and 4)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// multiply each of these even numbers by 2 (4 and 8)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// sum all these values
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// accumulator is initialized to 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// so we get 0 + 4 for the first value
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// then 4 + 8 for the second value, which is returned
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;curr&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// logs 12
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="marble-diagrams"&gt;Marble diagrams&lt;/h2&gt;
&lt;div class="image"&gt;
&lt;img src="https://www.sglavoie.com/images/posts/0041_first_steps_with_rxjs/marble_diagram.svg" alt="Showing a basic marble diagram with filter operator" /&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;They are used to represent the behavior of operators. They can be used to test observables.&lt;/li&gt;
&lt;li&gt;They allow comparing at a glance how a set of inputs maps to a set of outputs (i.e., which transformations are applied to the source stream).&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;X&lt;/code&gt; represents an error and a vertical line &lt;code&gt;|&lt;/code&gt; represents the end of emission of values from the input stream.&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;div class="image"&gt;
&lt;img src="https://www.sglavoie.com/images/posts/0041_first_steps_with_rxjs/marble_diagram_pipeline.svg" alt="Showing merging of streams with pipeline in the middle flowing towards a single output stream" /&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="subjects"&gt;Subjects&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;Subject&lt;/strong&gt; is an &lt;strong&gt;Observable&lt;/strong&gt;: it has both &lt;code&gt;pipe&lt;/code&gt; and &lt;code&gt;subscribe&lt;/code&gt; methods. A Subject is also an &lt;strong&gt;Observer&lt;/strong&gt;: it has &lt;code&gt;next&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt; and &lt;code&gt;complete&lt;/code&gt; methods. Unlike Observables which are unicast (1-to-1 mapping with observers, each observer gets an independent stream of data), Subjects are &lt;strong&gt;multicast&lt;/strong&gt;, meaning they broadcast the same information to any &amp;ldquo;listeners&amp;rdquo; (observers).&lt;/li&gt;
&lt;li&gt;Multicasting operators include &lt;code&gt;share&lt;/code&gt;, &lt;code&gt;shareReplay&lt;/code&gt; and &lt;code&gt;multicast&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="behaviorsubject"&gt;BehaviorSubject&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;It allows delivering a seed/initial value to late subscribers such that a subscription happening at a later point can still receive the last emitted value upon subscribing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="replaysubject"&gt;ReplaySubject&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;It allows replaying the whole stream (or the last items desired via its first argument) to late subscribers, effectively providing a way to multicast (i.e., the stream is played only once and multiple subscribers can receive the values at once).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shareReplay&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;This can be added to a pipeline and any subscriber to that stream will receive updates as if a ReplaySubject has been manually set up. For time sensitive matters where receiving a value too late isn&amp;rsquo;t useful, a second argument can be passed to &lt;code&gt;shareReplay&lt;/code&gt; to only capture events within the last &lt;code&gt;x&lt;/code&gt; milliseconds.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="asyncsubject"&gt;AsyncSubject&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Only emits the last value to all subscribers when it completes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is a real-world example of a setup making use of a &lt;code&gt;BehaviorSubject&lt;/code&gt; from an Excel add-in. The goal is to perform some actions in the UI whenever the connection status of a WebSocket changes by using a &lt;strong&gt;subject&lt;/strong&gt;, which can be subscribed to from another part of the add-in to perform the needed updates. Omitting the details of the WebSocket connection, the following code snippet shows how we can use a &lt;code&gt;BehaviorSubject&lt;/code&gt; to keep track of the connection status and emit the latest value to late subscribers.&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;typescript&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BehaviorSubject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;distinctUntilChanged&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;rxjs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;ConnectionManager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;apiConnection$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;BehaviorSubject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;ws&lt;/span&gt;: &lt;span class="kt"&gt;WebSocket&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Expose connection status as an Observable emitting whenever the connection status changes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;connectionStatus$&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiConnection$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asObservable&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;distinctUntilChanged&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ws://localhost:8080/ws&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onopen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiConnection$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onclose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiConnection$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In other words, we can subscribe to the &lt;code&gt;connectionStatus$&lt;/code&gt; observable and react to changes in the connection status. This is useful to update the UI, for instance, to show a &amp;ldquo;&lt;em&gt;disconnected&lt;/em&gt;&amp;rdquo; message when the connection is lost.&lt;/p&gt;
&lt;p&gt;In a state manager, this might be dealt with like this to keep track of the sheets we have:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;typescript&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;sheetSubscriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sheetId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// track the sheet subscriptions
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// delete the subscriptions to the sheet at some point in the future
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;sheetSubscriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sheetId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// unsubscribe from all subscriptions for this sheet
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The implementation details of the &lt;code&gt;sheetSubscriptions&lt;/code&gt; map are omitted here, but the idea is that we can use a &lt;code&gt;Subject&lt;/code&gt; to keep track of the subscriptions to a given sheet. When we want to unsubscribe from all subscriptions to a given sheet, we can call &lt;code&gt;next&lt;/code&gt; on the subject to complete the stream and unsubscribe from all subscriptions.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="schedulers"&gt;Schedulers&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;All schedulers accept three arguments: &lt;code&gt;work&lt;/code&gt; (task to execute), &lt;code&gt;delay&lt;/code&gt; (in milliseconds) and &lt;code&gt;state&lt;/code&gt; (when used, the first argument &lt;code&gt;work&lt;/code&gt; defines the function to execute and &lt;code&gt;state&lt;/code&gt; the data passed to it).&lt;/li&gt;
&lt;li&gt;Most basic observables (e.g., &lt;code&gt;range&lt;/code&gt;) accept as the last argument a scheduler, so this can be set there (e.g., &lt;code&gt;range(1, 5, asapScheduler)&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AsyncScheduler&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Can be used as an equivalent to setTimeout. It&amp;rsquo;s simpler to just use the &lt;code&gt;delay&lt;/code&gt; operator for that purpose. When used with &lt;code&gt;subscribeOn&lt;/code&gt; in the pipeline, this is equivalent to wrapping the whole observable with setTimeout.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;asapScheduler&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;This is used to queue micro tasks, which are run after other synchronous pieces of code but before asynchronous code.&lt;/li&gt;
&lt;li&gt;Micro tasks are run after the currently running synchronous code but will block the UI when run.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;animationFrameScheduler&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;This is similar to requestAnimationFrame. Set no delay or set it to &lt;code&gt;0&lt;/code&gt; so that it will not be async and will update before every browser repaint.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;queueScheduler&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;It is synchronous by default. Inner calls to &lt;code&gt;queueScheduler&lt;/code&gt; will be executed once the outer calls are done running.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While it&amp;rsquo;s possible to use schedulers directly to provide fine-grained control over concurrency, it&amp;rsquo;s often easier to use the &lt;code&gt;delay&lt;/code&gt; operator to delay the emission of values from the stream. For instance, we can use &lt;code&gt;delay(1000)&lt;/code&gt; to delay the emission of values by 1 second.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="example-flow"&gt;Example flow&lt;/h2&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;typescript&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;BehaviorSubject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;EMPTY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Observer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;catchError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;distinctUntilChanged&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;switchMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;rxjs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Create a subject (stream emitting AND receiving values over time) with a default
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// value. A common pattern is to suffix the variable name with a `$` to indicate that
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// it&amp;#39;s a stream
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream1Subject$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;BehaviorSubject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Get the observable (stream emitting values over time) from the subject
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Doing this, we can `pipe` and `subscribe` to the observable but we can&amp;#39;t emit values
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// with `next`, `error` or `complete` methods.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// We can also use the subject directly to emit values with `next`, `error` or
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// `complete`.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream1Observable$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stream1Subject$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asObservable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Will use to send the final result of the status$ observable to this second stream
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream2Subject$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;BehaviorSubject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream2Observable$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stream2Subject$&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asObservable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// ignore initial/empty values
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream3ObservableForErrorsSubject$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream3ObservableForErrors$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;stream3ObservableForErrorsSubject$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asObservable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Some async operation that will update the connection status
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;asyncFuncOnStatusChanged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;isConnected&lt;/span&gt;: &lt;span class="kt"&gt;boolean&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ... `await` some async operation to update the UI with the new connection status
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;` -&amp;gt; onStatusChanged: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;isConnected&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isConnected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Disconnected!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Create an observable (stream emitting values over time) from an another observable.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// The idea is to transform the original event into another event:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// event1 -&amp;gt; event2 -&amp;gt; event3 -&amp;gt; ... -&amp;gt; result
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Any observer on these events could push new values into other streams
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// (e.g. `otherSubject$.next(&amp;#34;value&amp;#34;)`)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observable1GetConnectionStatusChangedForRibbon$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;connectionStatus$&lt;/span&gt;: &lt;span class="kt"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Transform the original event with `pipe`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;connectionStatus$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Prevent the observable from completing when an error is thrown
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;catchError&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Caught error in observable1: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;` -&amp;gt; observable start: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// If the value is the same as the previous one, don&amp;#39;t emit it
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;distinctUntilChanged&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Perform side effects with `tap` (useful for logging/debugging)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;` -&amp;gt; observable before switchMap: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Ensure async execution
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;switchMap&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;asyncFuncOnStatusChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// This will catch errors thrown by `asyncFuncOnStatusChanged` in a &amp;#39;local&amp;#39; way.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// We would still need to catch errors thrown directly from the source observable
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// as we did above
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;catchError&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Caught error: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Emit the error to the third observable
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;stream3ObservableForErrorsSubject$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;error&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// This completes the inner observable
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;EMPTY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// EMPTY is an observable that completes immediately
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// In this case, we could just as well have used `status$` directly,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// but this could be useful if we want to transform the original event or if we want to
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// merge multiple observables into one
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream1ObservableTransformed$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;observable1GetConnectionStatusChangedForRibbon$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream1Observable$&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Create an observer (object with `next`, `error` and `complete` methods) to subscribe
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// to the observable
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observer1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; -&amp;gt; observer: subscription started&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; -&amp;gt; observer: status changed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// We could trigger another event here to continue the pipeline elsewhere, e.g.:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// otherSubject$.next(&amp;#34;value&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Then, if there&amp;#39;s a subscription to `otherSubject$`, it will receive the value
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// &amp;#34;value&amp;#34; and continue the pipeline
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;` -&amp;gt; observer: subscription errored: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39; -&amp;gt; observer: subscription completed, notifying stream2Subject$&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;stream2Subject$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;observer1 sent this message to observer2 in `.complete()`!&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Subscribing to secondObservable$...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observer2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; -&amp;gt; observer2: subscription started&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;` -&amp;gt; observer2: got &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;&amp;#39;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;` -&amp;gt; observer2: subscription errored: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; -&amp;gt; observer2: subscription completed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream2ObservableSubscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stream2Observable$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;observer2&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\nSubscribing to thirdObservableForErrors$...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream3ObservableSubscription$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stream3ObservableForErrors$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sb"&gt;` -&amp;gt; observer3: got value &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;&amp;#39; from observer2.complete()`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;` -&amp;gt; observer3: subscription errored: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; -&amp;gt; observer3: subscription completed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\nSubscribing to observable status$...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Subscribe to the observable. Until we subscribe, nothing happens!
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;stream1ObservableTransformed$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;observer1&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\nEmitting `true` to stream1Subject$...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;stream1Subject$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\nEmitting `true` to stream1Subject$...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;stream1Subject$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\nEmitting an error to stream1Subject$...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;stream1Subject$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Error emitted from the outside&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\nEmitting message to stream2Subject$...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;stream2Subject$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Sent unrelated message from the outside&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\nEmitting `false` to stream1Subject$...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;stream1Subject$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\nEmitting `true` again to stream1Subject$...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;stream1Subject$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\nEmitting `false` again to stream1Subject$, causing error...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;stream1Subject$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\nStreams keep listening until completion or unhandled error&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Add some async behavior so this will happen at the end
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;stream3ObservableForErrorsSubject$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Will receive one more value before unsubscribing&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\nUnsubscribing from thirdObservableSubscription$...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;stream3ObservableSubscription$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;stream3ObservableForErrorsSubject$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Will not be received after unsubscribe&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;stream1Subject$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\nEmitting `true` to statusSubject$...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// This is ignored because the stream is completed by now
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;stream1Subject$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// already unsubscribed since they completed: nothing will happen
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;stream1Subject$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;stream2ObservableSubscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This will result in the following output being logged to the console:&lt;/p&gt;
&lt;div class="code-block code-block--wrap"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Subscribing to secondObservable$...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; observer2: subscription started
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Subscribing to thirdObservableForErrors$...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Subscribing to observable status$...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; observer: subscription started
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; observable start: false
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; observable before switchMap: false
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; onStatusChanged: false
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Emitting `true` to stream1Subject$...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; observable start: true
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; observable before switchMap: true
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; onStatusChanged: true
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Emitting `true` to stream1Subject$...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; observable start: true
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Emitting an error to stream1Subject$...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Caught error in observable1: Error emitted from the outside
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; observable start: false
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; observable before switchMap: false
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; onStatusChanged: false
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Emitting message to stream2Subject$...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; observer2: got &amp;#39;Sent unrelated message from the outside&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Emitting `false` to stream1Subject$...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Emitting `true` again to stream1Subject$...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Emitting `false` again to stream1Subject$, causing error...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Streams keep listening until completion or unhandled error
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Emitting `true` to statusSubject$...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Caught error: Error: Disconnected!
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; observer3: got value &amp;#39;error&amp;#39; from observer2.complete()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; observer: subscription completed, notifying stream2Subject$
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; observer3: got value &amp;#39;Will receive one more value before unsubscribing&amp;#39; from observer2.complete()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Unsubscribing from thirdObservableSubscription$...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;While this example is a bit abstract and contrived, RxJS can be used for a bunch of different use cases, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;debounceTime&lt;/code&gt; and &lt;code&gt;switchMap&lt;/code&gt; to limit requests and cancel previous searches in a search auto-complete;&lt;/li&gt;
&lt;li&gt;Typeahead search box: &lt;code&gt;fromEvent&lt;/code&gt; on keystrokes, combined with API requests;&lt;/li&gt;
&lt;li&gt;Scroll position can be tracked with &lt;code&gt;fromEvent&lt;/code&gt; to lazily load content;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;combineLatest&lt;/code&gt; and &lt;code&gt;map&lt;/code&gt; can be used to validate fields as user fills them out;&lt;/li&gt;
&lt;li&gt;Progress bar updates could be animated using &lt;code&gt;interval&lt;/code&gt; and &lt;code&gt;map&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;fromEvent&lt;/code&gt; to track mouse drags and &lt;code&gt;merge&lt;/code&gt; to handle multiple event streams;&lt;/li&gt;
&lt;li&gt;Create a real-time dashboard updating based on WebSocket streams with &lt;code&gt;merge&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Debounce input changes with &lt;code&gt;debounceTime&lt;/code&gt; to trigger saves;&lt;/li&gt;
&lt;li&gt;Create heatmaps from click events using &lt;code&gt;fromEvent&lt;/code&gt; and &lt;code&gt;reduce&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Retry XHR requests on failure using &lt;code&gt;catchError&lt;/code&gt; and &lt;code&gt;retry&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Going back to the world of Excel add-ins, here is a real-world example of a setup using RxJS to listen to events from the Excel API. The goal is to listen to selection changes on a table and perform some actions when the selection changes. The &lt;code&gt;fromEventPattern&lt;/code&gt; method allows us to create an observable from an API event. We basically &amp;ldquo;hook&amp;rdquo; into the Excel API event and transform it into an observable. We can then use the &lt;code&gt;filter&lt;/code&gt; operator to filter out unrelated events and the &lt;code&gt;takeUntil&lt;/code&gt; operator to unsubscribe when the notifier emits (when the table is deleted).&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;typescript&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fromEventPattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;takeUntil&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;rxjs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notifier$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Excel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// some actual table object (omitted for brevity)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getTableOnSelectionChanged$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;table&lt;/span&gt;: &lt;span class="kt"&gt;Excel.Table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;notifier$&lt;/span&gt;: &lt;span class="kt"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;Excel.TableSelectionChangedEventArgs&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;fromEventPattern&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;Excel.TableSelectionChangedEventArgs&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;: &lt;span class="kt"&gt;TAnyExcelValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onSelectionChanged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;: &lt;span class="kt"&gt;TAnyExcelValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onSelectionChanged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// unsubscribe when notifier emits (table is deleted)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;takeUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notifier$&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// filter unrelated events...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;: &lt;span class="kt"&gt;Excel.TableSelectionChangedEventArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;: &lt;span class="kt"&gt;Excel.TableSelectionChangedEventArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Event triggered at &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Subscription errored: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Subscription completed for table &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;&amp;#39;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;selectionChanged$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getTableOnSelectionChanged$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;notifier$&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscriber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;selectionChanged$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ... do something with the subscription
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;notifier$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// unsubscribe from the observable and delete the table
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="reactive-testing"&gt;Reactive testing&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Reactive testing&lt;/strong&gt; is a way to test observables. It can be done with marble diagrams or by subscribing and asserting values. While it&amp;rsquo;s more difficult to wrap one&amp;rsquo;s head around marble diagrams, they are more accurate and can be used to test timing. Subscribing and asserting values is easier to understand but requires more boilerplate and is less accurate.&lt;/p&gt;
&lt;h2 id="marble-testing"&gt;Marble testing&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;We can test streams with expressive ASCII representations of marble diagrams.&lt;/li&gt;
&lt;li&gt;This allows for greater test accuracy of values and timing.&lt;/li&gt;
&lt;li&gt;It requires quite a bit of setup.&lt;/li&gt;
&lt;li&gt;A dash &lt;code&gt;-&lt;/code&gt; represents a frame of virtual time.&lt;/li&gt;
&lt;li&gt;Any character from the set &lt;code&gt;[a-z0-9]&lt;/code&gt; represents an emitted value.&lt;/li&gt;
&lt;li&gt;Errors are represented with a &lt;code&gt;#&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;()&lt;/code&gt; represent synchronous groupings.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;|&lt;/code&gt; represents the completion of the stream.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="example"&gt;Example&lt;/h3&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;typescript&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;rxjs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TestScheduler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;rxjs/testing&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;testScheduler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;TestScheduler&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;testing async behavior&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;should test asynchronous operations&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;testScheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;helpers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;expectObservable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;helpers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;final$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;source$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1s (abc|)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expectObservable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;final$&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;: &lt;span class="kt"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;: &lt;span class="kt"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;: &lt;span class="kt"&gt;3&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="subscribe-and-assert-testing"&gt;Subscribe and assert testing&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Less setup required.&lt;/li&gt;
&lt;li&gt;Can use typical testing APIs and testing patterns.&lt;/li&gt;
&lt;li&gt;Because this runs outside the test scheduler, we must manage async test completion.&lt;/li&gt;
&lt;li&gt;Requires more boilerplate for assertions with multiple items.&lt;/li&gt;
&lt;li&gt;Hard to accurately test timing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="example-1"&gt;Example&lt;/h3&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;typescript&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toArray&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;rxjs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;testing with subscribe and assert pattern&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;compare emitted values on completion&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;final$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;source$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;final$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="tips-and-tricks"&gt;Tips and tricks&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Using &lt;code&gt;finalize&lt;/code&gt; to execute side effects on completion.
&lt;ul&gt;
&lt;li&gt;Do not put side effects to run inside the &lt;code&gt;complete&lt;/code&gt; function as it is not called when manually unsubscribing or when an error occurs.&lt;/li&gt;
&lt;li&gt;Instead, we can add the &lt;code&gt;finalize&lt;/code&gt; operator tacked onto the end of the pipeline.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Extract common operator logic into standalone functions.
&lt;ul&gt;
&lt;li&gt;These functions will accept an observable source and return a new observable. See &lt;a href="#creating-a-custom-operator"&gt;Creating a custom operator&lt;/a&gt; for an example.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Use combination operators to access state from secondary streams.
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;withLatestFrom(store$)&lt;/code&gt; to retrieve the current state. This works when subscribing to a BehaviorSubject which will have emitted the value by the time it&amp;rsquo;s needed but may be a source of headaches if subscribing to a regular Subject since the last value will not have been emitted by the time the subscription happens.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Automate observable cleanup with &lt;code&gt;takeUntil&lt;/code&gt; and &lt;code&gt;Subjects&lt;/code&gt;.
&lt;ul&gt;
&lt;li&gt;While we can manually unsubscribe from observables and even unsubscribe from multiple observables at once by having a single subscription to which we add more subscriptions and then unsubscribe from, it is easier to react to a value emitted from a &lt;code&gt;Subject&lt;/code&gt; like &lt;code&gt;onDestroy$&lt;/code&gt; by appending &lt;code&gt;takeUntil(onDestroy$)&lt;/code&gt; to the pipeline. Whenever &lt;code&gt;onDestroy$.next()&lt;/code&gt; is called, any subscription depending on it will unsubscribe automatically, then &lt;code&gt;onDestroy$.complete()&lt;/code&gt; will clean up unused resources.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Use partition and filter for conditional logic.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;filter&lt;/code&gt; can be very useful when a single type of output is expected, filtering values before they reach the end of the stream.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;partition&lt;/code&gt; will return two observables, which we can destructure. The first one will filter and catch emissions that match our condition and the other stream will contain the values that didn&amp;rsquo;t match the condition. This is useful to avoid creating two different streams when there&amp;rsquo;s an &lt;code&gt;if&lt;/code&gt;/&lt;code&gt;else&lt;/code&gt; situation where two types of outputs would be expected.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;RxJS is a powerful yet complex toolkit for managing asynchronous data flows. While the learning curve can be steep, the benefits are significant for reactive web apps and complex logic with many asynchronous operations. With practice, RxJS makes it possible to write declarative code that reacts to real-time data updates. By modeling everything as streams of data and applying transformations, RxJS helps tackle challenging programming tasks in a maintainable way. While it takes effort to master, reactive programming with RxJS might just be an essential skill for modern JavaScript/TypeScript developers working on reactive web applications!&lt;/p&gt;
&lt;h2 id="resources-and-references"&gt;Resources and references&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/office/dev/add-ins/excel/?view=excel-js-preview"&gt;Excel add-ins documentation&lt;/a&gt;, Microsoft&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.learnrxjs.io/"&gt;Learn RxJS&lt;/a&gt;, Learn RxJS&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rxjs.dev/"&gt;RxJS - Reactive Extensions Library for JavaScript&lt;/a&gt;, RxJS&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=lkUrkNdczpI&amp;amp;list=PLj2oFNVaxfJ8nRFUA2CLyt8TymA0_vQux&amp;amp;index=1"&gt;RxJS &amp;amp; Reactive Programming&lt;/a&gt;, Josh Ribakoff&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rxjs.dev/guide/testing/marble-testing"&gt;RxJS Marbles - Testing&lt;/a&gt;, RxJS&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rxmarbles.com/"&gt;RxJS Marbles&lt;/a&gt;, RxJS Marbles&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Sun, 01 Oct 2023 19:45:00 -0600</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/first-steps-with-rxjs/</guid><category>tools</category><category>functional-programming</category><category>reactive-programming</category><category>rxjs</category><category>typescript</category></item><item><title>Uncovering some of the merits of text-based diagrams</title><link>https://www.sglavoie.com/posts/uncovering-some-of-the-merits-of-text-based-diagrams/</link><description>Every once in a short while, new digital technology comes out to iterate on existing products. Software targeting technical design (e.g., flowcharts) is no exception. Yet, there is still room to enjoy older tools that work simply… and simply work!</description><content:encoded>&lt;hr&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Ever since I was introduced to text-based diagrams through UML and more concretely thanks to some fanatic acquaintance who shall remain nameless (else he will turn &lt;em&gt;rufous&lt;/em&gt;), I was hooked by their simplicity, flexibility, predictability of presentation and usefulness. While they can be customized up to a point to look a bit better, the goal is really about efficiency. I came to peace with the fact that the final product will often look different from what I&amp;rsquo;d like to achieve. After all, one of the possible applications of text-based diagrams is to quickly share ideas that can be iterated upon without too much fuss and without slowing down to make things pretty. True, the arrows might not be placed precisely where I&amp;rsquo;d want them to show up, but the time savings gained by not having to re-arrange boxes and accommodate a canvas are hefty compensation prices! Besides, some tools in that space integrate really well on platforms like GitHub and GitLab without requiring any special piece of software to be produced or viewed, which makes them particularly convenient.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="plantuml-an-old-love-affair"&gt;PlantUML: an old love affair&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://plantuml.com/"&gt;PlantUML&lt;/a&gt; has been around for a while &amp;ndash; over &lt;strong&gt;12&lt;/strong&gt; years at this point! It is a bit cumbersome to use as it requires a server running with Java to do its job. There are good integrations for it though, including &lt;a href="https://marketplace.visualstudio.com/items?itemName=jebbs.plantuml"&gt;a VS Code extension&lt;/a&gt; and a &lt;a href="https://hub.docker.com/r/plantuml/plantuml-server"&gt;Docker image&lt;/a&gt;. Its documentation is pretty good and the diagrams are more easily configurable out of the box than with some other similar tools.&lt;/p&gt;
&lt;p&gt;Even when starting with an unremarkable use case diagram such as the following one, ideas can be shared efficiently without having to go to a drawing board.&lt;/p&gt;
&lt;p&gt;&lt;img
 src="https://www.sglavoie.com/images/posts/0033_text_based_diagrams/use_case_simplest.png"
 alt="Simple PlantUML use case diagram for end-user complaint handling"
 loading="lazy"
 decoding="async" width="703" height="478"/&gt;
&lt;/p&gt;
&lt;p&gt;Actually, the code responsible for generating this diagram is as follows:&lt;/p&gt;
&lt;div class="code-block code-block--wrap"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@startuml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;package Users {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; :End User 1: as EndUser1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; :End User 2: as EndUser2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; :User:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; User &amp;lt;|-- EndUser1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; User &amp;lt;|-- EndUser2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;package &amp;#34;End User actions&amp;#34; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (Escalate a case) as UC1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (Mark a case solved) as UC2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (Communicate with fellow citizens) as UC3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;EndUser1 -- UC3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;EndUser2 -- UC3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;note bottom of UC1 : Under specific conditions,\ne.g. after X amount of time
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@enduml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;While this may look like an unfamiliar syntax at first, it is concise, expressive and can be used for many types of UML diagrams. For instance, to take a different use case a notch further, it is not much work:&lt;/p&gt;
&lt;p&gt;&lt;img
 src="https://www.sglavoie.com/images/posts/0033_text_based_diagrams/use_case_simple.png"
 alt="Expanded PlantUML use case diagram including admin actions"
 loading="lazy"
 decoding="async" width="796" height="633"/&gt;
&lt;/p&gt;
&lt;div class="code-block code-block--wrap"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@startuml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;package Users {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; :Admin:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; :End User: as EndUser
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; :User:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;package &amp;#34;Admin actions&amp;#34; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (Identify users) as UC1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (Forward complaints) as UC2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (Give status update) as UC3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (Mark case solved) as UC4
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (Notify by email) as UC5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;package &amp;#34;End User actions&amp;#34; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (File a complaint) as UC6
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;User &amp;lt;|-- Admin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;User &amp;lt;|-- EndUser
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;UC1 &amp;lt;-- UC6
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;UC5 &amp;lt;.. UC2 : &amp;lt;&amp;lt; include &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;UC5 &amp;lt;.. UC3 : &amp;lt;&amp;lt; include &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;UC5 &amp;lt;.. UC4 : &amp;lt;&amp;lt; include &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;EndUser -- UC6
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Admin -- UC1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@enduml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Sky is the limit! For example, drawing from scratch the following diagram may start to get messy when done manually:&lt;/p&gt;
&lt;p&gt;&lt;img
 src="https://www.sglavoie.com/images/posts/0033_text_based_diagrams/use_case_more_advanced.png"
 alt="Advanced PlantUML use case diagram covering users, admins, and ISPs"
 loading="lazy"
 decoding="async" width="952" height="1007"/&gt;
&lt;/p&gt;
&lt;p&gt;This is when PlantUML starts to shine as it puts elements in sensible locations without one having to think about it &amp;ndash; or endlessly tinker to make them fit on a predefined area. Regardless of the relative visual complexity achieved, the code behind the scenes is still quite understandable and manageable:&lt;/p&gt;
&lt;div class="code-block code-block--wrap"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@startuml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;package Users {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; :ISP:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; :End User: as EndUser
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; :Admin:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; User &amp;lt;|-- EndUser
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; User &amp;lt;|-- ISP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; User &amp;lt;|-- Admin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;package &amp;#34;End User actions&amp;#34; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (File a complaint) as UC1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (Receive update on complaint) as UC9
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;package &amp;#34;Admin actions&amp;#34; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (Forward complaint to ISP) as UC2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (Receive feedback from ISP) as UC5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (Notify End User of progress on complaint) as UC6
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;package &amp;#34;ISP actions&amp;#34; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (Receive End User complaint) as UC8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (Reply to End User complaint) as UC3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (Mark complaint solved) as UC4
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (Inform Admin) as UC7
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;UC1 --&amp;gt; UC2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;UC2 --&amp;gt; UC8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;UC3 ..&amp;gt; UC7 : &amp;lt;&amp;lt; include &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;UC4 ..&amp;gt; UC7 : &amp;lt;&amp;lt; include &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;UC8 &amp;lt;.. UC3 : &amp;lt;&amp;lt; extend &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;UC3 &amp;lt;.. UC4 : &amp;lt;&amp;lt; extend &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;UC7 --&amp;gt; UC5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;UC5 ..&amp;gt; UC6 : &amp;lt;&amp;lt; include &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;UC6 --&amp;gt; UC9
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Admin -- UC2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;EndUser -left- UC1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ISP -- UC8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@enduml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;As can be seen, elements can be described on their own (here with a &lt;code&gt;package&lt;/code&gt;) and linked together in a clear way (with custom arrows to specify the connection type, length and direction, e.g. &lt;code&gt;--&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;..&lt;/code&gt; or &lt;code&gt;--&lt;/code&gt;). Of course, as with any programming task, it helps to adopt pertinent naming conventions&amp;hellip;&lt;/p&gt;
&lt;p&gt;Beyond use case diagrams, PlantUML is also nice for producing all sorts of outputs, for instance a component diagram (as seen in &lt;a href="https://www.sglavoie.com/posts/a-retrospective-on-creating-an-impractical-little-tool-just-for-fun/"&gt;A retrospective on creating an impractical little tool just for fun&lt;/a&gt;):&lt;/p&gt;
&lt;p&gt;&lt;img
 src="https://www.sglavoie.com/images/posts/0030_a_retrospective_on_creating_an_impractical_little_tool_just_for_fun/ugc-server.png"
 alt="PlantUML component diagram for the UGC server"
 loading="lazy"
 decoding="async" width="634" height="466"/&gt;
&lt;/p&gt;
&lt;p&gt;It can also handle other types of diagrams: sequence; class; activity; state; deployment; network; etc. One other option that I have found useful is its ability to create Gantt charts that are easily programmable! This one, for example, which was used to distribute human resources across a small project done in &lt;a href="https://www.london.ac.uk/courses/computer-science"&gt;a computer science degree at the University of London&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img
 src="https://www.sglavoie.com/images/posts/0033_text_based_diagrams/gantt_chart.png"
 alt="PlantUML Gantt chart for the DefineBroadband software project"
 loading="lazy"
 decoding="async" width="801" height="653"/&gt;
&lt;/p&gt;
&lt;p&gt;Its accompanying code was easy to re-use to cut parts of the diagram into sections that could be presented on their own:&lt;/p&gt;
&lt;div class="code-block code-block--wrap"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@startuml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#39; Metadata
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hide footbox
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;title Software Project Proposal — DefineBroadband
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;footer By Kris, Srijith and Sébastien
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;printscale weekly
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#39; Coursework 1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-- Phase 1 --
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-- Planning --
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Project starts the 2020-11-02
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Discuss project] as [DP] starts 2020-11-02 and ends 2020-11-09
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;note bottom
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Define team rules, project ideation, setting Git, define set of tools, etc.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;end note
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Competitor analysis] as [CA] starts 2020-11-16 and ends 2020-11-28
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[User survey and results analysis] starts 2020-11-14 and ends 2020-12-04
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Market research] as [MR] starts 2020-11-09 and ends 2020-12-04
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Requirements modelling (UML)] as [RM] starts 2020-12-14 and ends 2020-12-20
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Requirements/Specifications] as [RS] starts 2020-12-04 and ends 2020-12-20
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Proposal writing: Part 1] as [PW1] ends 2021-01-09
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Review process] starts 2021-01-03 and ends 2021-01-09
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[RS] --&amp;gt; [PW1]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[DP] --&amp;gt; [MR]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[MR] --&amp;gt; [RS]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-- Development --
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Prototyping design] as [PD0] ends 2021-01-04
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Investigate frameworks and online services] starts 2020-12-16 and ends 2021-01-06
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[RS] --&amp;gt; [Prototyping design]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#39; Coursework 2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-- Phase 2 --
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-- Planning --
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Proposal writing: Part 2] starts 2021-01-13 and ends 2021-03-13
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;note bottom
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Includes report and documentation
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;end note
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Verification and validation] starts 2021-01-25 and ends 2021-03-13
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Reflections on project] starts 2021-03-01 and ends 2021-03-13
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Final preparation] starts 2021-03-08 and ends 2021-03-13
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-- Development --
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Writing software - TDD approach] starts 2021-01-11 and ends 2021-03-13
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Functional testing with users] starts 2021-01-25 and ends 2021-03-13
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-- Development milestones --
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[User Database] happens 2021-01-06
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[User Registration] happens 2021-01-06
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Landing Site] happens 2021-01-20
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Initial Survey Collection] happens 2021-01-20
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Identifying your location on a map to update the GIS database] happens 2021-01-30
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Message Boards] happens 2021-02-15
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Regulatory Complaint Collection and Forwarding] happens 2021-03-10
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-- Project milestones --
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Prototypes done] happens at [PD0]&amp;#39;s end
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Proposal delivery] as [PD1] happens 2021-01-11
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[Project delivery] as [PD2] happens 2021-03-15
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[PD2] displays on same row as [PD1]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#39; Closed days: holidays
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2020-12-24 to 2020-12-25 is closed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2020-12-31 to 2021-01-01 is closed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#39; Closed days: margins before delivery
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2021-01-11 is closed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2021-03-15 is closed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@enduml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;It was in fact relatively frictionless to use this diagram as a template for a version 2:&lt;/p&gt;
&lt;p&gt;&lt;img
 src="https://www.sglavoie.com/images/posts/0033_text_based_diagrams/gantt_chart_updated.png"
 alt="Updated PlantUML Gantt chart derived from the original project plan"
 loading="lazy"
 decoding="async" width="1653" height="1124"/&gt;
&lt;/p&gt;
&lt;p&gt;This tool is incredibly awesome, yet it&amp;rsquo;s not as versatile as it could be. This is mainly due to the fact that while the output can be scaled as needed to get high quality diagrams, its format isn&amp;rsquo;t supported yet on GitHub (although &lt;a href="https://gitlab.com/gitlab-com/gl-infra/readiness/blob/master/plantuml/overview.md"&gt;GitLab does work with it without hassle&lt;/a&gt;). For this reason, I&amp;rsquo;ve looked elsewhere for greener grass and found an alternative for publishing on GitHub.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="mermaid-the-new-muse"&gt;Mermaid: the new muse&lt;/h2&gt;
&lt;p&gt;Born roughly 4 years after PlantUML, &lt;a href="https://mermaid-js.github.io/mermaid/"&gt;Mermaid&lt;/a&gt; has seen some mesmerizing growth in the open-source world (currently at &lt;a href="https://github.com/mermaid-js/mermaid"&gt;github.com/mermaid-js/mermaid&lt;/a&gt;, it has more than 7 times the number of GitHub stars that &lt;a href="https://github.com/plantuml/plantuml"&gt;PlantUML&lt;/a&gt; has!). While I personally enjoy looking at the old-school output produced by PlantUML and have found that diagrams produced by it come out with better spacing and flows on average compared to Mermaid, I like even more the native GitHub support for Mermaid straight from Markdown files (by the way, GitLab supports both PlantUML and Mermaid)! It makes it a powerful tool to use because the diagram is embedded within the Markdown file itself, perfect to keep it under revision while avoiding commits with heavy binary assets.&lt;/p&gt;
&lt;p&gt;Mermaid does not support as many diagram types as PlantUML, but it certainly works well for the main ones, which would include flowcharts, sequence diagrams, class diagrams, ER diagrams and Gantt diagrams. Its documentation is less dense and complete than that of PlantUML and diagrams are less customizable without messing around with external CSS files (which can&amp;rsquo;t be easily linked on every platform), but for the sheer convenience of being able to visualize diagrams from Markdown files on GitHub, it made it easier to switch over. Working locally with it is also a breeze as there is no need to point to a Java installation or have a server running from a Docker container when using, for instance, &lt;a href="https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid"&gt;this VS Code extension&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Mermaid is great for flowcharts, although I have found myself in need of workarounds more often than with PlantUML for sure. For example, the following diagram was relatively painless to come up with:&lt;/p&gt;
&lt;p&gt;&lt;img
 src="https://www.sglavoie.com/images/posts/0033_text_based_diagrams/mermaid_flowchart.png"
 alt="Mermaid flowchart showing a report-processing workflow"
 loading="lazy"
 decoding="async" width="827" height="979"/&gt;
&lt;/p&gt;
&lt;p&gt;As can be observed in its source code below, I had some issues with content alignment (plus, label positioning isn&amp;rsquo;t always optimal as can be seen with a particular &amp;lsquo;&lt;code&gt;Yes&lt;/code&gt;&amp;rsquo; in this case). That can often be fixed by changing the orientation of the diagram or by modifying the connection length between rectangles, but still.&lt;/p&gt;
&lt;div class="code-block code-block--wrap"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flowchart TB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;%% Define names
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;start[Start]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;processReport[Process report]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uploadMetadata[Upload metadata]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;triggerCloudFunction[Trigger Cloud Function]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;convertCSV[Convert CSV to Feather]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;lambdaFunctions[Lambda functions]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;%% Square ones with single brackets take too much space
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;newReportQ{{New report?}}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;newMetadataQ{{New metadata?}}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;reportValidQ{{Is report and is valid?}}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;csvFeatherValidQ{{Is metadata and is valid?}}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;stop[Stop]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;%% Outside subgraphs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;start --&amp;gt; lambdaFunctions
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uploadMetadata --&amp;gt; start
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;convertCSV --&amp;gt; start
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;newReportQ --&amp;gt;|No| stop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;newMetadataQ --&amp;gt;|No| stop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;reportValidQ --&amp;gt;|No| stop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;csvFeatherValidQ --&amp;gt;|No| stop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;subgraph AWS
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;lambdaFunctions --&amp;gt; newReportQ
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;lambdaFunctions --&amp;gt; newMetadataQ
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;end
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;%% Can&amp;#39;t align properly without extra spaces...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;subgraph &amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbsp&amp;amp;nbspCloud function
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;triggerCloudFunction --&amp;gt; csvFeatherValidQ
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;newReportQ --&amp;gt;|Yes| triggerCloudFunction
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;newMetadataQ --&amp;gt;|Yes| triggerCloudFunction
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;triggerCloudFunction --&amp;gt; reportValidQ
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;end
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;subgraph VM 1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;reportValidQ --&amp;gt;|Yes| processReport
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;processReport --&amp;gt; uploadMetadata
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;end
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;subgraph VM 2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;csvFeatherValidQ --&amp;gt;|Yes| convertCSV
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The syntax is straightforward to follow. Some shapes could take less space (the diamond/rhombus ones in particular), which is why I ended up using hexagonal nodes instead in this example. Overall, the experience is satisfying and makes it so easy to keep iterating and commit multiple versions of the work without taking much disk space at all in the Git repository! Admittedly, I&amp;rsquo;d rather work with PlantUML for other types of diagrams as I don&amp;rsquo;t personally use most of the ones Mermaid has to offer (e.g., pie charts, requirement diagrams, git graphs or user journeys) and I prefer the output from PlantUML for the other ones.&lt;/p&gt;
&lt;p&gt;One thing I really like about flowcharts in Mermaid is how easy it is to nest graphs, which often increases clarity without making it difficult to create or update:&lt;/p&gt;
&lt;p&gt;&lt;img
 src="https://www.sglavoie.com/images/posts/0033_text_based_diagrams/mermaid_flowchart_nested.png"
 alt="Mermaid flowchart with nested decisions and loops"
 loading="lazy"
 decoding="async" width="1132" height="993"/&gt;
&lt;/p&gt;
&lt;p&gt;While this isn&amp;rsquo;t perfect, it&amp;rsquo;s good enough to show the architecture of a system at a basic level.&lt;/p&gt;
&lt;div class="code-block code-block--wrap"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flowchart TB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;%% Looping over regions to fetch and update
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;subgraph LoopOverRegionsUpdate[ ]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; %%%% Looping over days within each region
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; subgraph LoopOverDaysUpdate[2.]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; loopDays[Fetch day metadata]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; updateRegion[Update region-level locally]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; loopDays
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --&amp;gt;|then| updateRegion
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --&amp;gt; loopDays
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; end %%%%
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fetchRegionMetadata[1. Fetch region metadata]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fetchRegionMetadata
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--&amp;gt;|then, loop over days| LoopOverDaysUpdate
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--&amp;gt; fetchRegionMetadata
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;end %%
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;%% Looping over regions to upload
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;subgraph loopOverRegionsUpload[ ]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;forEachRegion[For each region]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uploadRegion[Upload metadata]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;forEachRegion
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--&amp;gt; uploadRegion
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--&amp;gt; forEachRegion
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;end %%
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;%% Setting main node values
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;start[Automation runs]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;spawns[Spawns VM to update metadata]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fetchTopLevelMetadata[Fetch top-level metadata]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;updateTop[Update top-level metadata locally]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uploadTop[Upload top-level metadata]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;resize[Resize instance group to zero to shut down] %%
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;%% Fetch and update steps
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;subgraph fetchAndUpdate[Fetch and update everything locally before uploading back to the bucket]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fetchTopLevelMetadata
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--&amp;gt;|then, loop over regions| LoopOverRegionsUpdate
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--&amp;gt; updateTop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;end
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;%% Main flow
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;start
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--&amp;gt; spawns
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--&amp;gt; fetchAndUpdate
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--&amp;gt;|once all processing is done, upload everything| loopOverRegionsUpload
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--&amp;gt; uploadTop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--&amp;gt; resize %%&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Well, it&amp;rsquo;s just a matter of taste and in reality, I wouldn&amp;rsquo;t use either PlantUML or Mermaid for fancier-looking results or for something that would be a real mess to follow textually. For these purposes, I would go with something like &lt;a href="https://app.diagrams.net/"&gt;Draw.io&lt;/a&gt; or &lt;a href="https://www.figma.com/"&gt;Figma&lt;/a&gt;, although with the recent &lt;a href="https://news.adobe.com/news/news-details/2022/Adobe-to-Acquire-Figma/default.aspx"&gt;acquisition of Figma by Adobe&lt;/a&gt;, I might have to switch to another option, like &lt;a href="https://www.framer.com/"&gt;Framer&lt;/a&gt; or the promising open-source project &lt;a href="https://github.com/penpot/penpot"&gt;Penpot&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="when-text-based-is-too-clunky"&gt;When text-based is too clunky&lt;/h2&gt;
&lt;p&gt;Even though I love the possibilities offered by tools like PlantUML and Mermaid (we didn&amp;rsquo;t even touch on their theming capabilities!), there are instances where it&amp;rsquo;s just not as convenient. While it&amp;rsquo;s possible to &lt;a href="https://plantuml.com/salt"&gt;do wireframing with PlantUML&lt;/a&gt;, there is no way it can compete with some software like &lt;a href="https://balsamiq.com/"&gt;Balsamiq&lt;/a&gt; on that front. Likewise, it&amp;rsquo;s going to be difficult to create customized diagrams like the following ones. With &lt;strong&gt;Figma&lt;/strong&gt;, I could create this somewhat original-looking output:&lt;/p&gt;
&lt;p&gt;&lt;img
 src="https://www.sglavoie.com/images/posts/0033_text_based_diagrams/figma_custom_er_diagram.png"
 alt="Custom ER diagram drawn in Figma"
 loading="lazy"
 decoding="async" width="1600" height="845"/&gt;
&lt;/p&gt;
&lt;p&gt;Similarly, I could customize this ER diagram with &lt;strong&gt;Draw.io&lt;/strong&gt; to get something that would be nearly impossible to do with text-based diagrams in terms of presentation:&lt;/p&gt;
&lt;p&gt;&lt;img
 src="https://www.sglavoie.com/images/posts/0033_text_based_diagrams/drawio_er_diagram.png"
 alt="ER diagram exported from draw.io"
 loading="lazy"
 decoding="async" width="2620" height="2403"/&gt;
&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Text-based diagrams are clearly not a panacea, but neither are they archaic vestiges with no modern use! When they are used for what they are good for &amp;ndash; mainly &lt;em&gt;UML&lt;/em&gt; diagrams &amp;ndash; they get the job done quickly, consistently and appropriately based on UML standards that have been around for eons. They remove friction by not having to think about design and rather allow one to go deeper on the actual links to be created. While I&amp;rsquo;d often sketch early proposals roughly with pen and paper, I would take the next step in formalizing plans digitally with something like PlantUML or Mermaid. I would then jump from there as needed to other tools that are more flexible but also quite likely more time-consuming to create high-fidelity wireframes or mockups, but the key &amp;ndash; in my mind at least &amp;ndash; is to remember that &lt;em&gt;different tools have different purposes and it&amp;rsquo;s nearly always a great idea to use the one best suited for the job&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="resources-and-references"&gt;Resources and references&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://news.adobe.com/news/news-details/2022/Adobe-to-Acquire-Figma/default.aspx"&gt;Adobe to Acquire Figma&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://balsamiq.com/"&gt;Balsamiq&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://app.diagrams.net/"&gt;Draw.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.figma.com/"&gt;Figma&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.framer.com/"&gt;Framer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mermaid-js/mermaid"&gt;github.com/mermaid-js/mermaid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid"&gt;Markdown Preview Mermaid Support - VS Code extension&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mermaid-js.github.io/mermaid/"&gt;Mermaid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/penpot/penpot"&gt;Penpot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/plantuml/plantuml"&gt;PlantUML - GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=jebbs.plantuml"&gt;PlantUML - VS Code extension&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/gitlab-com/gl-infra/readiness/blob/master/plantuml/overview.md"&gt;PlantUML integration for GitLab.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/r/plantuml/plantuml-server"&gt;PlantUML server - DockerHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://plantuml.com/"&gt;PlantUML&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.london.ac.uk/courses/computer-science"&gt;University of London&amp;rsquo;s BSc computer science degree&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://plantuml.com/salt"&gt;Wireframing with PlantUML - Salt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Sat, 19 Nov 2022 21:00:00 -0600</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/uncovering-some-of-the-merits-of-text-based-diagrams/</guid><category>tools</category><category>customization</category><category>markdown</category><category>uml</category></item><item><title>A Guided Tour of an i3 Configuration</title><link>https://www.sglavoie.com/posts/a-guided-tour-of-an-i3-configuration/</link><description>This article will go over my dear i3 configuration file, which contains quite a few sections I hope I had stumbled upon right from the start when I got introduced to the tiling window manager world. It would probably have been less scary and intimidating. May you find the shortcut you needed!</description><content:encoded>&lt;hr&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;In &lt;a href="https://www.sglavoie.com/posts/a-guided-tour-of-an-i3-configuration/"&gt;Using i3 as a Window Manager for Increased Productivity&lt;/a&gt;, a number of reasons were given to get going with this fantastic tiling window manager. Now, it&amp;rsquo;s time to jump in and explore how to actually use i3! If you&amp;rsquo;re looking for an in-depth guide, the &lt;a href="https://i3wm.org/docs/userguide.html"&gt;official i3 documentation&lt;/a&gt; or the &lt;a href="https://wiki.archlinux.org/index.php/i3"&gt;i3 page on the Arch Wiki&lt;/a&gt; are awesome resources to dive deeper. Here, we&amp;rsquo;ll focus on day-to-day usage based on my current configuration file which I have used and refined over the past three years or so. I hope that you&amp;rsquo;ll be able to glean useful tips and tricks to apply to your own use case.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-configuration-file"&gt;The Configuration File&lt;/h2&gt;
&lt;p&gt;To set keybindings, it &lt;em&gt;may&lt;/em&gt; be handy to know the keycodes and their respective names. To get a full list, you can use &lt;code&gt;xmodmap -pk&lt;/code&gt; in the terminal. If you&amp;rsquo;re not sure of the name of the key you want to press (is it &amp;ldquo;PageDown&amp;rdquo;, &amp;ldquo;PageDwn&amp;rdquo; or &amp;ldquo;Next&amp;rdquo;?), you can retrieve it by executing the &lt;code&gt;xev&lt;/code&gt; command, which will open two windows: one for capturing the actual key press and the other one to display the relevant details for that key. For example, if you press the backspace key, the output will contain something like &lt;code&gt;keycode 22 (keysym 0xff08, BackSpace)&lt;/code&gt;. From there, you will know that a mapping would be of the form &lt;code&gt;mod+BackSpace&lt;/code&gt;, where &lt;code&gt;mod&lt;/code&gt; is the modifier of your choice that is usually specified at the top of the configuration file. Now we can start!&lt;/p&gt;
&lt;h2 id="i3-config-shortcuts"&gt;i3 config shortcuts&lt;/h2&gt;
&lt;div class="code-block code-block--wrap"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight" wrap="true"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Paths and names&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$brightness&lt;/span&gt;-laptop-down &lt;span class="s2"&gt;&amp;#34;light -U 1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$brightness&lt;/span&gt;-laptop-up &lt;span class="s2"&gt;&amp;#34;light -A 1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$brightness&lt;/span&gt;-monitor-default &lt;span class="s2"&gt;&amp;#34;python3 ~/.local/bin/monitor_brightness default&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$brightness&lt;/span&gt;-monitor-down &lt;span class="s2"&gt;&amp;#34;python3 ~/.local/bin/monitor_brightness down&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$brightness&lt;/span&gt;-monitor-up &lt;span class="s2"&gt;&amp;#34;python3 ~/.local/bin/monitor_brightness up&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$brightness&lt;/span&gt;-toggle-laptop ~/.local/bin/toggle_laptop_brightness
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$default&lt;/span&gt;-web-browser brave-browser-stable
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$launcher&lt;/span&gt;-python-term ~/.config/ipython_launch.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$launcher&lt;/span&gt;-temp-notes ~/.config/tmpnotes_launch.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$launcher&lt;/span&gt;-todoist ~/.config/todoist_launch.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt;-xrandr-both ~/.config/i3/run_xrandr.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt;-xrandr-laptop ~/.config/i3/run_xrandr_laptop.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt;-xrandr-monitor ~/.config/i3/run_xrandr_monitor.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$screenshots&lt;/span&gt;-folder ~/Pictures/screenshots/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$todoist&lt;/span&gt;-app-name &lt;span class="s2"&gt;&amp;#34;crx_elldfnmogicegdcphgljaoaklkpcnbnn&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$toggle&lt;/span&gt;-mic-on-off ~/.local/bin/toggle_mic_source
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# i3 specific&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$center&lt;/span&gt;-mouse &lt;span class="nb"&gt;exec&lt;/span&gt; ~/.config/i3/center-mouse
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$default&lt;/span&gt;-web-browser-class &lt;span class="s2"&gt;&amp;#34;(?i)brave-browser&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$exec&lt;/span&gt; &lt;span class="nb"&gt;exec&lt;/span&gt; --no-startup-id
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$output&lt;/span&gt;-primary output eDP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$output&lt;/span&gt;-secondary output HDMI-A-0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$python&lt;/span&gt;-term &lt;span class="s2"&gt;&amp;#34;ipythonterm&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$scratchpad&lt;/span&gt;-size &lt;span class="m"&gt;1920&lt;/span&gt; &lt;span class="m"&gt;1060&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$temp&lt;/span&gt;-notes &lt;span class="s2"&gt;&amp;#34;tmpnotes&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$volumepath&lt;/span&gt; ~/.config/i3-volume/volume
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# set mod key (Mod1=Alt, Mod4=Super)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ⇒ the main modifier used to trigger shortcuts&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$mod&lt;/span&gt; Mod4
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Workspaces&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#######################################&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Workspace names — can be customized with emojis and names&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ⇒ I find I like minimalism best and just mentally associate&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# one workspace number with a specific type of task&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ⇒ those are set across all monitors&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$ws1&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$ws2&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$ws3&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$ws4&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;4&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$ws5&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;5&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$ws6&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;6&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$ws7&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;7&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$ws8&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;8&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$ws9&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;9&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$ws10&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;10&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Modes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#######################################&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# set shut down, restart and locking features&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ⇒ type `$mod+Shift+Delete` then either `e`, `r` or `s`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mode &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$mode_system&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bindsym e &lt;span class="nb"&gt;exec&lt;/span&gt; --no-startup-id i3-msg &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bindsym r &lt;span class="nb"&gt;exec&lt;/span&gt; --no-startup-id systemctl reboot
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bindsym s &lt;span class="nb"&gt;exec&lt;/span&gt; --no-startup-id systemctl poweroff
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# exit system mode with &amp;#34;Enter&amp;#34; or &amp;#34;Escape&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bindsym Return mode &lt;span class="s2"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bindsym Escape mode &lt;span class="s2"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$mode_system&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;e&lt;span class="o"&gt;)&lt;/span&gt;xit, &lt;span class="o"&gt;(&lt;/span&gt;r&lt;span class="o"&gt;)&lt;/span&gt;eboot, &lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;hutdown
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+Delete mode &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$mode_system&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# resize with smaller steps to get more control&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mode &lt;span class="s2"&gt;&amp;#34;resize&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bindsym h resize shrink width &lt;span class="m"&gt;2&lt;/span&gt; px or &lt;span class="m"&gt;2&lt;/span&gt; ppt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bindsym j resize grow height &lt;span class="m"&gt;2&lt;/span&gt; px or &lt;span class="m"&gt;2&lt;/span&gt; ppt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bindsym k resize shrink height &lt;span class="m"&gt;2&lt;/span&gt; px or &lt;span class="m"&gt;2&lt;/span&gt; ppt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bindsym l resize grow width &lt;span class="m"&gt;2&lt;/span&gt; px or &lt;span class="m"&gt;2&lt;/span&gt; ppt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# exit resize mode: Enter or Escape&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bindsym Return mode &lt;span class="s2"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bindsym Escape mode &lt;span class="s2"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+r mode &lt;span class="s2"&gt;&amp;#34;resize&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Show the next scratchpad window or hide the focused scratchpad window.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# If there are multiple scratchpad windows, this command cycles through them.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# There is an additional keybinding to allow a floating window to be toggled&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# (recovered as a normal window).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# keycode 179 = F10 button&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# (not the actual F10 key, but same button on my laptop with default function!)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mode &lt;span class="s2"&gt;&amp;#34;Scratchpad&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bindcode &lt;span class="m"&gt;179&lt;/span&gt; scratchpad show, resize &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="m"&gt;1920&lt;/span&gt; 1060, move position center&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; scratchpad show, resize &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="m"&gt;1920&lt;/span&gt; 1060, move position center
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+space floating toggle&lt;span class="p"&gt;;&lt;/span&gt; mode &lt;span class="s2"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Escape kill&lt;span class="p"&gt;;&lt;/span&gt; mode &lt;span class="s2"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bindsym Escape scratchpad show&lt;span class="p"&gt;;&lt;/span&gt; mode &lt;span class="s2"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindcode &lt;span class="m"&gt;179&lt;/span&gt; mode &lt;span class="s2"&gt;&amp;#34;Scratchpad&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; scratchpad show, resize &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="m"&gt;1920&lt;/span&gt; 1060, move position center
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Caps Lock indicator, seen at&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# https://www.reddit.com/r/i3wm/comments/k8m4k4/share_your_i3_tips_and_tricks_that_you_have/gf2lppd&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mode &lt;span class="s2"&gt;&amp;#34;-------------------- CAPSLOCK --------------------&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bindsym Caps_Lock mode &lt;span class="s2"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym Caps_Lock mode &lt;span class="s2"&gt;&amp;#34;-------------------- CAPSLOCK --------------------&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Sound&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#######################################&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$statuscmd&lt;/span&gt; i3status
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Signal used to update the status line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$statussig&lt;/span&gt; SIGUSR1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Amount to increase/decrease volume as a percentage&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$volumestep&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Font&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#######################################&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# font for window titles&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ⇒ this is what you see in your i3bar&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$font&lt;/span&gt;-small font xft:Lato &lt;span class="m"&gt;12&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$font&lt;/span&gt;-medium font xft:Lato &lt;span class="m"&gt;14&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Set default font&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$font&lt;/span&gt;-small
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Font used with Rofi (dmenu alternative)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$rofi&lt;/span&gt;-font &lt;span class="s1"&gt;&amp;#39;JetBrainsMono 16&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Keyboard&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#######################################&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Set the compose key to right Control&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$compose&lt;/span&gt;-key &lt;span class="s2"&gt;&amp;#34;compose:rctrl&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;A few notes on the &lt;strong&gt;i3 config shortcuts&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This section appears at the top because it will affect how i3 works in general.&lt;/li&gt;
&lt;li&gt;It makes extensive use of the &lt;code&gt;set&lt;/code&gt; command so that all important shortcuts and paths to programs that could &amp;ldquo;break&amp;rdquo; if they are missing are easy to configure globally.&lt;/li&gt;
&lt;li&gt;It sets all the different modes that i3 can use so it can be extended in one single place.&lt;/li&gt;
&lt;li&gt;I use &lt;code&gt;Alt&lt;/code&gt; in a few applications and reserve &lt;code&gt;Super&lt;/code&gt; for the window manager only.&lt;/li&gt;
&lt;li&gt;Find the font name you need with &lt;code&gt;fc-list&lt;/code&gt;. For instance, to use JetBrains&amp;rsquo; font: &lt;code&gt;fc-list | grep Jet&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="general-settings"&gt;General settings&lt;/h2&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# launch a terminal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Return &lt;span class="nb"&gt;exec&lt;/span&gt; kitty
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# launch a terminal in the same directory currently used&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+Return &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;xcwd&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exec&lt;/span&gt; kitty
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# kill focused window&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ⇒ usual way to close applications&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Escape &lt;span class="nb"&gt;kill&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+mod1+Escape --release &lt;span class="nv"&gt;$exec&lt;/span&gt; xkill
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# reload the configuration file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ⇒ apply changes from this file on the fly&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+c reload
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# restart i3 inplace&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ⇒ preserves your layout/session, can be used when upgrading i3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+r restart
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# exit i3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ⇒ logs you out of your X session&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+e &lt;span class="nv"&gt;$exec&lt;/span&gt; i3-msg &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# resize windows&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# you can also use the mouse for that:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# for tiled windows ⇒&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# drag the border with LeftClick or RightClick&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# for floating windows ⇒&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# `mod + RightClick`, then drag&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# resize without entering &amp;#34;resize mode&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ⇒ I find this more convenient. I usually don&amp;#39;t care about&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# precise window sizes, so here the steps are far greater.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# If I want more control, then entering resize mode will do.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Control+l resize shrink width &lt;span class="m"&gt;10&lt;/span&gt; px or &lt;span class="m"&gt;10&lt;/span&gt; ppt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Control+k resize grow height &lt;span class="m"&gt;10&lt;/span&gt; px or &lt;span class="m"&gt;10&lt;/span&gt; ppt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Control+j resize shrink height &lt;span class="m"&gt;10&lt;/span&gt; px or &lt;span class="m"&gt;10&lt;/span&gt; ppt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Control+h resize grow width &lt;span class="m"&gt;10&lt;/span&gt; px or &lt;span class="m"&gt;10&lt;/span&gt; ppt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;A few notes on the &lt;strong&gt;General settings&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Resizing and moving floating windows with the mouse also works great: &lt;code&gt;mod+LeftClick&lt;/code&gt; to move around and &lt;code&gt;mod+RightClick&lt;/code&gt; to resize. This way, it&amp;rsquo;s also possible to move windows to other screens.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="screens-and-monitors"&gt;Screens and monitors&lt;/h2&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# lock the screens with a black color background&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Delete &lt;span class="nv"&gt;$exec&lt;/span&gt; i3lock -c &lt;span class="m"&gt;000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# hide/unhide i3status bar (laptop)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+m bar mode invisible bar-0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+m bar mode dock bar-0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# hide/unhide i3status bar (monitor)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+b bar mode invisible bar-1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+b bar mode dock bar-1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# set up displays&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt;-xrandr-both
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# use laptop display only (turn off monitor)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+o &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt;-xrandr-laptop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# use monitor display only (turn off laptop screen)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+mod1+o &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt;-xrandr-monitor
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# use laptop + monitor displays&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+shift+o &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt;-xrandr-both
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# disable screen saver&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$exec&lt;/span&gt; xset s off
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# disable power saving&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$exec&lt;/span&gt; xset -dpms&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;A few notes on the &lt;strong&gt;Screens and monitors&lt;/strong&gt; section:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It sets keybindings to hide/unhide bars on separate screens, which can be particularly useful when screen sharing without having to enter in fullscreen mode.&lt;/li&gt;
&lt;li&gt;It has additional keybindings to turn on/off the displays independently.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="navigation"&gt;Navigation&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: I rely on Vim keybindings to move between containers and workspaces as I find the position of those keys on the home row really comfortable to apply to the main window actions one would want to execute.&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# container focuses where the mouse is (yes/no)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;focus_follows_mouse no
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# go to last urgent workspace&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ⇒ this is the worspace that turns &amp;#34;red&amp;#34; by default when something happens&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# in an unfocused workspace, like an application pop-up&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+u &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;urgent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;latest&lt;span class="o"&gt;]&lt;/span&gt; focus&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$center&lt;/span&gt;-mouse
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# change focus&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ⇒ works in all modes: tabbed, stacked, etc.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+h focus left&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$center&lt;/span&gt;-mouse
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+j focus down&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$center&lt;/span&gt;-mouse
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+k focus up&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$center&lt;/span&gt;-mouse
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Down focus down&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$center&lt;/span&gt;-mouse
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Up focus up&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$center&lt;/span&gt;-mouse
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+l focus right&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$center&lt;/span&gt;-mouse
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# move focused window (works in all modes)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+h move left&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$center&lt;/span&gt;-mouse
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+j move down&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$center&lt;/span&gt;-mouse
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+k move up&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$center&lt;/span&gt;-mouse
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+l move right&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$center&lt;/span&gt;-mouse
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# for example, switching from workspace 1 to workspace 2 (e.g. mod+2) then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# typing again mod+2 would bring you back to workspace 1 without having to&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# remember that you came from workspace 1 in the first place.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;workspace_auto_back_and_forth no
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# alternate between the last two workspaces that have been used&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Tab workspace back_and_forth
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# move a container (window or group of windows if you grouped them) to the&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# &amp;#34;alternate&amp;#34; workspace you visited last&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# (the one brought with $mod+Tab in this case)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+mod1+Tab move container to workspace back_and_forth&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; workspace back_and_forth
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# switch to any window with Rofi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ⇒ this simulates the familiar behavior of Alt+Tab on Windows and allows you&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# to find windows by typing, but uses a better operating system to do it ;).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym Mod1+Tab &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rofi -theme lb -font &lt;/span&gt;&lt;span class="nv"&gt;$rofi&lt;/span&gt;&lt;span class="s2"&gt;-font \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; -show-icons -width 95 -show window&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# toggle fullscreen mode for the focused container&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ⇒ this hides the i3bar for the current monitor&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ⇒ you can still leave the i3bar hidden and switch to other workspaces&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+f fullscreen toggle
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# navigate workspaces next / previous&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ⇒ works for all monitors and will go in increasing order&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# if workspaces 1 to 6 are on monitorX and workspaces&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 7 to 10 are on monitorY as in this config, this will switch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# to any workspace where there is at least one container&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Mod1+l workspace next
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Mod1+h workspace prev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+Right workspace next
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+Left workspace prev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# the difference with the above is that it limits movements to the&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# the current monitor only&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Right workspace next_on_output
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+BackSpace workspace next_on_output
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+BackSpace workspace prev_on_output
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Left workspace prev_on_output
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Bindings to &amp;#34;mark&amp;#34; and &amp;#34;goto&amp;#34; mark specified with 1 character&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ⇒ rarely used, but can still be useful if you have many containers open&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+z &lt;span class="nb"&gt;exec&lt;/span&gt; i3-input -F &lt;span class="s1"&gt;&amp;#39;mark %s&amp;#39;&lt;/span&gt; -l &lt;span class="m"&gt;1&lt;/span&gt; -P &lt;span class="s1"&gt;&amp;#39;Mark: &amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+g &lt;span class="nb"&gt;exec&lt;/span&gt; i3-input -F &lt;span class="s1"&gt;&amp;#39;[con_mark=&amp;#34;%s&amp;#34;] focus&amp;#39;&lt;/span&gt; -l &lt;span class="m"&gt;1&lt;/span&gt; -P &lt;span class="s1"&gt;&amp;#39;Goto: &amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# switch to workspace (no matter which monitor it is set to)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+1 workspace &lt;span class="nv"&gt;$ws1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+2 workspace &lt;span class="nv"&gt;$ws2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+3 workspace &lt;span class="nv"&gt;$ws3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+4 workspace &lt;span class="nv"&gt;$ws4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+5 workspace &lt;span class="nv"&gt;$ws5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+6 workspace &lt;span class="nv"&gt;$ws6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+7 workspace &lt;span class="nv"&gt;$ws7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+8 workspace &lt;span class="nv"&gt;$ws8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+9 workspace &lt;span class="nv"&gt;$ws9&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+0 workspace &lt;span class="nv"&gt;$ws10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# determine which workspace appears on which screen&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ⇒ with a single output screen, this is not necessary&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;workspace &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;$output&lt;/span&gt;-secondary
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;workspace &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="nv"&gt;$output&lt;/span&gt;-secondary
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;workspace &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="nv"&gt;$output&lt;/span&gt;-secondary
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;workspace &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="nv"&gt;$output&lt;/span&gt;-secondary
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;workspace &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="nv"&gt;$output&lt;/span&gt;-secondary
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;workspace &lt;span class="m"&gt;6&lt;/span&gt; &lt;span class="nv"&gt;$output&lt;/span&gt;-secondary
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;workspace &lt;span class="m"&gt;7&lt;/span&gt; &lt;span class="nv"&gt;$output&lt;/span&gt;-primary
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;workspace &lt;span class="m"&gt;8&lt;/span&gt; &lt;span class="nv"&gt;$output&lt;/span&gt;-primary
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;workspace &lt;span class="m"&gt;9&lt;/span&gt; &lt;span class="nv"&gt;$output&lt;/span&gt;-primary
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;workspace &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="nv"&gt;$output&lt;/span&gt;-primary
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# move focused container to workspace and switch to workspace&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+1 move container to workspace &lt;span class="nv"&gt;$ws1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+2 move container to workspace &lt;span class="nv"&gt;$ws2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+3 move container to workspace &lt;span class="nv"&gt;$ws3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+4 move container to workspace &lt;span class="nv"&gt;$ws4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+5 move container to workspace &lt;span class="nv"&gt;$ws5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+6 move container to workspace &lt;span class="nv"&gt;$ws6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+7 move container to workspace &lt;span class="nv"&gt;$ws7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+8 move container to workspace &lt;span class="nv"&gt;$ws8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+9 move container to workspace &lt;span class="nv"&gt;$ws9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws9&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+0 move container to workspace &lt;span class="nv"&gt;$ws10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws10&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;A few notes on the &lt;strong&gt;Navigation&lt;/strong&gt; section:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I like to keep the number of workspaces to 10 because the number row is easily accessible and there&amp;rsquo;s less context switching involved.&lt;/li&gt;
&lt;li&gt;I also like to keep a higher number of workspaces on the main screen I work on as I can use &lt;code&gt;mod+Left&lt;/code&gt; and &lt;code&gt;mod+Right&lt;/code&gt; to switch focus on that screen only. This is usually to keep a reference opened on the other screen, like server logs.&lt;/li&gt;
&lt;li&gt;Those keybindings make it really easy to navigate between workspaces on one particular screen. Even though I like to work with a monitor being higher than the laptop located in front of it to avoid working in a twisted position at all times, this would work fantastically well in the horizontal direction too.&lt;/li&gt;
&lt;li&gt;If for whatever reason the main screen is disconnected (power outage or unplugged), I can still use the laptop screen without needing to modify the config file, although with a slightly more limited amount of workspaces. &lt;em&gt;Tip&lt;/em&gt;: if a monitor has been disconnected (such as in the power outage scenario), using the keybinding to turn it off will move all the workspaces to the other display(s), so everything will be visible again.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="nice-addition-to-make-the-mouse-follow-any-focused-container"&gt;Nice addition to make the mouse follow any focused container&lt;/h3&gt;
&lt;p&gt;By default, the focus of the mouse in i3 will go to the active screen, but it won&amp;rsquo;t follow the focus if switching to different containers on the &lt;em&gt;same&lt;/em&gt; screen. I found this little hack using the &lt;code&gt;center-mouse&lt;/code&gt; script after running commands like &lt;code&gt;move&lt;/code&gt; and &lt;code&gt;focus&lt;/code&gt; to force the mouse to move to any focused container. For convenience, &lt;a href="https://github.com/sglavoie/dotfiles/blob/main/.config/i3/center-mouse"&gt;here is the content of that script&lt;/a&gt;. You will need the program &lt;code&gt;xdotool&lt;/code&gt; to use it.&lt;/p&gt;
&lt;h2 id="layout"&gt;Layout&lt;/h2&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# set default desktop layout (default is tiling), &amp;lt;stacking|tabbed&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;workspace_layout tabbed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# change container layout (stacked, tabbed, toggle split)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+s layout stacking
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+w layout tabbed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+e layout toggle split
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# start in opposite orientation from your monitor&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;default_orientation vertical
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# toggle split at each new window (Fibonnaci layout)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# note: only useful if the layout is set to `default`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# for_window [class=&amp;#34;.*&amp;#34;] split toggle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# toggle the way a split happens in the focused window&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+t split toggle
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# toggle tiling / floating&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ⇒ this works on a container with any number of windows!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# if you select multiple windows with `$mod+a` (see below)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# to group them and then toggle them, the whole group will&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# either tile or float&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+space floating toggle
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# change focus between tiling / floating windows&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ⇒ If you leave a floating window on top of other tiled windows,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# you can switch the focus from the one on top to the one(s) in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# the background (useful with apps like Zoom or Google Meet that&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# you want to keep in the foreground)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+space focus mode_toggle
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Use Mouse+$mod to drag floating windows&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;floating_modifier &lt;span class="nv"&gt;$mod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# toggle sticky for floating windows&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+s sticky toggle
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# focus the surrounding containers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+a focus parent&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$center&lt;/span&gt;-mouse
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+c focus child&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$center&lt;/span&gt;-mouse&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;A few notes on the &lt;strong&gt;Layout&lt;/strong&gt; section:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To maximize the size of applications, I prefer using the &amp;ldquo;tabbed&amp;rdquo; mode, which is more compact than the stacking mode. If I quickly want to split windows side-by-side, I just type &lt;code&gt;mod+e&lt;/code&gt; to toggle split vertically by default and if I need a horizontal split, I just toggle again with &lt;code&gt;mod+e&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;I don&amp;rsquo;t use the &amp;ldquo;grouping&amp;rdquo; feature with &lt;code&gt;mod+a&lt;/code&gt; and &lt;code&gt;mod+c&lt;/code&gt; very often, but when I need to move a few windows to another workspace for instance, that&amp;rsquo;s quite handy!&lt;/li&gt;
&lt;li&gt;A floating window in &amp;ldquo;sticky mode&amp;rdquo; will follow you on all your workspaces and remember that this mode is enabled even if you put in back in a tiling window and then to a floating window again.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="scratchpad"&gt;Scratchpad&lt;/h2&gt;
&lt;p&gt;The scratchpad is sometimes so useful that it deserves its own section! When you send a window to the &amp;ldquo;scratchpad&amp;rdquo;, it disappears. Then, with a keybinding, you can bring in back in floating mode on top of any other window on any active workspace you happen to be in. The same keybinding will toggle on/off the display of that scratchpad and if you happen to have sent multiple windows to the scratchpad, activating the same keybinding again will cycle through all the scratchpads as we set a custom mode called &lt;code&gt;Scratchpad&lt;/code&gt; in this example.&lt;/p&gt;
&lt;p&gt;I often use that for an audio player I leave running in the background and instead of using a whole workspace to leave that window opened, I send it to the scratchpad, which means it doesn&amp;rsquo;t take any space and I can quickly bring it back in focus whenever I want, in any workspace. I find that having more than three windows in the list of scratchpads becomes cumbersome as you cycle through them, so I keep the scratchpad for specific, infrequent uses for applications that require less attention.&lt;/p&gt;
&lt;p&gt;If you want to convert a scratchpad back to a regular tiled container, it&amp;rsquo;s as easy as triggering &amp;ldquo;toggle tiling / floating&amp;rdquo; (in this config file, it happens with &lt;code&gt;mod+Shift+space&lt;/code&gt;).&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Move the currently focused window to the scratchpad.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# First, we toggle floating mode and we resize and position the window to make sure&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# it will appear correctly when showing it for the first time.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+minus floating toggle&lt;span class="p"&gt;;&lt;/span&gt; resize &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$scratchpad&lt;/span&gt;-size, &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; move position center&lt;span class="p"&gt;;&lt;/span&gt; move scratchpad
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Set up scratchpad applications&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Audacious&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; floating &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Audacious&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; move scratchpad
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Audacious&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; resize &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$scratchpad&lt;/span&gt;-size
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$temp&lt;/span&gt;-notes&lt;span class="o"&gt;]&lt;/span&gt; floating &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$temp&lt;/span&gt;-notes&lt;span class="o"&gt;]&lt;/span&gt; move scratchpad
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$temp&lt;/span&gt;-notes&lt;span class="o"&gt;]&lt;/span&gt; resize &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$scratchpad&lt;/span&gt;-size
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$python&lt;/span&gt;-term&lt;span class="o"&gt;]&lt;/span&gt; floating &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$python&lt;/span&gt;-term&lt;span class="o"&gt;]&lt;/span&gt; move scratchpad
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$python&lt;/span&gt;-term&lt;span class="o"&gt;]&lt;/span&gt; resize &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$scratchpad&lt;/span&gt;-size
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$todoist&lt;/span&gt;-app-name&lt;span class="o"&gt;]&lt;/span&gt; floating &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$todoist&lt;/span&gt;-app-name&lt;span class="o"&gt;]&lt;/span&gt; move scratchpad
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$todoist&lt;/span&gt;-app-name&lt;span class="o"&gt;]&lt;/span&gt; resize &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$scratchpad&lt;/span&gt;-size&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;A note on the &lt;strong&gt;Scratchpad&lt;/strong&gt; section:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In my setup, I have two screens with the same resolution of 1920x1080, so the same &amp;ldquo;resize&amp;rdquo; command works on both screens. You might want to use two different keybindings if you want to bring a scratchpad back to a screen with a different resolution.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="borders"&gt;Borders&lt;/h2&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Configure border style &amp;lt;normal|1pixel|pixel xx|none|pixel&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;new_window pixel &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;new_float normal
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;default_floating_border pixel &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Hide borders&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hide_edge_borders smart
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# change borders&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+n border none
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+n border pixel &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+y border normal &lt;span class="m"&gt;5&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;A few notes on the &lt;strong&gt;Borders&lt;/strong&gt; section:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I just &lt;strong&gt;love&lt;/strong&gt; knowing which container is active, so I set thick, bright borders with a nice kind of &amp;ldquo;electric blue&amp;rdquo; color that&amp;rsquo;s easy to spot on a light or dark background (colors are set below in the section &lt;strong&gt;Colors &amp;amp; theming&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;I also &lt;strong&gt;love&lt;/strong&gt; saving space whenever possible to see more stuff on the screen, so I avoid gaps altogether (if you like them, have a look at &lt;a href="https://github.com/Airblader/i3"&gt;i3-gaps&lt;/a&gt;). In that spirit, I also set the option &lt;code&gt;hide_edge_borders smart&lt;/code&gt; so that borders don&amp;rsquo;t show up when I have only one tiled container covering the whole space.&lt;/li&gt;
&lt;li&gt;This also shows that you can have a different border size for floating windows if you find the thickness distracting. Personally, I just find it helps me know which container is active when I toggle from foreground to background window with &lt;code&gt;mod+space&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
 &lt;a href="https://www.sglavoie.com/images/posts/0022_guided-tour-i3/demo_dark_light_background_thick_border.png"&gt;&lt;img src="https://www.sglavoie.com/images/posts/0022_guided-tour-i3/demo_dark_light_background_thick_border.png" alt="demo_dark_light_background_thick_border_i3" class="max-size-img-post"&gt;&lt;/a&gt;
 &lt;figcaption&gt;Bashtop on the left with a dark background, LibreOffice on the right with a light background, Neofetch floating window on top featuring a thick, blue border.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="dmenu"&gt;dmenu&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;dmenu&lt;/code&gt; is so useful that it also got its own section. By default, you usually launch applications with &lt;code&gt;mod+d&lt;/code&gt;: I kept the same behavior here. I added a few custom launchers that I use all the time to open files based on a topic, using mnemonics that work (for me, at least). If you want to know more about setting these kinds of shortcuts, you may be interested in reading &lt;a href="https://www.sglavoie.com/posts/using-dmenu-to-optimize-common-tasks/"&gt;Using dmenu to Optimize Common Tasks&lt;/a&gt; ;).&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Mod1+b &lt;span class="nv"&gt;$exec&lt;/span&gt; ~/Dropbox/.custom/dmenu/books.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Mod1+c &lt;span class="nv"&gt;$exec&lt;/span&gt; ~/Dropbox/.custom/dmenu/cheatsheets.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Mod1+d &lt;span class="nv"&gt;$exec&lt;/span&gt; ~/Dropbox/.custom/dmenu/documents.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Mod1+e &lt;span class="nv"&gt;$exec&lt;/span&gt; ~/Dropbox/.custom/dmenu/edit_scripts.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Mod1+f &lt;span class="nv"&gt;$exec&lt;/span&gt; ~/Dropbox/.custom/dmenu/filepaths.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Mod1+q &lt;span class="nv"&gt;$exec&lt;/span&gt; ~/Dropbox/.custom/dmenu/quotes.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Mod1+r &lt;span class="nv"&gt;$exec&lt;/span&gt; ~/Dropbox/.custom/dmenu/reading.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Mod1+s &lt;span class="nv"&gt;$exec&lt;/span&gt; ~/Dropbox/.custom/dmenu/scripts.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Mod1+u &lt;span class="nv"&gt;$exec&lt;/span&gt; ~/Dropbox/.custom/dmenu/university.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Mod1+w &lt;span class="nv"&gt;$exec&lt;/span&gt; ~/Dropbox/.custom/dmenu/work.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+f &lt;span class="nv"&gt;$exec&lt;/span&gt; ~/Dropbox/.custom/dmenu/thunar_launcher.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+u &lt;span class="nv"&gt;$exec&lt;/span&gt; ~/Dropbox/.custom/dmenu/university_launchers.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+d &lt;span class="nv"&gt;$exec&lt;/span&gt; ~/Dropbox/.custom/dmenu/software.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="shortcuts"&gt;Shortcuts&lt;/h2&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+control+r &lt;span class="nv"&gt;$exec&lt;/span&gt; thunar ~/Desktop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Display multiple time zones around the world&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Mod1+t &lt;span class="nv"&gt;$exec&lt;/span&gt; ~/Dropbox/.custom/dmenu/times.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;A few notes on the &lt;strong&gt;Shortcuts&lt;/strong&gt; mini-section:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In i3, you don&amp;rsquo;t really make use of icons on the desktop. I like the absence of visual clutter it provides, but I sometimes like to access a specific folder in which I drop a few shortcuts/symlinks to access other files from there.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;An idea for customization&lt;/em&gt;: working remotely with many teammates from around the world is sometimes confusing timewise, so I have a simple script that outputs the current time in the places I&amp;rsquo;m interested in monitoring.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
 &lt;a href="https://www.sglavoie.com/images/posts/0022_guided-tour-i3/timezones.png"&gt;&lt;img src="https://www.sglavoie.com/images/posts/0022_guided-tour-i3/timezones.png" alt="timezones_dmenu" class="max-size-img-post"&gt;&lt;/a&gt;
 &lt;figcaption&gt;Simple pop-up with a list of timezones.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="sound"&gt;Sound&lt;/h2&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# `pkill` is useful to quickly update the block in i3blocks&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym XF86AudioRaiseVolume &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nv"&gt;$volumepath&lt;/span&gt; -np up &lt;span class="nv"&gt;$volumestep&lt;/span&gt; -t &lt;span class="nv"&gt;$statuscmd&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -u &lt;span class="nv"&gt;$statussig&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pkill -RTMIN+1 i3blocks
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym XF86AudioLowerVolume &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nv"&gt;$volumepath&lt;/span&gt; -np down &lt;span class="nv"&gt;$volumestep&lt;/span&gt; -t &lt;span class="nv"&gt;$statuscmd&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -u &lt;span class="nv"&gt;$statussig&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pkill -RTMIN+1 i3blocks
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym XF86AudioMute &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nv"&gt;$volumepath&lt;/span&gt; mute -n -t &lt;span class="nv"&gt;$statuscmd&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -u &lt;span class="nv"&gt;$statussig&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pkill -RTMIN+1 i3blocks
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym XF86AudioMicMute &lt;span class="nv"&gt;$exec&lt;/span&gt; &lt;span class="nv"&gt;$toggle&lt;/span&gt;-mic-on-off
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+exclamdown &lt;span class="nv"&gt;$exec&lt;/span&gt; &lt;span class="nv"&gt;$toggle&lt;/span&gt;-mic-on-off
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Automatically set the volume to a given value when (re)launching i3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;exec_always --no-startup-id &lt;span class="s2"&gt;&amp;#34;amixer -D pulse set Master 50%&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Always mute the microphone source when (re)launching i3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;exec_always --no-startup-id pactl set-source-mute &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;alsa_input.pci-0000_03_00.6.analog-stereo&amp;#34;&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pkill -RTMIN+10 i3blocks&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This section will depend on the hardware being used, but it gives an idea of how to do things. I use &lt;a href="https://github.com/hastinbe/i3-volume"&gt;&lt;code&gt;i3-volume&lt;/code&gt;&lt;/a&gt; to get nice notifications when changing the volume and all the options being set here use &lt;code&gt;i3-volume&lt;/code&gt;. Toggling the mic output was not super obvious at first, but it&amp;rsquo;s obviously doable and it&amp;rsquo;s good to know there&amp;rsquo;s an &lt;code&gt;XF86Audio&lt;/code&gt; property for that purpose!&lt;/p&gt;
&lt;h2 id="brightness"&gt;Brightness&lt;/h2&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Control brightness more precisely through software `light`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym XF86MonBrightnessDown &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nv"&gt;$brightness&lt;/span&gt;-laptop-down
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym XF86MonBrightnessUp &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nv"&gt;$brightness&lt;/span&gt;-laptop-up
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Adjust external monitor brightness&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+F2 &lt;span class="nv"&gt;$exec&lt;/span&gt; &lt;span class="nv"&gt;$brightness&lt;/span&gt;-monitor-down
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+F1 &lt;span class="nv"&gt;$exec&lt;/span&gt; &lt;span class="nv"&gt;$brightness&lt;/span&gt;-monitor-up
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+F3 &lt;span class="nv"&gt;$exec&lt;/span&gt; &lt;span class="nv"&gt;$brightness&lt;/span&gt;-monitor-default
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Toggle laptop screen&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Control+b &lt;span class="nv"&gt;$exec&lt;/span&gt; &lt;span class="nv"&gt;$brightness&lt;/span&gt;-toggle-laptop&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;To control the screen brightness, I use &lt;a href="https://github.com/haikarainen/light"&gt;&lt;code&gt;light&lt;/code&gt;&lt;/a&gt;. I also like to just toggle the brightness of my laptop at night when I read something on my external monitor, so I use a &lt;a href="https://github.com/sglavoie/dotfiles/blob/main/.local/bin/toggle_laptop_brightness"&gt;&lt;code&gt;toggle_laptop_brightness&lt;/code&gt;&lt;/a&gt; script that I found somewhere online and adapted slightly.&lt;/p&gt;
&lt;h2 id="applications"&gt;Applications&lt;/h2&gt;
&lt;p&gt;This section will heavily depend on the software you use of course, but there&amp;rsquo;s the gist of it. I like to have a shortcut for &lt;a href="https://github.com/aristocratos/bashtop"&gt;bashtop&lt;/a&gt; to quickly monitor system resources (&lt;a href="https://github.com/nicolargo/glances"&gt;glances&lt;/a&gt; is also quite nice). Besides that, I like to have applications automatically appear on specific workspaces and be switched to in some instances.&lt;/p&gt;
&lt;p&gt;Controlling which application opens in floating mode is also useful as some just don&amp;rsquo;t play that well with tiling. To find information about an application, you can open a new terminal window and launch &lt;code&gt;xprop&lt;/code&gt;, click on the window you need information about and &lt;em&gt;voilà&lt;/em&gt;, a couple of strings will be displayed like so (output truncated a bit):&lt;/p&gt;
&lt;div class="code-block code-block--wrap"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;pre tabindex="0"&gt;&lt;code&gt;	Icon (16 x 16):
	 ▒▓▓▓▓▒
	 ▒▓██████▓▒
	 ▒█▓▒░ ░▒▓█▒
	 ▒█▓░ ░▓█▒
	 ▓▓░ ░░░ ░▓▓
	▒█▒ ░███▓░ ▒█▒
	▓█░ ░░▓█▓ ░█▓
	▓█ ░▓▓██▓ █▓
	▓█ ▓█▒▓█▓ █▓
	▓█░ ▓█▓▓█▓ ░█▓
	▒█▒ ░▓▓▒▓▒ ▒█▒
	 ▓▓░ ░▓▓
	 ▒█▓░ ░▓█▒
	 ▒█▓▒░ ░▒▓█▒
	 ▒▓██████▓▒
	 ▒▓▓▓▓▒
WM_NORMAL_HINTS(WM_SIZE_HINTS):
		program specified location: 0, 0
		program specified minimum size: 627 by 279
		window gravity: NorthWest
WM_CLASS(STRING) = &amp;#34;audacious&amp;#34;, &amp;#34;Audacious&amp;#34;
WM_ICON_NAME(STRING) = &amp;#34;47. David Bowie - Best Of Bowie - Heroes (3:36) - Audacious&amp;#34;
WM_NAME(STRING) = &amp;#34;47. David Bowie - Best Of Bowie - Heroes (3:36) - Audacious&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You may need to switch to tiling windows or make the terminal window that launches &lt;code&gt;xprop&lt;/code&gt; a floating window to be able to click on the desired window. Here, we get an output for the Audacious music player. You usually need the second string from &lt;code&gt;WM_CLASS(STRING)&lt;/code&gt; but if you don&amp;rsquo;t want to mess with letter casing being upper vs lowercase, you can prefix with &lt;code&gt;(?i)&lt;/code&gt; for &amp;ldquo;insensitive&amp;rdquo; as shown below and that will match any window containing that pattern. Sometimes, you&amp;rsquo;re just after matching a &amp;ldquo;title&amp;rdquo; (like it&amp;rsquo;s done with LibreOffice in the snippet of code below) and you can get that one by showing the container&amp;rsquo;s border. With this config file, the &lt;code&gt;mod+n&lt;/code&gt; keybinding will show the border you want at the top and &lt;code&gt;mod+y&lt;/code&gt; will make it disappear again.&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Autostart applications&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$exec&lt;/span&gt; &lt;span class="nv"&gt;$brightness&lt;/span&gt;-monitor-default
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$exec&lt;/span&gt; &lt;span class="nv"&gt;$default&lt;/span&gt;-web-browser
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$exec&lt;/span&gt; &lt;span class="nv"&gt;$launcher&lt;/span&gt;-todoist
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$exec&lt;/span&gt; brightnessctl s 20%
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$exec&lt;/span&gt; compton -CG --config /dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$exec&lt;/span&gt; nitrogen --restore
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$exec&lt;/span&gt; nm-applet
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$exec&lt;/span&gt; pasystray
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$exec&lt;/span&gt; xfce4-clipman
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# First kill it as it doesn&amp;#39;t always respect the `--timeout` option otherwise&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;exec_always --no-startup-id &lt;span class="s2"&gt;&amp;#34;killall unclutter; unclutter --jitter 40 --ignore-scrolling --timeout 5 &amp;amp;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Start&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+d --release &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;killall dunst; exec notify-send &amp;#39;restart dunst&amp;#39;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+t &lt;span class="nb"&gt;exec&lt;/span&gt; thunar
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+w &lt;span class="nv"&gt;$exec&lt;/span&gt; &lt;span class="nv"&gt;$default&lt;/span&gt;-web-browser
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+masculine &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$python&lt;/span&gt;-term&lt;span class="o"&gt;]&lt;/span&gt; scratchpad show&lt;span class="p"&gt;;&lt;/span&gt; move position center
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+masculine &lt;span class="nv"&gt;$exec&lt;/span&gt; kitty --name ipythonterm -o &lt;span class="nv"&gt;font_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt; -e &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$launcher&lt;/span&gt;-python-term -q
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+plus &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$temp&lt;/span&gt;-notes&lt;span class="o"&gt;]&lt;/span&gt; scratchpad show&lt;span class="p"&gt;;&lt;/span&gt; move position center
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+plus &lt;span class="nv"&gt;$exec&lt;/span&gt; kitty --name tmpnotes -e &lt;span class="nv"&gt;$launcher&lt;/span&gt;-temp-notes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+p &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Audacious&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; scratchpad show&lt;span class="p"&gt;;&lt;/span&gt; move position center
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+p &lt;span class="nv"&gt;$exec&lt;/span&gt; audacious
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+q &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$todoist&lt;/span&gt;-app-name&lt;span class="o"&gt;]&lt;/span&gt; scratchpad show&lt;span class="p"&gt;;&lt;/span&gt; move position center
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Shift+q &lt;span class="nv"&gt;$exec&lt;/span&gt; &lt;span class="nv"&gt;$launcher&lt;/span&gt;-todoist
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym Print --release &lt;span class="nv"&gt;$exec&lt;/span&gt; flameshot gui --path &lt;span class="nv"&gt;$screenshots&lt;/span&gt;-folder
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym &lt;span class="nv"&gt;$mod&lt;/span&gt;+Print --release &lt;span class="nv"&gt;$exec&lt;/span&gt; flameshot screen --path &lt;span class="nv"&gt;$screenshots&lt;/span&gt;-folder
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym mod1+F3 &lt;span class="nv"&gt;$exec&lt;/span&gt; kitty -e htop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Open applications on specific workspaces&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;assign &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;(?i)code&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nv"&gt;$ws1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;assign &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$default&lt;/span&gt;-web-browser&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nv"&gt;$ws3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;assign &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;(?i)firefox&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nv"&gt;$ws3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;assign &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$default&lt;/span&gt;-web-browser-class&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nv"&gt;$ws3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;assign &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Audacity&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nv"&gt;$ws4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;assign &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Gimp&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nv"&gt;$ws4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;assign &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Inkscape&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nv"&gt;$ws4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;assign &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;vlc&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nv"&gt;$ws4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;assign &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;(?i)atril&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nv"&gt;$ws6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;assign &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;(?i)zathura&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nv"&gt;$ws6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;assign &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;(?i)libreoffice&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nv"&gt;$ws4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;assign &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Transmission&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nv"&gt;$ws8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;assign &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;keepassxc&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nv"&gt;$ws8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;assign &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;obs&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nv"&gt;$ws8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Automatically switch to workspace when opening those&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;(?i)code&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$default&lt;/span&gt;-web-browser&lt;span class="o"&gt;]&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$default&lt;/span&gt;-web-browser-class&lt;span class="o"&gt;]&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;(?i)firefox&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Gimp&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Inkscape&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;audacity&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;vlc&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;(?i)zathura&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;(?i)atril&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;(?i)libreoffice&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Transmission&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;keepassxc&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;obs&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; workspace &lt;span class="nv"&gt;$ws8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Open specific applications in floating mode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;(?i)virtualbox&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; floating &lt;span class="nb"&gt;enable&lt;/span&gt; border pixel &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;GParted&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; floating &lt;span class="nb"&gt;enable&lt;/span&gt; border pixel &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Lightdm-settings&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; floating &lt;span class="nb"&gt;enable&lt;/span&gt; pixel &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Lxappearance&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; floating &lt;span class="nb"&gt;enable&lt;/span&gt; sticky &lt;span class="nb"&gt;enable&lt;/span&gt; border pixel &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Nitrogen&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; floating &lt;span class="nb"&gt;enable&lt;/span&gt; sticky &lt;span class="nb"&gt;enable&lt;/span&gt; border pixel &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;File Transfer*&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; floating &lt;span class="nb"&gt;enable&lt;/span&gt; border pixel &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;alsamixer&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; floating &lt;span class="nb"&gt;enable&lt;/span&gt; border pixel &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for_window &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;(?i)zoom&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; floating &lt;span class="nb"&gt;enable&lt;/span&gt; border pixel &lt;span class="m"&gt;5&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The section &lt;strong&gt;Autostart applications&lt;/strong&gt; is the list of applications that launch when you open your X session with i3.&lt;/p&gt;
&lt;h2 id="keyboard--mouse"&gt;Keyboard &amp;amp; mouse&lt;/h2&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Set compose key to Right Ctrl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;exec_always --no-startup-id setxkbmap -option &lt;span class="nv"&gt;$compose&lt;/span&gt;-key
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Delay, interval&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;exec_always --no-startup-id xset r rate &lt;span class="m"&gt;250&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I like to set those options in my config file as they can be modified on the fly if you reload i3. This is thanks to the &lt;code&gt;exec_always --no-startup-id&lt;/code&gt; directive which will always run the invoked command when relaunching, not just when logging in to a session.&lt;/p&gt;
&lt;h2 id="colors--theming"&gt;Colors &amp;amp; theming&lt;/h2&gt;
&lt;p&gt;Of interest in this section, apart from the actual colors, is where you will set your i3bar on the screen (top vs bottom) and on which monitor your tray (the space with little icons to show the volume, WiFi connectivity and so on) will be displayed if you have more than one monitor. In this case, the bar displays at the top of the screen &lt;em&gt;à la Mac&lt;/em&gt; or &lt;em&gt;à la GNOME&lt;/em&gt; with &lt;code&gt;position top&lt;/code&gt; and reveals information about system resources with &lt;code&gt;i3blocks&lt;/code&gt; (you can find how it can be configured in &lt;a href="https://github.com/sglavoie/dotfiles/tree/main/.config/i3blocks"&gt;my dotfiles&lt;/a&gt; or in the &lt;a href="https://github.com/vivien/i3blocks/"&gt;i3blocks GitHub repo&lt;/a&gt;).&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;bash&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Start i3bar to display a workspace bar (plus the system information i3status if available)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bar &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; id bar-1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$font&lt;/span&gt;-small
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$output&lt;/span&gt;-secondary
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# mode invisible&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tray_output none
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; status_command i3blocks
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; position top
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; colors &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; background &lt;span class="c1"&gt;#000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; statusline &lt;span class="c1"&gt;#ffffff&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; separator &lt;span class="c1"&gt;#666666&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# border backgr. text&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; focused_workspace &lt;span class="c1"&gt;#4c7899 #285577 #ffffff #ffffff&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; active_workspace &lt;span class="c1"&gt;#333333 #5f676a #ffffff #3c3b37&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; inactive_workspace &lt;span class="c1"&gt;#333333 #222222 #888888 #aea79f&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; urgent_workspace &lt;span class="c1"&gt;#2f343a #900000 #ffffff #ffffff&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bar &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; id bar-0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$font&lt;/span&gt;-medium
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$output&lt;/span&gt;-primary
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tray_output primary
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; status_command i3blocks
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; position top
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bindsym button4 nop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bindsym button5 nop
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; colors &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; background &lt;span class="c1"&gt;#000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; statusline &lt;span class="c1"&gt;#ffffff&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; separator &lt;span class="c1"&gt;#666666&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# border backgr. text&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; focused_workspace &lt;span class="c1"&gt;#4c7899 #285577 #ffffff #ffffff&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; active_workspace &lt;span class="c1"&gt;#333333 #5f676a #ffffff #3c3b37&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; inactive_workspace &lt;span class="c1"&gt;#333333 #222222 #888888 #aea79f&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; urgent_workspace &lt;span class="c1"&gt;#2f343a #900000 #ffffff #ffffff&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Theme colors&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# class border backgr. text indic. child_border&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; client.focused &lt;span class="c1"&gt;#ffffff #ff0054 #ffffff #2e9ef4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; client.focused_inactive &lt;span class="c1"&gt;#333333 #8338ec #ffffff #484e50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; client.unfocused &lt;span class="c1"&gt;#333333 #000000 #bbbbbb #292d2e&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; client.urgent &lt;span class="c1"&gt;#2f343a #900000 #ffffff #900000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; client.placeholder &lt;span class="c1"&gt;#F9FAF9 #061229 #FFFFFF #061229&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; client.background &lt;span class="c1"&gt;#000000&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;What I&amp;rsquo;ve found particularly useful here is the fact you can have a different &amp;ldquo;i3 bar&amp;rdquo; for each screen. I&amp;rsquo;ve set keybindings in the section &lt;a href="#screens-and-monitors"&gt;Screens and monitors&lt;/a&gt; so I can toggle them on/off separately too.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also switched away from the default &lt;a href="https://github.com/i3/i3status"&gt;i3status&lt;/a&gt; program to display the status bar(s) to &lt;a href="https://github.com/vivien/i3blocks/"&gt;i3blocks&lt;/a&gt;, which I&amp;rsquo;ve found a lot nicer to look at and easier to configure too.&lt;/p&gt;
&lt;p&gt;Theming i3 can be tedious, but thankfully there are tools to automate the process! One such tool is &lt;a href="https://github.com/altdesktop/i3-style"&gt;i3-style&lt;/a&gt;, which allows you to quickly switch in place your theme without having to log out or modify manually your i3 &lt;code&gt;config&lt;/code&gt; file. To go the manual route or to tweak an existing theme, you can always use a website like &lt;a href="https://htmlcolorcodes.com"&gt;HTML Color Codes&lt;/a&gt; to find an exact hexadecimal value. You can also use &lt;a href="https://terminal.sexy"&gt;terminal.sexy&lt;/a&gt; to come up with nice looking themes, which can be exported for other applications you may be using.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;You can find the &lt;a href="https://github.com/sglavoie/dotfiles/blob/main/.config/i3/config"&gt;complete &lt;code&gt;config&lt;/code&gt; file in my dotfiles on GitHub&lt;/a&gt;. If you liked this article, you may also enjoy reading the following ones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.sglavoie.com/posts/suckless-minimalist-tools-that-work-great/"&gt;Suckless: Minimalist Tools That Work Great&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sglavoie.com/posts/using-dmenu-to-optimize-common-tasks/"&gt;Using dmenu to Optimize Common Tasks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sglavoie.com/posts/using-i3-as-a-window-manager-for-increased-productivity/"&gt;Using i3 as a Window Manager for Increased Productivity&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="more-resources-and-references"&gt;More resources and references&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://fedoramagazine.org/getting-started-i3-window-manager/"&gt;Getting started with the i3 tiling window manager&lt;/a&gt; — Fedora Magazine&lt;/li&gt;
&lt;li&gt;&lt;a href="https://i3wm.org/docs/userguide.html"&gt;Official i3 documentation&lt;/a&gt; — i3wm.org&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.archlinux.org/index.php/i3"&gt;i3 page on the Arch Wiki&lt;/a&gt; — Arch Wiki&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Sat, 15 Aug 2020 10:51:00 -0500</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/a-guided-tour-of-an-i3-configuration/</guid><category>tools</category><category>i3</category><category>window-manager</category></item><item><title>My Current Set of Tools and Customizations</title><link>https://www.sglavoie.com/posts/my-current-set-of-tools-and-customizations/</link><description>I like to keep track of the tools I use and see the shifts that occur over time — may it be switching from a graphical interface to a command-line one or finding a totally new workflow for an existing need. Without further ado, here’s the list!</description><content:encoded>&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Category&lt;/th&gt;
 &lt;th&gt;#1 tool&lt;/th&gt;
 &lt;th&gt;Alternatives&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;3d animation&lt;/td&gt;
 &lt;td&gt;blender&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;audio editing&lt;/td&gt;
 &lt;td&gt;audacity&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;backup (script)&lt;/td&gt;
 &lt;td&gt;rsync&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;clipboard manager&lt;/td&gt;
 &lt;td&gt;copyq&lt;/td&gt;
 &lt;td&gt;clipman&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;cloud&lt;/td&gt;
 &lt;td&gt;dropbox-cli&lt;/td&gt;
 &lt;td&gt;google drive&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;code completion&lt;/td&gt;
 &lt;td&gt;coc.nvim&lt;/td&gt;
 &lt;td&gt;ale&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;code snippets&lt;/td&gt;
 &lt;td&gt;github gists&lt;/td&gt;
 &lt;td&gt;gitlab snippets&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;cursor hider&lt;/td&gt;
 &lt;td&gt;unclutter&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;desktop environment&lt;/td&gt;
 &lt;td&gt;xfce&lt;/td&gt;
 &lt;td&gt;gnome&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;diagram generation&lt;/td&gt;
 &lt;td&gt;plantuml&lt;/td&gt;
 &lt;td&gt;mermaid-js&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;dotfiles management&lt;/td&gt;
 &lt;td&gt;git bare repo&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;dynamic menu&lt;/td&gt;
 &lt;td&gt;dmenu&lt;/td&gt;
 &lt;td&gt;rofi&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;ebook manager&lt;/td&gt;
 &lt;td&gt;calibre&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;encrypted paste bin&lt;/td&gt;
 &lt;td&gt;paste.sh&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;file manager&lt;/td&gt;
 &lt;td&gt;thunar&lt;/td&gt;
 &lt;td&gt;pcmanfm, nautilus&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;file renamer&lt;/td&gt;
 &lt;td&gt;pyrenamer&lt;/td&gt;
 &lt;td&gt;thunar bulk rename&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;flowchart design&lt;/td&gt;
 &lt;td&gt;draw.io&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;fonts monospace&lt;/td&gt;
 &lt;td&gt;jetbrains mono&lt;/td&gt;
 &lt;td&gt;iosevka&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;fonts monospace&lt;/td&gt;
 &lt;td&gt;source code pro&lt;/td&gt;
 &lt;td&gt;inconsolata&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;fonts sansserif&lt;/td&gt;
 &lt;td&gt;lato&lt;/td&gt;
 &lt;td&gt;dejavu&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;gif screen recorder&lt;/td&gt;
 &lt;td&gt;peek&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;image editing (raster)&lt;/td&gt;
 &lt;td&gt;gimp&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;image editing (vector)&lt;/td&gt;
 &lt;td&gt;inkscape&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;interface design&lt;/td&gt;
 &lt;td&gt;figma&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;irc chat&lt;/td&gt;
 &lt;td&gt;hexchat&lt;/td&gt;
 &lt;td&gt;pidgin&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;latex typesetting&lt;/td&gt;
 &lt;td&gt;latex workshop&lt;/td&gt;
 &lt;td&gt;vimtex&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;linux distribution&lt;/td&gt;
 &lt;td&gt;xubuntu&lt;/td&gt;
 &lt;td&gt;debian, manjaro&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;memorizing&lt;/td&gt;
 &lt;td&gt;anki&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;mind mapping&lt;/td&gt;
 &lt;td&gt;freeplane&lt;/td&gt;
 &lt;td&gt;draw.io&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;music player&lt;/td&gt;
 &lt;td&gt;audacious&lt;/td&gt;
 &lt;td&gt;clementine&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;note-taking&lt;/td&gt;
 &lt;td&gt;vimwiki&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;notifications&lt;/td&gt;
 &lt;td&gt;xfce&lt;/td&gt;
 &lt;td&gt;dunst&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;office suite&lt;/td&gt;
 &lt;td&gt;libreoffice&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;operating system&lt;/td&gt;
 &lt;td&gt;gnu/linux&lt;/td&gt;
 &lt;td&gt;macOS&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;paragraph formatter&lt;/td&gt;
 &lt;td&gt;par&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;partition manager&lt;/td&gt;
 &lt;td&gt;gparted&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;password manager&lt;/td&gt;
 &lt;td&gt;keepassxc&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;pdf editing (minimal)&lt;/td&gt;
 &lt;td&gt;pdfarranger&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;pdf viewer&lt;/td&gt;
 &lt;td&gt;atril&lt;/td&gt;
 &lt;td&gt;evince, zathura&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;publishing&lt;/td&gt;
 &lt;td&gt;scribus&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;python documentation&lt;/td&gt;
 &lt;td&gt;sphinx&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;python formatter&lt;/td&gt;
 &lt;td&gt;black&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;python ide&lt;/td&gt;
 &lt;td&gt;pycharm&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;python linter&lt;/td&gt;
 &lt;td&gt;pylint&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;python refactoring&lt;/td&gt;
 &lt;td&gt;rope&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;python test suite&lt;/td&gt;
 &lt;td&gt;pytest&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;python virtualenv manager&lt;/td&gt;
 &lt;td&gt;venv module&lt;/td&gt;
 &lt;td&gt;virtualenvwrapper.sh&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;screen locker&lt;/td&gt;
 &lt;td&gt;slock&lt;/td&gt;
 &lt;td&gt;xflock4&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;screen recorder&lt;/td&gt;
 &lt;td&gt;obs studio&lt;/td&gt;
 &lt;td&gt;simplescreenrecorder&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;screenshot software&lt;/td&gt;
 &lt;td&gt;flameshot&lt;/td&gt;
 &lt;td&gt;xfce4-screenshooter&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;security&lt;/td&gt;
 &lt;td&gt;gnupg2&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;sound system&lt;/td&gt;
 &lt;td&gt;pulseaudio&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;source code explorer&lt;/td&gt;
 &lt;td&gt;sourcetrail&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;sql client&lt;/td&gt;
 &lt;td&gt;dbeaver&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;sql database&lt;/td&gt;
 &lt;td&gt;postgres&lt;/td&gt;
 &lt;td&gt;mysql, sqlite&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;sql viewer&lt;/td&gt;
 &lt;td&gt;sqlitebrowser&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;static website generator&lt;/td&gt;
 &lt;td&gt;pelican&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;task management&lt;/td&gt;
 &lt;td&gt;todoist&lt;/td&gt;
 &lt;td&gt;google keep&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal calculator&lt;/td&gt;
 &lt;td&gt;ipython&lt;/td&gt;
 &lt;td&gt;bc&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal clipboard&lt;/td&gt;
 &lt;td&gt;xclip&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal coursera downloader&lt;/td&gt;
 &lt;td&gt;coursera-dl&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal disk usage&lt;/td&gt;
 &lt;td&gt;ncdu&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal emulator&lt;/td&gt;
 &lt;td&gt;kitty&lt;/td&gt;
 &lt;td&gt;st, xfce4-terminal&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal file archiver&lt;/td&gt;
 &lt;td&gt;p7zip&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal file lister&lt;/td&gt;
 &lt;td&gt;exa&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal file lister tree-style&lt;/td&gt;
 &lt;td&gt;tree&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal file manager&lt;/td&gt;
 &lt;td&gt;vifm&lt;/td&gt;
 &lt;td&gt;nnn&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal file manager (minimal)&lt;/td&gt;
 &lt;td&gt;lf&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal file viewer&lt;/td&gt;
 &lt;td&gt;bat&lt;/td&gt;
 &lt;td&gt;cat&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal fuzzy finder&lt;/td&gt;
 &lt;td&gt;fzf&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal markup converter&lt;/td&gt;
 &lt;td&gt;pandoc&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal network downloader&lt;/td&gt;
 &lt;td&gt;wget&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal network tool&lt;/td&gt;
 &lt;td&gt;curl&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal partition manager&lt;/td&gt;
 &lt;td&gt;parted&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal search tool&lt;/td&gt;
 &lt;td&gt;the silver searcher&lt;/td&gt;
 &lt;td&gt;locate&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal session recorder&lt;/td&gt;
 &lt;td&gt;asciinema&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal shell&lt;/td&gt;
 &lt;td&gt;zsh&lt;/td&gt;
 &lt;td&gt;bash&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal system monitor&lt;/td&gt;
 &lt;td&gt;bashtop&lt;/td&gt;
 &lt;td&gt;glances, htop&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;terminal youtube downloader&lt;/td&gt;
 &lt;td&gt;youtube-dl&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;text editor&lt;/td&gt;
 &lt;td&gt;neovim&lt;/td&gt;
 &lt;td&gt;vim, vs code&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;torrent&lt;/td&gt;
 &lt;td&gt;transmission&lt;/td&gt;
 &lt;td&gt;deluge&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;version control stats&lt;/td&gt;
 &lt;td&gt;gitstats&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;version control system&lt;/td&gt;
 &lt;td&gt;git&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;video player&lt;/td&gt;
 &lt;td&gt;smplayer&lt;/td&gt;
 &lt;td&gt;vlc&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;visual plotting&lt;/td&gt;
 &lt;td&gt;geogebra&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;wallpaper browser/setter&lt;/td&gt;
 &lt;td&gt;nitrogen&lt;/td&gt;
 &lt;td&gt;feh&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;web browser&lt;/td&gt;
 &lt;td&gt;google-chrome&lt;/td&gt;
 &lt;td&gt;firefox, brave&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;window manager&lt;/td&gt;
 &lt;td&gt;i3&lt;/td&gt;
 &lt;td&gt;dwm&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;window manager status bar&lt;/td&gt;
 &lt;td&gt;i3blocks&lt;/td&gt;
 &lt;td&gt;i3status&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;p&gt;For a more granular selection of open source projects I&amp;rsquo;ve used and like (Vim plugins, color schemes, command-line tools, etc.), you can also have a look at &lt;a href="https://github.com/sglavoie?tab=stars"&gt;the GitHub repositories I&amp;rsquo;ve starred&lt;/a&gt; over time.&lt;/p&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Sat, 06 Jun 2020 10:41:00 -0500</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/my-current-set-of-tools-and-customizations/</guid><category>tools</category><category>customization</category><category>software</category></item><item><title>Tip the Scales in Times of Instability With the Rock-Solid Debian</title><link>https://www.sglavoie.com/posts/tip-the-scales-in-times-of-instability-with-the-rock-solid-debian/</link><description>Desperate times call for the best Linux experience possible… And in my experience so far, the Debian distribution with the Xfce desktop environment or with the even lighter alternative i3 tiling window manager makes for a very smooth ride.</description><content:encoded>&lt;hr&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m familiar with Linux distribution hopping, having tried out many options over the years, starting around the time Mandrake 9.2 was popular back in 2003, then switching to a few others including Ubuntu, KNOPPIX, Fedora, Xubuntu, openSUSE, Kubuntu, Mint, Manjaro, Mandriva&amp;hellip; And of course Debian along the way, which undoubtedly used to be relatively harder to get started with.&lt;/p&gt;
&lt;p&gt;My heart has also been stirred by different desktop environments and window managers, from more user-friendly/feature-rich solutions such as KDE, GNOME (along with Unity when it was still a thing), Xfce and Cinnamon to an arguably more obscure selection comprising Openbox, Fluxbox, Enlightenment, Awesome WM, dwm, i3 and bspwm.&lt;/p&gt;
&lt;p&gt;I haven&amp;rsquo;t tried everything that&amp;rsquo;s out there (XMonad being one such &lt;em&gt;esoteric&lt;/em&gt; possibility), but I&amp;rsquo;ve come to realize that what matters most to me is a great balance of stability and usability, allowing to be more focused on any given task at end and therefore leading to increased productivity. For this reason, I recently turned back to a fantastic combo: Debian with an Xfce base and the i3 tiling window manager.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="why-debian"&gt;Why Debian?&lt;/h2&gt;
&lt;h2 id="stability-and-reliability"&gt;Stability and Reliability&lt;/h2&gt;
&lt;p&gt;Debian has always been to me the father figure of stability in the Linux world. Countless servers have run on Debian and continue to do so for good reason: it just works once it is properly set up. I have had some bad luck with other Debian-based distributions like Ubuntu and Linux Mint which strive to be easier to use but remain easier to break, being a bit more bleeding edge and sometimes bloated too with tons of software I don&amp;rsquo;t really need. Fedora, although I like its philosophy and style very much, has always lead to hardware issues on my apparently less-than-ideal machines. Manjaro has been pleasant and easy to use, but Arch feels like the &lt;em&gt;right&lt;/em&gt; choice if one is to build a custom system from scratch. It does take a good amount of time to get things going on such a distribution and usually a good dose of maintenance too, which brought me to seek less time-consuming options. For stability and reliability, Debian is hard to beat in my experience.&lt;/p&gt;
&lt;h2 id="marvelous-package-manager"&gt;Marvelous Package Manager&lt;/h2&gt;
&lt;p&gt;I have always found the package manager on Debian (and derivatives) to be easier to use than alternatives like &lt;code&gt;yum&lt;/code&gt;, &lt;code&gt;DNF&lt;/code&gt; or &lt;code&gt;urpmi&lt;/code&gt; for RPM packages. The &lt;code&gt;pacman&lt;/code&gt; package manager available on Arch-based systems is truly excellent and powerful, but I simply find &lt;code&gt;apt&lt;/code&gt; to be more intuitive after using it for a lot longer.&lt;/p&gt;
&lt;h2 id="it-just-wont-break"&gt;It Just Won&amp;rsquo;t Break&lt;/h2&gt;
&lt;p&gt;Sitting on the bleeding edge is fun and sometimes even rewarding when things finally work as expected, but I have seen that it doesn&amp;rsquo;t stay that way forever. Big updates, sometimes relatively frequent ones, can leave the system in an unusable state. It&amp;rsquo;s often a minor annoyance to fix, but that just doesn&amp;rsquo;t happen on Debian, where packages are vetted mainly for their legendary stability before releasing them. They are indeed quite a bit older than what can be found on other distributions. It&amp;rsquo;s the price to pay, but I came to the conclusion that it&amp;rsquo;s worth it if you&amp;rsquo;re looking to get things done in the long run.&lt;/p&gt;
&lt;figure&gt;
 &lt;a href="https://www.sglavoie.com/images/posts/0020_stable_system_debian/debian_xfce.png"&gt;&lt;img src="https://www.sglavoie.com/images/posts/0020_stable_system_debian/debian_xfce.png" alt="debian_xfce" class="max-size-img-post"&gt;&lt;/a&gt;
 &lt;figcaption&gt;Debian Xfce in its glorious simplicity.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="xfce-is-minimalist-enough-and-hassle-free"&gt;Xfce Is Minimalist Enough and Hassle-Free&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s also about the combination of &lt;strong&gt;Debian + Xfce&lt;/strong&gt;: while Debian makes sure the system remains functional as a whole, Xfce is the solution for interacting with applications and windows without glitches in a &amp;ldquo;&lt;em&gt;floating mode&lt;/em&gt;&amp;rdquo; where all windows can be grabbed and moved around easily. Debian itself keeps old packages around by choice and Xfce has a &lt;em&gt;very&lt;/em&gt; slow release cycle yet also provides stability out of the box. This means that installing Xfce on Debian &lt;strong&gt;has&lt;/strong&gt; to work because bugs have been evened out over &lt;strong&gt;years&lt;/strong&gt; of effort from both teams. This is an easy yet very satisfying solution.&lt;/p&gt;
&lt;h3 id="xfce-vs-kde"&gt;Xfce vs KDE&lt;/h3&gt;
&lt;p&gt;One of the main appeals of desktop environments like KDE and GNOME is that they have more features and add them more often. They do use more resources and even though they have gone through substantial optimizations over the years, they still feel less snappy on older systems compared to simpler window managers or even Xfce. Recently, KDE caught up with Xfce in terms of resources utilization, but there&amp;rsquo;s one major reason I just can&amp;rsquo;t rely on it on a production machine: in every single release I have tried (from KDE 2.x in the early 2000s to Plasma 5.x), I was confronted with numerous bugs within minutes of delving into it. I have always been faced with some minor troubles on GNOME too (versions 2 and 3), but it&amp;rsquo;s never been as severe as with KDE. KDE has plenty of cool ideas and is fully customizable, but that&amp;rsquo;s to a fault in my view.&lt;/p&gt;
&lt;h3 id="xfce-vs-gnome"&gt;Xfce vs GNOME&lt;/h3&gt;
&lt;p&gt;On the other hand, GNOME does consume at least twice as much RAM and CPU compared with Xfce even with no extensions enabled on GNOME and a couple of plugins activated on Xfce. I enjoy how GNOME deals with virtual desktops and think this can be a great productivity enhancement, but once I&amp;rsquo;ve configured Xfce to be mainly keyboard-driven, I prefer keeping it simpler with less transition effects and other nice visual additions. On the graphical side of things, GNOME with Wayland has never worked as well for me as with Xorg, so that&amp;rsquo;s something else to watch for.&lt;/p&gt;
&lt;h3 id="xfce-vs-i3"&gt;Xfce vs i3&lt;/h3&gt;
&lt;p&gt;Well, &lt;code&gt;i3&lt;/code&gt; is a different beast in its own right and has taken a special place in my heart. It uses less memory and CPU than any desktop environment out there and can be used with minimalistic software, like the &lt;code&gt;st&lt;/code&gt; terminal and the dynamic menu &lt;code&gt;dmenu&lt;/code&gt; (read the article about &lt;a href="https://www.sglavoie.com/posts/tip-the-scales-in-times-of-instability-with-the-rock-solid-debian/"&gt;suckless tools&lt;/a&gt; if that resonates with your style!). There are still a few things that I didn&amp;rsquo;t like very much about it, such as the way windows are managed in fullscreen mode, the default lack of a convenient master/slave tiling layout (similar to &lt;code&gt;dwm&lt;/code&gt; and &lt;code&gt;bspwm&lt;/code&gt;) and the way one interacts with external monitor(s), where workspaces use the same numbering across multiple screens. But i3 with Debian feels more mature and is (again) a bit older than what you might find elsewhere. There is &lt;a href="https://i3wm.org/docs/userguide.html#multi_monitor"&gt;a comprehensive section in the i3 User&amp;rsquo;s Guide&lt;/a&gt; that explains how to work around some issues.&lt;/p&gt;
&lt;p&gt;I find that maximizing windows works like what you would expect in Xfce, covering everything but the menu bar. There&amp;rsquo;s also the fact that Xfce makes it very convenient to add and remove virtual desktops on the fly instead of having a bunch of those at all time. With minimal Bash scripting, windows in Xfce can be moved around from one monitor to the other, replicating a useful feature available in i3. Xfce, having only &lt;em&gt;floating&lt;/em&gt; windows, behaves better with applications like VirtualBox and GIMP by default. And there&amp;rsquo;s also some common ground with the &amp;ldquo;pseudo tiling&amp;rdquo; available in Xfce which makes i3 much less unique, although i3 makes it a lot easier to maximize the screen estate being used.&lt;/p&gt;
&lt;figure&gt;
 &lt;a href="https://www.sglavoie.com/images/posts/0020_stable_system_debian/debian_i3.png"&gt;&lt;img src="https://www.sglavoie.com/images/posts/0020_stable_system_debian/debian_i3.png" alt="debian_i3" class="max-size-img-post"&gt;&lt;/a&gt;
 &lt;figcaption&gt;Debian i3 using up to the last available pixel.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In the department of customization, i3 is very good, but having to edit a configuration file manually each time a little change is desired can become tiresome. Xfce has a wonderful settings manager which makes it easy to adjust many system settings as well as those related only to the window manager. i3 requires initially quite a bit more effort to work and will incur a few additional dependencies if one wants to make it look better. Xfce strikes a very good balance: it&amp;rsquo;s highly customizable; pretty much bug-free in my experience and is usable right away without any surprise. Once both are fully configured to match one&amp;rsquo;s need, I tend to rely on i3 to be more productive and keep Xfce as a functional backup if someone else is going to try to use the mouse on my system to accomplish any kind of useful action with the windows ;).&lt;/p&gt;
&lt;p&gt;I find that installing Debian with an Xfce base leads to having enough useful software pre-installed compared to a barebone installation and installing i3 afterwards only makes the transition easier.&lt;/p&gt;
&lt;h3 id="xfce-vs-other-window-managers"&gt;Xfce vs Other Window Managers&lt;/h3&gt;
&lt;p&gt;Now, there is an endless stream of other options to explore and I don&amp;rsquo;t know enough about many of those to dare to comment. But from those I tried, I can certainly say that I preferred the ease of use of Xfce over other more minimalistic approaches like Openbox, Enlightenment and Fluxbox. Xfce offers all you need out of the box, but not much more, which is what I&amp;rsquo;m looking for at this moment. On the other hand, making &lt;code&gt;dwm&lt;/code&gt; work in one precise way can be tricky and requires many tweaks before the configuration can remain untouched for a while. I do not like the necessity of recompiling the source code every single time a tiny change is made which led me to discard it as a convenient option in the long run. I like making small visual adjustments to my system from time to time and I want this to be as frictionless as possible.&lt;/p&gt;
&lt;p&gt;Otherwise, &lt;code&gt;dwm&lt;/code&gt; and &lt;code&gt;bspwm&lt;/code&gt; might be some very decent options if one is inclined to be tweaking everything from scratch (it&amp;rsquo;s not really a facultative step anyways!), but Xfce integrates everything one might need under very few different packages and has been extremely reliable for me from the beginning. On any kind of modern machine, the extra system resources used up by Xfce compared to those lighter alternatives really don&amp;rsquo;t mean much in the end because it is compensated with the presence of useful applets and tools which definitely make one&amp;rsquo;s life easier.&lt;/p&gt;
&lt;h2 id="its-ideal-for-developers"&gt;It&amp;rsquo;s Ideal for Developers&lt;/h2&gt;
&lt;h3 id="resources-efficiency--effectiveness"&gt;Resources Efficiency &amp;amp; Effectiveness&lt;/h3&gt;
&lt;p&gt;Debian with a light working environment is resource-efficient and comes with much less software out of the box compared to the KDE or GNOME editions: instead, more resources are available for hungry applications like heavy IDEs. Installing applications specific to those desktop environments work flawlessly too. Not only does Debian consume less resources (especially while running Xfce/i3 on top), it does so extremely well and offers great performance.&lt;/p&gt;
&lt;figure&gt;
 &lt;a href="https://www.sglavoie.com/images/posts/0020_stable_system_debian/resources_utilization.png"&gt;&lt;img src="https://www.sglavoie.com/images/posts/0020_stable_system_debian/resources_utilization.png" alt="resources_utilization" class="max-size-img-post"&gt;&lt;/a&gt;
 &lt;figcaption&gt;Debian Xfce mostly at rest.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="rock-solid-stability-again-big-selling-point"&gt;Rock-Solid Stability (Again, Big Selling Point!)&lt;/h3&gt;
&lt;p&gt;Since it&amp;rsquo;s so stable, any release of Debian is currently supported for at least 3 years and up to 5 years for LTS (Long Term Support) releases, which means one doesn&amp;rsquo;t have to worry about constantly upgrading the system. For a start, upgrading packages on the stable branch of Debian very rarely leads to a broken system, unlike on more bleeding edge distros like Fedora or Manjaro where downloading a few months old release almost guarantees some unexpected challenges.&lt;/p&gt;
&lt;h3 id="packages-availability"&gt;Packages Availability&lt;/h3&gt;
&lt;p&gt;With currently close to 58,000 packages offered on Debian, there&amp;rsquo;s a very good chance that anything one might need will be available. Working with up-to-date, proprietary applications like Google Chrome, Zoom or Slack is also pain-free: most companies, when they do consider Linux, opt for sharing a classic &lt;code&gt;deb&lt;/code&gt; package, the format used on Debian and Debian-based systems. Some of those packages can also be found or added directly to the sources managed by the package manager, allowing for easy, automatic updates.&lt;/p&gt;
&lt;figure&gt;
 &lt;a href="https://www.sglavoie.com/images/posts/0020_stable_system_debian/debian_packages.png"&gt;&lt;img src="https://www.sglavoie.com/images/posts/0020_stable_system_debian/debian_packages.png" alt="debian_packages" class="max-size-img-post"&gt;&lt;/a&gt;
 &lt;figcaption&gt;Synaptic, the legendary package manager.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="large-community-with-freedom-in-mind"&gt;Large Community with Freedom in Mind&lt;/h3&gt;
&lt;p&gt;Because no company backs Debian directly, it relies on a community effort to keep going and it has done so for a long time, having now hundreds of distributions being based on it. This means that it&amp;rsquo;s easy to find a ton of documentation and tutorials giving solutions taking into account Debian-based systems. One will be able to fix any issue cropping up thanks to the &lt;a href="http://forums.debian.net/"&gt;Debian User Forums&lt;/a&gt; and of course thanks to the immense popularity of Ubuntu, which contributes tremendously to making answers available in &lt;a href="https://ubuntuforums.org/"&gt;its forums&lt;/a&gt; and in the &lt;a href="https://askubuntu.com/"&gt;Stack Exchange&lt;/a&gt; network.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="resources-to-get-started"&gt;Resources to Get Started&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.debian.org/doc/"&gt;Debian Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://forums.debian.net/"&gt;Debian User Forums&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.debian.org/"&gt;Debian Wiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Developer-oriented: &lt;a href="https://www.debian.org/doc/manuals/developers-reference/resources.html"&gt;resources for contributing to Debian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Cloud-oriented: &lt;a href="https://www.digitalocean.com/community/tags/debian"&gt;tutorials for Debian on Digital Ocean&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://i3wm.org/"&gt;i3 tiling window manager&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.xfce.org/"&gt;Xfce Desktop Environment&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;After trying many different Linux distributions and desktop environments, I keep coming back to what satisfies me best in the end: stability and usability. Debian feels a bit old, but at the same time one knows it delivers on what it promises. The same can be said for Xfce and i3. This is also what makes them such a good combo: despite not being cutting edge in the realm of features, there&amp;rsquo;s no way it could break as easily as KDE or GNOME, particularly on less stable distributions. There are still a few rough edges when it comes to customizing both Xfce and i3 in my opinion, but when looking for something that just works and will keep doing so, Debian is hard to beat.&lt;/p&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Sun, 05 Apr 2020 12:14:00 -0500</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/tip-the-scales-in-times-of-instability-with-the-rock-solid-debian/</guid><category>tools</category><category>debian</category><category>i3</category><category>linux</category><category>stability</category><category>xfce</category></item><item><title>Suckless: Minimalist Tools That Work Great</title><link>https://www.sglavoie.com/posts/suckless-minimalist-tools-that-work-great/</link><description>I have been using a few different tools from suckless.org for a while and I must say that once everything is configured properly, they are a joy to use. Some tools, like dmenu and slock, seem simple on the surface, but they allow to simplify your workflow by quite a bit. Another one, st, is a truly fantastic and lightweight terminal!</description><content:encoded>&lt;hr&gt;
&lt;h2 id="what-are-suckless-tools"&gt;What are Suckless tools?&lt;/h2&gt;
&lt;p&gt;From the &lt;a href="https://suckless.org/"&gt;official website&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
 &lt;p&gt;Home of &lt;a href="https://dwm.suckless.org/"&gt;dwm&lt;/a&gt;,
&lt;a href="https://tools.suckless.org/dmenu"&gt;dmenu&lt;/a&gt; and other quality software
with a focus on simplicity, clarity, and frugality.&lt;/p&gt;

&lt;/blockquote&gt;&lt;p&gt;There are also other incredible pieces of software that are worth
mentioning, including &lt;a href="https://st.suckless.org/"&gt;st&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here, I will only go over the tools I use frequently, but keep in mind
that everything from Suckless&amp;hellip; Sucks less.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="st--simple-terminal"&gt;st — Simple Terminal&lt;/h2&gt;
&lt;p&gt;I did not know what to expect from this terminal and I found it delivers what I need! Configuring it is quite different than other terminals like &lt;a href="https://konsole.kde.org/"&gt;Konsole&lt;/a&gt; or &lt;a href="https://help.gnome.org/users/gnome-terminal/stable/"&gt;GNOME Terminal&lt;/a&gt;: you just edit a text file and compile the software before you can even use it. It may seem tedious at first, but compiling such a small piece of software is very fast and it needs to be done on seldom occasions when you may want to adjust a setting.&lt;/p&gt;
&lt;p&gt;I have experienced rendering issues when scaling the text with other terminals (including Konsole), where some weird flickering would happen at certain font sizes. &lt;a href="https://st.suckless.org/"&gt;Simple Terminal&lt;/a&gt; just works without hassle. It doesn&amp;rsquo;t have much bells and whistles out of the box, but it can be &lt;em&gt;patched&lt;/em&gt; to extend its functionality if you know what you are doing. I don&amp;rsquo;t, so my compiled version remains pretty basic although I must say I like the simplicity of it very much.&lt;/p&gt;
&lt;h3 id="configuration"&gt;Configuration&lt;/h3&gt;
&lt;p&gt;I didn&amp;rsquo;t have to adapt the configuration file very much. I ended up changing mainly the following lines:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;cpp&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;Consolas:pixelsize=18:antialias=true:autohint=true&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;shell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;/bin/zsh&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;/* Terminal colors (16 first used in escape sequence) */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;colorname&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="cm"&gt;/* 8 normal colors */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#1c1c1c&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* black */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#ff005b&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* red */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#70e502&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* green */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#fcdd11&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* yellow */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#00a0ea&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* blue */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#bd1efc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* magenta */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#14ecfc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* cyan */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#ededed&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* white */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="cm"&gt;/* 8 bright colors */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#666666&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* black */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#ff00a0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* red */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#5dff00&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* green */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#ff9f00&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* yellow */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#71a9fc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* blue */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#d571fc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* magenta */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#6cf2fc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* cyan */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#fcfcfc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* white */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="cm"&gt;/* special colors */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#0a0500&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* background */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;257&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#e0e0e0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* foreground */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * Default colors (colorname index)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * foreground, background, cursor
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;defaultfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;257&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;defaultbg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;defaultcs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;257&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;defaultrcs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * Colors used, when the specific fg == defaultfg. So in reverse mode this
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * will reverse too. Another logic would only make the simple feature too
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * complex.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;defaultitalic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;defaultunderline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * Default shape of cursor
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * 2: Block (&amp;#34;█&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * 4: Underline (&amp;#34;_&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * 6: Bar (&amp;#34;|&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * 7: Snowman (&amp;#34;☃&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;cursorshape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * Default columns and rows numbers
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="dmenu"&gt;dmenu&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://tools.suckless.org/dmenu/"&gt;dmenu&lt;/a&gt; can be configured in the same way as Simple Terminal by editing a text file and compiling the software. I like to use many keyboard shortcuts, but I find that for launching applications I don&amp;rsquo;t use that often or to launch custom Bash or Python scripts on the fly, nothing beats the simplicity of &lt;strong&gt;dmenu&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="configuration-1"&gt;Configuration&lt;/h3&gt;
&lt;p&gt;This one is very easy to configure and doesn&amp;rsquo;t really require any attention at all besides defining where you want it to appear on the screen and what colors you prefer. Here are some smalls changes I made to the configuration in &lt;code&gt;config.h&lt;/code&gt; before compiling it:&lt;/p&gt;
&lt;div class="code-block"&gt;
 &lt;div class="code-block__bar" data-pagefind-ignore&gt;
 &lt;span class="code-block__lang"&gt;cpp&lt;/span&gt;
 &lt;button class="code-block__wrap" type="button" aria-pressed="false" hidden&gt;Wrap&lt;/button&gt;
 
 &lt;/div&gt;
 &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;topbar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cm"&gt;/* -b option; if 0, dmenu appears at bottom */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;/* -fn option overrides fonts[0]; default X11 font or font set */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;fonts&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="s"&gt;&amp;#34;Consolas:size=12&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cm"&gt;/* -p option; prompt to the left of input field */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SchemeLast&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="cm"&gt;/* fg bg */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SchemeNorm&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#D8D8D8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#000000&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SchemeSel&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#000000&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#ff7400&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SchemeOut&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#000000&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#00ffff&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;/* -l option; if nonzero, dmenu uses vertical list with given number of lines */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * Characters not considered part of a word while deleting words
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * for example: &amp;#34; /?\&amp;#34;&amp;amp;[]&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;worddelimiters&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34; &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="slock"&gt;slock&lt;/h2&gt;
&lt;p&gt;What can be said about slock? It just works and never gets in your way. Set it and really forget it. From the official page:&lt;/p&gt;
&lt;blockquote&gt;
 &lt;p&gt;This is the simplest X screen locker we are aware of.&lt;/p&gt;

&lt;/blockquote&gt;&lt;p&gt;Again, its simplicity is charming and nothing really needs to be changed in &lt;code&gt;config.h&lt;/code&gt; apart from colors and setting the correct username and group name to lock and unlock the screen.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This was a lighting fast overview of some of the tools I now use on a daily basis. Suckless tools caught my attention and kept it! They offer other specialized software like a web browser, a tool for presentation from the terminal, IRC client and others&amp;hellip; but the ones described here are really those which I now can&amp;rsquo;t live without.&lt;/p&gt;
&lt;p&gt;I was originally inspired to seek out those tools thanks to &lt;a href="https://lukesmith.xyz/"&gt;Luke Smith&lt;/a&gt;, a &lt;a href="https://www.youtube.com/channel/UC2eYFnH61tmytImy1mTYvhA"&gt;fantastic YouTuber&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Sun, 12 May 2019 22:50:00 -0500</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/suckless-minimalist-tools-that-work-great/</guid><category>tools</category><category>productivity</category><category>software</category><category>web</category></item><item><title>Surfing with qutebrowser — a Keyboard-Driven Web Browser</title><link>https://www.sglavoie.com/posts/surfing-with-qutebrowser-a-keyboard-driven-web-browser/</link><description>I recently started using this software as my main web browser and, as a Vim (Neovim) user, I must say I am finding the adventure quite compelling!</description><content:encoded>&lt;hr&gt;
&lt;h2 id="what-is-qutebrowser"&gt;What is qutebrowser?&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://qutebrowser.org/"&gt;qutebrowser&lt;/a&gt; is a web browser that makes
great use of &lt;strong&gt;Vim&lt;/strong&gt; commands to navigate. From its website:&lt;/p&gt;
&lt;blockquote&gt;
 &lt;p&gt;qutebrowser is a keyboard-focused browser with a minimal GUI. It’s
based on Python and PyQt5 and free software, licensed under the GPL.
It was inspired by other browsers/addons like dwb and
Vimperator/Pentadactyl.&lt;/p&gt;

&lt;/blockquote&gt;&lt;hr&gt;
&lt;h3 id="why-use-it"&gt;Why use it?&lt;/h3&gt;
&lt;h4 id="perfect-companion-to-any-vim-user"&gt;Perfect companion to any Vim user&lt;/h4&gt;
&lt;p&gt;The first notable feature of &lt;strong&gt;qutebrowser&lt;/strong&gt; is obviously how well
the use of keyboard shortcuts is integrated with the experience of
using the software (there is a useful key binding cheatsheet on the
&lt;a href="https://qutebrowser.org/"&gt;homepage&lt;/a&gt;). Search bindings (&lt;code&gt;/&lt;/code&gt;, &lt;code&gt;n&lt;/code&gt;/&lt;code&gt;N&lt;/code&gt;,
&lt;code&gt;?&lt;/code&gt;), visual mode (&lt;code&gt;v&lt;/code&gt;), tabs (&lt;code&gt;gt&lt;/code&gt;, &lt;code&gt;T&lt;/code&gt;), copy (&lt;code&gt;yy&lt;/code&gt;), open URLs
(&lt;code&gt;o&lt;/code&gt;/&lt;code&gt;O&lt;/code&gt;), &lt;code&gt;J&lt;/code&gt; and &lt;code&gt;K&lt;/code&gt; for switching tabs, etc. Many key bindings will
look familiar to Vim users.&lt;/p&gt;
&lt;p&gt;It even features a &lt;em&gt;command mode&lt;/em&gt;, accessible by typing &lt;code&gt;:&lt;/code&gt;, just like
with Vim. Settings can be set from there directly, help can be accessed
by typing &lt;code&gt;:help [command/setting]&lt;/code&gt;, the configuration file can be
customized on the fly, the position and size of tabs can be set in many
ways, history and settings page can be accessed and navigated through
like any other page (&lt;code&gt;hjkl&lt;/code&gt;, &lt;code&gt;Ctrl + d&lt;/code&gt;, &lt;code&gt;Ctrl + u&lt;/code&gt;, &lt;code&gt;gg&lt;/code&gt;, &lt;code&gt;G&lt;/code&gt;), etc.&lt;/p&gt;
&lt;p&gt;Even though it is based on Python which is a &amp;ldquo;&lt;em&gt;slow&lt;/em&gt;&amp;rdquo; language, most of
the libraries and the web engine rely on C++ and the end result is a
fast and powerful browser that&amp;rsquo;s totally customizable&amp;hellip; Not unlike Vim.&lt;/p&gt;
&lt;h4 id="minimal-interface--more-space"&gt;Minimal interface ⇒ more space&lt;/h4&gt;
&lt;p&gt;Until I upgrade my laptop to a larger screen (≥ 17 inches), my
current setup with its 15 diagonal inches benefits tremendously
from configuring all applications to use pixels as efficiently as
possible — which is why I now use exclusively [i3 as a window
manager](/posts/2019/01/08/using-i3-as-a-window-manager-for-increased-pr
oductivity/).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;qutebrowser&lt;/strong&gt; doesn&amp;rsquo;t even have a menu bar. By default, it only
displays a small tab bar at the top (which can be moved to another
side or removed) and a thin status bar at the bottom to see the URL,
the current position in the page expressed in percentage, the number
assigned to the current tab, etc. There is also a fullscreen mode
available like in most other web browsers with the key &lt;code&gt;F11&lt;/code&gt;, which will
remove any window decoration and leave you in a (possibly) productive
mode.&lt;/p&gt;
&lt;h4 id="history-bookmarks-and-quickmarks"&gt;History, bookmarks and quickmarks&lt;/h4&gt;
&lt;p&gt;The history page (&lt;code&gt;:history&lt;/code&gt;), while being minimal like all the rest,
loads extremely fast and is a joy to navigate. The bookmarks page (&lt;code&gt;Sb&lt;/code&gt;)
is equally simple yet useful. The way I like to open a bookmark is in
a new tab by typing &lt;code&gt;gB&lt;/code&gt; (&lt;strong&gt;g&lt;/strong&gt;o &lt;strong&gt;b&lt;/strong&gt;ookmark as a mnemonic. Note that
usually all uppercase letters open or do some action in a new space,
such as a window or a tab).&lt;/p&gt;
&lt;p&gt;Where &lt;strong&gt;qutebrowser&lt;/strong&gt; shines in my opinion is with quickmarks. This is
a simple but genius way to find what you are looking for: you type &lt;code&gt;b&lt;/code&gt;
(or &lt;code&gt;B&lt;/code&gt; for new tab) to open a list of pages that are defined with a
keyword, you type that keyword (or can type anything to search for a
page), move around with arrow keys if necessary to select a page, type
&lt;code&gt;Enter&lt;/code&gt; and there you go. As far as I know, Google Chrome doesn&amp;rsquo;t offer
the equivalent (only keywords for search engines). Firefox allows to add
keywords for any bookmark that you have, but there is no good way to
search around for keywords, define them quickly, remove them quickly,
etc. &lt;strong&gt;qutebrowser&lt;/strong&gt; comes with a set of commands that serve just this
purpose (&lt;code&gt;:quickmark-add&lt;/code&gt;, &lt;code&gt;quickmark-del&lt;/code&gt;, etc.), apart from adding
them very quickly by typing &lt;code&gt;m&lt;/code&gt; for quickmarks and &lt;code&gt;M&lt;/code&gt; for bookmarks.&lt;/p&gt;
&lt;h4 id="hinting-system"&gt;Hinting system&lt;/h4&gt;
&lt;p&gt;You may have tried extensions for other web browsers such as Vimium,
Vimperator, or Vimium-FF that let you navigate with Vim shortcuts. They
usually use the letter &lt;code&gt;f&lt;/code&gt; for &lt;strong&gt;f&lt;/strong&gt;ollowing links. Some text appear on
the screen for each visible link and you type the letters that match the
link you want to see. The best way to get how it works is just to try it
out, really.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.sglavoie.com/images/posts/0007_surfing-with-qutebrowser/qutebrowser_hints.png"&gt;&lt;img src="https://www.sglavoie.com/images/posts/0007_surfing-with-qutebrowser/qutebrowser_hints.png" alt="qutebrowser-hints" class="max-size-img-post"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Well, &lt;strong&gt;qutebrowser&lt;/strong&gt; takes those features and make them even greater!
There is, for example, a &lt;em&gt;rapid hinting mode&lt;/em&gt; you can access by typing
&lt;code&gt;f&lt;/code&gt; followed by &lt;code&gt;Ctrl + r&lt;/code&gt; and it will open links in the background
for as long as you type any matching &lt;em&gt;hint&lt;/em&gt; provided as a letter or
combination of two letters when there are many visible links on the
page. This is especially useful when doing some research to avoid
breaking the rhythm on the current reading while preparing more
resources to be available.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="whats-the-best-place-to-start-learning-about-it"&gt;What&amp;rsquo;s the best place to start learning about it?&lt;/h3&gt;
&lt;p&gt;The &lt;a href="https://qutebrowser.org/doc/help/"&gt;help page&lt;/a&gt; on the website
contains all the information you really need. I did stumble upon the
following resources, which I would recommend to read in order of
appearance:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://qutebrowser.org/doc/quickstart.html"&gt;Quickstart Guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;[Key bindings
cheatsheet](&lt;a href="https://raw.githubusercontent.com/qutebrowser/qutebrowser/ma"&gt;https://raw.githubusercontent.com/qutebrowser/qutebrowser/ma&lt;/a&gt;
ster/doc/img/cheatsheet-big.png).&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qutebrowser.org/doc/faq.html"&gt;FAQ&lt;/a&gt;, which is interesting
but not indispensable. It did help me take a step forward in trying out
&lt;strong&gt;qutebrowser&lt;/strong&gt; though.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Open inside qutebrowser&lt;/em&gt;: &lt;a href="qute://help/settings.html#bindings.default"&gt;default
keybindings&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Open inside qutebrowser&lt;/em&gt;: &lt;a href="qute://settings/"&gt;settings page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qutebrowser.org/doc/help/commands.html"&gt;List of commands&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;qutebrowser&lt;/strong&gt; has been around for a while and is very stable. It is
ideal for any Vim user and is a great option for anyone interested in
efficiency and/or in trying out alternatives. Because I am very fond
of relying on the keyboard as much as possible and because I love to
program in Python, I have grown to like this web browser very quickly.
This is clearly a tool designed for a specific type of user in mind and
who knows, you might be one of those.&lt;/p&gt;</content:encoded><dc:creator>Sébastien Lavoie</dc:creator><pubDate>Mon, 11 Feb 2019 14:46:00 -0600</pubDate><guid isPermaLink="false">https://www.sglavoie.com/posts/surfing-with-qutebrowser-a-keyboard-driven-web-browser/</guid><category>tools</category><category>productivity</category><category>software</category><category>web</category></item></channel></rss>