<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Kleinprojects</title>
  <subtitle>Personal Projects and Blog by Marco Klein</subtitle>
  <link href="https://kleinprojects.com/feed.xml" rel="self"/>
  <link href="https://kleinprojects.com/"/>
  <updated>2026-01-22T00:00:00Z</updated>
  <id>https://kleinprojects.com/</id>
  <author>
    <name>Marco Klein</name>
    <email>marco.klein94@gmail.com</email>
  </author>
  
  <entry>
    <title>Testing HTMX 2 with Happy Dom</title>
    <link href="https://kleinprojects.com/testing-htmx-2-with-happy-dom/"/>
    <updated>2026-01-22T00:00:00Z</updated>
    <id>https://kleinprojects.com/testing-htmx-2-with-happy-dom/</id>
    <content type="html">&lt;p&gt;I have a long running e2e test setup with Playwright that takes around 30 seconds to complete on my local computer and around two minutes in CI. Since I have adopted HTMX I am trying to find a better approach for testing HTMX interactivity without having to work in a real, slow browser environment.&lt;/p&gt;
&lt;p&gt;The ~400 other unit tests execute in under a second - why can&#39;t I also run interactive tests via HTMX in under a second?&lt;/p&gt;
&lt;p&gt;So I gave &lt;a href=&quot;https://github.com/capricorn86/happy-dom&quot;&gt;Happy Dom&lt;/a&gt; a shot as it&#39;s supposed to be faster then the other popular JSDOM alternatives. However, I hit some limitations in the process. First off, Happy Dom does not implement the &lt;a href=&quot;https://developer.mozilla.org/de/docs/Web/API/XPathEvaluator&quot;&gt;XPathEvaluator&lt;/a&gt; that HTMX 2 needs:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token literal-property property&quot;&gt;ReferenceError&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; XPathEvaluator is not defined
      at &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;anonymous&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;libs&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;htmx&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2.0&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;.7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;min&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;js&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;24685&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since I was not using XPaths at all I mocked the XPathEvaluator implementation with a stub (see code appendix).&lt;/p&gt;
&lt;p&gt;In the test itself, I just apply the html to the Happy Dom document like this:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerHTML &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; html&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, I faced a second issue with an &lt;code&gt;TypeError: window[PropertySymbol.dispatchError] is not a function.&lt;/code&gt; error which turns out as a Happy Dom limitation due to the &lt;code&gt;onclick&lt;/code&gt; listeners that are directly assigned in some places:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;onclick&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value javascript language-javascript&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;scrollTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;behavior&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;smooth&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I had to refactor those instances, in my case to &lt;a href=&quot;https://hyperscript.org/&quot;&gt;hyperscript&lt;/a&gt; which looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;on click go to the top of the body smoothly&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then it works! With bun the initial test ran in around &lt;em&gt;20ms&lt;/em&gt; and the existing Playwright test in around &lt;code&gt;2000ms&lt;/code&gt; making the non-browser version around 100x faster on first sight.&lt;/p&gt;
&lt;p&gt;To not execute too many scripts I added an allow-list of scripts that I wanted to execute (see code appendix).&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I am happy that I am able to test HTMX interactions without having to spin up a full browser. Going forward I hope this can guide someone into a better testing setup for HTMX applications.&lt;/p&gt;
&lt;p&gt;I&#39;ll now wander off to migrate more tests into that new setup.&lt;/p&gt;
&lt;h2 id=&quot;code-appendix&quot; tabindex=&quot;-1&quot;&gt;Code Appendix&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;This is the code for the stub:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XPathEvaluatorPolyfill&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;createExpression&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;expression&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; XPathExpression &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XPathExpressionPolyfill&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;expression&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XPathExpressionPolyfill&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; expression&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    contextNode&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Node&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    result&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; XPathResult &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; XPathResult &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// For now, return an empty result since HTMX primarily uses CSS selectors&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// and only uses XPath for advanced attribute queries&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XPathResultPolyfill&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; XPathResult&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XPathResultPolyfill&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  resultType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Add required XPathResult properties (unused but needed for interface)&lt;/span&gt;
  booleanValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  invalidIteratorState &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  numberValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  singleNodeValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  stringValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  snapshotLength &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token constant&quot;&gt;ANY_TYPE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;NUMBER_TYPE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;STRING_TYPE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;BOOLEAN_TYPE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;UNORDERED_NODE_ITERATOR_TYPE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;ORDERED_NODE_ITERATOR_TYPE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;UNORDERED_NODE_SNAPSHOT_TYPE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;ORDERED_NODE_SNAPSHOT_TYPE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;ANY_UNORDERED_NODE_TYPE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;FIRST_ORDERED_NODE_TYPE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; nodes&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Node&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;snapshotLength &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; nodes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;iterateNext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Node &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;index &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nodes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nodes&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;index&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;snapshotItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;index&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Node &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nodes&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Adding to global&lt;/span&gt;
globalThis&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;XPathEvaluator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; XPathEvaluatorPolyfill&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Happy Dom setup as global registration:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Whitelist of scripts that should be loaded during tests&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// Scripts not in this list will be blocked to prevent errors from missing browser APIs&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;SCRIPT_WHITELIST&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;htmx&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;hyperscript&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Helper to check if a script path matches the whitelist&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; isScriptAllowed &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pathname&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;SCRIPT_WHITELIST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;some&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;allowed &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; pathname&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;allowed&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// App is a mocked implementation of the web server (in this case Hono with Bun)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createApplication&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

GlobalRegistrator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  settings&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    enableJavaScriptEvaluation&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    suppressInsecureJavaScriptEnvironmentWarning&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    disableCSSFileLoading&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    disableJavaScriptFileLoading&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    handleDisabledFileLoadingAsSuccess&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    fetch&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      virtualServers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          url&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;http://test.local/libs/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          directory&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./public/libs&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      interceptor&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function-variable function&quot;&gt;beforeAsyncRequest&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; window &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

          &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/libs/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isScriptAllowed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;token comment&quot;&gt;// Let virtualServers handle it&lt;/span&gt;
              &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;token comment&quot;&gt;// Return empty response to prevent script execution errors&lt;/span&gt;
              &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                statusText&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;OK&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                headers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string-property property&quot;&gt;&#39;Content-Type&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;application/javascript&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

          &lt;span class=&quot;token comment&quot;&gt;// Build headers object from happy-dom&#39;s headers&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; headersObj&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Record&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            headersObj&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

          &lt;span class=&quot;token comment&quot;&gt;// Forward request to Hono app using URL string&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; honoResponse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            method&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;method&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            headers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; headersObj&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            body&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; BodyInit&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

          &lt;span class=&quot;token comment&quot;&gt;// Convert Hono Response to happy-dom Response&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; responseText &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; honoResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

          &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; responseHeaders&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Record&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          honoResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            responseHeaders&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

          &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; happyDomResponse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;responseText&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; honoResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            statusText&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; honoResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;statusText&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            headers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; responseHeaders&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

          &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; happyDomResponse&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>Experimenting with LLMs for Dynamic Game Content Generation</title>
    <link href="https://kleinprojects.com/experimenting-with-llms-for-dynamic-game-content-generation/"/>
    <updated>2025-09-28T00:00:00Z</updated>
    <id>https://kleinprojects.com/experimenting-with-llms-for-dynamic-game-content-generation/</id>
    <content type="html">&lt;figure&gt;
  &lt;img src=&quot;https://kleinprojects.com/images/2025-09-28-llm-game.png&quot; class=&quot;responsive-image&quot; style=&quot;max-width: 400px&quot; /&gt;
  &lt;figcaption&gt;AI generated blobs in the game&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I recently built a proof of concept to experiment with large language models inside games, using a locally running LLM to generate dynamic content in real-time. The goal was to create a unique gaming experience where the game evolves based on player actions, with each level featuring procedurally generated content that responds to gameplay.&lt;/p&gt;
&lt;p&gt;You can find the complete implementation in the &lt;a href=&quot;https://github.com/marcoklein/llm-game&quot;&gt;llm-game repository&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-core-challenge%3A-constraining-creativity&quot; tabindex=&quot;-1&quot;&gt;The Core Challenge: Constraining Creativity&lt;/h2&gt;
&lt;p&gt;The fundamental challenge with integrating LLMs into games is finding the right balance between creative freedom and system stability. You need to constrain the AI within a specific frame so that a small, locally running LLM can generate content without breaking the game mechanics.&lt;/p&gt;
&lt;p&gt;However, this constraint comes with a significant downside: reduced creativity. In my implementation, I limited all entities to circles to simplify collision detection and positioning logic. While this made the system robust, it also made the generated content somewhat monotonous - everything was just circles with different colors and sizes.&lt;/p&gt;
&lt;p&gt;If I would loosen it up too much, I run in the risk of the LLM hallucinating or producing wrong code which would mitigate the player experience.&lt;/p&gt;
&lt;h2 id=&quot;how-the-system-works&quot; tabindex=&quot;-1&quot;&gt;How the System Works&lt;/h2&gt;
&lt;p&gt;The core game is limited to a Player, that is controlled with arrow keys, a target that the player has to reach and LLM-generated NPCs. When the player reaches the target the game generates a new NPC and thus gradually generating more and more game content.&lt;/p&gt;
&lt;p&gt;For each level, the system generates one or more NPCs with properties. The interface is limited, to reduce errors with the usage of the small LLM. The &lt;code&gt;behaviorCode&lt;/code&gt; is the only generated property that the game engine executes.&lt;/p&gt;
&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NPCData&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  position&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; x&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; y&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  color&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  size&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  behaviorCode&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Behavior code wrapped into a TypeScript template and directly executed:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;npc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; deltaTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// LLM generated code is added here&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;warn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;AI behavior error:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function is executed every update, giving the LLM access to the npc, context, and deltaTime variables.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Entity constraints (like only drawing circles), while necessary for system stability, severely limited visual variety and variety in behaviors. Everything generated was fundamentally the same shape, just with different colors and sizes. Those generations could have been done by a simpler generation algorithm. Next time, I would thus explore more fields where LLMs would really excel (like text and story generation).&lt;/p&gt;
&lt;p&gt;For me, the experiment reinforced that successful AI integration requires careful architectural decisions and robust fallback systems. The idea shows promise, but finding the right balance between creative freedom and constraints is challenging.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Announcing ... Impromat ... Improvisational Theatre Session Planning</title>
    <link href="https://kleinprojects.com/announcing-impromat-improvisational-theatre-session-planning/"/>
    <updated>2024-12-21T00:00:00Z</updated>
    <id>https://kleinprojects.com/announcing-impromat-improvisational-theatre-session-planning/</id>
    <content type="html">&lt;p&gt;I am sitting in front of my paper based notebook. My shiny improvisational theatre notes stare at me. It&#39;s time again to jot down thoughts as I signed up to lead our next session. I am opening a blank page.&lt;/p&gt;
&lt;p&gt;A goal.&lt;/p&gt;
&lt;p&gt;&amp;quot;I need a goal for my workshop&amp;quot;, I think.&lt;/p&gt;
&lt;p&gt;Something about group mind? Building characters? Emotions?&lt;/p&gt;
&lt;p&gt;&amp;quot;I need some games&amp;quot;.&lt;/p&gt;
&lt;p&gt;It&#39;s always the same.&lt;/p&gt;
&lt;p&gt;&amp;quot;I am a bloody software engineer. I can make this process more efficient&amp;quot;.&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;Three years pass and I am ready to introduce:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://impromat.app/?campaign=blog&quot;&gt;impromat.app&lt;/a&gt; - &lt;em&gt;dead simple improvisational theatre planning.&lt;/em&gt;&lt;/p&gt;
&lt;figure&gt;
  &lt;a href=&quot;https://impromat.app/?campaign=blog&quot;&gt;
    &lt;img src=&quot;https://kleinprojects.com/images/2024-12-21-Impromat.app-screenshot.png&quot; class=&quot;responsive-image&quot; style=&quot;max-width: 400px&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;Create Improv Sessions with &lt;a href=&quot;https://impromat.app/?campaign=blog&quot;&gt;impromat.app&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;why-an-improv-app%3F&quot; tabindex=&quot;-1&quot;&gt;Why an Improv app?&lt;/h2&gt;
&lt;p&gt;In most improv groups, one person facilitates an improv session. That person is responsible for providing structure, exercises, games and in rare cases, fun.&lt;/p&gt;
&lt;p&gt;There is books on improv. There exist encyclopedias like improwiki, improvencyclopedia, improvressourcecenter. Improv groups posses knowledge and unique styles. Most of that are individual games and exercises that exist to have fun. They are labelled sometimes. For example, the improwiki website assigns tags to their elements. That eases the search for warmup, exercises, or games.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When you ultimately prepare and run an improv session you are lacking tools.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I formed a new improv group and ran sessions on &amp;quot;how to do an improv workshop&amp;quot; to allow participants the facilitation. I run these sessions very spontaneously because there are many games and exercises that I know and that I can put into the workshop. That spontaneous setup makes it hard for me to pass on that knowledge or at least the games that we play.&lt;/p&gt;
&lt;p&gt;And how do you seek inspiration for you improv group? You have your known set of games, but how do you get new ideas? You could participate in external workshops, or hire an external workshop leader. You could read books, or browse some of the online sources.&lt;/p&gt;
&lt;p&gt;Guess what. There is a solution to all of these problems: Impromat.&lt;/p&gt;
&lt;p&gt;Impromat inspires you with unique and new games. Impromat provides you with a dead simple planning tool that allows you to plan and share workshops with participants or the community.&lt;/p&gt;
&lt;h3 id=&quot;introducing-impromat&quot; tabindex=&quot;-1&quot;&gt;Introducing Impromat&lt;/h3&gt;
&lt;p&gt;Known improv sites like, Improwiki, improvencyclopedia or improvresourcenter served as the initial content for my personal planning. I soon began developing a small application around it to make the planning of workshops more efficent. Then, I opened it up for everyone to use.&lt;/p&gt;
&lt;p&gt;There is no good way right now to have a good improv session planning tool available that covers improvisational theatre. Most people use Excel or their personal notes.&lt;/p&gt;
&lt;p&gt;Moreover, there is no easy approach in sharing improv workshops to inspire others.&lt;/p&gt;
&lt;p&gt;Impromat brings these two aspects together: getting inspiration through other sessions -&amp;gt; planning a new session -&amp;gt; inspire other sessions -&amp;gt; planning a new session...&lt;/p&gt;
&lt;p&gt;Of course, everyone has their private space. You don&#39;t have to share what you don&#39;t want to share.&lt;/p&gt;
&lt;h4 id=&quot;so%2C-what-the-heck-is-it%2C-how-is-it-build%3F&quot; tabindex=&quot;-1&quot;&gt;So, what the heck is it, how is it build?&lt;/h4&gt;
&lt;p&gt;Elements and collections are in the core of the application. Elements are exercises or games like Freeze Tag, Dutch Triangle, Association Circle, and so on. Collections contain elements in certain orders like workshops. They contain elements that an improv leader goes through in the course of the session.&lt;/p&gt;
&lt;p&gt;Elements are tagged to make your life easier with finding suitable ones. If you plan a workshop around character building you could filter all elements by the &lt;em&gt;character&lt;/em&gt; tag. Elements are the smallest set of information to describe an activity that the group or individuals can perform.&lt;/p&gt;
&lt;p&gt;Currently, there is the concept of workshops, e.g. sessions that you will actually lead in an improv group. But you can also see workshops like playlists and the elements like songs and create collections of elements that fit to your planned show format around the meaning of life.&lt;/p&gt;
&lt;h4 id=&quot;how-can-i-use-it%3F&quot; tabindex=&quot;-1&quot;&gt;How can I use it?&lt;/h4&gt;
&lt;p&gt;You can access Impromat on any device with a browser. It supports mobile and desktop layouts so you can dive into improv when procrastinating other important work on your PC, sitting in the bus or walking unprepared into the room where you should lead the next improv session.&lt;/p&gt;
&lt;p&gt;Over 1000 fun and boring improv elements are waiting to be opened on &lt;a href=&quot;https://impromat.app/elements?campaign=blog&quot;&gt;Impromat Elements&lt;/a&gt;. Browse them, search them, give them a pet. If you like them, they like you. If you don&#39;t like them, you have the power to make them better, or pick another one.&lt;/p&gt;
&lt;p&gt;For planning your workshops you can start with &lt;a href=&quot;https://impromat.app/workshops/create?campaign=blog&quot;&gt;Creating a Workshop&lt;/a&gt;. Here you can use our improv workshop generation engine or start with a plain collection to fill in your ideas.&lt;/p&gt;
&lt;p&gt;Use your private space to start with your creation. Contribute to the improv community by publishing your workshop so improvisers can use your inspiration.&lt;/p&gt;
&lt;h2 id=&quot;future&quot; tabindex=&quot;-1&quot;&gt;Future&lt;/h2&gt;
&lt;p&gt;There is some stuff planned down the road. But like a road is first just a path of gravel, Impromat is in its first iteration. Join to make Impromat an Autobahn:&lt;/p&gt;
&lt;p&gt;If you are an improviser, give it a shot.&lt;/p&gt;
&lt;p&gt;If you are no improviser, give it a shot.&lt;/p&gt;
&lt;p&gt;If you have an opinion, share your thoughts and ideas.&lt;/p&gt;
&lt;p&gt;If you want to develop, join with some coding.&lt;/p&gt;
&lt;p&gt;If you want to be a tree, give it at shot.&lt;/p&gt;
&lt;p&gt;If you just landed here and don&#39;t know why, give it a shot.&lt;/p&gt;
&lt;p&gt;If you feel frustrated, happy or hungry, give it a shot.&lt;/p&gt;
&lt;p&gt;Let us have some fun.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://impromat.app/?campaign=blog&quot;&gt;impromat.app&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Documenting Decisions</title>
    <link href="https://kleinprojects.com/documenting-decisions/"/>
    <updated>2024-08-17T00:00:00Z</updated>
    <id>https://kleinprojects.com/documenting-decisions/</id>
    <content type="html">&lt;p&gt;This sheet describes some of my experiences with documentation. In particular decisions that would impact technologies and teams. There exist several approaches, one of the is the documentation via Markdown in the ADR or MADR format.&lt;/p&gt;
&lt;p&gt;ADR is an architectural decision record, while MADR builds on that an tries to widen the scope to all decisions that are made in a project. Eventually, it doesn&#39;t matter what kind of format you would use as long as you decide on writing down agreements and context for your problems.&lt;/p&gt;
&lt;h2 id=&quot;most-important-sections-of-a-decision&quot; tabindex=&quot;-1&quot;&gt;Most Important Sections of a Decision&lt;/h2&gt;
&lt;p&gt;The most important sections of a decision record are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Context and Problem Statement&lt;/li&gt;
&lt;li&gt;Considered Options&lt;/li&gt;
&lt;li&gt;Decision Outcome&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;context-and-problem-statement&quot; tabindex=&quot;-1&quot;&gt;Context and Problem Statement&lt;/h3&gt;
&lt;p&gt;When writing the decision record, the most important section is the description of &lt;em&gt;Context and Problem Statement&lt;/em&gt;. This should describe the situation that led to the decision. Be clear and concise about the background and problem at hand.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;When constructing the `o_employee` table, we realized that there are false entries. For example, it showed roles for certain engagements that were not added by the intended user. This sheet documents how the issue was debugged and the proposed solutions.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;considered-options&quot; tabindex=&quot;-1&quot;&gt;Considered Options&lt;/h3&gt;
&lt;p&gt;The considered options gives a background on all options that you evaluated or thought about. You do not have to go into detail about each option, but this is the chance of providing a brief overview.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- Improve the internal merging of engagements for the employee table.
- Allow users to flag false and confirm correct merges.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;decision-outcome&quot; tabindex=&quot;-1&quot;&gt;Decision Outcome&lt;/h3&gt;
&lt;p&gt;This section describes the decision that was made. If applicable, you can add an implication section that depicts consequences of the decision.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;We decided to improve the internal merging of engagements. This will allow us to keep the system less complex because we would have to add additional logic for dealing with user input. The limitation is, that we might still miss on edge cases due to the complexity of merging engagements.
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;It can be very easy to document decisions but it is so often overlooked or de-prioritized. In my experience, writing down small decisions help a lot with improving team collaboration and communication by pulling out already taken decisions to avoid reoccurring conversations about tech choices.&lt;/p&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/joelparkerhenderson/architecture-decision-record&quot;&gt;Examples for Architecture Decision Records&lt;/a&gt;: GitHub repo with examples for ADRs&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/adr/madr&quot;&gt;Markown Any Decision Record&lt;/a&gt;: approach to document all decisions (not only architecture decisions) in a consistent way&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>The Hardest Part of a Personal Project is the Release</title>
    <link href="https://kleinprojects.com/the-hardest-part-of-a-personal-project-is-the-release/"/>
    <updated>2024-05-01T00:00:00Z</updated>
    <id>https://kleinprojects.com/the-hardest-part-of-a-personal-project-is-the-release/</id>
    <content type="html">&lt;p&gt;This article reflects my current struggles with releasing my personal project Impromat.&lt;/p&gt;
&lt;p&gt;Goal of the application is to create a cool tool for the improvisational theatre community where you can easily plan and share improv workshops. There is already existing wikis like improwiki, learnimprov, or improvresourcecenter. However, they are focusing on individual game and exercise descriptions and not on more holistic workshops. For example, what elements should you pick if you would do an improv session with pure beginners around the topic of accepting and saying yes to each other ideas?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is the value that Impromat.app brings: creating more holistic improv experiences through a collection of meaningful elements to create immersive improv workshops.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It started quite straight forward: fetch all elements from existing wikis (with respective licensing) and place them one after another in a list, which is then the description of a workshop. That works.&lt;/p&gt;
&lt;p&gt;Now the question of questions: when is it &lt;em&gt;actually&lt;/em&gt; finished? When do I feel comfortable with publishing the release number 1?&lt;/p&gt;
&lt;p&gt;Potentially never.&lt;/p&gt;
&lt;p&gt;Potentially never, when I do not decide on what to focus on.&lt;/p&gt;
&lt;p&gt;There is this saying: to go fast, go alone, to go far, go together. And I find that partially true. I am developing really fast, but often I am just developing things that are completely irrelevant for the projects success. For example, switching the frontend framework into a newer version, or redoing functioning deployment pipelines to try a different approach.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I am developing very fast. Very fast into any direction. That direction could be wrong. I don&#39;t know, I am just developing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This development and technically driven motivation used to me very fine for me. I am happy spending a couple of hours fixing something there, implementing something over here. However, I am now realising that I would like to show the stuff that I am doing to more people. It would be quite easy to show it to more people by posting my app in online forums or reach out to more friends to try it out.&lt;/p&gt;
&lt;p&gt;However, it is extremely challenging for me to find that cut, to be like &amp;quot;now, this is a good state Impromat is in&amp;quot;. That is, because I am finding details that do not add up. For example, designs that are not 100% accurate. Or that the website is a little bit too slow because a search request takes 600 milliseconds, but I want it to be under 100 so that it feels smoother. Then, on the other side I am grinding myself up in exactly those details.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I am getting lost in details because I am not 100% satisfied. And I do not have the definition of 100%.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then there are different saying like the 80/20 rule: you need 20% of your time to get to 80% success but then 80% for the last 20%. I think I am somewhere there. Overall it works great. Overall it is good. I am just always finding new things I could improve or even implement. Nonetheless, I might be stuck in my own box so it is crucial to show what Impromat is to a wider audience now.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Am I stuck in my own box of ideas? I only know when validating my product with end consumers as a good product manager would tell me.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To address this, I made a plan:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I am going to release version 1 of Impromat to friends as soon as the basic flow of workshop creation works for visitors (e.g. what is the minimum what a user requires for creating a simple improv workshop?).&lt;/li&gt;
&lt;li&gt;I am going to post a small announcement on Reddit to broaden the user base.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I even wrote a small &lt;a href=&quot;https://kleinprojects.com/impromat-strategy/&quot;&gt;strategy&lt;/a&gt; for this because I was reading an engineering management book from Will Larson.&lt;/p&gt;
&lt;p&gt;Those thoughts were possibly just for me, but possibly also for others that face struggles with releasing their personal projects.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Impromat Strategy</title>
    <link href="https://kleinprojects.com/impromat-strategy/"/>
    <updated>2024-04-27T00:00:00Z</updated>
    <id>https://kleinprojects.com/impromat-strategy/</id>
    <content type="html">&lt;p&gt;As I am currently reading An Elegant Puzzle by Will Larson, I am reflecting on the approach on writing strategies. I want to try out the approach of writing strategies with my personal application &lt;a href=&quot;https://impromat.app/&quot;&gt;impromat.app&lt;/a&gt;, a tool for planning improvisational theatre workshops.&lt;/p&gt;
&lt;p&gt;To test it out, I am going to tackle the challenge of me being the only developer in Impromat (because it&#39;s my hobby project) and me not finding the right cut to release a version 1.0 that I can then potentially market in Reddit or other improv communities.&lt;/p&gt;
&lt;h2 id=&quot;diagnosis&quot; tabindex=&quot;-1&quot;&gt;Diagnosis&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A diagnosis that defines or explains the nature of the challenge. A good diagnosis simplifies the often overwhelming complexity of reality by identifying certain aspects of the situation as critical.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I am the only developer in Impromat with a technical background, lots of ideas but limited time. I love implementing things and work on technical tasks but it is challenging for me to find the cut of when to end development and when to start marketing or generating a user basis.&lt;/p&gt;
&lt;p&gt;The challenge is rather, that I often loose focus on the features that I planned or that I did not even plan those features but just dive into the first technical challenge that comes up. This leads to feature development that might sometimes be unnecessary for the success of Impromat as a product.&lt;/p&gt;
&lt;p&gt;I am sometimes working on CI/CD because it is fun to optimize the deployment process. I work on framework optimizations and minor features that are fun to work with but not necessarily needed for the first version.&lt;/p&gt;
&lt;h2 id=&quot;guiding-policy&quot; tabindex=&quot;-1&quot;&gt;Guiding Policy&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A guiding policy for dealing with the challenge. This is an overall approach chosen to cope with or overcome the obstacles identified in the diagnosis.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Stick to the features that are needed for the first version.&lt;/li&gt;
&lt;li&gt;Show what you got to others, even if it is not perfect.&lt;/li&gt;
&lt;li&gt;If it works, it is good enough for the first version. Do not optimize that.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;coherent-actions&quot; tabindex=&quot;-1&quot;&gt;Coherent Actions&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Coherent actions designed to carry out the guiding policy. These are steps that are coordinated with one another to work together in accomplishing the guiding policy.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Define target group for version 1.0. Who are the users that I want to target with the first version?&lt;/li&gt;
&lt;li&gt;Define features for version 1.0 to target the defined group.&lt;/li&gt;
&lt;li&gt;Implement exactly those features. Not more.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;references&quot; tabindex=&quot;-1&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=4uWKEG0s9Kc&quot;&gt;Podcast with Good Strategy, Bad Strategy by Richard Rumelt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lethain.com/good-strategy-bad-strategy/&quot;&gt;Will Larson notes on Good Strategy, Bad Strategy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>Project Steering through Stakeholder Mapping</title>
    <link href="https://kleinprojects.com/project-steering-through-stakeholder-mapping/"/>
    <updated>2023-10-14T00:00:00Z</updated>
    <id>https://kleinprojects.com/project-steering-through-stakeholder-mapping/</id>
    <content type="html">&lt;figure&gt;
  &lt;a href=&quot;https://kleinprojects.com/images/2023-10-14-company.png&quot;&gt;
    &lt;img src=&quot;https://kleinprojects.com/images/2023-10-14-company.png&quot; class=&quot;responsive-image&quot; /&gt;
  &lt;/a&gt;
  &lt;!-- &quot;one big maze in a company building with desks and people and projects, colourful, between and confusion and clearness, photo realistic, 4k&quot; --&gt;
  &lt;figcaption&gt;AI Art: Company structures might seem complex and messy at first.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Steering new projects in a leading position can be challenging, especially in large companies where navigating relationships with multiple stakeholders is crucial. How can you guarantee that your project goals align with those of the company? And how can you ensure that the actions you take go in the right direction?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This article discusses an approach to stakeholder management that provides a better understanding of how to turn unknown environments into known dependencies by &lt;strong&gt;focusing on people&lt;/strong&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;overview-of-relevant-people&quot; tabindex=&quot;-1&quot;&gt;Overview of Relevant People&lt;/h2&gt;
&lt;p&gt;At the core of an organisation are individuals. The first step to stakeholder mapping, therefore, is to gather the people involved in your project activities. You could get them from meeting minutes, the company organisational chart, or by asking other project members. It&#39;s important to collect their names, roles and project involvement. Everything that provides context to that persons background. For important people you may also want to collect pictures so that you recognise them when you bump into them in the office . You could get those pictures from the company profile picture or from business networks like LinkedIn (pay attention to data privacy - if in doubt, collect only what is really necessary). Even though we are going to prioritise those people try to focus on important individuals and neglect unrelated ones.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sample for a raw collection (made-up names):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Mary&lt;/em&gt;: CEO&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Robert&lt;/em&gt;: Project Lead of Project X&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Susan&lt;/em&gt;: Project Lead of Project Y&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Thomas&lt;/em&gt;: Software Engineer in Project X&lt;/li&gt;
&lt;li&gt;&lt;em&gt;...&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Take this collection as an example: there are 2 different projects and the CEO Mary who is an important stakeholder to our project (in sake of the example). Choose a representation that is most suitable for you. You could also use a list, or directly a diagramming tool that you can use at a later stage for visualising relationships like this:&lt;/p&gt;
&lt;figure&gt;
  &lt;a href=&quot;https://kleinprojects.com/images/2023-10-14%20Stakeholder%20Management%20Collection.svg&quot;&gt;
    &lt;img src=&quot;https://kleinprojects.com/images/2023-10-14%20Stakeholder%20Management%20Collection.svg&quot; class=&quot;responsive-image&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;Collection of stakeholders&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;In the beginning of projects I usually rely on people that have been involved in the project already. Additionally, as I take extensive meeting minutes I extract relevant individuals directly from my notes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;prioritising-and-clustering&quot; tabindex=&quot;-1&quot;&gt;Prioritising and Clustering&lt;/h2&gt;
&lt;p&gt;This section describes how the collection is stripped down to most crucial and relevant individuals. There exist various dimensions on which the prioritisation could be based on. Try to reduce the collection while retaining the most comprehensive overview. Take the following examples as inspiration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you have many different projects ensure to keep &lt;strong&gt;the most important person per project&lt;/strong&gt;.
&lt;em&gt;Example&lt;/em&gt;: your list contains five people for project X and the project is not that relevant you write down the most important person from your point of view.&lt;/li&gt;
&lt;li&gt;To represent decision hierarchy, prioritise by role and &lt;strong&gt;influence of that person&lt;/strong&gt;.
&lt;em&gt;Example&lt;/em&gt;: people within a steering committee take budget decisions which has a direct influence on project extensions.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;personal relationship&lt;/strong&gt; with individuals is very valuable.
&lt;em&gt;Example:&lt;/em&gt; you have worked with &lt;em&gt;Thomas&lt;/em&gt; (from collected persons) in a previous project so you could exchange with him on a deeper level to get valuable insights into Project Y.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As mentioned, this is just an inspiration of dimensions and incomplete. Eventually, it is important for you to understand what your project currently needs: are you planning the next project extension? Then it is important to collect all decision makers for the budget. Are you planning on implementing an upcoming feature? Then it is crucial to prioritise people and projects that influence or depend on that feature.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In a recent project, I extracted over 70 individuals from meeting minutes associated with a particular project. After prioritisation, I reduced the list to 10. I used a combination of dimensions to ensure that the most important people and projects were captured.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;putting-people-in-perspective&quot; tabindex=&quot;-1&quot;&gt;Putting People in Perspective&lt;/h2&gt;
&lt;p&gt;This sections describes how the filtered collection is mapped into a dependency map of stakeholders. Take you favourite visualisation tool. If you are creating the map collaboratively ensure to have a tool that synchronises changes. Also think about maintenance: are you creating the map for depicting a one-time visualisation or do you want to continuously update changes potentially even within the team?&lt;/p&gt;
&lt;p&gt;With people names and roles wrapped in ellipses and project scopes denoted by rectangles a basic visualisation of above collection looks like the following picture. Arrows with a short text describe the relation of your project to other projects. The description is based on your understanding of the relation and might be directly related to a project or a person. If you are unsure about the relation you can also omit them in an initial step and just place a directional arrow.&lt;/p&gt;
&lt;figure&gt;
  &lt;a href=&quot;https://kleinprojects.com/images/2023-10-14%20Stakeholder%20Management%202023-10-14%2012.24.27.excalidraw.svg&quot;&gt;
    &lt;img src=&quot;https://kleinprojects.com/images/2023-10-14%20Stakeholder%20Management%202023-10-14%2012.24.27.excalidraw.svg&quot; class=&quot;responsive-image&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;Basic stakeholder map&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Mary as the CEO is sponsoring the project, while &lt;em&gt;Our Project&lt;/em&gt; relies on the functionality of &lt;em&gt;Project X&lt;/em&gt; and, &lt;em&gt;Project Y&lt;/em&gt; is interested in utilising our project&#39;s features. Note that you might want to add profile pictures to recognise people if you bump into them.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I usually have different maps for different occasions with different people. One time I had to visualise dependencies across companies and thus introduced a company boundary. Over time I have also established a fixed set of figures inspired by the &lt;a href=&quot;https://c4model.com/&quot;&gt;C4 modelling&lt;/a&gt; technique.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;conclude-understanding&quot; tabindex=&quot;-1&quot;&gt;Conclude Understanding&lt;/h2&gt;
&lt;p&gt;Your relational stakeholder map can help you understand the decisions and importance of the people involved. For example, what action should you take to secure Mary&#39;s continued sponsorship? This would require a deeper understanding of her goals and interests in your project, which you could derive from the stakeholder map.&lt;/p&gt;
&lt;p&gt;If you were only looking at titles, Thomas might not seem important in terms of project scope or governance, but you might have some questions about functionality that your project depends on, and Thomas might be the quickest person to answer (rather than going through Robert). Susan may contact you frequently if you fail to keep her informed of your next project delivery. To further define the scope and direction of the project, you could discuss the requirements with them.&lt;/p&gt;
&lt;p&gt;Once you have mapped all the important dependencies, you should share the map with your team, or anyone else who you think will find the map useful in understanding the bigger picture. The map allows you to create a shared understanding of your current project dependencies. It allows you to create alignment by clarifying relationships with people (who knows whom?).&lt;/p&gt;
&lt;p&gt;The stakeholder map you create is a living document. It is a snapshot of one dimension rather than a holistic understanding of the project. Use it as a tool to understand dependencies and create alignment. You will need to refine and update it as your project environment changes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I usually just create shared stakeholder maps that everyone in the team can access and edit. Especially at the beginning of new projects or when there is a big change in scope, the mapping has helped a lot to get a common understanding of the project.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This article presents a stakeholder management method that involves gathering relevant individuals and visualising their interdependencies. The resulting map can aid internal project team alignment and facilitate understanding of project decisions and their impact.&lt;/p&gt;
&lt;p&gt;It represents only one dimension of the overall project setup and requires effort to keep it up to date. In addition, there are many other perspectives on stakeholder management, such as approaches discussed in the &lt;a href=&quot;https://miro.com/blog/stakeholder-mapping/&quot;&gt;Miro blog&lt;/a&gt; or related topics on &lt;a href=&quot;https://staffeng.com/guides/&quot;&gt;staffeng.com&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For me the greatest strength of the whole exercise is the alignment with team members. When I present my drawings I have often gotten the feedback that this map really helped with understanding the bigger picture of projects and teams.&lt;/p&gt;
&lt;/blockquote&gt;
</content>
  </entry>
  
  <entry>
    <title>Improving the Navigation of Impromat.app with a Bottom Navigation Bar</title>
    <link href="https://kleinprojects.com/improving-the-navigation-of-impromat-app-with-a-bottom-navigation-bar/"/>
    <updated>2023-09-10T00:00:00Z</updated>
    <id>https://kleinprojects.com/improving-the-navigation-of-impromat-app-with-a-bottom-navigation-bar/</id>
    <content type="html">&lt;!-- Motivation &amp; Background --&gt;
&lt;p&gt;To collect information about user behavior, I observed individuals using &lt;a href=&quot;https://impromat.app/&quot;&gt;Impromat.app&lt;/a&gt;. Quickly, I noticed, that one issue was the navigation. For instance, users were not clicking on the drawer icon located in the top-right corner in order to view all functions of the app (such as the ability to search for improv exercises and games, build a personal workshop, or manage individual exercises and games). Even worse, some mobile devices have a native feature that navigates back when swiping from left to right. This made it extremely difficult to navigate the app, as users were unable to open the drawer.&lt;/p&gt;
&lt;!-- Problem Summary --&gt;
&lt;p&gt;So, I sought a solution to improve the discoverability of &lt;a href=&quot;https://impromat.app/&quot;&gt;Impromat.app&lt;/a&gt; and enable swiping from left to right to open a drawer on devices lacking the option. Below is a screenshot of the elements page which allows improvisers to search existing elements and exercises.&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;https://kleinprojects.com/images/2023-09-10-impromat-drawer-layout.png&quot; class=&quot;responsive-image&quot; /&gt;
  &lt;figcaption&gt;The screen displays Elements for searching exercises and games.  The app&#39;s other features are concealed in the drawer (refer to below), which may lead users to believe that only &quot;exploration,&quot; &quot;liking,&quot; or &quot;my library&quot; are possible actions.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;However, clicking on the drawer icon on the top right reveals the full functionality of the app.&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;https://kleinprojects.com/images/2023-09-10-impromat_drawer_open_layout.png&quot; class=&quot;responsive-image&quot; /&gt;
  &lt;figcaption&gt;Open drawer that shows all available sub-pages. Please note that the search function is only one of its components.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;!-- Possible Solutions --&gt;
&lt;h2 id=&quot;the-navigation-bar-solution&quot; tabindex=&quot;-1&quot;&gt;The Navigation Bar Solution&lt;/h2&gt;
&lt;p&gt;Initially, I had considered dropping web app support and exclusively backing the locally installed application to avoid browser incompatibilities. However, this solution would not resolve the app&#39;s discoverability.&lt;/p&gt;
&lt;p&gt;Therefore, I researched other frameworks and applications and discovered the top-level navigation of tabs, which are utilized by apps such as Spotify and Google Play store.&lt;/p&gt;
&lt;p&gt;The Material 3 design offers &lt;a href=&quot;https://m3.material.io/foundations/layout/applying-layout/compact#f328a230-8f35-4b54-adca-be4acc42fa25&quot;&gt;helpful documentation on its website&lt;/a&gt;. According to their suggestions, a bottom tabs navigation (referred to as a &amp;quot;navigation bar&amp;quot;) is suitable for compact screens, while a side bar navigation is better for larger screens.&lt;/p&gt;
&lt;p&gt;Given the mobile-centric approach of &lt;a href=&quot;https://impromat.app/&quot;&gt;Impromat&lt;/a&gt;, the bottom navigation bar with tabs is an appropriate starting point. Here is a preliminary version of the bottom navigation bar:&lt;/p&gt;
&lt;figure&gt;
  &lt;!-- &lt;img src=&quot;../images/2023-09-10-impromat_three_tabs_layout.png&quot; style=&quot;min-width: 600px; width: 50%; max-width: 840px;&quot;&gt; --&gt;
  &lt;img src=&quot;https://kleinprojects.com/images/2023-09-10-impromat_three_tabs_layout.png&quot; class=&quot;responsive-image&quot; /&gt;
  &lt;figcaption&gt;Impromat with a navigation bar to reveal more functions and simplify user interaction.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Sections are divided into the three main areas:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Home&lt;/strong&gt;: About information, news, profile, settings, legal notice, data privacy, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exercises &amp;amp; Games&lt;/strong&gt;: Search improvisational theater elements that you can add to workshops and manage your own entries.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workshops&lt;/strong&gt;: Plan and share your improv workshops.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;wrap-up&quot; tabindex=&quot;-1&quot;&gt;Wrap-up&lt;/h2&gt;
&lt;p&gt;The implementation of the new navigation approach is currently under development, but I already like it a lot for its easy app discoverability, conciseness, and ease of use (just one click instead of a swipe gesture). However, I am still determining the optimal high-level differentiation, which will depend on users&#39; goals when they open &lt;a href=&quot;https://impromat.app/&quot;&gt;Impromat&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the future, I will integrate a more adaptive design following the guidelines of Material Design &lt;a href=&quot;https://m3.material.io/foundations/layout/applying-layout&quot;&gt;source&lt;/a&gt;. Currently, I must assess whether implementing these changes will enhance the user experience before proceeding.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Navigating an Uncertain Project Landscape: Finding Your Path When Direction is Unknown</title>
    <link href="https://kleinprojects.com/navigating-an-uncertain-project-landscape-finding-your-path-when-direction-is-unknown/"/>
    <updated>2023-08-13T00:00:00Z</updated>
    <id>https://kleinprojects.com/navigating-an-uncertain-project-landscape-finding-your-path-when-direction-is-unknown/</id>
    <content type="html">&lt;p&gt;As a consultant, I work with clients in a variety of IT projects and roles. It is common to find oneself in a complex environment that one has to learn how to navigate. I have been reflecting on my learnings, particularly regarding projects that I am involved with at multiple levels, from management to writing code.&lt;/p&gt;
&lt;h2 id=&quot;find-the-right-people-to-talk-to&quot; tabindex=&quot;-1&quot;&gt;Find the right people to talk to&lt;/h2&gt;
&lt;p&gt;If you are starting a project with a high level of uncertainty, you will want to start by creating an overview. Gather all the available material and create a stakeholder or project overview that you can refer to when aligning priorities with your direct client. In hierarchical organisations, you will usually also find an organisational chart to help identify structures and project set-ups.&lt;/p&gt;
&lt;p&gt;The following image shows a very rudimentary description of people and projects. The arrows show the connection between our project and the neighbouring projects (e.g. &lt;em&gt;Project Pen&lt;/em&gt; or &lt;em&gt;Project Flower&lt;/em&gt;). Normally I would write the name and role of the person in the boxes:&lt;/p&gt;
&lt;div style=&quot;text-align: center;&quot;&gt;
  &lt;img style=&quot;max-width:600px; width:100%;&quot; src=&quot;https://kleinprojects.com/images/2023-08-13-Navigating-Unknown-Project-Overview.svg&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;Of course, the diagram is fluid: new decisions may affect the connection to a project. New stakeholders might become more important. People might leave projects, or projects might be reorganised. Also, there is no &amp;quot;one way&amp;quot; to approach this. The chart can have different views. For example, I once had a situation where we had to manoeuvre our project through a very political situation, so we put green, yellow and red dots on people to keep track of our communication with individuals.&lt;/p&gt;
&lt;p&gt;The main goal is to create transparency and easy alignment with your project team. This allows you to plan specific communication actions and helps you to understand the roles of existing projects. The goals of other projects (e.g. what might a collaboration look like?) may become clearer.&lt;/p&gt;
&lt;h2 id=&quot;find-the-right-abstraction-layer&quot; tabindex=&quot;-1&quot;&gt;Find the right abstraction layer&lt;/h2&gt;
&lt;p&gt;A project is not one-dimensional. It is not just a presentation to management to get the budget approved. It is not just the technical implementation. And it is not just the product specifications. There are different ways of looking at the project scope and requirements. As I support on all layers, e.g. management, architecture and coding layer, I found it extremely important to find the most effective layer and stick to it.&lt;/p&gt;
&lt;p&gt;You may define your own set of layers that help with leading questions. Here are some notes from my differentiations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Product layer: What is the value we create? Who are our end users? Why should others use our product?&lt;/li&gt;
&lt;li&gt;Management layer: Who will own the project in a year&#39;s time? What are our next milestones? How much budget do we need to continue?&lt;/li&gt;
&lt;li&gt;Technical layer: What are our technical interfaces? What are the components of our architecture? What programming languages are we using?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Layers can vary. If I were to write more code, I would also get into a coding layer: what do we need to implement to prove our architecture? What code do we need to write to demonstrate our MVP functionality?&lt;/p&gt;
&lt;p&gt;Thinking in layers allows you to reduce complexity and better assess what your project needs most right now. Is the value proposition clear to everyone? Is the budget realistic? Do we need to demonstrate feasibility through technical implementation? Questions like these can guide you to the next steps that will have the greatest impact and be the best use of your time. Conversely, it would not help to have the most visually appealing technical demonstration if none of your stakeholders understand the business value of the project.&lt;/p&gt;
&lt;h2 id=&quot;parallelise-streams&quot; tabindex=&quot;-1&quot;&gt;Parallelise Streams&lt;/h2&gt;
&lt;p&gt;Do not bet on a single horse, I learned it the hard way: after the first iteration we proposed a continuation of our project to management and got funding for a project plan approved. However, our collaboration was based on only one project, on whose success we were apparently too confident, because that one project collapsed in the second week after the start of our 6-month project iteration (which we had based on collaboration with that project).&lt;/p&gt;
&lt;p&gt;After a panicky search for our other project collaboration options, we ended up with seven parallel project streams again. This was too much for our three-person team. However, after some assessment, we ended up with a handful again, which was totally manageable and would spread our risk across multiple project collaborations.&lt;/p&gt;
&lt;div style=&quot;text-align: center;&quot;&gt;
  &lt;img style=&quot;max-width:400px; width:100%;&quot; src=&quot;https://kleinprojects.com/images/2023-08-13-Navigating-Unknown-Parallel-Streams.svg&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;We and I have learnt the hard way to parallelise project streams. This reduces risk, because project dependencies can always fail or lead in an unwanted direction. More options give you more freedom to steer and shape your own vision.&lt;/p&gt;
&lt;h2 id=&quot;create-transparency-by-sharing-progress-and-thoughts&quot; tabindex=&quot;-1&quot;&gt;Create Transparency by Sharing Progress and Thoughts&lt;/h2&gt;
&lt;p&gt;As discussed, we are taking about a project that is complex in the sense that business requirements are not yet defined. The end users have not yet been fully identified. So it is crucial to create transparency about what you are working on. At least this is how I would build trust for myself: keep others informed about what has happened during the week and share thoughts about it. Thoughts could be potential risks I see or certain priorities we should focus on (from my point of view).&lt;/p&gt;
&lt;p&gt;I would write these notes once a week and share them with my client and my internal delivery coaching team (e.g. consultants supporting my engagement). I also found that the right channel was important. Initially, I sent them by email without getting much response. After a while, I switched to our chat tool and just chatted my notes, which worked much better. It gives people a chance to react and comment on my weekly notes. It also gives me confidence and feedback that the points I have raised and will raise are the right ones.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Fuzzy Search Considerations for Impromat</title>
    <link href="https://kleinprojects.com/fuzzy-search-considerations-for-impromat/"/>
    <updated>2023-08-07T00:00:00Z</updated>
    <id>https://kleinprojects.com/fuzzy-search-considerations-for-impromat/</id>
    <content type="html">&lt;h2 id=&quot;context&quot; tabindex=&quot;-1&quot;&gt;Context&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Currently, the search for element is implemented with the fuzzy search library &lt;a href=&quot;https://www.fusejs.io/&quot;&gt;fuse.js&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;For a search, all elements of the database are loaded and then searched via &lt;em&gt;fuse.js&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;This provided an easy start BUT: the approach does not scale as all elements have to be searched for every call.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;technology-stack&quot; tabindex=&quot;-1&quot;&gt;Technology Stack&lt;/h3&gt;
&lt;p&gt;The investigated options consider the current stack:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.postgresql.org/&quot;&gt;Postgresql&lt;/a&gt; as database&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.prisma.io/&quot;&gt;Prisma&lt;/a&gt; for db mapping&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thus, options span from Postgres native features to Prisma capabilities to stay with the existing stack.&lt;/p&gt;
&lt;h2 id=&quot;considered-options&quot; tabindex=&quot;-1&quot;&gt;Considered Options&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Prisma Full Text Search&lt;/li&gt;
&lt;li&gt;Postgres &lt;code&gt;pg_trgm&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Basic text search via &lt;code&gt;LIKE %searchText%&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;prisma-full-text-search&quot; tabindex=&quot;-1&quot;&gt;Prisma Full Text Search&lt;/h3&gt;
&lt;p&gt;Prisma preview feature for searching through database fields via a search logic:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Good, because Prisma is the db abstraction layer (shift complexity to abstraction layer).&lt;/li&gt;
&lt;li&gt;Bad, because the search is not fuzzy but is a set of static search rules.&lt;/li&gt;
&lt;li&gt;Good, because it provides huge performance benefits over the current solution.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See &lt;a href=&quot;https://www.prisma.io/docs/concepts/components/prisma-client/full-text-search&quot;&gt;Prisma documentation&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;postgres-pg_trgm&quot; tabindex=&quot;-1&quot;&gt;Postgres &lt;code&gt;pg_trgm&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Postgres trigram extension for fuzzy search features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bad, because it requires raw SQL (mixing with Prisma as abstraction layer).&lt;/li&gt;
&lt;li&gt;Good, because it is extremely performant as it works directly with Postgres.&lt;/li&gt;
&lt;li&gt;Good, because it is fuzzy.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;basic-text-search-via-like&quot; tabindex=&quot;-1&quot;&gt;Basic text search via &lt;code&gt;LIKE&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Good, because it provides the fastest query time.&lt;/li&gt;
&lt;li&gt;Bad, because search is not fuzzy at all.&lt;/li&gt;
&lt;li&gt;Good, because it can be implemented via Prisma &lt;code&gt;contains&lt;/code&gt; filter.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;decision&quot; tabindex=&quot;-1&quot;&gt;Decision&lt;/h2&gt;
&lt;p&gt;Still unclear, how important fuzziness of the search is.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Switch to easiest &lt;code&gt;contains&lt;/code&gt; option.&lt;/li&gt;
&lt;li&gt;Observe user experience without fuzzy search and verify it is still usable.&lt;/li&gt;
&lt;li&gt;Switch to &lt;code&gt;pg_trgm&lt;/code&gt; in the mid-term if Prisma is not supporting a fuzzy search.&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  
  <entry>
    <title>Prometheus and Grafana with Dokku</title>
    <link href="https://kleinprojects.com/prometheus-and-grafana-with-dokku/"/>
    <updated>2023-07-30T00:00:00Z</updated>
    <id>https://kleinprojects.com/prometheus-and-grafana-with-dokku/</id>
    <content type="html">&lt;p&gt;The following instructions describe how I have setup server-side monitoring for &lt;a href=&quot;https://impromat.app/&quot;&gt;Impromat.app&lt;/a&gt;. The server is running &lt;a href=&quot;https://dokku.com/&quot;&gt;Dokku&lt;/a&gt; for managing all services.&lt;/p&gt;
&lt;p&gt;The monitoring primarily relies on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prometheus&lt;/li&gt;
&lt;li&gt;Grafana&lt;/li&gt;
&lt;li&gt;node-exporter&lt;/li&gt;
&lt;li&gt;cAdvisor&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It also sets up a data source for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tested with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ubuntu 22.04&lt;/li&gt;
&lt;li&gt;Dokku 0.30.2&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Based on &lt;a href=&quot;https://richardwillis.info/blog/monitor-dokku-server-prometheus-loki-grafana&quot;&gt;this article&lt;/a&gt; with adaptions to make it work with dokku version &lt;code&gt;0.30.2&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Replace &lt;code&gt;impromat.app&lt;/code&gt; with your own domain.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;install-required-plugins&quot; tabindex=&quot;-1&quot;&gt;Install required plugins&lt;/h2&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; dokku plugin:install http-auth https://github.com/dokku/dokku-http-auth.git
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; dokku plugin:install letsencrypt https://github.com/dokku/dokku-letsencrypt.git&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;install-prometheus&quot; tabindex=&quot;-1&quot;&gt;Install Prometheus&lt;/h2&gt;
&lt;p&gt;Add dedicated network for prometheus services (see &lt;a href=&quot;https://dokku.com/docs/networking/network/&quot;&gt;Dokku Networking&lt;/a&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;dokku network:create prometheus-bridge&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create &lt;code&gt;prometheus&lt;/code&gt; app:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;dokku apps:create prometheus
dokku proxy:ports-add prometheus http:80:9090
dokku network:set prometheus attach-post-deploy prometheus-bridge&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create volume mappings:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; /var/lib/dokku/data/storage/prometheus/&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;config,data&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;touch&lt;/span&gt; /var/lib/dokku/data/storage/prometheus/config/&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;alert.rules,prometheus.yml&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;chown&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-R&lt;/span&gt; nobody:nogroup /var/lib/dokku/data/storage/prometheus

dokku storage:mount prometheus /var/lib/dokku/data/storage/prometheus/config:/etc/prometheus
dokku storage:mount prometheus /var/lib/dokku/data/storage/prometheus/data:/prometheus&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add prometheus docker start command:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;dokku config:set --no-restart prometheus &lt;span class=&quot;token assign-left variable&quot;&gt;DOKKU_DOCKERFILE_START_CMD&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;--config.file=/etc/prometheus/prometheus.yml
--storage.tsdb.path=/prometheus
--web.console.libraries=/usr/share/prometheus/console_libraries
--web.console.templates=/usr/share/prometheus/consoles
--web.enable-lifecycle
--storage.tsdb.no-lockfile&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;--config.file&lt;/code&gt; sets the location of the prometheus config path.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Create config file at &lt;code&gt;/var/lib/dokku/data/storage/prometheus/config/prometheus.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;global&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;scrape_interval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 15s

&lt;span class=&quot;token key atrule&quot;&gt;scrape_configs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;job_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;prometheus&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;scrape_interval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 15s
    &lt;span class=&quot;token key atrule&quot;&gt;static_configs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;targets&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;localhost:9090&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;job_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;exporter
    &lt;span class=&quot;token key atrule&quot;&gt;scrape_interval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 15s
    &lt;span class=&quot;token key atrule&quot;&gt;scheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https
    &lt;span class=&quot;token key atrule&quot;&gt;basic_auth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &amp;lt;username&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &amp;lt;password&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;static_configs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;targets&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;node-exporter.impromat.app&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;job_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; cadvisor
    &lt;span class=&quot;token key atrule&quot;&gt;scrape_interval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 15s
    &lt;span class=&quot;token key atrule&quot;&gt;scheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; http
    &lt;span class=&quot;token key atrule&quot;&gt;static_configs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;targets&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; cadvisor.web&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8080&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Deploy prometheus:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; pull prom/prometheus:latest
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; tag prom/prometheus:latest dokku/prometheus:latest
dokku git:from-image prometheus dokku/prometheus:latest
dokku domains:set prometheus prometheus.impromat.app

dokku letsencrypt:enable prometheus
dokku http-auth:enable prometheus &lt;span class=&quot;token environment constant&quot;&gt;USER&lt;/span&gt; PASSWORD&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Validate running Prometheus at https://prometheus.impromat.app/targets (using respective &lt;code&gt;USER&lt;/code&gt; and &lt;code&gt;PASSWORD&lt;/code&gt;)&lt;/p&gt;
&lt;h2 id=&quot;create-node-exporter&quot; tabindex=&quot;-1&quot;&gt;Create node-exporter&lt;/h2&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;dokku apps:create node-exporter
dokku proxy:ports-set node-exporter http:80:9100
dokku config:set --no-restart node-exporter &lt;span class=&quot;token assign-left variable&quot;&gt;DOKKU_DOCKERFILE_START_CMD&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;--collector.textfile.directory=/data --path.procfs=/host/proc --path.sysfs=/host/sys&quot;&lt;/span&gt;

dokku docker-options:add node-exporter deploy &lt;span class=&quot;token string&quot;&gt;&quot;--net=host&quot;&lt;/span&gt;
dokku checks:disable node-exporter&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Storage mounts:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; /var/lib/dokku/data/storage/node-exporter
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;chown&lt;/span&gt; nobody:nogroup /var/lib/dokku/data/storage/node-exporter

dokku storage:mount node-exporter /proc:/host/proc:ro
dokku storage:mount node-exporter /:/rootfs:ro
dokku storage:mount node-exporter /sys:/host/sys:ro
dokku storage:mount node-exporter /var/lib/dokku/data/storage/node-exporter:/data&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Deploy node-exporter:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; image pull prom/node-exporter:latest
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; image tag prom/node-exporter:latest dokku/node-exporter:latest

dokku git:from-image node-exporter dokku/node-exporter:latest
dokku domains:set node-exporter node-exporter.impromat.app

dokku letsencrypt:enable node-exporter
dokku http-auth:enable node-exporter &lt;span class=&quot;token environment constant&quot;&gt;USER&lt;/span&gt; PASSWORD&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;create-cadvisor&quot; tabindex=&quot;-1&quot;&gt;Create cAdvisor&lt;/h2&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;dokku apps:create cadvisor
dokku proxy:ports-set cadvisor http:80:8080
dokku config:set --no-restart cadvisor &lt;span class=&quot;token assign-left variable&quot;&gt;DOKKU_DOCKERFILE_START_CMD&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;--docker_only --housekeeping_interval=10s --max_housekeeping_interval=60s&quot;&lt;/span&gt;
dokku network:set cadvisor attach-post-deploy prometheus-bridge&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Storage mounts:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;dokku storage:mount cadvisor /:/rootfs:ro
dokku storage:mount cadvisor /sys:/sys:ro
dokku storage:mount cadvisor /var/lib/docker:/var/lib/docker:ro
dokku storage:mount cadvisor /var/run:/var/run:rw&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Deploy cadvisor:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; image pull gcr.io/cadvisor/cadvisor:latest
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; image tag gcr.io/cadvisor/cadvisor:latest dokku/cadvisor:latest

dokku git:from-image cadvisor dokku/cadvisor:latest&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;setup-grafana&quot; tabindex=&quot;-1&quot;&gt;Setup Grafana&lt;/h2&gt;
&lt;p&gt;Create app:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;dokku apps:create grafana
dokku proxy:ports-add grafana http:80:3000
dokku network:set grafana attach-post-deploy prometheus-bridge&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Storage mounts:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; /var/lib/dokku/data/storage/grafana/&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;config,data,plugins&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; /var/lib/dokku/data/storage/grafana/config/provisioning/datasources
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;chown&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-R&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;472&lt;/span&gt;:472 /var/lib/dokku/data/storage/grafana

dokku storage:mount grafana /var/lib/dokku/data/storage/grafana/config/provisioning/datasources:/etc/grafana/provisioning/datasources
dokku storage:mount grafana /var/lib/dokku/data/storage/grafana/data:/var/lib/grafana
dokku storage:mount grafana /var/lib/dokku/data/storage/grafana/plugins:/var/lib/grafana/plugins&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add &lt;code&gt;prometheus&lt;/code&gt; data source to &lt;code&gt;/var/lib/dokku/data/storage/grafana/config/provisioning/datasources/prometheus.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    orgId: &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    url: http://prometheus.web:9090
    basicAuth: &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
    isDefault: &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
    version: &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    editable: &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Deploy Grafana:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; pull grafana/grafana:latest
&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; tag grafana/grafana:latest dokku/grafana:latest
dokku git:from-image grafana dokku/grafana:latest
dokku letsencrypt:enable grafana&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;login-to-grafana&quot; tabindex=&quot;-1&quot;&gt;Login to Grafana&lt;/h2&gt;
&lt;p&gt;Go to https://grafana.impromat.app. Login via &lt;code&gt;admin&lt;/code&gt;/&lt;code&gt;admin&lt;/code&gt; and set a new password.&lt;/p&gt;
&lt;h2 id=&quot;adding-more-data-sources&quot; tabindex=&quot;-1&quot;&gt;Adding more data sources&lt;/h2&gt;
&lt;h3 id=&quot;postgresql&quot; tabindex=&quot;-1&quot;&gt;PostgreSQL&lt;/h3&gt;
&lt;p&gt;Get database info from dokku via:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;dokku postgres:info &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;db-service-name&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add &lt;code&gt;/var/lib/dokku/data/storage/grafana/config/provisioning/datasources/postgresql.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;datasources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Postgres
    &lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; postgres
    &lt;span class=&quot;token key atrule&quot;&gt;access&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; proxy
    &lt;span class=&quot;token key atrule&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &amp;lt;Internal Ip&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5432&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; postgres
    &lt;span class=&quot;token key atrule&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &amp;lt;database name&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;secureJsonData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &amp;lt;DB_PASSWORD&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;jsonData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &amp;lt;database name&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;sslmode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;disable&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# disable/require/verify-ca/verify-full&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;maxOpenConns&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Grafana v5.4+&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;maxIdleConns&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Grafana v5.4+&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;connMaxLifetime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;14400&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Grafana v5.4+&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;postgresVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1400&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# 903=9.3, 904=9.4, 905=9.5, 906=9.6, 1000=10&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See &lt;a href=&quot;https://grafana.com/docs/grafana/latest/administration/provisioning/#data-sources&quot;&gt;Grafana Data Sources Documentation&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Thoughts on Blogging</title>
    <link href="https://kleinprojects.com/thoughts-on-blogging/"/>
    <updated>2023-07-24T00:00:00Z</updated>
    <id>https://kleinprojects.com/thoughts-on-blogging/</id>
    <content type="html">&lt;p&gt;So, I have been reflecting on blogging because it has been on my mind for quite some time to get more active with it. I have just been wondering why I blog and what I want to write about.&lt;/p&gt;
&lt;p&gt;The following are just some thoughts that I am collecting about blogging:&lt;/p&gt;
&lt;h2 id=&quot;personal-vs.-professional&quot; tabindex=&quot;-1&quot;&gt;Personal vs. Professional&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Personal blog is hosted on own website. Not necessarily bound to my current company or career defining topics. I could share anything that I personally identify with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Coding&lt;/li&gt;
&lt;li&gt;Improvisational Theatre&lt;/li&gt;
&lt;li&gt;Climbing&lt;/li&gt;
&lt;li&gt;Music&lt;/li&gt;
&lt;li&gt;Other random stuff&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Professionally, I could write about things that are related to my current project or my current employer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Topics I want do a knowledge sharing session about&lt;/li&gt;
&lt;li&gt;Experiences regarding my roles in software projects&lt;/li&gt;
&lt;li&gt;Coaching&lt;/li&gt;
&lt;li&gt;Mentoring&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Personal articles may be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Incomplete in respect to thoughts and written content&lt;/li&gt;
&lt;li&gt;Covering random topics&lt;/li&gt;
&lt;li&gt;Might be reflective loud thinking&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Professional articles may be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spellchecked&lt;/li&gt;
&lt;li&gt;Proof read&lt;/li&gt;
&lt;li&gt;Published and referenced in professional and social networks (e.g. LinkedIn, Medium, Reddit)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;draft-to-release&quot; tabindex=&quot;-1&quot;&gt;Draft to Release&lt;/h2&gt;
&lt;p&gt;I might start writing something without finishing it. Shall I still publish?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I should, because it might be motivating to publish stuff.&lt;/li&gt;
&lt;li&gt;I should, because it is hard to know when an article is truly finished for it&#39;s &amp;quot;release&amp;quot;.&lt;/li&gt;
&lt;li&gt;I should not, because I would publish scattered information.&lt;/li&gt;
&lt;li&gt;I should not, because it might also indicated that the article is not of a high priority.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;find-a-group-or-community-to-share-thoughts-with&quot; tabindex=&quot;-1&quot;&gt;Find a group or community to share thoughts with&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Could be at work or dedicated meetups.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Good, because you can go into discussions and get feedback.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Good, because you can validate ideas.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bad, because it is more time effort than just posting it (Really? A post might also take a long time.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In general, I see it as crucial step towards effective knowledge sharing.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;find-conferences-to-speak-at&quot; tabindex=&quot;-1&quot;&gt;Find conferences to speak at&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Good, because you get a wide reach.&lt;/li&gt;
&lt;li&gt;Good, because talking and presenting is fun.&lt;/li&gt;
&lt;li&gt;Bad, because it might be hard to find a conference that is a good fit.&lt;/li&gt;
&lt;li&gt;Bad, because it needs the most effort.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>Acting Authenticity</title>
    <link href="https://kleinprojects.com/acting-authenticity/"/>
    <updated>2023-05-02T00:00:00Z</updated>
    <id>https://kleinprojects.com/acting-authenticity/</id>
    <content type="html">&lt;p&gt;Was conducting a public speaking workshop session. One of the exercises was a 2 minute improvised talk about a certain topic. As participants also wanted me to present I took it and improvised a two minute speech about something.&lt;/p&gt;
&lt;p&gt;Was interesting to get feedback: ticking all boxes for all categories that we have listed for &amp;quot;voice&amp;quot;. BUT feedback was also that I did not came across authentic at all. &amp;quot;Felt like an advertisement&amp;quot;. I also wanted to make it feel like an advertisement but was wondering: how can I control when I &lt;em&gt;act&lt;/em&gt; versus when I try to be &lt;em&gt;authentic&lt;/em&gt;? Can one reach &lt;em&gt;authenticity&lt;/em&gt; when performing an act on stage?&lt;/p&gt;
&lt;p&gt;Still an open thought.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Next 13 and Mantine will not replace the Ionic framework for Impromat (yet)</title>
    <link href="https://kleinprojects.com/next-13-and-mantine-will-not-replace-the-ionic-framework-for-impromat-yet/"/>
    <updated>2023-03-26T00:00:00Z</updated>
    <id>https://kleinprojects.com/next-13-and-mantine-will-not-replace-the-ionic-framework-for-impromat-yet/</id>
    <content type="html">&lt;p&gt;Since Next 13 &lt;a href=&quot;https://nextjs.org/blog/next-13&quot;&gt;got released in October 2022&lt;/a&gt; I wanted to test the framework to see if it is a viable option for Impromat, potentially replacing the Ionic framework.&lt;/p&gt;
&lt;p&gt;Main motivations are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Customized styling with the styling framework Mantine.&lt;/li&gt;
&lt;li&gt;Trying out cutting-edge technology with newest Server Side Rendering (SSR) improvements.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, Impromat is not meant to be a website but it is targeting mainly mobile devices through an installable application. Technically, this is implemented via a Progressive Web App (PWA) to retain one code base for several target devices. Next 13 may offer great improvements regarding SSR and implements cutting edge patterns and newest React improvements. Nonetheless, one of the main questions remained: why should I implement a bleeding edge SSR framework, if I am going to wrap it into a PWA either way?&lt;/p&gt;
&lt;p&gt;An additional argument for Ionic is its support for Android and IOS devices including a easy component framework that offers most app requirement needs out of the box. Switching technologies would imply a re-implementation of the whole styling framework and developing most of the components from scratch.&lt;/p&gt;
&lt;p&gt;Nonetheless, for the future I might still consider a switch to Next 13 to try out latest technology and benefit from SSR that Impromat could leverage for an improved Search Engine Optimization (SEO). Right now switching costs would just be to high.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Reflection on making Impromat accessible for search engines</title>
    <link href="https://kleinprojects.com/reflection-on-making-impromat-accessible-for-search-engines/"/>
    <updated>2022-12-13T00:00:00Z</updated>
    <id>https://kleinprojects.com/reflection-on-making-impromat-accessible-for-search-engines/</id>
    <content type="html">&lt;p&gt;The following is a note to myself to reflect on options for making a current personal project &lt;a href=&quot;https://impromat.app/&quot;&gt;Impromat&lt;/a&gt; ready for search engines.&lt;/p&gt;
&lt;h2 id=&quot;background&quot; tabindex=&quot;-1&quot;&gt;Background&lt;/h2&gt;
&lt;p&gt;Impromat is a single page application (SPA) and currently hosted on https://impromat.app. However, this comes with several issues:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It&#39;s challenging to deal with search engine optimizations (SEO). As &lt;a href=&quot;https://web.dev/rendering-on-the-web/&quot;&gt;this article&lt;/a&gt; from Google describes you can assume that crawlers cannot process JavaScript and thus cannot process SPAs.&lt;/li&gt;
&lt;li&gt;In the future a login might be required. Then it&#39;s not possible to &amp;quot;just share&amp;quot; anymore, however, sharing workshops is still one core feature of the Impromat.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;possible-solutions&quot; tabindex=&quot;-1&quot;&gt;Possible Solutions&lt;/h2&gt;
&lt;h3 id=&quot;combine-seo-optimization-within-the-application&quot; tabindex=&quot;-1&quot;&gt;Combine SEO optimization within the application&lt;/h3&gt;
&lt;p&gt;This means, that the backend would have to implement some kind of server side rendering (SSR) to allow the delivery of pre-rendered HTML that search engines could parse. There is &lt;a href=&quot;https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering&quot;&gt;another article&lt;/a&gt; from Google that also mentions an,unfortunately discouraged, approach called &lt;em&gt;dynamic rendering&lt;/em&gt;. With this approach the web server would send a different, SEO optimized result to web-crawlers.&lt;/p&gt;
&lt;p&gt;Google also provides a very good discussion in &lt;a href=&quot;https://web.dev/rendering-on-the-web/&quot;&gt;this article&lt;/a&gt; about the options and their advantages and disadvantages.&lt;/p&gt;
&lt;h3 id=&quot;separate-the-application-page-from-a-seo-optimized-landing-page&quot; tabindex=&quot;-1&quot;&gt;Separate the application page from a SEO optimized landing page&lt;/h3&gt;
&lt;p&gt;Another approach would be the separation of the actual application that users access, and a separate landing page that can act as a representation within the world wide web. From SEO perspectives this would be an ideal case because a well optimized, dedicated web server could host the respective landing page and link to the Impromat application. However, this also adds another service and another application that needs maintenance.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Both approaches got advantages and disadvantages. I have to reflect on the goals in regards to SEO and future development of the Impromat.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Thoughts on Data Privacy for my free time project Impromat</title>
    <link href="https://kleinprojects.com/thoughts-on-data-privacy-for-my-free-time-project-impromat/"/>
    <updated>2022-11-06T00:00:00Z</updated>
    <id>https://kleinprojects.com/thoughts-on-data-privacy-for-my-free-time-project-impromat/</id>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Just some thoughts on data privacy and GDPR&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For my current project &lt;a href=&quot;https://impromat.app/&quot;&gt;Impromat&lt;/a&gt; which is an app for planning workshops for improvisational theatre, I have spent the last couple of weeks getting into that topic of data privacy and GDPR. In a nutshell: it&#39;s kind of complicated for such a small free time project. However, I value data privacy and I am sometimes a little bit afraid of some lawyers that specials in finding websites without data privacy notices.&lt;/p&gt;
&lt;p&gt;Nonetheless, there are already some good resources about GDPR and also some decent generates like the &lt;a href=&quot;https://datenschutz-generator.de/&quot;&gt;Datenschutz Generator of Dr. Thomas Schwenk&lt;/a&gt;. The author of the website was also interviewed in a podcast which helped me with my understanding of data privacy especially in regards to GDPR (just search for it on Spotify if you are interested). Especially the generator helped me a lot with the setup, even though, I had to change some text descriptions and some contract information with my hosting provider (e.g. the Impromat backend is hosted on &lt;a href=&quot;https://contabo.de/&quot;&gt;Contabo&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://impromat.app/&quot;&gt;Impromat&lt;/a&gt; is not a commercial application and does neither track nor collect any user specific data per default (would be far to complicated for me to set up :P ). However, there is also a login functionality so that users can synchronize workshops across devices. Through a Google login users can get access to that synchronization feature and workshop data gets transferred to the Impromat backend server and shared with devices of that user. This is where data privacy really gets interesting because according to GDPR there are some regulations that the application must fulfill in this case. For example: users must be able to retrieve all there stored data and they must be able to request deletion of their data. I am very happy that Impromat is currently in such a simple state that I have a 100% knowledge about what kind of data is stored where.&lt;/p&gt;
&lt;p&gt;After all, thinking about all those data sets that this small application processes is already very valuable to me. I still think that GDPR is a step into the right direction to protect user specific data, however, it&#39;s far to complicated for hobby or free time projects that you only spend a couple of hours per week or even month on.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Using the version from package.json in React applications</title>
    <link href="https://kleinprojects.com/using-the-version-from-package-json-in-react-applications/"/>
    <updated>2022-10-20T00:00:00Z</updated>
    <id>https://kleinprojects.com/using-the-version-from-package-json-in-react-applications/</id>
    <content type="html">&lt;p&gt;You want to display the application version from your &lt;code&gt;package.json&lt;/code&gt; in your frontend React application, that is using react-scripts.&lt;/p&gt;
&lt;p&gt;React scripts automatically picks up &lt;code&gt;.env&lt;/code&gt; files and populates environment variables that start with &lt;code&gt;REACT_APP_&lt;/code&gt;. Thus, for runtime use a &lt;code&gt;.env&lt;/code&gt; file with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;REACT_APP_VERSION=$npm_package_version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This populates the version from the &lt;code&gt;npm_package_version&lt;/code&gt; environment variable on runtime which is set by the NodeJs environment. However, this does not always work if you are building in different environments.&lt;/p&gt;
&lt;p&gt;Therefore, for build time use a tool like &lt;code&gt;genversion&lt;/code&gt; in package.json:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;start&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react-scripts start&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;build&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;genversion src/version.gen.ts &amp;amp;&amp;amp; react-scripts build&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And include the version in your application for example via:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; environment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;VERSION&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;REACT_APP_VERSION&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; version&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ignore the generated version file in your &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With this approach the version gets populated if you start your react app through the &lt;code&gt;.env&lt;/code&gt; and you ensure the version on a build via the &lt;code&gt;genversion&lt;/code&gt; command.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Incremental User Interface Development</title>
    <link href="https://kleinprojects.com/incremental-user-interface-development/"/>
    <updated>2022-08-21T00:00:00Z</updated>
    <id>https://kleinprojects.com/incremental-user-interface-development/</id>
    <content type="html">&lt;p&gt;Developing a cool user interface can be really hard. It starts with the &amp;quot;what does the user &lt;em&gt;actually&lt;/em&gt; wants to do?&amp;quot;-challenge and involves questions like &amp;quot;where does does the user wants to access the application?&amp;quot;. The answer is often that neither you nor your user really knows what they want.&lt;/p&gt;
&lt;p&gt;Recently, I had to deal quite often with this &amp;quot;user interface challenge&amp;quot; and how to develop and effective approach to develop a cool app that solves exactly the problem a user is facing. Therefore, I tried to take a step back and observe my approach to frontend development.&lt;/p&gt;
&lt;p&gt;Initially, I observed, that I would spend tons of time looking and reading through new UI frameworks that would fit my need. And then there is a difference between the (1) &lt;em&gt;UI&lt;/em&gt; framework and the (2) &lt;em&gt;frontend framework&lt;/em&gt;. (1) is a library for the actual styling, like Bootstrap, Evergreen, Onsen UI, ... and (2) is a library that helps with the implementation of logic and state, like Angular, Vue, or React.&lt;/p&gt;
&lt;p&gt;So, for the &lt;em&gt;frontend framework&lt;/em&gt; I would just pick whatever which is mostly React right now. The challenge is (1), the &lt;em&gt;UI&lt;/em&gt; framework because there are sooo many different frameworks and lots of advantages and disadvantages. E.g. if you want to build a hybrid app you might use Onsen UI, if you use React components only you might want to use Evergreen, or Material UI, there are old frameworks that still persist like Bootstrap. Simply too many to evaluate all options before even designing.&lt;/p&gt;
&lt;p&gt;So how could I avoid spending all that time reading through pros and cons for individual UI frameworks? The answer is simple:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Do not use a UI framework :)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To be more precise:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Do not incorporate a UI framework if you are still exploring what your application should actually do.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What does this mean?&lt;/p&gt;
&lt;p&gt;It means starting with the most basic components that HTML has to offer. If you need an input, use &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;. If you need a card use a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; with some &lt;code&gt;border&lt;/code&gt; property set.&lt;/p&gt;
&lt;p&gt;With this approach I am able to push the UI framework decision to the future as long as possible and can rather ship working prototypes that I can quickly evaluate with end users to build a better product and eventually choose the UI framework that fits.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Using the Raspberry Pi as a Syncthing Server</title>
    <link href="https://kleinprojects.com/using-the-raspberry-pi-as-a-syncthing-server/"/>
    <updated>2022-05-08T00:00:00Z</updated>
    <id>https://kleinprojects.com/using-the-raspberry-pi-as-a-syncthing-server/</id>
    <content type="html">&lt;p&gt;Today I reworked my Obsidian infrastructure setup to use Syncthing (&lt;a rel=&quot;noopener&quot; class=&quot;external-link&quot; href=&quot;https://syncthing.net/&quot; target=&quot;_blank&quot;&gt;https://syncthing.net/&lt;/a&gt;) instead of GitHub so I have complete control over my data. In this post I would like to share my setup with you.&lt;/p&gt;
&lt;h2 data-heading=&quot;Raspberry Pi as Syncthing Main Server&quot;&gt;Raspberry Pi as Syncthing Main Server&lt;/h2&gt;
&lt;p&gt;&lt;/p&gt;&lt;div&gt;&lt;img style=&quot;max-width:400px !important; width:100%;&quot; class=&quot;excalidraw-svg&quot; src=&quot;https://kleinprojects.com/images/2022-05-08-2022-05-08%20Blog%20Post%20-%20Syncthing%20on%20Raspberry%20PI%202022-05-08%2018.03.53.excalidraw.md.svg&quot; /&gt;&lt;/div&gt;&lt;br /&gt;
The main idea is having the Raspberry Pi as the &lt;em&gt;main&lt;/em&gt; Syncthing server that is &lt;em&gt;always online&lt;/em&gt;. Through relay servers any other client is able to establish a connection to that main server. This is important as Syncthing would at least need two devices that it can sync files properly within real-time.&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Let&#39;s dive into the technical setup.&lt;/p&gt;
&lt;h2 data-heading=&quot;Setting up the SD Card&quot;&gt;Setting up the SD Card&lt;/h2&gt;
&lt;p&gt;Download Raspbian Imager from &lt;a rel=&quot;noopener&quot; class=&quot;external-link&quot; href=&quot;https://www.raspberrypi.com/software/&quot; target=&quot;_blank&quot;&gt;https://www.raspberrypi.com/software/&lt;/a&gt;.&lt;br /&gt;
That software writes the Raspbian OS onto your SD Card. Pick &lt;em&gt;Raspberry Lite (no Desktop)&lt;/em&gt; and flash your SD Card.&lt;/p&gt;
&lt;p&gt;To automatically connect to WIFI place a &lt;code&gt;wpa_supplicant.conf&lt;/code&gt; into the SD card root folder with the following content:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;country=de
update_config=1
ctrl_interface=/var/run/wpa_supplicant
&lt;p&gt;network={
scan_ssid=1
ssid=&amp;quot;WiFi SSID&amp;quot;
psk=&amp;quot;WiFi password&amp;quot;
}
&lt;/p&gt;&lt;/code&gt;&lt;button class=&quot;copy-code-button&quot;&gt;Copy&lt;/button&gt;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Additionally, place an empty &lt;code&gt;ssh&lt;/code&gt; file into the root. This triggers an SSH login on the first boot.&lt;/p&gt;
&lt;h2 data-heading=&quot;Connecting to the Raspberry PI&quot;&gt;Connecting to the Raspberry PI&lt;/h2&gt;
&lt;p&gt;Next, start the Raspberry with the prepared SD Card. It should automatically connect to your network.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;To find the IP you might use an IP Scanner like &lt;a aria-label-position=&quot;top&quot; aria-label=&quot;https://angryip.org/&quot; rel=&quot;noopener&quot; class=&quot;external-link&quot; href=&quot;https://angryip.org/&quot; target=&quot;_blank&quot;&gt;Angry IP Scanner&lt;/a&gt; or if you have access to your router you can just see listed devices there. If this is your only raspberry pi in the network try to connecting to &lt;code&gt;raspberrypi&lt;/code&gt; as ip address.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Connect to it via SSH:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-sh is-loaded&quot;&gt;ssh pi@&amp;#x3C;raspberry pi ip&gt;
&lt;/code&gt;&lt;button class=&quot;copy-code-button&quot;&gt;Copy&lt;/button&gt;&lt;/pre&gt;
&lt;p&gt;Username: &lt;code&gt;pi&lt;/code&gt;&lt;br /&gt;
Password: &lt;code&gt;raspberry&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you encounter troubles just connect to a display via HDMI and see what the logs show.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After the initial login change the default password to something safe! (or better: enable SSH key authentication only - I will not discuss this here)&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-sh is-loaded&quot;&gt;passwd
&lt;/code&gt;&lt;button class=&quot;copy-code-button&quot;&gt;Copy&lt;/button&gt;&lt;/pre&gt;
&lt;h2 data-heading=&quot;Installing Syncthing&quot;&gt;Installing Syncthing&lt;/h2&gt;
&lt;p&gt;Syncthing is available as a Debian package at &lt;a rel=&quot;noopener&quot; class=&quot;external-link&quot; href=&quot;https://apt.syncthing.net/&quot; target=&quot;_blank&quot;&gt;https://apt.syncthing.net/&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-sh is-loaded&quot;&gt;sudo curl -s -o /usr/share/keyrings/syncthing-archive-keyring.gpg https://syncthing.net/release-key.gpg
echo &quot;deb [signed-by=/usr/share/keyrings/syncthing-archive-keyring.gpg] https://apt.syncthing.net/ syncthing stable&quot; | sudo tee /etc/apt/sources.list.d/syncthing.list
sudo apt-get update
sudo apt-get install syncthing
&lt;/code&gt;&lt;button class=&quot;copy-code-button&quot;&gt;Copy&lt;/button&gt;&lt;/pre&gt;
&lt;p&gt;Run Syncthing:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-sh is-loaded&quot;&gt;syncthing
&lt;/code&gt;&lt;button class=&quot;copy-code-button&quot;&gt;Copy&lt;/button&gt;&lt;/pre&gt;
&lt;p&gt;And note your device Id&lt;br /&gt;
This will print starting logs including the Syncthing ID of the raspberry. The line looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
[XXXXX] 2022/05/08 16:17:15 INFO: My ID: XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX
...
&lt;/code&gt;&lt;button class=&quot;copy-code-button&quot;&gt;Copy&lt;/button&gt;&lt;/pre&gt;
&lt;h2 data-heading=&quot;Exposing the Syncthing Web Console&quot;&gt;Exposing the Syncthing Web Console&lt;/h2&gt;
&lt;p&gt;There is a CLI for Syncthing but it&#39;s much easier to maintain configurations via the web browser. Enable it in the &lt;code&gt;~/.config/syncthing/config.xml&lt;/code&gt; by changing the &lt;code&gt;address&lt;/code&gt; attribute to a &lt;code&gt;0.0.0.0&lt;/code&gt; value like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;gui enabled=&quot;true&quot; tls=&quot;true&quot;&gt;
 &amp;#x3C;address&gt;0.0.0.0:8384&amp;#x3C;/address&gt;
 &amp;#x3C;apikey&gt;xxxxx&amp;#x3C;/apikey&gt;
&amp;#x3C;/gui&gt;
&lt;/code&gt;&lt;button class=&quot;copy-code-button&quot;&gt;Copy&lt;/button&gt;&lt;/pre&gt;
&lt;p&gt;Then, restart syncthing with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;syncthing
&lt;/code&gt;&lt;button class=&quot;copy-code-button&quot;&gt;Copy&lt;/button&gt;&lt;/pre&gt;
&lt;p&gt;And access the web console at &lt;code&gt;https://&amp;#x3C;raspberry pi ip&gt;:8384&lt;/code&gt;.&lt;br /&gt;
Add a new user authentication here to avoid others within the same network of accessing your Syncthing settings. The web console will also prompt you to do this.&lt;/p&gt;
&lt;h3 data-heading=&quot;Starting Syncthing on Login&quot;&gt;Starting Syncthing on Login&lt;/h3&gt;
&lt;p&gt;To run Syncthing on startup (e.g. when the Raspberry Pi powers up), we can edit the &lt;code&gt;~/.bashrc&lt;/code&gt;file that gets executed on login of the &lt;code&gt;pi&lt;/code&gt; user:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-sh is-loaded&quot;&gt;vi ~/.bashrc
&lt;/code&gt;&lt;button class=&quot;copy-code-button&quot;&gt;Copy&lt;/button&gt;&lt;/pre&gt;
&lt;p&gt;and add the following line to start syncthing:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ...
# Run Syncthing in detached mode (notice the &amp;#x26;)
syncthing serve &amp;#x26;
&lt;/code&gt;&lt;button class=&quot;copy-code-button&quot;&gt;Copy&lt;/button&gt;&lt;/pre&gt;
&lt;p&gt;Note that if you login into the pi user the script will always execute. You can also configure a service to run syncthing with init.d or systemd if you want a cleaner execution. The mentioned approach is very simple and sufficient for my case.&lt;/p&gt;
&lt;h2 data-heading=&quot;Conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This guide walked you through the basic setup of Syncthing on a Raspberry Pi.&lt;/p&gt;
&lt;p&gt;You can now start your Raspberry Pi to automatically run Syncthing and access the web console to manage all devices and folders.&lt;/p&gt;
&lt;p&gt;Consider adding a backup mechanism to your setup to make it even more robust.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Scoping a Hobby Project - Personal Learnings</title>
    <link href="https://kleinprojects.com/scoping-a-hobby-project-personal-learnings/"/>
    <updated>2022-04-16T00:00:00Z</updated>
    <id>https://kleinprojects.com/scoping-a-hobby-project-personal-learnings/</id>
    <content type="html">&lt;p&gt;This post is meant to be a small self-reflection for a personal note taking application called Noteberry.&lt;/p&gt;
&lt;p&gt;Noteberry is a hobby project I have been working on during evenings or weekends for the past months (think I started 1.5 years ago in around December 2020). It should have been &lt;em&gt;&quot;a toolbox for working with block-based linked markdown notes&quot;&lt;/em&gt; (&lt;a aria-label-position=&quot;top&quot; aria-label=&quot;https://github.com/marcoklein/noteberry&quot; rel=&quot;noopener&quot; class=&quot;external-link&quot; href=&quot;https://github.com/marcoklein/noteberry&quot; target=&quot;_blank&quot;&gt;GitHub Repo&lt;/a&gt;). It was meant to be an application to deal with my local markdown notes.&lt;/p&gt;
&lt;p&gt;However, I escalated a little too much in scope and built first of all an editor with VIM support to support easy editing. Then I continued developing a parser for markdown with wikilinks (&lt;code&gt;[[wikilink]]&lt;/code&gt;) and so on... I realised, that there were already applications that would implement exactly the thing that I was trying to develop (like &lt;a aria-label-position=&quot;top&quot; aria-label=&quot;http://logseq.com&quot; rel=&quot;noopener&quot; class=&quot;external-link&quot; href=&quot;http://logseq.com/&quot; target=&quot;_blank&quot;&gt;LogSeq&lt;/a&gt;, &lt;a aria-label-position=&quot;top&quot; aria-label=&quot;http://obsidian.md&quot; rel=&quot;noopener&quot; class=&quot;external-link&quot; href=&quot;http://obsidian.md/&quot; target=&quot;_blank&quot;&gt;Obsidian&lt;/a&gt; or &lt;a aria-label-position=&quot;top&quot; aria-label=&quot;https://github.com/srid/neuron&quot; rel=&quot;noopener&quot; class=&quot;external-link&quot; href=&quot;https://github.com/srid/neuron&quot; target=&quot;_blank&quot;&gt;Neuron&lt;/a&gt;. And I also realised that they have either spent a very long time developing it or have a whole team behind it.&lt;/p&gt;
&lt;p&gt;Recreating any of these applications wasn&#39;t my first intention when starting the project. I just wanted to deal with my local markdown files. However, over the past couple of months scope was increasing unexpectedly because my goal for Noteberry was not clear.&lt;/p&gt;
&lt;p&gt;Despite of this, I learned a lot of technical things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Work with syntax parsing and build a parser with &lt;a aria-label-position=&quot;top&quot; aria-label=&quot;https://remark.js.org/&quot; rel=&quot;noopener&quot; class=&quot;external-link&quot; href=&quot;https://remark.js.org/&quot; target=&quot;_blank&quot;&gt;Remark&lt;/a&gt; to parse markdown files (published a small article about it &lt;a aria-label-position=&quot;top&quot; aria-label=&quot;https://kleinprojects.com/linking-markdown-notes-with-typescript/&quot; rel=&quot;noopener&quot; class=&quot;external-link&quot; href=&quot;https://kleinprojects.com/linking-markdown-notes-with-typescript/&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Develop an editor extension with &lt;a aria-label-position=&quot;top&quot; aria-label=&quot;https://codemirror.net/6/&quot; rel=&quot;noopener&quot; class=&quot;external-link&quot; href=&quot;https://codemirror.net/6/&quot; target=&quot;_blank&quot;&gt;CodeMirror 6&lt;/a&gt; to create a text editor with VIM support&lt;/li&gt;
&lt;li&gt;Setup a monorepo with &lt;a aria-label-position=&quot;top&quot; aria-label=&quot;https://lerna.js.org/&quot; rel=&quot;noopener&quot; class=&quot;external-link&quot; href=&quot;https://lerna.js.org/&quot; target=&quot;_blank&quot;&gt;Lerna&lt;/a&gt; to manage and publish all packages from one repository&lt;/li&gt;
&lt;li&gt;Setup a working CI/CD Flow with &lt;a aria-label-position=&quot;top&quot; aria-label=&quot;https://pages.github.com/&quot; rel=&quot;noopener&quot; class=&quot;external-link&quot; href=&quot;https://pages.github.com/&quot; target=&quot;_blank&quot;&gt;GitHub Pages&lt;/a&gt; to test publish without any more effort&lt;/li&gt;
&lt;li&gt;Learn how the new NodeJS &lt;a aria-label-position=&quot;top&quot; aria-label=&quot;https://nodejs.org/api/esm.html&quot; rel=&quot;noopener&quot; class=&quot;external-link&quot; href=&quot;https://nodejs.org/api/esm.html&quot; target=&quot;_blank&quot;&gt;Ecma Script Modules (ESM)&lt;/a&gt; work with &lt;a aria-label-position=&quot;top&quot; aria-label=&quot;https://www.typescriptlang.org/&quot; rel=&quot;noopener&quot; class=&quot;external-link&quot; href=&quot;https://www.typescriptlang.org/&quot; target=&quot;_blank&quot;&gt;TypeScript&lt;/a&gt; to write modern and fast code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nonetheless, I realised that I have not produced something that I can actually &lt;strong&gt;use&lt;/strong&gt;. I have rather deepened my knowledge about the above topics.&lt;/p&gt;
&lt;p&gt;For my future projects I am taking this question with me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What is the problem that I am trying to solve?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And then will not directly solve it but rather look for existing solutions:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What solutions already exist to solve it?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I was neither aware of Obsidian nor LogSeq when starting out but the thing that I was trying to built was just too large to solve it with a one person show in a timely manner, so:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Can I produce results in a timely manner?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With these questions I should have a good foundation for scoping future (hobby) projects.&lt;/p&gt;
&lt;p&gt;Nonetheless, development and implementing stuff should also be fun and I will not only create projects to solve specific issues. I will also keep that in mind :)&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>How Daily Notes Work in Networked Note Taking</title>
    <link href="https://kleinprojects.com/how-daily-notes-work-in-networked-note-taking/"/>
    <updated>2022-04-09T00:00:00Z</updated>
    <id>https://kleinprojects.com/how-daily-notes-work-in-networked-note-taking/</id>
    <content type="html">&lt;p&gt;A daily note makes it simple to track thoughts, meetings, and events during a specific day.&lt;br /&gt;
If you create a link for a &lt;em&gt;topic&lt;/em&gt; that you mention in multiple dailies you will get time based relationships like the follow picture illustrates:&lt;br /&gt;
&lt;/p&gt;&lt;div&gt;&lt;img style=&quot;max-width:300px !important; width:100%;&quot; class=&quot;excalidraw-svg&quot; src=&quot;https://kleinprojects.com/images/2022-04-09-How%20Daily%20Notes%20work%20in%20a%20networked%20note%20taking%20tool%202022-04-07%2021.29.24.excalidraw.md.svg&quot; /&gt;&lt;/div&gt;&lt;br /&gt;
&lt;em&gt;Note that in this case there is a daily note for each day and one topic note that would get referenced with a &lt;code&gt;[[Topic]]&lt;/code&gt; link.&lt;/em&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;However, there will not be only one topic that you write down during a day but you will write notes regarding various topics like the following illustration depicts:&lt;br /&gt;
&lt;/p&gt;&lt;div&gt;&lt;img style=&quot;max-width:300px !important; width:100%;&quot; class=&quot;excalidraw-svg&quot; src=&quot;https://kleinprojects.com/images/2022-04-09-How%20Daily%20Notes%20work%20in%20a%20networked%20note%20taking%20tool%202022-04-07%2022.13.36.excalidraw.md.svg&quot; /&gt;&lt;/div&gt;&lt;br /&gt;
This means: all three days mention Topic A, the 4th and the 5th link Topic B, and the 5th and 6th reference Topic C.&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Let&#39;s see how this approach looks from different angles.&lt;br /&gt;
If we select Topic B we would see this:&lt;br /&gt;
&lt;/p&gt;&lt;div&gt;&lt;img style=&quot;max-width:200px !important; width:100%;&quot; class=&quot;excalidraw-svg&quot; src=&quot;https://kleinprojects.com/images/2022-04-09-How%20Daily%20Notes%20work%20in%20a%20networked%20note%20taking%20tool%202022-04-07%2022.29.20.excalidraw.md.svg&quot; /&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Selecting the 4th of April would show something like this:&lt;br /&gt;
&lt;/p&gt;&lt;div&gt;&lt;img style=&quot;max-width:200px !important; width:100%;&quot; class=&quot;excalidraw-svg&quot; src=&quot;https://kleinprojects.com/images/2022-04-09-How%20Daily%20Notes%20work%20in%20a%20networked%20note%20taking%20tool%202022-04-07%2022.31.50.excalidraw.md.svg&quot; /&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;In a conclusion, with a daily you can keep track of your information flow over time. Specific topics evolve over time which is inherently depicted by daily notes that reference and contribute to new topics.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Linking Markdown Notes with TypeScript</title>
    <link href="https://kleinprojects.com/linking-markdown-notes-with-typescript/"/>
    <updated>2021-12-19T00:00:00Z</updated>
    <id>https://kleinprojects.com/linking-markdown-notes-with-typescript/</id>
    <content type="html">&lt;p&gt;Linked note taking brings the advantage of connecting knowledge in an intuitive, connected matter as opposed to traditional, section-based note taking approaches. This approach gained traction through new networked note taking applications like &lt;a href=&quot;https://roamresearch.com/&quot;&gt;RoamResearch&lt;/a&gt;, &lt;a href=&quot;https://obsidian.md/&quot;&gt;Obsidian&lt;/a&gt;, and &lt;a href=&quot;https://logseq.com/&quot;&gt;Logseq&lt;/a&gt;. They pursue the concept of &lt;em&gt;linked&lt;/em&gt; notes - that means that instead of dividing notes into sections, they are all in a flat directory and define structure by relations to other notes.&lt;/p&gt;
&lt;p&gt;In this post we are using Markdown to stick to an easily editable text format without vendor lock-in. We could even drop these plain files into a Git folder for effortless backups and versioning. For linking markdown notes we are going to use the &lt;a href=&quot;https://en.wikipedia.org/wiki/Help:Link&quot;&gt;widely adopted Wikilink format&lt;/a&gt; (e.g. &lt;code&gt;[[wikilink]]&lt;/code&gt;). These connect arbitrary documents without the need of specifying the exact location.&lt;/p&gt;
&lt;p&gt;I did not find too many resources and guides on how you could implement algorithms by yourself to interlink Markdown notes with the power of &lt;code&gt;[[wikilinks]]&lt;/code&gt;. That is why this post dives into an implementation to&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://kleinprojects.com/linking-markdown-notes-with-typescript/#setup&quot;&gt;Setup a base project&lt;/a&gt; (&lt;a href=&quot;https://github.com/marcoklein/linked-markdown-notes&quot;&gt;Source on GitHub&lt;/a&gt;) for the implementation&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kleinprojects.com/linking-markdown-notes-with-typescript/#reading-markdown-files&quot;&gt;Read markdown files&lt;/a&gt; from your folder to have data to analyze&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kleinprojects.com/linking-markdown-notes-with-typescript/#parsing-markdown-files&quot;&gt;Parse the files&lt;/a&gt; to find all wikilinks for further mapping&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kleinprojects.com/linking-markdown-notes-with-typescript/#extracting-document-titles&quot;&gt;Extract titles&lt;/a&gt; to that we can map our links&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kleinprojects.com/linking-markdown-notes-with-typescript/#building-master-mappings&quot;&gt;Build a master mapping table&lt;/a&gt; to quickly interlink our notes&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kleinprojects.com/linking-markdown-notes-with-typescript/#mapping-links-to-document-paths&quot;&gt;Map links to document paths&lt;/a&gt; for an example on using our master mappings&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I assume knowledge of TypeScript but it is not necessary in order to grasp the concept and process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Objective: Building a basis to link any markdown notes through a mapping data structure that gives you direct access to &lt;em&gt;path&lt;/em&gt;, &lt;em&gt;title&lt;/em&gt;, and &lt;em&gt;links&lt;/em&gt; of documents.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;setup&quot; tabindex=&quot;-1&quot;&gt;Setup&lt;/h2&gt;
&lt;p&gt;At first, we are going to setup our environment and base project. Ensure you have a recent installation of &lt;a href=&quot;https://nodejs.org/&quot;&gt;Node.js&lt;/a&gt;. If not head over to their &lt;a href=&quot;https://nodejs.org/en/download/&quot;&gt;download page&lt;/a&gt; grab the latest version and install it.
We use the package manager &lt;a href=&quot;https://yarnpkg.com/&quot;&gt;yarn&lt;/a&gt; for dependency management. However, you could also install everything via &lt;a href=&quot;https://www.npmjs.com/&quot;&gt;npm&lt;/a&gt; if preferred. So, go ahead and install yarn.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--global&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s create a new folder for our project and initialize an empty yarn project with default options.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You could also find all code on &lt;a href=&quot;https://github.com/marcoklein/linked-markdown-notes&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; linked-markdown-notes
&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; linked-markdown-notes
&lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt; init &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add required dependencies for running the project.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--dev&lt;/span&gt; typescript ts-node @types/node @types/fs-extra
&lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; remark-parse remark-wiki-link unist-util-visit-parents&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We are using &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/esm-node.html&quot;&gt;ECMAScript Modules (ESM)&lt;/a&gt;, therefore add the &lt;code&gt;&amp;quot;type&amp;quot;: &amp;quot;module&amp;quot;&lt;/code&gt; option to your &lt;code&gt;package.json&lt;/code&gt; and create a &lt;code&gt;start&lt;/code&gt; script for running our project:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;linked-markdown-notes&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;index.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;license&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;MIT&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;module&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;start&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;node --loader ts-node/esm src/index.js&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a new &lt;code&gt;tsconfig.json&lt;/code&gt; file for our &lt;a href=&quot;https://www.typescriptlang.org/&quot;&gt;TypeScript&lt;/a&gt; configuration:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;compilerOptions&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;target&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ESNext&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;module&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ESNext&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;moduleResolution&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;node&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;esModuleInterop&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;forceConsistentCasingInFileNames&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;strict&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;skipLibCheck&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;paths&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;remark-wiki-link&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;./typings/remark-wiki-link.d.ts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As the code base of &lt;code&gt;remark-wiki-link&lt;/code&gt; is JavaScript and the package ships no types we have to create a new type definition file and reference it in the &lt;code&gt;tsconfig.json&lt;/code&gt;.
For this we just create a new file under &lt;code&gt;typings/remark-wiki-link.d.ts&lt;/code&gt; with the content&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;remark-wiki-link&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Either use an already existing folder with markdown files or create a new &lt;code&gt;test/resources&lt;/code&gt; folder that contains some sample markdown files. In this post, all examples and code will work with the latter one. If you want to follow it, the create 4 files in that folder:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;test/resources/Dog.md&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Dog
There is a dog.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;test/resources/Cat.md&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Cat
Cat plays with [[Dog]].
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;test/resources/Farm.md&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Farm
Farm has a [[Dog]], a [[Horse]], and a [[Cow]].
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;test/resources/Home.md&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Home
Home has a [[Cat]] and [[Dog]].
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The content of the files make no real sense, but we will need these examples as a basis to test the &lt;code&gt;[[wikilinks]]&lt;/code&gt;.
Eventually, our folder structure should look this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;test/
  resources/
    Dog.md
    Cat.md
    Home.md
    Farm.md
typings/
  remark-wiki-link.d.ts
package.json
tsconfig.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;With our basic setup we are now ready to dive into the actual implementation.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;reading-markdown-files&quot; tabindex=&quot;-1&quot;&gt;Reading Markdown Files&lt;/h2&gt;
&lt;p&gt;Let&#39;s write some code for reading our files that we want to link. For that create a new file under &lt;code&gt;src/index.ts&lt;/code&gt; as application entry. Add the following function to read markdown files of a specific directory:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; fs &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;node:fs/promises&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; path &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;path&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FileSystemDocument&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  path&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  content&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/**
 * Reads all markdown files of a directory.
 *
 * @param directoryPath Directory to read.
 * @returns Path and content of markdown files within the directory.
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readMarkdownFiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  directoryPath&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;FileSystemDocument&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// read files in folder&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; dirents &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readdir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;directoryPath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; withFileTypes&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// filter files from folder&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; files &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; dirents
    &lt;span class=&quot;token comment&quot;&gt;// only consider files that have a markdown extension&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dirent &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; dirent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;extname&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dirent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.md&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// return path of file&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dirent &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;directoryPath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dirent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// read file contents&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; filesWithContentPromises &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; files&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filePath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;FileSystemDocument&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      path&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; filePath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// read content of file&lt;/span&gt;
      content&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filePath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;utf-8&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// resolve asynchronous file loading&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filesWithContentPromises&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// call function with our test repository&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; files &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readMarkdownFiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./test/resources&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// prints results&lt;/span&gt;
files&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; path &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Read file path: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;path&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run the code snippet and examine its result:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt; start&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It should have printed a list of markdown files in your &lt;code&gt;test/resources&lt;/code&gt; folder:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Read &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; path: test/resources/Cat.md
Read &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; path: test/resources/Dog.md
Read &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; path: test/resources/Farm.md
Read &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; path: test/resources/Home.md&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Nice. We are set with our files. Now we can really start with file parsing and linking...&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;parsing-markdown-files&quot; tabindex=&quot;-1&quot;&gt;Parsing Markdown Files&lt;/h2&gt;
&lt;p&gt;As a second step we have to find all wikilink occurrences that we can use for lining our files. Therefore, we are going to parse the Markdown content and analyze the syntax tree.&lt;/p&gt;
&lt;p&gt;We use the library &lt;a href=&quot;https://github.com/remarkjs/remark/tree/main/packages/remark-parse&quot;&gt;remark-parse&lt;/a&gt; for markdown parsing with the &lt;a href=&quot;https://github.com/landakram/remark-wiki-link&quot;&gt;remark-wiki-link&lt;/a&gt; extension for parsing wikilinks of the format &lt;code&gt;[[...]]&lt;/code&gt;. We start with the creation of a &lt;code&gt;WikiLinkNode&lt;/code&gt; type that we need for the &lt;code&gt;remark-wiki-plugin&lt;/code&gt;. Afterwards, we we create a &lt;code&gt;SyntaxTree&lt;/code&gt; type we can use as the root type of our parser. Additionally, let us import &lt;code&gt;visit&lt;/code&gt; - an utility to traverse the syntax tree - see &lt;a href=&quot;https://unifiedjs.com/learn/recipe/tree-traversal-typescript/&quot;&gt;tree traversal article in TypeScript&lt;/a&gt; for more information about that.&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ... other imports&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;MDAST&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;mdast&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;UNIST&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;unist&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// gonna need visit for our next function&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; visit &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;unist-util-visit&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WikiLinkNode&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;UNIST&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Node &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;wikiLink&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  value&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    alias&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    permalink&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SyntaxTree&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mdast&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Root &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; WikiLinkNode&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/syntax-tree/unist&quot;&gt;Unist&lt;/a&gt; and &lt;a href=&quot;https://github.com/syntax-tree/mdast&quot;&gt;mdast&lt;/a&gt; are syntax tree structures, which &lt;code&gt;remark-parse&lt;/code&gt; uses for parsing markdown.
In the next step, we add two more functions to parse all document links and build a mapping table from all documents and links. Let&#39;s start with a function to parse wikilinks from our syntax tree. For this we are going to use the &lt;code&gt;visit&lt;/code&gt; utility we imported in the previous snippet:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parseDocumentLinks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parsedDocumentContent&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SyntaxTree&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; documentLinks&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;visit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parsedDocumentContent&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;wikiLink&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; wikiLinkNode &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; wikiLinkNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; alias &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; wikiLinkNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;alias&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// if a link contains a &quot;:&quot; the remark-wiki-link plugin would parse it as an alias&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// as we are not using the alias feature we re-construct the full link content&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// LIMITATION: this approach does not recognize links that have the same name and alias&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// e.g. name:name - keep that in mind when using this snippet&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; linkName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;wikiLinkNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;
      value &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; alias &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;:&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; alias &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;
    &lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    documentLinks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;linkName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; documentLinks&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The function returns an array of wikilinks that it finds in the syntax tree. Now, we can add our function for building the document to map links:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildDocumentsLinkMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;files&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; FileSystemDocument&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// call the unified parser&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; documentParser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;unified&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;remarkParse&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;remarkWikiLink&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// end result&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; documentLinksMap&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  files&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; content &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// parse each content and put its links into the result map&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; parsedDocumentContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; documentParser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    documentLinksMap&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parseDocumentLinks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parsedDocumentContent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; documentLinksMap&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// print the result&lt;/span&gt;
&lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Document links map: &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildDocumentsLinkMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;files&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running our program will now yield something like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Read &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; path: test/resources/Cat.md
Read &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; path: test/resources/Dog.md
Read &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; path: test/resources/Farm.md
Read &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; path: test/resources/Home.md
Documents links map: &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Cat.md&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Dog&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
  &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Dog.md&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
  &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Farm.md&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Dog&#39;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&#39;Horse&#39;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&#39;Cow&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
  &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Home.md&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Cat&#39;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&#39;Dog&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Voilà, we successfully parsed our markdown files, extracted links, and created a mapping table. With this mapping table we can easily match documents and links. The next sections discuss how we can implement that mapping.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;extracting-document-titles&quot; tabindex=&quot;-1&quot;&gt;Extracting Document Titles&lt;/h2&gt;
&lt;p&gt;Our links have to map against some sort of name or title. In our case, we are using the document name of each file as the matching target (you could also use e.g. the first heading in you markdown file). It is only important to create a map of titles that the &lt;code&gt;documentsLinksMap&lt;/code&gt; can use for linking.
Therefore, we add a new function for extracting document titles:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildDocumentsTitleMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;files&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; FileSystemDocument&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; documentTitleMap&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  files&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; path &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// extract document title&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; title &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;([^&#92;/]+)&#92;.md$&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// verify a consistent mapping&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Could not extract title from path: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;path&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;documentTitleMap&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Title &quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;title&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot; already exists for path &quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;documentTitleMap&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    documentTitleMap&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; documentTitleMap&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Documents title map: &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildDocumentsTitleMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;files&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The regex &lt;code&gt;([^&#92;/]+)&#92;.md$&lt;/code&gt; returns the title of a document and the first matching group contains the title. Additionally, the function contains two additional checks to guarantee uniqueness of titles.
A run with &lt;code&gt;yarn start&lt;/code&gt; now prints:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Read &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; path: test/resources/Cat.md
Read &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; path: test/resources/Dog.md
Read &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; path: test/resources/Farm.md
Read &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; path: test/resources/Home.md
Documents links map:  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Cat.md&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Dog&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
  &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Dog.md&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
  &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Farm.md&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Dog&#39;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&#39;Horse&#39;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&#39;Cow&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
  &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Home.md&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Cat&#39;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&#39;Dog&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
Documents title map:  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  Cat: &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Cat.md&#39;&lt;/span&gt;,
  Dog: &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Dog.md&#39;&lt;/span&gt;,
  Farm: &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Farm.md&#39;&lt;/span&gt;,
  Home: &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Home.md&#39;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Well done, with the &lt;code&gt;documentsLinksMap&lt;/code&gt; and the &lt;code&gt;documentsTitleMap&lt;/code&gt; we can now interlink our markdown files.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;building-master-mappings&quot; tabindex=&quot;-1&quot;&gt;Building Master Mappings&lt;/h2&gt;
&lt;p&gt;You might already wonder how we could actually visualize all the links by using the mapping tables. Thus, for our final mapping we create mappings based on our two original mapping tables and combine them all! They will enable any future algorithms to directly find mappings for links.
Let us add a function that creates our master mapping tables:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildMasterMappings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  documentTitleToPathMap&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  documentPathToLinksMap&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;path&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// create interim mapping tables to easily map entries&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; documentPathToTitleMap&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;path&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;documentTitleToPathMap&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;documentPathToTitleMap&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; title&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; documentTitleToLinksMap&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;documentPathToLinksMap&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; links&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// map document title to links&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;documentTitleToLinksMap&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;documentPathToTitleMap&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; links&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// return all mappings&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    documentTitleToPathMap&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    documentPathToLinksMap&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    documentPathToTitleMap&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    documentTitleToLinksMap&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&#39;Master mappings: &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;buildMasterMappings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;buildDocumentsTitleMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;files&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;buildDocumentsLinkMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;files&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A re-run of our project with &lt;code&gt;yarn start&lt;/code&gt; will now yield&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output from above&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
Master mappings:  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  documentTitleToPathMap: &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    Cat: &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Cat.md&#39;&lt;/span&gt;,
    Dog: &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Dog.md&#39;&lt;/span&gt;,
    Farm: &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Farm.md&#39;&lt;/span&gt;,
    Home: &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Home.md&#39;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,
  documentPathToLinksMap: &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Cat.md&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Dog&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
    &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Dog.md&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
    &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Farm.md&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Dog&#39;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&#39;Horse&#39;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&#39;Cow&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
    &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Home.md&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Cat&#39;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&#39;Dog&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,
  documentPathToTitleMap: &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Cat.md&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Cat&#39;&lt;/span&gt;,
    &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Dog.md&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Dog&#39;&lt;/span&gt;,
    &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Farm.md&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Farm&#39;&lt;/span&gt;,
    &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Home.md&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Home&#39;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,
  documentTitleToLinksMap: &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    Cat: &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Dog&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
    Dog: &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
    Farm: &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Dog&#39;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&#39;Horse&#39;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&#39;Cow&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
    Home: &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Cat&#39;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&#39;Dog&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Do You see where this is getting? Hence, we got mappings into all directions with all document titles, paths, and links. Now we just have to use it.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;mapping-links-to-document-paths&quot; tabindex=&quot;-1&quot;&gt;Mapping Links to Document Paths&lt;/h2&gt;
&lt;p&gt;We could utilize these master mappings in various use cases. To look into one in particular, we implement a function to map links to the respective file path:&lt;/p&gt;
&lt;p&gt;(Note, that some links don&#39;t have a matching target document - our function considers this case as well)&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// we build a global master mapping table&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// so we can reuse it later&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; globalMasterMapping &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildMasterMappings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;buildDocumentsTitleMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;files&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;buildDocumentsLinkMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;files&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findDocumentPathOfLink&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  masterMapping&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ReturnType&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; buildMasterMappings&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  linkName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; linkPath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; masterMapping&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;documentTitleToPathMap&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;linkName&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// return true, if the path exists&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// if false, there is no document for the link name&lt;/span&gt;
    existing&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; linkPath &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    path&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; linkPath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eventually, let us print some results of that function:&lt;/p&gt;
&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&#39;Path for link Home: &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;findDocumentPathOfLink&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;globalMasterMapping&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Home&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&#39;Path for link Farm: &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;findDocumentPathOfLink&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;globalMasterMapping&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Farm&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&#39;Path for link Farm: &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;findDocumentPathOfLink&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;globalMasterMapping&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Cow&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A run with &lt;code&gt;yarn start&lt;/code&gt; now logs:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Path &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;link&lt;/span&gt; Home:  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; existing: true, path: &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Home.md&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
Path &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;link&lt;/span&gt; Farm:  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; existing: true, path: &lt;span class=&quot;token string&quot;&gt;&#39;test/resources/Farm.md&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
Path &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;link&lt;/span&gt; Farm:  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; existing: false, path: undefined &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;This case shows us how easy it is to utilize the master mapping to quickly find paths for linked notes.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;limitations-and-outlook&quot; tabindex=&quot;-1&quot;&gt;Limitations and Outlook&lt;/h2&gt;
&lt;p&gt;This post covered a basic implementation for processing linked Markdown notes. Feel free to take it as an inspiration and let me know if you do something cool with it.&lt;/p&gt;
&lt;p&gt;Finally, I want to close with some questions worth asking when looking at your map of notes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Are all notes connected with each other, or are there outsiders? This might be an indication for lost notes.&lt;/li&gt;
&lt;li&gt;What links do not have a mapping target? This would be links pointing to non-existent documents.&lt;/li&gt;
&lt;li&gt;Which links point to a specific document (also known as backlinks)?&lt;/li&gt;
&lt;li&gt;What notes are the most important ones? E.g. you could sort with the &lt;a href=&quot;https://en.wikipedia.org/wiki/PageRank&quot;&gt;PageRank Algorithm&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Enjoy working with your set of networked notes.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers, Marco&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Find code for this article on &lt;a href=&quot;https://github.com/marcoklein/linked-markdown-notes&quot;&gt;GitHub&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Linking markdown files for better note taking</title>
    <link href="https://kleinprojects.com/linking-markdown-files-for-better-note-taking/"/>
    <updated>2021-11-28T00:00:00Z</updated>
    <id>https://kleinprojects.com/linking-markdown-files-for-better-note-taking/</id>
    <content type="html">&lt;p&gt;Currently, I am investigating approaches to work with linked notes - markdown notes in particular.
Linking notes is not a new concept but I found no application that meets my requirements to 100%. However, some applications start picking up these concepts like &lt;a href=&quot;https://obsidian.md/&quot;&gt;Obsidian&lt;/a&gt; or &lt;a href=&quot;https://roamresearch.com/&quot;&gt;Roam Research&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Inspirations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RoamResearch&lt;/li&gt;
&lt;li&gt;[[Obsidian]] &lt;a href=&quot;https://obsidian.md/&quot;&gt;https://obsidian.md/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Jermolene/TiddlyWiki5&quot;&gt;https://github.com/Jermolene/TiddlyWiki5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[[Typora Note Taking App]] &lt;a href=&quot;https://typora.io/&quot;&gt;https://typora.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[[Standard Notes]] &lt;a href=&quot;https://standardnotes.com/features&quot;&gt;https://standardnotes.com/features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Markdown backlink janitor &lt;a href=&quot;https://github.com/andymatuschak/note-link-janitor&quot;&gt;https://github.com/andymatuschak/note-link-janitor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  
  <entry>
    <title>Resolve tag clobber</title>
    <link href="https://kleinprojects.com/resolve-tag-clobber/"/>
    <updated>2021-02-08T00:00:00Z</updated>
    <id>https://kleinprojects.com/resolve-tag-clobber/</id>
    <content type="html">&lt;p&gt;If you got conflicting tags force pull the latest tags from your remote.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; pull &lt;span class=&quot;token parameter variable&quot;&gt;--tags&lt;/span&gt; origin develop
From github.com:name/repo
 * branch            develop    -&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; FETCH_HEAD
 &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;rejected&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;        &lt;span class=&quot;token number&quot;&gt;0.1&lt;/span&gt;.177    -&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.1&lt;/span&gt;.177  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;would clobber existing tag&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;rejected&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;        &lt;span class=&quot;token number&quot;&gt;0.1&lt;/span&gt;.179    -&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.1&lt;/span&gt;.179  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;would clobber existing tag&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Resolve with&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; fetch &lt;span class=&quot;token parameter variable&quot;&gt;--tags&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>Linux Cheat Sheet</title>
    <link href="https://kleinprojects.com/linux-cheat-sheet/"/>
    <updated>2021-01-12T00:00:00Z</updated>
    <id>https://kleinprojects.com/linux-cheat-sheet/</id>
    <content type="html">&lt;h2 id=&quot;check-kernel-version&quot; tabindex=&quot;-1&quot;&gt;Check Kernel Version&lt;/h2&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;uname&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-a&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;check-ip&quot; tabindex=&quot;-1&quot;&gt;Check IP&lt;/h2&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;ifconfig&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;ip&lt;/span&gt; addr show&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;check-disk-space&quot; tabindex=&quot;-1&quot;&gt;Check disk space&lt;/h2&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;df&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-ah&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;work-with-services&quot; tabindex=&quot;-1&quot;&gt;Work with services&lt;/h2&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;service-name&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; status
systemctl status &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;service-name&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;check-folder-size&quot; tabindex=&quot;-1&quot;&gt;Check folder size&lt;/h2&gt;
&lt;p&gt;Disk use&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;du&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-sh&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;directory&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;check-for-open-ports&quot; tabindex=&quot;-1&quot;&gt;Check for open ports&lt;/h2&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;netstat&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;check-cpu-usage&quot; tabindex=&quot;-1&quot;&gt;Check CPU usage&lt;/h2&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;ps&lt;/span&gt; aux &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;process-name&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;top&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;mounting&quot; tabindex=&quot;-1&quot;&gt;Mounting&lt;/h2&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;mount&lt;/span&gt; /dev/sda2 /mnt&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check for existing&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;mounts&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Auto mount in file&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;/etc/fstab&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;manpages&quot; tabindex=&quot;-1&quot;&gt;Manpages&lt;/h2&gt;
&lt;p&gt;Manual pages&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;man&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;command&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;permissions&quot; tabindex=&quot;-1&quot;&gt;Permissions&lt;/h2&gt;
&lt;p&gt;Show permissions&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;stat&lt;/span&gt; file.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change permissions&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# denies file read for others&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;chmod&lt;/span&gt; o-r file.txt
&lt;span class=&quot;token comment&quot;&gt;# read for user, read for group, deny access to others&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;chmod&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;r,g&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;r,o&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; file.txt&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;file-transformation&quot; tabindex=&quot;-1&quot;&gt;File Transformation&lt;/h2&gt;
&lt;p&gt;Use &lt;code&gt;jq&lt;/code&gt; to transform JSON files.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; logs.json &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; jq &lt;span class=&quot;token string&quot;&gt;&#39;.[&quot;@timestamp&quot;] + &quot;   &quot; + .message&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; logs.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Transforms extracts timestamp and message from JSON objects and prints them into logs.txt.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Practical Vim Command Cheat Sheet</title>
    <link href="https://kleinprojects.com/practical-vim-command-cheat-sheet/"/>
    <updated>2021-01-10T00:00:00Z</updated>
    <id>https://kleinprojects.com/practical-vim-command-cheat-sheet/</id>
    <content type="html">&lt;h3 id=&quot;surround-with-brackets&quot; tabindex=&quot;-1&quot;&gt;Surround with brackets&lt;/h3&gt;
&lt;p&gt;Select with &lt;code&gt;visual mode&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Use&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;c()&amp;lt;Esc&amp;gt;P
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;visual-mode&quot; tabindex=&quot;-1&quot;&gt;Visual mode&lt;/h2&gt;
&lt;p&gt;Visual mode for lines (Visual Line mode)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;V
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Visual mode for characters&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;v
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;leave-insert-mode&quot; tabindex=&quot;-1&quot;&gt;Leave insert mode&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Ctrl-C or ESC
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;more-movements&quot; tabindex=&quot;-1&quot;&gt;More movements&lt;/h1&gt;
&lt;p&gt;Insert line above&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;O
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Insert line below&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;o
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Paste above&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;P
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Paste below&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;p
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Search&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hop through results: &lt;code&gt;n&lt;/code&gt; reverse: &lt;code&gt;N&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Go to next occurrence: &lt;code&gt;*&lt;/code&gt; reverse: &lt;code&gt;#&lt;/code&gt;&lt;/p&gt;
&lt;h1 id=&quot;horizontal-movement&quot; tabindex=&quot;-1&quot;&gt;Horizontal Movement&lt;/h1&gt;
&lt;p&gt;Find and select character&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;f / F &amp;lt;character&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Find and not select character&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;t / T &amp;lt;character&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example combo with d&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;df &amp;lt;character&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Delete until &lt;code&gt;&amp;lt;character&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&quot;delete-and-insert&quot; tabindex=&quot;-1&quot;&gt;Delete and insert&lt;/h1&gt;
&lt;p&gt;Delete character&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;x / X
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Delete character and enter insert mode&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Delete line and enter insert mode&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;S
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;vertical-movement&quot; tabindex=&quot;-1&quot;&gt;Vertical Movement&lt;/h1&gt;
&lt;p&gt;Move to top&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;H
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Move to bottom&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;L
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Move to middle&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;M
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;search-and-replace&quot; tabindex=&quot;-1&quot;&gt;Search and replace&lt;/h1&gt;
&lt;p&gt;Search for all occurrences of foo and replace with bar after confirmation&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;:%s/foo/bar/gc&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;navigation&quot; tabindex=&quot;-1&quot;&gt;Navigation&lt;/h1&gt;
&lt;p&gt;Open finder&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:e &amp;lt;folder&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Jump between two last buffers&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Ctrl + ^
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Set mark with&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;m &amp;lt;letter&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Set global mark with&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;m &amp;lt;Shift + letter&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;splits&quot; tabindex=&quot;-1&quot;&gt;Splits&lt;/h1&gt;
&lt;p&gt;All under &lt;code&gt;Ctrl + W&lt;/code&gt; are window controls.&lt;/p&gt;
&lt;p&gt;Vertical&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Ctrl + W v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Horizontal&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Ctrl + W s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Equal split&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Ctrl + W =
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Resize horizontally&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:resize 10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Resize vertically&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:vertical resize 20
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>Vim for Firefox</title>
    <link href="https://kleinprojects.com/vim-for-firefox/"/>
    <updated>2020-12-07T00:00:00Z</updated>
    <id>https://kleinprojects.com/vim-for-firefox/</id>
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://github.com/tridactyl/tridactyl&quot;&gt;Tridactyl&lt;/a&gt; is a Firefox extension to enable vim key bindings for a more efficient browsing experience.&lt;/p&gt;
&lt;p&gt;Go back to tutor&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:tutor
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;modes&quot; tabindex=&quot;-1&quot;&gt;Modes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Normal mode&lt;/li&gt;
&lt;li&gt;Hint mode
&lt;ul&gt;
&lt;li&gt;Enter with &lt;code&gt;f&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Visual mode
&lt;ul&gt;
&lt;li&gt;Enter with &lt;code&gt;v&lt;/code&gt;, &lt;code&gt;;h&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Search via &lt;code&gt;s&lt;/code&gt;, &lt;code&gt;S&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Exit via &lt;code&gt;Esc&lt;/code&gt;, &lt;code&gt;&amp;lt;Ctr-[&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Command mode
&lt;ul&gt;
&lt;li&gt;Enter with &lt;code&gt;:&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Exit with &lt;code&gt;Esc&lt;/code&gt;, &lt;code&gt;Enter&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Ignore mode
&lt;ul&gt;
&lt;li&gt;Pass through key events&lt;/li&gt;
&lt;li&gt;Toggle with &lt;code&gt;Shift-Insert&lt;/code&gt;, &lt;code&gt;Ctrl-Alt-Escape&lt;/code&gt;, &lt;code&gt;Ctrl-Alt-Backtick&lt;/code&gt;, &lt;code&gt;Shift-Esc&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;normal-mode&quot; tabindex=&quot;-1&quot;&gt;Normal mode&lt;/h3&gt;
&lt;p&gt;Scroll with &lt;code&gt;h&lt;/code&gt;, &lt;code&gt;j&lt;/code&gt;, &lt;code&gt;k&lt;/code&gt;, &lt;code&gt;l&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Go to top with &lt;code&gt;gg&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Got to bottom with &lt;code&gt;G&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Go back in history with &lt;code&gt;H&lt;/code&gt; and forward with &lt;code&gt;L&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;.&lt;/code&gt; to repeat the last action.&lt;/p&gt;
&lt;p&gt;Open new tab with &lt;code&gt;t&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Open hinting mode to open tab in background use &lt;code&gt;F&lt;/code&gt; (use &lt;code&gt;f&lt;/code&gt; to open it in same window).&lt;/p&gt;
&lt;p&gt;Focus first textbox on page with &lt;code&gt;gi&lt;/code&gt;. Switch textboxes with &lt;code&gt;tab&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;useful-commands&quot; tabindex=&quot;-1&quot;&gt;Useful commands&lt;/h4&gt;
&lt;p&gt;Bring up list of tabs with &lt;code&gt;b&lt;/code&gt;. Use &lt;code&gt;Tab&lt;/code&gt;, &lt;code&gt;Shift-Tab&lt;/code&gt; to cycle through them.&lt;/p&gt;
&lt;p&gt;Open web pages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;w&lt;/code&gt; open in new window&lt;/li&gt;
&lt;li&gt;&lt;code&gt;o&lt;/code&gt; in current tab&lt;/li&gt;
&lt;li&gt;&lt;code&gt;t&lt;/code&gt; in new tab&lt;/li&gt;
&lt;li&gt;Use capital letters to open current URL&lt;/li&gt;
&lt;li&gt;&lt;code&gt;s&lt;/code&gt; search&lt;/li&gt;
&lt;li&gt;start search query with search engine like &lt;code&gt;duckduckgo&lt;/code&gt;, or &lt;code&gt;google&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use &lt;code&gt;yy&lt;/code&gt; to copy current URL.&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;p&lt;/code&gt; to open clipboard content as a webpage. Use &lt;code&gt;P&lt;/code&gt; to open in a new tab.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Quickly search for source of a quote with &lt;code&gt;;p&lt;/code&gt; to copy a paragraph and &lt;code&gt;P&lt;/code&gt; to search it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use &lt;code&gt;zi&lt;/code&gt;, &lt;code&gt;zo&lt;/code&gt;, &lt;code&gt;zz&lt;/code&gt; to zoom in, out, and to reset zoom.&lt;/p&gt;
&lt;p&gt;Search with &lt;code&gt;/&lt;/code&gt;. Navigate through matches with &lt;code&gt;Ctrl-g&lt;/code&gt;, &lt;code&gt;Ctrl-G&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;Ctrl-v&lt;/code&gt; to send next keystroke to website.&lt;/p&gt;
&lt;p&gt;To check what a binding is doing type &lt;code&gt;:bind [keys]&lt;/code&gt;.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Logging Exceptions with SLF4J</title>
    <link href="https://kleinprojects.com/logging-exceptions-with-slf4j/"/>
    <updated>2020-12-01T00:00:00Z</updated>
    <id>https://kleinprojects.com/logging-exceptions-with-slf4j/</id>
    <content type="html">&lt;p&gt;Exception logging in SLF4J works without including it in the format:&lt;/p&gt;
&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;An exception occured&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;my exception&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As opposed to the &lt;code&gt;{}&lt;/code&gt; format notation.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Go Cheat Sheet</title>
    <link href="https://kleinprojects.com/go-cheat-sheet/"/>
    <updated>2020-11-19T00:00:00Z</updated>
    <id>https://kleinprojects.com/go-cheat-sheet/</id>
    <content type="html">&lt;h2 id=&quot;go-lang-tour&quot; tabindex=&quot;-1&quot;&gt;Go Lang Tour&lt;/h2&gt;
&lt;p&gt;This post is my cheat sheet for the (Go Lang Tour)[https://tour.golang.org].&lt;/p&gt;
&lt;p&gt;Package &lt;code&gt;main&lt;/code&gt; runs programs.&lt;/p&gt;
&lt;h2 id=&quot;imports&quot; tabindex=&quot;-1&quot;&gt;Imports&lt;/h2&gt;
&lt;p&gt;Factored Import Statements&lt;/p&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;token string&quot;&gt;&quot;fmt&quot;&lt;/span&gt;
	&lt;span class=&quot;token string&quot;&gt;&quot;math&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;exports&quot; tabindex=&quot;-1&quot;&gt;Exports&lt;/h2&gt;
&lt;p&gt;In Go, a name is exported if it begins with a capital letter.&lt;/p&gt;
&lt;h2 id=&quot;functions&quot; tabindex=&quot;-1&quot;&gt;Functions&lt;/h2&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; y
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You may omit types if they are equal for consecutive parameters.&lt;/p&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; y
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;multiple-results&quot; tabindex=&quot;-1&quot;&gt;Multiple Results&lt;/h3&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;swap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; x
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;swap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;hello&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;world&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;naked-return&quot; tabindex=&quot;-1&quot;&gt;Naked return&lt;/h3&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sum &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sum &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt;
	y &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sum &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; x
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;variables&quot; tabindex=&quot;-1&quot;&gt;Variables&lt;/h2&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; c&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; python&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; java &lt;span class=&quot;token builtin&quot;&gt;bool&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Short declaration with &lt;code&gt;:=&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; i&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; j &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;
	k &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;
	c&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; python&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; java &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;no!&quot;&lt;/span&gt;

	fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; j&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; k&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; c&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; python&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; java&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;datatypes&quot; tabindex=&quot;-1&quot;&gt;Datatypes&lt;/h2&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;bool&lt;/span&gt;

&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;

&lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;  &lt;span class=&quot;token builtin&quot;&gt;int8&lt;/span&gt;  &lt;span class=&quot;token builtin&quot;&gt;int16&lt;/span&gt;  &lt;span class=&quot;token builtin&quot;&gt;int32&lt;/span&gt;  &lt;span class=&quot;token builtin&quot;&gt;int64&lt;/span&gt;
&lt;span class=&quot;token builtin&quot;&gt;uint&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;uint8&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;uint16&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;uint32&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;uint64&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;uintptr&lt;/span&gt;

&lt;span class=&quot;token builtin&quot;&gt;byte&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// alias for uint8&lt;/span&gt;

&lt;span class=&quot;token builtin&quot;&gt;rune&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// alias for int32&lt;/span&gt;
     &lt;span class=&quot;token comment&quot;&gt;// represents a Unicode code point&lt;/span&gt;

&lt;span class=&quot;token builtin&quot;&gt;float32&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;float64&lt;/span&gt;

&lt;span class=&quot;token builtin&quot;&gt;complex64&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;complex128&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id=&quot;conditions-and-loops&quot; tabindex=&quot;-1&quot;&gt;Conditions and Loops&lt;/h1&gt;
&lt;h2 id=&quot;for&quot; tabindex=&quot;-1&quot;&gt;For&lt;/h2&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;sum &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    sum &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; i
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;for-is-go&#39;s-while&quot; tabindex=&quot;-1&quot;&gt;For is Go&#39;s While&lt;/h2&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; sum &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    sum &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; sum
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;defer&quot; tabindex=&quot;-1&quot;&gt;Defer&lt;/h2&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;defer&lt;/span&gt; fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;world&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;hello&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.golang.org/defer-panic-and-recover&quot;&gt;Further Reading&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;examples&quot; tabindex=&quot;-1&quot;&gt;Examples&lt;/h1&gt;
&lt;p&gt;Read in line:&lt;/p&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Scanln&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err &lt;span class=&quot;token builtin&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example&lt;/p&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; name &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Scanln&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;life%2C-the-universe%2C-and-everything&quot; tabindex=&quot;-1&quot;&gt;Life, the Universe, and Everything&lt;/h2&gt;
&lt;p&gt;Read lines until we read &lt;code&gt;42&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;package&lt;/span&gt; main
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;fmt&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// your code goes here&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; line &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Scanln&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;line&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; line &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;42&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Scanln&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;line&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;line&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>A dead simple approach to using SASS in Eleventy</title>
    <link href="https://kleinprojects.com/a-dead-simple-approach-to-using-sass-in-eleventy/"/>
    <updated>2020-08-16T00:00:00Z</updated>
    <id>https://kleinprojects.com/a-dead-simple-approach-to-using-sass-in-eleventy/</id>
    <content type="html">&lt;p&gt;I prefer going with as simple solutions as possible, avoiding the usage of third party packages that always introduce some risk into your project. You don&#39;t know if they are up to date if there are no tests included, you don&#39;t know if they are secure and what packages are attached to them if you don&#39;t check source code.&lt;/p&gt;
&lt;p&gt;So, to include SASS within my Eleventy project I am using only the official &lt;a href=&quot;https://www.npmjs.com/package/sass&quot;&gt;SASS npm package&lt;/a&gt; and the officially included &lt;a href=&quot;https://nodejs.org/api/fs.html&quot;&gt;fs package&lt;/a&gt; of node to write the transpiled files.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;.eleventy.js&lt;/code&gt; file contains only three lines within the configuration function to compile SASS into CSS:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sass &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;sass&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;fs&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;compileSass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Compiling sass.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cssContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sass&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;renderSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;styles/main.scss&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mkdirSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;_site/css&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;recursive&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeFileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;_site/css/main.css&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cssContent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;css&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;watch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;styles&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filepath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Styles file changed&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filepath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;compileSass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;compileSass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This a a very simple approach without asynchronous code or anything. The only package you need is the npm sass package. They even write that &amp;quot;renderSync() is more than twice as fast as render()&amp;quot; (&lt;a href=&quot;https://www.npmjs.com/package/sass&quot;&gt;SASS npm package&lt;/a&gt;), so you might consider if its worth for you introducing parallel execution.&lt;/p&gt;
&lt;h2 id=&quot;important-note-to-fs.watch&quot; tabindex=&quot;-1&quot;&gt;Important note to fs.watch&lt;/h2&gt;
&lt;p&gt;This script does not work when publishing to GitHub pages automatically as the &lt;code&gt;fs.watch&lt;/code&gt; command will block input. To unblock if Eleventy isn&#39;t run with the &lt;code&gt;--serve&lt;/code&gt; flag we can only enable file watching if we discover that flag like so:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; isServing &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;argv&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;--serve&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isServing&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// watch only files if we are serving&lt;/span&gt;
  fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;watch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;src/styles&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filepath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Styles file changed&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filepath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;compileSass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  
  <entry>
    <title>Clearing the _site directory in Eleventy automatically</title>
    <link href="https://kleinprojects.com/clearing-the-site-directory-in-eleventy-automatically/"/>
    <updated>2020-08-16T00:00:00Z</updated>
    <id>https://kleinprojects.com/clearing-the-site-directory-in-eleventy-automatically/</id>
    <content type="html">&lt;p&gt;If you want to ensure an always up to date version, with all files being removed you might want to clear your &lt;code&gt;_site&lt;/code&gt; directory before each build. This needs no further packages then the already built in &lt;a href=&quot;https://nodejs.org/api/fs.html&quot;&gt;fs package&lt;/a&gt; of node. The function &lt;code&gt;rmdirSync(folderPath, {recursive:true})&lt;/code&gt; can accomplish excactly this.&lt;/p&gt;
&lt;p&gt;Your .eleventy.js then looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;fs&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// clear site on initial build&lt;/span&gt;
fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;rmdirSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;_site&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;recursive&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Cleared _site folder&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that we do not export a function as you would normally do it. This is, because we want to &lt;strong&gt;run the folder deletion only once per build or serve&lt;/strong&gt;!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That means, that this code does not block our subsequent building pipeline. However, when you run Eleventy with &lt;code&gt;eleventy serve&lt;/code&gt; you might end up with unwanted files in the end. So always remember to run &lt;code&gt;eleventy build&lt;/code&gt; before publishing your site and you will always get a clean version.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>My journey to using Eleventy as a static website generator</title>
    <link href="https://kleinprojects.com/my-journey-to-using-eleventy-as-a-static-website-generator/"/>
    <updated>2020-08-15T00:00:00Z</updated>
    <id>https://kleinprojects.com/my-journey-to-using-eleventy-as-a-static-website-generator/</id>
    <content type="html">&lt;p&gt;I started renting my first server something like 10 years ago back in school. Initially I merely used it to play around with some new technologies. While studying I launched my first websites including a small blog.&lt;/p&gt;
&lt;p&gt;First I put together a website from scratch to post content into the world wide web. Then I tried out more advanced and feature rich systems like the &lt;a href=&quot;https://ghost.org/&quot;&gt;ghost blogging platform&lt;/a&gt;. However, I always struggled with this one thing: updating the server.&lt;/p&gt;
&lt;p&gt;I only published content and applications sporadically. Therefore, my server kept getting out of date and I killed it before trying to walk through a complex updating process. Often my blog posts and content got lost in that process.&lt;/p&gt;
&lt;p&gt;So its now 2020 and again I killed my server as my last updates happened 2 years ago. Yes, initially I did backups and restored them on my new Ghost blog instance. However, now I am pursuing a more sustainable and lasting approach to keep stuff that I am working on online.&lt;/p&gt;
&lt;p&gt;Of course I also considered hosted blogging platforms like &lt;a href=&quot;https://medium.com/&quot;&gt;Medium&lt;/a&gt;. Nonetheless, I prefer hosting my own stuff and learning new things in the process.&lt;/p&gt;
&lt;h1 id=&quot;static-websites&quot; tabindex=&quot;-1&quot;&gt;Static Websites&lt;/h1&gt;
&lt;p&gt;During my research of a better hosting alternative I stumbled across &lt;a href=&quot;https://pages.github.com/&quot;&gt;GitHub Pages&lt;/a&gt; that allows hosting of websites for free, with SSL encryption. After some investigation I considered this as a valid hosting option for my new website. Building a static website also had the benefit of changing hosting providers quickly as it is only a bunch of HTML and CSS files without the need of backend services.&lt;/p&gt;
&lt;h1 id=&quot;templating-websites&quot; tabindex=&quot;-1&quot;&gt;Templating Websites&lt;/h1&gt;
&lt;p&gt;Building a website with merely HTML and CSS is a pain when you consider the long run. You might want to change some things and have to update layout and styles at lots of different places. Therefore I decided to go with a templating engine. &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; was my first consideration as it is a very popular framework for static website generation. It uses Ruby under its hood and after setup and some tests I quickly figured out that this is not the language I want to host my websites with.&lt;/p&gt;
&lt;h1 id=&quot;eleventy-as-static-website-generator&quot; tabindex=&quot;-1&quot;&gt;Eleventy as Static Website Generator&lt;/h1&gt;
&lt;p&gt;As mostly work with front-end technologies like Typescript or Javascript I searched for an engine that uses excactly these technologies. That was when I discovered &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt;, a static website generator built with Javascript and using the &lt;a href=&quot;https://www.npmjs.com/&quot;&gt;package manager NPM&lt;/a&gt; for building.&lt;/p&gt;
&lt;h1 id=&quot;getting-used-to-static-website-generators&quot; tabindex=&quot;-1&quot;&gt;Getting used to Static Website Generators&lt;/h1&gt;
&lt;p&gt;As Eleventy is my first static website generator I have ever worked with, I started reading through the documentation and setting up a very simple website. There are lots of demo projects, however I decided to start from scratch to learn about that technology and all the features it has.&lt;/p&gt;
&lt;p&gt;There are several things I already learned in the process: for example setting up basic fragments that can be reused in other templates, applying CSS, or building a basic post collection.&lt;/p&gt;
&lt;p&gt;So I decided to build a new website from scratch. It should be minimal, show off projects that I am working on, and provide the capability of posting stuff from time to time. It should be easy to maintain (from a coders perspective) and last for a long time.&lt;/p&gt;
</content>
  </entry>
</feed>