<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2025-07-08T06:09:09+00:00</updated><id>/feed.xml</id><title type="html">Code &amp;amp; Mind</title><subtitle>Exploring the intersection of technology and psychology.  Technical insights, human factors, and building better teams through code.</subtitle><entry><title type="html">A Practical Guide to Quantifying Attractor States with Hidden Markov Models</title><link href="/psychology/2025/07/05/from-metaphor-to-metric-quantifying-system-dynamics-hmm.html" rel="alternate" type="text/html" title="A Practical Guide to Quantifying Attractor States with Hidden Markov Models" /><published>2025-07-05T00:00:00+00:00</published><updated>2025-07-05T00:00:00+00:00</updated><id>/psychology/2025/07/05/from-metaphor-to-metric-quantifying-system-dynamics-hmm</id><content type="html" xml:base="/psychology/2025/07/05/from-metaphor-to-metric-quantifying-system-dynamics-hmm.html"><![CDATA[<h3 id="from-metaphor-to-metric-a-new-method-for-identifying-attractor-states-in-psychopathology"><strong>From Metaphor to Metric: A New Method for Identifying Attractor States in Psychopathology</strong></h3>

<p>For decades, the study of mental health has grappled with a fundamental challenge: how do we understand the dynamic, often unpredictable, nature of psychological change? Why do some individuals remain stuck in persistent states of depression, while others “bounce back” with remarkable resilience? And how can we anticipate sudden shifts in behavior, like a relapse or a breakthrough in therapy?</p>

<p>A powerful new perspective comes from complex systems science, which reframes mental health not as a static condition but as an <strong>emergent property</strong> of a dynamic system. This approach uses the “ball-and-landscape” metaphor: our psychological state is a ball rolling across a landscape of valleys and hills. The valleys are <strong>attractor states</strong>—stable, self-organizing patterns that our system naturally gravitates towards and resides in. A deep valley represents a robust state, like chronic depression or, conversely, a state of resilient well-being. The hills represent unstable transition zones.</p>

<p>This perspective is transformative because it allows us to see:</p>
<ul>
  <li><strong>Healthy and Pathological States as Attractors:</strong> Both well-being (resilience) and psychopathology (e.g., persistent sadness) are viewed as stable dynamic patterns.</li>
  <li><strong>Clinical Change as Phase Transitions:</strong> Sudden shifts, like recovery or relapse, are seen as “phase transitions,” where the system tips from one attractor valley into another. These transitions can be of different types, such as <strong>bifurcation-induced tipping (B-tipping)</strong>, caused by a gradual change in the landscape itself, or <strong>noise-induced tipping (N-tipping)</strong>, caused by a sudden, large perturbation.</li>
  <li><strong>Intervention as Landscape Design:</strong> Therapy and other interventions can be understood as efforts to reshape this landscape—to shallow a “depression” valley or deepen a “well-being” valley.</li>
</ul>

<p>The predictive power of this model is immense. As a system’s current attractor state becomes unstable and approaches a B-tipping point, it exhibits tell-tale signs like <strong>critical slowing down</strong> (slower recovery from minor setbacks) and <strong>critical fluctuations</strong> (increased behavioral variability). These are “early warning signals” that a major transition is imminent, creating a window for just-in-time interventions.</p>

<p>But this powerful metaphor raises a critical scientific question: Can we move beyond the metaphor? Can we use real-world data to empirically identify these hidden attractor states and test their properties? My goal was to develop a rigorous statistical methodology to answer a critical question: <strong>Can we use real-world time series data to not only identify distinct hidden states but also to test whether those states qualify as bona fide attractors?</strong></p>

<p>I discovered that Hidden Markov Models (HMMs) offer a powerful toolkit for this exact purpose. Instead of assuming that the hidden states an HMM identifies are attractors, my approach uses the various outputs of a fitted HMM as a suite of metrics to either <strong>support or refute this claim</strong>.</p>

<p>This document outlines the framework I developed for this investigation, a crucial conceptual insight that emerged, and a practical guide to implementing this validation process in R.</p>

<h3 id="a-framework-for-testing-attractor-state-characteristics">A Framework for Testing Attractor State Characteristics</h3>

<p>The first step was to deconstruct the “attractor” concept into its core, testable properties. An attractor is a state that a system is drawn towards, resides in, and resists leaving. I translated these properties into a set of hypotheses that could be evaluated using HMM parameters.</p>

<p>Crucially, the interpretation of the evidence is not black-and-white. A given metric might provide strong evidence for the <em>existence</em> of a stable pattern, while another might offer more nuanced information about the <em>type</em> of stability or the likelihood of a specific kind of transition.</p>

<table style="width: 100%; border-collapse: collapse; margin: 20px 0;">
  <thead>
    <tr style="background-color: #f8f9fa; border-bottom: 2px solid #dee2e6;">
      <th style="padding: 12px; text-align: left; border: 1px solid #dee2e6; width: 20%;">Attractor State Characteristic</th>
      <th style="padding: 12px; text-align: left; border: 1px solid #dee2e6; width: 20%;">Definition</th>
      <th style="padding: 12px; text-align: left; border: 1px solid #dee2e6; width: 20%;">Evidence for…</th>
      <th style="padding: 12px; text-align: left; border: 1px solid #dee2e6; width: 20%;">Implies Tipping Type...</th>
      <th style="padding: 12px; text-align: left; border: 1px solid #dee2e6; width: 20%;">HMM Quantifiable Metric</th>
    </tr>
  </thead>
  <tbody>
    <tr style="background-color: #e9ecef;">
      <td colspan="5" style="padding: 10px; font-weight: bold; text-align: center; border: 1px solid #dee2e6;">
        Section 1: Core Properties of Attractor States
      </td>
    </tr>
    <tr>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Depth of the attractor</strong> &amp; <strong>Resistance to change</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        A measure of state stability; a "deeper valley" is harder to leave.
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>The presence of a stable attractor.</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        The <em>shallowness</em> of a valley (low inertia) can make the system susceptible to <strong>N-tipping</strong> from noise.
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>State Inertia: <code>P(i|i)</code></strong> &amp; <strong>Expected State Duration</strong>
      </td>
    </tr>
    <tr style="background-color: #f8f9fa;">
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Return rate to equilibrium</strong> &amp; <strong>Speed along the attractor</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        The speed at which a system returns to its stable state after being perturbed.
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>The stability of an attractor.</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        A <em>slowing</em> return rate is a key indicator of <strong>B-tipping</strong> (Critical Slowing Down). A fast, stable rate suggests the system is not approaching a B-tipping point.
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Mean First Passage Time (MFPT)</strong>
      </td>
    </tr>
    <tr>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Location in phase space</strong> &amp; <strong>Amplitude</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        The central point or average signal level of the observations when the system is in a given state.
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>The existence and location of an attractor.</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        A <em>gradual change</em> in the mean can be an EWS for <strong>B-tipping</strong> (e.g., a saddle-node bifurcation). A sudden jump between two stable means suggests <strong>N-tipping</strong>.
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Mean of the emission distribution: <code>μₖ</code></strong>
      </td>
    </tr>
    <tr style="background-color: #f8f9fa;">
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Size and shape</strong> &amp; <strong>Variance</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        The geometric properties (spread, orientation) of the region the system occupies in its state space.
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>The structure and stability of an attractor.</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <em>Increasing</em> variance is a classic EWS for <strong>B-tipping</strong>, indicating the attractor valley is flattening.
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Variance/Covariance of the emission distribution: <code>σ²ₖ</code> or <code>Σₖ</code></strong>
      </td>
    </tr>
    <tr>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Density across the attractor</strong> &amp; <strong>Frequency</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        The long-run probability or empirical proportion of time the system spends in each state.
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>The dominance or preference for an attractor.</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        A high density in one state and low in another suggests a bi-stable system where <strong>N-tipping</strong> is possible.
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Stationary Distribution (<code>δ</code>)</strong> &amp; <strong>Fractional Occupancy</strong>
      </td>
    </tr>
    <tr style="background-color: #f8f9fa;">
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Topology</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        The structured map of connections and pathways between different states.
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>An organized, non-random dynamic system.</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        The existence of multiple, connected states is a prerequisite for any type of tipping.
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>The full Transition Probability Matrix: <code>Γ</code></strong>
      </td>
    </tr>
    <tr>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Force required for transition</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        The magnitude of an external factor needed to push the system out of its current state.
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>The robustness of an attractor.</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        A high "force" needed suggests a deep attractor resistant to <strong>N-tipping</strong>. The coefficient can model the gradual change leading to <strong>B-tipping</strong>.
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Regression coefficient (<code>β</code>)</strong> linking a covariate to a transition
      </td>
    </tr>
    <tr style="background-color: #e9ecef;">
      <td colspan="5" style="padding: 10px; font-weight: bold; text-align: center; border: 1px solid #dee2e6;">
        Section 2: Dynamic Behaviors &amp; Early Warning Signals (EWS)
      </td>
    </tr>
    <tr style="background-color: #f8f9fa;">
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Autocorrelation (ACF)</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        The "memory" in the data; how much the current value depends on past values.
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>The temporal structure of the system.</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <em>Increasing</em> ACF is a primary EWS for <strong>B-tipping</strong> and <strong>R-tipping</strong>, as it directly measures Critical Slowing Down.
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Autocorrelation Function</strong>
      </td>
    </tr>
    <tr>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Flickering</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        Frequent, rapid transitions between states, often due to high noise.
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>The presence of multiple, shallow attractors.</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        This behavior is a direct sign of <strong>N-diffusion</strong> or a system highly susceptible to <strong>N-tipping</strong>.
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Off-diagonal transition probabilities: <code>P(j|i), i≠j</code></strong>
      </td>
    </tr>
    <tr style="background-color: #f8f9fa;">
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Rigidity/Flexibility</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        The balance between ordered and random patterns in the system's dynamics.
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>The adaptive nature of a state.</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        Pathological rigidity suggests being "stuck" in a deep attractor, while healthy flexibility (pink noise) suggests an optimal balance, possibly more resilient to unwanted tipping.
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>State Inertia &amp; Switching Probabilities</strong>
      </td>
    </tr>
  </tbody>
</table>

<h3 id="the-engine-vs-dashboard-insight-a-key-to-correct-inference">The “Engine vs. Dashboard” Insight: A Key to Correct Inference</h3>

<p>A critical conceptual hurdle emerged during this work. I initially worried that the HMM’s core assumption—that the current hidden state depends only on the immediate past state (the Markov property)—would artificially limit my analysis to simple, lag-1 dynamics. This, however, is a misunderstanding of how the HMM’s two-layer structure works, and correcting it is essential for making valid inferences.</p>

<p>The “Engine vs. Dashboard” analogy proved invaluable:</p>

<ol>
  <li><strong>The Hidden “Engine” (The State):</strong> This is the underlying, unobserved state we are testing (e.g., a state of <code class="language-plaintext highlighter-rouge">Severe Depression</code>). The simple lag-1 Markov rule applies <em>only here</em>.</li>
  <li><strong>The Observed “Dashboard” (The Data):</strong> This is what we measure (e.g., daily depression scores). These scores are <em>emitted</em> by the hidden engine state.</li>
</ol>

<p>The link between them is <strong>state inertia</strong>. If the <code class="language-plaintext highlighter-rouge">Severe Depression</code> state is a true attractor, its inertia will be high (<code class="language-plaintext highlighter-rouge">P(Depression|Depression) ≈ 1</code>). This persistence means that even though the state-switching rule is simple, the <em>observed data</em> will exhibit long-range memory. A high depression score today will be correlated not just with tomorrow’s score, but with the score ten days from now, because all are likely generated by the same, persistent underlying state. This allows us to use the full autocorrelation structure of the data as a rich source of evidence without being constrained by the model’s internal mechanics.</p>

<h3 id="practical-implementation-a-guide-to-rs-depmixs4-package">Practical Implementation: A Guide to R’s <code class="language-plaintext highlighter-rouge">depmixS4</code> Package</h3>

<p>With the validation framework established, the final step was to operationalize it. The R package <code class="language-plaintext highlighter-rouge">depmixS4</code> is perfectly suited for this task. The workflow involves specifying a model with <code class="language-plaintext highlighter-rouge">depmix()</code>, fitting it with <code class="language-plaintext highlighter-rouge">fit()</code>, and then using the resulting <code class="language-plaintext highlighter-rouge">depmix.fitted</code> object (which I’ll call <code class="language-plaintext highlighter-rouge">fm</code>) to extract the metrics needed to test our hypotheses.</p>

<p>Here’s the complete guide on how to gather the evidence using <code class="language-plaintext highlighter-rouge">fm</code>:</p>

<table style="width: 100%; border-collapse: collapse; margin: 20px 0;">
  <thead>
    <tr style="background-color: #f8f9fa; border-bottom: 2px solid #dee2e6;">
      <th style="padding: 12px; text-align: left; border: 1px solid #dee2e6; width: 25%;">Attractor State Characteristic</th>
      <th style="padding: 12px; text-align: left; border: 1px solid #dee2e6; width: 25%;">HMM Quantifiable Metric</th>
      <th style="padding: 12px; text-align: left; border: 1px solid #dee2e6; width: 50%;">Implementation in R's <code>depmixS4</code> Package</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Depth of the attractor</strong> &amp;<br />
        <strong>Resistance to change</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>State Inertia: <code>P(i|i)</code></strong> &amp; <strong>Expected State Duration</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>How to get it:</strong> The transition probabilities are the most direct measure. They are printed in the <code>summary()</code> of a fitted model. The diagonals of this matrix are the state inertias. Expected duration is derived from inertia.<br /><br />
        <strong>R Code:</strong>
        <pre style="background-color: #f8f9fa; padding: 10px; border-radius: 4px; font-size: 12px; overflow-x: auto;">
# After fitting a model: fm &lt;- fit(mod)
# 1. View the transition matrix in the summary output
summary(fm)
# 2. To derive expected duration for state 1:
inertia_s1 &lt;- # (get value from summary)
exp_dur_s1 &lt;- 1 / (1 - inertia_s1)
        </pre>
      </td>
    </tr>
    <tr style="background-color: #f8f9fa;">
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Return rate to equilibrium</strong> &amp;<br />
        <strong>Speed along the attractor</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Mean First Passage Time (MFPT)</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>How to get it:</strong> This is a derived metric. You must first extract the transition probability matrix (<code>Γ</code>) from the model summary and then apply matrix algebra to calculate the MFPT. <code>depmixS4</code> does not provide a direct function for this.<br /><br />
        <strong>R Code (Conceptual):</strong>
        <pre style="background-color: #f8f9fa; padding: 10px; border-radius: 4px; font-size: 12px; overflow-x: auto;">
# 1. Get the transition probability matrix (tpm) from summary(fm)
# 2. Use a helper function (not in depmixS4) to compute MFPT
mfpt_matrix &lt;- calculate_mfpt(tpm)
        </pre>
      </td>
    </tr>
    <tr>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Location in phase space</strong> &amp;<br />
        <strong>Amplitude</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Mean of the emission distribution: <code>μₖ</code></strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>How to get it:</strong> The parameters of the response (emission) distributions for each state are printed in the <code>summary()</code>. For Gaussian responses, this will be the intercept/coefficient, which is the mean (<code>μ</code>).<br /><br />
        <strong>R Code:</strong>
        <pre style="background-color: #f8f9fa; padding: 10px; border-radius: 4px; font-size: 12px; overflow-x: auto;">
# 1. View the response parameters in the summary output
summary(fm)
# 2. Programmatically get the response model for state 1, response 1
response_s1 &lt;- getmodel(fm, which = "response", state = 1, number = 1)
# The mean is a parameter within this object
getpars(response_s1)
        </pre>
      </td>
    </tr>
    <tr style="background-color: #f8f9fa;">
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Size and shape</strong> &amp;<br />
        <strong>Variance</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Variance/Covariance of the emission distribution: <code>σ²ₖ</code> or <code>Σₖ</code></strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>How to get it:</strong> This is also a response parameter found in the <code>summary()</code>. For Gaussian responses (<code>family=gaussian()</code>), the standard deviation (<code>sd</code>) is estimated. For multivariate normal responses (<code>MVNresponse</code>), the covariance matrix (<code>Sigma</code>) is estimated.<br /><br />
        <strong>R Code:</strong>
        <pre style="background-color: #f8f9fa; padding: 10px; border-radius: 4px; font-size: 12px; overflow-x: auto;">
# View the response sd or Sigma matrix in the summary output
summary(fm)
# Programmatically access using getmodel() as shown for the mean.
        </pre>
      </td>
    </tr>
    <tr>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Density across the attractor</strong> &amp;<br />
        <strong>Frequency</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Stationary Distribution (<code>δ</code>)</strong> &amp; <strong>Fractional Occupancy</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>How to get it:</strong><br />
        1. <strong>Stationary Distribution:</strong> Use the <code>stationary()</code> function on the transition probability matrix obtained from the model summary.<br />
        2. <strong>Fractional Occupancy:</strong> Use the <code>posterior()</code> function with <code>type="global"</code> to get the most likely state sequence (Viterbi path), then calculate the proportion of time spent in each state.<br /><br />
        <strong>R Code:</strong>
        <pre style="background-color: #f8f9fa; padding: 10px; border-radius: 4px; font-size: 12px; overflow-x: auto;">
# 1. For Stationary Distribution (conceptual):
tpm &lt;- matrix(c(0.9, 0.1, 0.2, 0.8), 2, 2, byrow=TRUE)
stationary(tpm)
# 2. For Fractional Occupancy:
state_sequence &lt;- posterior(fm, type = "global")$state
table(state_sequence) / length(state_sequence)
        </pre>
      </td>
    </tr>
    <tr style="background-color: #f8f9fa;">
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Topology</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>The full Transition Probability Matrix: <code>Γ</code></strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>How to get it:</strong> The transition matrix is the direct representation of the system's topology. It is best viewed in the output of <code>summary()</code>.<br /><br />
        <strong>R Code:</strong>
        <pre style="background-color: #f8f9fa; padding: 10px; border-radius: 4px; font-size: 12px; overflow-x: auto;">
# The "Transition probabilities" section of the summary is the metric
summary(fm)
        </pre>
      </td>
    </tr>
    <tr>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Force required for transition</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Regression coefficient (<code>β</code>)</strong> linking a covariate to a transition
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>How to get it:</strong> When a covariate is included in the <code>transition</code> formula (e.g., <code>transition = ~ my_covariate</code>), its regression coefficient (<code>β</code>) is estimated. This coefficient is shown in the <code>summary()</code> output under the transition model parameters.<br /><br />
        <strong>R Code:</strong>
        <pre style="background-color: #f8f9fa; padding: 10px; border-radius: 4px; font-size: 12px; overflow-x: auto;">
# 1. Specify covariate in model:
mod &lt;- depmix(..., transition = ~ Pacc, ...)
fm &lt;- fit(mod)
# 2. View the coefficient for Pacc in the summary output
summary(fm)
        </pre>
      </td>
    </tr>
    <tr style="background-color: #f8f9fa;">
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Autocorrelation (ACF)</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Autocorrelation Function</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>How to get it:</strong> This is a derived metric. The most practical way to assess this is to generate a long time series from the fitted model using the <code>simulate()</code> function, and then compute the ACF on the simulated data using R's built-in <code>acf()</code> function.<br /><br />
        <strong>R Code:</strong>
        <pre style="background-color: #f8f9fa; padding: 10px; border-radius: 4px; font-size: 12px; overflow-x: auto;">
# 1. Simulate a long time series from the fitted model
sim_data &lt;- simulate(fm, nsim=10000)
# 2. Compute ACF on the simulated response
acf(sim_data$response)
        </pre>
      </td>
    </tr>
    <tr>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Flickering</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Off-diagonal transition probabilities: <code>P(j|i), i≠j</code></strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>How to get it:</strong> This is the same as "Depth of the attractor." High values on the off-diagonals of the transition matrix in the <code>summary()</code> output indicate a high probability of switching, which is characteristic of flickering.<br /><br />
        <strong>R Code:</strong>
        <pre style="background-color: #f8f9fa; padding: 10px; border-radius: 4px; font-size: 12px; overflow-x: auto;">
# Look at off-diagonal values in the transition matrix from:
summary(fm)
        </pre>
      </td>
    </tr>
    <tr style="background-color: #e9ecef;">
      <td colspan="3" style="padding: 10px; font-weight: bold; text-align: center; border: 1px solid #dee2e6;">
        Characteristics Not Directly Quantifiable by HMMs
      </td>
    </tr>
    <tr style="background-color: #f8f9fa;">
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Local curvature, Critical Fluctuations, Critical Slowing Down, Fractal Dimension, Spectral Properties</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>N/A</strong>
      </td>
      <td style="padding: 10px; border: 1px solid #dee2e6; vertical-align: top;">
        <strong>Reason:</strong> These characteristics are either concepts from continuous geometry, nonlinear dynamics, or are phenomena that must be inferred indirectly (e.g., by observing trends over time) rather than being a direct parameter output by the HMM. They are outside the native scope of the discrete-state probabilistic HMM framework.
      </td>
    </tr>
  </tbody>
</table>

<h3 id="conclusion">Conclusion</h3>

<p>This work culminated in a robust, data-driven methodology for using HMMs as an investigatory tool. We no longer need to simply assume that the hidden states identified by a model are attractors. We can now subject them to a battery of statistical tests, using the model’s own parameters as evidence. This approach allows us to build a case for or against a state’s status as an attractor, and more importantly, to begin hypothesizing about the <em>types</em> of transitions a system is prone to. It moves the complex systems approach from a powerful metaphor to a testable scientific framework, unlocking new possibilities for predicting and influencing psychological change.</p>]]></content><author><name></name></author><category term="psychology" /><category term="hidden-markov-models" /><category term="attractor-states" /><category term="quantitative-methods" /><category term="complex-systems" /><category term="R-programming" /><summary type="html"><![CDATA[From Metaphor to Metric: A New Method for Identifying Attractor States in Psychopathology]]></summary></entry><entry><title type="html">How I Set Up Seamless, Passwordless SSH from my Mac to WSL2</title><link href="/coding/2025/07/04/seamless-passwordless-ssh-mac-to-wsl2.html" rel="alternate" type="text/html" title="How I Set Up Seamless, Passwordless SSH from my Mac to WSL2" /><published>2025-07-04T00:00:00+00:00</published><updated>2025-07-04T00:00:00+00:00</updated><id>/coding/2025/07/04/seamless-passwordless-ssh-mac-to-wsl2</id><content type="html" xml:base="/coding/2025/07/04/seamless-passwordless-ssh-mac-to-wsl2.html"><![CDATA[<h3 id="level-up-your-wsl2-connection-passwordless-ssh-for-a-one-click-workflow">Level Up Your WSL2 Connection: Passwordless SSH for a One-Click Workflow</h3>

<p><strong>July 5, 2025</strong></p>

<p>In our <a href="/coding/2025/06/30/ultimate-guide-macbook-wsl2-vscode.html">previous guide</a>, we built the ultimate bridge between a MacBook and a Windows powerhouse, enabling a full-featured VS Code development experience inside WSL2. We set up port forwarding, configured the firewall, and established a solid connection.</p>

<p>But there’s one lingering piece of friction in that workflow: the password prompt.</p>

<p>Every time you connect, VS Code asks for your Ubuntu user’s password. It’s a small interruption, but it stands in the way of a truly seamless, one-click experience.</p>

<p>In this guide, we’ll eliminate that final hurdle. We’ll replace password authentication with a more secure and convenient method: <strong>SSH keys</strong>. By the end, you’ll be able to launch your remote VS Code session without ever typing a password again.</p>

<h4 id="the-big-picture-keys-vs-passwords">The Big Picture: Keys vs. Passwords</h4>

<p>An SSH key pair is like a sophisticated lock and key for your digital world.</p>

<ul>
  <li><strong>The Public Key (The Lock):</strong> You can freely share this and install it on any server you want to access. It acts as a lock that only your private key can open.</li>
  <li><strong>The Private Key (The Key):</strong> This is your secret. You keep it safe on your local machine (your MacBook). It can unlock any system that has your public key installed.</li>
</ul>

<p>This method is vastly more secure than a password, which can be guessed or brute-forced. Let’s get it set up.</p>

<hr />

<h3 id="part-3-upgrading-to-key-based-authentication">Part 3: Upgrading to Key-Based Authentication</h3>

<p>This guide assumes you have completed the steps in our <a href="/coding/2025/06/30/ultimate-guide-macbook-wsl2-vscode.html">original article</a>. We’ll be modifying that setup.</p>

<h4 id="step-1-create-your-ssh-key-pair-inside-wsl2">Step 1: Create Your SSH Key Pair (Inside WSL2)</h4>

<p>First, we need to generate our secure keys inside the Ubuntu environment.</p>

<p><em>On your Windows machine, launch your <strong>Ubuntu WSL2 terminal</strong> and run the following commands.</em></p>

<p><strong>1. Generate the Key Pair</strong>
We’ll use <code class="language-plaintext highlighter-rouge">ed25519</code>, a modern and highly secure algorithm.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># This will start the key generation process</span>
ssh-keygen <span class="nt">-t</span> ed25519
</code></pre></div></div>

<p>You will be prompted for two things:</p>
<ul>
  <li><strong>File to save the key:</strong> Press <strong>Enter</strong> to accept the default location (<code class="language-plaintext highlighter-rouge">~/.ssh/id_ed25519</code>).</li>
  <li><strong>Enter passphrase:</strong> <strong>This is highly recommended!</strong> A passphrase is a password that encrypts your private key on disk. It ensures that even if someone steals your private key file, they can’t use it without the passphrase. We’ll see how to avoid typing it constantly in a later step.</li>
</ul>

<p><strong>2. Authorize Your New Key for Login</strong>
Now, you need to tell your Ubuntu server that this new key is allowed to log in. You do this by adding the public key to a special file called <code class="language-plaintext highlighter-rouge">authorized_keys</code>.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Add the public key to the list of authorized keys</span>
<span class="nb">cat</span> ~/.ssh/id_ed25519.pub <span class="o">&gt;&gt;</span> ~/.ssh/authorized_keys

<span class="c"># Set strict permissions, which SSH requires for security</span>
<span class="nb">chmod </span>700 ~/.ssh
<span class="nb">chmod </span>600 ~/.ssh/authorized_keys
</code></pre></div></div>

<p>Your WSL2 instance is now ready to accept a connection using your new key.</p>

<h4 id="step-2-configure-your-macbook-to-use-the-key">Step 2: Configure Your MacBook to Use the Key</h4>

<p>Now, let’s go back to your MacBook and teach it how to use this new, more secure method.</p>

<p><em>The following steps are performed on your <strong>MacBook terminal</strong>.</em></p>

<p><strong>1. Securely Copy the Private Key to Your Mac</strong>
The private key (<code class="language-plaintext highlighter-rouge">id_ed25519</code>) needs to be on your Mac to authenticate.</p>

<p>First, display the key’s content in your WSL2 terminal so you can copy it.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># In your WSL2 terminal</span>
<span class="nb">cat</span> ~/.ssh/id_ed25519
</code></pre></div></div>
<p>Select and copy the entire output, including the <code class="language-plaintext highlighter-rouge">-----BEGIN OPENSSH PRIVATE KEY-----</code> and <code class="language-plaintext highlighter-rouge">-----END OPENSSH PRIVATE KEY-----</code> lines.</p>

<p>Now, on your <strong>Mac</strong>, create a file to store this key.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># In your macOS terminal</span>
<span class="c"># Create a new file for the key and set secure permissions</span>
<span class="nb">touch</span> ~/.ssh/wsl2_key
<span class="nb">chmod </span>600 ~/.ssh/wsl2_key

<span class="c"># Open the file in a text editor to paste the key</span>
nano ~/.ssh/wsl2_key
</code></pre></div></div>
<p>Paste the private key you copied from WSL2 into this file, then save and exit (in <code class="language-plaintext highlighter-rouge">nano</code>, that’s <code class="language-plaintext highlighter-rouge">Ctrl+X</code>, then <code class="language-plaintext highlighter-rouge">Y</code>, then <code class="language-plaintext highlighter-rouge">Enter</code>).</p>

<p><strong>2. Update Your VS Code SSH Config</strong>
In the previous guide, we added a host to the <code class="language-plaintext highlighter-rouge">~/.ssh/config</code> file. Now we just need to tell it which key to use.</p>

<p>Open <code class="language-plaintext highlighter-rouge">~/.ssh/config</code> on your Mac and find the host entry for your WSL2 connection. Add the <code class="language-plaintext highlighter-rouge">IdentityFile</code> line to it:</p>

<pre><code class="language-ssh_config"># In ~/.ssh/config on your MacBook

Host 192.168.0.113 # Or whatever you named your host
    HostName 192.168.0.113
    User your-wsl-username
    Port 2222
    # Add this line to specify which key to use!
    IdentityFile ~/.ssh/wsl2_key
</code></pre>

<p>Now, when you try to connect, VS Code will use your SSH key instead of asking for a password. However, it <em>will</em> ask for the key’s passphrase. Let’s fix that.</p>

<h4 id="step-3-the-final-polish---automating-the-passphrase">Step 3: The Final Polish - Automating the Passphrase</h4>

<p>We need a way to unlock our private key once and have it stay unlocked for our entire work session. This is exactly what <code class="language-plaintext highlighter-rouge">ssh-agent</code> is for. It’s a secure background utility that holds your unlocked keys in memory.</p>

<p>We can configure your Mac’s shell to start the agent and load your key automatically.</p>

<p>Add the following lines to the end of your shell configuration file on your Mac (<code class="language-plaintext highlighter-rouge">~/.zshrc</code> for Zsh, or <code class="language-plaintext highlighter-rouge">~/.bash_profile</code> for Bash).</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Auto-start ssh-agent and add my WSL2 key on terminal launch</span>
<span class="k">if</span> <span class="o">!</span> pgrep <span class="nt">-u</span> <span class="s2">"</span><span class="nv">$USER</span><span class="s2">"</span> ssh-agent <span class="o">&gt;</span> /dev/null<span class="p">;</span> <span class="k">then
    </span>ssh-agent <span class="o">&gt;</span> ~/.ssh/ssh-agent.env
<span class="k">fi
if</span> <span class="o">[[</span> <span class="o">!</span> <span class="s2">"</span><span class="nv">$SSH_AUTH_SOCK</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">source</span> ~/.ssh/ssh-agent.env <span class="o">&gt;</span>/dev/null
<span class="k">fi
</span>ssh-add <span class="nt">-l</span> | <span class="nb">grep</span> <span class="nt">-q</span> wsl2_key <span class="o">||</span> ssh-add ~/.ssh/wsl2_key
</code></pre></div></div>

<p>Now, <strong>close and reopen your terminal</strong>. The very first time, it will prompt for the passphrase for your <code class="language-plaintext highlighter-rouge">wsl2_key</code>. Enter it one last time.</p>

<h3 id="the-final-result-true-one-click-connection">The Final Result: True One-Click Connection</h3>

<p>That’s it! The <code class="language-plaintext highlighter-rouge">ssh-agent</code> will now remember your unlocked key.</p>

<p>Open VS Code, go to the <strong>Remote Explorer</strong> tab, and click the connect icon next to your WSL2 host. It will connect instantly, with no password and no passphrase prompt.</p>

<p>You have now perfected your cross-platform setup. Your connection is not only more convenient but also significantly more secure. You’ve replaced a brittle password with strong public-key cryptography and automated the entire login process.</p>

<p>Happy (and seamless) coding! 🚀</p>]]></content><author><name></name></author><category term="coding" /><category term="ssh" /><category term="wsl2" /><category term="mac" /><category term="security" /><category term="development" /><summary type="html"><![CDATA[Level Up Your WSL2 Connection: Passwordless SSH for a One-Click Workflow]]></summary></entry><entry><title type="html">Deconstructing the Paragraph: A Thought Experiment in Clean Research Dissemination</title><link href="/psychology/2025/07/02/deconstructing-the-paragraph.html" rel="alternate" type="text/html" title="Deconstructing the Paragraph: A Thought Experiment in Clean Research Dissemination" /><published>2025-07-02T12:00:00+00:00</published><updated>2025-07-02T12:00:00+00:00</updated><id>/psychology/2025/07/02/deconstructing-the-paragraph</id><content type="html" xml:base="/psychology/2025/07/02/deconstructing-the-paragraph.html"><![CDATA[<h3 id="musings-on-the-architecture-of-a-thought">Musings on the Architecture of a Thought</h3>

<p>A curious thought has occupied me recently. It revolves around the fundamental unit of our written work: the paragraph. We often treat a paragraph as a singular, atomic thing, but what if it’s more like a composite structure? What if a single collection of facts or ideas could be assembled in fundamentally different ways to achieve entirely different effects? It seems to me that the <em>form</em> of the delivery is just as crucial as the <em>content</em> being delivered, and I wanted to document some of my own thinking on how one might begin to formally separate these concerns.</p>

<h4 id="a-functional-model-of-the-paragraph">A Functional Model of the Paragraph</h4>

<p>To get my own head around this, I started thinking about a kind of three-tiered functional model. This isn’t meant to be a rigid prescription, but rather a way of organizing the different jobs a paragraph does.</p>

<ul>
  <li>
    <p><strong>Level 1: The Structural Role (The “Where”).</strong> This seems to be the highest level, defining the paragraph’s job within the entire document. Is it an <strong>Introductory</strong> paragraph, setting the stage? Is it a <strong>Body</strong> paragraph, doing the heavy lifting of the argument? Or is it a <strong>Concluding</strong> paragraph, bringing things to a satisfying close? Its position dictates its overarching function.</p>
  </li>
  <li>
    <p><strong>Level 2: The Communicative Purpose (The “Why”).</strong> Once we know <em>where</em> it is (e.g., a Body Paragraph), we can ask <em>why</em> it exists. What is its core intent? I see four main purposes here. Is it <strong>Expository</strong>, aiming to explain or inform? Is it <strong>Persuasive</strong>, built to convince the reader of a claim? Is it <strong>Descriptive</strong>, trying to paint a sensory picture? Or is it <strong>Narrative</strong>, recounting a sequence of events?</p>
  </li>
  <li>
    <p><strong>Level 3: The Method of Development (The “How”).</strong> This is the most granular level, the internal architecture. <em>How</em> is the paragraph built to achieve its purpose? This is where we find the familiar rhetorical strategies: <strong>Compare and Contrast</strong>, <strong>Cause and Effect</strong>, <strong>Problem-Solution</strong>, <strong>Exemplification</strong>, and so on.</p>
  </li>
</ul>

<p>The interesting part, for me, is the dependency. The choice of Structural Role (Level 1) constrains the Purpose (Level 2), which in turn strongly suggests a Method (Level 3). It’s a cascade from general architecture to specific implementation.</p>

<h4 id="an-experiment-in-generative-composition">An Experiment in Generative Composition</h4>

<p>As a playful exercise to see this model in action, I put together a small Python script. The idea was simple: could I take a set of “core content”—just raw notes and facts about a topic—and use this three-tiered model to instruct a generative AI to assemble that content into different paragraph forms? The script simply takes the core content, a chosen purpose from Level 2, and a chosen method from Level 3, and asks the model to write the paragraph accordingly. It’s a way of operationalizing the theory.</p>

<h4 id="observing-the-model-in-action">Observing the Model in Action</h4>

<p>The results were quite striking. Watching the same set of core concepts get refracted through different purposeful and methodological lenses is fascinating. Here are two examples generated by the script, both using the exact same source material but with different instructions.</p>

<p><strong>Shared Inputs</strong></p>

<ul>
  <li><strong>Core Concepts:</strong>
    <ul>
      <li><strong>Concept:</strong> Human systems can have multiple points of equilibrium, representing different long-term steady-state behaviors.</li>
      <li><strong>Equilibrium Points:</strong> Can be stable (system tends to return) or unstable (system moves away). They generally correspond to a system’s lowest energy state.</li>
      <li><strong>Linear vs. Nonlinear Dynamics:</strong> Linear systems typically have one equilibrium state, whereas nonlinear systems can have multiple possible “comfortable” long-term states.</li>
      <li><strong>State Switching:</strong> Perturbations can cause the system to shift from one equilibrium state to another.</li>
      <li><strong>Examples in Humans:</strong> This applies to psychological states (focused vs. unfocused), psychophysiological states (relaxed vs. stressed), and behavioral states (cooperative vs. independent).</li>
      <li><strong>References:</strong> (Allen &amp; Carifio, 1995; Chow et al., 2015; Moulder et al., 2023).</li>
    </ul>
  </li>
</ul>

<p><strong>Example Generated Paragraphs</strong></p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Aspect</th>
      <th style="text-align: left">Generation 1</th>
      <th style="text-align: left">Generation 2</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>Instructions</strong></td>
      <td style="text-align: left"><strong>Method:</strong> Expository + Problem-Solution</td>
      <td style="text-align: left"><strong>Method:</strong> Expository + Definition</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Opening Frame</strong></td>
      <td style="text-align: left">Frames the topic as a “significant challenge” in understanding behavior.</td>
      <td style="text-align: left">Begins with a direct definition of the core concept.</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Generated Output</strong></td>
      <td style="text-align: left">A significant challenge in understanding human behavior lies in the limitations of linear models, which often fail to account for the existence of multiple stable equilibrium states within complex human systems. While linear systems typically exhibit a single equilibrium point representing a system’s lowest energy state, human systems—encompassing psychological, psychophysiological, and behavioral domains—demonstrate nonlinear dynamics, capable of existing in multiple “comfortable” long-term states, such as focused versus unfocused attention or cooperative versus independent behavior. The existence of both stable and unstable equilibrium points means that perturbations, rather than simply causing temporary deviations, can trigger transitions between fundamentally different steady-state behaviors. This multi-equilibrium perspective, supported by research on nonlinear dynamics (Allen &amp; Carifio, 1995; Chow et al., 2015; Moulder et al., 2023), offers a more nuanced understanding of human behavior by acknowledging the possibility of substantial, long-term state shifts in response to seemingly minor disruptions.</td>
      <td style="text-align: left">Human systems, unlike many simpler linear systems, often exhibit multiple points of equilibrium, representing distinct long-term steady-state behaviors (Allen &amp; Carifio, 1995). These equilibria, generally corresponding to the system’s lowest energy state, can be either stable, whereby the system tends to return to the equilibrium point following a perturbation, or unstable, leading to a departure from the initial state. The existence of multiple equilibria is a defining characteristic of nonlinear dynamics, contrasting sharply with linear systems which typically possess a single equilibrium point. In the context of human systems, this translates to the possibility of multiple “comfortable” long-term states, such as focused versus unfocused psychological states, relaxed versus stressed psychophysiological states, or cooperative versus independent behavioral states. Significant perturbations can induce state switching, causing a transition from one equilibrium point to another (Chow et al., 2015). The precise nature and stability of these equilibria are complex and context-dependent, making their identification and analysis a significant challenge in understanding human behavior.</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Analysis</strong></td>
      <td style="text-align: left">The paragraph is structured as a solution to the problem of linear models. It emphasizes the <em>why</em> before the <em>what</em>.</td>
      <td style="text-align: left">The paragraph is structured like a textbook entry, defining terms sequentially (equilibrium, stability, nonlinear dynamics) before applying them.</td>
    </tr>
  </tbody>
</table>

<h4 id="broader-musings-towards-a-clean-architecture-for-research">Broader Musings: Towards a “Clean Architecture” for Research</h4>

<p>My first reaction to seeing this work was, well, a sense of amusement. It’s quite something to see a machine play with argumentation structures so fluidly. But it quickly led to a deeper line of thought. It really forces one to question the nature of authorship in an era where the framework that delivers information can be generated so easily. It seems less realistic, and perhaps less important, for individuals to focus on the fine-grained mechanics of paragraph construction. The real skill might be shifting to a higher level: a deeper understanding of argumentation itself, of rhetorical architecture, and of the audience one is trying to reach.</p>

<p>This is where my mind wanders to ideas from software engineering, specifically the concept of “clean architecture.” The principle there is to separate the core business logic—the truly essential stuff—from the delivery mechanisms like databases or user interfaces. I wonder if a similar pattern could be applied to research. The final output—the formal paper—is really just one possible delivery mechanism. The more fundamental “core” is the research project itself: the data, the core concepts, the key findings.</p>

<p>If one can successfully extract that core and hold it as a distinct entity, one could then, in theory, pipe it into any number of frameworks for dissemination. The same core research could be rendered as a formal academic paper for a peer-reviewed journal, a persuasive opinion piece for a broader audience, a series of blog posts, or a conference presentation. Each output would simply be a different “view” of the same underlying entity, tailored to a specific medium and audience.</p>

<p>This little script and the model behind it are, of course, just a bit of experimental fun. But they feel like a small, playful step toward conceptualizing this kind of “clean architecture research environment.” It feels like a way to spend less time on the syntactical details and more time on the deep architecture of our work, which, to my mind, is a fascinating possibility to consider.</p>]]></content><author><name></name></author><category term="psychology" /><summary type="html"><![CDATA[Musings on the Architecture of a Thought]]></summary></entry><entry><title type="html">Internal Agents and External Events: A Computational Sketch of the Self</title><link href="/psychology/2025/07/01/computational-sketch-of-the-self.html" rel="alternate" type="text/html" title="Internal Agents and External Events: A Computational Sketch of the Self" /><published>2025-07-01T15:00:00+00:00</published><updated>2025-07-01T15:00:00+00:00</updated><id>/psychology/2025/07/01/computational-sketch-of-the-self</id><content type="html" xml:base="/psychology/2025/07/01/computational-sketch-of-the-self.html"><![CDATA[<hr />

<p>This post muses on a potential computational framework for psychopathology, built around two tentative ideas:</p>

<ul>
  <li><strong>Internal Agents:</strong> The mind <strong>could perhaps be viewed</strong> as a system of interacting “agents” (e.g., a “self-critic,” “problem-solver”) that seem to influence mood and behavior.</li>
  <li><strong>External Events:</strong> Life stressors <strong>might be conceptualized</strong> as probabilistic streams, reflecting their likelihood of happening over time.</li>
</ul>

<p>Connecting these concepts <strong>could suggest a lens</strong> for thinking about intervention, pointing toward possible ways to either address external factors or foster more adaptive internal responses.</p>

<hr />

<p>I had a curious thought cross my mind today, a sort of conceptual model for thinking about the dynamic nature of our internal worlds. It’s a way to bridge systems thinking, psychopathology, and some of the newer tools in computational modeling. This idea is in it’s infancy, and this represents my first attemp to get down the basics.</p>

<p>The idea begins with a framework I often come back to: modeling psychological distress, like suicidality, as a network of interacting factors. We often talk about internalizing factors (like rumination or negative self-talk) and externalizing factors (like impulsivity or aggression). We can visualize these as nodes in a directed acyclic graph (DAG), showing how one symptom can trigger another.</p>

<p>But here’s the thought that struck me as new: what if we modeled these internal factors not just as static nodes, but as <em>agents</em>?</p>

<h4 id="the-internal-system-a-society-of-agents">The Internal System: A Society of Agents</h4>

<p>Imagine the internal world as a collection of different “parts” or “voices,” a concept seen in therapeutic approaches like Internal Family Systems Therapy. It’s the idea that within us, we have different roles that take the lead at different times. There might be a “social planner,” a “harsh critic,” an “anxious protector,” or even a “wise advocate.”</p>

<p>Now, what if each of these parts was a computational agent, maybe even powered by a large language model? Each agent would have its own function, its own level of competence, and its own triggers for activation. When faced with a challenge, the system would “call upon” different agents to help regulate emotion or plan a response.</p>

<p>A system like this would be incredibly dynamic. The overall mood or behavior of the person wouldn’t be a simple sum of its parts, but an emergent property of the interactions between these agents. You could have a highly competent “internal clinical psychologist” agent that suggests helpful behavioral strategies, but it might be rarely activated because a louder, more reactive “anxious protector” agent always jumps in first.</p>

<p>This moves beyond thinking about intervention as simply a list of behavioral techniques. It frames it in a more dynamic, language-based way. How do we help the system learn to call on its more adaptive agents? How do we bolster the skills of a struggling agent?</p>

<h4 id="the-external-world-a-probabilistic-stream-of-events">The External World: A Probabilistic Stream of Events</h4>

<p>Of course, our internal system doesn’t operate in a vacuum. It’s constantly being influenced by the outside world. This is the second piece of the puzzle: modeling contextual factors.</p>

<p>Life events—a negative comment from a boss, a fight with a partner, getting good grades, a delayed train—are inputs that can either regulate or dysregulate our internal system. The interesting part is that while we can’t predict <em>exactly</em> when a specific negative event will happen, we can often model its general rate of occurrence.</p>

<p>A tool like a Poisson distribution immediately comes to mind, and it may be quite suitable for this. It’s used to model the probability of a given number of events happening in a fixed interval of time, assuming these events occur independently and at a constant average rate. So, for a person experiencing chronic social friction, their “negative social event” category would likely have a high rate of occurrence—a high lambda ($\lambda$) value, in statistical terms. This mathematically captures the idea of a continuous “stress load” that bombards the individual’s internal system. One could imagine having different probabilistic models for different categories of life events: work, social, health, etc. This would need to be considered more carefully, obviously.</p>

<hr />

<h4 id="bringing-it-all-together-in-a-unified-framework">Bringing It All Together in a Unified Framework</h4>

<p>This is where the directed acyclic graph comes back in. A DAG could visualize the entire system:</p>

<ol>
  <li><strong>External Event Nodes:</strong> These would be the probabilistic generators of life stressors, like “Academic Stress” or “Relationship Conflict,” each with its own rate of occurrence modeled by a Poisson distribution.</li>
  <li><strong>Internal Agent Nodes:</strong> These are the internal voices, like the “Self-Critic,” “Social Planner,” or “Problem-Solver.”</li>
  <li><strong>State Nodes:</strong> These could represent mood, anxiety levels, or energy.</li>
</ol>

<p>The edges of the graph would show the influence. An event from the “Academic Stress” node (e.g., a bad grade) might activate the “Self-Critic” agent. The “Self-Critic” agent, in turn, would strongly influence the “Mood” state node in a negative direction. Meanwhile, a positive event might activate a different, more helpful agent.</p>

<h4 id="a-conceptual-path-to-intervention">A Conceptual Path to Intervention</h4>

<p>Thinking about this as a case formulation is what really excites me. It makes the potential targets for change so clear. You could intervene in two fundamental ways:</p>

<ol>
  <li><strong>Treat the External Nodes:</strong> This involves addressing the contextual factors themselves. If the “Delayed Train” node is a major source of dysregulation, a practical intervention might be exploring alternative transportation. It’s a direct attempt to lower the rate of a specific stressor.</li>
  <li><strong>Treat the Internal Nodes:</strong> This is about changing the internal system’s response. It could involve “bolstering” the helpful agents—making the “internal psychologist” more skilled and more easily activated—or helping the system learn to down-regulate the unhelpful agents. This feels like a powerful computational metaphor for the work of psychotherapy.</li>
</ol>

<p>Ultimately, this is just a sketch. But I like how it brings together the internal, agentic self and the probabilistic nature of the external world into one dynamic system. It helps conceptualize a person not as a static collection of symptoms, but as a complex, self-regulating entity constantly adapting to a stream of life events. It’s a compelling way to think.</p>]]></content><author><name></name></author><category term="psychology" /><category term="computational clinical psychology" /><category term="network psychopathology" /><category term="complex systems theory" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">From Localhost to Live &amp;amp; Private: The Ultimate Jekyll Blog Setup Guide</title><link href="/coding/2025/07/01/ultimate-jekyll-blog-setup-guide.html" rel="alternate" type="text/html" title="From Localhost to Live &amp;amp; Private: The Ultimate Jekyll Blog Setup Guide" /><published>2025-07-01T15:00:00+00:00</published><updated>2025-07-01T15:00:00+00:00</updated><id>/coding/2025/07/01/ultimate-jekyll-blog-setup-guide</id><content type="html" xml:base="/coding/2025/07/01/ultimate-jekyll-blog-setup-guide.html"><![CDATA[<h1 id="from-localhost-to-live--private-the-ultimate-jekyll-blog-setup-guide">From Localhost to Live &amp; Private: The Ultimate Jekyll Blog Setup Guide</h1>

<p>So, you want to start a technical blog. You’re a developer. You don’t want a clunky, database-driven CMS. You want speed, version control, and the ability to write in your favorite editor using Markdown. You want <strong>Jekyll</strong>.</p>

<p>But setting up a blog is more than just <code class="language-plaintext highlighter-rouge">jekyll new</code>. What happens when posts don’t show up? How do you add downloadable code snippets? And most importantly, what if you want your blog to be <strong>private</strong>—a personal knowledge base or a pre-launch project accessible only to you and a select few?</p>

<p>This guide covers the entire lifecycle. We’ll go from a blank command line to a fully customized, password-protected Jekyll blog hosted for free. We’ll hit the common roadblocks and solve them, just like in a real-world project.</p>

<h2 id="part-1-local-setup---your-blogging-foundation">Part 1: Local Setup - Your Blogging Foundation</h2>

<p>First, let’s build the blog on your local machine. This allows you to write, preview, and customize everything before it ever touches the internet.</p>

<h3 id="step-1-install-the-tools">Step 1: Install the Tools</h3>

<p>Jekyll is built with Ruby. You’ll need Ruby and its package manager, RubyGems.</p>

<ul>
  <li><strong>On macOS (with Homebrew):</strong>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew <span class="nb">install </span>ruby
</code></pre></div>    </div>
  </li>
  <li><strong>On Linux (Debian/Ubuntu):</strong>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt update <span class="o">&amp;&amp;</span> <span class="nb">sudo </span>apt <span class="nb">install </span>ruby-full build-essential zlib1g-dev
</code></pre></div>    </div>
  </li>
  <li><strong>On Windows:</strong>
Download and run the <strong>Ruby+DevKit</strong> installer from <a href="https://rubyinstaller.org/downloads/">RubyInstaller for Windows</a>. During installation, accept the default to run <code class="language-plaintext highlighter-rouge">ridk install</code> on the last step.</li>
</ul>

<p>Once Ruby is installed, install Jekyll and Bundler (a tool to manage project-specific gems):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem <span class="nb">install </span>jekyll bundler
</code></pre></div></div>

<h3 id="step-2-create-and-run-your-blog">Step 2: Create and Run Your Blog</h3>

<p>Now, let’s create the blog and see it live on your machine.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 1. Create a new Jekyll site in a folder named "my-personal-blog"</span>
jekyll new my-personal-blog

<span class="c"># 2. Navigate into the new directory</span>
<span class="nb">cd </span>my-personal-blog

<span class="c"># 3. Install the specific gems this theme needs</span>
bundle <span class="nb">install</span>

<span class="c"># 4. Start the local development server</span>
bundle <span class="nb">exec </span>jekyll serve
</code></pre></div></div>

<p>Open your browser to <strong><code class="language-plaintext highlighter-rouge">http://localhost:4000</code></strong>. You should see your new Jekyll blog with the default Minima theme!</p>

<h3 id="step-3-your-first-blog-post">Step 3: Your First Blog Post</h3>

<p>Posts live in the <code class="language-plaintext highlighter-rouge">_posts</code> directory. The filename format is crucial: <code class="language-plaintext highlighter-rouge">YYYY-MM-DD-your-title-goes-here.md</code>.</p>

<p>Create a new file: <code class="language-plaintext highlighter-rouge">_posts/2025-07-15-my-first-real-post.md</code></p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">layout</span><span class="pi">:</span> <span class="s">post</span>
<span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">My</span><span class="nv"> </span><span class="s">First</span><span class="nv"> </span><span class="s">Real</span><span class="nv"> </span><span class="s">Post"</span>
<span class="na">date</span><span class="pi">:</span> <span class="s">2025-07-15 11:00:00 -0500</span>
<span class="na">categories</span><span class="pi">:</span> <span class="s">getting-started</span>
<span class="nn">---</span>

Welcome to my blog! This is content written in <span class="gs">**Markdown**</span>.

The section at the top between the <span class="sb">`---`</span> lines is called <span class="gs">**Front Matter**</span>. It's where you define metadata for the post, like its title and layout.
</code></pre></div></div>

<p>Save the file. The running server will automatically detect the change and rebuild your site. Refresh your browser, and your new post will appear.</p>

<h2 id="part-2-troubleshooting-the-inevitable-glitches">Part 2: Troubleshooting the Inevitable Glitches</h2>

<p>Your server is running, but things can go wrong. Here are the most common issues and how to fix them.</p>

<ul>
  <li><strong>Problem: “My new post isn’t showing up!”</strong>
    <ul>
      <li><strong>Cause:</strong> Jekyll, by default, will not publish posts with a date set in the future. If you wrote a post today and dated it for tomorrow, it won’t appear.</li>
      <li><strong>Solution:</strong> For local development, run the server with the <code class="language-plaintext highlighter-rouge">--future</code> flag to tell Jekyll to build all posts, regardless of their date.
        <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bundle <span class="nb">exec </span>jekyll serve <span class="nt">--future</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li><strong>Problem: “Server won’t start! <code class="language-plaintext highlighter-rouge">Address already in use</code>“</strong>
    <ul>
      <li><strong>Cause:</strong> You have another Jekyll server (or another process) already running on port 4000. This often happens if you closed a terminal window without stopping the server with <code class="language-plaintext highlighter-rouge">Ctrl+C</code>.</li>
      <li><strong>Solution:</strong> Start the server on a different port.
        <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bundle <span class="nb">exec </span>jekyll serve <span class="nt">--port</span> 4001
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li><strong>Problem: “<code class="language-plaintext highlighter-rouge">Could not locate Gemfile</code>” or “<code class="language-plaintext highlighter-rouge">jekyll: command not found</code>“</strong>
    <ul>
      <li><strong>Cause:</strong> You are trying to run the command from the wrong directory.</li>
      <li><strong>Solution:</strong> Make sure your terminal is inside your blog’s project folder (e.g., <code class="language-plaintext highlighter-rouge">my-personal-blog</code>) before running <code class="language-plaintext highlighter-rouge">bundle exec jekyll serve</code>.</li>
    </ul>
  </li>
</ul>

<h2 id="part-3-customizing-your-content">Part 3: Customizing Your Content</h2>

<p>A blog is more than just a list of posts. Let’s add sections and downloadable files.</p>

<h3 id="feature-1-creating-coding-and-psychology-sections">Feature 1: Creating “Coding” and “Psychology” Sections</h3>

<p>We’ll use Jekyll’s powerful <code class="language-plaintext highlighter-rouge">categories</code> feature.</p>

<ol>
  <li><strong>Categorize Your Posts:</strong> In the front matter of each post, assign a category.
    <ul>
      <li>For your technical post (<code class="language-plaintext highlighter-rouge">_posts/...-wsl2-guide.md</code>):
        <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">layout</span><span class="pi">:</span> <span class="s">post</span>
<span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Connecting</span><span class="nv"> </span><span class="s">Your</span><span class="nv"> </span><span class="s">MacBook</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">WSL2"</span>
<span class="na">categories</span><span class="pi">:</span> <span class="s">coding</span>
<span class="na">tags</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">wsl2</span><span class="pi">,</span> <span class="nv">vscode</span><span class="pi">,</span> <span class="nv">development</span><span class="pi">]</span>
<span class="nn">---</span>
</code></pre></div>        </div>
      </li>
      <li>For a future psychology post:
        <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">layout</span><span class="pi">:</span> <span class="s">post</span>
<span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">The</span><span class="nv"> </span><span class="s">Psychology</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">Code</span><span class="nv"> </span><span class="s">Reviews"</span>
<span class="na">categories</span><span class="pi">:</span> <span class="s">psychology</span>
<span class="nn">---</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li><strong>Create Category Pages:</strong> Now, create pages that will list all posts from a specific category.
    <ul>
      <li>Create <code class="language-plaintext highlighter-rouge">coding.md</code> in your root directory:
        <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">layout</span><span class="pi">:</span> <span class="s">page</span>
<span class="na">title</span><span class="pi">:</span> <span class="s">Coding</span>
<span class="na">permalink</span><span class="pi">:</span> <span class="s">/coding/</span>
<span class="nn">---</span>
        
<span class="gh"># Posts about Coding</span>
        
<span class="nt">&lt;ul&gt;</span>
  {% for post in site.categories.coding %}
    <span class="nt">&lt;li&gt;</span>
      <span class="nt">&lt;h3&gt;&lt;a</span> <span class="na">href=</span><span class="s">"{{ post.url | relative_url }}"</span><span class="nt">&gt;</span>{{ post.title }}<span class="nt">&lt;/a&gt;&lt;/h3&gt;</span>
      <span class="nt">&lt;p&gt;</span>{{ post.excerpt }}<span class="nt">&lt;/p&gt;</span>
    <span class="nt">&lt;/li&gt;</span>
  {% endfor %}
<span class="nt">&lt;/ul&gt;</span>
</code></pre></div>        </div>
      </li>
      <li>Create <code class="language-plaintext highlighter-rouge">psychology.md</code> in your root directory with the same content, but replace <code class="language-plaintext highlighter-rouge">site.categories.coding</code> with <code class="language-plaintext highlighter-rouge">site.categories.psychology</code>.</li>
    </ul>
  </li>
  <li><strong>Update Navigation:</strong> Add links to your new pages in <code class="language-plaintext highlighter-rouge">_config.yml</code> (for the Minima theme).
```yaml
header_pages:
    <ul>
      <li>about.md</li>
      <li>coding.md</li>
      <li>psychology.md
```</li>
    </ul>
  </li>
</ol>

<h3 id="feature-2-adding-downloadable-scripts">Feature 2: Adding Downloadable Scripts</h3>

<p>Let’s say your WSL2 guide has PowerShell scripts that readers should be able to download.</p>

<ol>
  <li><strong>Organize Your Files:</strong> The best practice is to store downloadable assets in the <code class="language-plaintext highlighter-rouge">assets</code> folder. Create a clear structure.
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>my-personal-blog/
└── assets/
    └── downloads/
        └── wsl2-automation/
            ├── setup-wsl2-ssh.ps1
            ├── update-wsl2-ip.ps1
            └── connect-wsl2.sh
</code></pre></div>    </div>
  </li>
  <li><strong>Link to the Files in Your Post:</strong> In your blog post’s Markdown, simply create a standard link pointing to the file.
    <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gu">### Download the Automation Scripts</span>
    
You can download the complete package of scripts to automate this entire process.
<span class="p">    
*</span>   <span class="p">[</span><span class="nv">Download setup-wsl2-ssh.ps1</span><span class="p">](</span><span class="sx">/assets/downloads/wsl2-automation/setup-wsl2-ssh.ps1</span><span class="p">)</span>
<span class="p">*</span>   <span class="p">[</span><span class="nv">Download update-wsl2-ip.ps1</span><span class="p">](</span><span class="sx">/assets/downloads/wsl2-automation/update-wsl2-ip.ps1</span><span class="p">)</span>
<span class="p">*</span>   <span class="p">[</span><span class="nv">Download connect-wsl2.sh</span><span class="p">](</span><span class="sx">/assets/downloads/wsl2-automation/connect-wsl2.sh</span><span class="p">)</span>
</code></pre></div>    </div>
    <p>Jekyll will automatically serve these files, and clicking the link will trigger a download.</p>
  </li>
</ol>

<h3 id="troubleshooting-what-if-the-downloads-give-a-404-error">Troubleshooting: What If the Downloads Give a 404 Error?</h3>

<p>You’ve set everything up, the links are in your post, but clicking them gives a “404 Not Found” error. This is a common hiccup when first setting up downloadable assets in Jekyll. Here are the most likely culprits, from most to least common:</p>

<h4 id="1-the-build-failed-silently">1. The Build Failed Silently</h4>

<p>This is the sneakiest and most common issue. Your downloads aren’t working because <strong>your entire Jekyll site isn’t building correctly</strong>. An error in a <em>completely different file</em> can prevent Jekyll from generating the <code class="language-plaintext highlighter-rouge">_site</code> folder, meaning your assets never get copied to their destination.</p>

<ul>
  <li><strong>Symptom:</strong> The local server crashes or your continuous deployment build fails on Netlify.</li>
  <li><strong>Likely Cause:</strong> You have a blog post (especially one <em>about</em> coding, like a Jekyll guide) that contains unescaped Liquid template tags (e.g., ``). Jekyll tries to process this code, leading to an error.</li>
  <li>
    <p><strong>The Fix:</strong> Wrap any example code that uses Liquid syntax in <code class="language-plaintext highlighter-rouge">and</code> tags in your Markdown files.</p>

    <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    
This is an example: {{ site.posts | size }}
    
</code></pre></div>    </div>
    <p>This tells Jekyll to ignore the code inside, fixing the build error.</p>
  </li>
</ul>

<h4 id="2-filename-mismatch-or-case-sensitivity">2. Filename Mismatch or Case Sensitivity</h4>

<p>Your deployment server (like Netlify) runs on Linux, which is <strong>case-sensitive</strong>. Your local machine (especially Windows) might not be.</p>

<ul>
  <li><strong>Check for Typos:</strong> Ensure the link in your post (<code class="language-plaintext highlighter-rouge">/assets/downloads/wsl2-automation/setup-wsl2-ssh.ps1</code>) exactly matches the filename in your Git repository.</li>
  <li><strong>Check for Case:</strong> <code class="language-plaintext highlighter-rouge">MyScript.ps1</code> is <strong>not</strong> the same as <code class="language-plaintext highlighter-rouge">myscript.ps1</code> on the server.</li>
</ul>

<h4 id="3-how-to-test-locally">3. How to Test Locally</h4>

<p>Before blaming the live server, confirm the files are being served correctly on your local machine.</p>

<ol>
  <li><strong>Start the local Jekyll server:</strong>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># In your blog's directory</span>
bundle <span class="nb">exec </span>jekyll serve <span class="nt">--port</span> 4001
</code></pre></div>    </div>
  </li>
  <li><strong>Use <code class="language-plaintext highlighter-rouge">curl</code> to check the file headers.</strong> This command doesn’t download the file, it just checks if the server can find it.
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-I</span> http://localhost:4001/assets/downloads/wsl2-automation/setup-wsl2-ssh.ps1
</code></pre></div>    </div>
  </li>
  <li><strong>Look for the <code class="language-plaintext highlighter-rouge">HTTP/1.1 200 OK</code> response.</strong>
    <ul>
      <li>✅ If you get <code class="language-plaintext highlighter-rouge">200 OK</code>, the file is correctly configured in your Jekyll project. The problem is with your live deployment.</li>
      <li>❌ If you get <code class="language-plaintext highlighter-rouge">404 Not Found</code>, the problem is in your local project structure. Go back and check steps 1 and 2.</li>
    </ul>
  </li>
</ol>

<p>By adding this section, you anticipate a common reader frustration and provide them with a clear, actionable debugging guide, making your post even more authoritative and helpful.</p>

<h2 id="part-4-the-private-web-host">Part 4: The Private Web Host</h2>

<p>This is the critical step. We want the blog live on the web, but <strong>not</strong> public. The source code and the live site must be private.</p>

<p><strong>Warning:</strong> Do <strong>not</strong> use standard GitHub Pages. Even with a private repository, a GitHub Pages site is <strong>always public</strong>.</p>

<p>Our weapon of choice is <strong>Netlify</strong>, paired with a <strong>private GitHub repository</strong>. This gives us free hosting, continuous deployment, and robust password protection.</p>

<h3 id="step-1-prepare-for-deployment-with-git">Step 1: Prepare for Deployment with Git</h3>

<ol>
  <li><strong>Create a <code class="language-plaintext highlighter-rouge">.gitignore</code> file:</strong> This file tells Git what <em>not</em> to track. It’s essential for keeping your repository clean. Create a file named <code class="language-plaintext highlighter-rouge">.gitignore</code> in your blog’s root with the following content:
    <pre><code class="language-gitignore"># Jekyll
_site/
.sass-cache/
.jekyll-cache/
.jekyll-metadata
    
# Ruby / Bundler
vendor/bundle/
.bundle/
    
# OS files
.DS_Store
Thumbs.db
    
# Logs and temp files
*.log
*~
</code></pre>
  </li>
  <li><strong>Create a Private GitHub Repository:</strong>
    <ul>
      <li>Go to GitHub and create a new repository.</li>
      <li>Give it a name (e.g., <code class="language-plaintext highlighter-rouge">private-jekyll-blog</code>).</li>
      <li><strong>Crucially, set its visibility to “Private”.</strong></li>
    </ul>
  </li>
  <li><strong>Push Your Code:</strong> In your terminal, from your blog’s directory, run these commands.
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git init
git add <span class="nb">.</span>
git commit <span class="nt">-m</span> <span class="s2">"Initial blog setup with content"</span>
git branch <span class="nt">-M</span> main
<span class="c"># Replace with your actual repo URL</span>
git remote add origin https://github.com/your-username/private-jekyll-blog.git
git push <span class="nt">-u</span> origin main
</code></pre></div>    </div>
    <p>Your blog’s source code is now securely stored on GitHub.</p>
  </li>
</ol>

<h3 id="step-2-deploy-and-secure-with-netlify">Step 2: Deploy and Secure with Netlify</h3>

<ol>
  <li><strong>Connect Netlify to GitHub:</strong>
    <ul>
      <li>Sign up for a free account at <a href="https://netlify.com">Netlify.com</a>.</li>
      <li>Click “Add new site” -&gt; “Import an existing project” -&gt; “Deploy with GitHub”.</li>
      <li>Authorize Netlify to access your repositories and select your new private repo.</li>
    </ul>
  </li>
  <li><strong>Configure Build Settings:</strong> Netlify will auto-detect that it’s a Jekyll site, but let’s confirm the settings are correct:
    <ul>
      <li><strong>Build command:</strong> <code class="language-plaintext highlighter-rouge">bundle exec jekyll build</code></li>
      <li><strong>Publish directory:</strong> <code class="language-plaintext highlighter-rouge">_site</code></li>
      <li>Click “Deploy site”. Netlify will now pull your code from GitHub and build your site.</li>
    </ul>
  </li>
  <li><strong>Enable Password Protection (Netlify Identity):</strong>
    <ul>
      <li>In your new site’s dashboard on Netlify, go to <strong>Site settings &gt; Identity</strong>.</li>
      <li>Click <strong>Enable Identity</strong>.</li>
      <li>Scroll down to “Registration” and set the preference to <strong>“Invite only”</strong>. This ensures only people you explicitly invite can create an account.</li>
      <li>Go to the <strong>Identity</strong> tab (in the top navigation) and click <strong>“Invite users”</strong> to add yourself and anyone else who needs access. They will receive an email to set their password.</li>
    </ul>
  </li>
  <li><strong>Add the Login Widget to Your Site:</strong> To trigger the login modal, you need to add Netlify’s script.
    <ul>
      <li>In your Jekyll project, create the file <code class="language-plaintext highlighter-rouge">_includes/head.html</code>.</li>
      <li>Inside this new file, add the following line:
        <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;script </span><span class="na">type=</span><span class="s">"text/javascript"</span> <span class="na">src=</span><span class="s">"https://identity.netlify.com/v1/netlify-identity-widget.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
</code></pre></div>        </div>
        <p><em>This overrides the theme’s default <code class="language-plaintext highlighter-rouge">head</code> include to add the script.</em></p>
      </li>
    </ul>
  </li>
  <li><strong>Commit and Push the Change:</strong>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git add _includes/head.html
git commit <span class="nt">-m</span> <span class="s2">"Add Netlify Identity widget"</span>
git push
</code></pre></div>    </div>
  </li>
</ol>

<p>Netlify will automatically detect the push, rebuild your site, and deploy the new version. Now, when anyone visits your site’s URL, they will be greeted by the Netlify Identity login modal. Only invited and registered users can get past it.</p>

<h2 id="part-5-connecting-your-custom-domain-from-cloudflare">Part 5: Connecting Your Custom Domain (from Cloudflare)</h2>

<p>Your site is live on a <code class="language-plaintext highlighter-rouge">.netlify.app</code> address, but the final professional touch is using your own domain. This guide shows how to connect a domain managed by Cloudflare to your Netlify site, which is a powerful and common setup.</p>

<h3 id="prerequisites">Prerequisites</h3>

<ol>
  <li><strong>Domain in Cloudflare:</strong> You have successfully purchased your domain name through Cloudflare Registrar.</li>
  <li><strong>Site on Netlify:</strong> Your Jekyll site is deployed on Netlify and has a default URL like <code class="language-plaintext highlighter-rouge">your-awesome-site.netlify.app</code>.</li>
</ol>

<h3 id="step-1-get-dns-information-from-netlify">Step 1: Get DNS Information from Netlify</h3>

<ol>
  <li>Log in to your Netlify account and go to the dashboard for your site.</li>
  <li>Navigate to <strong>Site configuration</strong> &gt; <strong>Domain management</strong>.</li>
  <li>Click <strong>Add a domain</strong> (or <strong>Add custom domain</strong>).</li>
  <li>Enter your custom domain name (e.g., <code class="language-plaintext highlighter-rouge">yourdomain.com</code>) and click <strong>Verify</strong>.</li>
  <li>Netlify will detect that the domain is already managed elsewhere. Click <strong>Add domain</strong>.</li>
  <li>Netlify will now show you the DNS records you need to create. It will be an <strong>A record</strong> for your root domain (<code class="language-plaintext highlighter-rouge">yourdomain.com</code>) pointing to Netlify’s load balancer IP address (e.g., <code class="language-plaintext highlighter-rouge">75.2.60.5</code>) and a <strong>CNAME record</strong> for the <code class="language-plaintext highlighter-rouge">www</code> subdomain pointing to your Netlify site address (<code class="language-plaintext highlighter-rouge">your-awesome-site.netlify.app</code>). Keep this page open.</li>
</ol>

<h3 id="step-2-configure-dns-records-in-cloudflare">Step 2: Configure DNS Records in Cloudflare</h3>

<ol>
  <li>Log in to your Cloudflare account and select your domain.</li>
  <li>On the left sidebar, click on <strong>DNS</strong>.</li>
  <li>
    <p>You may need to delete any existing placeholder <code class="language-plaintext highlighter-rouge">A</code>, <code class="language-plaintext highlighter-rouge">AAAA</code>, or <code class="language-plaintext highlighter-rouge">CNAME</code> records that would conflict with the new ones.</p>
  </li>
  <li><strong>Create the A Record (for the root domain):</strong>
    <ul>
      <li>Click <strong>Add record</strong>.</li>
      <li><strong>Type:</strong> <code class="language-plaintext highlighter-rouge">A</code></li>
      <li><strong>Name:</strong> <code class="language-plaintext highlighter-rouge">@</code> (This represents your root domain).</li>
      <li><strong>IPv4 address:</strong> <code class="language-plaintext highlighter-rouge">75.2.60.5</code> (Always use the IP address shown in your Netlify dashboard).</li>
      <li><strong>Proxy status:</strong> Leave it as <strong>Proxied</strong> (orange cloud). This is crucial for enabling Cloudflare’s features.</li>
      <li>Click <strong>Save</strong>.</li>
    </ul>
  </li>
  <li><strong>Create the CNAME Record (for the ‘www’ subdomain):</strong>
    <ul>
      <li>Click <strong>Add record</strong> again.</li>
      <li><strong>Type:</strong> <code class="language-plaintext highlighter-rouge">CNAME</code></li>
      <li><strong>Name:</strong> <code class="language-plaintext highlighter-rouge">www</code></li>
      <li><strong>Target:</strong> <code class="language-plaintext highlighter-rouge">your-awesome-site.netlify.app</code> (Use your unique Netlify site URL).</li>
      <li><strong>Proxy status:</strong> Leave as <strong>Proxied</strong> (orange cloud).</li>
      <li>Click <strong>Save</strong>.</li>
    </ul>
  </li>
</ol>

<p>Your Cloudflare DNS settings should now look like this:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Type</th>
      <th style="text-align: left">Name</th>
      <th style="text-align: left">Content</th>
      <th style="text-align: left">Proxy Status</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">A</td>
      <td style="text-align: left">@</td>
      <td style="text-align: left">75.2.60.5</td>
      <td style="text-align: left">Proxied</td>
    </tr>
    <tr>
      <td style="text-align: left">CNAME</td>
      <td style="text-align: left">www</td>
      <td style="text-align: left">your-awesome-site.netlify.app</td>
      <td style="text-align: left">Proxied</td>
    </tr>
  </tbody>
</table>

<h3 id="step-3-configure-ssltls-in-cloudflare">Step 3: Configure SSL/TLS in Cloudflare</h3>

<p>This is a critical step to prevent redirect errors.</p>
<ol>
  <li>In your Cloudflare dashboard, go to <strong>SSL/TLS</strong> &gt; <strong>Overview</strong>.</li>
  <li>Set your encryption mode to <strong>Full (Strict)</strong>.
    <ul>
      <li><strong>Why?</strong> Netlify provides its own SSL certificate. This setting tells Cloudflare to maintain a secure, encrypted connection all the way to Netlify’s servers.</li>
    </ul>
  </li>
</ol>

<h3 id="step-4-verification-and-finalization">Step 4: Verification and Finalization</h3>

<ol>
  <li>Return to your Netlify domain management page. DNS changes can take a few minutes to a few hours to propagate.</li>
  <li>Netlify will automatically detect the new records and begin provisioning a free Let’s Encrypt SSL certificate for your domain.</li>
  <li>Once complete, your custom domain will be listed as the primary domain. Your site is now live and secure at <code class="language-plaintext highlighter-rouge">https://yourdomain.com</code>.</li>
</ol>

<h2 id="conclusion-your-professional-blogging-workflow">Conclusion: Your Professional Blogging Workflow</h2>

<p>You’ve done it. You now have:</p>
<ul>
  <li>A fast, modern Jekyll blog running locally.</li>
  <li>A clean, organized content structure with categories and downloads.</li>
  <li>A private Git repository for version control.</li>
  <li>A live, password-protected website with free hosting and automatic deployment.</li>
  <li>A professional custom domain from Cloudflare pointing to your site.</li>
</ul>

<p>Your workflow is now beautifully simple:</p>
<ol>
  <li>Write a new post in Markdown.</li>
  <li>Preview it locally with <code class="language-plaintext highlighter-rouge">bundle exec jekyll serve</code>.</li>
  <li>Commit and push your changes with <code class="language-plaintext highlighter-rouge">git push</code>.</li>
</ol>

<p>Within minutes, your private blog is updated on your custom domain. You have full control, professional-grade security, and a powerful platform to build your knowledge base. Happy blogging!</p>

<hr />

<h2 id="frequently-asked-questions-faq">Frequently Asked Questions (FAQ)</h2>

<p><strong>Q: Is Netlify Identity the only way to password-protect my site?</strong></p>

<p>A: For the free tier, Netlify Identity is the most powerful and recommended method. Netlify’s Pro plans offer a simpler site-wide Basic Password Protection, but Identity provides role-based access and is more flexible.</p>

<p><strong>Q: Why is the <code class="language-plaintext highlighter-rouge">Full (Strict)</code> SSL setting in Cloudflare so important?</strong></p>

<p>A: This setting ensures the connection is encrypted end-to-end: from the user’s browser to Cloudflare, and from Cloudflare to Netlify’s servers. If you use <code class="language-plaintext highlighter-rouge">Flexible</code>, the connection from Cloudflare to Netlify is unencrypted (HTTP). This often causes a “redirect loop” error, because Netlify automatically tries to redirect HTTP traffic to secure HTTPS, creating a conflict.</p>

<p><strong>Q: Do I have to pay for anything in this setup?</strong></p>

<p>A: The only mandatory cost is purchasing your domain name from Cloudflare. The entire workflow described—private GitHub repository, Jekyll software, Netlify hosting with continuous deployment, and Netlify Identity for a small number of users—is free. Costs would only be incurred if you exceed Netlify’s generous free tier limits for bandwidth or build minutes.</p>]]></content><author><name></name></author><category term="coding" /><category term="jekyll" /><category term="blogging" /><category term="deployment" /><category term="netlify" /><category term="tutorial" /><category term="guide" /><summary type="html"><![CDATA[From Localhost to Live &amp; Private: The Ultimate Jekyll Blog Setup Guide So, you want to start a technical blog. You’re a developer. You don’t want a clunky, database-driven CMS. You want speed, version control, and the ability to write in your favorite editor using Markdown. You want Jekyll. But setting up a blog is more than just jekyll new. What happens when posts don’t show up? How do you add downloadable code snippets? And most importantly, what if you want your blog to be private—a personal knowledge base or a pre-launch project accessible only to you and a select few? This guide covers the entire lifecycle. We’ll go from a blank command line to a fully customized, password-protected Jekyll blog hosted for free. We’ll hit the common roadblocks and solve them, just like in a real-world project. Part 1: Local Setup - Your Blogging Foundation First, let’s build the blog on your local machine. This allows you to write, preview, and customize everything before it ever touches the internet. Step 1: Install the Tools Jekyll is built with Ruby. You’ll need Ruby and its package manager, RubyGems. On macOS (with Homebrew): brew install ruby On Linux (Debian/Ubuntu): sudo apt update &amp;&amp; sudo apt install ruby-full build-essential zlib1g-dev On Windows: Download and run the Ruby+DevKit installer from RubyInstaller for Windows. During installation, accept the default to run ridk install on the last step. Once Ruby is installed, install Jekyll and Bundler (a tool to manage project-specific gems): gem install jekyll bundler Step 2: Create and Run Your Blog Now, let’s create the blog and see it live on your machine. # 1. Create a new Jekyll site in a folder named "my-personal-blog" jekyll new my-personal-blog # 2. Navigate into the new directory cd my-personal-blog # 3. Install the specific gems this theme needs bundle install # 4. Start the local development server bundle exec jekyll serve Open your browser to http://localhost:4000. You should see your new Jekyll blog with the default Minima theme! Step 3: Your First Blog Post Posts live in the _posts directory. The filename format is crucial: YYYY-MM-DD-your-title-goes-here.md. Create a new file: _posts/2025-07-15-my-first-real-post.md --- layout: post title: "My First Real Post" date: 2025-07-15 11:00:00 -0500 categories: getting-started --- Welcome to my blog! This is content written in **Markdown**. The section at the top between the `---` lines is called **Front Matter**. It's where you define metadata for the post, like its title and layout. Save the file. The running server will automatically detect the change and rebuild your site. Refresh your browser, and your new post will appear. Part 2: Troubleshooting the Inevitable Glitches Your server is running, but things can go wrong. Here are the most common issues and how to fix them. Problem: “My new post isn’t showing up!” Cause: Jekyll, by default, will not publish posts with a date set in the future. If you wrote a post today and dated it for tomorrow, it won’t appear. Solution: For local development, run the server with the --future flag to tell Jekyll to build all posts, regardless of their date. bundle exec jekyll serve --future Problem: “Server won’t start! Address already in use“ Cause: You have another Jekyll server (or another process) already running on port 4000. This often happens if you closed a terminal window without stopping the server with Ctrl+C. Solution: Start the server on a different port. bundle exec jekyll serve --port 4001 Problem: “Could not locate Gemfile” or “jekyll: command not found“ Cause: You are trying to run the command from the wrong directory. Solution: Make sure your terminal is inside your blog’s project folder (e.g., my-personal-blog) before running bundle exec jekyll serve. Part 3: Customizing Your Content A blog is more than just a list of posts. Let’s add sections and downloadable files. Feature 1: Creating “Coding” and “Psychology” Sections We’ll use Jekyll’s powerful categories feature. Categorize Your Posts: In the front matter of each post, assign a category. For your technical post (_posts/...-wsl2-guide.md): --- layout: post title: "Connecting Your MacBook to WSL2" categories: coding tags: [wsl2, vscode, development] --- For a future psychology post: --- layout: post title: "The Psychology of Code Reviews" categories: psychology --- Create Category Pages: Now, create pages that will list all posts from a specific category. Create coding.md in your root directory: --- layout: page title: Coding permalink: /coding/ --- # Posts about Coding &lt;ul&gt; {% for post in site.categories.coding %} &lt;li&gt; &lt;h3&gt;&lt;a href="{{ post.url | relative_url }}"&gt;{{ post.title }}&lt;/a&gt;&lt;/h3&gt; &lt;p&gt;{{ post.excerpt }}&lt;/p&gt; &lt;/li&gt; {% endfor %} &lt;/ul&gt; Create psychology.md in your root directory with the same content, but replace site.categories.coding with site.categories.psychology. Update Navigation: Add links to your new pages in _config.yml (for the Minima theme). ```yaml header_pages: about.md coding.md psychology.md ``` Feature 2: Adding Downloadable Scripts Let’s say your WSL2 guide has PowerShell scripts that readers should be able to download. Organize Your Files: The best practice is to store downloadable assets in the assets folder. Create a clear structure. my-personal-blog/ └── assets/ └── downloads/ └── wsl2-automation/ ├── setup-wsl2-ssh.ps1 ├── update-wsl2-ip.ps1 └── connect-wsl2.sh Link to the Files in Your Post: In your blog post’s Markdown, simply create a standard link pointing to the file. ### Download the Automation Scripts You can download the complete package of scripts to automate this entire process. * [Download setup-wsl2-ssh.ps1](/assets/downloads/wsl2-automation/setup-wsl2-ssh.ps1) * [Download update-wsl2-ip.ps1](/assets/downloads/wsl2-automation/update-wsl2-ip.ps1) * [Download connect-wsl2.sh](/assets/downloads/wsl2-automation/connect-wsl2.sh) Jekyll will automatically serve these files, and clicking the link will trigger a download. Troubleshooting: What If the Downloads Give a 404 Error? You’ve set everything up, the links are in your post, but clicking them gives a “404 Not Found” error. This is a common hiccup when first setting up downloadable assets in Jekyll. Here are the most likely culprits, from most to least common: 1. The Build Failed Silently This is the sneakiest and most common issue. Your downloads aren’t working because your entire Jekyll site isn’t building correctly. An error in a completely different file can prevent Jekyll from generating the _site folder, meaning your assets never get copied to their destination. Symptom: The local server crashes or your continuous deployment build fails on Netlify. Likely Cause: You have a blog post (especially one about coding, like a Jekyll guide) that contains unescaped Liquid template tags (e.g., ``). Jekyll tries to process this code, leading to an error. The Fix: Wrap any example code that uses Liquid syntax in and tags in your Markdown files. This is an example: {{ site.posts | size }} This tells Jekyll to ignore the code inside, fixing the build error. 2. Filename Mismatch or Case Sensitivity Your deployment server (like Netlify) runs on Linux, which is case-sensitive. Your local machine (especially Windows) might not be. Check for Typos: Ensure the link in your post (/assets/downloads/wsl2-automation/setup-wsl2-ssh.ps1) exactly matches the filename in your Git repository. Check for Case: MyScript.ps1 is not the same as myscript.ps1 on the server. 3. How to Test Locally Before blaming the live server, confirm the files are being served correctly on your local machine. Start the local Jekyll server: # In your blog's directory bundle exec jekyll serve --port 4001 Use curl to check the file headers. This command doesn’t download the file, it just checks if the server can find it. curl -I http://localhost:4001/assets/downloads/wsl2-automation/setup-wsl2-ssh.ps1 Look for the HTTP/1.1 200 OK response. ✅ If you get 200 OK, the file is correctly configured in your Jekyll project. The problem is with your live deployment. ❌ If you get 404 Not Found, the problem is in your local project structure. Go back and check steps 1 and 2. By adding this section, you anticipate a common reader frustration and provide them with a clear, actionable debugging guide, making your post even more authoritative and helpful. Part 4: The Private Web Host This is the critical step. We want the blog live on the web, but not public. The source code and the live site must be private. Warning: Do not use standard GitHub Pages. Even with a private repository, a GitHub Pages site is always public. Our weapon of choice is Netlify, paired with a private GitHub repository. This gives us free hosting, continuous deployment, and robust password protection. Step 1: Prepare for Deployment with Git Create a .gitignore file: This file tells Git what not to track. It’s essential for keeping your repository clean. Create a file named .gitignore in your blog’s root with the following content: # Jekyll _site/ .sass-cache/ .jekyll-cache/ .jekyll-metadata # Ruby / Bundler vendor/bundle/ .bundle/ # OS files .DS_Store Thumbs.db # Logs and temp files *.log *~ Create a Private GitHub Repository: Go to GitHub and create a new repository. Give it a name (e.g., private-jekyll-blog). Crucially, set its visibility to “Private”. Push Your Code: In your terminal, from your blog’s directory, run these commands. git init git add . git commit -m "Initial blog setup with content" git branch -M main # Replace with your actual repo URL git remote add origin https://github.com/your-username/private-jekyll-blog.git git push -u origin main Your blog’s source code is now securely stored on GitHub. Step 2: Deploy and Secure with Netlify Connect Netlify to GitHub: Sign up for a free account at Netlify.com. Click “Add new site” -&gt; “Import an existing project” -&gt; “Deploy with GitHub”. Authorize Netlify to access your repositories and select your new private repo. Configure Build Settings: Netlify will auto-detect that it’s a Jekyll site, but let’s confirm the settings are correct: Build command: bundle exec jekyll build Publish directory: _site Click “Deploy site”. Netlify will now pull your code from GitHub and build your site. Enable Password Protection (Netlify Identity): In your new site’s dashboard on Netlify, go to Site settings &gt; Identity. Click Enable Identity. Scroll down to “Registration” and set the preference to “Invite only”. This ensures only people you explicitly invite can create an account. Go to the Identity tab (in the top navigation) and click “Invite users” to add yourself and anyone else who needs access. They will receive an email to set their password. Add the Login Widget to Your Site: To trigger the login modal, you need to add Netlify’s script. In your Jekyll project, create the file _includes/head.html. Inside this new file, add the following line: &lt;script type="text/javascript" src="https://identity.netlify.com/v1/netlify-identity-widget.js"&gt;&lt;/script&gt; This overrides the theme’s default head include to add the script. Commit and Push the Change: git add _includes/head.html git commit -m "Add Netlify Identity widget" git push Netlify will automatically detect the push, rebuild your site, and deploy the new version. Now, when anyone visits your site’s URL, they will be greeted by the Netlify Identity login modal. Only invited and registered users can get past it. Part 5: Connecting Your Custom Domain (from Cloudflare) Your site is live on a .netlify.app address, but the final professional touch is using your own domain. This guide shows how to connect a domain managed by Cloudflare to your Netlify site, which is a powerful and common setup. Prerequisites Domain in Cloudflare: You have successfully purchased your domain name through Cloudflare Registrar. Site on Netlify: Your Jekyll site is deployed on Netlify and has a default URL like your-awesome-site.netlify.app. Step 1: Get DNS Information from Netlify Log in to your Netlify account and go to the dashboard for your site. Navigate to Site configuration &gt; Domain management. Click Add a domain (or Add custom domain). Enter your custom domain name (e.g., yourdomain.com) and click Verify. Netlify will detect that the domain is already managed elsewhere. Click Add domain. Netlify will now show you the DNS records you need to create. It will be an A record for your root domain (yourdomain.com) pointing to Netlify’s load balancer IP address (e.g., 75.2.60.5) and a CNAME record for the www subdomain pointing to your Netlify site address (your-awesome-site.netlify.app). Keep this page open. Step 2: Configure DNS Records in Cloudflare Log in to your Cloudflare account and select your domain. On the left sidebar, click on DNS. You may need to delete any existing placeholder A, AAAA, or CNAME records that would conflict with the new ones. Create the A Record (for the root domain): Click Add record. Type: A Name: @ (This represents your root domain). IPv4 address: 75.2.60.5 (Always use the IP address shown in your Netlify dashboard). Proxy status: Leave it as Proxied (orange cloud). This is crucial for enabling Cloudflare’s features. Click Save. Create the CNAME Record (for the ‘www’ subdomain): Click Add record again. Type: CNAME Name: www Target: your-awesome-site.netlify.app (Use your unique Netlify site URL). Proxy status: Leave as Proxied (orange cloud). Click Save. Your Cloudflare DNS settings should now look like this: Type Name Content Proxy Status A @ 75.2.60.5 Proxied CNAME www your-awesome-site.netlify.app Proxied Step 3: Configure SSL/TLS in Cloudflare This is a critical step to prevent redirect errors. In your Cloudflare dashboard, go to SSL/TLS &gt; Overview. Set your encryption mode to Full (Strict). Why? Netlify provides its own SSL certificate. This setting tells Cloudflare to maintain a secure, encrypted connection all the way to Netlify’s servers. Step 4: Verification and Finalization Return to your Netlify domain management page. DNS changes can take a few minutes to a few hours to propagate. Netlify will automatically detect the new records and begin provisioning a free Let’s Encrypt SSL certificate for your domain. Once complete, your custom domain will be listed as the primary domain. Your site is now live and secure at https://yourdomain.com. Conclusion: Your Professional Blogging Workflow You’ve done it. You now have: A fast, modern Jekyll blog running locally. A clean, organized content structure with categories and downloads. A private Git repository for version control. A live, password-protected website with free hosting and automatic deployment. A professional custom domain from Cloudflare pointing to your site. Your workflow is now beautifully simple: Write a new post in Markdown. Preview it locally with bundle exec jekyll serve. Commit and push your changes with git push. Within minutes, your private blog is updated on your custom domain. You have full control, professional-grade security, and a powerful platform to build your knowledge base. Happy blogging! Frequently Asked Questions (FAQ) Q: Is Netlify Identity the only way to password-protect my site? A: For the free tier, Netlify Identity is the most powerful and recommended method. Netlify’s Pro plans offer a simpler site-wide Basic Password Protection, but Identity provides role-based access and is more flexible. Q: Why is the Full (Strict) SSL setting in Cloudflare so important? A: This setting ensures the connection is encrypted end-to-end: from the user’s browser to Cloudflare, and from Cloudflare to Netlify’s servers. If you use Flexible, the connection from Cloudflare to Netlify is unencrypted (HTTP). This often causes a “redirect loop” error, because Netlify automatically tries to redirect HTTP traffic to secure HTTPS, creating a conflict. Q: Do I have to pay for anything in this setup? A: The only mandatory cost is purchasing your domain name from Cloudflare. The entire workflow described—private GitHub repository, Jekyll software, Netlify hosting with continuous deployment, and Netlify Identity for a small number of users—is free. Costs would only be incurred if you exceed Netlify’s generous free tier limits for bandwidth or build minutes.]]></summary></entry><entry><title type="html">The Ultimate Guide: Connecting Your MacBook to WSL2 on Windows with VS Code</title><link href="/coding/2025/06/30/ultimate-guide-macbook-wsl2-vscode.html" rel="alternate" type="text/html" title="The Ultimate Guide: Connecting Your MacBook to WSL2 on Windows with VS Code" /><published>2025-06-30T21:30:00+00:00</published><updated>2025-06-30T21:30:00+00:00</updated><id>/coding/2025/06/30/ultimate-guide-macbook-wsl2-vscode</id><content type="html" xml:base="/coding/2025/06/30/ultimate-guide-macbook-wsl2-vscode.html"><![CDATA[<p>As a developer, you want the best tools for the job. You love the sleekness and Unix-based environment of your MacBook Air, but you also have a powerhouse Windows 11 Pro desktop. How do you get the best of both worlds? How can you use your Mac to develop inside a full-fledged Linux environment running on your Windows machine?</p>

<p>The answer lies in a powerful combination: <strong>Windows Subsystem for Linux (WSL2)</strong>, <strong>SSH</strong>, and <strong>VS Code’s Remote Development extension</strong>.</p>

<p>This guide will walk you through the entire process, from setting up WSL2 on your Windows machine to seamlessly connecting and coding from your MacBook. We’ll cover the manual setup so you understand the mechanics, and then show you how to automate the process to handle common issues like changing IP addresses.</p>

<h2 id="the-big-picture-how-does-this-work">The Big Picture: How Does This Work?</h2>

<p>Before we dive in, let’s understand the architecture. Your MacBook can’t directly “see” the Linux instance running inside WSL2, as it lives in its own virtual network on your Windows machine. We need to create a bridge.</p>

<ol>
  <li><strong>WSL2 Ubuntu:</strong> Runs a full Linux environment with its own private IP address. We’ll install an SSH server here.</li>
  <li><strong>Windows 11 Pro:</strong> Acts as the host. We will configure it to forward traffic from one of its network ports to the SSH port on the WSL2 instance.</li>
  <li><strong>Your MacBook:</strong> Will connect via SSH to your <em>Windows machine’s</em> IP address on the special forwarded port, which Windows will then transparently route to WSL2.</li>
</ol>

<hr />

<h2 id="part-1-setting-up-the-host-windows-11-pro">Part 1: Setting Up the Host (Windows 11 Pro)</h2>

<p>First, we need to get WSL2 and our Ubuntu environment ready to accept connections.</p>

<h3 id="step-1-install-wsl2-and-ubuntu">Step 1: Install WSL2 and Ubuntu</h3>

<p>If you haven’t already, installing WSL2 and the default Ubuntu distribution is a one-line command. Open <strong>PowerShell as an Administrator</strong> and run:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">wsl</span><span class="w"> </span><span class="nt">--install</span><span class="w"> </span><span class="nt">-d</span><span class="w"> </span><span class="nx">Ubuntu</span><span class="w">
</span></code></pre></div></div>

<p>This command will enable the necessary Windows features (<code class="language-plaintext highlighter-rouge">Virtual Machine Platform</code>, <code class="language-plaintext highlighter-rouge">Windows Subsystem for Linux</code>), download the latest Linux kernel, and install the Ubuntu distribution. Restart your computer if prompted.</p>

<h3 id="step-2-configure-the-ubuntu-ssh-server">Step 2: Configure the Ubuntu SSH Server</h3>

<p>Once installed, launch your new Ubuntu environment from the Start Menu. Now, let’s set up the SSH server inside it.</p>

<ol>
  <li>
    <p><strong>Update and Install OpenSSH Server:</strong>
It’s always good practice to update your package lists before installing new software.</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Inside the Ubuntu WSL2 terminal</span>
<span class="nb">sudo </span>apt update
<span class="nb">sudo </span>apt <span class="nb">install </span>openssh-server
</code></pre></div>    </div>
  </li>
  <li>
    <p><strong>Start and Enable the SSH Service:</strong>
We need to start the SSH service and ensure it launches automatically every time WSL2 starts.</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>service ssh start
   
<span class="c"># Optional but recommended: enable the service to start on boot</span>
<span class="nb">sudo </span>systemctl <span class="nb">enable </span>ssh
</code></pre></div>    </div>

    <p>You can verify it’s running with <code class="language-plaintext highlighter-rouge">sudo service ssh status</code>.</p>
  </li>
</ol>

<h3 id="step-3-create-the-network-bridge-port-forwarding">Step 3: Create the Network Bridge (Port Forwarding)</h3>

<p>This is the most critical step. We need to tell Windows to forward traffic to our WSL2 instance.</p>

<ol>
  <li>
    <p><strong>Find the WSL2 IP Address:</strong>
Your WSL2 instance has a dynamic IP address that can change each time it restarts. Find the current one by running this command inside the Ubuntu terminal:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># This will print the IP address, e.g., 172.28.17.73</span>
<span class="nb">hostname</span> <span class="nt">-I</span>
</code></pre></div>    </div>
    <p>Take note of this IP address. For our example, we’ll use <code class="language-plaintext highlighter-rouge">172.28.17.73</code>.</p>
  </li>
  <li>
    <p><strong>Set Up Port Forwarding in PowerShell:</strong>
<strong>Exit</strong> the Ubuntu terminal and go back to a <strong>PowerShell window running as Administrator</strong>. We will forward port <code class="language-plaintext highlighter-rouge">2222</code> on Windows to port <code class="language-plaintext highlighter-rouge">22</code> (the default SSH port) on our WSL2 instance.</p>

    <blockquote>
      <p><strong>Note:</strong> Replace <code class="language-plaintext highlighter-rouge">172.28.17.73</code> with the IP address you found in the previous step.</p>
    </blockquote>

    <div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># In an Administrator PowerShell</span><span class="w">
</span><span class="n">netsh</span><span class="w"> </span><span class="nx">interface</span><span class="w"> </span><span class="nx">portproxy</span><span class="w"> </span><span class="nx">add</span><span class="w"> </span><span class="nx">v4tov4</span><span class="w"> </span><span class="nx">listenport</span><span class="o">=</span><span class="mi">2222</span><span class="w"> </span><span class="n">listenaddress</span><span class="o">=</span><span class="mf">0.0</span><span class="o">.</span><span class="nf">0</span><span class="o">.</span><span class="nf">0</span><span class="w"> </span><span class="n">connectport</span><span class="o">=</span><span class="mi">22</span><span class="w"> </span><span class="n">connectaddress</span><span class="o">=</span><span class="mf">172.28</span><span class="o">.</span><span class="nf">17</span><span class="o">.</span><span class="nf">73</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>
    <p><strong>Create a Firewall Rule:</strong>
Windows Firewall will block the new port by default. Let’s create a rule to allow incoming traffic on port <code class="language-plaintext highlighter-rouge">2222</code>.</p>

    <div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># In the same Administrator PowerShell</span><span class="w">
</span><span class="n">New-NetFirewallRule</span><span class="w"> </span><span class="nt">-DisplayName</span><span class="w"> </span><span class="s2">"WSL2 SSH Bridge"</span><span class="w"> </span><span class="nt">-Direction</span><span class="w"> </span><span class="nx">Inbound</span><span class="w"> </span><span class="nt">-LocalPort</span><span class="w"> </span><span class="nx">2222</span><span class="w"> </span><span class="nt">-Protocol</span><span class="w"> </span><span class="nx">TCP</span><span class="w"> </span><span class="nt">-Action</span><span class="w"> </span><span class="nx">Allow</span><span class="w">
</span></code></pre></div>    </div>
  </li>
</ol>

<p>Your Windows host is now ready!</p>

<hr />

<h2 id="part-2-connecting-from-your-macbook">Part 2: Connecting From Your MacBook</h2>

<p>Now for the fun part. Let’s connect from your MacBook using VS Code.</p>

<h3 id="step-1-find-your-windows-ip-address">Step 1: Find Your Windows IP Address</h3>

<p>You need the local network IP of your Windows machine. On the Windows PC, open a regular Command Prompt or PowerShell and run:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ipconfig</span><span class="w">
</span></code></pre></div></div>

<p>Look for the “IPv4 Address” under your active Wi-Fi or Ethernet adapter. It will likely look something like <code class="language-plaintext highlighter-rouge">192.168.0.113</code>.</p>

<h3 id="step-2-connect-with-vs-code-remote-ssh">Step 2: Connect with VS Code Remote-SSH</h3>

<p>On your MacBook, make sure you have the <a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack">Remote Development extension pack</a> installed in VS Code.</p>

<ol>
  <li>Open VS Code and press <code class="language-plaintext highlighter-rouge">Cmd+Shift+P</code> to open the Command Palette.</li>
  <li>Type <code class="language-plaintext highlighter-rouge">Remote-SSH: Add New SSH Host...</code> and press Enter.</li>
  <li>
    <p>Enter the connection command using your WSL Ubuntu username, your <strong>Windows IP</strong>, and the <strong>forwarded port (<code class="language-plaintext highlighter-rouge">2222</code>)</strong>.</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Format: ssh &lt;wsl-username&gt;@&lt;windows-ip&gt; -p 2222</span>
<span class="c"># Example:</span>
ssh jboldsenryan@192.168.0.113 <span class="nt">-p</span> 2222
</code></pre></div>    </div>
  </li>
  <li>Choose the first option to update your SSH config file (<code class="language-plaintext highlighter-rouge">~/.ssh/config</code>).</li>
  <li>Now, connect! Press <code class="language-plaintext highlighter-rouge">Cmd+Shift+P</code> again and select <code class="language-plaintext highlighter-rouge">Remote-SSH: Connect to Host...</code>.</li>
  <li>Choose the host you just added (e.g., <code class="language-plaintext highlighter-rouge">192.168.0.113</code>).</li>
  <li>The first time you connect, you’ll be asked to confirm the server’s fingerprint (type <code class="language-plaintext highlighter-rouge">yes</code>) and then enter the password for your <strong>Ubuntu user</strong>. VS Code will then install its server components in WSL2.</li>
</ol>

<p><strong>That’s it!</strong> Your VS Code window is now running remotely inside WSL2. The integrated terminal is a full Ubuntu shell, and any files you open are on the Linux filesystem.</p>

<hr />

<h2 id="your-daily-workflow--troubleshooting">Your Daily Workflow &amp; Troubleshooting</h2>

<p>The initial setup is done once. Here’s what your daily use looks like.</p>

<p><strong>Daily Workflow:</strong></p>

<ol>
  <li><strong>On Windows:</strong> Make sure your WSL2 instance is running. You can start it with <code class="language-plaintext highlighter-rouge">wsl -d Ubuntu</code>.</li>
  <li><strong>On MacBook:</strong> Open VS Code, go to the Remote Explorer tab, and click the connect icon next to your saved host.</li>
</ol>

<h3 id="troubleshooting-common-issues">Troubleshooting Common Issues</h3>

<p><strong>1. Connection Refused?</strong>
This usually means the SSH server isn’t running in WSL2 or WSL2 itself is shut down.</p>
<ul>
  <li><strong>On Windows:</strong> Check WSL status with <code class="language-plaintext highlighter-rouge">wsl --list --verbose</code>.</li>
  <li><strong>In WSL2:</strong> Start the SSH server with <code class="language-plaintext highlighter-rouge">sudo service ssh start</code>.</li>
</ul>

<p><strong>2. The WSL IP Address Changed!</strong>
This is the most common problem. After a reboot of your Windows PC, WSL2 will likely get a new IP address, breaking your port forwarding rule.</p>

<ul>
  <li><strong>Solution:</strong>
    <ol>
      <li>Get the new WSL2 IP with <code class="language-plaintext highlighter-rouge">hostname -I</code>.</li>
      <li>On Windows (in an Admin PowerShell), remove the old rule and add the new one:
        <div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Get the new IP first! Let's say it's now 172.28.20.55</span><span class="w">
</span><span class="n">netsh</span><span class="w"> </span><span class="nx">interface</span><span class="w"> </span><span class="nx">portproxy</span><span class="w"> </span><span class="nx">delete</span><span class="w"> </span><span class="nx">v4tov4</span><span class="w"> </span><span class="nx">listenport</span><span class="o">=</span><span class="mi">2222</span><span class="w"> </span><span class="n">listenaddress</span><span class="o">=</span><span class="mf">0.0</span><span class="o">.</span><span class="nf">0</span><span class="o">.</span><span class="nf">0</span><span class="w">
</span><span class="n">netsh</span><span class="w"> </span><span class="nx">interface</span><span class="w"> </span><span class="nx">portproxy</span><span class="w"> </span><span class="nx">add</span><span class="w"> </span><span class="nx">v4tov4</span><span class="w"> </span><span class="nx">listenport</span><span class="o">=</span><span class="mi">2222</span><span class="w"> </span><span class="n">listenaddress</span><span class="o">=</span><span class="mf">0.0</span><span class="o">.</span><span class="nf">0</span><span class="o">.</span><span class="nf">0</span><span class="w"> </span><span class="n">connectport</span><span class="o">=</span><span class="mi">22</span><span class="w"> </span><span class="n">connectaddress</span><span class="o">=</span><span class="mf">172.28</span><span class="o">.</span><span class="nf">20</span><span class="o">.</span><span class="nf">55</span><span class="w">
</span></code></pre></div>        </div>
      </li>
    </ol>
  </li>
</ul>

<h3 id="level-up-automate-the-ip-problem">Level Up: Automate the IP Problem</h3>

<p>Manually updating the IP is tedious. I’ve created a set of PowerShell scripts to automate this entire process. The main script, <code class="language-plaintext highlighter-rouge">setup-wsl2-ssh.ps1</code>, can automatically detect the WSL2 IP and update the port forwarding rule for you.</p>

<p>If you have these scripts, you can fix a changed IP by simply running this on Windows:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Run PowerShell as Administrator</span><span class="w">
</span><span class="o">.</span><span class="n">\setup-wsl2-ssh.ps1</span><span class="w">
</span></code></pre></div></div>

<p>This single command finds the current IP, sets the port forward, and configures the firewall, making maintenance a breeze!</p>

<h2 id="-download-the-automation-scripts">📥 Download the Automation Scripts</h2>

<p>I’ve created practical scripts to automate this entire process. You can download them directly:</p>

<h3 id="essential-scripts">Essential Scripts</h3>
<ul>
  <li><strong><a href="/assets/downloads/wsl2-automation/setup-wsl2-ssh.ps1">setup-wsl2-ssh.ps1</a></strong> - Complete automated setup and IP management</li>
  <li><strong><a href="/assets/downloads/wsl2-automation/update-wsl2-ip.ps1">update-wsl2-ip.ps1</a></strong> - Quick IP update when WSL2 restarts</li>
  <li><strong><a href="/assets/downloads/wsl2-automation/connect-wsl2.sh">connect-wsl2.sh</a></strong> - Simple connection script for Mac/Linux</li>
</ul>

<h3 id="quick-start-with-scripts">Quick Start with Scripts</h3>

<ol>
  <li><strong>Download and run the main setup script:</strong>
    <div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># In PowerShell as Administrator (Windows)</span><span class="w">
</span><span class="o">.</span><span class="n">\setup-wsl2-ssh.ps1</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li><strong>When WSL2 IP changes (after restarts):</strong>
    <div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Quick IP update (Windows)</span><span class="w">
</span><span class="o">.</span><span class="n">\update-wsl2-ip.ps1</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li><strong>Connect from Mac/Linux:</strong>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Make executable and run (Mac/Linux)</span>
<span class="nb">chmod</span> +x connect-wsl2.sh
./connect-wsl2.sh
</code></pre></div>    </div>
  </li>
</ol>

<p>The scripts handle error checking, provide colored output, and can recover from most common issues automatically.</p>

<hr />

<h2 id="conclusion">Conclusion</h2>

<p>You now have a professional-grade, cross-platform development environment. You can leverage the raw power of your Windows machine to run Linux containers, build heavy projects, and run backend services, all while enjoying the fluid user experience of your MacBook Air. By understanding the manual steps and leveraging automation for maintenance, you’ve created a workflow that is both powerful and sustainable.</p>

<p>Happy coding! 🚀</p>]]></content><author><name></name></author><category term="coding" /><category term="macbook" /><category term="windows" /><category term="wsl2" /><category term="ssh" /><category term="vscode" /><category term="remote-development" /><category term="cross-platform" /><category term="development" /><summary type="html"><![CDATA[As a developer, you want the best tools for the job. You love the sleekness and Unix-based environment of your MacBook Air, but you also have a powerhouse Windows 11 Pro desktop. How do you get the best of both worlds? How can you use your Mac to develop inside a full-fledged Linux environment running on your Windows machine? The answer lies in a powerful combination: Windows Subsystem for Linux (WSL2), SSH, and VS Code’s Remote Development extension. This guide will walk you through the entire process, from setting up WSL2 on your Windows machine to seamlessly connecting and coding from your MacBook. We’ll cover the manual setup so you understand the mechanics, and then show you how to automate the process to handle common issues like changing IP addresses. The Big Picture: How Does This Work? Before we dive in, let’s understand the architecture. Your MacBook can’t directly “see” the Linux instance running inside WSL2, as it lives in its own virtual network on your Windows machine. We need to create a bridge. WSL2 Ubuntu: Runs a full Linux environment with its own private IP address. We’ll install an SSH server here. Windows 11 Pro: Acts as the host. We will configure it to forward traffic from one of its network ports to the SSH port on the WSL2 instance. Your MacBook: Will connect via SSH to your Windows machine’s IP address on the special forwarded port, which Windows will then transparently route to WSL2. Part 1: Setting Up the Host (Windows 11 Pro) First, we need to get WSL2 and our Ubuntu environment ready to accept connections. Step 1: Install WSL2 and Ubuntu If you haven’t already, installing WSL2 and the default Ubuntu distribution is a one-line command. Open PowerShell as an Administrator and run: wsl --install -d Ubuntu This command will enable the necessary Windows features (Virtual Machine Platform, Windows Subsystem for Linux), download the latest Linux kernel, and install the Ubuntu distribution. Restart your computer if prompted. Step 2: Configure the Ubuntu SSH Server Once installed, launch your new Ubuntu environment from the Start Menu. Now, let’s set up the SSH server inside it. Update and Install OpenSSH Server: It’s always good practice to update your package lists before installing new software. # Inside the Ubuntu WSL2 terminal sudo apt update sudo apt install openssh-server Start and Enable the SSH Service: We need to start the SSH service and ensure it launches automatically every time WSL2 starts. sudo service ssh start # Optional but recommended: enable the service to start on boot sudo systemctl enable ssh You can verify it’s running with sudo service ssh status. Step 3: Create the Network Bridge (Port Forwarding) This is the most critical step. We need to tell Windows to forward traffic to our WSL2 instance. Find the WSL2 IP Address: Your WSL2 instance has a dynamic IP address that can change each time it restarts. Find the current one by running this command inside the Ubuntu terminal: # This will print the IP address, e.g., 172.28.17.73 hostname -I Take note of this IP address. For our example, we’ll use 172.28.17.73. Set Up Port Forwarding in PowerShell: Exit the Ubuntu terminal and go back to a PowerShell window running as Administrator. We will forward port 2222 on Windows to port 22 (the default SSH port) on our WSL2 instance. Note: Replace 172.28.17.73 with the IP address you found in the previous step. # In an Administrator PowerShell netsh interface portproxy add v4tov4 listenport=2222 listenaddress=0.0.0.0 connectport=22 connectaddress=172.28.17.73 Create a Firewall Rule: Windows Firewall will block the new port by default. Let’s create a rule to allow incoming traffic on port 2222. # In the same Administrator PowerShell New-NetFirewallRule -DisplayName "WSL2 SSH Bridge" -Direction Inbound -LocalPort 2222 -Protocol TCP -Action Allow Your Windows host is now ready! Part 2: Connecting From Your MacBook Now for the fun part. Let’s connect from your MacBook using VS Code. Step 1: Find Your Windows IP Address You need the local network IP of your Windows machine. On the Windows PC, open a regular Command Prompt or PowerShell and run: ipconfig Look for the “IPv4 Address” under your active Wi-Fi or Ethernet adapter. It will likely look something like 192.168.0.113. Step 2: Connect with VS Code Remote-SSH On your MacBook, make sure you have the Remote Development extension pack installed in VS Code. Open VS Code and press Cmd+Shift+P to open the Command Palette. Type Remote-SSH: Add New SSH Host... and press Enter. Enter the connection command using your WSL Ubuntu username, your Windows IP, and the forwarded port (2222). # Format: ssh &lt;wsl-username&gt;@&lt;windows-ip&gt; -p 2222 # Example: ssh jboldsenryan@192.168.0.113 -p 2222 Choose the first option to update your SSH config file (~/.ssh/config). Now, connect! Press Cmd+Shift+P again and select Remote-SSH: Connect to Host.... Choose the host you just added (e.g., 192.168.0.113). The first time you connect, you’ll be asked to confirm the server’s fingerprint (type yes) and then enter the password for your Ubuntu user. VS Code will then install its server components in WSL2. That’s it! Your VS Code window is now running remotely inside WSL2. The integrated terminal is a full Ubuntu shell, and any files you open are on the Linux filesystem. Your Daily Workflow &amp; Troubleshooting The initial setup is done once. Here’s what your daily use looks like. Daily Workflow: On Windows: Make sure your WSL2 instance is running. You can start it with wsl -d Ubuntu. On MacBook: Open VS Code, go to the Remote Explorer tab, and click the connect icon next to your saved host. Troubleshooting Common Issues 1. Connection Refused? This usually means the SSH server isn’t running in WSL2 or WSL2 itself is shut down. On Windows: Check WSL status with wsl --list --verbose. In WSL2: Start the SSH server with sudo service ssh start. 2. The WSL IP Address Changed! This is the most common problem. After a reboot of your Windows PC, WSL2 will likely get a new IP address, breaking your port forwarding rule. Solution: Get the new WSL2 IP with hostname -I. On Windows (in an Admin PowerShell), remove the old rule and add the new one: # Get the new IP first! Let's say it's now 172.28.20.55 netsh interface portproxy delete v4tov4 listenport=2222 listenaddress=0.0.0.0 netsh interface portproxy add v4tov4 listenport=2222 listenaddress=0.0.0.0 connectport=22 connectaddress=172.28.20.55 Level Up: Automate the IP Problem Manually updating the IP is tedious. I’ve created a set of PowerShell scripts to automate this entire process. The main script, setup-wsl2-ssh.ps1, can automatically detect the WSL2 IP and update the port forwarding rule for you. If you have these scripts, you can fix a changed IP by simply running this on Windows: # Run PowerShell as Administrator .\setup-wsl2-ssh.ps1 This single command finds the current IP, sets the port forward, and configures the firewall, making maintenance a breeze! 📥 Download the Automation Scripts I’ve created practical scripts to automate this entire process. You can download them directly: Essential Scripts setup-wsl2-ssh.ps1 - Complete automated setup and IP management update-wsl2-ip.ps1 - Quick IP update when WSL2 restarts connect-wsl2.sh - Simple connection script for Mac/Linux Quick Start with Scripts Download and run the main setup script: # In PowerShell as Administrator (Windows) .\setup-wsl2-ssh.ps1 When WSL2 IP changes (after restarts): # Quick IP update (Windows) .\update-wsl2-ip.ps1 Connect from Mac/Linux: # Make executable and run (Mac/Linux) chmod +x connect-wsl2.sh ./connect-wsl2.sh The scripts handle error checking, provide colored output, and can recover from most common issues automatically. Conclusion You now have a professional-grade, cross-platform development environment. You can leverage the raw power of your Windows machine to run Linux containers, build heavy projects, and run backend services, all while enjoying the fluid user experience of your MacBook Air. By understanding the manual steps and leveraging automation for maintenance, you’ve created a workflow that is both powerful and sustainable. Happy coding! 🚀]]></summary></entry><entry><title type="html">Measurement, Temporal Distance, and The Hierarchical Taxonomy of Purple</title><link href="/psychology/2025/03/01/measurement-temporal-distance-hierarchical-taxonomy-purple.html" rel="alternate" type="text/html" title="Measurement, Temporal Distance, and The Hierarchical Taxonomy of Purple" /><published>2025-03-01T15:00:00+00:00</published><updated>2025-03-01T15:00:00+00:00</updated><id>/psychology/2025/03/01/measurement-temporal-distance-hierarchical-taxonomy-purple</id><content type="html" xml:base="/psychology/2025/03/01/measurement-temporal-distance-hierarchical-taxonomy-purple.html"><![CDATA[<blockquote>
  <p><strong>Gist:</strong> This post is a musing on whether the ‘factor structure’ we find for psychological constructs like emotion is just an artifact of the temporal scale we use to measure them. I’m using the analogy of a Japanese Maple tree: from a distance, it’s a single “purple” factor, but up close, it resolves into many distinct color factors. The idea is that our measurement intervals—moment, day, month—are just different temporal distances, each revealing a different, but equally ‘true’, view of a person’s inner world. It makes me cautious about mixing scales.</p>
</blockquote>

<p>I’ve been turning over an idea that I can’t seem to shake. It’s about measurement, and how the timescale we choose to measure with might fundamentally change the psychological phenomena we think we’re observing. It feels almost obvious when I write it down, but the implications seem to ripple out in interesting ways.</p>

<p>The core of it is this: as our measurement interval gets longer—moving from a moment, to a day, to a week—what we’re measuring seems to shift from something discrete and flickering to something more stable and “trait-like.” I wonder if this means that the number of “factors” we find in our data isn’t just a property of the person, but an artifact of the temporal lens we’re using to look at them.</p>

<h4 id="the-japanese-maple-from-a-distance">The Japanese Maple from a Distance</h4>

<p>I keep coming back to a visual analogy. Imagine looking at a Japanese Maple tree from 200 yards away. From that distance, the canopy might resolve into a single, dominant color. If I were to “factor analyze” the colors of that tree from afar, I’d likely find a one-factor solution: “Purple.” All the colors would load heavily onto this single dimension. It’s a true representation of the tree <em>from that distance</em>.</p>

<p>But what happens if I walk 100 yards closer? Now, my perception has a higher resolution. I can distinguish the bright orange leaves on the sunny side from the deeper violet hues in the shade. My one-factor “Purple” model breaks down. I now have at least two factors: “Orange” and “Violet.”</p>

<p>If I walk right up to the tree and stand beneath its branches, the complexity multiplies. I can see that some leaves are a brilliant, almost-neon red. Others have yellow edges. Some of the “violet” leaves I saw from a distance are actually a deep, subtle blue mixed with purple. Suddenly, my two-factor model is insufficient. I might now need five factors to adequately describe the colors: Red, Orange, Yellow, Blue, and Purple.</p>

<p>None of these factor solutions are “wrong.” They are all accurate representations of the tree at different observational distances. The number of factors, the “structure” of the color, is a function of my proximity.</p>

<h4 id="the-distant-self-and-the-momentary-self">The Distant Self and the Momentary Self</h4>

<p>This is where the idea gets interesting for me when thinking about affect. What if time is just another kind of distance?</p>

<p>When we ask someone to fill out a questionnaire about their “past month,” we are, in a sense, asking them to view their “distant self.” From that temporal distance, the fine-grained details of their emotional life may blur. The momentary flashes of pride, joy, and excitement that occurred over thousands of moments might blend together in memory’s calculus. When they reflect, they may only be able to access the broad strokes, the overarching impression. It wouldn’t surprise me if their experience, when measured this way, resolves into just two dominant “colors”: a “Positive Affect” factor and a “Negative Affect” factor.</p>

<p>But what if we measure their “daily self”? Here, we’re a bit closer. The resolution is higher. They might be able to distinguish “anxious” feelings from general “sadness,” or “contentment” from “excitement.” More factors might emerge because the temporal distance is shorter, and less mental averaging is required.</p>

<p>And then there’s the “momentary self,” the target of things like Ecological Momentary Assessment (EMA). This is our attempt to stand right under the branches of the tree. Here, we might expect to see the most complexity, the greatest number of distinct affective “factors.” Yet, even here, I have my doubts. Is EMA truly measuring a single, instantaneous emotional state? Or is it asking someone to pause and perform a rapid “mental calculus” of the last 60 seconds? Even our most granular measurements might still be a slight time-average, a small bundle of experiences rather than a single, discrete emotional leaf.</p>

<h4 id="some-wrinkles-in-the-analogy">Some Wrinkles in the Analogy</h4>

<p>This line of thinking makes me cautious. If the factor structure of affect is dependent on the temporal scale of measurement, then what does it mean to mix those scales? It seems akin to trying to run a single analysis on a photograph of the whole tree from 200 yards away and a microscopic image of a single leaf’s cell structure. The data are measuring phenomena at such fundamentally different levels of organization that asking how they “group” together feels like a category error.</p>

<p>It also makes me question what a “factor” even <em>is</em> in this context. Perhaps, at a given timescale, a factor doesn’t represent some deep, underlying latent entity. Maybe it simply tells us which discrete emotional states are most likely to be bundled together or co-occur within that specific temporal window. The “Positive Affect” factor in a weekly survey might not be a singular entity, but simply a name we give to the observation that moments of joy, interest, and contentment tended to happen in close succession during that period.</p>

<p>I don’t have an answer here. It’s just a thought experiment. But it does seem that the “truth” of our inner world’s structure might not be a single, static architecture. Perhaps it’s more like that Japanese Maple—a different, but equally valid, truth revealed at every possible distance from which we choose to look.</p>]]></content><author><name></name></author><category term="psychology" /><summary type="html"><![CDATA[Gist: This post is a musing on whether the ‘factor structure’ we find for psychological constructs like emotion is just an artifact of the temporal scale we use to measure them. I’m using the analogy of a Japanese Maple tree: from a distance, it’s a single “purple” factor, but up close, it resolves into many distinct color factors. The idea is that our measurement intervals—moment, day, month—are just different temporal distances, each revealing a different, but equally ‘true’, view of a person’s inner world. It makes me cautious about mixing scales. I’ve been turning over an idea that I can’t seem to shake. It’s about measurement, and how the timescale we choose to measure with might fundamentally change the psychological phenomena we think we’re observing. It feels almost obvious when I write it down, but the implications seem to ripple out in interesting ways. The core of it is this: as our measurement interval gets longer—moving from a moment, to a day, to a week—what we’re measuring seems to shift from something discrete and flickering to something more stable and “trait-like.” I wonder if this means that the number of “factors” we find in our data isn’t just a property of the person, but an artifact of the temporal lens we’re using to look at them. The Japanese Maple from a Distance I keep coming back to a visual analogy. Imagine looking at a Japanese Maple tree from 200 yards away. From that distance, the canopy might resolve into a single, dominant color. If I were to “factor analyze” the colors of that tree from afar, I’d likely find a one-factor solution: “Purple.” All the colors would load heavily onto this single dimension. It’s a true representation of the tree from that distance. But what happens if I walk 100 yards closer? Now, my perception has a higher resolution. I can distinguish the bright orange leaves on the sunny side from the deeper violet hues in the shade. My one-factor “Purple” model breaks down. I now have at least two factors: “Orange” and “Violet.” If I walk right up to the tree and stand beneath its branches, the complexity multiplies. I can see that some leaves are a brilliant, almost-neon red. Others have yellow edges. Some of the “violet” leaves I saw from a distance are actually a deep, subtle blue mixed with purple. Suddenly, my two-factor model is insufficient. I might now need five factors to adequately describe the colors: Red, Orange, Yellow, Blue, and Purple. None of these factor solutions are “wrong.” They are all accurate representations of the tree at different observational distances. The number of factors, the “structure” of the color, is a function of my proximity. The Distant Self and the Momentary Self This is where the idea gets interesting for me when thinking about affect. What if time is just another kind of distance? When we ask someone to fill out a questionnaire about their “past month,” we are, in a sense, asking them to view their “distant self.” From that temporal distance, the fine-grained details of their emotional life may blur. The momentary flashes of pride, joy, and excitement that occurred over thousands of moments might blend together in memory’s calculus. When they reflect, they may only be able to access the broad strokes, the overarching impression. It wouldn’t surprise me if their experience, when measured this way, resolves into just two dominant “colors”: a “Positive Affect” factor and a “Negative Affect” factor. But what if we measure their “daily self”? Here, we’re a bit closer. The resolution is higher. They might be able to distinguish “anxious” feelings from general “sadness,” or “contentment” from “excitement.” More factors might emerge because the temporal distance is shorter, and less mental averaging is required. And then there’s the “momentary self,” the target of things like Ecological Momentary Assessment (EMA). This is our attempt to stand right under the branches of the tree. Here, we might expect to see the most complexity, the greatest number of distinct affective “factors.” Yet, even here, I have my doubts. Is EMA truly measuring a single, instantaneous emotional state? Or is it asking someone to pause and perform a rapid “mental calculus” of the last 60 seconds? Even our most granular measurements might still be a slight time-average, a small bundle of experiences rather than a single, discrete emotional leaf. Some Wrinkles in the Analogy This line of thinking makes me cautious. If the factor structure of affect is dependent on the temporal scale of measurement, then what does it mean to mix those scales? It seems akin to trying to run a single analysis on a photograph of the whole tree from 200 yards away and a microscopic image of a single leaf’s cell structure. The data are measuring phenomena at such fundamentally different levels of organization that asking how they “group” together feels like a category error. It also makes me question what a “factor” even is in this context. Perhaps, at a given timescale, a factor doesn’t represent some deep, underlying latent entity. Maybe it simply tells us which discrete emotional states are most likely to be bundled together or co-occur within that specific temporal window. The “Positive Affect” factor in a weekly survey might not be a singular entity, but simply a name we give to the observation that moments of joy, interest, and contentment tended to happen in close succession during that period. I don’t have an answer here. It’s just a thought experiment. But it does seem that the “truth” of our inner world’s structure might not be a single, static architecture. Perhaps it’s more like that Japanese Maple—a different, but equally valid, truth revealed at every possible distance from which we choose to look.]]></summary></entry></feed>