Compare commits

...

25 Commits

Author SHA1 Message Date
58cc6f35a4 Minor updates 2024-11-06 07:08:38 -05:00
d4fca30f08 Adds initial emacs post 2024-11-05 10:10:23 -05:00
54961ebd1e Adds dates, fixes orders of posts 2024-10-13 17:15:21 -04:00
11cb9f387e Adds more tool notes 2024-10-13 16:41:27 -04:00
f359329fa8 Adds date to multi-room post 2024-10-13 16:41:17 -04:00
360a4af22d Reformatting job description 2024-10-10 20:36:12 -04:00
e1c595f482 Initial tools post 2024-10-10 20:34:32 -04:00
1d2fa80c73 Updates to job description 2024-10-09 08:28:09 -04:00
4ff168940a More work on job description 2024-10-07 09:52:12 -04:00
be99876af5 More layout tweaks 2024-10-05 09:15:16 -04:00
3f42924242 Small touchups 2024-10-04 22:25:37 -04:00
43d3891bee Switching to one.el
This seems to be a really nice balance of control and getting me to
write some more elisp.
2024-10-04 22:19:12 -04:00
a73cd37980 Updates 2024-10-04 20:17:29 -04:00
564c89af7c More journaling 2024-10-04 08:27:31 -04:00
9f533ffd1a Some new posts 2024-10-03 21:08:01 -04:00
7b1899a010 More tweaks to setup 2024-10-03 08:55:48 -04:00
4c4d048b5c Different theme, deploy, all posts 2024-10-02 22:22:22 -04:00
3b30684a5d Updating posts - cleaning things up 2024-10-02 21:18:22 -04:00
620011bf79 Adding individual posts 2024-10-02 20:28:42 -04:00
46e9d9712c Switching to pure org 2024-10-02 18:25:09 -04:00
d6bd7d3359 Typo theme 2024-10-02 13:34:43 -04:00
1fb4ba8109 Ignoring theme dir so it's easier to switch themes for now 2024-10-02 07:04:18 -04:00
7177095d03 Using ox-hugo and converted one post 2024-10-02 06:55:01 -04:00
88439859d9 More hugo conversion, deploy script 2024-10-01 22:04:18 -04:00
d545bd6918 Removes gitmodules 2024-09-30 21:02:14 -04:00
19 changed files with 1057 additions and 12 deletions

2
.gitignore vendored
View File

@@ -1 +1 @@
public public

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "themes/apollo"]
path = themes/apollo
url = https://github.com/not-matthias/apollo

View File

@@ -1,5 +0,0 @@
+++
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
date = {{ .Date }}
draft = true
+++

9
assets/wfot.css Normal file
View File

@@ -0,0 +1,9 @@
.post-item {
display: flex;
}
.post-date {
display: block;
margin-right: 16px;
min-width: 100px;
}

54
aws-metric-filters.org Normal file
View File

@@ -0,0 +1,54 @@
:PROPERTIES:
#+SETUPFILE: setup.org
#+subtitle:
:END:
** Structed and passively collected metrics via AWS CloudWatch
AWS is a vast and sprawling set of services. It can be hard to find the
hidden gems like this one so I wanted to point this one out.
Structured metrics are very helpful to monitoring the health and
function of an software system.
- Do you want to know how long a particular transaction typically takes?
- How fast your database queries are?
- How long external APIs take to respond?
- Fire an alert when a particular function on the site happens too many
times? Or too few times?
...plus a million other things specific to whatever system you're
working on.
There are a lot of great tools for doing this and one that you might not
know about is AWS CloudWatch Metric Filters. If you're already on AWS
then you should consider these because it requires only that your
application logs to CloudWatch.
If you're on ECS then the [[https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_awslogs.html][awslogs]] log driver for Docker gets you that
nearly for free. By "free" I mean that your application itself can
have /zero/ dependencies on AWS services and not require any AWS
credentials or libraries to start pumping out metrics that you can
visualize, alert on and record over time.
The [[https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/MonitoringLogData.html][AWS docs]] themselves offer the canonical reference for configuring
these so I won't go into detail here.
However, the gist is that for a log filter you define the following
properties
- A filter pattern for extracting a discrete metric value out of a log
entry
- A metric name to store the value in
- An optional dimension for sub-classifying the value
- And finally a log group to extract the metric values from
After that you just run the application and as the logs roll in the
metric values get pumped out. Then you can [[https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Create-alarm-on-metric-math-expression.html][define alarms for alerting]]
on them, [[https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Dashboards.html][graph them]], [[https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-scaling-simple-step.html#policy-creating-alarm-console][define autoscaling rules]] from them and more.
To conclude - AWS is big and hairy. While there are benefits to staying
platform agnostic, some AWS services don't require much or any coupling
of your application code to take advantage of. Cloudwatch Metrics is one
of those services and you can get a lot of value out of it with not much
effort.

15
big-companies.org Normal file
View File

@@ -0,0 +1,15 @@
:PROPERTIES:
#+SETUPFILE: setup.org
:END:
Organizing people is a difficult problem which only gets more difficult as youmore people need to be organized.
The larger a company is the more of its internal structures, rules, policies, history, etc are devoted _just_ to organizing people.
For me, realizing this was like the first time you hear a flourescent light buzzing in an otherwise quiet room.
Reasonable people can differ on this point, but for my own sake I'd much rather avoid all the people-organizing baggage that comes with large companies.
I don't have a hard-and-fast rule about the size of a place I want to work but the larger a place is then generally the more reason I need to want to be there.
Of course, this is all kind of theoretical at this point, as [[https://flipstone.com][Flipstone]] is my forever home.

51
https-at-home.org Normal file
View File

@@ -0,0 +1,51 @@
:PROPERTIES:
#+SETUPFILE: setup.org
#+keywords: homelab
#+subtitle:
:END:
** HTTPS @ Home
I run a lot of services at home.
This includes, but isn't limited to
- [[https://archivebox.io/][ArchiveBox]]
- [[https://github.com/dani-garcia/vaultwarden][VaultWarden]]
- [[https://github.com/navidrome/navidrome][Navidrome]]
- [[https://plex.tv][Plex]]
- [[https://github.com/LibrePhotos/librephotos][LibrePhotos]]
- This blog
and a lot more.
Pretty much anything that's served up over HTTP is always nice if not
necessary to have behind TLS.
[[https://letsencrypt.org/][LetsEncrypt]] long ago brought free certs to
the masses and there are a lot of tools for automating that nowadays.
My preferred approach for getting all the unnecessary nonsense I
self-host at home behind TLS is [[https://caddyserver.com][Caddy]].
I have a super straight forward setup, generally:
- Run Caddy in a docker container
- Create a wildcard CNAME record in my DNS pointing at my home's
(effectively) static IP
- Add an entry in my Caddyfile for each services I'm running at home on
its own subdomain
- If it's a service then I add it with a =reverse_proxy= block
- If it's a static site (like this) then there's a block for
- If it's something I want only accessible on my home network then I put
a block like
#+BEGIN_EXAMPLE
@local_network {
path *
remote_ip
}
#+END_EXAMPLE
in the directive. And voila.
Then tell Caddy to reload the config and I'm done.

View File

@@ -1,3 +0,0 @@
baseURL = 'https://jamesbrechtel.com/'
languageCode = 'en-us'
title = 'Excessively verbose'

16
index.org Normal file
View File

@@ -0,0 +1,16 @@
:PROPERTIES:
#+SETUPFILE: setup.org
#+export_file_name: index
#+title: circumlocuting
#+subtitle:
:END:
* [[file:big-companies][The problem with large organizations]] - 2024-10-02
* [[file:simple-css.org][Simple CSS frameworks]] - 2024-09-30
* [[file:let-people-fail.org][Let people fail]] - 2024-09-25
* TODO [[file:job-description.org][Just what is it you do here?]]
* TODO [[file:managing-expectations.org][Managing Expectations]]
* [[file:https-at-home.org][HTTPS @ Homelab]]
* [[file:multi-room-audio.org][Multi-room audio setup]]
* [[file:vi-everywhere.org][vi-editing everywhere]]
* [[file:aws-metric-filters.org][AWS Cloudwatch Metric Filters]]

10
job-description.org Normal file
View File

@@ -0,0 +1,10 @@
:PROPERTIES:
#+SETUPFILE: setup.org
:END:
** Just what is it you do here?
I've never liked working at [[file:big-companies.org][larger companies]].
But that doesn't _always_ simplify things.
Sometimes at smaller companies you end up defining your own job, to some degree.

29
let-people-fail.org Normal file
View File

@@ -0,0 +1,29 @@
:PROPERTIES:
#+SETUPFILE: setup.org
#+keywords: advice relationships people
#+subtitle:
:END:
* How (and why) to let people fail
Warning: This, like most things, will involve a fair bit of projection.
Effective and enjoyable collaboration with other people requires mutual trust.
I believe that for someone to feel trusted by another person then they need the space to fail.
I _think_ this is obvious when considering what not having the space to fail looks like.
Not having the space to fail means your collaborator is doing one of two things:
1. Directing every action you take a.k.a. micromanaging
2. Coming behind you and redoing all of your work
Both of these are attempts by the other person to minimize risk (or simply cases where they're failing to manage their own anxieties).
These actions are counter productive to fostering trust and should be avoided unless failure is too costly.
I'm _not_ saying all collaboration _requires_ building trust. There are times when you simply can't afford failure or mistakes.
What I am saying is that people frequently misjudge the value in deliberately giving others the space to fail for the sake of fostering trust.
Building trust is important and we should do it deliberately.

View File

@@ -0,0 +1,5 @@
:PROPERTIES:
#+SETUPFILE: setup.org
:END:
* Managing expectations!

46
multi-room-audio.org Normal file
View File

@@ -0,0 +1,46 @@
:PROPERTIES:
#+SETUPFILE: setup.org
#+keywords: homelab snapcast audio
#+subtitle:
:END:
** My multiroom audio setup
I've put my home audio solution together out of the following
components.
- [[https://github.com/badaix/snapcast][Snapcast]]
- [[https://www.musicpd.org/][MPD]]
- [[https://github.com/librespot-org/librespot][Librespot]]
- [[https://github.com/mikebrady/shairport-sync][Shairport-sync]]
- A mini-PC in my closet running the above software
- Two Raspberry Pi 4s
- Four Raspberry Pi Zero Ws
- Some desktop speakers and some Bluetooth speakers (wired to the Pis)
Each of the Raspberry Pis is in a room or porch attached to a speaker.
Snapcast lets me take an audio source and synchronize it across multiple
clients. Each of the Raspberry Pis are running a =snapclient= instance
and play whatever the =snapserver= instance tells them to.
Snapcast is setup to send whichever of the streams (MPD, Spotify,
Shairport-sync/AirPlay) is playing audio to each of the clients that are
connected to it.
This lets me or anyone else on my WiFi network play directly on one or
more of the speakers - each named for the room that they're in using
either Spotify, AirPlay, picking from my own music collection or by
pointing at a URL (like to a podcast episode).
This works out great and we've used it at home for the past year.
I'd like to get the podcast experience to a more seamless place but it's
pretty OK right now using AirMusic on my phone to play audio to the
speakers over AirPlay.

519
one.org Normal file
View File

@@ -0,0 +1,519 @@
* You're being willfully obtuse
:PROPERTIES:
:ONE: wfot-default-home-list-pages
:CUSTOM_ID: /
:END:
Here's what I'm thinking...
* Multi-room audio setup
:PROPERTIES:
:ONE: wfot-default
:DATE: 2022-11-08
:CUSTOM_ID: /multi-room-audio/
:END:
I've put my home audio solution together out of the following
components.
- [[https://github.com/badaix/snapcast][Snapcast]]
- [[https://www.musicpd.org/][MPD]]
- [[https://github.com/librespot-org/librespot][Librespot]]
- [[https://github.com/mikebrady/shairport-sync][Shairport-sync]]
- A mini-PC in my closet running the above software
- Two Raspberry Pi 4s
- Four Raspberry Pi Zero Ws
- Some desktop speakers and some Bluetooth speakers (wired to the Pis)
Each of the Raspberry Pis is in a room or porch attached to a speaker.
Snapcast lets me take an audio source and synchronize it across multiple
clients. Each of the Raspberry Pis are running a =snapclient= instance
and play whatever the =snapserver= instance tells them to.
Snapcast is setup to send whichever of the streams (MPD, Spotify,
Shairport-sync/AirPlay) is playing audio to each of the clients that are
connected to it.
This lets me or anyone else on my WiFi network play directly on one or
more of the speakers - each named for the room that they're in using
either Spotify, AirPlay, picking from my own music collection or by
pointing at a URL (like to a podcast episode).
This works out great and we've used it at home for the past year.
I'd like to get the podcast experience to a more seamless place but it's
pretty OK right now using AirMusic on my phone to play audio to the
speakers over AirPlay.
* HTTPS @ Homelab
:PROPERTIES:
:ONE: wfot-default
:DATE: 2022-11-08
:CUSTOM_ID: /large-companies/
:END:
I run a lot of services at home.
This includes, but isn't limited to
- [[https://archivebox.io/][ArchiveBox]]
- [[https://github.com/dani-garcia/vaultwarden][VaultWarden]]
- [[https://github.com/navidrome/navidrome][Navidrome]]
- [[https://plex.tv][Plex]]
- [[https://github.com/LibrePhotos/librephotos][LibrePhotos]]
- This blog
and a lot more.
Pretty much anything that's served up over HTTP is always nice if not
necessary to have behind TLS.
[[https://letsencrypt.org/][LetsEncrypt]] long ago brought free certs to
the masses and there are a lot of tools for automating that nowadays.
My preferred approach for getting all the unnecessary nonsense I
self-host at home behind TLS is [[https://caddyserver.com][Caddy]].
I have a super straight forward setup, generally:
- Run Caddy in a docker container
- Create a wildcard CNAME record in my DNS pointing at my home's
(effectively) static IP
- Add an entry in my Caddyfile for each services I'm running at home on
its own subdomain
- If it's a service then I add it with a =reverse_proxy= block
- If it's a static site (like this) then there's a block for
- If it's something I want only accessible on my home network then I put
a block like
#+BEGIN_EXAMPLE
@local_network {
path *
remote_ip
}
#+END_EXAMPLE
in the directive. And voila.
Then tell Caddy to reload the config and I'm done.
* AWS Cloudwatch Metric Filters
:PROPERTIES:
:ONE: wfot-default
:DATE: 2022-11-12
:CUSTOM_ID: /large-companies/
:END:
** Structed and passively collected metrics via AWS CloudWatch
AWS is a vast and sprawling set of services. It can be hard to find the
hidden gems like this one so I wanted to point this one out.
Structured metrics are very helpful to monitoring the health and
function of an software system.
- Do you want to know how long a particular transaction typically takes?
- How fast your database queries are?
- How long external APIs take to respond?
- Fire an alert when a particular function on the site happens too many
times? Or too few times?
...plus a million other things specific to whatever system you're
working on.
There are a lot of great tools for doing this and one that you might not
know about is AWS CloudWatch Metric Filters. If you're already on AWS
then you should consider these because it requires only that your
application logs to CloudWatch.
If you're on ECS then the [[https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_awslogs.html][awslogs]] log driver for Docker gets you that
nearly for free. By "free" I mean that your application itself can
have /zero/ dependencies on AWS services and not require any AWS
credentials or libraries to start pumping out metrics that you can
visualize, alert on and record over time.
The [[https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/MonitoringLogData.html][AWS docs]] themselves offer the canonical reference for configuring
these so I won't go into detail here.
However, the gist is that for a log filter you define the following
properties
- A filter pattern for extracting a discrete metric value out of a log
entry
- A metric name to store the value in
- An optional dimension for sub-classifying the value
- And finally a log group to extract the metric values from
After that you just run the application and as the logs roll in the
metric values get pumped out. Then you can [[https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Create-alarm-on-metric-math-expression.html][define alarms for alerting]]
on them, [[https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Dashboards.html][graph them]], [[https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-scaling-simple-step.html#policy-creating-alarm-console][define autoscaling rules]] from them and more.
To conclude - AWS is big and hairy. While there are benefits to staying
platform agnostic, some AWS services don't require much or any coupling
of your application code to take advantage of. Cloudwatch Metrics is one
of those services and you can get a lot of value out of it with not much
effort.
* vi-editing everywhere
:PROPERTIES:
:ONE: wfot-default
:DATE: 2022-11-13
:CUSTOM_ID: /vi-everywhere/
:END:
For my sake, I prefer to have Vim bindings in as many places as
possible.
Most shells can be configured to use Vim bindings by putting =set -o vi=
somewhere in your shell startup script.
If you're using ZSH then you'll probably want an additional binding to
restore CTRL-R reverse history search.
=bindkey '^R' history-incremental-search-backward=
For CLI tools that use the =readline= library then you can configure its
input mode using a =.inputrc= file in your =$HOME= directory.
This affects REPLs like =ghci= and tools like =psql=.
#+begin_src txt
set editing-mode vi
$if mode=vi
set keymap vi-command
# these are for vi-command mode
Control-l: clear-screen
set keymap vi-insert
# these are for vi-insert mode
Control-l: clear-screen
$endif
#+end_src
* The problem with large companies
:PROPERTIES:
:ONE: wfot-default
:DATE: 2024-09-20
:CUSTOM_ID: /large-companies/
:END:
Organizing people is a difficult problem which only gets more difficult as youmore people need to be organized.
The larger a company is the more of its internal structures, rules, policies, history, etc are devoted _just_ to organizing people.
For me, realizing this was like the first time you hear a flourescent light buzzing in an otherwise quiet room.
Reasonable people can differ on this point, but for my own sake I'd much rather avoid all the people-organizing baggage that comes with large companies.
I don't have a hard-and-fast rule about the size of a place I want to work but the larger a place is then generally the more reason I need to want to be there.
Of course, this is all kind of theoretical at this point, as [[https://flipstone.com][Flipstone]] is my forever home.
* Simple CSS frameworks
:PROPERTIES:
:ONE: wfot-default
:DATE: 2024-09-29
:CUSTOM_ID: /simple-css-frameworks/
:END:
I really like simple drop-in CSS resets like the one I use for this site.
At the time of writing, I'm using [[https://picocss.com/][Pico]] but I also considered [[https://yegor256.github.io/tacit/][tacit]]
The idea is that they provide nice default styling of HTML elements out of the box without the need to reference any specific classes.
The idea works well for sites that are much more content than layout - like this one.
Using tacit is a matter of incluing this link tag in the page's HEAD element:
#+BEGIN_SRC html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
#+END_SRC
* Let people fail
:PROPERTIES:
:ONE: wfot-default
:DATE: 2024-10-02
:CUSTOM_ID: /let-people-fail/
:END:
Warning: This, like most things, will involve a fair bit of projection.
Effective and enjoyable collaboration with other people requires mutual trust.
I believe that for someone to feel trusted by another person then they need the space to fail.
I _think_ this is obvious when considering what not having the space to fail looks like.
Not having the space to fail means your collaborator is doing one of two things:
1. Directing every action you take a.k.a. micromanaging
2. Coming behind you and redoing all of your work
Both of these are attempts by the other person to minimize risk (or simply cases where they're failing to manage their own anxieties).
These actions are counter productive to fostering trust and should be avoided unless failure is too costly.
I'm _not_ saying all collaboration _requires_ building trust. There are times when you simply can't afford failure or mistakes.
What I am saying is that people frequently misjudge the value in deliberately giving others the space to fail for the sake of fostering trust.
Building trust is important and we should do it deliberately.
* TODO Managing Expectations
:PROPERTIES:
:ONE: wfot-default
:DATE: 2024-10-04
:CUSTOM_ID: /managing-expectations/
:END:
:DRAFT:
I'll figure this out one day. Until then I'll just keep saying yes and burning myself out making everyone happy.
:END:
* TODO Tools I love
:PROPERTIES:
:ONE: wfot-default
:DATE: 2024-10-07
:CUSTOM_ID: /tools-i-love/
:END:
I work on a computer all day. I do things from query databases, making
a lot of HTTP API calls, parsing, transforming and generally munging
structured and unstructured data of all kinds. I edit and compile
code. I remote into servers and automate a lot of my daily tasks. I
take, organize, search and refine notes.
I prefer terminal-based tools because I can use them on any of my
machines remotely without much hassle. I also find that I'm just more
efficient with text-input and keyboard driven interactions, especially
if modal editing is available.
Fortunately for me we're the golden age of terminal-based tools, IMHO.
Anyway, with all that said, I present the long list of tools I use daily and why I like them:
** Arch Linux
I won't go into too much detail here. Rolling release, Arch Wiki and
AUR. I run Arch with ZFS on 4 machines. My desktop install is over 10
years old at this point. I rarely encounter any sort of bleeding edge
breakage that people seem to think is common with Arch.
** KDE Plasma
It has just enough shortcuts for window management that I can do 90%
of what I would do with tiling WMs like i3 and Sway and I prefer not
having to spend time configuring one of the various i3bar-etc
applications to get the system tray and all that working right.
KDE just works, it looks nice and lets me configure it as much as I want.
I do find myself WM/DE-curious and I try other things from time to
time. [[https://github.com/paperwm/PaperWM][PaperWM]] is on my short list to try out.
** Emacs
Won't elaborate here much either - I like Emacs mostly because of Evil
mode and Org mode but there is a long-tail of other packages and
configurability that also make me prefer it over Vim or Neovim.
I still use Neovim for quick one-off edits from time to time but 90%
of my day is spent in Emacs.
I think newer editors like Helix are interesting, but without full Org
mode support I couldn't imagine switching.
** Nushell
[[https://www.nushell.sh/][Nushell]] is a modern shell written in Rust with first class support for
structured data, syntax and semantics that draw a nice balance between
reasonable programming language and an ergonomic shell UX.
I find myself being willing to automate more complex tasks using
Nushell than I would with Z shell or Bash.
** VisiData
[[https://www.visidata.org/][VisiData]] is a nice terminal spreadsheet and CSV tool. I don't have to
_create_ a lot of spreadsheets but I do have to find data in them and
CSVs often enough, for work, that having something that fits my normal
workflow here is really nice.
While I don't think VisiData will write to XLSX files, it will let you
edit sheets and save them as CSV or TSV.
This tool was the initial motivation for writing this post.
** Konsole
Nothing super interesting to say about it. It just works.
All the modern terminals are nice, I like WezTerm, Alacritty and
Kitty. I switch between them for one reason or another at different
times, but I currently have Konsole bound to my terminal shortcut in
KDE so it's my terminal of choice.
** TODO Unison
** Firefox
There's not a whole lot, I think, that differentiates the browsers from one another these days
** Chrome Emacs
[[https://addons.mozilla.org/en-US/firefox/addon/chrome-emacs/?utm_source=addons.mozilla.org&utm_medium=referral&utm_content=search][Chrome Emacs]] is both a Chrome and Firefox extension that lets you
pretty seamlessly edit text areas in your browser from within
Emacs. Since I find myself [[#/job-description/][writing a lot at work]] these days, it's nice
to be able to edit story details in [[https://www.shorcut.com][Shortcut]] from Emacs with nice
modal editing.
** TODO Syncthing
* TODO Just what is it you do here?
:PROPERTIES:
:ONE: wfot-default
:DATE: 2024-10-08
:CUSTOM_ID: /job-description/
:END:
I've never liked working at [[#/large-companies/][large companies]]. Mostly because I think
they complicate things, but some things are more complicated at small
companies.
Specifically, leadership roles tend to be fluid in their definition
where they grow organically over time. Also, as I'm learning, old
responsibilities don't tend to get pruned on their own. So you have to
be diligent about shaping your role over time or you'll end up
over-burdened and unable to do a good job at any of your
responsibilities.
My formal job title is (I think) "Director of Engineering"
My reponsibilities include
**** Software design
I don't design all of the software here by any means, but I am either
doing the design myself or I'm involved in the design conversation of nearly
any non-trivial component.
We're starting to outgrow this but it's proving a little difficult for
both me to let go of being involved in everything and for others to
let me be less involved.
**** Keeping production from breaking
At the end of the day, if the system is on fire then I have to make
sure it gets fixed.
Fortunately, our system is fairly resilient and we have a rotating
"support" role that everyone gets a turn at - so I am not personally
responding to every issue that comes up.
However, if we do have a big enough or hard enough problem then I need
to be able to provide support. And that usually means the situation is
urgent so I have to know the details of the system well enough to
resolve issues quickly.
Truly, though, this responsibility is a long-view one - I need to
ensure that the software we're building is not falling over on
itself. Thankfully, our tech stack is faily reliable compared
to many others I've used in companies prior.
**** Business goal prioritization
Providing technical input to and vetting of business goals.
This is basically a combination of saying
- "X will take (days|weeks|months|years)"
- "Y will be (easier|better|less risky) if we do it after X"
- "We can get 90% of the benefits of A if we do B - at half the cost - instead"
- etc...
**** Writing stories and technical plans
Many (but not all) of our "tickets" "cards" "stories" what-have-you
end up getting written directly by me. This is, in some sense, an easy
job to delegate out but it's risky to do so because getting this part
wrong can lead to a lot of re-work amongst other costs.
**** Iteration planning
Deciding what the team will actually do in a given week.
**** Ensuring timelines get met
While we don't have a lot of "hard dates" on deliverables compared to
[[#/large-companies/][large companies]], we have them sometimes and it's my responsibility to
either ensure we hit them or to understand why we didn't so we can
better [[#/managing-expectations][manage expectations]] in the future.
**** Discuss story details, expectations, changes, etc
[[https://en.wikipedia.org/wiki/User_story][A user story is a promise for a conversation]]. Very often I am the one
keeping that promise and this puts me in the middle of a lot of
conversations.
**** Adjudicate technical disagreements
Fortunately this doesn't happen all that often - and usually when it
does it's around more trivial things (bikeshedding affects us all),
but it does happen.
**** Maintaining technical quality
When we have code quality issues I feel personally responsible.
It's my job to either prevent them in the first place, or plan an
execute work to alleviate quality issues.
Balancing that work with business deliverables is a skill unto itself.
**** Primary interrupt
This responsiblity is a hold-over from my tech lead days and it's one
I need to get rid of.
Being the primary interrupt means, to me, protecting the team from
interruptions and allowing them to focus on executing the current plan
of work (i.e. the stories in the iteration, our current deliverable or
goal otherwise).
In practice this means it's hard for me to focus.
We've made some changes to the support structure this past year that
have helped with this immensely.
However, at the same time we've grown the development team so now my
other responsibility of feeding the machine (i.e. writing stories) has
chipped away at some of the focus gains I've made.
* Finally, Emacs
:PROPERTIES:
:ONE: wfot-default
:DATE: 2024-10-15
:CUSTOM_ID: /emacs/
:END:
I've been thinking a lot about [[#/tools-i-love][tooling]] lately. Being a programmer at heart my most important tool is, of course, my editor.
I have been promiscuous in my use of editors in the past, ranging from my [[https://www.ultraedit.com/][first love]] back from my heathen days on Windows to my forever home: [[https://www.gnu.org/software/emacs/][Emacs]].
I've been using Emacs for a few years at this point, but I don't think I really internalized that this is where I _want_ to stay until just recently.
While reading through the OrgMode FAQ one evening, as one does, I found myself looking at a link to a video about Emacs from 2008.
"2008!" I thought to myself. WTF was I doing in 2008 wasting my time with Resharper (or some such nonsense, probably).
If only I'd been mastering Org Mode to, well, organize my life! How far would I in my Emacs journey by now?
I refuse to spend much time on that actual line of thinking, tempting as it is to self-flagellate, I do think it indicates a shift in perspective.
I'm in this for the long haul. I'm reminded of advice from [[http://davidvollbracht.com/][smartest person I know]] about editors: (paraphrased, because I wasn't diligent about note taking when he said it!) "Your text editor is a use-it-for-life tool. You should pick one worthy of such an investment in your time."
In his view there were only two such editors at the time: Vim and Emacs.
He was already well on his way down the Vim road and I spent several years following along.
As a result, I found ~Jesus~ modal editing and I [[#/vi-everywhere][haven't looked back]].
Anyway, none of this is meant to be rhetorical in any way...more just a description of my own journey.
I won't elaborate on my specific usage of Emacs and Org Mode but suffice to say I'm getting a lot of benefit out of both tools lately and I only see more value in them down the road.
I know this kind of post is essentially a death knell for a personal blog. I'll see you again in 2028, I guess.
* TODO Story writing
:PROPERTIES:
:ONE: wfot-default
:DATE: 2024-11-05
:CUSTOM_ID: story-writing
:END:
* TODO Election 2024
:PROPERTIES:
:ONE: wfot-default
:DATE: 2024-11-05
:CUSTOM_ID: election-2024
:END:

221
onerc.el Normal file
View File

@@ -0,0 +1,221 @@
(setq wfot-styles
'((mvp . "https://unpkg.com/mvp.css")
(bahunya . "https://cdn.jsdelivr.net/gh/kimeiga/bahunya/dist/bahunya.min.css")
(awsm . "https://unpkg.com/awsm.css/dist/awsm.min.css")
(holiday . "https://cdn.jsdelivr.net/npm/holiday.css@0.11.2")
(tacit . "https://cdn.jsdelivr.net/gh/yegor256/tacit@gh-pages/tacit-css-1.8.1.min.css")
(writ . "//writ.cmcenroe.me/1.0.4/writ.min.css")
(simple . "https://cdn.simplecss.org/simple.min.css")
(pico-amber . "https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.amber.min.css")
(pico-fluid . "https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.fluid.classless.min.css")
(pico . "https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css")))
(setq primary-css (alist-get 'pico-amber wfot-styles))
(defun wfot-default (page-tree pages _global)
"willfullyobtuse default render function
See `one-is-page', `one-render-pages' and `one-default-css'."
(let* ((title (org-element-property :raw-value page-tree))
(path (org-element-property :CUSTOM_ID page-tree))
(content (org-export-data-with-backend
(org-element-contents page-tree)
'one-ox nil))
(website-name (one-default-website-name pages))
(nav (one-default-nav path pages)))
(jack-html
"<!DOCTYPE html>"
`(:html
(:head
(:meta (@ :name "viewport" :content "width=device-width,initial-scale=1"))
(:link (@ :rel "stylesheet" :href primary-css))
(:link (@ :rel "stylesheet" :href "wfot.css"))
(:title ,title))
(:body
(:header (:a (@ :href "/") ,website-name))
(:main
(:section (:h1.title
,(if (not (string= path "/"))
`(:div.title (:h1 ,title))
'(:div.title-empty))))
(:section (:article ,content))))))))
(defun wfot-default-home-list-pages (page-tree pages _global)
"Default render function to use in the home page that lists pages.
See `one-is-page' for the meaning of PAGE-TREE and PAGES.
Also see `one-render-pages' and `one-default-css'."
(let* ((title (org-element-property :raw-value page-tree))
(content (org-export-data-with-backend
(org-element-contents page-tree)
'one-ox nil))
(website-name (one-default-website-name pages))
;; All pages but the home pages
(pages-list (wfot-default-pages pages "/.+")))
(jack-html
"<!DOCTYPE html>"
`(:html
(:head
(:meta (@ :name "viewport" :content "width=device-width,initial-scale=1"))
(:link (@ :rel "stylesheet" :href primary-css))
(:link (@ :rel "stylesheet" :href "wfot.css"))
(:title ,title))
(:body
(:header (:a (@ :href "/") ,website-name))
(:main
(:div/home-list-pages ,content)
(:div/pages (:ul ,(reverse pages-list)))))))))
(defun wfot-default-pages (pages &optional filter)
"Return `jack-html' list of PAGES component.
If FILTER is non-nil, a page is listed only when its path (value
of `:one-path' property) matches FILTER regexp.
Evaluating the following form
(wfot-default-pages
\\='((:one-title \"HOME\" :one-path \"/\")
(:one-title \"FOO-1\" :one-path \"/foo-1/\")
(:one-title \"FOO-2\" :one-path \"/foo-2/\")))
returns:
(:ul
(:li (:a (@ :href \"/\") \"HOME\"))
(:li (:a (@ :href \"/foo-1/\") \"FOO-1\"))
(:li (:a (@ :href \"/foo-2/\") \"FOO-2\")))
And evaluating the following form with the filter \"/.+\"
(wfot-default-pages
\\='((:one-title \"HOME\" :one-path \"/\")
(:one-title \"FOO-1\" :one-path \"/foo-1/\")
(:one-title \"FOO-2\" :one-path \"/foo-2/\"))
\"/.+\")
returns a list which doesn't include the home page:
(:ul
(:li (:a (@ :href \"/foo-1/\") \"FOO-1\"))
(:li (:a (@ :href \"/foo-2/\") \"FOO-2\")))"
(when-let ((li-items
(delq nil
(mapcar
(lambda (page)
(let ((href (plist-get page :one-path))
(title (plist-get page :one-title))
(date (org-element-property :DATE (plist-get page :one-page-tree))))
(when (string-match-p (or filter ".*") href)
`(:li (@ :class "post-item") (:div (@ :class "post-date") ,date) (:a (@ :href ,href) (:span ,title)) ))))
pages))))
`(:ul ,@li-items)))
(defun wfot-default-home (page-tree pages _global)
"Default render function to use in the home page.
See `one-is-page' for the meaning of PAGE-TREE and PAGES.
Also see `one-render-pages' and `one-default-css'."
(let* ((title (org-element-property :raw-value page-tree))
(content (org-export-data-with-backend
(org-element-contents page-tree)
'one-ox nil))
(website-name (one-default-website-name pages)))
(jack-html
"<!DOCTYPE html>"
`(:html
(:head
(:meta (@ :name "viewport" :content "width=device-width,initial-scale=1"))
(:link (@ :rel "stylesheet" :href primary-css))
(:link (@ :rel "stylesheet" :href "wfot.css"))
(:title ,title))
(:body
(:header ,website-name)
(:main
(:div/home ,content)))))))
(defun wfot-default-with-sidebar (page-tree pages global)
"Default render function with a sidebar listing PAGES.
See `one-is-page' for the meaning of PAGE-TREE and GLOBAL.
Also see `one-default-sidebar', `one-render-pages' and `one-default-css'."
(wfot-default-sidebar page-tree pages global))
(defun wfot-default-sidebar (page-tree pages _global &optional with-toc)
"Return a HTML string with PAGES listed in a sidebar.
The arguments PAGE-TREE, PAGES and _GLOBAL are the same as
render functions take (See `one-is-page').
When WITH-TOC is non-nil, add the table of content of PAGE-TREE
in the HTML string.
This function is meant to be used by `one-default-with-sidebar'
and `one-default-doc' render functions.
See `one-render-pages', `one-default-css' and `wfot-default-pages'."
(let* ((title (org-element-property :raw-value page-tree))
(path (org-element-property :CUSTOM_ID page-tree))
(content (org-export-data-with-backend
(org-element-contents page-tree)
'one-ox nil))
(website-name (one-default-website-name pages))
(pages-list (wfot-default-pages pages))
(headlines (cdr (one-default-list-headlines page-tree)))
(toc (one-default-toc-component headlines))
(nav (one-default-nav path pages)))
(jack-html
"<!DOCTYPE html>"
`(:html
(:head
(:meta (@ :name "viewport" :content "width=device-width,initial-scale=1"))
(:link (@ :rel "stylesheet" :href primary-css))
(:link (@ :rel "stylesheet" :href "wfot.css"))
(:title ,title))
(:body
;; sidebar-left and sidebar-main are for small devices
(:div/sidebar-left (@ :onclick "followSidebarLink()")
(:div (:div "Pages"))
,pages-list)
(:div/sidebar-main)
(:div/sidebar-header
(:svg/hamburger (@ :viewBox "0 0 24 24" :onclick "sidebarShow()")
(:path (@ :d "M21,6H3V5h18V6z M21,11H3v1h18V11z M21,17H3v1h18V17z")))
(:a (@ :href "/") ,website-name))
(:div/sidebar-content
(:div/sidebar ,pages-list)
(:article
,(if (not (string= path "/"))
`(:div.title (:h1 ,title))
'(:div.title-empty))
,(when with-toc toc)
,content
,nav)))
(:script "
function sidebarShow() {
if (window.innerWidth < 481)
document.getElementById('sidebar-left').style.width = '75vw';
else {
document.getElementById('sidebar-left').style.width = 'min(300px, 34vw)';
}
document.getElementById('sidebar-main').setAttribute('onclick', 'sidebarHide()');
document.getElementById('sidebar-main').style.display = 'block';
}
function sidebarHide() {
document.getElementById('sidebar-left').style.width = '0';
document.getElementById('sidebar-main').style.display = 'none';
}
")))))
(defun wfot-default-doc (page-tree pages global)
"Default render function with a sidebar listing PAGES and the table of content.
See `one-is-page' for the meaning of PAGE-TREE and GLOBAL.
Also see `one-default-sidebar', `one-render-pages' and `one-default-css'."
(wfot-default-sidebar page-tree pages global 'with-toc))

4
scripts/build-and-deploy Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
emacs --eval "(org-publish 'blog' t)"
rsync --delete-after -avzr public/ verin.brechtel:/nas/www/willfullyobtuse.com

22
setup.org Normal file
View File

@@ -0,0 +1,22 @@
:PROPERTIES:
#+author: James Brechtel
#+email: me@jamesbrechtel.com
#+bind: org-export-publishing-directory "./public"
#+html_head_writ: <link rel="stylesheet" href="//writ.cmcenroe.me/1.0.4/writ.min.css" type="text/css">
#+html_head_bahunya: <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kimeiga/bahunya/dist/bahunya.min.css">
#+html_head_awsm: <link rel="stylesheet" href="https://unpkg.com/awsm.css/dist/awsm.min.css" type="text/css">
#+html_head_simple: <link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
#+html_head_holiday: <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/holiday.css@0.11.2" />
#+html_head_mvp: <link rel="stylesheet" href="https://unpkg.com/mvp.css">
#+html_head: <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css" />
#+html_head_pico_amber: <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.amber.min.css">
#+html_head_tacit: <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/yegor256/tacit@gh-pages/tacit-css-1.8.1.min.css"/>
#+html_container: main
#+html_content_class: container
#+options: html-link-use-abs-url:nil html-postamble:nil
#+options: html-preamble:t html-scripts:nil html-style:nil
#+options: html5-fancy:t tex:t
#+options: author:t broken-links:mark c:nil creator:nil f:t tasks:t toc:nil todo:nil
#+OPTIONS: num:nil
#+language: en
:END:

20
simple-css.org Normal file
View File

@@ -0,0 +1,20 @@
:PROPERTIES:
#+SETUPFILE: setup.org
#+subtitle:
:END:
** Very simple CSS frameworks
*** Minimal CSS / fancy resets
I really like simple drop-in CSS resets like the one I use for this site.
At the time of writing, I'm using [[https://picocss.com/][Pico]] but I also considered [[https://yegor256.github.io/tacit/][tacit]]
The idea is that they provide nice default styling of HTML elements out of the box without the need to reference any specific classes.
The idea works well for sites that are much more content than layout - like this one.
Using tacit is a matter of incluing this link tag in the page's HEAD element:
#+BEGIN_SRC html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
#+END_SRC

35
vi-everywhere.org Normal file
View File

@@ -0,0 +1,35 @@
:PROPERTIES:
#+SETUPFILE: setup.org
#+keywords: vim
#+subtitle:
:END:
** vi modal editing in most places
For my sake, I prefer to have Vim bindings in as many places as
possible.
Most shells can be configured to use Vim bindings by putting =set -o vi=
somewhere in your shell startup script.
If you're using ZSH then you'll probably want an additional binding to
restore CTRL-R reverse history search.
=bindkey '^R' history-incremental-search-backward=
For CLI tools that use the =readline= library then you can configure its
input mode using a =.inputrc= file in your =$HOME= directory.
This affects REPLs like =ghci= and tools like =psql=.
#+begin_src txt
set editing-mode vi
$if mode=vi
set keymap vi-command
# these are for vi-command mode
Control-l: clear-screen
set keymap vi-insert
# these are for vi-insert mode
Control-l: clear-screen
$endif
#+end_src