Full Width [alt+shift+f] Shortcuts [alt+shift+k]
Sign Up [alt+shift+s] Log In [alt+shift+l]
12
In my note taking application Edna I’ve implemented unorthodox UI feature: in the editor a top left navigation element is only visible when you’re moving the mouse or when mouse is over the element. Here’s UI hidden: Here’s UI visible: The thinking is: when writing, you want max window space dedicated to the editor. When you move mouse, you’re not writing so I can show additional UI. In my case it’s a way to launch note opener or open a starred or recently opened note. Implementation details Here’s how to implement this: the element we show hide has CSS visibility set to hidden. That way the element is not shown but it takes part of layout so we can test if mouse is over it even when it’s not visible. To make the element visible we change the visibility to visible we can register multiple HTML elements for tracking if mouse is over an element. In typical usage we would only we install mousemove handler. In the handler we set isMouseMoving variable and clear it after a...
a week ago

Improve your reading experience

Logged in users get linked directly to articles resulting in a better reading experience. Please login for free, it takes less than 1 minute.

More from Krzysztof Kowalczyk blog

2025-06-22 Sun: Ban std::string

The use of std::string should be banned in C++ code bases. I’m sure this statement sounds like heresy and you want to burn me at stake. But is it really controversial? Java, C#, Go, JavaScript, Python, Ruby, PHP: they all have immutable strings that are basically 2 machine words: a pointer to string data and size of the string. If they have an equivalent of std:string it’s something like StringBuilder. C++ should also use immutable strings in 97% of situations. The problem is gravity: the existing code, the culture. They all pull you strongly towards std::string and going against the current is the hardest thing there is. There isn’t a standard type for that. You can use newish std::span<char*> but there really should be std::str (or some such). I did that in SumatraPDF where I mostly pass char* but I don’t expect many other C++ code bases to switch away from std::string.

yesterday 1 votes
Compressing for the browser in Go

Comparing gzip, brotli and zstd compression in Go. When a modern browser sends a HTTP request to a web server, it includes the following header: Accept-Encoding: gzip, deflate, br, zstd This tells the server that the response can be compressed using one of the following compression algorithms: gzip and deflate (oldest, very similar), brotli and zstandard. If your server is written in Go, which algorithm should you use? I wondered that myself so I decided to test it. I measured compression time and the size of a compressed data. In benchmarking it’s important to pick a representative sample. I’m currently working on Edna - a scratchpad and note-taker for developers and power users, like myself (this very article was written in Edna). It’s an SPA written in Svelte. After bundling and optimizing I get ~640 kB index.js which is a good test case for a real-life, production, optimized JavaScript. Here are results of compression test: found index-YpZ0JZes.js of size 639861 (640 kB) compressing with gzip compressing with brotli: default (level 6) compressing with brotli: best (level 11) compressing with zstd level: better (3) compressing with zstd level: best (4) gzip: 200746 (201 kB) in 12 ms brotli default: 206298 (206 kB) in 18 ms brotli best: 183887 (184 kB) in 977 ms zstd better: 106458 (106 kB) in 3 ms zstd best: 93966 (94 kB) in 14 ms the winner is: zstd level 3 zstd level 3 is a clear winner: it achieves much better compression ratio than gzip/brotli and much faster speeds. If you want the absolute smallest files, zstd level 4 has a slight edge over level 3 but at a cost of much higher compression times. the code We use the following Go libraries: github.com/andybalholm/brotli for brotli github.com/klauspost/compress for gzip and zstd The code of benchmark function: func benchFileCompress(path string) {   d, err := os.ReadFile(path)   panicIfErr(err)   var results []benchResult   gzipCompress := func(d []byte) []byte {     var buf bytes.Buffer     w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression)     panicIfErr(err)     _, err = w.Write(d)     panicIfErr(err)     err = w.Close()     panicIfErr(err)     return buf.Bytes()   }     zstdCompress := func(d []byte, level zstd.EncoderLevel) []byte {     var buf bytes.Buffer     w, err := zstd.NewWriter(&buf, zstd.WithEncoderLevel(level), zstd.WithEncoderConcurrency(1))     panicIfErr(err)     _, err = w.Write(d)     panicIfErr(err)     err = w.Close()     panicIfErr(err)     return buf.Bytes()   }   brCompress := func(d []byte, level int) []byte {     var dst bytes.Buffer     w := brotli.NewWriterLevel(&dst, level)     _, err := w.Write(d)     panicIfErr(err)     err = w.Close()     panicIfErr(err)     return dst.Bytes()   }   var cd []byte   logf("compressing with gzip\n")   t := time.Now()   cd = gzipCompress(d)   push(&results, benchResult{"gzip", cd, time.Since(t)}) logf("compressing with brotli: default (level 6)\n")   t = time.Now()   cd = brCompress(d, brotli.DefaultCompression)   push(&results, benchResult{"brotli default", cd, time.Since(t)})   logf("compressing with brotli: best (level 11)\n")   t = time.Now()   cd = brCompress(d, brotli.BestCompression)   push(&results, benchResult{"brotli best", cd, time.Since(t)})   logf("compressing with zstd level: better (3)\n")   t = time.Now()   cd = zstdCompress(d, zstd.SpeedBetterCompression)   push(&results, benchResult{"zstd better", cd, time.Since(t)})   logf("compressing with zstd level: best (4)\n")   t = time.Now()   cd = zstdCompress(d, zstd.SpeedBestCompression)   push(&results, benchResult{"zstd best", cd, time.Since(t)})   for _, r := range results {     logf("%14s: %6d (%s) in %s\n", r.name, len(r.data), humanSize(len(r.data)), r.dur)   } }

yesterday 1 votes
2025-06-21 Sat: Thoughts on C/C++ killer

There are several programming languages that are “better C/C++”: zig, odin, jai, D. The problem is adoption: it’s hard to break from all the code already written in C/C++. It’s too expensive to rewrite large code bases in new language. My idea on how to improve adoption: perfect integration into C/C++ ecosystem. It should be possible to call C/C++ code from new language. That mostly works in those languages, although typically not as easily as it could be. But it should also be possible to integrate the other way: to e.g. add just one .zig file to C code base and be able to call zig functions from C. This would require some code generation i.e. generating .h files with C declaration for zig code and compiling .zig code to C-compatible object file. That would allow incremental adoption and incremental porting of C to new language. It would still be difficult and time consuming but at it wouldn’t require cost-prohibitive porting of everything at once. activity SumatraPDF : use temporary allocator few more times blog: writing more posts

2 days ago 2 votes
Implementing nested context menu in Svelte 5

I was working on Edna, my open source note taking application that is a cross between Obsidian and Notational Velocity. It’s written in Svelte 5. While it runs in the browser, it’s more like a desktop app than a web page. A useful UI element for desktop-like apps is a nested context menu. I couldn’t find really good implementation of nested menus for Svelte. The only good quality library I found is shadcn-svelte but it’s a big dependency so I decided to implement a decent menu myself. This article describes my implementation clocking at around 550 lines of code. Indecent implementations Most menu implementations have flaws: verbose syntax not nested sub-menus hard to access (they go away before you can move mouse over it) show partially offscreen i.e. not fully visible no keyboard navigation My implementation doesn’t have those flaws but I wouldn’t go as far as saying it’s a really, really good implementation. It’s decent and works well for Edna. Avoiding the flaws Verbose syntax I find the following to be very verbose: <Menu.Root> <Menu.Items> <Menu.Item> <Menu.Text>Open</Menu.Text> <Menu.Shortcut>Ctrl + P</Menu.Shortcut> </Menu.Item> </Menu.Items> </Menu.Root> That’s just for one menu item. My syntax My syntax is much more compact. Here’s a partial menu from Edna: const contextMenu = [ ["Open note\tMod + P", kCmdOpenNote], ["Create new note", kCmdCreateNewNote], ["Create new scratch note\tAlt + N", kCmdCreateScratchNote], ["This Note", menuNote], ["Block", menuBlock], ["Notes storage", menuStorage], ]; const menuNote = [ ["Rename", kCmdRenameCurrentNote], ["Delete", kCmdDeleteCurrentNote], ]; contextMenu is a prop passed to Menu.svelte component. The rule is simple: menu is an array of [text, idOrSubMenu] elements text is menu text with optional shortcut, separated by tab \t second element is either a unique integer that identifies menu item or an array for nested sub-menu To ensure menu ids are unique I use nmid() (next menu id) function: let nextMenuID = 1000; function nmid() { nextMenuID++; return nextMenuID; } export const kCmdOpenNote = nmid(); export const kCmdCreateNewNote = nmid(); Disabling / removing items Depending on the current state of the application some menu items should be disabled and some should not be shown at all. My implementation allows that via menuItemStatus function, passed as a prop to menu component. This function is called for every menu item before showing the menu. The argument is [text, idOrSubMenu] menu item definition and it returns current menu state: kMenuStatusNormal, kMenuStatusRemoved, kMenuStatusDisabled. Acting on menu item When user clicks a menu item we call onmenucmd(commandID) prop function,. We can define complex menu and handle menu actions with very compact definition and only 2 functions. Representing menu at runtime Before we address the other flaws, I need to describe how we represent menu inside the component, because this holds secret to solving those flaws. Menu definition passed to menu component is converted to a tree represented by MenuItem class: class MenuItem { text = ""; shortcut = ""; /** @type {MenuItem[]} */ children = null; cmdId = 0; /** @type {HTMLElement} */ element = null; /** @type {HTMLElement} */ submenuElement = null; /** @type {MenuItem} */ parent = null; zIndex = 30; isSeparator = false; isRemoved = false; isDisabled = false; isSubMenu = false; isSelected = $state(false); } A top level menu is an array of MenuItem. text, shortcut and cmdId are extracted from menu definition. isRemoved, isDisabled is based on calling menuItemStatus() prop function. children is null for a menu item or an array if this is a trigger for sub-menu. isSubMenu could be derived as children != null but the code reads better with explicit bool. parent helps us navigate the tree upwards. zIndex exists so that we can ensure sub-menus are shown over their parents. element is assigned via bind:this during rendering. submenuElement is for sub-menu triggers and represents the element for sub-menu (as opposed to the element that triggers showing the menu). And finally we have isSelected, the only reactive attribute we need. It represents a selected state of a given menu item. It’s set either from mouseover event or via keyboard navigation. Selected menu is shown highlighted. Additionally, for menu items that are sub-menu triggers, it also causes the sub-menu to be shown. Implementing nesting A non-nested dropdown is easy to implement: the dropdown trigger element is position: relative the child, dropdown content (i.e. menu), is position: absolute, starts invisible (display: none) and is toggled visible either via css (hover attribute) or via JavaScript Implementing nesting is substantially more difficult. For nesting we need to keep several sub-menu shown at the same time (as many as nesting can go). Some menu implementations render sub-menus as peer elements. In my implementation it’s a single element so I have a only one onmouseover handler on top-level parent element of menu. There I find the menu item by finding element with role menuitem. I know it corresponds to MenuItem.element so I scan the whole menu tree to find matching MenuItem object. To select the menu I use a trick to simplify the logic: I unselect all menu items, select the one under mouse and all its parents. Selecting MenuItem happens by setting isSelected reactive value. It causes the item to re-render and sets is-selected css class to match isSelected reactive value, which highlights the item by changing the background color. Making sub-menus easy to access The following behavior is common and very frustrating: you hover over sub-menu trigger, which cases sub-menu to show up you try to move a mouse towards that sub-menu but the mouse leaves the trigger element causing sub-menu to disappear There are some clever solution to this problem but I found it can be solved quite simply by: delaying hiding of sub-menu by 300 millisecond. That gives the user enough time to reach sub-menu before it disappears showing sub-menu partially on top of its parent. Most implementations show sub-menus to the right of the parent. This reduces the distance to reach sub-menu Specifically, my formula for default sub-menu position is: .sub-menu-wrapper { left: calc(80% - 8px); } so it’s moved left 20% of parent width + 8px. Making menus always visible Context menu is shown where the mouse click happened. If mouse click happened near the edge of the window, the menu will be partially offscreen. To fix that I have aensurevisible(node) function which checks the position and size of the element and if necessary repositions the node to be fully visible by setting left and top css properties. I use it as an action for top-level menu element and call manually on sub-menu elements when showing them. For this to work, the element must have position: absolute. Implementing keyboard navigation To implement keyboard navigation I handle keydown event on top-level menu element and on ArrowUp, ArrowDown, ArrowLeft and ArrowDown I select the right MenuItem based on currently selected menu items. Tab works same as ArrowDown and selects the next menu item. Enter triggers menu command. Recursive rendering with snippets This is actually my second Svelte menu implementation. The first one was in Svelte 4 made for notepad2. Nested menu is a tree which led me to re-cursive rendering via <svelte:self> tag. However, this splits the information about the menu between multiple run-time components. Keyboard navigation is hard to implement without access to the global state of all menu items, which is why I didn’t implement keyboard navigation there. With Svelte 5 we can mount a single Menu component and render sub-menus with recursive snippets. Keyboard shortcuts Menu.svelte only shows keyboard shortcuts, you have to ensure that the shortcuts work somewhere else in the app. This is just as well because in Edna some keyboard shortcuts are handled by CodeMirror so it wouldn’t always be right to have menu register for keyboard events and originate those commands. You can use it I didn’t extract the code into a stand-alone re-usable component but you can copy Menu.svelte and the few utility functions it depends on into your own project. I use tailwindcss for CSS, which you can convert to regular CSS if needed. And then you can change how you render menu items and sub-menus. Potential improvements The component meets all the needs of Edna, but more features are always possible. It could support on/off items with checkmarks on the left. It could support groups of radio items and ability to render more fancy menu items. It could support icons on the left. It could support keyboard selection similar to how Windows does it: you can mark a latter in menu text with & and it becomes a keyboard shortcuts for the item, shown as underlined. Figma used to have a search box at the top of context menu for type-down find of menu items. I see they changed it to just triggering command-palette. References Edna is a note-taking application for programmers and power users written in Svelte 5 you can see the full implementation (and use in your own projects)

3 days ago 3 votes
2025-06-20 Fri: Idea for code editors

Added a daily notes section to the blog. It’s the one you’re reading right now. Data stored in \daily_notes.md file. Trying Obsidian for editing markdown files I’m always looking for that perfect note taking / markdown editor. I’m even writing one (but it’s web based so not useful for files on disk). I wish Obsidian had some way to “star” notes so that they show up at the top of the note list. I’ve implemented that in Edna and it’s useful. idea for code editors A virtual file with parts of other files. When editing code I often have to refer to other parts of the code. For example I need to see definition of the struct I’m using. There is no good way to look at other parts of the code while I’m writing the code. We have bookmarks but you can see only one at a time and when you go to the bookmarked part, you navigate away from the part you were editing. My idea: ability to select part of a file and add it to “virtual” file. Call it “scratch” or whatever. You can add multiple selections (and remove them). The workflow I imagine: in the research phase you look at the code, adding chunks of interest to “scratch” file then you use dual-pane view with the code you edit in one pane and the “scratch” for reference in another. You can write the code while referencing multiple sections of existing code at once activity posted my article about nested svelte menus to reddit tweaked how Edna shows quick access list of notes in the top right. Used to show on mouse move but it was too annoying. Now only shows when mouse is over it, which less discoverable. Also allow duplicate names in history section and show where history starts. This is an example of tweaking the UI based on using the software and getting annoyed by how it works. improved Edna search dialog. I stole design from Visual Studio Code. Only one thing missing: showing number of matches.

3 days ago 2 votes

More in programming

My Copy of The Internet Phone Book

I recently got my copy of the Internet Phone Book. Look who’s hiding on the bottom inside spread of page 32: The book is divided into a number of categories — such as “Small”, “Text”, and “Ecology” — and I am beyond flattered to be listed under the category “HTML”! You can dial my site at number 223. As the authors note, the sites of the internet represented in this book are not described by adjectives like “attention”, “competition”, and “promotion”. Instead they’re better suited by adjectives like “home”, “love”, and “glow”. These sites don’t look to impose their will on you, soliciting that you share, like, and subscribe. They look to spark curiosity, mystery, and wonder, letting you decide for yourself how to respond to the feelings of this experience. But why make a printed book listing sites on the internet? That’s crazy, right? Here’s the book’s co-author Kristoffer Tjalve in the introduction: With the Internet Phone Book, we bring the web, the medium we love dearly, and call it into a thousand-year old tradition [of print] I love that! I think the juxtaposition of websites in a printed phone book is exactly the kind of thing that makes you pause and reconsider the medium of the web in a new light. Isn’t that exactly what art is for? Kristoffer continues: Elliot and I began working on diagram.website, a map with hundreds of links to the internet beyond platform walls. We envisioned this map like a night sky in a nature reserve—removed from the light pollution of cities—inviting a sense of awe for the vastness of the universe, or in our case, the internet. We wanted people to know that the poetic internet already existed, waiting for them…The result of that conversation is what you now hold in your hands. The web is a web because of its seemingly infinite number of interconnected sites, not because of it’s half-dozen social platforms. It’s called the web, not the mall. There’s an entire night sky out there to discover! Email · Mastodon · Bluesky

10 hours ago 2 votes
2025-06-22 Sun: Ban std::string

The use of std::string should be banned in C++ code bases. I’m sure this statement sounds like heresy and you want to burn me at stake. But is it really controversial? Java, C#, Go, JavaScript, Python, Ruby, PHP: they all have immutable strings that are basically 2 machine words: a pointer to string data and size of the string. If they have an equivalent of std:string it’s something like StringBuilder. C++ should also use immutable strings in 97% of situations. The problem is gravity: the existing code, the culture. They all pull you strongly towards std::string and going against the current is the hardest thing there is. There isn’t a standard type for that. You can use newish std::span<char*> but there really should be std::str (or some such). I did that in SumatraPDF where I mostly pass char* but I don’t expect many other C++ code bases to switch away from std::string.

yesterday 1 votes
I'm writing a book!

Over the course of my career, I introduced a couple of engineers into the topic of query engines. Every time, I bumped into the same problem: query engines are extremely academic. Despite the fact that industry has over 40 years of expertise, reading foundational papers and then sort of just googling around is the only way to dive deep. Database courses and seminars from Andy Pavlo definitely help, but they are still targeted at academic audience and require a lot of extra reading to be useful.

yesterday 2 votes
Compressing for the browser in Go

Comparing gzip, brotli and zstd compression in Go. When a modern browser sends a HTTP request to a web server, it includes the following header: Accept-Encoding: gzip, deflate, br, zstd This tells the server that the response can be compressed using one of the following compression algorithms: gzip and deflate (oldest, very similar), brotli and zstandard. If your server is written in Go, which algorithm should you use? I wondered that myself so I decided to test it. I measured compression time and the size of a compressed data. In benchmarking it’s important to pick a representative sample. I’m currently working on Edna - a scratchpad and note-taker for developers and power users, like myself (this very article was written in Edna). It’s an SPA written in Svelte. After bundling and optimizing I get ~640 kB index.js which is a good test case for a real-life, production, optimized JavaScript. Here are results of compression test: found index-YpZ0JZes.js of size 639861 (640 kB) compressing with gzip compressing with brotli: default (level 6) compressing with brotli: best (level 11) compressing with zstd level: better (3) compressing with zstd level: best (4) gzip: 200746 (201 kB) in 12 ms brotli default: 206298 (206 kB) in 18 ms brotli best: 183887 (184 kB) in 977 ms zstd better: 106458 (106 kB) in 3 ms zstd best: 93966 (94 kB) in 14 ms the winner is: zstd level 3 zstd level 3 is a clear winner: it achieves much better compression ratio than gzip/brotli and much faster speeds. If you want the absolute smallest files, zstd level 4 has a slight edge over level 3 but at a cost of much higher compression times. the code We use the following Go libraries: github.com/andybalholm/brotli for brotli github.com/klauspost/compress for gzip and zstd The code of benchmark function: func benchFileCompress(path string) {   d, err := os.ReadFile(path)   panicIfErr(err)   var results []benchResult   gzipCompress := func(d []byte) []byte {     var buf bytes.Buffer     w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression)     panicIfErr(err)     _, err = w.Write(d)     panicIfErr(err)     err = w.Close()     panicIfErr(err)     return buf.Bytes()   }     zstdCompress := func(d []byte, level zstd.EncoderLevel) []byte {     var buf bytes.Buffer     w, err := zstd.NewWriter(&buf, zstd.WithEncoderLevel(level), zstd.WithEncoderConcurrency(1))     panicIfErr(err)     _, err = w.Write(d)     panicIfErr(err)     err = w.Close()     panicIfErr(err)     return buf.Bytes()   }   brCompress := func(d []byte, level int) []byte {     var dst bytes.Buffer     w := brotli.NewWriterLevel(&dst, level)     _, err := w.Write(d)     panicIfErr(err)     err = w.Close()     panicIfErr(err)     return dst.Bytes()   }   var cd []byte   logf("compressing with gzip\n")   t := time.Now()   cd = gzipCompress(d)   push(&results, benchResult{"gzip", cd, time.Since(t)}) logf("compressing with brotli: default (level 6)\n")   t = time.Now()   cd = brCompress(d, brotli.DefaultCompression)   push(&results, benchResult{"brotli default", cd, time.Since(t)})   logf("compressing with brotli: best (level 11)\n")   t = time.Now()   cd = brCompress(d, brotli.BestCompression)   push(&results, benchResult{"brotli best", cd, time.Since(t)})   logf("compressing with zstd level: better (3)\n")   t = time.Now()   cd = zstdCompress(d, zstd.SpeedBetterCompression)   push(&results, benchResult{"zstd better", cd, time.Since(t)})   logf("compressing with zstd level: best (4)\n")   t = time.Now()   cd = zstdCompress(d, zstd.SpeedBestCompression)   push(&results, benchResult{"zstd best", cd, time.Since(t)})   for _, r := range results {     logf("%14s: %6d (%s) in %s\n", r.name, len(r.data), humanSize(len(r.data)), r.dur)   } }

yesterday 1 votes
In Praise of “Normal” Engineers

This article was originally commissioned by Luca Rossi (paywalled) for refactoring.fm, on February 11th, 2025. Luca edited a version of it that emphasized the importance of building “10x engineering teams” . It was later picked up by IEEE Spectrum (!!!), who scrapped most of the teams content and published a different, shorter piece on March […]

4 days ago 7 votes