Clojure Sublimed is a Clojure development environment for Sublime Text.
+
+- date: 2021-11-30 00:00:00 +0000
+ title: 'EQL Logo'
+ id: eql
+ url: /design/#eql
+ src: 2021-11-eql.svg
+ desc: 'EQL is a declarative query language to make hierarchical (and possibly nested) selections of information.'
+ content: |-
+
+
+
EQL is a declarative query language to make hierarchical (and possibly nested) selections of information.
+
+- date: 2021-11-24 00:00:00 +0000
+ title: 'clj-commons Logo'
+ id: clj-commons
+ url: /design/#clj-commons
+ src: 2021-11-clj-commons.svg
+ desc: 'clj-commons is a community-led project to build up the supporting infrastructure around Clojure. Idea: Erik Assum.'
+ content: |-
+
+
+
clj-commons is a community-led project to build up the supporting infrastructure around Clojure. Idea: Erik Assum.
+
+
- date: 2021-06-30 00:00:00 +0000
title: 'JWM Logo'
id: jwm
diff --git a/_data/talks.yaml b/_data/talks.yaml
index 92223134..d883fa1e 100644
--- a/_data/talks.yaml
+++ b/_data/talks.yaml
@@ -1,16 +1,67 @@
+- lang: RU
+ date: 2022-12-23 00:00:00 +0000
+ url: https://soundcloud.com/javaswag/e40
+ title: 'Java Swag'
+ place: Java Swag
+ content: 'Разочарование в Java, простота Clojure и опенсорс проекты'
+ cover: 'Java Swag.png'
+ embed: ''
+
+- lang: RU
+ date: 2022-12-19 00:00:00 +0000
+ url: https://www.youtube.com/watch?v=6B92JkMWajw
+ title: 'Как мы попали в IT'
+ place: Разрабы
+ content: 'Как влиять на технологии и делать значимый опенсорс'
+ embed: ''
+
+- lang: EN
+ date: 2022-10-29 00:00:00 +0000
+ url: https://www.youtube.com/watch?v=cM5e_tJqbT8
+ title: 'Clojure + UI = ❤️'
+ place: Dutch Clojure Days, Amsterdam
+ content: 'Introducing Humble UI, a desktop UI framework for Clojure'
+ embed: ''
+
+- lang: EN
+ date: 2022-04-29 00:00:00 +0000
+ url: https://www.youtube.com/watch?v=KC0HUOlsXzk
+ title: 'Your frontend needs a database'
+ place: Have you tried rubbing a database on it
+ content: 'Communication between frontend and backend should be handled by database'
+ embed: ''
+
+- lang: EN
+ date: 2022-03-30 00:00:00 +0000
+ url: https://soundcloud.com/clojurestream/sublimed-with-nikita-prokopov
+ title: 'Clojure Sublimed'
+ place: ClojureStream
+ content: 'Sublime Text setup for Clojure development'
+ cover: ClojureStream.png
+ embed: ''
+
+- lang: RU
+ date: 2021-11-17 00:00:00 +0000
+ url: https://www.youtube.com/watch?v=1qDyWccmcuY
+ title: 'Мы обречены'
+ place: 'Мы обречены'
+ content: 'Разочарование в софте, разочарование в Дюне Вильнева'
+ embed: ''
+
- lang: RU
date: 2021-04-10 00:00:00 +0000
url: https://devzen.ru/episode-0331/
title: 'Подкаст DevZen, эпизод 331'
content: 'Разработка IDE, какой она должна быть, какие они были за последние 50 лет, отзывчивость UI, тренды и идеи.'
- embed:
+ embed: ''
- lang: RU
date: 2021-04-07 00:00:00 +0000
url: https://soundcloud.com/vsapronov/my-obrecheny-open-source-govorim-s-nikitoi-prokopovym
title: 'Мы обречены — Open Source'
content: 'Говорим про open source c Никитой Прокоповым.'
- embed:
+ embed: ''
+
- lang: EN
date: 2020-12-23 00:00:00 +0000
@@ -24,28 +75,25 @@
url: https://changelog.com/podcast/401
title: 'The Changelog episode 401'
content: 'The intersection of coding and fonts: coding fonts! Talking with The Changelog about Fira Code.'
- embed: ''
- extras:
- - https://s.tonsky.me/tonsky.me/2020-07%20The%20Changelog%20401.mp3
- https://cdn.changelog.com/uploads/podcast/401/the-changelog-401.mp3
-
+ embed: ''
+
- lang: EN
date: 2019-11-08 00:00:00 +0000
- url: https://soundcloud.com/user-959992602/s4-e4-rum-with-nikita-prokopov
+ url: https://soundcloud.com/clojurestream/s4-e4-rum-with-nikita-prokopov
title: 'ClojureScript podcast, s04e04'
content: 'Rum With Nikita Prokopov.'
- embed: ''
+ embed: ''
- lang: RU
date: 2019-08-09 00:00:00 +0000
url: https://devzen.ru/episode-0245/
title: 'Подкаст DevZen, эпизод 245'
content: 'Обсуждаем физические принципы клавиатур, виды раскладок, как клавиатура приводит к RSI и как этого избежать, как правильно собрать свою клавиатуру.'
- embed: ''
+ embed: ''
- lang: RU
date: 2019-05-20 00:00:00 +0000
url: https://t.me/KgOfHedgehogs/509
title: 'Подкаст Высший Клик, эпизод 2'
content: 'Обсуждаем эргономику клавиатур и отвечаем на вопросы из комментариев.'
- embed: ''
\ No newline at end of file
+ embed: ''
\ No newline at end of file
diff --git a/_layouts/default.html b/_layouts/default.html
index 56f39a46..2556a7f3 100644
--- a/_layouts/default.html
+++ b/_layouts/default.html
@@ -49,6 +49,8 @@
+
+
\ No newline at end of file
+
diff --git a/_layouts/post.html b/_layouts/post.html
index b8d47d4a..4737190c 100644
--- a/_layouts/post.html
+++ b/_layouts/post.html
@@ -13,7 +13,7 @@
-
+
{{ page.title }}
{{ page.permalink }}
@@ -64,4 +64,4 @@
{{ page.title }}
{% endif %}
-
+
diff --git a/_posts/2018-01-22-cursor-keys.md b/_posts/2018-01-22-cursor-keys.md
index 3484c39e..dd766826 100644
--- a/_posts/2018-01-22-cursor-keys.md
+++ b/_posts/2018-01-22-cursor-keys.md
@@ -9,11 +9,11 @@ starred: true
*TL;DR Remap `CapsLock` + `IJKL` to act as cursor keys and teach yourself to use it*
-Have you ever wondered if default keyboard layout is optimal for today’s tasks? Modern keyboards inherit their design all the way back from early typewriters. Typewriters were optimized for, well, typing. That makes modern keyboards best optimized for typing too. With your hands in the _default position_ every letter and punctuation are easy to access.
+Have you ever wondered if the default keyboard layout is optimal for today’s tasks? Modern keyboards inherit their design all the way back from early typewriters. Typewriters were optimized for, well, typing. That makes modern keyboards best optimized for typing too. With your hands in the _default position_ every letter and punctuation are easy to access.
-But on modern computers you also need to move the cursor around. Typewriter didn’t have a cursor, so there were no buttons for it nor was there a good place saved for them. So, on computers cursor keys are put far away from the center.
+But on modern computers you also need to move the cursor around. The typewriter didn’t have a cursor, so there were no buttons for it nor was there a good place saved for them. So, on computers cursor keys are put far away from the center.
@@ -37,7 +37,7 @@ This is what I did: I scripted `CapsLock+IJKL` to act as cursor keys system-wide
-Keyboard geeks know that CapsLock key is special: an absolutely useless key sitting on a home row, almost at the center, super easy to reach. How lucky are we to have it? And `IJKL` form nice “reverse T” shape that is natural to use.
+Keyboard geeks know that the CapsLock key is special: an absolutely useless key sitting on a home row, almost at the center, super easy to reach. How lucky are we to have it? And `IJKL` form a nice “reverse T” shape that is natural to use.
## How-to for macOS
@@ -127,4 +127,4 @@ If you’re curious, learning new keyboard or an alternative layout takes about
## What’s next?
-You now have a superpower. Go write something awesome!
\ No newline at end of file
+You now have a superpower. Go write something awesome!
diff --git a/_posts/2018-09-17-disenchantment.md b/_posts/2018-09-17-disenchantment.md
index 3be55665..fd0839ef 100644
--- a/_posts/2018-09-17-disenchantment.md
+++ b/_posts/2018-09-17-disenchantment.md
@@ -15,7 +15,7 @@ starred: true
-_Translations: [Chinese](zh/) [French](fr/) [Italian](it/) [Korean](ko/) [Portuguese](pt/) [Russian](ru/) [Spanish](es/)_
+_Translations: [Chinese](zh/) [French](fr/) [Hungarian](hu/) [Italian](it/) [Korean](ko/) [Portuguese](pt/) [Russian](ru/) [Spanish](es/)_
I’ve been programming for 15 years now. Recently, our industry’s lack of care for efficiency, simplicity, and excellence started really getting to me, to the point of me getting depressed by my own career and IT in general.
diff --git a/_posts/2018-12-29-slow-wrong.md b/_posts/2018-12-29-slow-wrong.md
index be97ab93..b2cfbeee 100644
--- a/_posts/2018-12-29-slow-wrong.md
+++ b/_posts/2018-12-29-slow-wrong.md
@@ -15,9 +15,9 @@ I’ve been doing some [Advent of Code](https://adventofcode.com/2018) programmi
One thing it taught me is that there are two types of solutions you end up with: ones that can calculate the answer in a couple of milliseconds and the ones that would take years to finish. If you end up with the second type, you’re doing it wrong. There’s no point in waiting, although _technically_ it might do the right thing too.
-Another interesting observation is that it makes no difference what hardware you use to run it. If the solution is fast, it’ll be fast on a notebook as well as on a beefed-up workstation-class desktop. Sure, it might be twice or thrice slower, but it’ll be a difference between 10ms and 30ms. You still get your answer, so it doesn’t really matter.
+Another interesting observation is that it makes no difference what hardware you use to run it. If the solution is fast, it’ll be fast on a laptop as well as on a beefed-up workstation-class desktop. Sure, it might be twice or thrice slower, but it’ll be a difference between 10ms and 30ms. You still get your answer, so it doesn’t really matter.
-If it’s slow, on the other hand, then you can throw at it any amount of computing power and it still won’t be enough. It might reduce the time from three years on a notebook to one year on a most powerful computer I can possibly build. So what? It’s still too long.
+If it’s slow, on the other hand, then you can throw at it any amount of computing power and it still won’t be enough. It might reduce the time from three years on a laptop to one year on a most powerful computer I can possibly build. So what? It’s still too long.
Now let’s get to software. It’s easy to call solutions of Advent Of Code wrong when they are slow since we know the fast solution has to exist. With real-world problems, nobody guarantees that.
@@ -74,4 +74,4 @@ There’re just limits on how far this wrong solution can go. Electron text edit
The whole solution, the “web stack”, _is wrong_. The same thing could be done faster and more efficient _easily_—there is just so much wasted potential. Time to admit that and start over. There are fast text editors and chat programs around, very well within capabilities of even the least powerful netbooks.
-I can go on and on. The thing to remember is: think what are you getting out of it. Are the problem and the resources wasted on it even comparable? It’s easy to find excuses why things are the way they are. They are all probably valid, but they are _excuses_. We know way faster programs _are possible_, and that makes everything else just plain wrong.
\ No newline at end of file
+I can go on and on. The thing to remember is: think what are you getting out of it. Are the problem and the resources wasted on it even comparable? It’s easy to find excuses why things are the way they are. They are all probably valid, but they are _excuses_. We know way faster programs _are possible_, and that makes everything else just plain wrong.
diff --git a/_posts/2019-02-28-github-redesign.md b/_posts/2019-02-28-github-redesign.md
index 1ec452e3..18bfa5cb 100644
--- a/_posts/2019-02-28-github-redesign.md
+++ b/_posts/2019-02-28-github-redesign.md
@@ -18,7 +18,7 @@ starred: true
- Illustration by Yulia Prokopova
+ Illustration by Yulia Prokopova
Github design is pretty good: it gets the job done, it’s clean, has consistent visual language, its design is calm and suitable for everyday use.
diff --git a/_posts/2019-03-11-hiring.md b/_posts/2019-03-11-hiring.md
index 9fbed328..72e1dac2 100644
--- a/_posts/2019-03-11-hiring.md
+++ b/_posts/2019-03-11-hiring.md
@@ -12,7 +12,7 @@ starred: true
- Illustrations by Yulia Prokopova
+ Illustrations by Yulia Prokopova
_Translations: [Korean](https://muchtrans.com/translations/how-not-to-hire-a-software-engineer.ko.html) [Russian](https://habr.com/ru/company/itelma/blog/551152/)_
diff --git a/_posts/2020-06-15-syncthing.md b/_posts/2020-06-15-syncthing.md
index 26d4ae9e..956d6000 100644
--- a/_posts/2020-06-15-syncthing.md
+++ b/_posts/2020-06-15-syncthing.md
@@ -5,11 +5,12 @@ category: blog
summary: "File synchronization can be fun and painless if you don’t have to deal with corporate bullshit."
starred: true
hackernews_id: 23537243
+hackernews_id_2: 29837696
---
- Illustration by Yulia Prokopova
+ Illustration by Yulia Prokopova
_Translations: [Russian](https://habr.com/ru/company/itelma/blog/550404/)_
diff --git a/_posts/2020-06-17-monitors.md b/_posts/2020-06-17-monitors.md
index b189e2c9..deee4114 100644
--- a/_posts/2020-06-17-monitors.md
+++ b/_posts/2020-06-17-monitors.md
@@ -15,7 +15,7 @@ hackernews_id: 23551983
- Illustration by Yulia Prokopova
+ Illustration by Yulia Prokopova
_Translations: [Chinese](https://sspai.com/post/61252) [Russian](https://habr.com/ru/post/508824/)_
@@ -168,7 +168,7 @@ I’m not sure what the default value for it is these days, but make sure it’s
UPD: Based on the feedback, seems that default value for it is ON. Be sure to switch it OFF!
-That preference name is misleading. It used to be called “LCD font smoothing”, which suggested subpixel antialiasing. But Apple removed subpixel antialiasing from macOS in 2018, the same month it retired its last non-retina notebook.
+That preference name is misleading. It used to be called “LCD font smoothing”, which suggested subpixel antialiasing. But Apple removed subpixel antialiasing from macOS in 2018, the same month it retired its last non-retina laptop.
The other thing the name suggests is that your fonts might not be smoothed at all. This is not the case either.
@@ -266,7 +266,7 @@ Luckily for us, this is easy to fix (at least for now). Go to System Preferences
-This will make everything on the screen slightly bigger, leaving you (slightly!) less screen estate. This is expected. My opinion is, a notebook is a constrained environment by definition. Extra 15% won’t magically turn it into a huge comfortable desktop. But at least you can enjoy that gorgeous screen and pixel-crisp fonts. Otherwise, why would you buy a retina screen at all?
+This will make everything on the screen slightly bigger, leaving you (slightly!) less screen estate. This is expected. My opinion is, a laptop is a constrained environment by definition. Extra 15% won’t magically turn it into a huge comfortable desktop. But at least you can enjoy that gorgeous screen and pixel-crisp fonts. Otherwise, why would you buy a retina screen at all?
## ClearType on Windows
@@ -302,7 +302,7 @@ But ClearType-d text on Windows still looks good, even on 4k display. It’s jus
# Get a monitor
-Let me express an opinion. This is my blog, after all. I think that notebooks are not good for development. They are great in mobility and convenience, and this argument might outweigh everything else for some people. I accept that. But still, a desktop monitor + external keyboard are _always_ better than a notebook. There might be other reasons for not buying a monitor, but having one, I hope, no one would argue it’s a superior dev environment.
+Let me express an opinion. This is my blog, after all. I think that laptops are not good for development. They are great in mobility and convenience, and this argument might outweigh everything else for some people. I accept that. But still, a desktop monitor + external keyboard are _always_ better than a laptop. There might be other reasons for not buying a monitor, but having one, I hope, no one would argue it’s a superior dev environment.
With this out of the way, the question arises, which monitor should you get? From what we already discussed, two things should be clear:
@@ -321,7 +321,7 @@ It’s also possible to run a 4k display at native 3840×2160 pixels. It depends
This is what I think. PPI defines the physical size of the pixel (220 PPI means there are 220 pixels per inch, or 1 pixel is 1/220 inch wide). So Apple ensures that pixels on all its devices have the same size. Does it mean macOS controls have the same physical size? Not anymore, after Apple started applying non-integer scaling by default on Macbooks.
-Then, it’s nearly impossible to ensure that _perceived_ size, or how big the user _sees_ the control, is the same because the distance to the display is different. For example, on average my eye-to-screen distance is 33 cm with the notebook, but 68 cm with the monitor. That’s 2× difference!
+Then, it’s nearly impossible to ensure that _perceived_ size, or how big the user _sees_ the control, is the same because the distance to the display is different. For example, on average my eye-to-screen distance is 33 cm with the laptop, but 68 cm with the monitor. That’s 2× difference!
@@ -542,7 +542,7 @@ To sum up, this is the best setup for programmers:
- Text can’t be made look good on low-resolution displays.
- High-PPI displays are now a commodity, it’s time to switch.
-- Notebooks are ok, but a standalone monitor is always better.
+- Laptops are ok, but a standalone monitor is always better.
- 4k monitor only makes sense with 2× / 200% scaling.
- If you want to go further, there are now affordable 4k @ 120 Hz options.
diff --git a/_posts/2020-11-14-skija.md b/_posts/2020-11-14-skija.md
index 10b305c7..3154776a 100644
--- a/_posts/2020-11-14-skija.md
+++ b/_posts/2020-11-14-skija.md
@@ -68,8 +68,8 @@ The road to high-quality UI on JVM is a long one. We’ll need:
Today I am happy to announce the first part of this epic quest: the graphics library. It’s called Skija and it is just a collection of bindings to a very powerful, well-known Skia library. The one that powers Chrome, Android, Flutter and Xamarin.
-
- GitHub.com/JetBrains/Skija
+
+ GitHub.com/HumbleUI/Skija
Like any other JVM library, it’s cross-platform. It runs on Windows, Linux and macOS. It’s as simple to use as adding a JAR file. Much simpler than massaging C++ compiler flags for days before you can even compile anything. Skija takes care of memory management for you. And the bindings are hand-crafted, so they always make sense and are a pleasure to use (as far as Skia API allows, at least).
@@ -77,8 +77,8 @@ Like any other JVM library, it’s cross-platform. It runs on Windows, Linux and
What you can do with it? Draw things, mostly. Lines. Triangles. Rectangles. But also: curves, paths, shapes, letters, shadows, gradients.
-
- Drawn with Skija
+
+ Drawn with Skija
Think of it as a Canvas API. But, like, really advanced. It understands color spaces, modern typography, text layout, GPU acceleration, stuff like that.
@@ -118,10 +118,12 @@ Compose performs poorly on your load? Pray for someone to write an alternative,
And, by all means, if you _want_ to roll your own window library or widget toolkit—please, do! That’s what we hope to happen.
+*UPD 11/2021: There’re now [JWM](https://github.com/HumbleUI/JWM) for OS integration and [HumbleUI](https://github.com/HumbleUI/HumbleUI) for UI framework.*
+
### In conclusion
Skija is a part of the bigger picture. Java UI progress was blocked by the poor-performing Graphics2D. It’s not anymore. What will grow out of it? Time will tell.
Please give Skija a try, and let us know what you think. Or, maybe, start using it—we’d be happy if you did! Here’s the link:
-[https://github.com/JetBrains/skija/](https://github.com/JetBrains/skija/)
\ No newline at end of file
+[https://github.com/HumbleUI/Skija/](https://github.com/HumbleUI/Skija/)
\ No newline at end of file
diff --git a/_posts/2021-02-24-monitors-mac.md b/_posts/2021-02-24-monitors-mac.md
index b4179fc7..7796d3bc 100644
--- a/_posts/2021-02-24-monitors-mac.md
+++ b/_posts/2021-02-24-monitors-mac.md
@@ -30,54 +30,190 @@ Send a PR! Read the contribution guidelines at the end.
This list is about supporting full 4k resolution (3840×2160) at 120Hz only! Green icon means this Mac can work with that display at 4k _and_ 120Hz. Red icon means it works, but in compromised settings (60 Hz, 1440p resolution).
-# The list
-
## Mac mini 2018
-
Acer XV273KPbmiipphzx
+
Acer Nitro XV3 (XV273K Pbmiipphzx)
+
ASUS ROG Swift PG32UQX
+
+## MacBook Air (Retina, 13-inch, 2018)
+
+
Acer Nitro XV3 (XV273K Pbmiipphzx)
-## MacBook Air (Retina, 13", 2018)
+## MacBook Pro (Retina, 13-inch, 2018)
-
Acer XV273KPbmiipphzx
+
LG 32GQ950 UltraGear
-## MacBook Pro (13 inch, 2019)
+
LG TV 65NANO867NA
-
Acer XV273KPbmiipphzx
+## MacBook Pro (13-inch, 2019)
+
+
Acer Nitro XV3 (XV273K Pbmiipphzx)
## MacBook Pro (15-inch, 2019) w/ Radeon Pro 560X
-
Acer XV273KPbmiipphzx
+
Acer Nitro XV3 (XV273K Pbmiipphzx)
+
+
ASUS ROG Strix XG27UQ
LG UltraGear 27GN950-B
-
LG UltraGear 34GN850-B (1440p @ 144Hz)
+
LG UltraGear 34GN850-B (1440p @ 144Hz)
-
ASUS XG27UQ
+## Mac Pro (2019) w/ Radeon Pro 580X
+
+
Acer Predator X27
+
+## MacBook Pro (15-inch, 2018) w/ Radeon Pro 560X
+
+
Gigabyte M28U
+
Gigabyte M32U (4k @ 120Hz)
## MacBook Pro (16-inch, 2019) w/ Radeon Pro 5300M
-
Acer NITRO V3 (XV273K Pbmiipphzx)
+
Acer Nitro XV3 (XV273K Pbmiipphzx)
+
+
LG UltraGear 27GP950
-## Macbook Pro (16-inch, 2019) w/ Radeon 5500M
+
LG C1 4K OLED TV (65-inch, 2021)
-
ASUS XG27UQ
+
Samsung G70A 28" 4K 144 Hz
+
+## MacBook Pro (16-inch, 2019) w/ Radeon Pro 5500M
+
+
+
# How to contribute?
If you have a combination of 4k monitor with 120+ Hz refresh rate and a Mac that’s not listed here, please [open a PR](https://github.com/tonsky/tonsky.github.io) (don’t worry about graphics/icons, I can add those myself).
diff --git a/_posts/2021-07-12-icfpc-2021.md b/_posts/2021-07-12-icfpc-2021.md
new file mode 100644
index 00000000..32cb2ce7
--- /dev/null
+++ b/_posts/2021-07-12-icfpc-2021.md
@@ -0,0 +1,306 @@
+---
+layout: post
+title: "Zig, Skia, Clojure, Geometry and the Japanese TV Show: ICFP Contest 2021"
+category: blog
+summary: "4-day coding marathon"
+hackernews_id: 27829635
+---
+
+Every year I participate in ICFP Contest, or ICFPC for short (ICFP stands for International Conference on Functional Programming). This is a team coding challenge that lasts for 72 hours and in which you have to solve a series of very hard tasks by writing (functional) code.
+
+Tasks are usually too hard to find a perfect solution, the best you can hope for is a good approximation. The team that found the most solutions and approximated the best wins.
+
+This weekend was my third contest, and, as usual, it was great fun. Here’s my experience report.
+
+# Preparation
+
+None :)
+
+ICFPC is best played with friends in the same room. Second best is the team over Skype.
+
+Unfortunately, I completely forgot about the contest and didn’t assemble any team in advance. So I played solo this time.
+
+It also makes sense to prepare a little: have a project template set, rent some EC2 servers for hard number crunching, build a dashboard, build a mastermind service that distributes problems and collects solutions, assign the roles in the team, etc.
+
+
+
+ Some teams come prepared
+
+
+As you can imagine, I did nothing of that too :)
+
+# The Problem
+
+The problem this year was inspired by a Japanese show where people must fit in a hole in the wall, usually very creatively shaped.
+
+
+
+
+
+More precisely, you are given a hole (gray) and a figure (green):
+
+
+
+
+
+Your task is to transform the green figure so that it fits in a hole. The original figure is not ok: it doesn’t fit.
+
+If we move some vertices around, we can make it fit:
+
+
+
+
+
+Unfortunately, this won’t do either: you are not allowed to change the length of the segments.
+
+Moves, rotations, flips and overlaps are ok though. This is a valid solution:
+
+
+
+
+
+Of course, many different valid solutions are possible. To score most points, you must find a solution that is most spread out (for exact definition check [the spec](https://icfpcontest2021.github.io/specification.html)).
+
+Oh, and all the points have integer coordinates. But don’t get excited: most tasks have 10+ vertices and a grid in the range of 100×100. That gives you _at least_ (100×100)10 = 1040 permutations, rendering bruteforce waaaaay out of the question. But at least there’s a grid!
+
+# First quick score
+
+Ok, so what’s the approach here? After reading the problem on Friday at 2 pm, the best idea is to inspect some problems with your bare eyes. Kudos to the organizers who put PICTURES next to the problem descriptions! The source of truth is JSON, but pictures certainly help.
+
+After scrolling through few first problems, I found one that can be solved in a notepad! Problem 11 requires you to just rotate a triangle.
+
+
+
+
+
+Perfect! I typed in the solution into the Sublime Text directly and uploaded it via web form. The first score, just 22 minutes after the start!
+
+
+
+
+
+# Visualizer + manual solver
+
+After exploring a few more problems, it became obvious that many of them can be solved manually by just moving a few points. Together with [Twitch Chat](https://twitch.tv/nikitonsky) we decided to build a tool to assist with manual solving.
+
+My language of choice is Clojure (of course!), a language perfect for rapid prototyping.
+
+But what to use for UI? The obvious choice is browser, but I didn’t want to go to the browser: it’s slow, complicated, not reliable, ClojureScript is a pain in the ass. So I decided to aim for the native UI.
+
+Luckily for me, I have just built the thing for that! It’s called JWM, a [Java Window Management library](https://github.com/JetBrains/JWM), and Skija, a [Skia wrapper for JVM](https://github.com/JetBrains/Skija). So I went with those.
+
+
+
+
+
+An anecdote: JWM is still in the active prototyping phase, barely enough to get something useful out of it. It works, but don’t expect miracles. E.g., it doesn’t have `mousedown` event yet, but it does have `mousemove` and `keydown`. So in my UI you press space and then move your mouse :)
+
+Otherwise, all went great with the visualizer. Clojure + Skija are performant enough to hit 144 Hz with thousands of nodes, edges, circles, text labels, and grid redrawn every frame with no significant slowdown. Perks of not living in the browser!
+
+After that, I just went and solved as many tasks as I could. Here’s an example:
+
+
+
+
+
+Sorry, no dark theme this time.
+
+# First taste of automation
+
+After a few hours, it became obvious that some problems have the perfect solution: if a figure’s vertices occupy all of the hole’s vertices, that gives you a perfect score of zero (less is better).
+
+Since the amount of hole/figure vertices is relatively small, this became a perfect target for brute-forcing. Here, all the edges are places automatically (if there’re multiple possible placements, they are iterated over), and the rest is easy to figure out manually.
+
+
+
+
+
+Believe it or not, this approach allowed me to do as high as 14th place in the first 12 hours. Unfortunately, after that, other teams caught up.
+
+
+
+ The joy of first half-automated solution bearing fruit
+
+
+# The comforts of Clojure
+
+To figure out which problems have the perfect solution, I scrapped the organizers’ website where they posted the best scores for each problem.
+
+Surprisingly, it only took me ten minutes, including figuring out page structure, looking up http library docs, making a request, a few trials and errors, integrating and committing the final code.
+
+Here’s the [entirety of that code](https://github.com/tonsky/icfpc2021/blob/6b7d1228915de9c15da9cc831c5e4ebad8529282/src/icfpc/main.clj#L53-L62):
+
+
+
+That’s the perfect example of why Clojure is so good for rapid prototyping.
+
+# Going low-level
+
+Solving tasks manually did well in the beginning, but going forward, we need some automation.
+
+Participating in 2019 ICFPC taught me one thing: it’s hopeless to do number-crunching and brute-forcing in anything but C/C++/Rust. Clojure is too slow, Java is too slow, and speed here is the most important advantage. Faster solutions win the ladder.
+
+The irony: _functional_ programming contest is won year after year by good old imperative/OOP languages:
+
+
+
+Ok, so I need down-to-the-metal, low-level language. C was ruled out as too old, C++ was ruled out as no fun, Rust was ruled out based on my 2019 experience with it.
+
+Rust is great for reliable production systems, sure, but for quick-and-dirty prototyping, it’s too strict. Imagine figuring out a perfect algorithm and spending a few hours implementing it just to be told by the borrow checker it won’t let it pass.
+
+For something like a 3-day coding sprint I’m happy to have unallocated memory, data races, undefined behavior, and data corruption as long as I get to try out ideas quickly. And it’s hard to convince Rust to let it go.
+
+# Meet Zig
+
+Based on the logic above, and also my curiosity, I decided to give Zig a go. Did I have tons of experience in it? No. Do I like checking out hot new things and learning languages? Yes. So in the end, was it fun? Hell yes.
+
+What do I have to say about Zig?
+
+I like its approach: get rid of all the complexity that computers and programming languages have accumulated over the years and try to re-implement everything from scratch, raw, bare, but also simple and not constrained by the prior conventions and restrictions.
+
+It’s primarily developed by one person, so it’s not bloated (yet, hopefully, forever).
+
+It’s very low-level, C-like. I honestly almost forgot how it feels to carefully worry over every string you concatenate. It’s not bad, it’s exactly what the language aims for. Just took me by surprise.
+
+It improves over C significantly. Both in the sane defaults, language ergonomics, macros and build complexity.
+
+`defer` is pure genious. It lets you write cleanup code (free memory, close file, etc) right next to the resource initialization itself:
+
+```zig
+const path = try std.fmt.allocPrint(allocator, "problems/{}", .{ id });
+defer allocator.free(path);
+
+var file = try std.fs.cwd().openFile(path, .{});
+defer file.close();
+
+const stat = try file.stat();
+var contents = try allocator.alloc(u8, stat.size);
+defer allocator.free(contents);
+```
+
+This way it’s way harder to forget to clean up since the code is right next to initialization, not at the end of the scope. And if you need to return early, only already allocated resources will be cleaned. Genious!
+
+Error monad is built into the language too, which is also very convenient. Every function has two return paths: normal and error. There’s also special syntax to work with that (`try`, `errdefer`, Error Union Type).
+
+C interop must be very good, but I didn’t have a chance to check it out. Comptime (\~macros) should be good as well, but I haven’t got that far either.
+
+So overall I like Zig language very much and absolutely happy with my choice. Just beware: it’s still way less finished than, say, Rust is. But it already feels great and can be used for a lot of things already.
+
+
+
+ A quick home-made snack in the middle of the contest
+
+
+I only have one serious question for Andrew Kelley, the author of the language: why did you name a `for` loop a `while`?
+
+```
+var i: i64 = 0;
+while (i < 1000) : (i += 1) {
+}
+```
+
+# A quick geometry lesson
+
+Ok, so we are working with geometry. What do we need?
+
+First, we need a way to check if two line segments intersect. Turned out it’s pretty simple: line segment A and line segment B interset if and only if:
+
+1. Looking from A, B’s ends lie on different sides of A.
+2. Looking from B, A’s ends lie on different sides of B.
+
+It’s that simple.
+
+
+
+ Left: true/false, middle: false/true, right: true/true
+
+
+Quick lesson for the future participants: don’t hesitate to google code like this. Someone has already figured that out.
+
+Quick lesson #2: when copying it, try to not mess up variables name/order, otherwise you’ll be like me :)
+
+Ok, the second thing we need: how to figure out if a point is inside a polygon?
+
+Very simple, actually. Cast a ray from the point in any direction, count how many times the ray intersects with the polygon. If the number is odd, the point is inside, if it’s even, then it’s outside:
+
+
+
+ 0, 4 = even = outside, 1, 3 = odd = inside
+
+
+One gotcha, though. What if your ray hits a vertex exactly, intersecting two edges this way? Or what if the polygon’s edge aligns perfectly parallel with your ray?
+
+Unfortunately, we hit those problems really hard. After trying to patch it with special case handling, we gave up and just choose a point with large prime coordinates way outside of our polygon.
+
+We chose [(530711, 378353)](https://github.com/tonsky/icfpc2021/blob/4111b95764691bd19e52b1a8d371535aee02ba0d/src/bruteforce.zig#L192), two different large prime numbers. What it gives you is that line segment (0, 0) .. (530711, 378353) is guaranteed to not hit ANY integer coordinate in 0..530711 × 0..378353 area.
+
+P.S. Our implementation has a bug, though. The far end should’ve been `x + 530711`, not always `530711`. Still worked in practice—I guess we were lucky!
+
+Do I need to explain how to calculate the edge’s length? Hopefully not :)
+
+# Benefits of programming live
+
+I was not completely accurate when saying I was flying solo. After the start, [Dmitry](https://github.com/ethercrow) joined me through [Twitch Chat](https://twitch.tv/nikitonsky) and gave me a lot of useful advice. Enough, in fact, that I consider him a part of the team.
+
+Many other people visited, too, which kept the whole thing alive and entertaining. Thank you all!
+
+# Brute forcing all the problems
+
+With all this preparation in place, we started writing a brute force solution.
+
+The idea was quite stupid, really: place the first node in all possible 0..maxx × 0..maxy coordinates, check if it’s inside, for each position, try placing the second node in all possible coordinates. If edges are too long/short, abort early, otherwise, continue with point three and so on.
+
+This is a stupid, most straightforward solution and it only worked for a very few problems with a low number of nodes. Anything 12+ was out of reach and required a different approach.
+
+Unfortunately, by that time I had no time nor energy to experiment. So I left the solver overnight in a hope that it will find at least something. Spend a good hour writing a program that runs my solvers in parallel and kills them after 30 minutes to free up CPU time for other tries.
+
+
+
+ Leaving it for the “night” at 6am in the morning
+
+
+# The end
+
+I woke up an hour before the end of the contest to see if my program has fished anything out.
+
+The algorithm did, in fact, found _some_ solutions. Many of them could be very obviously improved, though: it finished (didn’t have time) too early.
+
+
+
+
+
+Still, it was partially useful, and I corrected and submitted everything that was found during the night.
+
+Before the rating was frozen, I rated at 70-80 place in the ladder, about its 50% median.
+
+Ideas started coming, though. If only I had one more day!
+
+# Meta game
+
+As usual, rules change a little along with the contest. There are two major additions: one 24 hours after the start, and then one more 24 hours after that.
+
+I suppose it’s mostly for the teams who perform better so that they have something to do. Not me, though: I was too busy solving the original task, though, and haven't looked into additionals at all.
+
+# To sum it up
+
+As you might’ve guessed, ICFPC is totally my cup of tea. 72 hours of very condensed programming on a very hard problem—what could be better?
+
+Also, few things compare to the feeling when you hit a super confusing task, run a program that you _just wrote_ and haven’t even tested, and after a slight delay it figures out a beautiful and simple solution. I almost cried every time it happened.
+
+Some hints if you decide to participate next year:
+
+- Certainly try, it’s worth it 100%.
+- Gather a small team, preferably in a single physical location.
+- Prepare some infrastructure, at the very least: a github repo and a project template.
+- Take breaks, sleep well and eat well. Did I do it myself? Haha, of course not. But don’t be like me :)
+- Bet on a low-level language, you’ll get an upper hand in the end.
+- Rapid prototyping is another key. Whether it’s more important than raw performance, I don’t know :)
+- Do write a visualizer. No matter how much time it’ll take, it’s always net win in the end.
+- Save all solutions in some central shared location, good or bad. In the end, then choose the best solution, no matter which algorithm or which version found it.
+- Don’t make your code too clean, best practices don’t really apply to a code that you’ll throw out in 3 days. But try to keep it at least sane.
+- Do read writeups after the thing is over. It’s about as much fun as the contest itself. Different algorithms, different ideas, different UIs. It’s almost like looking at parallel realities.
+- Write writeups, too. At the very least, share some screenshots.
+
+Source code? You don’t want to look at the source code. Even more: I strongly discourage you from looking at the source code. But it’s [available](https://github.com/tonsky/icfpc2021).
+
+Thanks to the organizers, to Dmitry, to everyone who came visiting my stream. Hope to see you in the rating next year!
\ No newline at end of file
diff --git a/_posts/2021-09-08-clojure-ui.md b/_posts/2021-09-08-clojure-ui.md
new file mode 100644
index 00000000..06e5e26e
--- /dev/null
+++ b/_posts/2021-09-08-clojure-ui.md
@@ -0,0 +1,324 @@
+---
+layout: post
+title: "Thoughts on Clojure UI framework"
+category: blog
+summary: "Ideas and inspirations for new Clojure framework for desktop apps"
+hackernews_id: 28469498
+reddit_url: "https://www.reddit.com/r/Clojure/comments/pkjthk/thoughts_on_clojure_ui_framework/"
+---
+
+I had a long‑standing dream: to implement a UI framework. Nothing inspires me more than noticing hundreds of subtle interactions (e.g. text selection in a text box) and seeing how combined, they bring together a feel of an alive and native component.
+
+For a long time, I thought it’s a Leviathan task: something for a hundred‑people team and tens of years. But then Flutter came along and showed that it’s actually very feasible to re‑implement from scratch the entirety of platform UI to the very last detail.
+
+After that, I joined JetBrains and worked not on one, but two different UI frameworks, which, again, turned out to be a very doable task for a small team.
+
+Lately, Clojurists Together and Roam Research agreed to sponsor this work. I just can’t keep ignoring the signs the Universe is sending me.
+
+I have no framework code yet, but some foundations are laid out in [Skija](https://tonsky.me/blog/skija/) and [JWM](https://github.com/HumbleUI/JWM/).
+
+This post is my thoughts on the subject, some opinions I have and some questions I am not sure how to answer. It’s aimed to facilitate discussion, so please, share your thoughts!
+
+## Why cross‑platform
+
+The very same CPU, memory, and graphics card have no problem executing Windows, Linux, or macOS apps. They don’t care. Yet you can’t run a macOS app on Windows, Linux on macOS, or Windows on Linux. This is not a fundamental property of software, it’s a stupid historical mistake and we should work as hard as we can to correct it.
+
+## Why desktop apps
+
+Despite mobile dominance, desktop remains important for professional and productivity apps, and to build them we need tools.
+
+Mobile has Flutter, Compose, SwiftUI, and desktop is... less advanced. Especially cross‑platform desktop.
+
+## Why not mobile and desktop together
+
+They’re too different. I can’t imagine writing a single app that works both on desktop AND on mobile and is good at both. Two different UIs — sure, but in that case, mobile is already pretty well covered.
+
+I plan to focus on the high‑quality desktop instead of finding a mediocre middle ground between desktop and mobile.
+
+## Why not web
+
+The web started to get more suitable for apps probably since Gmail in 2004. It’s very convenient, yet still very limited and has shortcomings.
+
+I think the fundamental problem of the web is that its APIs are too high‑level. Without low‑level APIs, simple things are sometimes stupidly hard to do, and you have to undo the stuff web is doing for you to get what you need. This is backward.
+
+To top this off, the web is also too bloated, [too unstable](https://tonsky.me/blog/chrome-intervention/), OS integrations are too limited, and it puts a very hard limit on your performance.
+
+In other words, the web is good for simple stuff, but not for anything complex because of lack of control.
+
+And you don’t get to choose a programming language :(
+
+## Why not Electron?
+
+Electron is pretty much web, but with better OS integrations. But it’s still full of compromises: you don’t have threads, you’re stuck with JavaScript, can’t do your own layout, render smooth 144Hz animations, etc. It adds 150Mb to your app package and makes it a memory hog.
+
+Yet it has been a massive success. I think it means our industry is craving for a good desktop UI framework. Electron is a great step in the right direction but not the final form.
+
+## Native or custom
+
+There are two classes of cross‑platform UI frameworks: ones that try to wrap native widgets (SWT, QT) and ones that draw everything themselves (Swing, JavaFX, Flutter).
+
+I don’t really see a choice here, because I’ve never seen native widgets wrapped in cross‑platform abstractions that work. They always get a million little details wrong and still don’t feel native. QT might look decent on some Linux DEs but falls apart on other systems.
+
+
+
+ QT delivering a mix of native and custom widgets, mistreating native and creating a visual mess
+
+
+On the other hand, the web has taught people not to care for OS‑native look and feel. Your app will be accepted on all three platforms no matter which fonts, colors, and button shapes you use, as long as it looks and feels good.
+
+## Why new framework
+
+Well, I want to build UI in Clojure, and Clojure is limited to what Java has: Swing/AWT or JavaFX.
+
+Swing/AWT is very old, has lots of shortcomings, and modern problems are solved on top of the old APIs. The downside of being in Java Core is that you can’t really evolve as the world changes because you can’t break or remove things.
+
+JavaFX has learned a lot from Swing mistakes but has very limited graphics APIs and weird HiDPI and ClearType rendering issues.
+
+Finally, declarative frameworks seem to be a good idea, but neither Swing nor JavaFX is declarative. There’s [cljfx](https://github.com/cljfx/cljfx/) which is declarative but it’s based on JavaFX widgets and I don’t want to use those.
+
+## Why Clojure
+
+Finally, the biggest reason I think this is a worthy idea is Clojure itself. Having worked on UIs in ClojureScript + Figwheel, live reload is a blessing, and Clojure has even a better story there. REPL + live reload + declarative UI framework is a match made in heaven. Anything else will have to try _really_ hard to get even close to this combination.
+
+## Tweak and reuse
+
+The web’s solution to customization is that each button has hundreds of properties you can tweak. You can set background, gradients, border radii, but if you want tricky behavior, you are out of luck. On the other hand, if you don’t want any of that, you still have to bear the weight and complexity of 100 default properties.
+
+I am thinking of another way of approaching things. Somewhere deep in the Compose internals I once saw something like this (not verbatim):
+
+```
+fun MaterialButton(text) {
+ Hoverable {
+ Clickable {
+ RoundedRectangleClip {
+ RippleEffect {
+ SolidFill {
+ Padding {
+ Text(text)
+ }
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+(and they say Lisps have too many parentheses)
+
+What struck me here was that:
+
+a. Internals are perfectly composable with each other, and
+
+b. It’s trivial to write your own button!
+
+If I don’t want different corners, I just write my own button, using 6 out of 7 existing components and only replacing RoundedRectangleClip with my own implementation. Want gradient? Replace `SolidFill` with `GradientFill`, but keep the rest!
+
+This creates a great benefit both for the library (built‑in buttons don’t need hundreds of properties to satisfy everyone) and for the users (they can meaningfully reuse parts from the standard library and only replace parts they don’t like).
+
+Call it a Lego model, if you will. Perfectly composable and reusable chunks and an invitation to play with it.
+
+## First‑class rendering access
+
+At some point, somebody has to draw the actual button. I think it would be great to have direct and first‑class access to override the rendering of anything and draw what you need directly. I can spend all day guessing what type of rounded corners you might need, or can give you the Skia canvas and let you do what you want.
+
+Nothing hurts me more than seeing people try to render diagonals with “creative” use of border‑width. It just feels wrong:
+
+
+
+The same goes for the layout, by the way.
+
+## Declarative model
+
+I’ve been on board with React VDOM approach since 2014 when it first made its way into the Clojure community. I think it’s a fantastic model and a huge breakthrough in how we build UIs.
+
+I also think it works twice as good with an immutable and data‑oriented language like Clojure, where you can load in and out parts of your application, keep the state and see changes update the UI live without reloading.
+
+The approach is seeing even more adoption now, as Flutter, Compose and SwiftUI joined the hype train. And I don’t see the reason not to go in that direction either.
+
+## Why not immediate mode
+
+Immediate mode sounds great in terms of simplicity and speed of development. Unfortunately, it has fundamental problems with layout (you only get one pass and can’t know the size of your content in advance), so retained mode it is.
+
+I’d love to get as close as possible to the simplicity of it, though.
+
+## Data‑oriented
+
+I find the particular approach Compose has chosen a step in the wrong direction.
+
+In Compose, you don’t pass components around. Instead, you call side‑effecting functions that modify the global state somewhere.
+
+```
+var button = Button("Press me") // <- button already added to the dom
+List(button) // <- impossible
+```
+
+This approach means that if someone has created a button, you can’t “hold onto it”: you can’t log, inspect, modify, throw it away. The shape of your code defines the resulting UI tree, not the shape of your values. And programming languages have been optimizing value manipulation, not code manipulation.
+
+In contrast, in React components are values that you can save, pass and do whatever you want. That’s the approach I like.
+
+## Clojure‑native
+
+All ClojureScript‑React wrappers have to transform Clojure values to React values somehow. The beauty of building a new UI framework is that it can be Clojure‑native and completely skip this step: stuff like Hiccup could be interpreted _directly_.
+
+## Layout
+
+I have three main inspirations here. The first one is a one‑pass layout algorithm from Flutter:
+
+
+
+
+
+It’s a brilliant system: simple, performant, easy to understand, easy to extend/hack around. This is important because we want people to intercept control and build their own layout logic for the components that are different enough from the components that will be shipped.
+
+The second is [Subform layout](https://subformapp.com/articles/why-not-flexbox/), which taught me that layout system can be beautiful and symmetric, the same units can be used for everything and that flexbox is not the pinnacle of human engineering.
+
+The third is a notion that parents should position children, and spacing is a part of the parent’s layout, thus [margins are considered harmful](https://mxstbr.com/thoughts/margin).
+
+## Defaults are part of the API
+
+SwiftUI [is notorious](https://tonsky.me/blog/swiftui/) for shipping components that change defaults depending on the OS version and context they are used in.
+
+
+
+
+
+What can you build if your foundation is unsteady? Not much. The approach SwiftUI is taking is to ask developers to update their apps every year.
+
+We don’t play this way in Clojure. In Clojure, we want people to build apps that last tens of years without a single change.
+
+The solution is simple: if _we_ commit to some defaults (e.g. paddings, line heights, colors), _you_ can consider them to be part of the stable API.
+
+## Text handling
+
+Typography is constrained the most in the existing Java UI frameworks: there simply is no place in the API to specify a stylistic set or load a variable font.
+
+Since I’ve [worked with fonts](https://github.com/tonsky/FiraCode/) a little bit, I am eager to include high-quality modern typography into the new framework.
+
+At the very minimum, I want to get these things:
+
+- Variable fonts
+- Open‑type features (stylistic sets, fractions, etc)
+- [Cap‑height‑based text alignment](https://tonsky.me/blog/font-size/)
+
+The latter means font size and line-height will probably work a little different than they do on the web, but it will be much easier to correctly and reliably center a text label inside a button.
+
+
+
+
+
+## Being pedantic
+
+As a pedantic person, I want every little detail to be right. I believe that even small discrepancies can communicate a feeling of a poorly made, unreliable product.
+
+Stuff I aim to get right:
+
+- UI scaling & pixel grid. No blurry lines even on fractional UI scales.
+- VSync. Getting refresh rate correct and synchronized with the monitor.
+- Color spaces. Believe it or not, it’s the responsibility of the app to render in the monitor color space. With the popularity of Macs, P3, and HDR external monitors, things are not as simple as they used to be in sRGB days anymore.
+- Multi‑monitor. Each can have its own scale/refresh rate/color profile.
+
+## Performance
+
+Getting smooth animations is very important. I am aiming at 144 Hz, as this seems to be the most common “above 60” refresh rate for the next few years. No concrete plan here, just “don’t do stupid things and measure performance often” for now.
+
+## Startup time
+
+The startup time of Clojure is bothering me, but maybe compiling to GraalVM or bundling a [custom Clojure runtime](https://github.com/zajac/clojure) could help — remains to be seen. There’s hope.
+
+## Full package
+
+I imagine everything will come together as some sort of Electron but for JVM.
+
+The goal is this: write your app in Clojure, package and distribute it as any other native desktop app.
+
+Definition of done: nobody can tell the app is written in Clojure.
+
+## Your feedback
+
+This is just a dump of everything that is in my head right now. Nothing is final, many things are vague, everything could change.
+
+That’s why I want to start a discussion:
+
+- What do you expect from a desktop UI framework for Clojure?
+- What would you build?
+- What will not work for you?
+- Where am I wrong?
+- Do you have a better insight into any of the topics?
+- Did I miss something important that should be covered?
+
+Share your thoughts! Reply [on Twitter](https://twitter.com/nikitonsky/status/1435624249381826566?s=20) or [drop me a letter](mailto:niki@tonsky.me).
+
+## UPD: feedback
+
+I finally processed most (all?) of the feedback this post has accumulated. Thank you for it, I’ll be looking deeper into every link mentioned. Here’s the list:
+
+Accessibility—I missed it in the original post, but yes, it will be accounted from the start. BTW any good resources on how to get it right/not get it wrong?
+
+Localization (RTL)—Same.
+
+### Accessibility
+
+- [AccessKit](https://github.com/AccessKit/accesskit)
+
+### State management
+
+- Reagent and Re-frame were mentioned both positively and negatively.
+- SwiftUI only negatively.
+- Automerge
+
+### Styling
+
+CSS in JS as a good idea (we won’t start from CSS separated from layout, but it’s good to not to repeat HTML’s mistake).
+
+Adobe [Design tokens](https://spectrum.adobe.com/page/design-tokens/)
+
+[Registering a ‘defaults pack’ with a set of defaults, pass them around as arguments.](https://twitter.com/danielwithmusic/status/1435941938906099714?s=20)
+
+### Layout
+
+Flexbox/Grid as inspiration
+
+[Every Layout](https://every-layout.dev/)
+
+[Elm-UI](https://github.com/mdgriffith/elm-ui) and specifically these two talks:
+
+
+
+
+
+
+
+
+
+### Things to draw inspiration from
+
+- [Makepad whitepaper](https://github.com/makepad/makepad_docs/blob/main/Makepad%20Whitepaper%202020.pdf)
+- Elm Model
+- [Druid](https://raphlinus.github.io/rust/druid/2020/09/25/principled-reactive-ui.html)
+- [Membrane](https://github.com/phronmophobic/membrane/blob/master/docs/tutorial.md)
+- [How to build a functional UI library from scratch](https://blog.phronemophobic.com/what-is-a-user-interface.html)
+- [A Layout Language](https://github.com/matt0xFF/uiwiki/wiki/A-Layout-Language)
+- WxWidgets
+- [Tauri Apps](https://tauri.studio/en/)
+- [Re-frame without React](https://blog.danieljanus.pl/2019/02/05/clj-tvision/)
+- Flutter
+- ClojureDart
+- SWT
+- TornadoFX, a Kotlin wrapper for JavaFX
+- [Seesaw](https://github.com/clj-commons/seesaw), Swing wrapper for Clojure
+- [Vrac](https://app.pitch.com/app/public/player/d673c9f7-c98f-45eb-a6ff-668b42909f1c), Write once, Run Anyhow
+- [Adobe ASL](https://stlab.adobe.com/group__asl__overview.html)
+- AutoCAD’s AutoLisp/VisualLisp
+- [Gio](https://eliasnaur.com/blog/immediate-mode-gui-programming)
+- [Best cross-platform GUI toolkits](https://www.slant.co/topics/983/~best-cross-platform-gui-toolkits)
+
+### Further ideas
+
+- [Shipping UI over the wire](https://twitter.com/alekseipublic/status/1435700344907051013?s=20)
+- Linkable navigation (URL-like)
+
+### General concerns
+
+- [Text Input being hard](https://twitter.com/alex_frantic/status/1435705191987232770?s=20)
+- Framework is as great as the apps people will build with it
\ No newline at end of file
diff --git a/_posts/2021-09-23-python-build.md b/_posts/2021-09-23-python-build.md
new file mode 100644
index 00000000..456a5694
--- /dev/null
+++ b/_posts/2021-09-23-python-build.md
@@ -0,0 +1,78 @@
+---
+layout: post
+title: "Python as a build tool"
+category: blog
+summary: "Why Skija and JWM use Python instead of existing build tool"
+---
+
+Normally, when starting a Java project (or any other programming project, really), you don’t want to reinvent the wheel. You go with the de-facto build system, folder structure, environment, etc. The ones that the rest of the world is using.
+
+Yet, both [Skija](https://github.com/JetBrains/skija/tree/master/script) and [JWM](https://github.com/HumbleUI/JWM/tree/main/script) are built using Python scripts instead of more traditional Ant/Maven/Gradle/SBT. Why? Let’s find out!
+
+# Maven
+
+When we just started Skija, I used Maven because I knew it well. Pretty soon we run into Maven limitations: it’s a very rigid system. It does well on standard projects, but when you need something extra, it becomes an obstacle.
+
+And we had a very non-standard project. Skija is 50/50 Java AND C++ project. We build native artifacts and pack them in JARs files. We have multiple different JARs built from the same source (one per platform). We also pre-process Java sources with Lombok before compiling them.
+
+> Fun fact: the only IDE that supports simultaneous Java + C++ development is Android Studio. No, we weren’t ready to make this sacrifice.
+
+With each complication, I fought hard to make it work with Maven. But its rigidness made easy things extremely hard. E.g. I couldn’t control which files from the directory should go into JARs and which should not. I couldn’t tell it at which directory to look.
+
+Eventually, I gave up. It was _ridiculously_ hard affair to achieve trivial things. E.g. you need to generate/modify an XML file on the fly just to pass credentials from the environment. Or I couldn’t _stop_ Maven from running tests when I didn’t need to.
+
+# Gradle
+
+We didn’t even try Gradle on the Skija project, but I saw my colleagues use it in the wild. Gradle is mostly magic: you write “hints” to the build system and then, hopefully, the right thing will happen at the right time. It’s also full of defaults and conventions, so many details are implicit. You have to know them to guess what will happen, but you can’t google them, because there’s no code.
+
+# Make
+
+We tried Make but I was unable to make it compile Java incrementally. It can process single files, but `javac` requires that you collect all new classes first and then invoke it once. It also kind of depends on Bash (or some other scripting) for things it doesn’t directly support, see below.
+
+# Bash
+
+We tried Bash, but Bash is a terrible, terrible language. Variables do not work as you expect them to work, there are significant whitespaces in your script, spaces in strings are special, and to top it all there are slight differences in CLI utilities you have to use with Bash. We had OS checks for `sed` and `xargs` already because they work differently on macOS/Linux, and we barely wrote 200 lines of Bash.
+
+Bash also wasn’t cross-platform enough. The easiest way to make it work on Windows was installing Git (Yes, Git! I know!), but then C++ compilation started to behave funny because it couldn’t figure out if it’s supposed to build Windows or Linux binaries.
+
+Once again, it started to feel like too much effort for such simple stuff. I just wanted to invoke `cmake` and `javac` and move some files around. Is it too much to ask?
+
+# Babashka
+
+Being a faithful Clojurist, I tried Babashka. It’s a Clojure interpreter with some extra batteries and ridiculously fast startup time. In other words, perfect for scripting.
+
+Babashka almost made it. It was imperative, doing exactly what I wanted in the most straightforward way possible. It was cross-platform. Sure, it felt a little like a stretch (JetBrains isn’t known for its love for Clojure, and other people who wanted to participate would have to learn it) but I was in despair and ready to try anything.
+
+Babashka also was fast. I didn’t mention it earlier, but by invoking javac/cmake directly you can have an incremental rebuild of both Java and C++ done and a new version of the app running by the time that only takes Maven/Gradle to wake up. It’s a difference between 1 second and 3-5 seconds, but when you do it a few times a minute (yes, I like to try things out a lot), it becomes important and _very_ noticeable.
+
+So what was wrong? Unfortunately, batteries weren’t exactly there. Working with files (default directory, parent, listing children, etc) was a bit cumbersome, invoking external processes was also tricky. I caught myself trying to improve the usability of some functions but then it wouldn’t be portable, I’d have to copy the implementation whenever I go, etc.
+
+Also, I was really concerned about forcing random people to learn Clojure. I’d probably try it again when it matures and when the rest of the code will already be in Clojure. Then it’ll be a match made in Heaven.
+
+# Python
+
+Enter Python. It’s cross-platform. It’s fast to invoke. Unlike Bash, it’s a real programming language. It’s one of the most popular languages on the planet. The standard library seems pretty good: it is optimized for scripting as much as for programming. All the necessary batteries are there: to this day, all my build scripts only depend on Python standard library and on zero external dependencies.
+
+From the very beginning, I was unbelievably happy. The contrast with Maven was stark: if I wanted to move some files to the folder, I just wrote one line to copy them. If I wanted to run a test, I wrote one line that invokes `java`. No more fighting with the defaults. No more “declarative” hints that might or might not do what you need.
+
+Easy things were easy, hard things... well, that’s the thing. We didn’t have any hard things, it’s just a build script!
+
+You might be wondering, what about java-specific tasks?
+
+E.g. how do you download maven dependencies? Well, it’s a simple `urllib.request` (we considered vendoring, too). How do you build a classpath? With string concatenation, `"a.jar" + ":" + "b.jar"`. How do you build a jar? By invoking `jar` tool, the one that comes with JVM. I think at some point we even were zipping files directly with `zipfile.ZipFile`, not sure why we changed. Oh, and we also create `pom.properties`/`pom.xml` in Python on the fly.
+
+Wait, really? Yes, really. It’s not that hard! “Real” Java developers could argue that it’s not a “proper” way to do things because responsibility, separation of concerns, you are not supposed to write this code, it’s already implemented, yada-yada-yada. But hey, it’s three lines of code, Maven wouldn’t do it any better than my Python script, and the end result is the same. If generating `pom.properties` is a price I have to pay to avoid touching Maven, I am happy to pay!
+
+In retrospect, Python is the obvious choice. It is the perfect glue. It’s scriptable. It doesn’t care what language the rest of your system is written in. It has all the batteries. It’s fast and cross-platfrom. Google has figured it out long time ago, because (I assume) there’s a lot of gluing in Google. Now I have figured it out, too.
+
+# Conclusion
+
+If you play by common wisdom rules, you get rewarded with integration, support, ease of setup. If you have lots of programmers, or a huge project, you get the benefit of standardization.
+
+But there’s overhead, and the overhead is non-zero. I would argue that for small or non-standard projects it makes more sense to write a few custom scripts rather than investing and fighting with existing build systems that weren’t designed for your use case. You’ll win some performance, too.
+
+As for the best scripting language, please don’t use Bash. I know, it’s tempting, it’s just “two lines of code”. It always starts small until one day it grows into an unportable, unsupportable mess. In fact, Bash is so simple and so natural to just start using that I had to make a very strict rule: never use Bash. Just. Don’t.
+
+But Python is great. Use Python! Or Ruby, Ruby might be good, too. Just, please, don’t use 2.7, it’s not even funny anymore.
+
+If you want, you can take a look at [our scripts](https://github.com/JetBrains/skija/tree/master/script).
diff --git a/_posts/2021-12-13-sublime-clojure.md b/_posts/2021-12-13-sublime-clojure.md
new file mode 100644
index 00000000..028fd4cd
--- /dev/null
+++ b/_posts/2021-12-13-sublime-clojure.md
@@ -0,0 +1,251 @@
+---
+layout: post
+title: "Sublime ❤︎ Clojure"
+category: blog
+summary: "New Sublime Text plugin for working with Clojure"
+hackernews_id: 29549392
+reddit_url: "https://www.reddit.com/r/Clojure/comments/rflhxf/sublime_clojure/"
+---
+
+REPL is Clojure’s superpower. For a long time, I’ve been enjoying Sublime Text but was unable to match it with Clojure. This ends today: I’m happy to present my new Sublime Text plugin, [Clojure Sublimed](https://github.com/tonsky/Clojure-Sublimed).
+
+## Road there
+
+The work started in 2018 when I got too annoyed with built-in Clojure Sublimed grammar. It was there and highlighted _some_ things while reporting errors or misunderstanding others perfectly valid things:
+
+
+
+
+
+On top of that, Sublime just introduced nested contexts in their grammars, so I was able to express the true tree-like nature of S-expressions. This allowed me to highlight unbalanced brackets and even build rainbow parens directly in the color scheme.
+
+
+
+
+
+I didn’t know that yet, but this fact will later allow me to utilize highlighting info to efficiently detect form boundaries. There’s a lesson there, I think: embrace the true nature in the API and it will pay off sooner or later.
+
+I never published this grammar as a separate package but have been using it since 2018 and it has been rock solid. It now comes as a part of the Clojure Sublimed plugin, so feel free to install it. Even if you don’t need a REPL, it’s very useful.
+
+To be honest, in the last three years the Clojure grammar that is built-in in Sublime Text was improved, too, from what I can see.
+
+## Why not Sublime REPL?
+
+Sublime REPL is a universal REPL that tries to cover as many languages as possible. What it does, basically, is that it opens a tab with a terminal for you. It never has been or will be Clojure-specific. In fact, the last time I tried, I wasn’t even able to make it connect.
+
+## Why not Tutkain?
+
+[Tutkain](https://tutkain.flowthing.me/) is a great piece of software with which I, unfortunately, could not make peace with.
+
+First, it packs too much in one package (in my opinion): it does paredit, it ships with its own syntax (yes, I am guilty of that too), it indents code, it runs tests. It is great if you need all of it, but what if I don’t need paredit, for example? It’s a shame I can’t use the rest because of this.
+
+Second, and more importantly, it follows this idiom of “dedicate half of your screen to REPL” which I never understood:
+
+
+
+
+
+Forms on the right more or less repeat forms on the left, namespace indicator is repeated a million times, matching what was evaluated on the left and what appeared on the right is non-trivial, and many results are probably not necessary anymore, yet they take up precious space.
+
+Imagine, like, your bash history always present and taking up so much space? Or a browser that only uses half of the screen. Crazy? Yet this is more or less the default in Clojure tools, as far as I understand.
+
+So I was left with the only option: to try and build my own REPL client.
+
+## REPL, reimagined
+### Inline evaluation
+
+The first and most important feature for me is inline evaluation. Display results directly in the file they come from, _in the context of the code_.
+
+
+
+
+
+Need to compare multiple results? They could all be visible on the screen at once. Changed code? Result disappears. Need to look something up in a different file? The result will stick with the code.
+
+Later I found that Tutkain can do this, too. Only their results disappear once you move your cursor, which is not exactly what I want.
+
+### No “REPL” panel
+
+The second feature that was important to me: get rid of the extra “REPL” panel for good. Why do you need it if you can see your results inline?
+
+
+
+
+
+Well, stdout. Printing is unbounded and unpredictable, so most of the time you don’t really want to see it inline.
+
+The way most REPLs deal with it is simple: they already have an extra panel, so they capture the output that was produced during your evaluation and print it on the panel.
+
+But this also means there are now two places to check, depending on how your `println` was executed. Directly in the function you just called? Look in the REPL panel. In another thread (e.g. in a web server or inside a `core.async` loop)? Check your primary stdout.
+
+And it drove me crazy before: I was banging my head way too often trying to understand why my `println` prints nothing, only to realize I was looking in the wrong place.
+
+So Clojure Sublimed just gets rid of this complexity. Any stdout, either from evaluation or from any other thread goes straight to `System.out`. If you want something else, just wrap your code with `(with-out-str ...) ` or `(binding [*out* ...] ...`).
+
+As an added bonus, there’s no extra panel to manage. There might be one if you choose to start your Clojure program directly from Sublime, but it’s completely up to you.
+
+### Parallel evaluation
+
+The idea of REPL in the command line is that it’s an interactive protocol. You ask something from your program, the program responds back. 1 request, 1 response. It would be too hard to manage otherwise from the CLI.
+
+But in the editor, we are not really constrained by the amount of code we can operate on. So why not operate everything at the same time? Why not make a blocking call that starts a web server and go do something else?
+
+Honestly, I don’t see why not.
+
+
+
+
+
+### Exceptions
+
+Clojure exceptions are... hard to make sense of.
+
+
+
+
+
+What is `:via`? Why does it repeat `:message` and `:data`? Why is message called `:cause` in one place but `:message` in the other? Why Clojure function names have so many $$$ in them?
+
+Compare:
+
+
+
+
+
+Clojure Sublimed makes an effort to clean it up, present Clojure functions with their original names, and remove the parts that happen inside middleware, nREPL, Clojure runtime, or in a Clojure compiler. Most of the time you want to debug your own app, not the REPL or clojure.core.
+
+It’s been two weeks since I started using Clojure Sublimed REPL myself. And you know what? I started to really enjoy Clojure exceptions! Short, concise, pointing directly to the error, formatted in Clojure naming convention, not in Java munged style which you need to decypher back to Clojure. It’s a fantastic experience, even if on paper it doesn’t sound as much.
+
+### Little conveniences
+
+Clojure Sublimed will display the time it took to run your command if it took more than 100 ms (configurable).
+
+
+
+
+
+Let’s just say it’s my way to motivate developers to write faster software. Being aware of the problem is the first step.
+
+### Animations
+
+During long evaluation, there’s an animation playing. Seems minor and stupidly simple to implement, but so much fun looking at it!
+
+
+
+
+
+The best part? It’s trivially customizable. For example, by setting
+
+```
+"progress_phases": ["⠏", "⠛", "⠹", "⢸", "⣰", "⣤", "⣆", "⡇"]
+```
+
+I get this:
+
+
+
+
+
+By applying a little more imagination, it’s not hard getting this:
+
+
+
+
+
+Finally, utilizing Fira Code 6 progress bar glyphs, you can get this:
+
+
+
+
+
+Your imagination is the limit! Send me crazy progress bars you come up with!
+
+### Overloaded shortcuts
+
+Human memory is limited, so some commands in Clojure Sublimed do different things depending on the context.
+
+`Ctrl` + `Enter` evals topmost form if there’s no selection, but current selection if there is one.
+
+
+
+
+
+`Ctrl` + `I` expands stacktrace if you stand on an error, but shows symbol info if standing in plain text:
+
+
+
+
+
+In the future, I plan to overload it further to show pretty-printed values of evaluation.
+
+The idea is that you have one single shortcut to remember and muscle memory will be built much faster.
+
+### Binding keys to eval code
+
+Every project is different, and sometimes it’s convenient to run a piece of code so often you’d want it on a shortcut. It might be namespace reload, test execution, linter, database reconnect — possibilities are endless.
+
+Trick is, it’s hard for plugin developer to know what you might need in advance. That’s why best way to address this need is to give users the ultimate tool: ability to run code.
+
+To support such use cases, Clojure Sublimed allows you to bind arbitrary piece of code to a keyboard shortcut. E.g. by adding something like this:
+
+```
+{"keys": ["ctrl+t"],
+ "command": "sublime_clojure_eval_code",
+ "args": {"code": "(clojure.test/run-all-tests)"}}
+```
+
+and then pressing `Ctrl` + `T` I get myself a little ad-hoc test runner without any need to address this in the plugin itself!
+
+
+
+
+
+The result is conveniently displayed in the status bar.
+
+### nREPL
+
+Clojure Sublimed chooses to communicate with nREPL instead of Socket REPL/pREPL/unREPL. Why?
+
+Because (surprisingly) all other REPLs are built for interactive use. Using web analogy: they are all human-readable HTML pages when nREPL is the JSON API: it’s machine-oriented. It would be strange to build tools on top of human-readable web page, right?
+
+Simple test. Send this to evaluate:
+
+```
+(+ 1 2
+```
+
+All REPLs with the exception of nREPL will get stuck: they will stop accepting any input and will wait for you to close the missing paren. This is ok when you work from the console but completely unacceptable when trying to integrate with another program.
+
+Exceptions are part of the process of developing software. They are called exceptions, but that doesn’t mean they are exceptional or rare. Exceptions should be _expected_, especially during development, and dealt with grace. They certainly shouldn’t punish you by getting the system in a weird stuck state. Simple “Sorry, Dave, I don’t understand you” is a million times better than “I will not tell you what my problem is, you should’ve guessed”.
+
+
+
+
+
+There’re other benefits to nREPL, too. First, it powers CIDER, which Emacs folks use, and Emacs still dominates Clojure ecosystem. So strategically it’s the right place to be — if there’re problems, they will be addressed by a huge community way faster than by me alone.
+
+It also (potentially) gives you access to ClojureScript, at least I hope so (I have not tried yet).
+
+And it comes with batteries: e.g. interrupt, background evaluation, a safeguard against infinite sequences, and extension points.
+
+Bencode is also a much simpler beast to deal with from non-Clojure environments than EDN is. Ideally, it would’ve been JSON, but we are not living in a perfect world.
+
+In other words, I don’t really see an alternative. And I’m also surprised how many things nREPL got right and everyone else got wrong since.
+
+### How to get
+
+The initial version is out now on GitHub (Package Control [will come later](https://github.com/wbond/package_control_channel/pull/8394)). All the instructions can be found in the repo:
+
+
+
+
+
+Feel free to try it out and leave feedback [in the issues](https://github.com/tonsky/Clojure-Sublimed/issues).
+
+For future plans, check out the issues in the repo, too. There’s a few already, perfect if you want to help. Come help me improve this!
+
+## Conclusion
+
+It’s magical how good the ability to evaluate code directly in editor feels. The very first day when I got basic evaluation working I felt the power of it on my fingertips immediately. It’s a superpower.
+
+Now it’s much more polished and ready for the world. The future of Clojure in Sublime looks very bright right now.
\ No newline at end of file
diff --git a/_posts/2022-02-17-humble-decomposition.md b/_posts/2022-02-17-humble-decomposition.md
new file mode 100644
index 00000000..4aa668fb
--- /dev/null
+++ b/_posts/2022-02-17-humble-decomposition.md
@@ -0,0 +1,128 @@
+---
+layout: post
+title: "Humble Chronicles: Decomposition"
+category: blog
+summary: "Overall shape of Humble UI project, a Clojure UI framework"
+---
+
+Now feels like a good time to make this blog an actual log, documenting my findings as I develop Clojure UI library, [Humble UI](https://github.com/HumbleUI/HumbleUI/).
+
+This is an introductory post, describing the overall shape of the project.
+
+None of the decisions are final and might change at any time. In fact, my expectation is that talking about them in public might help either solidify or replace them, rubber duck-style.
+
+# Library decomposition
+
+Humble UI is a Clojure framework, but it’s based on [JWM](https://github.com/HumbleUI/JWM/) and [Skija](https://github.com/HumbleUI/Skija/), both Java libraries. Skija draws graphics, JWM takes care of window management and OS integrations.
+
+
+
+
+
+Yes, the fact that there are three libraries instead of a single monolithic package makes it harder to work with.
+
+But I think it’s a worthy goal: without strong coupling, each library is much more versatile on its own. Use one, use both, or use neither: the decision is up to you. For example, Skija is already being used with AWT, LWJGL, winit, etc. The same applies to JWM: want window management but do your own graphics? Easy!
+
+The use of Java also makes these available to every JVM language out there. So I am happy both with separation and the choice of Java as an implementation language.
+
+# Shared types
+
+As you can imagine, in a UI framework there are lots of points, vectors, and rectangles flying around. The same is true for both Skija and JWM, actually.
+
+And if there’s one thing I hate to see the most it’s pointless conversions between structurally the same types but named differently. E.g. from `class SkijaPoint { int x, y; }` to `class JWMPoint { int x, y; }` to `(defrecord HumblePoint [^int x ^int y])`.
+
+So we need to unify. How?
+
+a) Use built-in AWT classes. A good option but might require linking with java.desktop module and that’s a huge dependency.
+
+b) Use a shared library. That’s what I [ended up doing](https://github.com/HumbleUI/Types), even though I hate to add one more project to the mix. Turned out Point and Rect are pretty much all you need to share, so it’s not that bad and probably won’t need to update it too often.
+
+
+
+ Sorry, no logo :)
+
+
+
+# Java interop
+
+Clojure has a great Java interop, but it’s relying on type annotations too much. And points and rectangles are really everywhere. An example:
+
+```
+(let [content-y (- (.-offset ^VScroll child))
+ content-h (.-height ^IPoint (.-child-size ^VScroll child))
+ scroll-y (.-y ^IPoint child-rect)
+ scroll-h (.-height ^IPoint cs)
+ scroll-r (.getRight ^IRect child-rect)
+```
+
+Now, this poses an interesting challenge. How does one improve on this?
+
+Solution one will be `(defrecord HumblePoint [^int x ^int y])`. But then we're back to square one: converting from JWM points to Clojure ones.
+
+Solution two is to make `class IPoint` implement `clojure.lang.ILookup`, `IPersistentCollection`, `Associative` etc. It’s pretty easy to do and could make any Java class behave like Clojure map!
+
+```
+(:x (IPoint. 1 2)) ;; => 1
+```
+
+This makes working with Java classes from Clojure _very_ pleasant. Code snipped above turns into
+
+```
+(let [content-y (- (:offset child))
+ content-h (:height (:child-size child))
+ scroll-y (:y child-rect)
+ scroll-h (:height cs)
+ scroll-r (:right child-rect)
+```
+
+which is _much_ more readable in my opinion.
+
+The problem is, to implement e.g. `clojure.lang.ILookup` you need to depend on Clojure (static typing problems, ugh). And Clojure is a huge dependency to impose on everyone who would want to use Skija or JWM from Java.
+
+I was struggling with this dilemma for a while until I arrived at a rather unorthodox decision: implement two versions of `types` library, one with Clojure interfaces and one without. Both contain the same classes, but the latter implements a few Clojure interfaces on them.
+
+`io.github.humbleui.types`:
+
+```
+public class IPoint {
+ public final int _x;
+ public final int _y;
+}
+```
+
+`io.github.humbleui.types-clojure`:
+
+```
+public class IPoint extends AFn implements Associative {
+ public final int _x;
+ public final int _y;
+
+ @Override
+ public Object valAt(Object key) {
+ return valAt(key, null);
+ }
+
+ @Override
+ public Object invoke(Object arg1) {
+ return valAt(arg1, null);
+ }
+
+ ...
+}
+```
+
+Both Skija and JWM depend on Clojure-free version of `types`. But when used through Humble UI we already have Clojure on classpath, so we replace `types` with `types-clojure`:
+
+
+
+
+
+Am I happy with this decision? I don’t know. It sure sounds complicated, and I don’t like that.
+
+But:
+
+- it works,
+- it is simple for the end-user (you depend on `humbleui` and it does the right thing by default),
+- I _love_ how natural it feels to use Clojure-enabled classes, even though they are written in Java.
+
+I guess, until we find a better solution, we’ll keep this one.
\ No newline at end of file
diff --git a/_posts/2022-02-21-humble-layout.md b/_posts/2022-02-21-humble-layout.md
new file mode 100644
index 00000000..1b991704
--- /dev/null
+++ b/_posts/2022-02-21-humble-layout.md
@@ -0,0 +1,284 @@
+---
+layout: post
+title: "Humble Chronicles: The Layout"
+category: blog
+summary: "Humble UI approach to layout"
+---
+
+This is a second post documenting the process of developing [Humble UI](https://github.com/HumbleUI/HumbleUI/), a Clojure UI framework. In this post, we discuss Humble UI approach to layout.
+
+None of the decisions are complete or final and might change at any time. The main purpose of these posts is to share ideas and get a better understanding of what can work and what can’t. Feedback is welcome!
+
+# UI Scale
+
+The way I like to think about it is this:
+
+- 1 physical pixel is 1 set of diodes on your screen.
+- 1 logical pixel is an abstract unit of measurement that interfaces are described in.
+
+Logical sizes stay the same between screens, physical sizes vary depending on pixel density and OS settings.
+
+
+
+ 19 logical pixels are still 19 logical pixels, retina or not
+
+
+Logical pixels are converted to physical ones by multiplying them to UI scale. Old-fashioned 1080p screens usually use UI scale of 1.0. Retina screens use UI scale of 2.0. For Windows and Linux, UI scale could be any number: 1, 1.25, 1.5, 1.75, 2, 2.5 are all reasonable UI scales.
+
+Humble UI approach is simple:
+
+- All render surfaces are in physical pixels.
+- UI scale is an arbitrary floating-point number set once per window.
+- By default UI scale is set to OS settings, but could be also arbitrarily adjusted.
+- All component sizes are defined in logical pixels.
+- Logical pixels are floating-point, physical pixels are integers.
+- All sizes (e.g. component width/height) are rounded to physical pixels.
+
+So there could be no 20.5 physical px button in Humble UI, but 20.5 logical px—why not? As long as physical result fits the physical pixel grid, we are happy:
+
+
+
+
+
+UI scale is not a hard-coded number, does not have to match OS setting and everything in Humble UI is scale-aware and ready to render at any scale.
+
+By default, it is used to match OS settings, but can also be used to easily zoom your UI per window:
+
+
+
+
+
+# Layout model
+
+Internally layout is implemented through `IComponent` protocol:
+
+```
+(defprotocol IComponent
+ (-measure [_ ctx size])
+ (-draw [_ ctx size canvas]))
+```
+
+`-measure` returns component’s intrinsic size, given the available space of `size`.
+
+`-draw` must draw a component filling given `size`.
+
+Draw calls are top-down: starting from the outermost container, we divide space and ask internal components to fill it.
+
+Measures are bottom-up: if you want to measure a container, it asks its children to measure themselves, then combines the results.
+
+If you put a button directly into a window, it will fill the whole window:
+
+
+
+
+
+Under the hood, a window will just ask the button to `-draw` with a known window size:
+
+```
+(-draw button ctx {:width 600 :height 400} canvas)
+```
+
+Alternatively, if you wrap the button with `align` to center it, the situation will change:
+
+- Window will ask `align` component to `-draw` at window size;
+- `align` will `-measure` button to determine its intrinsic size;
+- `align` will translate `canvas` and then ask the button to `-draw`, but with its own previously determined size.
+
+
+
+
+
+So far this model proved sufficient enough, we’ll see how much it can handle as Humble UI evolves.
+
+# Stretch by default
+
+Button stretching to the full window might sound counter-intuitive at first, but if you think about it, it’s the only logical thing to do: button inside a container without anything else _must_ fill the whole container.
+
+Now, if you want different behavior, you must specify it further. Do you want it aligned to the top left corner? Left centered? Centered stretched? None of these sounds like an obvious default, so you must add it explicitly.
+
+# Additive model
+
+Humble UI favors small single-purpose components over settings or modifiers because they tend to compose better.
+
+E.g. there’s no setting on a button to set its width to 300px, but there’s `width` component that can set width to any child it contains. Same for vertical/horizontal alignment, padding, etc.
+
+This:
+
+```
+(ui/halign 0.5
+ (ui/valign 0.5
+ (ui/width 300
+ (ui/padding 10 10
+ (ui/button "Click me")))))
+```
+
+Will get you this:
+
+
+
+
+
+Basically you add more and more stuff until you get what you want. Don’t like something, or want to do something differently? Pull it apart, replace, add, all without modifying components themselves.
+
+Looks a little verbose, but hopefully this approach will be more “simple than easy” and faster to understand than, say, CSS.
+
+The tricky part was to figure out a model that actually combines well. At various stages, I’ve seen component stretching or not stretching where expected, `halign` not being able to be nested inside `valign` and vice versa, `halign` not working inside `column`/`valign` inside `row` and many others.
+
+But all well that ends well, and I am happy with where we are at right now:
+
+
+
+
+
+# Gaps and margins
+
+One thing I feel very strongly about is margins in CSS. Consider following UI:
+
+
+
+
+
+If you try to describe it with words, you would say something like “profile picture, profile link and logout link with 10 px between components”.
+
+We think about 10 px spacing as its own thing, part of the layout, not part of the components. We don’t say “profile picture with 10 px right margin” because that’s not how we think.
+
+In CSS, however, the way to add spacing between components is to modify components themselves, adding the right margin, like this:
+
+```
+.userpic { margin-right: 10px; }
+```
+
+
+
+
+
+There are three main problems with this:
+
+1. Components with margins are hard to reuse. That’s because margins make sense only in a certain context. They belong to the container, not to the component.
+2. First/last components need special treatment because you don’t want 10 extra pixels there.
+3. How do you decide if one should use the left or right margin?
+
+Space between components doesn’t naturally belong to one or the other. It’s literally _between_ them.
+
+So naturally Humble UI does not have margins. What do we have instead? Gaps!
+
+```
+(ui/row
+ userpic
+ (ui/gap 10 0)
+ proflie
+ (ui/gap 10 0)
+ logout)
+```
+
+Gaps are great because they are simple things that you _add_ to your layout instead of modifying existing components. Logically they live on the same level as other container children, which is where they belong.
+
+
+
+
+
+They are also easy to work with and have a simple conceptual model: they are just blocks, like any other block.
+
+# Universal align
+
+Align idea is [stolen from Flutter](https://api.flutter.dev/flutter/widgets/Align-class.html), with the only difference being that I split horizontal and vertical aligns into two separate components.
+
+To align something inside a container, you specify two numbers: percentage of the child’s width and percentage of the container’s width.
+
+```
+(
+ (ui/halign 0.6 0.2
+ ))
+```
+
+will get you:
+
+
+
+
+
+What’s cool about this model is that it gives you all the usual alignment modes:
+
+```
+(ui/halign 0.0 0.0) => left
+(ui/halign 0.5 0.5) => center
+(ui/halign 1.0 1.0) => right
+```
+
+and then some:
+
+
+
+
+
+# Text bounding box
+
+If you read my [deep dive into font construction](https://tonsky.me/blog/font-size/), you know that I don’t like how text is aligned right now on the web. Humble UI feels like a good moment to revisit it and improve on the status quo.
+
+In short, text boundaries in Humble UI are defined by baseline and cap-height, not by ascender/descender/em square or any other arbitrary unit.
+
+
+
+
+
+What are the upsides?
+
+Text inside buttons is easy to center:
+
+
+
+
+
+Baseline alignment is the same as center alignment:
+
+
+
+
+
+Text aligned to picture looks good by default:
+
+
+
+
+
+Line height is easier to control (remember gaps?):
+
+
+
+
+
+But wait, what about ascenders/descenders? They will render outside the boundary box? Yes they will:
+
+
+
+
+
+I don’t consider that a problem because most of the time you add extra padding around text blocks anyway.
+
+# Dynamic sizes
+
+Remember `calc` from CSS? Well, you can do a similar thing in Humble UI, but using plain Clojure code:
+
+```
+(ui/width
+ #(-> (:width %)
+ (- (* 3 padding))
+ (/ 2)
+ (+ padding))
+ (button "C" color-clear))
+```
+
+Variables, functions, macros—all at your fingertips.
+
+Works everywhere a dimension is needed: e.g. in padding component.
+
+# What’s next?
+
+Notable missing parts are:
+
+- multiline text blocks (paragraphs),
+- wrappable containers.
+
+Hope to add them soon. Meanwhile, you can play with the code [here](https://github.com/HumbleUI/HumbleUI/).
+
+Am I missing something else? Do you have an opinion? Make sure to [let me know](mailto:niki@tonsky.me)!
\ No newline at end of file
diff --git a/_posts/2022-02-22-humble-dx.md b/_posts/2022-02-22-humble-dx.md
new file mode 100644
index 00000000..8e560c4c
--- /dev/null
+++ b/_posts/2022-02-22-humble-dx.md
@@ -0,0 +1,58 @@
+---
+layout: post
+title: "Humble Chronicles: Developer Experience"
+category: blog
+summary: "Using Humble UI from the REPL"
+---
+
+This is a third post documenting the process of developing [Humble UI](https://github.com/HumbleUI/HumbleUI/), a Clojure UI framework. In this post, we discuss cross-platform REPL-driven development.
+
+# Interactive development
+
+When building UIs, fast iteration times are crucial for good results. The quality of the result is directly proportional to the amount of iterations you go through during the development.
+
+Another thing about UI development: most of the time it’s a constant stream of small tweaks: change a color here, add a padding there, make line shorter over there. Almost never building a UI is a huge blind upfront effort, it’s just doesn’t happen.
+
+Now, statically typed languages traditionally struggle with that type of experience. You either need to recompile and re-run your app, which takes time and loses you your state, or build and approximate version of the UI from the XML description or code, which might differ from a real thing.
+
+
+
+
+
+Clojure is uniquely positioned for UI development specifically: it’s a language designed around REPL and live modifications, which is [great for any day-to-day coding](https://tonsky.me/blog/interactive-development/), but really starts to shine when applied to UI.
+
+In fact, REPL-driven development is the main reason why I think this effort has any chance of getting traction: it’s qualitative difference from almost any other tool out there (except, maybe, browser, if cooked right).
+
+Here, watch me changing constants, updating the layout, modifying _the UI code_ and running the tests all without ever leaving the editor. With instant feedback and full state persistence:
+
+
+
+
+
+Can your UI framework do that?
+
+# Crossing platforms
+
+Humble UI is a cross-platform framework, which might be both good and bad thing.
+
+Bad because it doesn’t make use of platform-native widgets and as a result apps don’t look native. But, in the age of web and Electron, I don’t think it’s a big deal.
+
+Good because it somewhat delivers on “Write once, run anywhere” mantra.
+
+With a slight twist: we do try to abstract common stuff (e.g. window size or mouse events) but also respect platform special traits (e.g. toolbar styles on macOS). Latter will only work on specific platforms, but if you want to go extra mile, there’ll be an option for you.
+
+# Building an app
+
+After many months working on JWM and Humble UI and a few days getting calculator app to work on macOS, I finally booted Windows and it was there, too, real, flesh and bone, working with no extra effort. I booted in Linux and it also was a success!
+
+
+
+
+
+It’s a fantastic feeling, after working on the internals for so long, finally apply it to something and feel that it does something useful and has a reason to exist.
+
+If you are wondering what it feels building an app from scratch, there’s a video of Wordle implementation from start to finish in \~3 hours:
+
+
+
+Humble UI if far from being usable: it lacks a lot, and what it got will very likely change in the near future. Yet, for the curious, the link is [GitHub.com/HumbleUI/HumbleUI/](https://github.com/HumbleUI/HumbleUI/)
\ No newline at end of file
diff --git a/_posts/2022-03-28-horizon.md b/_posts/2022-03-28-horizon.md
new file mode 100644
index 00000000..501f1278
--- /dev/null
+++ b/_posts/2022-03-28-horizon.md
@@ -0,0 +1,292 @@
+---
+layout: post
+title: "Forbidden West and the Art of Editing"
+category: blog
+summary: "Why editing is a crucial part of visual arts"
+---
+
+Horizon: Forbidden West seems like a great game: fantastic design and art direction, great music. Only one thing falls short: cut scene editing.
+
+In this article, we’ll analyze one particular cut scene, discuss every mistake, and step by step edit a better version of it.
+
+# The problem
+
+First of all, watch the scene as it’s present in the game. It’s just 1 minute long (sound on):
+
+
+
+
+
+Again, the story feels great, the message clear, and usually you want your editing to amplify that. Unfortunately, here it’s more of hit and miss, sometimes helping to tell the story and sometimes sending an unplanned meaning and confusing the message.
+
+# Waking up in a dream
+
+Let’s start from the very beginning: Aloy goes to sleep and sees a recurring dream.
+
+
+
+
+
+I see a few things wrong with how this scene is depicted.
+
+First, she “wakes up” in a dream, like she suddenly teleports there and looks confused. This is not how dreams really work: they don’t have an explicit beginning. They sort of go on and you suddenly realize you’ve been in a dream for a while.
+
+The second problem is that it feels like she makes a decision to move forward. Again, not the dream logic: in dreams, things happen, sometimes with you, but you are still more of a passive observer.
+
+For the editing, we’ll leave this shot out completely.
+
+# Walk to the tree
+
+Next Aloy walks to the tree:
+
+
+
+
+
+The problem here is that Alow walks unnecessarily energetic like she has a goal and anxiously wants to accomplish it. This might be ok for the later stages of the dream, as the story develops and she gets more agency but seems out of place at the very beginning. We want to get a feel of the situation first, of what’s going on, before seeing her acting.
+
+The order of shots here also has a problem. In the first one, we see Aloy walking towards the tree. Not a mistake on its own, but it’s followed by a second shot where we see her just walking in the field, without the tree visible.
+
+
+
+
+
+This feels weird because we already learned where she is walking, her goal has been revealed. It is followed by a shot that brings no new information and serves no purpose.
+
+You might think that you can “just show the pictures” for no purpose, but in a well-edited movie, each shot has a meaning, a purpose, a piece of information that it gives you that you didn’t have before. As you watch more and more cinema, you start to feel and appreciate this rule and it becomes instantly noticeable when it is broken and something is shown for no reason.
+
+But we are lucky this time: if we swap the shots, they start to work well together! In the first one, we see Aloy walking, but don’t know why or where. In the second shot, the goal is revealed: to the tree! Now it has a purpose.
+
+
+
+
+
+There’s a third shot, a god eye’s view, but I can find no reason for it to exist, no matter where we put it. It is usually used to give something a sense of scale or add clarity, but that doesn’t seem like the story that’s been told here. So we just drop it.
+
+This is what we get:
+
+
+
+
+
+# Arrival
+
+The original footage of Aloy’s arrival:
+
+
+
+
+
+Let’s analyze this.
+
+
+
+
+
+A wide shot of Aloy walking to the tree. Since we’ve already covered it in the previous sequence, we don’t really need this.
+
+A subtler issue is that it places the camera under the tree and allows Aloy to walk to it. Since we were building the whole sequence from Aloy's perspective, this sudden change of pivot point seems unnecessary and sends the wrong message. But since we don’t need the shot at all, we’ll just drop it.
+
+
+
+
+
+Medium shot of Aloy entering the frame. It serves a clear purpose: Aloy arriving at her destination. We’ll keep it.
+
+
+
+
+
+Wide shot of Elisabet on a bench, followed by a medium shot of Elisabet on a bench. These are great! Aloy was walking to something, now we learn that it’s a person, then we learn that it’s Elisabet, a person Aloy was cloned from (oops, spoilers!).
+
+
+
+
+
+This medium shot is just a repetition: we already saw Aloy recognizing who’s sitting under the tree. Don’t need it.
+
+
+
+
+
+Closeup of Elisabet noticing Aloy. This is okay on its own, but the whole Elisabet sequence here adds too much extra detail for no obvious need or purpose. We see that Elisabet is busy with her own things, then she notices Aloy, and then the two reunite.
+
+As far as my opinion goes, we don’t need those. We don’t need Elisabet going through phases and changing her behavior. We want that for Aloy since it’s her story and her dream, everything else here is only to affect her. In short, we don’t care what Elisabet is busy with, we only care what she means for Aloy.
+
+So let’s simplify this and only keep 2 (Aloy arrives) and 6 (Elisabet is revealed):
+
+
+
+
+
+Are we losing anything by leaving just two shots out of six? I wouldn’t say so. I’d say we made the message clearer and stronger by using less.
+
+# Reunion
+
+Once Aloy meets Elisabet, they reunite.
+
+
+
+
+
+Let’s go through it:
+
+
+
+
+
+Elisabet gets up and walks to the Aloy. All good, no comment.
+
+
+
+
+
+A close-up of Aloy’s face. Completely unnecessary. We’ve already seen this perspective twice, and the third time adds nothing. Maybe it was supposed to be a reaction shot, but I am completely unable to decipher what reaction that is.
+
+Also, why the heck is the camera so low in this shot?
+
+
+
+
+
+Medium shot of Elisabet putting a pendant on Aloy. All is great, but! For some reason, it breaks the 180-degree rule: do not cross an imaginary line (stay on the same side) between two characters for no reason.
+
+
+
+
+
+
+
+
+
+Aloy walked from the left, so let’s keep her on the left for clarity:
+
+
+
+
+
+
+
+
+
+Finally, we get a wide shot of Elisabet and Aloy hugging:
+
+
+
+
+
+Great as a shot, but has a problem in the overall dramatic structure, so let’s talk about it for a moment.
+
+For a scene to work, you need an emotional pendulum: you feel good, then bad, then good again. Or you build tension, then release it, then build again. Get in trouble, get out of it, etc. The bigger the amplitude, the better the scene.
+
+Everything we’ve seen so far was building tension and uncertainty: the music is intense, we don’t know where Aloy is or what to expect. We’ve seen a little bit of progression here, as we learn a little about the place and what is going on.
+
+Finally, we’ve arrived at the highest point of Aloy’s journey: a huge release of emotion and tension, as she learns she was walking to a friend who means a lot to her and is happy to see her.
+
+This is why we need this moment to last. We’ve been building the tension for so long we just owe it to the viewer to have a prolonged feel-good moment about the situation, before we take them back to the ride. It’s an oasis of calm in the sea of trouble.
+
+That’s why we don’t reveal the red dawn just yet. It’s too soon to start trouble immediately after our heroes met: we need to give them space to catch their breath. Don’t worry, we’ll return to trouble soon :)
+
+
+
+
+
+Since I don’t have access to the original 3D scene, all I can do here is to stretch the existing footage a little longer. Notice also how music helps to sell this moment, and how it has a perfect length for what we need. The picture, unfortunately, does not.
+
+# The trouble
+
+Good things don’t last in these final shots of the scene:
+
+
+
+
+
+This is what’s happing in the scene:
+
+1. Wide shot of Elisabet and Aloy with Red Blight on the background
+2. Elisabet starts to dissolve
+3. A wide shot of red tentacles (Red Blight)
+4. Back to Aloy, Elisabet finishes dissolving
+5. God-eye’s view of Aloy and approaching Red Blight
+6. Medium shot of Aloy noticing the tentacles
+7. Tentacle’s POV approaching Aloy
+8. Aloy noticing the tentacle again
+9. Tentacle crawls up the Aloy’s leg
+10. Aloy tries to shake it off
+11. Black screen
+12. Aloy wakes up
+
+Just reading this, it feels like a long scene, but it’s not. It’s just cut too much.
+
+We won’t discuss every shot separately, let’s talk general direction instead.
+
+Right now the sequence of events is: We see Red Dawn - Elisabet disappears - Tentacled crawl to Aloy.
+
+It’s confusing: did Elisabet disappear because of the tentacles or on her own? Should we be scared of Red Dawn or tentacles? Or disappearing? Because those are separate and not directly linked, the total effect is lessened: what should we worry about?
+
+My proposal is to glue those together with cause and effect so that the viewer will worry about everything at once, and also add a progressive reveal to add suspense to the scene.
+
+Here’s the order I’d do it:
+
+1. We don’t see Red Dawn during Aloy and Elisabet's hug.
+2. We start hearing tentacle hissing closer to the end of the hug, about when Aloy says “But it never lasts”.
+3. We see an approaching tentacle but heroes don’t notice it yet.
+4. Tentacle starts to crawl up to the Aloy.
+5. She notices it but can’t shake it off.
+6. Elisabet gets Thanos-ed.
+7. Wide/god eye’s shot of Red Blight consuming space around Aloy.
+8. Finally, we see the real scale of it: red dawn takes over the horizon.
+
+Notice the progression here: everything is good - something feels bad - something bad starts happening, but it feels Aloy can shake it off - real bad thing happens - we learn the scale of the problem.
+
+Small - medium - big - boom. Like clockwork.
+
+Also, notice that the loss of Elisabet is now caused by tentacles so that Aloy gets a real stake in this: she loses what she loves because of the red blight, and this becomes her personal problem, not just some abstraction.
+
+Couple of notes on the original shots:
+
+
+
+
+
+This weirdly puts us at the POV of the tentacle. Why? We should be afraid of it, not become it.
+
+
+
+
+
+The Red Dawn uses Dolly Zoom which seems... strange here? It zooms in, while if anything we should be zooming out, revealing how huge the scale of this thing is.
+
+
+
+
+
+By a total oversight, the very musical peak of the whole scene, where the tension is at its highest, gets resolved over a completely black screen. The obvious play here is to match the highest note in the music, the biggest visual shot (Red Dawn), and the moment the Aloy wakes up with a scream. Works SO. MUCH. BETTER.
+
+This is the altered scene:
+
+
+
+
+
+# The Final Cut
+
+As a reminder, here’s where we started:
+
+
+
+
+
+And this is what we got:
+
+
+
+
+
+Is it better? I hope so.
+
+I also hope that I made a good point about how editing is a crucial part of the visual arts.
+
+But most of all, I do hope it was fun. Thanks for watching, hope you enjoyed it!
+
+And do play the game, I am sure it’s way better than its cutscenes.
\ No newline at end of file
diff --git a/_posts/2022-04-13-datascript-2.md b/_posts/2022-04-13-datascript-2.md
new file mode 100644
index 00000000..b0d3d6ec
--- /dev/null
+++ b/_posts/2022-04-13-datascript-2.md
@@ -0,0 +1,135 @@
+---
+layout: post
+title: "Ideas for DataScript 2"
+category: blog
+summary: "Things that came to mind while working on DataScript 1"
+---
+
+No, I am not working on second version. This is just a list of ideas based on 8 years developing DataScript and 3 years building web-applications with it full-time.
+
+Maybe you’ll find inspiration in these? Feel free to borrow anything you like!
+
+## UUIDs for entity IDs
+
+Makes it easier to generate new IDs in distributed environment without consulting central authority.
+
+## Attribute IDs
+
+Right now attributes are stored as keywords in DataScript. This is inefficient both for storage (CLJS creates different keywords each time, CLJ interns them) and for comparison.
+
+Integer IDs are fast to compare and compact to store. Keyword ↔︎ id translation could be done transparently to user (Datomic fails to do that in some cases, need to check if could be avoided).
+
+## Optimized B-Trees
+
+Right now B-Trees store datoms (JS objects/POJOs), which leads to crazy amount of pointer chasing during binary searches.
+
+E.g. to access attribute, you first go to datom, then to `a` field, which points to keyword. In CLJS, keyword is an object which points to two strings: namespace and name. Each string itself is probably a pointer. You get the idea.
+
+It will probably be much more efficient if entity ID and attribute ID would be stored directly in B-Tree array, without any need to go anywhere. This will make EAVT and AEVT indexes way faster, and those are responsible for majority of use-cases.
+
+## Transaction IDs
+
+In Datomic, each datom is branded with transaction ID when it was created. They are used in full history DB view to filter out past from the future.
+
+DataScript doesn’t have that, so TX serves no real purpose. Same for `added` flag: it’s barely needed.
+
+## Entities without cache
+
+Entities (object-like view into database) are caching attributes right now, copied from Datomic.
+
+It feels like it’s unnecessary: you can just store a pointer into EAVT index and scan/binary search every time somebody asks for attribute. EAVT is a cache in some sense.
+
+## No queries
+
+I know this is controvertial. Query engine amounts for a lot of complexity in DataScript, and it’s very convenient to use. I have a feeling a lot of people are attracted to DataScript because they want to use queries.
+
+Well, it surprised me as well that in 3 years of full-time web app development with DataScript I haven’t used a single query.
+
+I guess if you build UI with a database, you don’t need queries that much. What you need is a graph database: get this object and follow this relation to another object/collection.
+
+Another reason why I don’t like queries is performance. Index scans are the same, but converting form raw datoms into relations takes time, building set (removing duplicates) takes time, etc. I find it faster to use direct index access for simple stuff.
+
+Performance for simple queries could be solved, I think. But the general viability of queries (despite their attractivenes) is under question.
+
+## Recursive walking
+
+What saddens me the most in DataScript/Datomic is that rules are often used to do simple recursive walking. Like, go all the way up or all the way down the tree through these relations until a condition is met. Some examples:
+
+- Go all the way up through `:entity/parent` until `(= (:entity/type %) :document)`
+- Do breadth-first search recusrive through `:entity/children` and collect `:entity/text`
+
+You don’t really need a Datalog semantic there: building sets, joining and all that. But people still use it because it’s the only thing they got.
+
+So maybe a collection of convenient recursive walk functions could do instead?
+
+Suggestions:
+
+- Circular reference attribute grammars by [Brandon Bloom](https://twitter.com/BrandonBloom/status/1514294816095449088)
+- [Pathom 3’s smart maps](https://pathom3.wsscode.com/docs/smart-maps/) by [Kenny Williams](https://twitter.com/kennyjwilli/status/1514408433037893635)
+
+## Ordering
+
+We need a good built-in way to order stuff. Not sure how API would look like, but we need that. Order in UI is very important.
+
+Suggestions:
+
+- 4-tuples to support an additional indexed `order` value by [@denik](https://twitter.com/denik/status/1514278505814577154)
+- [Fractional indexing](https://observablehq.com/@dgreensp/implementing-fractional-indexing) by [@ccorcos](https://twitter.com/ccorcos/status/1514319234829991937)
+
+## Reactive updates
+
+Re-rendering your whole application is great (and it worked very well for us). But I always want to be more efficient. Since we won’t have queries, how about subscribing to individual entity updates?
+
+I have a feeling this could be done even today, without modifying DataScript even, but designing it from the beginning might work out even better.
+
+The simplest API I’m thinking is like
+
+```
+(d/subscribe conn e a v callback)
+```
+
+where any of `e`, `a`, `v` could be `nil`. E.g.
+
+```
+(d/subscribe conn 100 nil nil callback)
+```
+
+means you want any changes in any attributes for entity 100.
+
+This API is simple, could be implemented efficiently, could get you a long way (hopefully).
+
+## Persistence
+
+As experience shows, even in browser DataScript databases could grow quite large. Which means it’ll be great to have:
+
+- Lazy loading segments from a persistent storage/network
+- Append to transaction log
+- Rebuild indexes once in a while
+
+Would be cool to have file system / SQLite / IndexedDB storages for starters.
+
+Suggestions:
+
+- LSM trees by [Tweets McSjørup](https://twitter.com/zorendk/status/1514306839625801731)
+
+## Async API
+
+The reason DataScript doesn’t have persistence yet is because all DataScript APIs are synchronous, and IndexedDB APIs are asynchronous.
+
+For persistence to work, you need to make all APIs to be async, too (not on a server, of course, but in JS, which makes me sad).
+
+## Replication
+
+Client-to-server, server-to-server, client-to-firebase, client-to-Datomic, client-to-client.
+
+The idea is simple: if you run DataScript on both client and server, it would be cool if they could talk to each other directly and you don’t have to implement sync layer yourself.
+
+You don’t want to do it yourself: there’s a lot of edge cases and failure modes that are tricky to get right.
+
+The main obstacle here is API: how would subscriptions look like? How to start/cancel them? How to track which datom came from which subscription? How to clean up unneded data?
+
+## Conclusion
+
+This is a dump of raw ideas I had collected over the years in case I ever decide to start DataScript over.
+
+If you have more—don’t hesitate to reach out! I’d be glad to hear them out.
\ No newline at end of file
diff --git a/_posts/2022-05-13-network-eval.md b/_posts/2022-05-13-network-eval.md
new file mode 100644
index 00000000..8e45d163
--- /dev/null
+++ b/_posts/2022-05-13-network-eval.md
@@ -0,0 +1,210 @@
+---
+layout: post
+title: "Ideas for Clojure Network Eval API"
+category: blog
+summary: "How nREPL could be improved"
+---
+
+Jack Rusher [is asking](https://twitter.com/jackrusher/status/1525092640286560258) for nREPL feedback from Clojure tooling authors:
+
+> After implementing an nREPL server for a new Clojure runtime, I'm begging the other tools maintainers to work with me to define an actual standard that works.🥹
+
+Since I am also a [tool maintainer](https://tonsky.me/blog/sublime-clojure/) AND my tool works with nREPL, I thought I share my ideas here.
+
+# What is REPL?
+
+Rich Hickey [famously said](https://groups.google.com/g/clojure-dev/c/Dl3Stw5iRVA/m/IHoVWiJz5UIJ) that nREPL is not a REPL:
+
+> REPL stands for something - Read, Eval, Print, Loop. It does not stand for - Eval RPC Server/Window. It is not something I "feel" about. But I do think we should be precise about what we call REPLs.
+[...]
+'read' is a protocol. It works on character streams, one of the most widely available transports. It uses delimiters (spaces, \[], (), {} etc) to separate forms/messages. Readers implement this protocol and return data upon encountering complete forms.
+
+I am happy to accept these definitions and have no desire to force REPL to mean what it’s not. But then, in my opinion, Clojure does not need a REPL, it needs an Eval RPC protocol. To turn Rich’s methods against him, current REPL _complects_ command-line interactions with code evaluation.
+
+It’s trivial to build human-friendly CLI on top of machine-friendly RPC, but much harder to build machine-friendly RPC on top of a human-oriented command line.
+
+Sure, `ls -lah` output looks nice in my terminal, but imagine parsing this for machine consumption?
+
+
+
+
+
+So I insist that Clojure needs a “Remote Eval API” — message-oriented machine-friendly network API that’s easy to build on.
+
+One feature that is crucial for me that nREPL gets right and all of the Clojure REPLs get wrong is evaling incomplete forms. If I send `(+ 1 2`, I don’t want to see my REPL stuck in the intermediate state. I want to see an error.
+
+# JSON serialization
+
+nREPL uses bencode by default, which was a brilliant decision. Bencode is super-simple and trivial to implement, meaning nREPL clients could exist in any environment: Python for Sublime, Java for IDEA, Elisp for Emacs, JS for VS Code, VimScript for Vim.
+
+This day I would go with JSON, though. I know nREPL has JSON backend, but I mean by default.
+
+Using EDN would be a terrible mistake, though. EDN is much less popular, and REPL clients exist in all sorts of environments, rarely in Clojure.
+
+# Automatic session management
+
+nREPL has a concept of sessions. I think this exists mainly for capturing dynamic bindings like `*warn-on-reflection*` or `*print-namespace-maps*`.
+
+What troubles me is that sessions require manual management: you clone them manually and have to close them manually. If you forgot to close them, they will exist indefinitely, leaking resources.
+
+I would replace this with automatic management, a session per connection. Connection dropped? Kill the session.
+
+# Session-scoped middlewares
+
+Another thing sessions could be useful for are middlewares. Currently, they are installed globally, but that seems like an oversight? If I need middlewares X, Y, and Z for my editor to work, why should anyone else see them too?
+
+In true network API, what I do should not affect what user in a parallel session is doing.
+
+This all applies, of course, only if we allow middlewares to exist.
+
+# No middlewares
+
+If I understand Jack right, his main concern is that middlewares are not portable. If you wrote an nREPL client that installs middlewares for Clojure, it won’t work with ClojureScript.
+
+I faced the same problem, and that’s the main reason why Clojure Sublimed doesn’t talk to CLJS yet.
+
+Sure, middlewares are a nice escape hatch. But maybe the goal should be to get the basic protocol enough so that nobody will have to write middlewares?
+
+# No “upgrade”
+
+In my head, Network EVAL API should work like any other API: you connect to the server you want, send predefined commands and get results back. Want to eval Clojure? Connect to Clojure server. ClojureScript? Connect to CLJS.
+
+Instead, the route Clojure REPLs choose is “REPL upgrade”. You connect to Clojure server always, then eval some magic forms and commands start to behave differently, e.g. being compiled to CLJS and sent to the browser.
+
+I think even nREPL does that because Piggieback is a middleware installed globally that shares server with Clojure instead of starting its own.
+
+This makes API non-uniform, complects CLJS (or any other) REPL with Clojure REPL, and just feels weird to use. Evaluating Clojure code should not assume JVM Clojure environment!
+
+# Parallel eval
+
+For some reason, nREPL allows a single pending eval per session. This seems like an arbitrary limitation. I think evaluating one form should not prevent you from evaluating another, given that threads are easy to create in JVM.
+
+CLJS might be in a different situation, but that shouldn’t mean Clojure users should suffer.
+
+# Single connection
+
+Not sure if worth mentioning since nREPL got this right, but some REPLs suggest you open second connection to control the session in the first one.
+
+This is a terrible idea. Multiple connections (even two) are order of magnitude harder to manage than a single one.
+
+Don’t block the line, allow parallel eval and you won’t need a second connection.
+
+# Stacktraces
+
+I don’t think nREPL sends stack trace if an exception happened? That is a crucial information, seems strange to omit it.
+
+P.S. Stacktraces as data would be nice.
+
+P.P.S. Stacktraces that don’t show nREPL internal code would be double nice.
+
+P.P.P.S. Unmunged Clojure names would be triple nice.
+
+P.P.P.P.S. Correct error position would be quadruple nice, but this is on Clojure more than on REPL.
+
+# Execution time
+
+Stupidly simple, but quite an obvious feature: how long did that form take to eval?
+
+Can’t measure that on the client due to network overhead.
+
+# Extract info from Clojure file
+
+Not 100% sure this belongs in the Network API server, but then on the other hand repeating this in every client feels excessive too?
+
+Imagine you go to a file and eval the last form here:
+
+```
+(ns my-ns
+ (:require [clojure.string :as str]))
+
+(defn reverse [s]
+ (str/reverse s))
+
+(reverse "abc")
+```
+
+Which namespace should it be evaled in? `my-ns`, the namespace of the file, right?
+
+But the network server doesn’t know that! All it sees is something like:
+
+```
+{"op": "eval",
+ "code": "(reverse \"abc\")"}
+```
+
+This won’t work because `reverse` is not defined. You can specify namespace though:
+
+```
+{"op": "eval",
+ "code": "(reverse \"abc\")",
+ "ns": "my-ns"}
+```
+
+This improves the experience a bit, but how do you get this `my-ns` string? Well, to do that, you have to parse Clojure file, find the closest `ns` form and take the first symbol. Which in turn could be preceded by arbitrary-complex metadata form, like this:
+
+```
+(ns my-ns
+ (:require [clojure.string :as str]))
+
+(defn reverse [s]
+ (str/reverse s))
+
+(ns ^{:doc "Hello"} another-ns
+ (:require [my-ns :refer [reverse]]))
+
+(reverse "abc")
+```
+
+Not exactly the simplest task, is it? Well, it’s only hard if you are in Python, or JS, or VimScript.
+
+But if you are in Clojure, it’s trivial! Clojure already ships with Clojure parser, so figuring out namespace is a matter of a few calls into stdlib.
+
+Another common problem arises when you want to eval “the outermost form”. Like, you stand somewhere inside a function and ask the network REPL to eval that function. Finding start and end of the form to send is again, a hard task unless you have access to Clojure.
+
+Finally, requesting info on symbol (lookup) requires you to identify where that symbol starts/ends. And yes, would be way easier in Clojure.
+
+I’m not sure what the solution here is. Send a file to the network server and ask for parse information back when needed? Send the entire file on each eval?
+
+I guess the reason nREPL has no solution here is because all possible solutions here are cumbersome? Bad? But it doesn’t mean the problem doesn’t exist.
+
+# Auto-require
+
+On the other hand, if I try to eval `(+ 1 2)` in `my-ns` file before loading `my-ns` first, I’ll get an error: namespace is not loaded.
+
+Clients could work around that, sure, but it still feels like an unnecessary dance. If ns could be ignored (e.g. evaluated code does not depend on it at all), eval it as is, otherwise load namespace first? Or always load namespace when client provides it? Or have an option?
+
+# Capturing \*out*
+
+It became a norm to display part of the stdout output in the REPL panel:
+
+
+
+
+
+But if you don’t have REPL panel, where did that output go?
+
+
+
+
+
+In that model, it goes to the same place where any other output goes: to the console that started the server.
+
+In other words, I would like an option to _disable_ output capturing and just let Clojure do what it would do by default.
+
+# What to keep
+
+Good parts of nREPL that I definitely would want to keep:
+
+- interrupt
+- print/buffer-size
+- lookup
+
+I don’t have an opinion on code analysis/suggestions/etc since I don’t use those (well, I do use suggestions from Sublime but those don’t require Clojure server).
+
+# In conclusion
+
+During my development of Clojure Sublimed, I was deciding between using nREPL or writing my own server. I decided on nREPL due to it being a standard but ended up modifying a significant part of it and losing CLJS support in the process.
+
+Because of that, I would love to see a Clojure Network Eval API that could be used to build editor integrations with minimal modifications and interoperable with all existing Clojure servers: JVM, CLJS, Babashka, nbbjs etc.
+
+This is my starting point/idea dump. Join the discussion at [nREPL forum](https://github.com/nrepl/nrepl/discussions/275).
\ No newline at end of file
diff --git a/_posts/2022-07-26-dice-out.md b/_posts/2022-07-26-dice-out.md
new file mode 100644
index 00000000..0729e9f7
--- /dev/null
+++ b/_posts/2022-07-26-dice-out.md
@@ -0,0 +1,142 @@
+---
+layout: post
+title: "GMTK Game Jam 2022: Dice Out"
+category: blog
+summary: "Experience report from participating in latest GMTK Game Jam"
+---
+
+I’ve made a game! Well, a small one and in just 48 hours, but still a game.
+
+You can play it in the browser:
+
+
+
+
+
+Here are my observations.
+
+## Game engine
+
+Using a game engine helps a lot. It solves a lot of problems that you don’t want to deal with in 48 hours or, probably, never.
+
+I chose Godot and liked it a lot: Python-like language seems like a better fit for casual scripting than C#, and apparently it has much better HTML export than Unity.
+
+A game engine also gives you a visual editor for many resources you’ll need for a game: tilesets, sprites, animations, levels, and state machines. You don’t want to tune all that in JSON.
+
+## Game programming
+
+Game programming is quite different from standard web- or whatever type of programming we do.
+
+In games they prefer ECS over OOP (data and behavior separation, components instead of inheritance), encourage global mutable state, like simple solutions, and dislike abstractions.
+
+## Art pipeline
+
+Figma feels a great fit for 2D sprites because of the components. In the case of our dice, I designed dots and faces separately and then combined them into dice. When I wanted to tune colors or shapes, I changed them in one place and they updated everywhere.
+
+
+
+Feels like programming!
+
+## Characters
+
+Any game is better with a character. We made a pretty abstract roll-the-dice-in-a-labyrinth game which by itself would be inherently boring:
+
+
+
+That’s why we introduced Gnargle! Look how he follows the dice with his eyes:
+
+
+
+Much more lively, isn’t it?
+
+## Juice
+
+Juice are small effects that add the feeling of “impact” to the actions. In the example above, it’s a puff of smoke after the dice, a little jump, screen shake, and dynamic camera position.
+
+Compare it to the version without all that:
+
+
+
+Still functional, but much less fun.
+
+## Level design (spoilers!)
+
+I’m pretty proud of our level design. Each level tries to explore an interesting property that’s inherent to the dice as a mathematical object.
+
+I’m going to go through each level explaining its purpose, so if you want first to experience it yourself, open our Itch.io page and play the game. It shouldn’t take you more than 10-15 minutes.
+
+### Level 1
+
+A pretty standard tutorial level that explains how the game works and what your goal is:
+
+
+
+On top of that, it shows that returning dice exactly the same way changes nothing, or that any path is completely reversible.
+
+### Level 2
+
+Still tutorial, it demonstrates that if you take another path you might get a different result. It also shows that the solution from level 1 won’t work, so you have to adapt:
+
+
+
+### Level 3
+
+This teaches an interesting property: rotation around one vertex.
+
+
+
+Notice how 1 travels from the top left to bottom left to bottom right. That’s because only three faces are touching one vertex which makes a cycle of three (1, 2, and 3 in that case). But there are four tiles on the board, so using this rotation you can move any top-facing face to any of the four squares you want.
+
+### Level 4
+
+This is pretty much an extended version of level 3, but now, given more than four tiles, you can get faces that weren’t previously possible:
+
+
+
+### Level 5
+
+This was supposed to demonstrate that now that you can get any face with 3-by-3 tiles, you can get them anywhere. This is the level how it went to the game:
+
+
+
+But after the release I thought of a better way to demonstrate the point:
+
+
+
+### Level 6
+
+This is a tiny aha moment (hopefully). You thought your goal is to move your dice somewhere, but here’s no goal. How come?
+
+
+
+Of course, because the target tile is under your starting position!
+
+### Level 7
+
+To be honest, designing puzzles for this wasn’t easy because you can get absolutely anything given 3x3 and almost anything given 2x2, as we’ve explored before.
+
+
+
+So in this case you don’t get a single 2x2 configuration and instead get a cycle that you need to run multiple times to get the dice in the correct position.
+
+### Level 8
+
+This is in some sense a re-iteration of level 5, again illustrating that you can pretty much get anything anywhere.
+
+
+
+The gotcha in this one is, I guess, that an easy-looking tile configuration doesn’t mean the solution will be equally straightforward.
+
+### Level 9
+
+Finally, level 9, which could be best described with the video:
+
+
+
+Again, this is a non-verbal joke that is communicated entirely through level design.
+
+But beyond that, the missing tile is located so inconveniently that players will (hopefully) figure out a way to predict the value they will get there (which is quite simple, because going in a straight line is a simple 4-cycle).
+
+## Conclusion
+
+Thanks for reading and I hope you liked the game! I for sure had a lot of fun!
\ No newline at end of file
diff --git a/_posts/2023-03-09-clojure-sublimed-3.md b/_posts/2023-03-09-clojure-sublimed-3.md
new file mode 100644
index 00000000..625103b8
--- /dev/null
+++ b/_posts/2023-03-09-clojure-sublimed-3.md
@@ -0,0 +1,471 @@
+---
+layout: post
+title: "Adventures in REPL implementation"
+category: blog
+summary: "Writing Clojure REPL plugin for Sublime Text"
+---
+
+It’s a strange thing to announce, but I wrote [Clojure plugin for Sublime Text](https://github.com/tonsky/Clojure-Sublimed). [Again](https://tonsky.me/blog/sublime-clojure/).
+
+I mean, the previous version worked fine, but it had a few flaws:
+
+- REPL depended on syntax highlighting (yikes!),
+- the whole implementation was in a single file,
+- it was hard to add REPLs.
+
+So, let’s do it again, almost from scratch, and right this time!
+
+# What is REPL?
+
+In a nutshell, REPL consists of three parts: client, server, and communication protocol between them.
+
+Here’s an architectural diagram for you:
+
+
+
+# REPL Client
+
+As you can see from the diagram, REPL clients live in a variety of environments dictated by their host: Java for Idea, Python for Sublime Text, JS for VS Code, etc. In my case, it was Sublime Text, so the environment I was stuck with happened to be Python 3.8.
+
+Of course, writing client-server apps is not hard in any language. Unfortunately for us, REPL client needs to be able to read, speak and actually understand Clojure for a few features:
+
+## Problem 1: Automatic namespace switching
+
+When you go to a file and eval something there, you want it to be run in the context of that file’s namespace. But how to figure out which namespace it is, without parsing Clojure source file?
+
+The level of understanding is non-trivial: there could be multiple namespace declarations, not necessarily at the top of the file:
+
+
+
+The declaration itself could be complex, too, containing comments and/or meta tokens before the actual name:
+
+
+
+## Problem 2: Form boundaries
+
+I want a shortcut that evals “the topmost form” around my cursor. To do so, I need to know where those boundaries are.
+
+Notice how I don’t explicitly “select” what I want to evaluate. Instead, REPL client finds form boundary for me:
+
+
+
+This is tricky, too. For the very least, you can count parens, but even then you’d have to be aware of strings.
+
+To make things harder, Clojure also has reader tags `#inst "2023-02-24"`, metadata `^bytes b` and different weird symbols like `@` or `#'` that are not wrapped in parens but are still considered to be part of the form.
+
+Bonus points for treating technically second-level forms inside `(comment)` as top-level.
+
+All of this requires a really deep understanding of Clojure syntax.
+
+## Problem 3: Indentation and pretty-printing
+
+Clojure Sublimed originally started when I wasn’t happy with what happened when I press “Enter” in Clojure file. Cursor would go to the wrong place, and I (subjectively) was spending too much time correcting it, so I decided to fix that once and for all.
+
+
+
+Indentation is not really a REPL concern, but it’s another part of Clojure Sublimed that requires a model of Clojure code.
+
+Indentation logic re-applied to the whole file is formatting, so I got this one for free (both follow [Better Clojure formatting](https://tonsky.me/blog/clojurefmt/) rules).
+
+
+
+Finally, there’s a question of pretty printing, which is basically indentation + deciding where to put line breaks. Normally this would be done on Clojure side, but doing it on a client has clear advantages:
+
+- you have to send less data when transferring evaluation results (no need to send spaces at the beginning of the line),
+- it works for every Clojure REPL the same,
+- it can adjust for your current editor configuration instead of some arbitrary server-side number like 80 characters.
+
+
+
+ Wrapping on current window width
+
+
+Another upside is that I can adjust pretty-printing rules to my liking, of course.
+
+# Clojure parser in Python
+
+So, client lives inside your code editor and needs to understand Clojure _before_ it starts communicating with it. Meaning, without Clojure runtime. Meaning, we had to parse Clojure in Python!
+
+This is where things get hard because support for libraries, especially native ones, is not great in Sublime Text. Meaning, pure Python implementation!
+
+I was put off by that task for a long time because it felt enormous. In the first Clojure Sublimed version, I deduced this information from syntax highlighting (Sublime Text already parses your source code for highlighting, and you can kind of access the results of that). But this time I wanted to make things right.
+
+And, in fact, it turned out not to be all that bad! Clojure, like any Lisp, _is_ relatively easy to parse. This is the entire grammar:
+
+
+
+Full source at [GitHub](https://github.com/tonsky/Clojure-Sublimed/blob/master/cs_parser.py).
+
+Some notable details below.
+
+## Parser: Cutting Corners
+
+I cut some corners to write less code and get max performance by e.g. not distinguishing between numbers, keywords, symbols — they are all just tokens. It’s probably not hard to add them, but I don’t really need them for what I do, so they are not there.
+
+## Parser: Performance
+
+I haven’t spent any serious time trying to optimize the parser. In its current state, it can go through clojure.core (enormous 8000 loc file) in 170ms on my M1 Mac.
+
+Clojure itself does the same in roughly 30-50 ms, though, so there’s definitely potential for improvement.
+
+## Parser: Incrementality
+
+Should be possible one day :) So far performance is good enough to re-parse on each “enter” keypress.
+
+## Parser: Testing
+
+Parse trees could be quite hard to navigate, and twice as hard to compare.
+
+Parsing also requires _a lot_ of tests to get everything right and to avoid regressions. So having a nice and ergonomic way to write and inspect tests was super important.
+
+I ended up copying test syntax from tree-sitter.
+
+
+
+Test runner just compares the actual result with the expected one string-wise and if they are different, reports an error in a nice to comprehend table:
+
+
+
+Having this early on saved me a ton of time and I am 100% happy I made that investment.
+
+## Parser: Error recovery
+
+Parsing valid Clojure code is quite easy. But code in the process of editing is not always correct, even syntactically. That means that our parser has to work around errors somehow!
+
+So I decided to see how people smarter than me do it and found this:
+
+> In the yacc and bison parser generators, the parser has an ad hoc mechanism to abandon the current statement, discard some parsed phrases and lookahead tokens surrounding the error, and resynchronize the parse at some reliable statement-level delimiter like semicolons or braces.
+
+So yeah, I guess no beautiful theory on error recovery.
+
+For our purposes, though, it was quite simple: see something that you don’t understand? That must be an error. Most stuff gets consumed as a symbol or a number, though, so these were rare.
+
+We did accept some invalid programs as valid, but that’s okay for our use case: find expression boundaries, throw it over the fence, and let Clojure work out the rest of the details.
+
+## Parser: Accidentally quadratic
+
+One funny thing happened during testing: I noticed that sometimes the parser was becoming ultra-slow on moderately-sized files. Like, seconds instead of milliseconds. That means I had quadratic behavior somewhere.
+
+
+
+And that was indeed the case. The first version of the parser, roughly, was parsing parens/brackets/braces like this:
+
+```
+Seq(Char("["),
+ Repeat(Choice('_gap', '_form')),
+ Char("]"))
+```
+
+This basically means: if you see an opening bracket, consume forms and whitespace inside as long as you can, and in the end, there must be a closing bracket.
+
+Well, what if it’s not there? That means it wasn’t a `'brackets'` form in the first place! This is technically correct, but also means we have to mark the opening bracket as an error and then _re-parse everything inside it again_. That’s your quadratic behavior right here!
+
+
+A simple change got rid of this problem:
+
+```
+Seq(Char("["),
+ Repeat(Choice('_gap', '_form')),
+ Optional(Char("]")))
+```
+
+Technically, this accepts incorrect programs. In practice, though, it works exactly as we need for indentation: we only care about opening parens up to the point of the cursor and don’t really care what happens after it.
+
+## Parser: Conclusion
+
+Writing the parser was very fun! So many little details to figure out and get right, but in the end, when everything snaps into place, it’s so satisfying!
+
+I also now understand why Lisps were so popular back in the day: they are really made for ease of implementation. Hacking together an entire parser from scratch in a weekend — can you imagine it for something like C++ or Python?
+
+Anyways, if you need Clojure parser in Python, take a peek at [my implementation](https://github.com/tonsky/Clojure-Sublimed/blob/master/cs_parser.py) — maybe it’ll help you out!
+
+# Protocol
+
+Let’s move to the second part of our architecture: the communication channel.
+
+How does a server talk to a client? Die-hard Clojure fans would answer immediately: EDN! But it’s not that simple.
+
+Yes, EDN is the simplest thing for Clojure users. But what about the rest of the world? Don’t forget that on the other side there’s an arbitrary platform and, despite Rich’s best efforts, EDN is not as widespread as we’d like.
+
+## Just send... forms?
+
+This is what `clojure.core.server/repl` does. Basically, it’s the same interactive experience as with command-line REPL, but over a socket:
+
+
+
+Not machine-friendly at all.
+
+## Half-EDN
+
+Type in forms, receive EDN-formatted output. `clojure.core.server/io-prepl` does that:
+
+
+
+Half machine-friendly and you have to be able to parse EDN.
+
+## JSON + EDN
+
+Following the half-EDN example, our protocol doesn’t have to be symmetric, either. If we make ease of implementation our first priority, we can go crazy:
+
+- Client sends EDN, which is easy to parse in Clojure,
+- Server sends JSON, which could be parsed with Python stdlib.
+
+Not elegant, but, you know, gets the job done. In both cases, messages are simple enough to be composed with string concatenation, so we only really care about parsing.
+
+The only problem I have with this solution is that it offends my sense of beauty.
+
+## Bencode
+
+nREPL also had this problem: a common denominator for multiple clients in all possible languages. Their answer? Bencode.
+
+Bencode is a simple binary encoding developed for BitTorrent. And when I say simple, I mean _very_ simple. Yes, simpler than JSON.
+
+This is the entire Bencode grammar:
+
+```
+number = '0' / '-'? [1-9][0-9]*
+int = 'i' number 'e'
+list = 'l' value* 'e'
+dict = 'd' (value value)* 'e'
+string = length ':' bytes
+length = '0' / [1-9][0-9]*
+value = int / list / dict / string
+```
+
+And here are some actual messages when communicating with nREPL server:
+
+
+
+Bencode is not supported out of the box by either Python or Clojure, but implementation easily fits in 200 LoC.
+
+Problem? It’s binary. Unfortunately, you can’t run binary protocols on top of Socket Server, only the text ones. So to be able to use bencode you’ll have to start your own server.
+
+Clojure Sublimed uses bencode for connecting to nREPL servers.
+
+## MessagePack
+
+MessagePack is beautiful, exactly as I would’ve designed a compact binary serialization format. Everything is length-prefixed, super-simple to implement and you can support only parts that you actually use.
+
+But it’s binary, so can’t be used on top of Socket Server, and I’m not prepared to write my own REPL server yet.
+
+Consider voting for [this issue](https://clojure.atlassian.net/browse/CLJ-2752) and the situation might change! I believe Clojure deserves binary REPLs as much as text-based ones.
+
+## EDN both ways
+
+A lucky coincidence saved me here. Remember the first part where I was writing Clojure parser? Guess what? Since EDN is a subset of Clojure, my parser also can parse EDN well enough to understand REPL server responses!
+
+This is what my upgraded Socket Server REPL looks like on the wire:
+
+
+
+Yes, it looks like nREPL over EDN.
+
+No, it’s not exactly nREPL, it’s subtly different (see the server breakdown below), so there can be more chaos.
+
+Did I invent another wheel? Maybe. But it’s a good wheel and it suits my needs well.
+
+## A note on message boundaries
+
+The tricky part of EDN-on-the-wire? How to separate messages.
+
+Clojure has a streaming parser: it consumes data from socket char-by-char and parses it as it goes until it reads a complete form. That’s why, for example, you can’t evaluate something like `(+ 1 2` in the REPL, no matter how many times you press Enter.
+
+But my Python parser wasn’t streaming :( You give it a string, it’ll parse it. But it can’t tell you how much of that string to read from a socket. If only TCP was message-oriented — one can dream!
+
+So the solution was... split on newlines :) Lucky for me, the default Clojure printer escapes newlines in strings, so it can’t occur inside the message.
+
+EDN doesn’t exactly forbid newlines, though, so let’s hope they won’t suddenly start to appear one day.
+
+# Server
+
+Finally, the third and final part of our architecture: the server. When I only started learning about Clojure and Lisps, I imagined that REPL is literally that:
+
+
+
+Because of that ignorance, it was hard for me to understand why there are different REPL implementations and why you need to “implement” REPL at all.
+
+Let’s go from the simplest case to more complex ones.
+
+## Naive REPL
+
+Funny enough, the function I showed you above works:
+
+
+
+It’s very fragile, though: it’ll die on the first exception.
+
+It also doesn’t do many things which you’ll see more sophisticated REPLs provide.
+
+## clojure.main/repl
+
+This is the REPL you get when you run `clj` or `clojure` command-line utility.
+
+It works essentially the same, but does a little bit of extra work for you:
+
+First and most notably, it prints a command prompt that displays the current namespace:
+
+
+
+Which you can actually customize to your liking:
+
+
+
+Then, it catches and prints exceptions, so your REPL doesn’t die when you make a mistake:
+
+
+
+It also stores the last calculated values in special `*1`..`*3` dynamic vars and the last exception in `*e`. These variables do not exist outside of REPL:
+
+
+
+Another convenience that default REPL does is requiring some stuff from `clojure.repl` and `clojure.pprint`:
+
+
+
+Were you wondering where `(doc)` in REPL comes from? Now you know.
+
+Finally, it isolates vars like `*ns*` or `*warn-on-reflection*` so that when you `set!` them in your REPL session it doesn’t alter their root bindings.
+
+Quite a bit of nuance, huh? With all that, `clojure.main/repl` is still considered very basic. There’s more stuff you can do!
+
+## clojure.core.server/repl
+
+Basically the same as `main/repl`, but for the access over the network.
+
+The only difference is output. If your entire program IS the REPL, you don’t have to do anything special with it.
+
+But if you are connecting dynamically to a working program, things get trickier. Where should `(println "Hello")` print?
+
+If it prints to stdout of the process, you won’t see it in your REPL. It’ll go to wherever the server process redirects its standard output.
+
+So what Server REPL does is it redefines `*in*`/`*out*`/`*err*` to socket streams instead of process’s stdout and sends to you what _you_ print over the network. Everybody gets their own stdout!
+
+
+
+Really tricky stuff to figure out, but essential to understand if you consider yourself an advanced Clojure REPL user.
+
+The rest is the same. Server REPL literally calls into `main/repl` after rebinding `*in*`/`*out*`/`*err*`.
+
+## clojure.core.server/io-prepl
+
+pREPL is Clojure team’s answer to nREPL and critique that Clojure Socket REPL is not machine-friendly. It’s basically `server/repl` but with EDN-formatted output:
+
+
+
+pREPL consumes raw Clojure forms but outputs EDN-structured data.
+
+In terms of what it does for you, it also formats exceptions (not in a Clojure-aware way, unfortunately) and synchronizes your output so that two threads can’t print simultaneously. But that’s about it.
+
+The main problem with pREPL is that it’s based on EDN and, thus, aimed at Clojure clients first and foremost.
+
+## nREPL
+
+nREPL is a third-party server started by Chas Emerick and lately adopted by Bozhidar Batsov. It’s a separate library that you have to add to your project and start the server yourself.
+
+As Rich Hickey put it, ”[nREPL is not a REPL, it’s remote evaluation API](https://nextjournal.com/mk/rich-hickey-on-repls)”. He’s not wrong, but I think that’s exactly what tooling authors need: remote eval API, not interactive console.
+
+First, nREPL is machine-friendly both ways. It receives bencode-d data and sends bencode-d data back.
+
+Second, it walks an extra mile for you:
+
+- Its `eval` optionally accepts file name and position in that file, so that stack traces would contain the correct position.
+- It limits the size of the output to a user-provided threshold, saving you from printing infinite sequences which are not rare in Clojure.
+- It provides interruption for already executing evals.
+- Some of its functions like `lookup` and `load-file` solve problems that their Clojure alternatives don’t.
+- Some are just conveniences like `completions`.
+- It’s extensible, allowing you to create your own operations.
+
+All this stuff is very useful and doesn’t come “naturally” with naive REPL implementations.
+
+The downside? You need to add nREPL server dependency to your app. It also has a noticeable startup cost (~500ms on my machine).
+
+## Extended nREPL
+
+Since nREPL is extensible, one can extend it to do even more. That’s what the first version of Clojure Sublimed did and still does. Including:
+
+- Formating stack traces in a Clojure-aware way and sending them back with errors:
+
+
+
+- Parallel evaluation and execution time:
+
+
+
+It worked well for me for 1.5 years, but still, you know, nREPL dependency, startup time, NIH syndrome. I wanted to give REPL a shot on my own.
+
+## REPL, upgraded
+
+Even the simplest REPL still has the full power of Clojure in it! We can start with something very basic, like `server.repl`, send our own server’s code to it first thing after connecting, and then take control over stdin/stdout and start serving our own protocol with our own execution model.
+
+This is called “upgrading” your REPL and that’s how Christophe Grand’s Unrepl works, for example. The beauty of it is zero dependencies: you only need Clojure and nothing more. Everything you need you bring with you.
+
+In our case, it looks like this. First, we send a lot of Clojure code (unformatted, because machine doesn’t care):
+
+
+
+Then, we receive this:
+
+
+
+Which basically means “Yes, I’ve heard you”.
+
+This is all happening inside basic `server/repl`. It looks messy because it was designed for human consumption (eye-balling), and we don’t even try to interpret it. We just cross our fingers and hope everything we sent works.
+
+At this point, we’re ready to “upgrade” our REPL. This is how we do it:
+
+
+
+`(repl)` is a function we defined in our initial payload. `{"tag" "started"}` is the first message of our own protocol. I really, really, really hope here that it will not be messed up by other output (printing in Socket Server is not synchronized, and everyone who worked with Clojure REPL in the terminal knows how often it messes up your output).
+
+After the client sees `{"tag" "started"}` somewhere in the socket, it considers the upgrade to be finished and now works in our own nREPL-like EDN-based protocol.
+
+## Clojure Sublimed REPL Server
+
+Our upgraded Clojure Sublimed REPL does all the same basic stuff that nREPL does. The only practical difference for clients is batch evaluation: send multiple forms together (e.g. when evaluating the whole buffer) and get separate results for each one.
+
+nREPL eval-buffer:
+
+
+
+Clojure Sublimed eval-buffer:
+
+
+
+Under the hood, though, it’s a completely new REPL. It sits on top of Socket Server, yes, but it has its own evaluation model and its own protocol. It’s clean, minimal, fast to load, and works much better with Clojure Sublimed client than nREPL.
+
+I don’t want to release yet separately from Clojure Sublimed (yet?), but, you know, take a peek [at the implementation](https://github.com/tonsky/Clojure-Sublimed/blob/master/src_clojure/clojure_sublimed/socket_repl.clj) anyway.
+
+## Your own REPL!
+
+The original version of Clojure Sublimed (client) was organized quite poorly and adding new REPLs was problematic.
+
+New, refactored Clojure Sublimed was designed to be easy to extend. Out of the box, we ship with these now:
+
+- JVM nREPL which installs a few extra middlewares.
+- (new) Raw nREPL for non-JVM environments (babashka, etc).
+- Shadow-CLJS nREPL which (now) adapts better to shadow-cljs quirks.
+- (new) JVM Socket REPL which works on top of bare-bone Clojure Socket Server.
+
+And there could be more! If you are interested, let me know, or, better, jump in with a PR! I promise it should be much easier now. I even wrote docstrings everywhere at some places :)
+
+# Conclusion
+
+So, Clojure Sublimed v3 is out there. To sum up the major differences:
+
+- REPL doesn’t depend on syntax highlighting,
+- new JVM Socket REPL,
+- easier to add new REPLs,
+- client-side pretty-printer,
+- faster indenter and formatter.
+
+As always, you can get the new version in [Package Control](https://packagecontrol.io/packages/Clojure%20Sublimed) or on Github:
+
+
+
+Let me know what you think! Issues are open :) And happy Clojur-ing!
+
+# You were going to ask anyway
+
+Color scheme: [Niki Berkeley](https://github.com/tonsky/sublime-color-schemes/).
+
+The font on screenshots: [Berkeley Mono](https://berkeleygraphics.com/typefaces/berkeley-mono/).
\ No newline at end of file
diff --git a/_posts/2023-04-29-humble-state.md b/_posts/2023-04-29-humble-state.md
new file mode 100644
index 00000000..3afd41c8
--- /dev/null
+++ b/_posts/2023-04-29-humble-state.md
@@ -0,0 +1,591 @@
+---
+layout: post
+title: "Humble Chronicles: State Management"
+category: blog
+summary: "Search for the best state management solution for Humble UI"
+---
+
+Recently I’ve been trying to improve state management and component API in Humble UI. For that, I’ve tried to read and compile all the possible known approaches and synthesize something from them.
+
+I haven’t decided on anything for Humble UI yet, let’s say I’m in an experimenting phase. But I think my notes could be useful to quickly get a birds-eye overview of the field.
+
+This is a compilation of my research so far.
+
+# Object-oriented user interface (OOUI)
+
+Classic UIs like Swing, original native Windows/macOS APIs and the browser’s DOM are all implemented in an OOP manner: components are objects and they have methods: `addChild`, `removeChild`, `render`. That fits so well, actually, that you might think OOP was invented specifically for graphical UIs.
+
+The problem with that approach is code duplication: you have to define both how your initial UI is constructed and how it will change in the future. Also, amount of transitions scales as N² compared to the amount of states, so at some point you either get overwhelmed or miss something.
+
+Nevertheless, OOUIs show great performance, a very straightforward programming model and are widely used everywhere.
+
+
+
+# Humble UI — current state
+
+Given the above, it’s only natural that Humble UI started with the OOP paradigm. Yes, we have stateful widgets and component instances, not functions or classes.
+
+Look, mutable fields and inheritance! In Clojure!
+
+```
+(defparent AWrapper [child ^:mut child-rect]
+ protocols/IComponent
+ (-measure [this ctx cs]
+ (when-some [ctx' (protocols/-context this ctx)]
+ (core/measure child ctx' cs))))
+
+(core/deftype+ Clip []
+ :extends core/AWrapper
+
+ protocols/IComponent
+ (-draw [_ ctx rect ^Canvas canvas]
+ (canvas/with-canvas canvas
+ (canvas/clip-rect canvas rect)
+ (core/draw child ctx rect canvas))))
+```
+
+These objects can lay out, draw themselves and handle events, but in true Clojure fashion, they can’t be modified.
+
+I mean, theoretically, sure, I could add something like `setChildren` and the like, but what’s the point if we are not going in that direction anyway?
+
+But wait! — you’d say. I’ve certainly seen Humble UI apps modifying themselves!
+
+
+
+And you’ll be right. There’s a special type of component, `dynamic`, that doesn’t have a fixed list of children. Instead, it takes a function that generates and caches them based on its inputs. When inputs change, old children are thrown away and new ones are generated.
+
+In this example, when `*clicks` changes, a new label will be created and the old one will be thrown away.
+
+```
+(def *clicks
+ (atom 0))
+
+(def app
+ (ui/dynamic _ [clicks @*clicks]
+ (ui/label (str "Clicks: " clicks))))
+```
+
+You can get quite far with that approach. However, not far enough. The problem with dynamic is that it throws away the entire subtree, no matter which parts have changed. Consider
+
+```
+(ui/dynamic _ [p @*p]
+ (ui/padding p
+ (ui/rect (paint/fill 0xFFF3F3F3)
+ (ui/label "Label"))))
+```
+
+In this example, only `ui/padding` component needs to be re-created, but its children would be thrown away and re-created, too. Sometimes it can be fixed, actually, by writing it this way:
+
+```
+(let [body (ui/rect (paint/fill 0xFFF3F3F3)
+ (ui/label "Label"))]
+ (ui/dynamic _ [padding @*padding]
+ (ui/padding padding
+ body)))
+```
+
+Remember — components are values, so the `body` reference will stay the same and will be captured by dynamic.
+
+This is both good and bad: it works, but it’s kinda backward (you wouldn’t want to write your UI this way).
+
+It also creates very confusing “owning” semantics. Like, who should “unmount” the `body` in that case? Should it be `padding`? Or `dynamic`? Actually, it can be neither of them, because `body` outlives them both, and they have no way of knowing this.
+
+Why is it a problem? Stateful components. If I throw away and re-create the text field, for example, it’ll lose selection, cursor position, scroll position, etc. Not good.
+
+Funnily enough, current implementation of text field asks you to hold its state because it has nowhere to put it reliably.
+
+But overall, we managed to get quite far with this approach and polish some stateful components, so I don’t consider it a waste.
+
+# Declarative UIs
+
+So at some point, programmers decided: we’ve had enough. Enough with OOUI, we don’t want to write
+
+```
+window.add(button);
+window.show();
+```
+
+anymore. We want comfort!
+
+And that’s how declarative UIs were born. The idea is that you describe your UI in some simpler language, bind it to your data, and then the computer goes brrr and displays it somehow.
+
+Well, why not? Sounds good, right?
+
+This is what people have come up with.
+
+## Templates
+
+You describe your UI in XML/PHP/HTML/what have you, sprinkle special instructions on top, give it to a black box and it magically works!
+
+Example: Svelte
+
+```
+
+
+
+```
+
+The upsides of this approach are that language is probably very simple and very declarative, and also that you can do a lot of optimizations/preprocessing before turning it into UI. Maximum declarativity!
+
+The downsides are, of course, that you have to work in a second, “not real” language, which is usually less powerful, harder to interop with, and less dynamic.
+
+Probably because of the limitations of templates, the MVVM pattern was born: you prepare your data in a real language and get it into a shape that can be consumed by a simple template.
+
+## Procedural DSLs
+
+You call builder functions in the right context and your UI framework somehow tracks them and turns them into components. Examples would be Dear Imgui or Jetpack Compose:
+
+```
+ImGui::Text("Hello, world %d", 123);
+if (ImGui::Button("Save"))
+ MySaveFunction();
+ImGui::InputText("string", buf, IM_ARRAYSIZE(buf));
+ImGui::SliderFloat("float", &f, 0.0f, 1.0f);
+```
+
+Notice that you don’t explicitly “add” or “return” components anywhere. Instead, just calling builders is enough. Almost procedural style :)
+
+The upside: the code is very compact.
+
+The downside: you can’t work with components as values. Like, can’t put them in an array and reverse it, or take the first 10, or something like that. Your builders become lambdas, and lambdas are opaque: you can’t do much about them except call. Call sites start to matter, where they normally don’t: what looks like a normal program has a few non-obvious gotchas.
+
+## Value-oriented DSLs
+
+In value-oriented DSLs, you return values from your components. Like in React:
+
+```
+export default function Button() {
+ return (
+
+ );
+}
+```
+
+Notice that you return the button, not call some constructor that adds it to the form. How you get — doesn’t matter. The only thing that matters is the actual value you return. You can do what you want with it: use, ignore, compare, use twice, cache, throw away.
+
+This is also the most natural way to write programs in my opinion: pure functions taking and returning data. It also suits Clojure the best, so we’ll probably want something like that for Humble UI.
+
+Note also that `