Hi Adam, Steve and team!
My name is Lukas.
Who am I?
I'm Lukas Gjetting, a 25 year old self-taught software builder from Copenhagen, Denmark. That's the same time zone as Belgium!
I am experienced in making software joyful to build and a breeze to use.
I like the process of building software. I love optimizing it.
While I see myself as very detailed and process-oriented, one of the things I take the most pride in is my ability to ship fast.
Not "ship crap".
Not "ship unfinished things".
Just a strong bias towards action.
I am currently working on BravoShop, a drag-and-drop app builder for Shopify merchants. It's a solo project, though I try to regularly bring in one of my friends (a fellow React dev), to pair on larger projects. As BravoShop's revenue is still not enough to cover my expenses, I also do consulting work for various small companies.
I enjoy the flexibility of doing my own thing. The freedom. Though, I have to admit I miss the regular sparring and bouncing ideas that only comes from being around likeminded geeks.
I had no plans to get a job. But when I saw your job post, I had to apply.
I have worked quite a lot from home. And through that, realized I enjoy being surrounded by other people. That's why I currently work out of a co-working space in Copenhagen.
Just as much as I love learning and shipping, I love setting goals and working towards them.
Last year, I set the goal to reach 1400 ELO on Chess.com - and got it by May. This year my goal is to complete the level 5 physical test for the Danish military (I've translated the chart here). I'm struggling the most with the burpees and the running, though it's recently started to seem possible.
How I got here
I started writing code back in 2011 when I learned that it was possible to make arrows in Minecraft explode on impact (how cool is that?!). Soon after, I got into web development. Turns out people are willing to hand over actual money if you can make their website look a certain way - who would've thought?
At 15 years old, I had completed a series of freelance gigs. Most of them were very small, often less than $100, but some around $1000. What a rush! Making money. From home. Doing a task I enjoyed! It was almost unbelievable.
Since then (just realized it was 10 years ago...) I have gotten deep in to React, React Native and later TypeScript.
When I first started exploring React Native, back in the pre-0.60, manual linking days (shudder), I was amazed. Amazed by the React Native team, who had the audacity to think that React could (and should) power native apps. But also amazed by the tech that allowed React to render actual, real, just-as-intended native views.
What I had previously thought of as the core of React (mapping the component tree to actual DOM elements) was actually the not done by React itself. Rather, it was done by react-dom. And interestingly, react-native was "simply" another "mapping engine" (or reconciler).
Things that excite me
I love learning new stuff. I especially love diving deeper than strictly neccessary to actually understand what happens.
Animation syntax for Tailwind CSS
Would love to work on adding syntax for inline animation definitions.
The current docs state:
Animations by their very nature tend to be highly project-specific.
Agree! I woud even go a step further and say that many animations are only ever used in a single place. Therefore, it would be great if you could define them inline, instead of having to reach for the Tailwind config every time.
Here's an example of how it could look (inspired by the gradient syntax, class names on separate lines for readability):
1<div class="
2 animation
3 animation-from:translate-x-0
4 animation-to:translate-x-12
5 animation-duration-700
6">
7 Hello
8</div>And even:
1<div class="
2 (...)
3 animation-via-30%:translate-x-4
4 animation-duration-700
5 animation-loop
6 animation-alternate
7">
8 Hello
9</div>This would then generate something like the following CSS:
1/*
2 * Combining every animation-from/animation-via-/animation-to should
3 * ensure that the animation name is unique (though if it becomes too long,
4 * it could make sense to calculate a hash instead)
5 */
6@keyframes .animation-from:translate-x-0.animation-via-30%:translate-x-4.animation-to:translate-x-12 {
7 from {
8 /*
9 * We can't use the regular strategy based on CSS variables,
10 * as it seems to break the animation.
11 */
12 transform: translate(0px);
13 }
14
15 30% {
16 transform: translate(1rem);
17 }
18
19 to {
20 transform: translate(3rem);
21 }
22}
23
24.animation-duration-700 {
25 --tw-animation-duration: 700ms;
26}
27
28.animation-loop {
29 --tw-animation-iteration-count: infinite;
30}
31
32.animation-alternate {
33 --tw-animation-direction: alternate;
34}
35
36.animation-from:translate-x-0.animation-via-30%:translate-x-4.animation-to:translate-x-12 {
37 --tw-animation-name: .animation-from:translate-x-0.animation-via-30%:translate-x-4.animation-to:translate-x-12;
38}
39
40.animation {
41 animation:
42 var(--tw-animation-duration)
43 var(--tw-animation-iteration-count)
44 var(--tw-animation-direction)
45 var(--tw-animation-name);
46}Result
The actual styles can be found in /animation-demo.css
Notes
- We use the prefix
animation-instead ofanimate-to avoid colliding with the existing API. - Using
:to separate the animation stop (e.g.animation-from) and the property being animated (e.g.translate-x-0) may not be the most semantically correct way to do it- However, separating with
-would result in an extremely long list of possible class names, making the autocomplete experience worse.
- However, separating with
- If we supported multiple
animation-from:classes on the same element, I think it would be able to serve most use cases
Performance
Last week, while researching react-native-skia, I spent a few hours learning about shaders. It blew my mind that my Macbook, which takes 5-10 seconds to load the type definitions in some TS projects, could run a function more than 50 million times per second! Even with the parallelization applied by the GPU, that's still a mind-numbing amount of raw processing power.
While I'm sure "rewriting in Rust" is not a silver bullet to solve all performance issues (nor is it even the first tool you should reach for), it is an intriguing thought. Before today I had never ventured beyond the rust-lang.org front page.
Psst: This is where I was going to embed a video of me learning Rust. It turned out... boring. Can't really do a re-take of "learning X for the first time", so here it is anyways (my first hour of Rust edited down to 3 minutes): YouTube
Projects and contributions
Open source
To be completely honest, I have not contributed as much to open-source as I would like. However, I always try to be helpful where I can. Whether it's by adding context to existing discussions or writing thoughtful issues.
I have published 3 open source packages, though none of them have seen significant usage (they've all peaked at almost 1K weekly downloads).
eslint-plugin-parentheses-single-line-jsxeslint-plugin-react-native-fontawesome-deep-importsreact-hook-search
While the ESLint plugins are by themselves small and insignificant, they were part of a project I am very proud of:
ClearVoyage: Avoiding resentment during the development process
Right before the pandemic, I joined ClearVoyage (back then known as Wine Dark Sea) full time. I had been doing freelance for them for about 6 months, but they wanted to have my full attention (fair enough).
I joined as the first employee to help the technical co-founder build an MVP of a "voyage management system" (essentially a platform to manage your fleet of ships as they transport goods).
As the team grew to 5 developers, it was clear we needed more structure. We had a hard time managing projects (meaning we shipped less) and our PR reviews were filled with code style nitpicks (meaning we were less happy). Often, we would have reviews with dozens of comments, of which only a few were actually meaningful.
I lead the effort, and eventually the team, to combat this tendency and to improve the way we produced software.
To counteract the growing resentment towards reviews in general, I wrote a bunch of ESLint rules via no-restricted-syntax. This way, style errors were caught before the review, rather than during it (this was before we discovered and truly appreciated Prettier). Additionally, I published eslint-plugin-parentheses-single-line-jsx to NPM, as it allowed us to support auto-fixing.
A few months before leaving ClearVoyage, I also introduced pairing to the team, after discovering Tuple's pair programming guide (IIRC from The Art of Product podcast). It didn't catch on immediately. None of us had any experience with the practice, so the sessions felt forced and unnatural. It never really clicked.
Until we realized: We were already basically pairing.
While we didn't structure it as explicit pairing sessions, we already spent a fair amount of time looking at the same screen, solving problems together. Instead of formalizing the entire process, we decided to keep how it had been. It worked. And now that we were more aware of why it worked, we were able to agree on some best practices: early in the day, no distractions, single task.
The combination of:
- Cleaner reviews
- Improved "pairing" practices
- A handful of new checks that would run against PRs via GitHub Actions
- Improved PR visibility via Toast
- An internal knowledge base via Slab
... resulted in a smoother, less frustrating development process.
And a much, much happier team.
BravoShop
On the technical side, BravoShop uses Node, tRPC, Vite, React and React Native. Because of React Native's cross-platform capabilities (h/t react-native-web), I'm able to render the actual app as part of the builder. Instead of just having a mock-up like many similar tools. All this without making compromises on the native feel of the resulting app.
One of the coolest parts of BravoShop was a recent refactor I made to how app settings are stored. As you can probably imagine, the platform contains numerous settings that users can change to customize their app.

Previously, all settings were stored as a part of the App model (think storing every user setting on the User model). This served me well early on, as there were a limited number of settings. However, as the App model grew, so did the underlying table.
While I wasn't running into any performance issues, it wasn't nice to work with. Having 100 columns in a single table doesn't feel right. Also, it meant that a migration had to run for every new app setting I wanted to add.
Instead, I refactored it into a property bag (or EAV model). Simple enough, right? Sure! Except I wanted it to be type safe.
This required coming up with a way to (1) ensure type safety when saving, (2) ensure type safety when fetching and most importantly (3) a developer-friendly way to define new app settings.
To validate the values, I decided to use Zod. I already used Zod to validate request parameters, so no new dependencies were required.
Starting with the way to define app settings, I decided on a simple object satisfying the type Record<string, [ZodSchema, defaultValue]>.
1const AppSettingSchemas = {
2 primaryColor: [Zod.string(), DEFAULT_APP_PRIMARY_COLOR],
3 logoIconSource: [Zod.string().nullable(), null],
4 // (...)
5};From this single object, inferring the necessary types was simple.
1type AppSettingKey = keyof typeof AppSettingSchemas;
2type AppSettings = {
3 [key in AppSettingKey]: Zod.infer<typeof AppSettingSchemas[key][0]>
4};And to verify that all settings had valid default values, I checked that each default value extended the schema output.
1type DefaultsType = {
2 [key in keyof typeof AppSettingSchemas]: typeof AppSettingSchemas[key][1]
3};
4
5// This will fail if any of the defaults are not set to a valid value.
6// You can see which defaults are invalid by hovering over the error.
7const _defaultsTest: {
8 [key in keyof DefaultsType]:
9 DefaultsType[key] extends AppSettings[key] ?
10 undefined :
11 'Invalid default value';
12} = {} as Record<AppSettingKey, undefined>;Adding new app settings is as easy as adding a new line to the AppSettingSchemas object - easy peazy!
The simple structure also meant that (1) and (2) were extremely simple. We simply provide a getter and a setter, which run every value through the relevant Zod schema.
I am very satisfied with just how simple the AppSettingSchemas definition is. I have since added many new settings, and it has been just as easy as expected.
So, why me?
Wanna read the whole thing again? Go to top
Allow me to do something as un-Danish as listing 5 reasons why I might be the right candidate:
- I love to learn, experiment and improve systems
- I have a strong bias towards shipping, while maintaining high standards
- I am thoughtful when designing APIs, both for internal and external use
- I communicate clearly, positively and thoughtfully
- I have a deep understanding of the mechanics of React
Thanks for reading! I hope you liked it. I would appreciate any and all feedback, and can't wait to hear from you.
Have a great day!
Lukas
