Jekyll2023-07-07T20:21:55+00:00https://www.flyinggrizzly.net/Flying GrizzlyTabletop games, chatter, and code thingsSean DMRsay-hi@grz.liImporting save and system data from Trails of Cold Steel III and IV, and Zero and Ao, into Trails into Reverie on Steam Deck2023-07-07T00:00:00+00:002023-07-07T00:00:00+00:00https://www.flyinggrizzly.net/2023/07/importing-falcom-system-and-save-data-on-steam-deck-for-trails-into-reverie<p>I love my Steam Deck. I also love that it’s a full-on Linux computer. I also love the <em>Trails</em> series, and I’ve been
playing them for ages. They usually include new-game bonuses if they can detect save or system data from the prior games
in the series. But this detection relies on Windows’ “Saved Games” folder structure, which is emulated, but <em>per-game</em>
in Proton/Steam Deck. Here’s how to get <em>Trails into Reverie</em> to detect your save data from the other games.</p>
<!-- more -->
<p>Quick note–this guide uses Reverie as an example, but some version of this should work for all Falcom games, and
probably lots of others too.</p>
<h2 id="prereqs">Prereqs</h2>
<ul>
<li>have the other games installed
<ul>
<li>Trails from Zero</li>
<li>Trails to Azure</li>
<li>Trails of Cold Steel III</li>
<li>Trails of Cold Steel IV</li>
</ul>
</li>
<li>have Reverie installed, and booted once</li>
</ul>
<h2 id="procedure">Procedure</h2>
<p>Steam/Proton install these Windows games into their own isolated virtual Windows filesystems. What we need to do is copy
the save and data folders from each game’s own virtual space into the Reverie virtual space.</p>
<p>It’s useful to know the Steam IDs for each game. You can find these on the store page. Ex: Azure is at
<code class="highlighter-rouge">https://store.steampowered.com/app/1668520/The_Legend_of_Heroes_Trails_to_Azure/</code>, where that number between <code class="highlighter-rouge">/app/</code>
and <code class="highlighter-rouge">/The_Legend_of_Heroes_Trails_to_Azure</code> is the ID. Grab these for each game.</p>
<p>All of these folders are at
<code class="highlighter-rouge">/users/deck/.local/share/Steam/steamapps/compatdata/GAME_ID/pfx/drive_c/users/steamuser/Saved Games/Falcom</code></p>
<p>If you created a different user than the default for a Steam Deck, I assume you know what you’re doing, but just in case
replace the <code class="highlighter-rouge">/users/deck</code> with <code class="highlighter-rouge">/users/YOUR_USERNAME</code>.</p>
<p>Once you’re in the <code class="highlighter-rouge">/drive_c</code> folder, things should start to look like a Windows filesystem.</p>
<p>Inside the <code class="highlighter-rouge">/Falcom</code> directory, you’ll find the subfolder for the specific game. Falcom names these kinda oddly unless
you know the production history of the games. For example, CS4 is <code class="highlighter-rouge">Falcom/ed8_psv4</code>. <code class="highlighter-rouge">ed</code> stands for “Eiyuu Densetsu”,
or “Legend of Heroes in Japanese”, and the 8 is because the Cold Steel series as a whole is the 8th entry in ED. Not
important, but curious.</p>
<p>Now:</p>
<ol>
<li>Boot to the Desktop view on your Steam Deck. This is a lot easier if you connect a keyboard and mouse too</li>
<li>Open a fileviewer, and find the Reverie data folder, at the filepath above, interpolating the right game ID. It should already have a <code class="highlighter-rouge">Falcom/ed8_psv5</code> folder for Reverie, which I guess is still part of the Cold Steel series</li>
<li>For each of Zero, Azure, CS3, and CS4, copy the save folder from their specific folder into the Reverie folder.</li>
</ol>
<p>Now go back into handheld mode, boot up Reverie, and you should be able to get all the bonuses for Zero, Azure, CS3 and
CS4. I’ve found that if I didn’t get the import bonuses for Zero/Ao, the game wasn’t offering me the option to check for
CS3+4 data.</p>
<h2 id="bonuses-in-reverie">Bonuses in Reverie</h2>
<p>OK so this is obvi specific to Reverie, but the bonsues I got were:</p>
<table>
<thead>
<tr>
<th>Game</th>
<th>Condition</th>
<th>Bonus</th>
</tr>
</thead>
<tbody>
<tr>
<td>Zero</td>
<td>Have data</td>
<td>All Sepith x200</td>
</tr>
<tr>
<td>Ao</td>
<td>Have data</td>
<td>Sepith Mass x600</td>
</tr>
<tr>
<td>Zero + Ao</td>
<td>Have data for both games</td>
<td>Liberator’s Testament accessory (All stats boost + CP rate up)<br />Zeram Capsule x1</td>
</tr>
<tr>
<td>Cold Steel III</td>
<td>Have data</td>
<td>U-Material x50</td>
</tr>
<tr>
<td>Cold Steel IV</td>
<td>Have data</td>
<td>Shining Pom Incense x1</td>
</tr>
<tr>
<td>Cold Steel III + IV</td>
<td>Have data for both games</td>
<td>Hero’s Testament (All stats boost + CP rate up)<br />Zeram Capsule x1</td>
</tr>
<tr>
<td>Cold Steel IV</td>
<td>Have clear data</td>
<td>Brave Soul x1</td>
</tr>
<tr>
<td>Cold Steel IV</td>
<td>Thors Unity at max or max - 1</td>
<td>Spirit Incense x2<br />Dragon Incense x1 (only for max, “Unbreakable”)</td>
</tr>
<tr>
<td>Cold Steel IV</td>
<td>Instructor Rank A (max - 1)</td>
<td>Crimson Medal (All stats boost + stat debuffs nullified)</td>
</tr>
<tr>
<td>Cold Steel IV</td>
<td>Instructor Rank A0/S (max)</td>
<td>Lionheart’s Grand Cordon (All stats big boost + stat debuffs nullified)</td>
</tr>
</tbody>
</table>SeanI love my Steam Deck. I also love that it’s a full-on Linux computer. I also love the Trails series, and I’ve been playing them for ages. They usually include new-game bonuses if they can detect save or system data from the prior games in the series. But this detection relies on Windows’ “Saved Games” folder structure, which is emulated, but per-game in Proton/Steam Deck. Here’s how to get Trails into Reverie to detect your save data from the other games.Usable multi-selects in Rails forms2022-09-12T00:00:00+00:002022-09-12T00:00:00+00:00https://www.flyinggrizzly.net/2022/09/usable-multi-selects-in-rails-forms<p>Look. I use Rails for side projects because I’m lazy. But not so lazy that I’m willing to put up with really really bad
multi-selects.</p>
<p>This is a quick and not very dirty way to get clean and largely seamless multi-selects for Rails forms that still send
data over REST, without changing the way anything works. It uses React, Stimulus, and Rails ActionController.</p>
<!-- more -->
<p>I’m assuming a Rails 7+ setup with <code class="highlighter-rouge">esbuild</code> as the JS bundler. This will also work with Webpack/er, and with importmap,
assuming you use a JSX shim. I’ve used this as far back as Rails 6 too, with minor changes for API change.</p>
<h2 id="adding-a-multi-select-react-component">Adding a multi-select React component</h2>
<p>This component is going to be mounted on a single DOM node in our form, and provide our multi-select behavior. <strong>It is
also going to write out our form data on every render</strong>, which is how we get our data back out of JS-land and into the
Rails form.</p>
<p>First, we need to install React, ReactDOM, and <code class="highlighter-rouge">react-select</code>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% yarn add react react-dom react-select
</code></pre></div></div>
<p>Then, add a basic component:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// app/javascript/components/multi_select.jsx</span>
<span class="k">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">useState</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'react'</span>
<span class="k">import</span> <span class="nx">Select</span> <span class="k">from</span> <span class="s1">'react-select'</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">RailsFormInputMultiSelect</span> <span class="o">=</span> <span class="p">({</span>
<span class="nx">name</span><span class="p">,</span> <span class="c1">// Rails form input field name, like "kit[producers][]"</span>
<span class="nx">collection</span><span class="p">,</span> <span class="c1">// JSON, array of objects like { value: ID, label: NAME }</span>
<span class="nx">currentValue</span><span class="p">,</span> <span class="c1">// JSON, array of scalars whose values must be a subset of the collection values</span>
<span class="p">})</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">options</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">collection</span><span class="p">)</span>
<span class="kd">const</span> <span class="p">[</span> <span class="nx">values</span><span class="p">,</span> <span class="nx">setValues</span> <span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">currentValue</span><span class="p">))</span>
<span class="kd">const</span> <span class="nx">updateValues</span> <span class="o">=</span> <span class="p">(</span><span class="nx">selections</span><span class="p">,</span> <span class="nx">_selectionEvent</span><span class="p">)</span> <span class="o">=></span> <span class="nx">setValues</span><span class="p">(</span><span class="nx">selections</span><span class="p">.</span><span class="nx">map</span><span class="p">(({</span> <span class="nx">value</span> <span class="p">})</span> <span class="o">=></span> <span class="nx">value</span><span class="p">))</span>
<span class="kd">const</span> <span class="nx">selectValue</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">filter</span><span class="p">(({</span> <span class="nx">value</span> <span class="p">})</span> <span class="o">=></span> <span class="nx">values</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">value</span><span class="p">))</span>
<span class="k">return</span> <span class="p">(</span>
<span class="o"><></span>
<span class="p">{</span>
<span class="nx">value</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">v</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="o">=></span> <span class="p">(</span>
<span class="o"><</span><span class="nx">input</span> <span class="nx">key</span><span class="o">=</span><span class="p">{</span> <span class="nx">index</span> <span class="p">}</span> <span class="nx">type</span><span class="o">=</span><span class="s2">"hidden"</span> <span class="nx">name</span><span class="o">=</span><span class="p">{</span> <span class="nx">name</span> <span class="p">}</span> <span class="nx">value</span><span class="o">=</span><span class="p">{</span> <span class="nx">v</span> <span class="p">}</span> <span class="nx">readOnly</span> <span class="o">/></span>
<span class="p">))</span>
<span class="p">}</span>
<span class="o"><</span><span class="nx">Select</span>
<span class="nx">value</span><span class="o">=</span><span class="p">{</span> <span class="nx">selectValue</span> <span class="p">}</span>
<span class="nx">onChange</span><span class="o">=</span><span class="p">{</span> <span class="nx">updateValues</span> <span class="p">}</span>
<span class="nx">options</span><span class="o">=</span><span class="p">{</span> <span class="nx">options</span> <span class="p">}</span>
<span class="nx">isMulti</span>
<span class="nx">closeMenuOnSelect</span><span class="o">=</span><span class="p">{</span> <span class="kc">false</span> <span class="p">}</span>
<span class="sr">/</span><span class="err">>
</span> <span class="o"><</span><span class="sr">/</span><span class="err">>
</span> <span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<aside class="callout" id="callout-fixed-the-hidden-inputs">
<h6>Fixed the hidden inputs</h6>
<p><p>A previous version of this had a single input with all the values joined. Which works, but means you’d have to
parse the stringy array on the backend, which is extra pointness work.</p>
<p>Instead, map over the value and render an input per value. Rails will pick this up, as long as they all share a name,
and convert the multiple inputs to an array.</p>
</p>
</aside>
<p>This component accepts 3 props:</p>
<ul>
<li>a <code class="highlighter-rouge">name</code>, which corresponds to the <code class="highlighter-rouge">name</code> attribute Rails would have given this input if we used <code class="highlighter-rouge">f.select :attr_name</code>
in a standard Rails model. This will look like <code class="highlighter-rouge">"my_model[association_ids][]"</code> (which indicates that Rails is
expecting an array of scalars representing the IDs in a <code class="highlighter-rouge">has_and_belongs_to_many</code> association). Easiest way to figure
out what to put here is to actually render out the <code class="highlighter-rouge">f.select :attr_name</code> and inspect the <code class="highlighter-rouge">name</code> attr on the generated
<code class="highlighter-rouge"><input /></code> and steal it</li>
<li>a <code class="highlighter-rouge">collection</code> prop, which is a JSON array of objects representing each option in the select. They need to be structured
like <code class="highlighter-rouge">{ value: ID, label: "Friendly label representing the ID" }</code>, where the ID is probably your database ID. In
reality this can be any scalar you want, so long as your controller can convert that into something that’s useful in
the backend</li>
<li>a <code class="highlighter-rouge">currentValue</code> prop, which is a JSON array of scalars, which should be a subset of the <code class="highlighter-rouge">value</code> fields in your
<code class="highlighter-rouge">collection</code> prop. For example, the currently present IDs on <code class="highlighter-rouge">my_model.association_ids</code></li>
</ul>
<p>Don’t worry about how we’re going to get these props into the component from our Rails backend yet.</p>
<p>The real work being done here (other than a sane and usable multi-select React component) is the <code class="highlighter-rouge"><input name={ name } type="hidden"
value={ values.join(',') } readOnly /></code> being written out. Every time that the <code class="highlighter-rouge">values</code> state is updated, this input
node will get rerendered with the most up-to-date selection. And, <strong>because it is using the name we passed in (having
stolen it from a standard Rails input render)</strong>, when the Rails form gets submitted this is going to get picked up and
passed along to the backend as if it had been rendered by <code class="highlighter-rouge">f.select ...</code>.</p>
<h2 id="mounting-the-react-component-without-going-batshit">Mounting the React component without going batshit</h2>
<p>We’re using this one component to add a small bit of behavior to an existing Rails view, so we definitely don’t want a
massive new JS frontend. Which… most React applications want to be.</p>
<p>Instead, we’re going to use Stimulus to create a small controller that will allow us to tactically mount and render this
component when we need it in a form.</p>
<p>I’m assuming you have Stimulus installed, since it’s a Rails application. If not, <a href="https://hotwire.dev">hotwire.dev</a> and
the Rails docs have you covered.</p>
<p>Add a Stimulus controller something like this:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// app/javascript/controllers/rails_input_multi_select_controller.js</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Controller</span> <span class="p">}</span> <span class="k">from</span> <span class="s2">"@hotwired/stimulus"</span>
<span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="s1">'react'</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">createRoot</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'react-dom/client'</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">RailsFormInputMultiSelect</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'components/rails_form_input_multi_select'</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">class</span> <span class="nx">RailsFormInputMultiSelectController</span> <span class="kd">extends</span> <span class="nx">Controller</span> <span class="p">{</span>
<span class="nx">connect</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">collection</span><span class="p">,</span> <span class="nx">currentValue</span> <span class="p">}</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">element</span><span class="p">.</span><span class="nx">dataset</span>
<span class="k">import</span><span class="p">(</span><span class="s1">'components/rails_form_input_multi_select'</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">module</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">RailsFormInputMultiSelect</span> <span class="o">=</span> <span class="nx">module</span><span class="p">.</span><span class="nx">RailsFormInputMultiSelect</span>
<span class="kd">const</span> <span class="nx">root</span> <span class="o">=</span> <span class="nx">createRoot</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">element</span><span class="p">)</span>
<span class="nx">root</span><span class="p">.</span><span class="nx">render</span><span class="p">(</span>
<span class="o"><</span><span class="nx">RailsFormInputMultiSelect</span>
<span class="nx">name</span><span class="o">=</span><span class="p">{</span> <span class="nx">name</span> <span class="p">}</span>
<span class="nx">collection</span><span class="o">=</span><span class="p">{</span> <span class="nx">collection</span> <span class="p">}</span>
<span class="nx">currentValue</span><span class="o">=</span><span class="p">{</span> <span class="nx">currentValue</span> <span class="p">}</span>
<span class="sr">/</span><span class="err">>
</span> <span class="p">)</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>and register it like this:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// app/javascript/controllers/index.js</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">application</span> <span class="p">}</span> <span class="k">from</span> <span class="s2">"./application"</span>
<span class="k">import</span> <span class="nx">RailsFormInputMultiSelectController</span> <span class="k">from</span> <span class="s1">'./rails_form_input_multi_select_controller'</span>
<span class="nx">application</span><span class="p">.</span><span class="nx">register</span><span class="p">(</span><span class="s2">"multi_select"</span><span class="p">,</span> <span class="nx">RailsFormInputMultiSelectController</span><span class="p">)</span>
</code></pre></div></div>
<p>Or, use the <code class="highlighter-rouge">bin/rails stimulus:manifest:update</code> command, and adjust the next steps accordingly (since it’ll register
the controller against an auto-generated name by default).</p>
<p>With the controller in place, we can now mount this input wherever we need to very easily.</p>
<h2 id="using-the-multi-select-in-anger">Using the multi-select in anger</h2>
<p>In a form, it’s now hella easy to get a decent multi-select. Assuming a model <code class="highlighter-rouge">Kit</code> with a has-and-belongs-to-many
association to <code class="highlighter-rouge">Material</code>, it will look something like this:</p>
<div class="language-haml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">-</span> <span class="n">form_for</span><span class="p">(</span><span class="vi">@kit</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span>
<span class="p">=</span> <span class="n">f</span><span class="p">.</span><span class="nf">text_field</span> <span class="ss">:name</span>
# etc
<span class="p">-</span> <span class="n">material_options</span> <span class="o">=</span> <span class="no">Material</span><span class="p">.</span><span class="nf">pluck</span><span class="p">(</span><span class="ss">:id</span><span class="p">,</span> <span class="ss">:name</span><span class="p">).</span><span class="nf">map</span> <span class="p">{</span><span class="o">|</span><span class="nb">id</span><span class="p">,</span> <span class="nb">name</span><span class="o">|</span> <span class="p">{</span> <span class="ss">value: </span><span class="nb">id</span><span class="p">,</span> <span class="ss">label: </span><span class="nb">name</span> <span class="p">}}</span>
<span class="p">=</span> <span class="n">f</span><span class="p">.</span><span class="nf">label</span> <span class="ss">:my_association</span>
<span class="nt">%div</span><span class="p">{</span> <span class="ss">data: </span><span class="p">{</span> <span class="ss">controller: </span><span class="s2">"multi_select"</span><span class="p">,</span> <span class="ss">name: </span><span class="s2">"kit[materials][]"</span><span class="p">,</span> <span class="ss">collection: </span><span class="n">material_options</span><span class="p">.</span><span class="nf">to_json</span><span class="p">,</span> <span class="ss">current_value: </span><span class="vi">@kit</span><span class="p">.</span><span class="nf">material_ids</span><span class="p">.</span><span class="nf">to_json</span> <span class="p">}</span> <span class="p">}</span>
</code></pre></div></div>
<p>I’ve even got this extracted out to a partial to make it cleaner:</p>
<div class="language-haml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// app/views/application/_multi_select.html.haml
</span>
<span class="nc">.py-1</span><span class="p">{</span> <span class="ss">data: </span><span class="p">{</span> <span class="ss">controller: </span><span class="s2">"multi_select"</span><span class="p">,</span> <span class="nb">name</span><span class="p">:,</span> <span class="ss">collection: </span><span class="n">collection</span><span class="p">.</span><span class="nf">to_json</span><span class="p">,</span> <span class="ss">current_value: </span><span class="n">current_value</span><span class="p">.</span><span class="nf">to_json</span> <span class="p">}</span> <span class="p">}</span>
</code></pre></div></div>
<p>and my form becomes this</p>
<div class="language-haml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="p">-</span> <span class="n">material_options</span> <span class="o">=</span> <span class="no">Material</span><span class="p">.</span><span class="nf">pluck</span><span class="p">(</span><span class="ss">:id</span><span class="p">,</span> <span class="ss">:name</span><span class="p">).</span><span class="nf">map</span> <span class="p">{</span><span class="o">|</span><span class="nb">id</span><span class="p">,</span> <span class="nb">name</span><span class="o">|</span> <span class="p">{</span> <span class="ss">value: </span><span class="nb">id</span><span class="p">,</span> <span class="ss">label: </span><span class="nb">name</span> <span class="p">}}</span>
<span class="p">=</span> <span class="n">f</span><span class="p">.</span><span class="nf">label</span> <span class="ss">:my_association</span>
render('multi_select', name: "kit[material_ids][]", collection: material_options, current_value: @kit.material_ids)
</code></pre></div></div>
<p>It hides the JSONification away in one place, and I als o get to use some nice Ruby variable restructuring because I’m
edgy and cool and on the bleeding edge (I am so not).</p>
<p>(Also, the <code class="highlighter-rouge">.mt-2</code> is because I use Bootstrap and this matche the spacing of the React select to the standard Bootstrap form
input selects).</p>SeanLook. I use Rails for side projects because I’m lazy. But not so lazy that I’m willing to put up with really really bad multi-selects.Chipotle chili2021-12-04T00:00:00+00:002021-12-04T00:00:00+00:00https://www.flyinggrizzly.net/2021/12/chili-recipe<p>This is a warm, often spicy, chili recipe. Black beans only. Smoky chipotle
chilis and toasted cumin. Poblano peppers.</p>
<h2 id="ingredients">Ingredients</h2>
<ul>
<li>2 big onions, diced (usually red, not that important)</li>
<li>4 biggish cloves of garlic, minced</li>
<li>3-6 rehydrated morita chipotle peppers, chopped (adjust based on spice preference. Rehydration usually takes ~24 hours, but is more reliable if you put a cut into the bottom of the pepper so the water can get inside faster). Save the water/juice for later</li>
<li>4/5 poblano peppers or 3/4 green bell peppers cut into ~1”x2” strips</li>
<li>1 “standard” package of ground beef (I usually get 500g packages, but I can’t remember what size the standard US package is, but I just use one of those)</li>
<li>4 cans of black beans, drain juice away from 1.5 of them</li>
<li>3 cans chopped tomatoes</li>
<li>1-2 Tbsp wholeseed cumin, toasted and ground</li>
<li>4 tsp of salt</li>
</ul>
<h3 id="garnishes">Garnishes</h3>
<ul>
<li>lime wedges</li>
<li>avocado</li>
<li>sharp jack or cheddar cheese</li>
</ul>
<h2 id="prodedure">Prodedure</h2>
<p>Toast the cumin in a frying pan (no oil) for 2-3 minutes, until you smell the
fragrance. Let it cool for a minute, then grind up into a fine powder.</p>
<p>Put the onions, garlic, cumin, and chipotle peppers (saving the juice for later)
into a big pot, with just enough oil that they won’t scorch. Start this on
medium-low heat. Add in 1 teaspoon of salt.</p>
<p>Add the poblanos/bellpeppers in as soon as they’re ready and 1 more teaspoon of
salt (2 total so far), and cook until the peppers are starting to sweat just a
little and the onions are starting to turn yellow/brown.</p>
<p>Add in the ground beef to brown–don’t cook it through. Add a third teaspoon of
salt.</p>
<p>Once the beef is browned, add back in the chipotle juice, and then all the
canned tomatoes and beans. Add a 4th teaspoon of salt.</p>
<p>Bring to a simmer, and adjust temperature to keep it at a low simmer for 2-4
hours. The longer you go, the better.</p>
<p>You can take the lid off the pot to reduce the chili to be thicker, or keep the
lid on for more liquid chili. I usually reduce it down until the tide mark on
the side of the pot is ~1” lower than when I started, then put the lid on for
the rest of the cook time.</p>
<p>This recipe is salt-conservative, so have some to hand on the table. Serve each
bowl with a quarter-wedge of lime.</p>
<p>It’s also great with avocado chunks in the bowl, and sharp cheddar or jack cheese.</p>SeanThis is a warm, often spicy, chili recipe. Black beans only. Smoky chipotle chilis and toasted cumin. Poblano peppers.Sane Ubuntu UI scaling part 22019-12-04T00:00:00+00:002019-12-04T00:00:00+00:00https://www.flyinggrizzly.net/2019/12/sane-ubuntu-ui-scaling-part-2<p>Recently I posted about getting fractional scale factors for GNOME 3 on Ubuntu.
I’ve since abandoned GNOME in favor of Mate because GNOME was crashing on me on
a default install, and could be quite slow at times when loading large
resources. Mate has neither of these problems, and as an added bonus, scales in
a reasonable way out of the box.</p>
<!-- more -->
<p>Mate looks a little older than GNOME, and is nowhere near as flashy… but
honestly, considering I spend most of my time in a terminal, that’s fine. Also,
despite less flash being present, I’m finding I have more and more useful
information in the default desktop setup, presented unobtrusively, which makes
life generally easier.</p>
<p>So Mate for the win.</p>SeanRecently I posted about getting fractional scale factors for GNOME 3 on Ubuntu. I’ve since abandoned GNOME in favor of Mate because GNOME was crashing on me on a default install, and could be quite slow at times when loading large resources. Mate has neither of these problems, and as an added bonus, scales in a reasonable way out of the box.Getting sane UI scale factors in GNOME 32019-11-28T00:00:00+00:002019-11-28T00:00:00+00:00https://www.flyinggrizzly.net/2019/11/sane-gnome-ui-scaling<p>I’m going to go out there and say it, I really like GNOME. I know that a lot of
people don’t. Whatever. I’m usually on hardware fast enough that it can handle
this beast of a DE. But, having just installed it on a ThinkPad X1 Carbon with a
nice WQHD display, I was a little irritated that the only 2 display scale
factors were 100% and 200%. Which is to say, “too tiny for most people without a
microscope”, or “dangit now I can only see like 5 characters on the screen”.
Basically, I needed 150% scale factor. This is how to get it.</p>
<!-- more -->
<p>The way to get this is slightly different for X and Wayland display servers, but
generally pretty similar:</p>
<p>X:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>gsettings <span class="nb">set </span>org.gnome.mutter experimental-features <span class="s2">"['x11-randr-fractional-scaling']"</span>
</code></pre></div></div>
<p>Wayland:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>gsettings <span class="nb">set </span>org.gnome.mutter experimental-features <span class="s2">"['scale-monitor-framebuffer']"</span>
</code></pre></div></div>
<p>Credit goes to <a href="https://www.omgubuntu.co.uk/2019/06/enable-fractional-scaling-ubuntu-19-04">Ask Ubuntu</a></p>SeanI’m going to go out there and say it, I really like GNOME. I know that a lot of people don’t. Whatever. I’m usually on hardware fast enough that it can handle this beast of a DE. But, having just installed it on a ThinkPad X1 Carbon with a nice WQHD display, I was a little irritated that the only 2 display scale factors were 100% and 200%. Which is to say, “too tiny for most people without a microscope”, or “dangit now I can only see like 5 characters on the screen”. Basically, I needed 150% scale factor. This is how to get it.Resizing Encrypted Linux volumes on LVM2019-11-21T00:00:00+00:002019-11-21T00:00:00+00:00https://www.flyinggrizzly.net/2019/11/resizing-encrypted-linux-volumes-on-lvm<p>Recently I set up a fully encrypted Linux install alongside Windows using LVM to
encrypt the <code class="highlighter-rouge">/</code> and <code class="highlighter-rouge">/home</code> partitions, as well as <code class="highlighter-rouge">swap</code>. This all went fine
and dandy until I realized I had been a little greedy and given root nowhere
near enough space (I was trying to horde it all for the <code class="highlighter-rouge">/home</code> volume). So, I
had to figure out how to shift that space around. This, for my own memory, is
how:</p>
<!-- more -->
<p>This assumes a setup like the one I described in my <a href="#missing">setup post</a>.</p>
<p>So, if like me, you only gave 8GB to the <code class="highlighter-rouge">/</code> logical volume, you’ll probably
quickly realize that that’s nowhere near enough (I found out when I tried
installing WINE and ran out of space… which was also the same time neovim
started going haywire with the async linter).</p>
<p>This is thankfully easy to fix.</p>
<ol>
<li>Power down and boot from your install USB (you still have that right?
Otherwise make one in the Ubuntu install media creator)</li>
<li>Don’t start the installer, just “try” Ubuntu</li>
<li>Open a terminal, and unlock your LUKS partition:</li>
</ol>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># where /dev/sdaMAIN is the same partition you set up LUKS on before,</span>
<span class="c"># and encryptedubuntu is the name you gave to that partition when you encrypted it</span>
<span class="nv">$ </span><span class="nb">sudo </span>cryptsetup luksOpen /dev/sdaMAIN encryptedubuntu
</code></pre></div></div>
<p>Now, if you run <code class="highlighter-rouge">pvdisplay</code>, <code class="highlighter-rouge">vgdisplay</code>, and <code class="highlighter-rouge">lvdisplay</code> you should see the
physical volume, volume group, and logical volumes you’ve created previously.</p>
<p>I’m going to assume that we’re subtracting space from the <code class="highlighter-rouge">/home</code> logical volume
to cede to the <code class="highlighter-rouge">/</code> LV. If you’re actually resizing the <code class="highlighter-rouge">/dev/sdaMAIN</code> partition,
go do some DDG searching–lotsa answers (I mean… same for this process really,
but still).</p>
<p>First, shrink the home LV:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>lvreduce <span class="nt">--resizefs</span> <span class="nt">--size</span> <span class="nt">-50G</span> /dev/VGencryptedubuntu/LVencryptedubuntuHOME
</code></pre></div></div>
<p>Then, expand the root LV:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>lvextend <span class="nt">-l</span> +100%FREE /dev/VGencryptedubuntu/LVencryptedubuntuROOT
</code></pre></div></div>
<p>If you don’t want to give it all the available space, <code class="highlighter-rouge">man</code> will be your friend
(now <em>that’s</em> a sentence amirite?).</p>
<p>Finally, you need to expand the filesystem on the now-expanded root LV:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>resize2fs /dev/VGencryptedubuntu/LVencryptedubuntuROOT
</code></pre></div></div>
<p>This will likely complain at you about running <code class="highlighter-rouge">fsck -f</code> on the filesystem first
to find and fix any issues (usually about making sure that the blocks are
contiguous). So do that.</p>
<p>And now, reboot. You done doed it.</p>SeanRecently I set up a fully encrypted Linux install alongside Windows using LVM to encrypt the / and /home partitions, as well as swap. This all went fine and dandy until I realized I had been a little greedy and given root nowhere near enough space (I was trying to horde it all for the /home volume). So, I had to figure out how to shift that space around. This, for my own memory, is how:Installing Linux with full partition encryption2019-11-13T00:00:00+00:002019-11-13T00:00:00+00:00https://www.flyinggrizzly.net/2019/11/installing-linux-with-full-partition-encryption<p>So, this is going to be one of any number of posts out on the web about setting
up a dev environment on Linux, but it’ll be good for me as reference in the
future. This is going to cover setting up Ubuntu 19.10 on a ThinkPad X1C (7g),
dual-boot with Windows 10, with both OSes fully encrypted at the partition level
(full-disk encryption is technically impossible since we’re splitting the disk
for Windows and Linux).</p>
<!-- more -->
<p>A lot of these instructions are very top-level. If you can’t remember what to do
exactly, DDG will almost certainly be able to tell you.</p>
<ol>
<li>Set up Windows. It will encrypt itself. This part is boring.</li>
<li>Resize your Windows install with the Windows Disk Manager/Disk Utility</li>
<li>Disable fast boot in Windows</li>
<li>Disable Secure Boot in the BIOS (technically the UEFI firmware), so that we
can boot things other than the Redmond pile
<ul>
<li>you can get to the UEFI fw manager by holding <code class="highlighter-rouge"><Shift></code> and clicking
“Restart” in the Windows menu, then hunting around for the right option</li>
</ul>
</li>
<li>Prep Linux (Ubuntu) installation Live USB</li>
<li>Boot from the Live USB by holding shift while selecting “Restart” from the
Windows menu, and choosing to boot from your (inserted) Live USB by name</li>
<li>Once booted into the Live USB, slow down, <strong>DO NOT JUST INSTALL UBUNTU</strong></li>
</ol>
<p>This is where it gets interesting.</p>
<h2 id="encrypted-ubuntu-partitions">Encrypted Ubuntu partitions</h2>
<p>Ubuntu unfortunately does not offer a default encryption option, and even if it
did, it wouldn’t work for us since we aren’t encrypting the entire disk, but
only the Linux partition.</p>
<p><strong>Check:</strong> did you resize your Windows installation in Windows?</p>
<p>Fire up GParted, and identify your free space. We need to make two new
partitions:</p>
<p>One, for <code class="highlighter-rouge">/boot</code>, which is the only part of our install that will sit outside
the encrypted area (there are ways to get this into encryption too, since GRUB
can handle it, but it’s just more headache and we’re not big enough fish to
really be worried about people tampering with our computer)</p>
<p>Two, which we’ll call <code class="highlighter-rouge">MAIN</code> for simplicity for now for the rest of our install,</p>
<p>First, create the <code class="highlighter-rouge">/boot</code> partition, and format it <code class="highlighter-rouge">ext3</code>, as a <strong>primary</strong>
partition. Give it at <em>least</em> 1 GB. I gave it 10 because I had space to spare.</p>
<p>Second, create the <code class="highlighter-rouge">MAIN</code> partition, and leave it <strong>unformatted</strong> and mark it as
a <strong>logical</strong> volume/partition. Give it as much space as you can, because this
is going to hold <code class="highlighter-rouge">/</code>, <code class="highlighter-rouge">/home</code>, and <code class="highlighter-rouge">swap</code>.</p>
<p>Once these are created, take note of their paths. These should be something like
<code class="highlighter-rouge">/dev/sda3</code> and <code class="highlighter-rouge">/dev/sda4</code>. For now, I’m going to refer to them as
<code class="highlighter-rouge">/dev/sdaBOOT</code> and <code class="highlighter-rouge">/dev/sdaMAIN</code> for clarity, but Sean, and this is important,
<em>these won’t be the same paths on the next machine you’re working with</em>.</p>
<p>Now we’re going to deal with the unformatted <code class="highlighter-rouge">MAIN</code> partition, by turning it
into a <strong>LUKS</strong> encrypted volume. The name <code class="highlighter-rouge">encryptedubuntu</code> is just an
arbitrary name I’m giving to the volume being created, which will become it’s
mount name. Name it what you want.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>cryptsetup luksFormat /dev/sdaMAIN <span class="c"># will ask you to create a password. FFS don't forget this</span>
<span class="nv">$ </span><span class="nb">sudo </span>cryptsetup luksOpen /dev/sdaMAIN encryptedubuntu
</code></pre></div></div>
<p>Now that you’ve named and opened the LUKS volume, we should wipe zero out
any data that was there before to make the hackers work a little bit harder to
get our gold:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>dd <span class="k">if</span><span class="o">=</span>/dev/zero <span class="nv">of</span><span class="o">=</span>/dev/mapper/cryptcherries <span class="nv">bs</span><span class="o">=</span>16M
</code></pre></div></div>
<p>It’ll take a second, be patient.</p>
<p>Now, on this LUKS… partition/volume/whatever (I don’t know the right word
OK?), we’re going to actually set up the volumes:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># identify a physical volume on the mounted LUKS partition</span>
<span class="nv">$ </span><span class="nb">sudo </span>pvcreate /dev/mapper/encryptedubuntu
<span class="c"># create a family of volumes</span>
<span class="nv">$ </span><span class="nb">sudo </span>vgcreate VGencryptedubuntu /dev/mapper/encryptedubuntu
<span class="c"># create the logical volumes: /, /home, swap</span>
<span class="nv">$ </span><span class="nb">sudo </span>lvcreate <span class="nt">-n</span> LVencryptedubuntuROOT <span class="nt">-L</span> 128g VGencryptedubuntu
<span class="nv">$ </span><span class="nb">sudo </span>lvcreate <span class="nt">-n</span> LVencryptedubuntuSWAP <span class="nt">-L</span> 32g VGencryptedubuntu <span class="c"># 2x my installed RAM</span>
<span class="nv">$ </span><span class="nb">sudo </span>lvcreate <span class="nt">-n</span> LVencryptedubuntuHOME <span class="nt">-L</span> 100g VGencryptedubuntu <span class="c"># or the rest of your available space really</span>
</code></pre></div></div>
<aside class="callout" id="callout-volume-group-naming-quirks">
<h6>Volume group naming quirks</h6>
<p><p>It seems that <code class="highlighter-rouge">cryptsetup</code> will insert additional <code class="highlighter-rouge">-</code> (hyphens) into your name if you use them, I suspect because it uses the character itself to delimit relationship of a volume group to one of its volumes. So <code class="highlighter-rouge">vg-vol-group</code> with a volume <code class="highlighter-rouge">my-volume</code> will later on become <code class="highlighter-rouge">vg--vol-group-my--volume</code>. This is fine, but just be aware. Probably best to rely on camelCase instead of skewer-case for this name. In the example above, the root (logical) volume will show up as <code class="highlighter-rouge">VGencryptedubuntu-LVencryptedubuntuROOT</code>.</p>
</p>
</aside>
<p>Now these new LVs need file systems:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>mkfs.ext4 /dev/mapper/VGencryptedubuntu-LVencryptedubuntuROOT
<span class="nv">$ </span><span class="nb">sudo </span>mkfs.ext4 /dev/mapper/VGencryptedubuntu-LVencryptedubuntuHOME
<span class="nv">$ </span><span class="nb">sudo </span>mkswap /dev/mapper/VGencryptedubuntu-LVencryptedubuntuSWAP
</code></pre></div></div>
<p>Now, <strong>without rebooting</strong> (seriously, don’t do it until I explicitly say to),
fire up the Ubuntu installer. Choose your language (US English), keyboard layout
(US PC) (yes, even, and especially if you have a UK PC layout keyboard).</p>
<p>When it asks you if you want to install alongside Windows, or nuke the disk from
orbit, <strong>choose something else</strong>.</p>
<p>This takes you into a partition manager screen a lot like GParted, where we’re
going to select the different available spaces for the different parts of the
system.</p>
<p>Assuming things are going like they are here, you should:</p>
<ul>
<li>choose <code class="highlighter-rouge">/dev/sdaBOOT</code> as <code class="highlighter-rouge">/boot</code> (<strong>remember this one partition outside of LUKS?</strong>)</li>
<li>choose <code class="highlighter-rouge">VGencryptedubuntu-LVencryptedubuntuROOT</code> as <code class="highlighter-rouge">/</code></li>
<li>choose <code class="highlighter-rouge">VGencryptedubuntu-LVencryptedubuntuHOME</code> as <code class="highlighter-rouge">/home</code></li>
<li>choose <code class="highlighter-rouge">VGencryptedubuntu-LVencryptedubuntuSWAP</code> as <code class="highlighter-rouge">swap</code> space</li>
</ul>
<p>Now let the installer do its thing. <strong>DO NOT REBOOT YET</strong>. When it asks if it
can, don’t let it.</p>
<p>We first need to tell the OS how to find the logical volumes that we’ve
installed it to for when we reboot.</p>
<p>First, we need to snag the UUID of the encrypted partition where we’ve got our
LUKS volumes:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>blkid /dev/sdaMAIN
/dev/sdaMAIN: <span class="nv">UUID</span><span class="o">=</span><span class="s2">"some-uuid-that-you-need-to-remember"</span> <span class="nv">TYPE</span><span class="o">=</span><span class="s2">"crypto_LUKS"</span>
</code></pre></div></div>
<p>This involves mounting the relevant LVs and then <code class="highlighter-rouge">chroot</code>ing into the context of
the newly installed OS:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>mount /dev/mapper/VGencryptedubuntu-LVencryptedubuntuROOT /mnt
<span class="nv">$ </span><span class="nb">sudo </span>mount /dev/sdaBOOT /dev/boot <span class="c"># Remember this unencrypted partition we're using for boot?</span>
<span class="nv">$ </span><span class="nb">sudo </span>mount <span class="nt">--bind</span> /dev /mnt/dev
<span class="nv">$ </span><span class="nb">sudo </span>chroot /mnt
<span class="c"># And now in the chroot shell</span>
% mount <span class="nt">-t</span> proc proc /proc
% mount <span class="nt">-t</span> sysfs sys /sys
% mount <span class="nt">-t</span> devpts devpts /dev/pts
</code></pre></div></div>
<p>Now, still in the <code class="highlighter-rouge">chroot</code>, we need to create a file to tell the boot process
how and where to find <code class="highlighter-rouge">/</code>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># ... still in chroot</span>
% <span class="nb">sudo </span>vi /etc/crypttab
</code></pre></div></div>
<p>And then give it the content:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code># <target name> <source device> <key file> <options>
encryptedubuntu UUID=some-uuid-that-you-needed-to-rememeber none luks,retry=1,lvm=VGencryptedubuntu
</code></pre></div></div>
<p>If in a minute something complains about unknown options, come back and remove
that option from that comma-separated list at the end.</p>
<p>And now, finally, still in the <code class="highlighter-rouge">chroot</code>, update the initial filesystem:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% <span class="nb">sudo </span>update-initramfs <span class="nt">-k</span> all <span class="nt">-c</span>
</code></pre></div></div>
<p>With this done, you have everything installed. Reboot into Ubuntu. You should be
asked for your password twice (once in order to unlock the disks, and then to
log in as your user).</p>
<p>The last thing to do is to test that your data is sitting inside the encrypted
LUKS volumes:</p>
<p>For your <code class="highlighter-rouge">/</code> and <code class="highlighter-rouge">/home</code> mounts, I’m a fan of passing <code class="highlighter-rouge">mount</code> through <code class="highlighter-rouge">grep</code> to
verify the right logical volume is attached to the right mount:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>mount | <span class="nb">grep </span>VG <span class="c"># assuming you prefixed your vol-group with VG</span>
/dev/mapper/VGencryptedubuntu-LVencryptedubuntuROOT on / <span class="nb">type </span>ext4 <span class="o">(</span>...permissions<span class="o">)</span>
/dev/mapper/VGencryptedubuntu-LVencryptedubuntuHOME on /home <span class="nb">type </span>ext4 <span class="o">(</span>...permissions<span class="o">)</span>
</code></pre></div></div>
<p>The <code class="highlighter-rouge">grep</code> is not necessary, but helps filter down to what we’re interested in.
If you don’t see the logical volumes mapped to those mounts, something’s gone
funny, and it might be easiest to back up to the volume setup.</p>
<p>For <code class="highlighter-rouge">swap</code>, you can check by running <code class="highlighter-rouge">swapon -s</code>. Problem for me is that I don’t
get it listed as mapped to
<code class="highlighter-rouge">/dev/mapper/VGencryptedubuntu-LVencryptedubuntuSWAP</code>, but rather as sitting on
<code class="highlighter-rouge">/dev/dm-N</code>, and I have no idea what that path is. But there’s a decent chance that
<code class="highlighter-rouge">dm</code> stands for <code class="highlighter-rouge">device-mapper</code> (spoilers, it does), which means we’re probably
in the right place… but let’s check anyways.</p>
<p>To verify this, I then run</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>lsblk <span class="nt">-o</span> NAME,KNAME /dev/sdaMAIN
sdaMAIN
NAME KNAME
sdaMAIN sdaMAIN
└─encryptedubuntu dm-0
├─VGencryptedubuntu-LVencryptedubuntuROOT dm-1
├─VGencryptedubuntu-LVencryptedubuntuSWAP dm-2
└─VGencryptedubuntu-LVencryptedubuntuHOME dm-3
</code></pre></div></div>
<p>As long as the report from <code class="highlighter-rouge">swapon -s</code> matches the <code class="highlighter-rouge">KNAME</code> from <code class="highlighter-rouge">lsblk</code>, we’re
in business and <code class="highlighter-rouge">swap</code> is encrypted.</p>
<p>All credit for the setup of the encrypted volumes goes to the author of <a href="https://askubuntu.com/questions/293028/how-can-i-install-ubuntu-encrypted-with-luks-with-dual-boot#answer-293029">this SO
post</a></p>SeanSo, this is going to be one of any number of posts out on the web about setting up a dev environment on Linux, but it’ll be good for me as reference in the future. This is going to cover setting up Ubuntu 19.10 on a ThinkPad X1C (7g), dual-boot with Windows 10, with both OSes fully encrypted at the partition level (full-disk encryption is technically impossible since we’re splitting the disk for Windows and Linux).Mocking the browser window object in Jest2019-10-25T00:00:00+00:002019-10-25T00:00:00+00:00https://www.flyinggrizzly.net/2019/10/mocking-window-in-jest<p>In the application I work on most of the time, we pass a lot of data from Rails
to the React frontend by writing constants to the <code class="highlighter-rouge">window</code> object in the
browser. Testing this can be a bit of a pain though.</p>
<!-- more -->
<p>While I’m sure there are better ways to do this, it’s certainly fast and a very
straightforward solution to validate.</p>
<p>Testing JavaScript that relies on values in the <code class="highlighter-rouge">window</code> object, though, is kind
of a pain in the ass.</p>
<p>The issue is that in our testing environment, the world begins and ends with
Jest and <code class="highlighter-rouge">jsdom</code>, so there is no Rails server to write those values into the
<code class="highlighter-rouge">window</code>.</p>
<p>The simple solution to this is to do a bit of a temp-var shuffle:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">describe</span><span class="p">(</span><span class="s1">'a feature'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">initialValue</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">AWESOME_VALUE</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">AWESOME_VALUE</span> <span class="o">=</span> <span class="s1">'new_value'</span>
<span class="nx">afterEach</span><span class="p">(()</span> <span class="o">=></span> <span class="nb">window</span><span class="p">.</span><span class="nx">AWESOME_VALUE</span> <span class="o">=</span> <span class="nx">initialValue</span><span class="p">)</span>
<span class="nx">it</span><span class="p">(</span><span class="s1">'does some cool stuff'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// ...</span>
<span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>
<p>The issue is that while it’s pretty straightforward to mock functions and
modules in Jest, getting that constant mocked is <em>hard</em>. It also doesn’t work if
you try to do the shuffle and your module reads values off of the window into
local <code class="highlighter-rouge">const</code> values at the top of a JS file, since those values are initialized
(and then <em>never</em> changed) when you <code class="highlighter-rouge">import</code> or <code class="highlighter-rouge">require</code> that file, which
happens before you have a chance to mock it.</p>
<p>The best solution I’ve come up with so far is to add a simple function that sits
between <code class="highlighter-rouge">window</code> constants and the rest of your code:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// utils/window_values.js</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nx">windowValues</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nb">window</span>
<span class="p">}</span>
</code></pre></div></div>
<p>And then, in testing, provide a mock with some easy ways to set and unset mocked
values to support whatever functionality you need to exercise:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// utils/__mocks__/window_values.js</span>
<span class="kd">let</span> <span class="nx">mockedWindow</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">create</span><span class="p">(</span><span class="kc">null</span><span class="p">)</span>
<span class="kd">function</span> <span class="nx">addMockedWindowValue</span><span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">mockedWindow</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span> <span class="o">=</span> <span class="nx">value</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">removeMockedWindowValue</span><span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">mockedWindow</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span> <span class="o">=</span> <span class="kc">undefined</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">windowValues</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">({},</span> <span class="nb">window</span><span class="p">,</span> <span class="nx">mockedWindow</span><span class="p">)</span>
<span class="p">}</span>
<span class="nx">windowValues</span><span class="p">.</span><span class="nx">__addMockedWindowValue</span> <span class="o">=</span> <span class="nx">addMockedWindowValue</span>
<span class="nx">windowValues</span><span class="p">.</span><span class="nx">__removeMockedWindowValue</span> <span class="o">=</span> <span class="nx">removeMockedWindowValue</span>
<span class="k">export</span> <span class="p">{</span> <span class="nx">windowValues</span> <span class="p">}</span>
</code></pre></div></div>
<p>Then, you can easily organize your <code class="highlighter-rouge">window</code> object as you need it in testing:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">windowValues</span> <span class="p">}</span> <span class="k">from</span> <span class="s1">'utils/window_values'</span>
<span class="nx">jest</span><span class="p">.</span><span class="nx">mock</span><span class="p">(</span><span class="s1">'utils/window_values'</span><span class="p">)</span>
<span class="nx">describe</span><span class="p">(</span><span class="s1">'windowValues'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">describe</span><span class="p">(</span><span class="s1">'when no values have been mocked'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="s1">'probably has no data'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">windowValues</span><span class="p">().</span><span class="nx">AWESOME_VALUE</span><span class="p">).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">undefined</span><span class="p">)</span>
<span class="p">})</span>
<span class="p">})</span>
<span class="nx">describe</span><span class="p">(</span><span class="s1">'setting a mocked constant'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="s1">'returns the mock'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// Set up a constant with a fixture</span>
<span class="nx">windowValues</span><span class="p">.</span><span class="nx">__addMockedWindowValue</span><span class="p">(</span><span class="s1">'AWESOME_VALUE'</span><span class="p">,</span> <span class="p">{</span> <span class="na">awesome</span><span class="p">:</span> <span class="s1">'value'</span> <span class="p">})</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">windowValues</span><span class="p">().</span><span class="nx">AWESOME_VALUE</span><span class="p">).</span><span class="nx">toEqual</span><span class="p">({</span> <span class="na">awesome</span><span class="p">:</span> <span class="s1">'value'</span> <span class="p">})</span>
<span class="p">})</span>
<span class="p">})</span>
<span class="nx">describe</span><span class="p">(</span><span class="s1">'removing the mocked value'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">it</span><span class="p">(</span><span class="s1">'unsets the mock'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// Set up a constant with a fixture</span>
<span class="nx">windowValues</span><span class="p">.</span><span class="nx">__addMockedWindowValue</span><span class="p">(</span><span class="s1">'AWESOME_VALUE'</span><span class="p">,</span> <span class="p">{</span> <span class="na">awesome</span><span class="p">:</span> <span class="s1">'value'</span> <span class="p">})</span>
<span class="c1">// ...then unset it</span>
<span class="nx">windowValues</span><span class="p">.</span><span class="nx">__removeMockedWindowValue</span><span class="p">(</span><span class="s1">'AWESOME_VALUE'</span><span class="p">)</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">windowValues</span><span class="p">().</span><span class="nx">AWESOME_VALUE</span><span class="p">).</span><span class="nx">toEqual</span><span class="p">(</span><span class="kc">undefined</span><span class="p">)</span>
<span class="p">})</span>
<span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>SeanIn the application I work on most of the time, we pass a lot of data from Rails to the React frontend by writing constants to the window object in the browser. Testing this can be a bit of a pain though.Filesystem case-sensitivity and git2019-10-20T00:00:00+00:002019-10-20T00:00:00+00:00https://www.flyinggrizzly.net/2019/10/file-system-case-sensitivity-and-git<p>I’m in the process of moving from OS X to Ubuntu (via WSL2) for all of my
development work, and ran into an odd behavior with a <code class="highlighter-rouge">zsh</code> function I use all
the time to diff my current work against the <code class="highlighter-rouge">master</code> branch, that it turns out,
is caused by the case-sensitivity of the Linux filesystem (unless I’ve
misunderstood things).</p>
<!-- more -->
<p>The two functions <a href="https://github.com/flyinggrizzly/dotfiles-local/blob/master/zsh/functions/this-branch-files-by-commit">list all files touched in each
commit</a>
in a PR, or <a href="https://github.com/flyinggrizzly/dotfiles-local/blob/master/zsh/functions/this-branch-commits-by-file">all files changed in a branch and all commits that affect
them</a>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>this-branch-files-by-commit
SHA Update contnroller
app/controllers/my_controller.rb
spec/controllers/my_controllers_spec.rb
SHA Update model
app/models/my_model.rb
spec/models/my_model.rb
</code></pre></div></div>
<p>or</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>this-branch-commits-by-file
app/controllers/my_controller.rb
1: SHA Update controller
spec/controllers/my_controllers_spec.rb
1: SHA Update controller
app/models/my_model.rb
2: SHA Update model
spec/models/my_model.rb
2: SHA Update model
</code></pre></div></div>
<p>These two functions rely on running <code class="highlighter-rouge">git diff master..head</code>, which works fine on
OS X, but on Ubuntu results in…</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>flying-grizzly production % git diff master..head
fatal: ambiguous argument <span class="s1">'master..head'</span>: unknown revision or path not <span class="k">in </span>the
working tree.
Use <span class="s1">'--'</span> to separate paths from revisions, like this:
<span class="s1">'git <command> [<revision>...] -- [<file>...]'</span>
</code></pre></div></div>
<p>It turns out that this is because OS X is willing to interpret <code class="highlighter-rouge">.git/HEAD</code> as
<code class="highlighter-rouge">.git/head</code> if there is no conflicting file already in that place.</p>
<p>Ubuntu (and Linux, to my new understanding), not so much.</p>
<p>The solution was to update these two functions to use <code class="highlighter-rouge">git diff master..HEAD</code>
instead of the lazy lowercase option.</p>
<p>Thanks to <a href="https://stackoverflow.com/questions/48137927/git-head-lowercase-vs-head-uppercase#answer-56346962">this answer on
SO</a>
for helping me figure why this was the case.</p>SeanI’m in the process of moving from OS X to Ubuntu (via WSL2) for all of my development work, and ran into an odd behavior with a zsh function I use all the time to diff my current work against the master branch, that it turns out, is caused by the case-sensitivity of the Linux filesystem (unless I’ve misunderstood things).Better RSpec diffs with super_diff2019-10-18T00:00:00+00:002019-10-18T00:00:00+00:00https://www.flyinggrizzly.net/2019/10/better-rspec-diffs-with-super-diff<p>Every now and then I need to build up some complex hash/JSON data in Ruby to
feed over to a JS frontend. I tend to work slowly and iteratively as I push the
nesting deeper, which helps me find mistakes before it gets too complicated…
but sometimes that doesn’t work.</p>
<!-- more -->
<p>The problem is, that when you’ve got gnarly nested data, the RSpec result
message is really not easy to read:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Diff:
@@ <span class="nt">-1</span>,4 +1,4 @@
-:domain_models <span class="o">=></span> <span class="o">[{</span>:domain_model_name<span class="o">=></span><span class="s2">"group_b"</span>, :domain_model_slug<span class="o">=></span><span class="s2">"organization-experienced-hires-domain_model"</span>, :second_domain_model_definitions<span class="o">=>[{</span>:sections<span class="o">=>[{</span>:questions<span class="o">=>[{</span>:stats_tag<span class="o">=></span><span class="s2">"experience_overall"</span>, :type<span class="o">=></span><span class="s2">"RangeSelectQuestion"</span><span class="o">}</span>, <span class="o">{</span>:stats_tag<span class="o">=></span><span class="s2">"experience_average"</span>, :type<span class="o">=></span><span class="s2">"RangeSelectQuestion"</span><span class="o">}</span>, <span class="o">{</span>:stats_tag<span class="o">=></span><span class="s2">"recommendation_nps"</span>, :type<span class="o">=></span><span class="s2">"ScoreQuestion"</span><span class="o">}</span>, <span class="o">{</span>:stats_tag<span class="o">=></span><span class="s2">"big_choice_suggestions"</span>, :type<span class="o">=></span><span class="s2">"FreeTextQuestion"</span><span class="o">}</span>, <span class="o">{</span>:allow_multiple<span class="o">=></span><span class="nb">false</span>, :stats_tag<span class="o">=></span><span class="s2">"ethnicity"</span>, :type<span class="o">=></span><span class="s2">"MultiChoiceQuestion"</span><span class="o">}</span>, <span class="o">{</span>:allow_multiple<span class="o">=></span><span class="nb">true</span>, :stats_tag<span class="o">=></span><span class="s2">"big_choice_decision_influences"</span>, :type<span class="o">=></span><span class="s2">"MultiChoiceQuestion"</span><span class="o">}]</span>, :section_number<span class="o">=></span>11<span class="o">}</span>, <span class="o">{</span>:section_number<span class="o">=></span>112<span class="o">}]</span>, :second_domain_model_name<span class="o">=></span><span class="s2">"Application"</span><span class="o">}</span>, <span class="o">{</span>:sections<span class="o">=>[{</span>:questions<span class="o">=>[]</span>, :section_number<span class="o">=></span>12<span class="o">}]</span>, :second_domain_model_name<span class="o">=></span><span class="s2">"Outcome"</span><span class="o">}]}</span>, <span class="o">{</span>:domain_model_name<span class="o">=></span><span class="s2">"group_a"</span>, :domain_model_slug<span class="o">=></span><span class="s2">"organization-group_a-domain_model"</span>, :second_domain_model_definitions<span class="o">=>[]}]</span>,
+:domain_models <span class="o">=></span> <span class="o">[{</span>:domain_model_name<span class="o">=></span><span class="s2">"group_b"</span>, :domain_model_slug<span class="o">=></span><span class="s2">"organization-experienced-hires-domain_model"</span>, :second_domain_model_definitions<span class="o">=>[{</span>:sections<span class="o">=>[{</span>:questions<span class="o">=>[{</span>:stats_tag<span class="o">=></span><span class="s2">"experience_overall"</span>, :type<span class="o">=></span><span class="s2">"RangeSelectQuestion"</span><span class="o">}</span>, <span class="o">{</span>:stats_tag<span class="o">=></span><span class="s2">"experience_average"</span>, :type<span class="o">=></span><span class="s2">"RangeSelectQuestion"</span><span class="o">}</span>, <span class="o">{</span>:stats_tag<span class="o">=></span><span class="s2">"recommendation_nps"</span>, :type<span class="o">=></span><span class="s2">"ScoreQuestion"</span><span class="o">}</span>, <span class="o">{</span>:stats_tag<span class="o">=></span><span class="s2">"big_choice_suggestions"</span>, :type<span class="o">=></span><span class="s2">"FreeTextQuestion"</span><span class="o">}</span>, <span class="o">{</span>:allow_multiple<span class="o">=></span><span class="nb">false</span>, :stats_tag<span class="o">=></span><span class="s2">"ethnicity"</span>, :type<span class="o">=></span><span class="s2">"MultiChoiceQuestion"</span><span class="o">}</span>, <span class="o">{</span>:allow_multiple<span class="o">=></span><span class="nb">true</span>, :stats_tag<span class="o">=></span><span class="s2">"big_choice_decision_influences"</span>, :type<span class="o">=></span><span class="s2">"MultiChoiceQuestion"</span><span class="o">}]</span>, :section_number<span class="o">=></span>11<span class="o">}</span>, <span class="o">{</span>:questions<span class="o">=>[]</span>, :section_number<span class="o">=></span>112<span class="o">}]</span>, :second_domain_model_name<span class="o">=></span><span class="s2">"Application"</span><span class="o">}</span>, <span class="o">{</span>:sections<span class="o">=>[{</span>:questions<span class="o">=>[]</span>, :section_number<span class="o">=></span>12<span class="o">}]</span>, :second_domain_model_name<span class="o">=></span><span class="s2">"Outcome"</span><span class="o">}]}</span>, <span class="o">{</span>:domain_model_name<span class="o">=></span><span class="s2">"group_a"</span>, :domain_model_slug<span class="o">=></span><span class="s2">"organization-group_a-domain_model"</span>, :second_domain_model_definitions<span class="o">=>[]}]</span>,
</code></pre></div></div>
<p>And look. I have a hard time spotting the difference between these…</p>
<p>Installing <a href="https://github.com/mcmire/super_diff"><code class="highlighter-rouge">super_diff</code></a> makes it a heck
of a lot easier. In addition to the full diff (as above), it also gives you:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="o">{</span>
section_number: 112,
+ questions: <span class="o">[]</span>
<span class="o">}</span>
</code></pre></div></div>
<p>in a normalized hash structure. Which makes it wicked easy to find the issue.</p>
<p><code class="highlighter-rouge">super_diff</code> is the kind of thing I’d install just when needed, and not track
permanently into my dependencies.</p>SeanEvery now and then I need to build up some complex hash/JSON data in Ruby to feed over to a JS frontend. I tend to work slowly and iteratively as I push the nesting deeper, which helps me find mistakes before it gets too complicated… but sometimes that doesn’t work.