<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Blog on Julio's Blog</title><link>https://jvblog.net/</link><description>Recent content in Blog on Julio's Blog</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><atom:link href="https://jvblog.net/index.xml" rel="self" type="application/rss+xml"/><item><title>Human Friendly Data Science Interviews</title><link>https://jvblog.net/human-friendly-data-science-interviews/</link><pubDate>Sun, 22 Nov 2020 00:00:00 +0000</pubDate><guid>https://jvblog.net/human-friendly-data-science-interviews/</guid><description>&lt;p>&lt;em>TL;DR. We focused on a holistic view of our candidates (technical and interpersonal skills) while trying to be fair with everyone&amp;rsquo;s time and life experiences. We could identify the outstanding people and those that weren&amp;rsquo;t a good fit and have had a great experience working with our hires!&lt;/em>&lt;/p>
&lt;p>After reading &lt;a href="https://www.neilwithdata.com/developer-hiring">The software industry&amp;rsquo;s greatest sin: hiring&lt;/a> by Neil Sainsbury and &lt;a href="https://andrewrondeau.com/blog/2020/04/take-home-vs-whiteboard-coding-the-problem-is-bad-interviews">Take-home vs. whiteboard coding: The problem is bad interviews&lt;/a> by Andrew Rondeau, several critical points about interviewing software developers stood out to me:&lt;/p>
&lt;ul>
&lt;li>Software developers are usually assessed based on technical aspects ignoring their personal and organizational qualities. This might produce technically correct software with good performance, but that might be far from fulfilling users&amp;rsquo; needs.&lt;/li>
&lt;li>Someone can be technically excellent but lack the skills to understand and interact with your users and the rest of the team.&lt;/li>
&lt;li>Someone can be technically excellent but keep using technologies they find interesting but are not aligned with the company&amp;rsquo;s goals.&lt;/li>
&lt;li>There are tradeoffs between whiteboard and take-home questions: time invested by both parties, different development environment/conditions, visibility of the candidate&amp;rsquo;s technical and personal qualities, and the feedback loop between the examiner and the applicant.&lt;/li>
&lt;li>A key aspect is to plan a good interview with coding assignments that consider the company&amp;rsquo;s needs and are fair for everyone involved.&lt;/li>
&lt;li>Presenting existing code is briefly discussed by Andrew Rondeau as an alternative to whiteboard and take-home questions.&lt;/li>
&lt;/ul>
&lt;p>I am a postdoctoral researcher at a group that explores mobile data&amp;rsquo;s role in monitoring or supporting people with different health conditions. Broadly speaking, we collect smartphone and wearable sensor data, process it, and use it to create statistical and machine learning models that provide relevant behavioral or clinical insights. This is possible thanks to our team&amp;rsquo;s multi-disciplinary nature with expertise in psychology, statistics, computer science, software engineering, and data science.&lt;/p>
&lt;p>Recently, we needed to hire a couple of data science interns from the local master&amp;rsquo;s program, and I was in charge of leading the technical part of the interviews. This was an excellent opportunity for me to pilot the type of technical interview that I&amp;rsquo;d like to experience based on the points I summarized above and the lab&amp;rsquo;s needs.&lt;/p>
&lt;p>I divided our interviews into two 30-minute stages, one to talk about one of the candidate&amp;rsquo;s past projects and the other to find out how they would approach a data science problem that represents the kind of work we do.&lt;/p>
&lt;p>For the first stage, we asked applicants to submit in advance a past data science project that they would like to discuss with us. I want to clarify that we accepted any industry, school, or hobby code repository and did not judge its purpose or complexity. We don&amp;rsquo;t expect that everyone will have the time to work on side-projects in their free time or disclose code from a previous employer. However, their sample project allowed us to understand some of the person&amp;rsquo;s experience with data science and software engineering practices like data cleaning, modeling, documentation, version control, variable and function naming, code comments, code formatting, and code refactoring. If any of these aspects were missing or seemed unsatisfactory, we made a note and ask about them during the interview.&lt;/p>
&lt;p>When the time came for the first part of our face-to-face chat (where we talked about their chosen project), we focused on their hard skills (technical expertise, domain knowledge, and problem-solving abilities), soft skills (communication, multi-disciplinary collaboration, feedback reception), and traits like proactiveness, enthusiasm, motivation, clarity of thought, independence and attention to detail. This is our take on what Neil refers to as a candidate&amp;rsquo;s &amp;ldquo;holistic&amp;rdquo; view. Crucially, having technical and non-technical members from our team present made it easier to discuss and evaluate our candidates. More specifically, we inquired people about their role in previous teams (if any), their approach to learning, and their thought process to choose the best tool for the job. We also prompted them to explain complex non-technical concepts to everyone in the interview panel and to talk more about their experience interacting with past &amp;ldquo;clients&amp;rdquo; (teachers, fellow students, or any other stakeholders for those with experience in Industry). One of the advantages of this setup was that it allowed everyone to interact in a work environment very similar to what we experience every day while planning, implementing, executing, analyzing, and publishing a health intervention or monitoring study.&lt;/p>
&lt;p>In the second part of the interview, we asked participants the following question: how would you implement a sleep classifier based on smartphone and Fitbit data? Even if this problem appears simple at first sight, numerous decisions and considerations can be taken into account along the way. For example, we can talk about missing data, feature engineering, data resampling, data imputation, class imbalance, type of model (population or individual), hyper-parameter tuning, model choice, baselines, cross-validation, evaluation metrics, etc. Consequently and to foster the discussion, we always dropped clues, clarifications, and follow up questions.&lt;/p>
&lt;p>We did not expect our interviewees to reach a comprehensive solution or write any code (it took us weeks to finish a publishable solution, and the first part of the interview already would have given us an idea of their programming skills). Instead, we wanted to know more about their thinking process. We paid particular attention to the candidate&amp;rsquo;s understanding of the problem (do they ask relevant questions?), creativity (how do they suggest tackling this problem?), experience (are they levering solutions to past problems?), technical expertise (what programming language, libraries, or methods would they like to use?), and communication skills (can they engage the whole team in the discussion?).&lt;/p>
&lt;p>This process fits well within our workflow and our team&amp;rsquo;s characteristics, and we hope that by sharing it, you can adapt it to your needs and provide a better experience for your candidates.&lt;/p></description></item><item><title>Quick Blog Style Update</title><link>https://jvblog.net/quick-blog-style-update/</link><pubDate>Sun, 22 Nov 2020 00:00:00 +0000</pubDate><guid>https://jvblog.net/quick-blog-style-update/</guid><description>&lt;p>This is an update to this &lt;a href="../personalizing-my-blogs-theme/">post&lt;/a>.&lt;/p>
&lt;p>I set the default font to Wotfard and the default mono font to FiraCode, reduced the font size and increased the line height:&lt;/p>
&lt;pre>&lt;code class="language-css">@font-face {
font-family: 'Wotfard';
src: url('{{ &amp;quot;css/fonts/wotfard-regular-webfont.woff2&amp;quot; | absURL }}') format('woff2');
font-weight: regular;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'FiraCode';
src: url('{{ &amp;quot;css/fonts/FiraCode-Regular.woff2&amp;quot; | absURL }}') format('woff2');
font-weight: regular;
font-style: normal;
font-display: swap;
}
html {
font-size: calc(1em + 0.23vw);
font-family: 'Wotfard', Helvetica Neue, sans-serif;
line-height: 2.0;
color: #111;
background-color: #fefefe;
}
h1,
h2,
h3,
h4 {
font-family: Wotfard, serif;
line-height: 1.125;
letter-spacing: 2px;
}
code {
font-family: FiraCode, Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 0.9em;
background-color: #eee;
border-radius: 3px;
padding: 0 3px;
color: #004085;
}
&lt;/code>&lt;/pre>
&lt;p>I added color to links so they are easier to find&lt;/p>
&lt;pre>&lt;code class="language-css">main#main a {
color: #3f7fa6;
}
&lt;/code>&lt;/pre>
&lt;p>I removed the bookmark icon from the posts&amp;rsquo; title (also delete the SVG element from &lt;code>layouts/_default/single.html&lt;/code>)&lt;/p>
&lt;pre>&lt;code class="language-css">/* Pattern lists */
.patterns-list {
list-style-type: square;
}
&lt;/code>&lt;/pre></description></item><item><title>Setting up Travis CI to test R and Python scripts in MacOS and Ubuntu</title><link>https://jvblog.net/settingup-travis-for-r-and-python-project/</link><pubDate>Thu, 28 May 2020 00:00:00 +0000</pubDate><guid>https://jvblog.net/settingup-travis-for-r-and-python-project/</guid><description>&lt;p>A colleague and I configured Travis CI to run the tests of a project that relies on R and Python scripts. This project supports both macOS and Linux, so it was essential to test it in both environments. After some trial and error, we got this working with the &lt;code>travis.yaml&lt;/code> file below. Our deployment manages the following project requirements: MySQL, Python 3.7, miniconda with a virtual environment, R and a cached virtual environment with renv, and slack notifications.&lt;/p>
&lt;p>For Linux (Ubuntu 16.04) we do:&lt;/p>
&lt;ul>
&lt;li>Install brew, linuxbrew-wrapper and linuxbrew/xorg&lt;/li>
&lt;li>Install R using brew&lt;/li>
&lt;li>Install miniconda using their provided script installer&lt;/li>
&lt;li>Restore our conda virtual env&lt;/li>
&lt;li>Cache &lt;a href="https://rstudio.github.io/renv/index.html">renv&lt;/a>&amp;lsquo;s library. We use renv to keep a reproducible R environment with 161 packages; however, renv had to build them from source in Ubuntu 16.04 and our travis build was timing out. The solution we implemented was building the renv library (and therefore the travis&amp;rsquo; cache) in three steps, first committing a small renv.lock with 40 packages, then 100, then all 161.&lt;/li>
&lt;/ul>
&lt;p>For MacOS (10.14.4)&lt;/p>
&lt;ul>
&lt;li>Set up the OS image with &lt;code>osx_image: xcode11.3&lt;/code> and &lt;code>language: generic&lt;/code>&lt;/li>
&lt;li>Install MySQL, R and miniconda using brew (brew and Python 3.7 are already installed)&lt;/li>
&lt;li>Restore our conda virtual env&lt;/li>
&lt;li>Cache renv&amp;rsquo;s library. We faced and solved the same problem we had in Linux with renv&amp;rsquo;s building times timing out our travis build (see above). In addition, in MacOS, renv&amp;rsquo;s library path contains a space &lt;code>~/Library/Application Support/renv&lt;/code> which was causing issues with travis&amp;rsquo; cache mechanism, as a quick fix we disabled renv&amp;rsquo;s global cache &lt;code>R -e 'renv::settings$use.cache(FALSE)'&lt;/code> for both Linux and MacOS (for consistency) and cached our project&amp;rsquo;s renv library folder &lt;code>$TRAVIS_BUILD_DIR/renv/library&lt;/code> as it now contains the actual packages instead of symbolic links.&lt;/li>
&lt;/ul>
&lt;p>&lt;em>UPDATE&lt;/em> Recently &lt;code>r&lt;/code> tap dependency on gcc changed from 9 to 10 and that broke our &lt;code>renv&lt;/code> library cache. To force travis to use gcc 9 and r 4.0.0 use this updated file:&lt;/p>
&lt;pre>&lt;code class="language-yaml">services:
- mysql
language: python # this works for Linux but is an error on macOS or Windows
jobs:
include:
- name: &amp;quot;Python 3.7 on Xenial Linux&amp;quot;
os: linux
language: python
python: 3.7
before_install:
- /bin/bash -c &amp;quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)&amp;quot;
- export PATH=/home/linuxbrew/.linuxbrew/bin:$PATH
- source ~/.bashrc
- sudo apt-get install linuxbrew-wrapper
- brew tap --shallow linuxbrew/xorg
- brew install r
- R --version
- wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh;
- bash miniconda.sh -b -p $HOME/miniconda
- source &amp;quot;$HOME/miniconda/etc/profile.d/conda.sh&amp;quot;
- hash -r
- conda config --set always_yes yes --set changeps1 no
cache:
directories:
- /home/travis/.linuxbrew
- $HOME/.local/share/renv
- $TRAVIS_BUILD_DIR/renv/library
- name: &amp;quot;Python 3.7 on macOS&amp;quot;
os: osx
osx_image: xcode11.3 # Python 3.7 running on macOS 10.14.4
language: generic # 'language: python' is an error on Travis CI macOS
before_install:
- HOMEBREW_NO_AUTO_UPDATE=1 brew install gcc@9 # HOMEBREW_NO_AUTO_UPDATE=1 is not set as an env variable because that invalidates renv's cache in travis
- HOMEBREW_NO_AUTO_UPDATE=1 brew install https://github.com/Homebrew/homebrew-core/raw/218998d/Formula/r.rb # R 4.0.0
- R --version
- HOMEBREW_NO_AUTO_UPDATE=1 brew install mysql
- HOMEBREW_NO_AUTO_UPDATE=1 brew services start mysql
- HOMEBREW_NO_AUTO_UPDATE=1 brew cask install miniconda
- eval &amp;quot;$(/opt/miniconda3/condabin/conda shell.bash hook)&amp;quot;
- eval &amp;quot;$(conda shell.bash hook)&amp;quot; # properly initialise non-interactive shell with gcc 9 and no auto update brew
env:
- RENV_PATHS_ROOT=&amp;quot;$HOME/renv/cache&amp;quot;
cache:
directories:
- /usr/local/lib/R
- $RENV_PATHS_ROOT
- $TRAVIS_BUILD_DIR/renv/library
install:
- conda init bash
- conda update -q --all --yes conda
- conda env create -q -n test-environment python=$TRAVIS_PYTHON_VERSION --file environment.yml
- conda activate test-environment
- snakemake renv_install
- R -e 'renv::settings$use.cache(FALSE)'
- snakemake renv_restore
script:
- bash tests/scripts/run_tests.sh
notifications:
email: false
slack:
# if: branch = travis_test
secure: cJIpmIjb3zA5AMDBo9axF1v6fYNIgMm6s6UdMNOlHiT511xHGsaLUFej3lACwQLig4Gr94ySI61YdrP+RX1lFcYxusH+kUU/c8LX0PmSKNeKnycM3w/pCM+yTp/6oQG6ZrJD7pNm6zhB0xPL61uSmYhcr+JJ1sh4iLiON+J8/C+IfnAHm1ORkxJ0IxASkiP/LvaiAQDw8lNyYIZNWjSDNZbx68o1VNakyk6Vik3x8omiE3w33rzI2/JAx//QTxOq2J0dtV1AqYYSOWS4iXblV09NLBqgGrhAhrQ6+TbPHSPIyL/4EdhvS+YXO+SBWS7ODD7j/MuL6XiA4SujW72od2rgXNmOjFnlQvIrULO5bzv39BKKDkldvz9+XCyXLcjoLIwA/rmUnwMndNoC7NoD/CkQEevUxswXXB9811BmIFx/7GOHouVxwB2gaMAzkCroZJVwgbrc6ESSOVE5SMcb3wPMbpd8cXOgVZXJcmk5wK206zxXPigCvFfknqOnwDqRgyIWSFoTd/2wHppA7ND3R5U42nQTbEQ7MiONsOo61GlJTTxJELz32sLKl388AuAgOY7+0sqPibxMaHJkF1V4nYVTH0/H5bO/edK4VHMloJ6s0kuyko7LT5EMQf3pBJij5TnYmD2E60t+bSBAxHuH7WA5dvL+igjGEwROnxDc9pc=
on_success: always
template:
- &amp;quot;Repo `%{repository_slug}` *%{result}* build (&amp;lt;%{build_url}|#%{build_number}&amp;gt;) for commit (&amp;lt;%{compare_url}|%{commit}&amp;gt;) on branch `%{branch}`.&amp;quot;
- &amp;quot;Execution time: *%{duration}*&amp;quot;
- &amp;quot;Message: %{message}&amp;quot;
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-yaml"># This is the old file for reference only
services:
- mysql
language: python
jobs:
include:
- name: &amp;quot;Python 3.7 on Xenial Linux&amp;quot;
os: linux
language: python
python: 3.7
before_install:
- /bin/bash -c &amp;quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)&amp;quot;
- export PATH=/home/linuxbrew/.linuxbrew/bin:$PATH
- source ~/.bashrc
- sudo apt-get install linuxbrew-wrapper
- brew tap --shallow linuxbrew/xorg
- brew install r
- R --version
- wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh;
- bash miniconda.sh -b -p $HOME/miniconda
- source &amp;quot;$HOME/miniconda/etc/profile.d/conda.sh&amp;quot;
- hash -r
- conda config --set always_yes yes --set changeps1 no
cache:
directories:
- /home/travis/.linuxbrew
- $HOME/.local/share/renv # global renv cache in linux (not used)
- $TRAVIS_BUILD_DIR/renv/library # local renv cache
- name: &amp;quot;Python 3.7 on macOS&amp;quot;
os: osx
osx_image: xcode11.3 # Python 3.7 running on macOS 10.14.4
language: generic
before_install:
- brew install mysql
- brew services start mysql
- brew install r
- R --version
- brew cask install miniconda
- eval &amp;quot;$(/usr/local/bin/conda shell.bash hook)&amp;quot;
env:
- RENV_PATHS_ROOT=&amp;quot;$HOME/renv/cache&amp;quot;
cache:
directories:
- /usr/local/lib/R
- $RENV_PATHS_ROOT # global renv cache in MacOS (not used)
- $TRAVIS_BUILD_DIR/renv/library # local renv cache
install:
- conda init
- conda update -q --all --yes conda
- conda env create -q -n test-environment python=$TRAVIS_PYTHON_VERSION --file environment.yml
- conda activate test-environment
- snakemake renv_install
- R -e 'renv::settings$use.cache(FALSE)'
- snakemake renv_restore
script:
- python -m unittest discover tests/scripts/ -v
notifications:
email: false
slack:
secure: SLACK_SECURE_KEY
on_success: always
template:
- &amp;quot;Repo `%{repository_slug}` *%{result}* build (&amp;lt;%{build_url}|#%{build_number}&amp;gt;) for commit (&amp;lt;%{compare_url}|%{commit}&amp;gt;) on branch `%{branch}`.&amp;quot;
- &amp;quot;Execution time: *%{duration}*&amp;quot;
- &amp;quot;Message: %{message}&amp;quot;
&lt;/code>&lt;/pre></description></item><item><title>Quickly exploring CSV files with wc and awk</title><link>https://jvblog.net/quickly-exploring-files-with-wc-awk/</link><pubDate>Sat, 18 Apr 2020 00:00:00 +0000</pubDate><guid>https://jvblog.net/quickly-exploring-files-with-wc-awk/</guid><description>&lt;p>This week I was extracting high-intensity activity episodes from the Fitbit data of 150 people. The first thing I wanted to know after processing all participants was how many people had at least 1 episode. I am using &lt;a href="https://rapidspitt.readthedocs.io/en/latest/">RAPIDS&lt;/a> to process the data, which means that the activity episodes for each participant are stored one per line in CSV files in individual folders. As I was looking for a quick and short solution, I went for Bash instead of Python or R.&lt;/p>
&lt;p>Then, the problem is reduced to three steps: list all files in a subfolder with names that match a pattern, count the lines on each file, and filter those files with at least 2 lines (all files have at least the header row). For that, we can use &lt;code>wc&lt;/code> and &lt;code>awk&lt;/code>.&lt;/p>
&lt;pre>&lt;code class="language-bash">wc $(find . -name 'p*_fitbit_mvpa_episodes.csv') | awk '{if (($1 &amp;gt; 1) &amp;amp;&amp;amp; ($4 ~ /^\.\/data/)) { print }}' | wc -l
&lt;/code>&lt;/pre>
&lt;p>The first part &lt;code>wc $(find . -name 'p*_fitbit_mvpa_episodes.csv')&lt;/code> executes the &lt;code>wc&lt;/code> command on the output of the &lt;code>find&lt;/code> command, which retrieves all files in the current directory and any subdirectories with a name that matches the regular expression between quotes. The &lt;a href="https://www.mkssoftware.com/docs/man1/wc.1.asp">default output&lt;/a> of the &lt;code>wc&lt;/code> command has four columns for each file: line count, word count, byte count, and its path. These are piped into &lt;code>awk '{if (($1 &amp;gt; 1) &amp;amp;&amp;amp; ($4 ~ /^\.\/data/)) { print }}'&lt;/code> which filters and prints those lines where the value of the first column &lt;code>$1&lt;/code> (line count) is bigger than one and the fourth column &lt;code>$4&lt;/code> (file path) starts with &lt;code>./data&lt;/code>. The first part of the filter gets all the files with at least one activity episode (header + episode line), and the second part excludes the total count that &lt;code>wc&lt;/code> appends. Finally, to obtain the &lt;em>number&lt;/em> of files with at least one activity episode, I piped the previous list to &lt;code>wc&lt;/code> with the &lt;code>-l&lt;/code> flag that counts the number of lines (files) that &lt;code>awk&lt;/code> printed. It turns out that out of 150 participants, only 20 have high-intensity activity episodes (this lead us to discover a problem with the data I was working with that is a matter for another post).&lt;/p>
&lt;p>As an extra bit of information useful for our collaborators, I wanted to know the average number of episodes across all participants. For this I followed a similar process but instead of the second &lt;code>wc -l&lt;/code>, I piped the output to &lt;code>awk&lt;/code> where it is possible to keep a counter and sum of the values of each line, obtaining the average for the first column (line count) as follows:&lt;/p>
&lt;pre>&lt;code class="language-bash">wc $(find . -name 'p*_fitbit_mvpa_episodes.csv') | awk '{if (($1 &amp;gt; 1) &amp;amp;&amp;amp; ($4 ~ /^\.\/data/)) { print $1}}' | awk '{ total += $1; count++ } END { print total/count }'
&lt;/code>&lt;/pre>
&lt;p>If you want to export a list of all files with more than one line along their line count to a CSV file, you can execute:&lt;/p>
&lt;pre>&lt;code class="language-bash">wc $(find . -name 'p*_fitbit_mvpa_episodes.csv') | awk '{if (($1 &amp;gt; 1) &amp;amp;&amp;amp; ($4 ~ /^\.\/data/)) { print }}'| sort | awk '{ {print $1&amp;quot;,&amp;quot;$4}}'&amp;gt; participants_with_episodes.csv
&lt;/code>&lt;/pre>
&lt;p>If you want to zip all files with more than one line, you can execute:&lt;/p>
&lt;pre>&lt;code class="language-bash">wc $(find . -name 'p*_fitbit_mvpa_episodes.csv') | awk '{if (($1 &amp;gt; 1) &amp;amp;&amp;amp; ($4 ~ /^\.\/data/)) { print }}'| sort | awk '{ {print $4}}'| zip participants_with_episodes.zip -@
&lt;/code>&lt;/pre>
&lt;p>We have an average of 17.3 episodes across 20 people.&lt;/p></description></item><item><title>Polishing my blog's appearance and performance</title><link>https://jvblog.net/personalizing-my-blogs-theme/</link><pubDate>Wed, 08 Apr 2020 00:00:00 +0000</pubDate><guid>https://jvblog.net/personalizing-my-blogs-theme/</guid><description>&lt;p>UPDATE. I made more small changes as detailed &lt;a href="../quick-blog-style-update/">here&lt;/a>&lt;/p>
&lt;p>When I was setting up this blog with Hugo and Netlify, I found five themes that I liked for their simplicity and aesthetics. I chose &lt;a href="https://cupper-hugo-theme.netlify.com/">Cupper&lt;/a> because it focuses on content, is accessible, posts are grouped by tags and not categories, it supports multiple shortcodes (like notes, warnings, code, among others), it includes minimal javascript, and it provides a dark theme.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://cupper-hugo-theme.netlify.com/">https://cupper-hugo-theme.netlify.com/&lt;/a> (the one I went for)&lt;/li>
&lt;li>&lt;a href="https://themes.gohugo.io//theme/harbor/">https://themes.gohugo.io//theme/harbor/&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://themes.gohugo.io/theme/kiss/">https://themes.gohugo.io/theme/kiss/&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://themes.gohugo.io/theme/hugo-ink/">https://themes.gohugo.io/theme/hugo-ink/&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.bespinian.io/">https://blog.bespinian.io/&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>As good as the theme is, I tweaked some things, and this post is a compilation of them for my future reference and for other people that might find them useful. A neat tip is that you don&amp;rsquo;t need to modify the original theme to make changes, instead you can add it as a submodule in the &lt;code>themes&lt;/code> folder, create the files with the modifications you need in the folders at the top level of your blog and &lt;a href="https://gohugo.io/templates/lookup-order/">Hugo will use them first&lt;/a>.&lt;/p>
&lt;p>The TL;DR list of changes is below, but you can keep reading for more details:&lt;/p>
&lt;ul>
&lt;li>Support for more highlighted languages in code snippets by Prism JS&lt;/li>
&lt;li>CSS changes for text readability&lt;/li>
&lt;li>Make the blog&amp;rsquo;s post list its homepage&lt;/li>
&lt;li>Support for static comments using Staticman&lt;/li>
&lt;li>Support for web analytics using GoatCounter&lt;/li>
&lt;li>RSS feed with full posts instead of a short description&lt;/li>
&lt;li>Image, CSS, and JavaScript optimization in Netlify&lt;/li>
&lt;/ul>
&lt;p>First, I updated the code highlighting languages supported by Prism JS, the highlighting library used by Cupper. This is the &lt;a href="https://prismjs.com/download.html#themes=prism&amp;amp;languages=markup+css+clike+javascript+bash+git+java+json+latex+markdown+python+r+rest+sql+toml+yaml&amp;amp;plugins=toolbar+copy-to-clipboard">URL&lt;/a> of my configuration that includes Markup, CSS, JS, Bash, Git, Java, JSON, Latex, Markdown, Python, R, SQL, YAML, and TOML. Don&amp;rsquo;t forget you need to update Prism&amp;rsquo;s JS and CSS.&lt;/p>
&lt;p>I swapped the original logo for a text title, and made some small CSS adjustments to make the text more readable: decreased the contrast of the dark theme by 15%, changed the general line height, letter spacing of titles, and top margin of paragraphs.&lt;/p>
&lt;pre>&lt;code class="language-css">/* The dark theme settings are a separate style tag in the header*/
.intro-and-nav, .main-and-footer { filter: invert(85%) }
* { background-color: inherit }
img:not([src*=&amp;quot;.svg&amp;quot;]), .colors, iframe, .demo-container { filter: invert(85%) }
/* This is the site's main CSS */
html {
font-size: calc(1em + 0.33vw);
font-family: Arial, Helvetica Neue, sans-serif;
line-height: 1.5;
line-height: 1.8;
color: #111;
background-color: #fefefe;
}
h1,
h2,
h3,
h4 {
font-family: Miriam Libre, serif;
line-height: 1.125;
letter-spacing: 2px;
}
p + p {
margin-top: 1rem;
}
.logo {
border: 0;
font-size: 1.5rem;
}
&lt;/code>&lt;/pre>
&lt;p>I substituted the original homepage for the blog&amp;rsquo;s post list as I want them to be the focus of visitors, and made the about section an external link to my personal website. For the first change, I had to move the theme&amp;rsquo;s post template from &lt;code>post/single.html&lt;/code> to &lt;code>_default/single.html&lt;/code>, so all .md files in the &lt;code>content&lt;/code> folder are rendered with it, and moved &lt;code>_default/list.html&lt;/code> to &lt;code>layouts/index.html&lt;/code>, so the original list of posts is now at the homepage. For the second change, I added a conditional to the navigation links rendering to make it open in a new tab:&lt;/p>
&lt;pre>&lt;code class="language-liquid">&amp;lt;a href=&amp;quot;{{ .URL }}&amp;quot; {{ if $active }}aria-current=&amp;quot;page&amp;quot;{{ end }} {{ if eq .Name &amp;quot;About&amp;quot; }}target=&amp;quot;_blank&amp;quot;{{ end }}&amp;gt;
&lt;/code>&lt;/pre>
&lt;p>I added support for Staticman comments instead of Disqus to have a git-backed, lightweight, ethical comment provider. You can read more about the whole process &lt;a href="https://jvblog.net/configuring-staticman-hugo/">here&lt;/a>&lt;/p>
&lt;p>I added support for &lt;a href="https://www.goatcounter.com/">GoatCounter&lt;/a> for web analytics instead of Google Analytics for &lt;a href="https://plausible.io/blog/remove-google-analytics">these reasons&lt;/a>; &lt;a href="https://mentalpivot.com/ethical-web-analytics-alternatives-google/">this post&lt;/a> by David Papandrew made the search for an alternative easier. GoatCounter gives me the level of detail just right to know what are the most visited posts, referrals, and visitors&amp;rsquo; platforms, it is GDPR friendly as it does not rely on cookies, just around 1.5Kb, open-source, and free for under 100k pageviews a month. You can support them in &lt;a href="https://github.com/sponsors/arp242">GitHub Sponsors&lt;/a> and &lt;a href="https://www.patreon.com/arp242">Patreon&lt;/a>. Installing GC was really easy, all I had to do was to create an account there and add the following script:&lt;/p>
&lt;pre>&lt;code class="language-js">&amp;lt;script data-goatcounter=&amp;quot;https://MY_SITE.goatcounter.com/count&amp;quot; async src=&amp;quot;//gc.zgo.at/count.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code>&lt;/pre>
&lt;p>I updated the RSS template to include full posts instead of just descriptions to make them work better with readers like Feedly, which I use a lot. I added &lt;a href="https://github.com/JulioV/blog/commit/81a11583027f5d4b83a00ea2808ab88e87adb731">this file&lt;/a> to &lt;code>layouts/rss.xml&lt;/code>.&lt;/p>
&lt;p>Finally, I activated the asset optimization in Netlify to compress images and minify and bundle CSS and JS files using my &lt;code>netlify.toml&lt;/code> file. For the latter to work, all their links need to be relative, so I modified the following lines in my templates:&lt;/p>
&lt;pre>&lt;code class="language-liquid">&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;{{ &amp;quot;css/prism.css&amp;quot; | relURL }}&amp;quot; media=&amp;quot;none&amp;quot; onload=&amp;quot;this.media='all';&amp;quot;&amp;gt;
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; href=&amp;quot;{{ $styles.RelPermalink }}&amp;quot;&amp;gt;
&amp;lt;script src=&amp;quot;{{ &amp;quot;js/prism.js&amp;quot; | relURL }}&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;{{ &amp;quot;js/dom-scripts.js&amp;quot; | relURL }}&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code>&lt;/pre>
&lt;pre>&lt;code class="language-toml">// Append this to your netlify.toml
[build.processing]
skip_processing = false
[build.processing.css]
bundle = true
minify = true
[build.processing.js]
bundle = true
minify = true
[build.processing.images]
compress = true
&lt;/code>&lt;/pre>
&lt;p>All these changes gave the blog a &lt;a href="https://gtmetrix.com/reports/jvblog.net/XsWRPzvq">PageSpeed&lt;/a> score of 100%, Ylow Score of 94% with a Fully Loaded Time of 1.7s and a Total Page Size of 109Kb.&lt;/p></description></item><item><title>Configuring Staticman Comments with Hugo</title><link>https://jvblog.net/configuring-staticman-hugo/</link><pubDate>Sun, 05 Apr 2020 00:00:00 +0000</pubDate><guid>https://jvblog.net/configuring-staticman-hugo/</guid><description>&lt;p>UPDATE: Fix broken links to staticman&amp;rsquo;s partial, CSS and JS files.&lt;/p>
&lt;p>I wanted to add comments to my blog, and Disqus seemed like a good option as the &lt;a href="https://github.com/zwbetz-gh/cupper-hugo-theme">theme&lt;/a> I&amp;rsquo;m using supports it out of the box. However, as things stand, I am happy with a solution that doesn&amp;rsquo;t require storing people&amp;rsquo;s data in third party databases and doesn&amp;rsquo;t add &lt;a href="https://replyable.com/2017/03/disqus-is-your-data-worth-trading-for-convenience/">ads and unnecessary tracking scripts&lt;/a> that could make the reading experience slower or cluttered.&lt;/p>
&lt;p>After searching for open-source/ethical comment suppliers, I found out about &lt;a href="https://staticman.net/">Staticman&lt;/a>, and I am giving it a try since it integrates with Hugo blogs, it uses a git repository to store and triage comments, it&amp;rsquo;s been around since 2015, and has good documentation. I just had to work around some constraints. In essence, you need to deploy your own instance of Staticman to Heroku as the official Staticman API hits its quota frequently (Heroku&amp;rsquo;s free tier is enough tho&amp;rsquo;), I wanted to keep this blog&amp;rsquo;s comments on a separate repository, and I am using Staticman API V2 since everything is hosted on GitHub (V3 supports other providers like Gitlab).&lt;/p>
&lt;p>This &lt;a href="https://petersen.pro/blog/staticman-comments-in-separate-repository/">post&lt;/a> by Arne Petersen was of great help to put everything together. After some tweaks, my deployment works like this:&lt;/p>
&lt;ul>
&lt;li>I&amp;rsquo;m only collecting people&amp;rsquo;s names and comments&lt;/li>
&lt;li>I&amp;rsquo;m using reCaptcha 2 to avoid spam&lt;/li>
&lt;li>I only load reCaptcha&amp;rsquo;s JS script when you click the &amp;ldquo;Show Comments&amp;rdquo; button&lt;/li>
&lt;li>I accept/reject comments using pull requests.&lt;/li>
&lt;li>After accepting a comment, my blog is re-build and published automatically in Netlify using webhooks&lt;/li>
&lt;li>After someone submits a comment, they get redirected to the original blog post with a message explaining their comment will go live after approval. No AJAX or popups are required, and you can try it leaving a comment!&lt;/li>
&lt;/ul>
&lt;p>And the instructions:&lt;/p>
&lt;ol>
&lt;li>Create a repository for your comments in your &lt;code>main&lt;/code> GitHub account; we will call it &lt;code>blog_comments&lt;/code>&lt;/li>
&lt;li>Create a secondary GitHub account; we will call it &lt;code>account2&lt;/code>. This is for security reasons as Arne pointed out, you are creating a Personal Access Token and keeping it in your Heroku instance which could give anyone who gets hold of it full access to your GitHub account.&lt;/li>
&lt;li>Create a Personal Access Token in &lt;code>account2&lt;/code> at &lt;a href="https://github.com/settings/tokens">https://github.com/settings/tokens&lt;/a>. Save it because you can only see it once, and you will need it in a bit.&lt;/li>
&lt;li>Invite &lt;code>account2&lt;/code> as a collaborator to &lt;code>blog_comments&lt;/code> going to &lt;code>https://github.com/YOUR_MAIN_GITHUB_ACCOUNT/blog_comments/settings/access&lt;/code>&lt;/li>
&lt;li>Deploy Staticman to Heroku using the purple button in the project&amp;rsquo;s &lt;a href="https://github.com/eduardoboucas/staticman">README&lt;/a> (make sure it&amp;rsquo;s in the master branch)&lt;/li>
&lt;li>Create a private key for Staticman (you can do this in your Heroku instance going to &amp;ldquo;More&amp;rdquo; -&amp;gt; &amp;ldquo;Run console&amp;rdquo;): &lt;code>openssl genrsa –out key.pem; cat key.pem&lt;/code>&lt;/li>
&lt;li>Add the following three Config vars to your Heroku instance in &lt;code>https://dashboard.heroku.com/apps/YOUR_INSTANCE_NAME/settings&lt;/code>:
&lt;pre>&lt;code class="language-yaml">NODE_ENV production
GITHUB_TOKEN &amp;quot;YOUR PERSONAL ACCESS TOKEN&amp;quot;
RSA_PRIVATE_KEY &amp;quot;CONTENT OF key.pem&amp;quot;
&lt;/code>&lt;/pre>
&lt;/li>
&lt;li>If you want to use reCaptcha to avoid spam, do the following:&lt;/li>
&lt;li>Register your blog &lt;a href="https://www.google.com/recaptcha/admin">here&lt;/a>. You can add &lt;code>localhost&lt;/code> to the domain list to be able to test everything in your local machine. Save your &lt;code>siteKey&lt;/code> and &lt;code>secret&lt;/code>&lt;/li>
&lt;li>Encrypt your reCaptcha &lt;code>secret&lt;/code> obtained before by querying your Heroku instance in this URL: &lt;code>https://YOUR_HEROKU_APP_NAME.herokuapp.com/v2/encrypt/YOUR_UNENCRYPTED_RECAPTCHA_SECRET&lt;/code>&lt;/li>
&lt;li>Add &lt;a href="https://gist.github.com/JulioV/c1386fde8920406f3871666bf059d1a3">this partial&lt;/a> to your blog and call &lt;code>{{ partial &amp;quot;staticman.html&amp;quot; . }}&lt;/code> where you want to load your comments.&lt;/li>
&lt;li>Add these &lt;a href="https://gist.github.com/JulioV/5e0297961e4425054ec94c44c880fc70">CSS rules&lt;/a> to your blog.&lt;/li>
&lt;li>Add this &lt;a href="https://gist.github.com/JulioV/8f3bfd3113764fc9c66726a12d651820">JS script&lt;/a> to your &lt;strong>partials&lt;/strong>&lt;/li>
&lt;li>Add the following lines to the &lt;code>params&lt;/code> list in your Hugo blog&amp;rsquo;s &lt;code>config.yaml&lt;/code>
&lt;pre>&lt;code class="language-yaml">staticman:
api: https://&amp;lt;YOUR_HEROKU_APP_NAME&amp;gt;.herokuapp.com/v2/entry/YOUR_MAIN_GITHUB_ACCOUNT/blog_comments/master/comments
recaptcha:
sitekey: &amp;quot;YOUR RECAPTCHA KEY&amp;quot;
secret: &amp;quot;YOUR ENCRYPTED RECAPTCHA SECRET&amp;quot;
&lt;/code>&lt;/pre>
&lt;/li>
&lt;li>Add your &lt;a href="https://staticman.net/docs/configuration">Staticman configuration file&lt;/a>, &lt;code>staticman.yaml&lt;/code>, to the root of &lt;code>blog_comments&lt;/code>. You can use the one below or &lt;a href="https://raw.githubusercontent.com/eduardoboucas/staticman/master/staticman.sample.yml">this other one&lt;/a> as a reference if you want to collect more data like emails or personal websites.
&lt;pre>&lt;code class="language-yaml">comments:
allowedFields: [&amp;quot;name&amp;quot;, &amp;quot;comment&amp;quot;]
branch: &amp;quot;master&amp;quot;
commitMessage: &amp;quot;New comment in {options.slug}&amp;quot;
filename: &amp;quot;comment-{@timestamp}&amp;quot;
format: &amp;quot;yaml&amp;quot;
generatedFields:
date:
type: date
options:
format: &amp;quot;iso8601&amp;quot;
moderation: true
name: &amp;quot;YOUR SITES NAME&amp;quot;
path: &amp;quot;{options.slug}&amp;quot;
requiredFields: [&amp;quot;name&amp;quot;, &amp;quot;comment&amp;quot;]
transforms:
email: md5
// Delete this if you do not want to use reCaptcha
reCaptcha:
enabled: true
siteKey: &amp;quot;YOUR RECAPTCHA KEY&amp;quot;
secret: &amp;quot;YOUR ENCRYPTED RECAPTCHA SECRET&amp;quot;
&lt;/code>&lt;/pre>
&lt;/li>
&lt;li>Add your &lt;code>blog_comments&lt;/code> repo as a submodule to your main repo in the &lt;code>data/comments&lt;/code> folder: &lt;code>git submodule add https://github.com/YOUR_MAIN_GITHUB_ACCOUNT/blog-comments.git data/comments&lt;/code>&lt;/li>
&lt;li>I use Netlify to publish my blog, so I had to modify my Netlify build command to pull the latest version of &lt;code>blog_comments&lt;/code> to render any new comments. You can do this using Netlify&amp;rsquo;s website or by adding a &lt;code>netlify.toml&lt;/code> file to the root of your blog repo with the following lines:
&lt;pre>&lt;code class="language-toml">[build]
publish = &amp;quot;public&amp;quot;
command = &amp;quot;git submodule update --remote data/comments &amp;amp;&amp;amp; hugo --gc --minify&amp;quot;
[context.production.environment]
HUGO_VERSION = &amp;quot;v0.68.3&amp;quot;
HUGO_ENV = &amp;quot;production&amp;quot;
HUGO_ENABLEGITINFO = &amp;quot;true&amp;quot;
&lt;/code>&lt;/pre>
&lt;/li>
&lt;li>Every time someone comments on a blog post, Staticman creates a new branch and a Pull Request (PR) in &lt;code>blog_comments&lt;/code> which you can accept or reject to publish it or not. Branches will start to pile up, so, for those PRs you reject, you have to delete their branches using GitHub&amp;rsquo;s UI manually. Still, for those PRs you accept, GitHub can automatically delete them by activating &lt;a href="https://help.github.com/en/github/administering-a-repository/managing-the-automatic-deletion-of-branches">this feature&lt;/a>.&lt;/li>
&lt;li>At this point, you can submit your first comment from your computer or, commit everything to GitHub and do it online.&lt;/li>
&lt;li>&lt;em>Optional&lt;/em>. If you want to avoid triggering a new Netlify build manually every time you accept a comment, you can automatize it by using &lt;a href="https://www.integromat.com">Integromat&amp;rsquo;s webhooks&lt;/a>. You could also use Zappier, but you have to switch to their paid tier.
&lt;ol>
&lt;li>Got to Netlify&amp;rsquo;s &lt;code>Build hooks&lt;/code> section in &lt;code>https://app.netlify.com/sites/YOUR_NETLIFY_DOMAIN/settings/deploys#build-hooks&lt;/code> and click on &lt;code>Add build hook&lt;/code>. Save the generated URL&lt;/li>
&lt;li>Create a new Scenario in Integromat&lt;/li>
&lt;li>Add a &lt;code>Custom Webhook trigger&lt;/code>. Inside, add a new &lt;code>Webhook&lt;/code> and copy its URL. Click on &lt;code>Determine data structure&lt;/code>&lt;/li>
&lt;li>Go to &lt;code>https://github.com/YOUR_MAIN_GITHUB_ACCOUNT/blog_comments/settings/hooks&lt;/code>. Click on &lt;code>Add webhook&lt;/code>, in &lt;code>Payload URL&lt;/code> add the URL of the Integromat &lt;code>Custom Webhook trigger&lt;/code>, in &lt;code>Content type&lt;/code> select &lt;code>application/json&lt;/code>, and under &lt;code>Let me select individual events&lt;/code> check &lt;code>Pull requests&lt;/code>. Click on &lt;code>Add Webhook&lt;/code>.&lt;/li>
&lt;li>Submit a comment in your blog, so Staticman creates a new Pull Request in &lt;code>blog_comments&lt;/code>, and Integromat infers its content. You should see a confirmation message in the &lt;code>Custom Webhook trigger&lt;/code>.&lt;/li>
&lt;li>Add a &lt;code>HTTP action&lt;/code> in Integromat. Connect this to the &lt;code>Custom Webhook trigger&lt;/code>&lt;/li>
&lt;li>In the connection between the &lt;code>HTTP action&lt;/code> and the &lt;code>Custom Webhook trigger&lt;/code>, add two conditions joined by an &lt;code>AND&lt;/code> operator: &lt;code>action&lt;/code> = &lt;code>closed&lt;/code> and &lt;code>pul_request: merged&lt;/code> = &lt;code>true&lt;/code>. They should be autocompleted if Integromat was able to infer the PR&amp;rsquo;s content&lt;/li>
&lt;li>Click in the &lt;code>HTTP action&lt;/code>, add the Netlify hook&amp;rsquo;s URL you got earlier to the action&amp;rsquo;s &lt;code>URL&lt;/code> field, and change its &lt;code>Method&lt;/code> to &lt;code>POST&lt;/code>&lt;/li>
&lt;li>Turn the scenario ON using the switch at the bottom left and set &lt;code>Schedule setting&lt;/code> to &lt;code>Immediatly&lt;/code>&lt;/li>
&lt;li>From now on, the scenario should trigger a Netlify build every time you accept a Staticman&amp;rsquo;s Pull Request&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ol>
&lt;p>Feel free to leave a comment if you have issues or questions!&lt;/p></description></item><item><title>Organizing ideas with the Zettelkasten method</title><link>https://jvblog.net/organizing-ideas-zettelkasten/</link><pubDate>Fri, 03 Apr 2020 00:00:00 +0000</pubDate><guid>https://jvblog.net/organizing-ideas-zettelkasten/</guid><description>&lt;p>I came across the Zettelkasten (ZK) method as a flexible way of organizing knowledge. I have read different descriptions, and most of them describe it as a second brain, a single text-based repository where you can dump all your ideas and link them to not only store but also generate knowledge. This is what resonated with me the most, as I know that I am the most creative once I know any topic(s) in-depth and can make connections between its different components.&lt;/p>
&lt;p>According to &lt;a href="https://zettelkasten.de/posts/overview/#principles">this website&lt;/a>, keeping a ZK repository has multiple benefits like improving your thinking, writing, memory, and learning. I do think that keeping one will help me resurface ideas that I have after reading papers or technical content and retain concepts for longer as you are supposed to add notes in your own words instead of copy and pasting. That said, I need to check if the recommendation of writing long pieces in a ZK works for me. The idea is that you outline your text in a note and then add subsections in other notes linked to the original. I usually follow this iterative approach to writing the difference is that I like to have a quick overview of what I have written to expand and reorganize content, so I have to see if I can get used to not having this.&lt;/p>
&lt;p>For the actual implementation of my ZK repo, I chose to type all my notes in markdown files stored in a single directory backed up in Github and &lt;a href="https://www.sublimetext.com">Sublime&lt;/a> with the &lt;a href="(https://github.com/renerocksai/sublime_zk#zettelkasten-mode)">ZK plugin&lt;/a> in Mac OS (it should be cross-platform).&lt;/p>
&lt;h2 id="sublime-as-editor">Sublime as editor&lt;/h2>
&lt;p>I set up my ZK repository in Mac OS using Sublime and a few extra plugins as suggested &lt;a href="https://github.com/renerocksai/sublime_zk#zettelkasten-mode">here&lt;/a>. Most of the steps below are taken from that project&amp;rsquo;s README, but I replicate some of them here for future reference (I find the official docs a bit overwhelming).&lt;/p>
&lt;ol>
&lt;li>Install the &lt;a href="(https://github.com/renerocksai/sublime_zk#zettelkasten-mode)">ZK plugin&lt;/a>:&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>In Sublime&amp;rsquo;s command palette (&lt;code>cmd + P&lt;/code>) run &lt;em>Install Package Control&lt;/em>&lt;/li>
&lt;li>In Sublime&amp;rsquo;s command palette run &lt;em>Package Control: Add Repository&lt;/em> and paste this URL when prompted &lt;code>https://github.com/renerocksai/sublime_zk&lt;/code>&lt;/li>
&lt;li>In Sublime&amp;rsquo;s command palette run &lt;em>Package Control: Install Package&lt;/em> and search for &lt;code>sublime_zk&lt;/code> when prompted&lt;/li>
&lt;/ul>
&lt;ol start="2">
&lt;li>Install the Silver Searcher plugin using: &lt;code>brew install the_silver_searcher&lt;/code>&lt;/li>
&lt;li>Install Pandoc using: &lt;code> brew install pandoc&lt;/code>&lt;/li>
&lt;li>Re-start Sublime&lt;/li>
&lt;/ol>
&lt;h3 id="initializing-my-zk-folder-and-creating-my-first-note">Initializing my ZK folder and creating my first note&lt;/h3>
&lt;ol>
&lt;li>In Sublime&amp;rsquo;s command palette run &lt;em>ZK: New Zettel Note&lt;/em> and type a name for your new note&lt;/li>
&lt;li>When prompted choose or create a new folder as your ZK repository (I added mine to git source control)&lt;/li>
&lt;li>In Sublime&amp;rsquo;s command palette run &lt;em>ZK: Enter in Zettelkasten Mode&lt;/em>&lt;/li>
&lt;li>Now you can create a new note by typing &lt;code>Shift + Enter&lt;/code>&lt;/li>
&lt;/ol>
&lt;h3 id="configuring-my-zk-folder">Configuring my ZK folder&lt;/h3>
&lt;p>I am using the default file extension (&lt;code>.md&lt;/code>), link notation (&lt;code>[[link]]&lt;/code>) and ID precision in minutes (&lt;code>YYYYMMDDHHMM&lt;/code>)&lt;/p>
&lt;p>I configured ZK to insert the title of a note next to its ID when they are linked from other notes. In addition, I changed the color scheme, set the bib citation format to pandoc&amp;rsquo;s, and modified the template for new notes to insert the note ID, title, date, and tags at the beginning of the file. To do all this, go to Sublime&amp;rsquo;s &lt;em>Preferences&lt;/em> &amp;gt; &lt;em>Package Settings&lt;/em> &amp;gt; &lt;em>Sublime ZK&lt;/em> &amp;gt; &lt;em>Settings User&lt;/em> and add the following code:&lt;/p>
&lt;pre>&lt;code class="language-json">{
// Insert note titles next to links when linking a note frome another
&amp;quot;insert_links_with_titles&amp;quot;: true,
// Template for new notes
&amp;quot;new_note_template&amp;quot;:
&amp;quot;---\nnote-id: {id}\ntitle: {title}\ndate: {timestamp: %Y-%m-%d}\ntags: \n---\n&amp;quot;,
&amp;quot;color_scheme&amp;quot;: &amp;quot;Packages/sublime_zk/Monokai Extended-zk.tmTheme&amp;quot;,
&amp;quot;citations-mmd-style&amp;quot;: false,
}
&lt;/code>&lt;/pre>
&lt;p>When you create a new note with the config above, its header will look like this:&lt;/p>
&lt;pre>&lt;code class="language-yaml">---
note-id: 202003281428
title: My second note
date: 2020-03-28
tags:
---
&lt;/code>&lt;/pre>
&lt;h3 id="taking-notes-in-my-zk">Taking notes in my ZK&lt;/h3>
&lt;p>I create notes with the following &lt;a href="https://zettelkasten.de/posts/overview/#principles">principles&lt;/a> in mind:&lt;/p>
&lt;ul>
&lt;li>Each note is atomic and self-contained. This means that a note is related to a single idea, and I don&amp;rsquo;t need anything else to remember what a note means.&lt;/li>
&lt;li>A link is a stronger connection than a tag. In other words, a single idea is developed throughout different notes connected by links, and multiple ideas related to the same broad topic are grouped with tags.&lt;/li>
&lt;li>If a new note is related to an existing note, I link the parent note in the child note (&lt;code>[[parent ID]]&lt;/code>).&lt;/li>
&lt;li>I try to use specific tags, and before adding a new one, I list all existent tags to make sure I am not duplicating any. Using the &lt;code># + ?&lt;/code> shortcut helps me avoid typos.&lt;/li>
&lt;/ul>
&lt;p>Finally, I found the following shortcuts the most useful:&lt;/p>
&lt;ul>
&lt;li>Create a new note &lt;code>shift + enter&lt;/code>&lt;/li>
&lt;li>Open the note pointed by a link &lt;code>opt + double-click on link&lt;/code> or &lt;code>cursor on link + ctrl + enter&lt;/code>&lt;/li>
&lt;li>Insert a link to a note &lt;code>[ + [&lt;/code>&lt;/li>
&lt;li>Find all notes referencing another note &lt;code>cursor on link + opt + enter&lt;/code>&lt;/li>
&lt;li>View all tags &lt;code># + !&lt;/code>&lt;/li>
&lt;li>View all notes &lt;code>[ + !&lt;/code>&lt;/li>
&lt;li>Autocomplete tag &lt;code># + ?&lt;/code>&lt;/li>
&lt;li>Find all notes tagged by a tag &lt;code>cursor on tag + ctrl + enter&lt;/code>&lt;/li>
&lt;li>Expand note link inline &lt;code>ctrl + .&lt;/code>&lt;/li>
&lt;li>Expand tag inline (all referencing notes) &lt;code>ctrl + .&lt;/code>&lt;/li>
&lt;li>Expand citekey inline (all referencing notes) &lt;code>ctrl + .&lt;/code>&lt;/li>
&lt;li>Insert pandoc citation &lt;code>[ + @&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>I will update my experience using ZK in a few months.&lt;/p></description></item></channel></rss>