# Vlad Sabev > Essays on software, freelancing, and AI Full Markdown sources for published posts and navigation pages (same text as each `/{slug}.md` twin). Catalog with summaries and tag index: [https://vdsabev.github.io/llms.txt](https://vdsabev.github.io/llms.txt). Each body is a fenced `markdown` code block so frontmatter and headings stay isolated from this outline. ## Posts ### Smoothing the Path with AI - Markdown: https://vdsabev.github.io/smoothing-the-path-with-ai.md - HTML: https://vdsabev.github.io/smoothing-the-path-with-ai/ ```markdown --- status: "published" title: "Smoothing the Path with AI" date: "2026-07-28" description: "My favorite use of AI - not vibe-coding toy apps, but smoothing over the path - like driving a car down the highway instead of going on a cross-country hike." image: "https://github.com/user-attachments/assets/0d022af4-6294-4ea7-99f2-ee55a0632a92" imageAlt: "A sleek white autonomous car with glowing headlights drives straight down a dark asphalt highway that stretches into a thick misty fog toward a distant vanishing point. On both sides of the road, rugged rocky hillsides covered in scrub and tangled vegetation rise up. Glowing abstract overlays of computer code, configuration snippets, and flowing data streams appear on the road surface and dissolve into the surrounding landscape under soft cinematic lighting with cool blue fog and warm amber accents." author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["ai","testing","software development"] navigationIndex: 0 --- My favorite use of AI at the moment is not vibe coding toy applications but setting up systems. A test suite that would have previously taken 2 weeks to configure end-to-end now only takes a few hours while I'm doing something else in the background. I've found the biggest time saving to be in exploring radically different approaches, searching through piles of settings, and resolving various compatibility issues. I could spend hours digging through documentation, StackOverflow, or forums before I finally unearth something useful. Now AI can search everything for me, find a solution, try it out, then try a different one if that doesn't work out, and finally come back with the issue fixed. On the surface there seems to be less learning now, but did this process really lead to that much learning to begin with? Were most of these hours of research not spent on grappling with useless [leaky abstractions](https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/), and then once I finally solved the problem - was I not relieved to immediately forget about it? Perhaps there is something to lose. I did eventually learn most of the gotchas and know what to steer away from, though this knowledge was never really all that useful for achieving my goal of building reliable software. Yes, AI can smooth the path to the actual task, like driving a car on a highway versus cross-country hiking. Yet when we go hiking we're more adaptable to obstacles, we exercise, we learn the way of the land - all of these have value on their own, and there is still purpose in pursuing them. We should still occasionally try to [make difficulties everywhere](https://jonmalesic.com/kierkegaard-in-seven-quotations-part-i-making-things-difficult/). So every day we have a choice to make - what's more important right now: to get to know the land, or to get from A to B? It is for each of us to decide. But if we choose the smooth path instead of the hike every day, we may be losing something elusive and difficult to measure. ``` ### The Economics of Self-Hosting AI - Markdown: https://vdsabev.github.io/the-economics-of-self-hosting-ai.md - HTML: https://vdsabev.github.io/the-economics-of-self-hosting-ai/ ```markdown --- status: "published" title: "The Economics of Self-Hosting AI" date: "2026-07-28" description: "Enthusiasts want to self-host models but can't justify the hardware. Marketplaces for spare compute could enable the economics for individuals, while privacy already makes it a no-brainer for companies." image: "https://github.com/user-attachments/assets/72d4755a-d308-49b9-b2bc-f4f6a8425ecc" imageAlt: "Dark cinematic illustration of multi-GPU NVIDIA server hardware in the foreground with glowing green accents, floating holographic privacy shields and padlock icons, code interfaces, and a silhouetted consultant working at a laptop in the background — cover art for an article on self-hosting AI economics and spare compute marketplaces" author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["ai","self-hosting","freelancing"] navigationIndex: 0 --- ## 💸 Hardware You Can't Amortize Rumor has it Apple's M7 Ultra, landing around 2029, could support **1.5 TB of RAM** - the kind of machine that enables serious on-device AI.
My first reaction wasn't that I'll be able to run my own AI - rather, I thought if Apple built a marketplace to sell spare compute, with clear isolation, metering, and payouts, then took an App Store-style cut, I would seriously consider buying an Apple product for the first time in my life. This move would instantly increase demand for high-end Apple hardware as many tech professionals have been grumbling against their software direction and gradually switching to [various distributions of Linux](https://omarchy.org), finally fulfilling the prophecy and declaring 2025 the year of the Linux laptop. Who else is well-positioned to compete with Apple on this? NVIDIA of course. If they ran a marketplace where you could rent out your idle GPUs, you'd see a lot more self-hosting overnight. Demand for GPUs is sky-high regardless, but enthusiasts are still buying - partially from FOMO, but also because they want to try new models at home. But decent boxes cost a small fortune, and the power consumption is significant. The models aren't state-of-the-art, the box sits unused most of the day, and subscriptions are much cheaper (likely subsidized) while delivering better results. For most people the economics simply don't work - you pay full price for capacity you only use a fraction of the time, and there's no clear way to recoup the rest. ## 🏢 Privacy: Where the Costs are Already Justified For corporations, self-hosting is a different story. You don't want proprietary code, deal data, customer records, or half-baked strategy docs flowing into someone else's training pipeline or sitting in their logs. Leak your IP to a frontier lab and you risk having your business stolen from under you. Yes, you can handle this through zero data retention agreements, but some companies in sectors like medicine or law would rather keep everything on premise. Individuals feel a milder version of the same pressure: personal notes, health context, family stuff, unreleased product ideas. The difference is while enterprises can write off the hardware costs as privacy insurance, individuals still have to justify the bills. ## 🛠️ The Consultant Angle For people already experienced with open models, local coding agents, and the whole messy stack, there's a side bet ripe for the taking right now - companies that don't want their IP stolen will pay for someone who knows: - What fits on which hardware - How to wire auth, logging, and model updates - When *not* to self-host (because sometimes an API is still the right call) If self-hosting enthusiasts positioned themselves as **self-hosting consultants** and found a few good leads, that could be a sweet gig. Not just installing Linux for fun, but for fun an profit! While open models get better each cycle, hardware slowly improves, and manufacturers scale up production, the potential cost of leaking IP remains equal to the value of your whole business. If you're already running models at home for fun, take note. Those who learn this stack early might not just save money on their own API bills, but become the ones companies call when using corporate cloud AI providers is not an acceptable answer. ``` ### cmless: Seamless Blogging with GitHub Issues - Markdown: https://vdsabev.github.io/cmless-seamless-blogging-with-github-issues.md - HTML: https://vdsabev.github.io/cmless-seamless-blogging-with-github-issues/ ````markdown --- status: "published" title: "cmless: Seamless Blogging with GitHub Issues" date: "2026-07-23" description: "I've tried to maintain a blog for years, but every tool had too much friction. So I made cmless: turn a GitHub repo into a blog with Issues as the CMS and GitHub Pages for hosting." image: "https://github.com/user-attachments/assets/81a6f404-3e6d-4df4-b19d-390c87658744" imageAlt: "cmless - seamless blogging with GitHub issues" author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["blogging","software development"] navigationIndex: 0 --- ## 🤔 Too Much Friction I've been trying to maintain a blog for years but anything I used had too much friction. WordPress is overkill. Medium is not your own. Ghost needs hosting. Markdown-in-a-repo means you have to make a commit just to fix a typo. I wanted something simpler - write where I already work, keep assets out of the code tree, and publish without managing a server. So I made [cmless](https://github.com/vdsabev/cmless) - a tool to seamlessly turn a GitHub repo into a blog. ## ℹ️ What It Does cmless takes your repo's GitHub issues, turns them into blog posts, and hosts them on GitHub Pages. - Open issues to write posts - Drag-and-drop or paste images (GitHub hosts them on its CDN) - Embed YouTube, X posts, Instagram, CodePen, and GitHub gists with a simple `![](...)` image-style link - Optional Frontmatter for date, description, tags, and more - Publish with labels: `status: draft`, `status: unlisted`, `status: published` - Updating an issue rebuilds the blog This post is part of that workflow. If you're reading it on my website - it started as [issue #24](https://github.com/vdsabev/vdsabev.github.io/issues/24). **Code stays code. Posts stay posts.** Since content lives in Issues, the repo doesn't bloat with every draft and image, and you can write from any device with a browser and a GitHub login. ## 🛠️ How It Works ### 1. Write the post as a GitHub issue Title = post title. Body = Markdown. Drag and drop images into the issue. Embeds use the same syntax as images - put the URL in `![](...)`:
### 2. Add Frontmatter at the top ```yaml --- date: "2026-07-10" description: "Short summary for SEO." tags: tech, dev --- ``` Author, avatar, and profile link default to the GitHub user who opened the issue. You can override those, plus title, slug, OG image, or place a link to the page in the header or footer if you need to. ### 3. Apply a status label | Label | Listed on homepage? | Reachable by URL? | Best for | | --- | --- | --- | --- | | `status: draft` | No | No | Work in progress, only on GitHub | | `status: unlisted` | No | Yes | Previews, private links, pages like About me | | `status: published` | Yes | Yes | Public posts | `status: published` adds the post to your list. `status: unlisted` publishes it without listing it - handy for custom pages like About me. When you're done editing you can close the issue - or keep it open - the published status of the post is only based on the label. ### 4. Update the issue to rebuild Create or edit an issue (or change a status label) and the site rebuilds. Contributors control the labels so open issues by random people don't get put on your homepage. But if you want to make sure there's no noise you can limit issue creation to collaborators. ## ⚙️ Under the Hood A GitHub Actions workflow runs on issue events and pushes, using the `gh` GitHub CLI tool and a small generator (`scripts/generate.ts`) to turn labeled issues into Markdown for the build. Astro produces a static site, and GitHub Pages serves it. The repo holds the theme, pipeline, and config - the issues hold the writing. If you use the [cmless template](https://github.com/vdsabev/cmless) to make your own repo you can still sync engine updates later by adding an upstream remote and merging it into your repo. ## 🚀 Getting Started Click **Use this template** on [vdsabev/cmless](https://github.com/vdsabev/cmless) and start creating issues - it's that simple. 1. Create a repo from the template - `.github.io` → `https://.github.io` - any other name → `https://.github.io/` 2. In **Settings → Pages**, set **Source** to **GitHub Actions** 3. Create or edit an issue to trigger the first build Labels come from the Blog post issue template, or create them with: ```sh gh label create "status: draft" --color "d73a4a" --description "Not published" gh label create "status: unlisted" --color "5319e7" --description "Published but hidden from list" gh label create "status: published" --color "0e8a16" --description "Published and listed" ``` Site title comes from the repository description. And if you want a custom domain you can do that just like with any other static website on GitHub Pages - drop a `CNAME` in `public/` and point DNS to it. For local preview: clone your repo, install, `npm run dev` (or Bun, etc.). Point `GH_REPO` at any cmless-based repo if you want to develop against live issues without publishing from your laptop. You can also have an AI agent use the `gh` GitHub CLI to manage posts (except uploading assets - paste those yourself in the web UI, or link to external hosting). ## 🏁 In Conclusion I don't need another platform optimized for engagement metrics. Just a place to publish my thoughts with tools I already know and understand. cmless is small on purpose: Issues for writing, labels for status, Actions for rebuilds, Pages for hosting. Simple to reason about, very little maintenance. Try the template from [github.com/vdsabev/cmless](https://github.com/vdsabev/cmless) Or see it in action on my blog: [vdsabev.github.io](https://vdsabev.github.io) ```` ### The AI Casino - Markdown: https://vdsabev.github.io/the-ai-casino.md - HTML: https://vdsabev.github.io/the-ai-casino/ ```markdown --- status: "published" title: "The AI Casino" date: "2026-02-17" description: "How variable rewards and prompt routing could be turning LLMs into digital slot machines" image: "https://github.com/user-attachments/assets/d0909e71-6e3d-49c4-bfa1-bff0233cc178" imageAlt: "A conceptual digital illustration of a slot machine emerging from a computer monitor, framed by glowing neon cherry signs. The slot reels display coding brackets, brains, and the number 7. Streams of glowing blue binary data flow out from the sides, and gold coins are scattered across the foreground against a dark, futuristic background." author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["ai","psychology","self-hosting"] navigationIndex: 0 --- ## 🎰 The Slot Machine Loop of LLMs I recently stumbled upon a piece that got me thinking: [Generative AI runs on gambling addiction: "Just one more prompt, bro"](https://pivot-to-ai.com/2025/06/05/generative-ai-runs-on-gambling-addiction-just-one-more-prompt-bro). Purely hypothetically, if you were an AI provider who wanted to make your product not just useful, but truly addictive - how would you actually do it? ## 🪝 Engineering the Hook To build a hook, you would look at the gambling industry. They perfected the science of variable payouts decades ago. > The most powerful way to reinforce a behavior isn't to reward it every time, but to reward it *randomly*. In a casino, this is a slot machine. In AI, it's **one-shotting** a complex problem. You prompt a model ten times. Nine times it may give you nothing too special, or even broken code. But the tenth time? It may produce a piece of code so delightful and elegant that it gives you a huge hit of dopamine. You're hooked. And you'll spend the rest of the day chasing that high again. ## 🫥 The Routing Layer If AI providers are slottifying their products, are they doing so intentionally, or is it just an emergent property of the tech? From an engineering perspective, every AI provider *most likely* has a prompt router. Running your top-of-the-line model for every "Hello World" request while massively subsidizing token costs with cheap subscriptions is financial suicide. You put a cache and a very cheap, lightweight model (a Flash or Lite variant) in front of every prompt. If the router thinks the prompt is easy, it sends it to the cheap model. If it looks complex - or if the user is a "whale" they want to impress - maybe it gets sent to the heavy hitter. What if that routing isn't just about cost? What if it's about retention? If a user hasn't had a "win" in a while, do you route their next coding prompt to your best and brightest model just to keep them in the game? ## 📈 Degradation or Deliberate Variance? We've all heard the complaints: "Opus is getting dumber" or "Gemini used to be better at this." Usually, we chalk it up to manual alignment. Prompt routing offers an alternative explanation. If AI companies don't legally guarantee that a specific model name maps to a specific set of weights for every single token, they have a massive lever for cost-cutting and psychological manipulation. What if 1 out of 10 prompts gets the "Pro" treatment, and the other 9 get the "Lite" version with a "Pro" label on the UI? You get just enough brilliance to stick around, and just enough mediocrity to keep you prompting. ## 🤔 What about Benchmarks? Wouldn't benchmarks catch such manipulation? Remember the Volkswagen emissions scandal? The car's software would detect when it was being tested and "cheat" by reducing emissions only during the test. Is it such a stretch to imagine an LLM router detecting a benchmark prompt (MMLU, HumanEval, etc.) and routing it to a specialized cluster, while average users get the "distilled" version? I'm not claiming AI model providers are putting their thumb on the scale. But if they wanted to, there's little to prevent them from doing so for the random consumer. The countermeasure to manipulation however is not regulation - any regulation in that direction would probably be too heavy-handed. Rather, it's open-source models, which are now only a few months behind the state-of-the-art models. ## 💪 Hardware Sovereignty The only way to know for sure what weights are processing your tokens is to host them yourself. But it's 2026, and with current supply chain crunches getting your hands on decent GPU and RAM is more and more cost-prohibitive. Maybe a few years from now the manufacturing will be so advanced and the tech so powerful and cheap that we'll have a Kimi K2.5-equivalent model running locally in our pocket. Until then, consider this: every time you hit "Generate" with a supposedly state-of-the-art model, you might be placing a bet. And in the long run, the house always wins. ``` ### The Spiritual Cost of the Nanny State - Markdown: https://vdsabev.github.io/the-spiritual-cost-of-the-nanny-state.md - HTML: https://vdsabev.github.io/the-spiritual-cost-of-the-nanny-state/ ```markdown --- status: "published" title: "The Spiritual Cost of the Nanny State" date: "2026-02-07" description: "The shift from personal responsibility toward government paternalism and human self-domestication." image: "https://github.com/user-attachments/assets/cabcac33-6cef-4c74-ad96-862fb99201a6" imageAlt: "A conceptual collage contrasting old and new technology, featuring a vintage radio and headphones on the left and a modern cylindrical smart speaker on the right. In the center background, two hands hold a glowing, digital shield that says 'PERSONAL RESPONSIBILITY' as pieces fly off it. Above the smart speaker, a holographic user interface displays 'MAX VOLUME RESTRICTED' and a button that reads 'I AM NOT IN THE EU'." author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["eu","parenting","personal responsibility","technology"] navigationIndex: 0 --- ## 📜 The Bluetooth Decree Earlier this week I saw a post about a seemingly trivial piece of EU regulation: "Are you located in the EU? If yes, max volume will be restricted. Yes/No" On the surface, it's about public safety. Protecting hearing. Preventing disturbance. But as I found myself in a discussion about it online, it became clear that my issue wasn't really with the decibels, but about a **fundamental shift** in how we relate to our tools, our governments, and our responsibilities. ## 🤔 Public Health or Safety Theater? Let's give the regulators the benefit of the doubt - loud music can damage your hearing, and this is well-intended. But in this case circumventing the max volume is trivial. Of course the owner in the original post selected "No". But even if you selected "Yes", simply restarting the device brings up the prompt again. With some other devices it's just a warning once you go past a certain volume, gently nudging you to not listen at the max. Many young people genuinely don't know that loud music is dangerous. And this is what I'm really concerned about - parents are the ones who must bear primary responsibility for their children's health. So why are they letting children habitually listen to loud music at 90-100dB for long periods of time? Work, chores, and stress leave many parents with less and less family time. This decrease in parenting (and increase of threats to our children) is unfortunately leading us to **delegate** more and more of our traditional social structures to government. ## ✅ The Case for Localized Responsibility First, let's quickly tackle the safety issue - if we truly care about protecting hearing, we can solve this at a much more local level. Instead of an easy to circumvent max volume, we could require that all standalone speakers allow customers to set their own limits. Say, a **parental control** setting protected by a password. Manufacturers can pre-set limits for safe sale to minors. Parents can limit the max volume for their kids. Audiophiles can blast their eardrums away at will. This approach respects individual agency. It acknowledges that an adult using a speaker at a private outdoor event might need more power than a child in a bedroom. It moves the decision-making power from unelected bureaucrats in Brussels back to the citizen holding the device. ## 📈 The True Cost isn't Financial, but Spiritual There is, of course, a cost to any regulation. We pay the salaries of bureaucrats to write the rules. Then we pay engineers like myself to implement them, and companies pass that cost down to consumers. My proposal for password-protected max volume could be even more expensive than a simple yes/no prompt. But more importantly, there is a **spiritual toll**. It's the cost of being increasingly "parented" by our well-meaning governments. When every risk is smoothed over by regulation, we lose the habit of personal responsibility. We might even stop thinking about "safe levels" because we assume the government has already done the thinking for us. What happens when we're in a situation that doesn't have this protection? Our car stereo, our old analog speakers, a loud concert? We can't pave the jungle - we have to put on some boots. ## ☠️ Death by a Thousand Cuts A single regulation on Bluetooth speakers is a non-issue. It may even be a good thing, raising awareness of the dangers of loud noises. But over decades, such regulation is death by a thousand cuts from bureaucratic paper - each one a tiny slice of agency removed from us. In my mind, this ties into the concept of **human self-domestication**. By removing all friction and risk from our environment through centralized decree, we are essentially domesticating ourselves. We become more docile, more dependent on the system, and less capable of navigating reality without a regulatory harness. This process has [physical effects across many domesticated animals](https://www.frontiersin.org/journals/psychology/articles/10.3389/fpsyg.2019/00521/full), including humans. While these effects like having a lower aggression drive are not necessarily a bad thing, we have to preserve our competence and ability to think independently. Otherwise, when some part of the system inevitably fails catastrophically, we will find ourselves unable to function. And one day the fate of human civilization may depend on the few who still can. ``` ### Overtherapizing Men's Mental Health - Markdown: https://vdsabev.github.io/overtherapizing-mens-mental-health.md - HTML: https://vdsabev.github.io/overtherapizing-mens-mental-health/ ```markdown --- status: "published" title: "Overtherapizing Men's Mental Health" date: "2026-02-02" description: "Talking about emotions isn't enough for men's mental health - finding meaning through Function, Family, and Faith offers a better path." image: "https://github.com/user-attachments/assets/435e5746-13b6-4f78-b91f-f311ddf08a58" imageAlt: "A gnarled pine tree with exposed roots on a rocky cliff overlooking Yosemite's Half Dome, illuminated by golden sunset light piercing through dramatic storm clouds with low mist in the valley." author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["culture","family","psychology"] navigationIndex: 0 --- *"Men need to talk about their feelings."* But when they do, they're called *attention-seeking*. *"Men should be expressing their emotions."* But when they do, they're labeled as *toxic*. *"Men can cry in public too."* But when they do, women get the ick. You *are* actually supposed to be composed and suppress your emotions in front of people. If all men expressed our emotions directly and instantly, the result would be an inordinate amount of violence and crime. And while talking about your feelings is fine, it doesn't solve the underlying problem, which is usually downstream of feeling useless, powerless, or replaceable. Instead, we need to channel our emotions into productive efforts. Controlling your emotions is not unhealthy - we're just in the wrong environment for it. We have effectively defeated most natural problems we evolved for over a million years, and are left with societal problems that are infinite at any level of affluence (as classic Russian literature amply demonstrates). Men are not as good at solving those as they are at natural problems. The other reason men's culture has become "unhealthy" in modernity is that we're increasingly giving up on following the main pathways to meaning: - **Function** - work, charity, general usefulness to society. Email jobs, scrolling on social media, and playing video games does not help with feeling productive and useful. - **Family** - most men will say providing for their family is the greatest purpose of their life, yet we are getting married and having children later and later in life, if at all. - **Faith** - accessible to all, this path goes the deepest, and you can choose to follow it at any time - even at your very last breath. I have hope more of us will decide to. In [Small Bets](https://smallbets.com) we talk about working like a lion on the hunt. But humans were once hunters too - and some still find meaning in defeating natural adversities. If you haven't, I recommend watching [Happy People](https://www.imdb.com/title/tt1683876) by Werner Herzog, or how some other indigenous people live to this day. Maybe your instinct to go work on a farm is correct - if for nothing else, then to remember what it feels like when a problem is finite, physical, and responds directly to your effort. Then when you find yourself sitting around the table having dinner with family, pleasantly tired after a good day's work - all your troublesome feelings magically disappear. On to the next day! ``` ### Rich Storytelling with AI World Models - Markdown: https://vdsabev.github.io/rich-storytelling-with-ai-world-models.md - HTML: https://vdsabev.github.io/rich-storytelling-with-ai-world-models/ ```markdown --- status: "published" title: "Rich Storytelling with AI World Models" date: "2026-02-02" description: "How AI world models like Genie 3 could revolutionize storytelling, democratize game development, and let creators build immersive worlds from imagination." image: "https://github.com/user-attachments/assets/fa9dbba3-e376-4062-8083-591dc7e5cd19" imageAlt: "Four friends in a living room watching a large, futuristic screen displaying a fantasy castle floating above the clouds, surrounded by digital data streams." author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["ai","game development","storytelling"] navigationIndex: 0 --- ## 🧞 Genie 3 Out of the Bottle A wider release of [Genie 3](https://deepmind.google/models/genie/) last week resulted in some [impressive demos on X](https://x.com/mattshumer_/status/2017058981286396001) of gameplay generated on the fly, without playing an actual game. Things that even 5 years ago nobody would imagine possible are now becoming reality. Critics will point out one serious flaw - and it's not the low frame rate or the high cost, but the **lack of persistence**. One demo showed how [a mountain ledge disappears when you turn your back](https://x.com/firagawalkwthme/status/2017502322654646436). For most games, this is untenable - place a vase on a table, turn around, and it should still be there - not transform into a teapot or vanish entirely. **Persistence matters** because it maintains the suspension of disbelief required for immersive storytelling. Without it, AI worlds become chaos engines that break reality with every glance. Instead of a coherent experience, you get a whooshing sense of vertigo. This could work well enough in a linear rogue-like game on rails, but it would never reach the richness and depth of the well-crafted worlds we know from literary works. --- ## ⛏️ The IP Goldmine Remember the last time your beloved book series got adapted to TV? *Rings of Power*, *Wheel of Time*, *The Witcher*. How did those turn out? Exorbitantly expensive productions that strayed far from the source material fans loved. Literally adapting a book scene-by-scene remains too expensive for traditional media. Studios face impossible choices: compress 1,000 pages into 8 hours, change characters to fit modern sensibilities, or invent new plotlines. Each compromise alienates fans who just wanted to see the world they know brought to life. Games have been somewhat better at respecting the source material - one could, in fact, [simply walk into Mordor](https://www.youtube.com/watch?v=LYipECdYpXc) - but the development costs are still high. The entertainment industry has long prioritized existing IP over original concepts. AI world generation will continue this - just as the Marvel Cinematic Universe built an interconnected franchise, models like Genie could let fans step into their favorite worlds - without the adaptation compromises. --- ## 🐉 Be the Hero in Our Own Story While the prospect of experiencing our favorite worlds is exciting, the endless stream of remakes recycling the same characters and plots has created much fatigue. Stories need fresh settings, characters, and world mechanics - areas where AAA studios have been struggling. Smaller developers have stepped into this gap. Games like *Stardew Valley*, *Hollow Knight*, and *Outer Wilds* prove small teams can create rich worlds that rival AAA productions. The indie game market has been growing much faster year-over-year compared to AAA sales. Meanwhile, tabletop gaming has exploded, with D&D 5th Edition reaching 50 million players globally, and actual-play shows like *Critical Role* drawing millions of viewers and spawning multiple Prime Video animated adaptations. Creating compelling worlds remains the hardest part of game development. A medieval city of a million, a vast jungle of mysterious ruins, a galaxy of a trillion planets - these require time and resources most creators lack. Procedural generation helped *Minecraft* and *No Man's Sky* achieve scale, but at the cost of story depth. World models like Genie can help bridge this gap - the scale of procedural generation plus the allure of a captivating story, delivered at the speed of AI. With **Vibe Editing**, the world emerges before your eyes - mountains shift, cities grow, stars are born with a sentence. Tabletop Dungeon Masters plug their D&D campaign into an AI model and get a world where landscapes are generated on the fly and the Beholder's lair adapts to narration as the players battle. This eliminates physical constraints - you can keep rolling dice, but there's no need to print battle maps and move minis around anymore. --- ## 🔥 A Tale Worth Telling Studios will profit from IP-driven world models - immersive Lord of the Rings experiences, explorable Hogwarts, interactive Star Wars galaxies. But the same technology that enables corporations to mine established IP will democratize world-building for solo creators - provided it is affordable enough. Today, building a game world requires either a AAA budget or years of procedural system development. Soon, a storyteller with a vision might prototype their setting in an afternoon, iterate through natural conversation with an AI, and export a playable space. An indie developer could compete with the giants on imagination rather than pocket depth. We're not there just yet - persistence remains glitchy, costs are high, copyright questions loom. Once we've made enough progress on these issues, we can build on a foundation of procedural generation, combined with persistent world models and vibe-editing. The trajectory points toward a future where world-building becomes an act of storytelling once again, not an engineering problem. And when you have a tale worth telling - get some snacks ready, gather your friends and family, and sit comfortably around the living room screen. Like cavemen telling stories around the fire. ``` ### Effective Code Reviews - Markdown: https://vdsabev.github.io/effective-code-reviews.md - HTML: https://vdsabev.github.io/effective-code-reviews/ ```markdown --- status: "published" title: "Effective Code Reviews" date: "2023-09-26" description: "How to foster a culture of learning through code review." image: "https://github.com/user-attachments/assets/dfd49146-348f-4e86-a7c1-c2030ed57c89" imageAlt: "Photo by NESA by Makers on Unsplash" author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["code review","software development","teamwork"] navigationIndex: 0 --- Photo by [NESA by Makers](https://unsplash.com/@nesabymakers?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com/?utm_source=medium&utm_medium=referral) Don't want to read? Listen to this dude's video, it covers most of what I have to say:
Or, since you're here already — read on. ## 💭 Which one are you? You might be one of these people: 1️⃣ You're sitting at your desk, your hair turning white while scrolling through your colleague **Alex**'s PR. You know, *that* **Alex**, the one who always shortens variable names to incomprehensible gibberish, writes zero tests, and copy-pastes 3 files with only a few lines changed instead of taking the time to write a scalable solution. Your fingers are itching to write a comment for each variable name using `camelCase` instead of `snake_case`, leave a scathing review, show everyone how it's done!!! 2️⃣ You've taken the time with your PR — added a link to the ticket, put some nice labels, even found a fun GIF! Then **Greg**, your lead engineer, leaves a review asking why you didn't "just do X". You could kindly tell **Greg** that X was the first thing you tried and it didn't work for a very good reason. But since **Greg** didn't ask politely you'll *snap at him* and *leave snarky comments* on his PRs from now on. *F-ing Greg!* Hey, he started it!! 3️⃣ Pull requests come and go. You just don't care to participate. What's all the fuss about anyway? The app seems to work. Code quality? That's just, like, your opinion, man 🤷‍♂️ 4️⃣ Your pull requests are a *pleasure to review*, you start discussions by asking *polite questions* out of *genuine curiosity*, you're *proactive* about *proposing alternatives*, and *open to changing your mind* while also knowing how to *stand your ground* on the things that matter. Recognize anyone you know? Maybe yourself? ## ✅ How to approach code reviews You might think reviewing code is about catching bugs. Surely that *is* the case sometimes. Yet is that all it's good for? > *🤔* But code review keeps our code clean, and clean code is good! If maintaining clean code was the entirety of software development I would wholeheartedly agree. Other important parts are *learning* and *maintaining good social relationships* within your team — does telling people to "do it *this way*" without first asking *why* they did it it *that way* really help with those? > *🤷‍♂️* It's just code, we shouldn't be that sensitive! If you don't want the people you work with to be *sensitive* does that mean you want them to be…*insensitive*? If you as a reviewer aren't willing to have an open and reasonable discussion on the best way to do something, but say *"just do it my way"* then who's really the one really being emotional about code? > *😔* Okay I'm listening. What are you suggesting instead? ### ✅ Do ask questions and start discussions Even the most experienced developers occasionally make mistakes or incorrect assumptions. They get tired, stressed out, or distracted. So is leaving a comment like this really the best way to go about it? > Extract this JSON to a separate file. **Ask, don't tell.** Instead of assuming the author was being lazy or didn't know what they were doing, try to find out what was really going on. *Caveat 1:* adding a question mark or *"maybe?"* after your statement does not make it a real question. *Caveat 2:* rephrasing your statement as *"why didn't you"* or worse — *"why didn't you* ***just****"* does make it a real question, but a negative one. How about turning it into a positive question? *"Have you considered"*, *"have you tried"*, *"how about"* (heeey — I used that one here), *"what would happen if"*, *"would this idea work?"* ### ✅ Do proactively propose solutions Look for related documentation, articles, video, code examples, codepens, performance tests, libraries, tools. When proposing a code change — open a real code editor and try to type it out yourself first — you might find it doesn't work out as well as you thought it would. ### ✅ Respect that accepting your solution is the author's decision Let people express their personal coding style now and then. Your project's code can look like it was written by the same super neat person and that's okay. But it's also okay if there's some personality in there as long as everyone can understand and work with it. **Alex** used a `for … of` loop instead of `.forEach`? What would happen if you let that slide and focused on whether the code does what it's supposed to? Here's how to address some situations. ## 1️⃣ When code works well but could use small improvements ### ❌ Rarely request code changes Requesting code changes (e.g. on GitHub) should be used sparingly, only as a last resort to warn other reviewers against merging obviously buggy code. While PR authors can choose to dismiss the blocking review and merge the changes anyway, reviewers might take that as an open act of disrespect. The red color in GitHub's UI indicating "something's wrong" doesn't help either: Changes requested screenshot of GitHub Getting a red-colored message from a dispassionate machine like a compiler, linter, or bot is to be expected; seeing it next to the picture of a real person you're working closely with is another thing. Sooner or later there's bound to be some resentment. ### ✅ Do leave a comment and withhold your approval until later It costs you nothing and is completely neutral. If the author simply ignores your comment without even acknowledging it then you might have bigger problems in your team than just "code quality". Work on those first. Also, if other team members happily approve the PR despite your cautious comment and everything turned out fine, were the changes you wanted really that important after all? ## 2️⃣ When you notice incorrect formatting ### ❌ Don't be a human linter Machines are perfectly suited for linting and formatting code — they're infinitely faster, cheaper, and more reliable in enforcing conformity than humans. And humans program machines, turning many hours of manual human work into mere seconds. Which job do you want to do? ### ✅ Do propose a lint config fix (or learn to let go) If *prettier* and *ESLint* are okay with the code is it really worth talking about formatting? Unless it's to discuss whether to change the lint rules long-term, I think not. This is after all [why prettier exists in the first place](https://prettier.io/docs/en/why-prettier.html): > By far the biggest reason for adopting Prettier is to stop all the on-going debates over styles. And if we do decide to change the lint rules, we can do that in a separate PR instead of holding off on an important feature because there's no newline before that `return` statement. ## 3️⃣ When a PR has the same issue in multiple places ### ❌ Don't leave a comment for each instance This clutters the discussion timeline and spams everyone's inbox. What if you only noticed 7 out of the 10 places this issue was present in? Would you rather get 70% only in this one PR, or think long-term, automate the solution, and get 100% of what you wanted in this and all future PRs? ### ✅ Do only leave 1 comment Link to the other affected areas of the code, or leave instructions for the author to verify everything works on their own. Discuss long-term solutions like improving the linter configuration, building utility functions that make good practices easier to follow, or enhancing the project documentation. And a final Do: ### ✅ Do stay curious and open to changing your mind If you always insist on your own solution with no room for discourse does that mean you've made up your mind *forever*? Are you even the same person you were last year, or have some of your opinions changed *ever so slightly*? Do you want to think the exact same way a year from now, or do you want to be *better*? To even *have* the opportunity of becoming better some day, you have to first be *open* to the possibility that you *don't know everything* at present. Genuine curiosity can shatter the concrete shell that judgment forms around our minds, and then willingness to learn can sprout up through the cracks. So keep those cracks open — stay curious. ``` ### On Alphabetically Ordering CSS Properties - Markdown: https://vdsabev.github.io/on-alphabetically-ordering-css-properties.md - HTML: https://vdsabev.github.io/on-alphabetically-ordering-css-properties/ ````markdown --- status: "published" title: "On Alphabetically Ordering CSS Properties" date: "2020-12-02" description: "And other properties, really" image: "https://github.com/user-attachments/assets/96a6a2ec-a8f7-4160-919e-75c5144f1ade" imageAlt: "Photo by Héctor J. Rivas on Unsplash" author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["css","software development"] navigationIndex: 0 --- Photo by [Héctor J. Rivas](https://unsplash.com/@hjrc33?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com/?utm_source=medium&utm_medium=referral) It's a beautiful sunny day out. You're on your way back from the store when you see this person in the street: Random person on the street You don't know exactly who he is, but he seems vaguely familiar. You remember his name was Michael or something, but you've been living under a rock for the past couple of decades so you're not quite sure. When you get home you try to describe Michael to your friend Janice over chat, hoping she can help you remember who he is. How do you do that? Let's say you and Janice agreed to use a language similar to CSS, but for **describing people**. A popular way of listing CSS properties is **alphabetically**: ```css belt: 3in solid leather black; build: athletic; clothes: suit shirt tie; facial-hair: goatee 3days; fingers: 10 extra-long; first-name: 'Michael; glasses: sunglasses cool; hair: medium straight brown full; last-name: unknown; max-age: 40; max-height: 6ft 6in; max-weight: 200lbs; min-age: 30; min-height: 6ft; min-weight: 180lbs; shoes: black official 14us; shoulders: extra-wide; ``` It's certainly easy to list properties alphabetically — we can sort them in our IDE in seconds. However, there's a principle in the software industry that code should be **optimized for reading rather than writing** because [we spent much more time doing the former](https://www.goodreads.com/quotes/835238-indeed-the-ratio-of-time-spent-reading-versus-writing-is). Does the alphabetical order follow that principle? What if we put the **most relevant properties first**? Michael Whatever, 30-something, tall, built like an athlete, wide shoulders, and the longest fingers you've seen in a while: ```css first-name: 'Michael'; last-name: unknown; min-age: 30; max-age: 40; min-height: 6ft; max-height: 6ft 6in; min-weight: 180lbs; max-weight: 200lbs; build: athletic; shoulders: extra-wide; fingers: 10 extra-long; hair: medium straight brown full; facial-hair: goatee 3days; clothes: suit shirt tie; glasses: sunglasses cool; belt: 3in solid leather black; shoes: black official 14us; ``` You might choose a different **order** of properties to describe Michael than someone else would. You might **group** the properties differently. Is the order and grouping going to be perfectly uniform every time for every person describing anyone? Probably not, though we could enforce it using [the right tools](https://github.com/hudochenkov/stylelint-order). We would need tools to double-check for alphabetical order anyway since people often forget to sort or make mistakes. Then why not also go the extra mile and make our code more meaningful and **easier to read rather than easier to write**? ```` ### Writing Reliable Unit Tests for Our Components - Markdown: https://vdsabev.github.io/writing-reliable-unit-tests-for-our-components.md - HTML: https://vdsabev.github.io/writing-reliable-unit-tests-for-our-components/ ````markdown --- status: "published" title: "Writing Reliable Unit Tests for Our Components" date: "2020-11-08" description: "Three common pitfalls when writing component unit tests and how to write more reliable tests that focus on the component's public interface." image: "https://github.com/user-attachments/assets/5c0ec3ce-0627-493c-a58a-9a32c913d299" imageAlt: "Photo by Clem Onojeghuo on Unsplash" author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["software development","testing","vue"] navigationIndex: 0 --- Have you been painstakingly cranking out unit tests for the past few months, trying to hit and maintain the coveted > 90% code coverage, only to now find yourself rewriting them again and again because of minor changes to your codebase? Have your users bumped into subtle bugs that your unit tests just weren't able to catch, even though your component code is supposedly 100% covered? Have your QA engineers been constantly asking you to add more selectors to elements in your components so that they are able to do their job and write automated tests? In this post we'll be going over these 3 pain points that many have experienced because of the way we sometimes write component unit tests, and then we'll explore an alternative approach that addresses them. ## 📦 What are we testing? For starters, let's take a simple Counter component implemented in Vue: Here's what it looks like: Counter component UI Now let's write some unit tests using [Vue Test Utils](http://vue-test-utils.vuejs.org): We've tested each method that our component can call, and since there isn't much code in there it's relatively easy to cover all of it: 100% code coverage But is that really enough? ## 🤨 What's wrong with these tests? Maybe nothing — at first glance they may look like perfectly acceptable tests. After all they do give us 100% code coverage, that means we should be good, right? Let's first see if we can agree on 3 properties of reliable component tests: 1. **When tests pass, the component works; when the component works, the tests pass.** This gives us confidence to refactor our code. 2. **When tests fail, the component doesn't work; when the component doesn't work, the tests fail.** This gives us easier debugging since failing tests pinpoint *where* in the code we should be looking for a bug. 3. **Writing tests encourages better engineering practices for our components and makes them easier to test by anyone,** not just people intimately familiar with the internals. This gives us scale across the entire team, including QA and even 3rd party consumers of our components. Now let's see where the tests we have right now fall short. ### 1️⃣ Refactoring makes them fail even though our component still works One of the benefits of having reliable unit tests is they help us refactor fearlessly. You might have noticed we pass the `step` prop when calling `increment` and `decrement` in the template when instead we could just access the prop in the method using `this.step` and clean up our template a little bit. Let's make that small change: This is just some internal refactoring, the Counter component still works, but what happens to our tests? They break right away, because we were previously passing 10 to the method and since it no longer accepts an argument it uses the default value of the `step` prop which is 1! Tests failing after refactor Right about now you may be shaking your head saying — "of course they would break, what did you expect, it's obvious?!" Well, I expect that when my component works the exact same way as before for an end user my tests will also pass without having to change them. Think about that for a moment — does testing components like this make it *easier* to refactor them or *more difficult*? Does it *remove* obstacles or does it *add more*? Now we have to rewrite those failing tests. Here's what it would take to fix them: We have to set the `step` prop beforehand and we're good to go for now. ### 2️⃣ Changing the template doesn't make them fail even though the component no longer works Remember when we removed that pesky `step` prop from the template? That cleaned it up a little bit, but we're still passing an argument to `setValue` in there. So let's clean that up as well: Ah, looks much better, and our tests are still green! 100% code coverage Wait, what happened to our component? Broken component We just deleted the argument from `setValue` which was sending the input element's value — and now typing in the input no longer works because it calls `setValue` with `$event` instead of `$event.target.value` And our tests, with their glorious 100% of code coverage, did exactly nothing to protect us from that. You may think you're a careful and experienced developer and would *never* make a silly mistake like that. But let me ask you this — are you the only person working on this project — now and forever? Besides, mistakes don't happen solely due to inexperience — people get tired because of long working hours, distracted by children or pets, or stressed out when an important deadline looms close. *(I also sincerely hope you don't drive with* ***no*** *seatbelt on just because you think you're a careful and experienced driver)* ### 3️⃣ They don't help us make the component easier to test from the outside How would a QA engineer write an automated script that tests this component in the UI? Maybe query for the buttons with JavaScript like this: ```javascript const decrementButton = document.querySelectorAll('button')[0]; const incrementButton = document.querySelectorAll('button')[1]; ``` This works for now, but what if we put out a new design that switches the order of the DOM nodes and renders them vertically like in the [HTML number input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number)? Alternatively, a QA engineer could select the buttons by their text content `-` or `+` which is a viable approach but also has trade-offs — for example it would break if we replaced the text content with equivalent icons. Classes also work but could be inadvertently removed by a developer who doesn't know they're used for testing. And so in this specific case I prefer [`aria-label`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-label_attribute): It's explicit, meaningful, and yes it does happen to make our component more accessible to users with special needs, but more importantly in this case — to our QA engineers. However, no matter which approach to selecting elements we take, the component tests we have right now leave the QA team as an afterthought. We're likely to only add the selectors when we're asked to do so by someone else trying to test our component from the outside (and failing miserably). In summary, there's an ancient Zen saying that describes our predicament succinctly: > ***💭* Checking whether a component behaves correctly by testing its internals is like judging a tapestry by looking at its back side.** Embroidery front and back side Yes, I know that's embroidery 🤓 ## 🤔 What's a reliable test supposed to do? As long as a component looks and behaves the same, unit tests should leave us free to rework our code so we can make it easier to maintain. Also, code that affects component behavior should be tested, even if it doesn't get captured in a coverage report. Finally, components should be accessible for testing from the outside. So how do we actually do that? What's going on here? First, we select the DOM node we want to test using [`component.find`](https://vue-test-utils.vuejs.org/api/wrapper/#find), then trigger the corresponding event. Remember the issues we had while refactoring our component code before? As expected, the first test would've caught the incorrect argument of `setValue`, and the second and third tests would still be passing after removing the `step` prop from the template. This is also where we make use of the `aria-label` attributes, and because we needed to set those to select the elements in our unit tests, accessibility to QA is now a first-class citizen! You might still have some questions right about now. ### 🙋‍♂ Isn't this unnecessarily testing the framework? We're testing the event bindings we wrote in the HTML template, not the internal workings of Vue. In fact these tests aren't even specific to Vue — we could just as well write our own `mount` function and re-implement the component in another framework while still keeping our tests unchanged. If we want to be able to trust our test suite, then all code that affects component behavior should be tested, even if it's "just HTML", maybe even if it's CSS like `display: none` or `pointer-events: none` which can change how users interact with an element. And the earlier we can test it, the better — bugs don't have to be caught by a QA engineer after our code has already passed multiple developer reviews and gone through 60-minute cycles of CI/CD build and deploy. ### 🙋‍♀ Isn't this more work? There are trade-offs to any approach. In this case the first test is 2 lines longer, and we do need to learn how to work with the DOM and think about what selectors to put in our code beforehand. What if instead of thinking about those extra few lines of code we were to consider all the hours saved by *not* having to fix subtle bugs, rewrite tests because of minor code refactors, or have back-and-forth discussions about selectors with our QA engineers? ### 🙋‍♀ Isn't this way of testing non-obvious? This approach is far from niche — in fact testing the component's public interface and template logic is even mentioned in the [Vue Test Utils docs](https://vue-test-utils.vuejs.org/guides/#knowing-what-to-test). There are also libraries that specifically focus on this school of testing, the best know of which is the aptly named [Testing Library](https://testing-library.com) utilities. Last but not least — keep in mind that QA engineers have been black-box testing for decades, and there's a lot we could learn from each other. ## 🏁 In summary You do need a mental shift to write tests this way — it's not always easy or clear-cut, especially when using 3rd party components in your templates that don't have accessible selectors. I hope you'll find that once you start thinking about the *front side of the tapestry* you'll be noticing opportunities to write more reliable tests everywhere. ```` ### Simple Naming Tips for Event Handlers - Markdown: https://vdsabev.github.io/simple-naming-tips-for-event-handlers.md - HTML: https://vdsabev.github.io/simple-naming-tips-for-event-handlers/ ````markdown --- status: "published" title: "Simple Naming Tips for Event Handlers" date: "2018-12-23" description: "Helping our team (and ourselves) write more meaningful and robust code." image: "https://github.com/user-attachments/assets/79b7d8b3-688c-4be0-a5ff-aea60b9b56bd" imageAlt: "Image" author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["javascript","react","software development"] navigationIndex: 0 --- Photo by [Samuel Zeller](https://unsplash.com/@samuelzeller?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com/?utm_source=medium&utm_medium=referral) ## 🎯 The Challenge Here's something we've probably all seen— component methods named after the events they're handling: In addition to React, I've also seen this pattern in Angular, Backbone, jQuery…it has probably existed for as long as the first programming language that had event handlers. Here's an example with event listeners in vanilla JavaScript: ```javascript const modal = document.querySelector('#confirmation-modal'); modal.addEventListener('yes', onYes); modal.addEventListener('no', onNo); modal.addEventListener('close', onClose); ``` > For the difference between event **handlers** and **listeners** see [this definition on MDN](https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Event_handlers#Terminology). The tips in this post apply to both. Okay, so handler names like `onYes`, `onNo`, `onClose`…what about them? ### ⚠️ Duplicate functionality In the `EditForm` component it's relatively easy to see that `this.onNo` and `this.onClose` do the exact same thing — especially since they're defined right next to each other! Of course we can change those events to use the same handler: ```jsx ... ... ``` Whew! Problem solved? Perhaps, at least in this case. Now think about this for a second — if we continue this practice of naming all handlers based on the events that trigger them, how likely is it that we'd introduce duplicates like this in the future? And how likely is it that we'd miss those duplicates in a large and complex component? ### ⚠️ Short-lived names Eh, maybe we don't care about duplicates as much — our code works fine and at least the naming is consistent. Oh look, our colleague Jane just found a really awesome open source modal library that looks utterly amazing! She's now starting to use it across the whole application, and asks us to help by also changing it in the `EditForm` since we're working on that component anyway. So we do: ```jsx ... ... ``` Hmm, looks like the event names for this one are a bit different. It actually doesn't even have `onClose` anymore. Our code still works fine — it's just the naming that has become inconsistent! What do we do? Should we rename all our handlers now? ## 💭 The Suggestion I think we should! And if we want the new names to stick around, I would suggest we base them on **what** they do instead of **when** they do it. Here's our `EditForm` using the original `ConfirmationModal` component, but with a different way of naming event handlers: What differences did you see? For example did you notice how we now used `this.hideDiscardModal()` in multiple places instead of `this.setState({ discardModalIsShown: false })`? > It's much easier to think of using something when we put a meaningful name to it. `hideDiscardModal` carries more meaning about **what** it does than `onNo`, so we'll naturally think of it in more situations where we need to…you know…hide the discard modal. ## 🤔 The Dilemma We've renamed all the component's handlers, yet its props are still called `onSave` and `onDiscard` instead of `save` and `discard` respectively. Should we rename them too? I've heard both opinions — my preference is to keep the `on` prefix, as they're event names, not handlers. > Think of `onSave` as something that allows subscribing to the component's `save` event, and [we already use the `on` prefix for that in JavaScript](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers). ## 📝 The Summary When handling events like this: ```jsx ``` We followed these simple naming tips: > 1\. Event names should answer the question **when**, because they're on the **left side** and describe the **conditions under which events trigger**. > > 2\. Handler names should answer the question **what**, because they're on the **right side** and describe the **effects that events have**. What's the way you do it? Got any further naming tips? ```` ### Learning Form Management in React - Markdown: https://vdsabev.github.io/learning-form-management-in-react.md - HTML: https://vdsabev.github.io/learning-form-management-in-react/ ````markdown --- status: "published" title: "Learning Form Management in React" date: "2018-10-08" description: "By building our own reusable component from first principles." image: "https://github.com/user-attachments/assets/3ad59f4f-05d1-4a81-9b8e-0b8bd14b0efc" imageAlt: "Photo by rawpixel on Unsplash" author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["forms","javascript","react","software development"] navigationIndex: 0 --- Photo by [rawpixel](https://unsplash.com/@rawpixel?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com/?utm_source=medium&utm_medium=referral) Learning often comes from deliberate practice and repetition—making the same mistakes, writing the same code, solving the same problems over and over again. For example, how many times have you had to: - Render a form with some inputs - Collect data from these inputs as the user enters values - Process the collected data on submit Me personally — multiple times, in every project. ## 🤔 The Simple Solution Of course there already *are* libraries for managing forms in React. A couple of the most popular are: - [Jared Palmer](https://medium.com/u/fb7a3c353cc1)'s [Formik](https://github.com/jaredpalmer/formik), which uses the same principles described in this post - [redux-form](https://github.com/erikras/redux-form), which manages form state via Redux (well, duh) **If you're in a real hurry to put some forms in your project I encourage you to stop reading right now and use one of those libraries — they're full-featured, well-tested, and have a significant community around them.** Otherwise, I've found that to understand a tool in more depth it really helps to try and build your own and *then* switch to a library if you really need to. In fact, I only started appreciating the benefits and trade-offs of Redux when I went through the process of writing [a state management library](https://github.com/vdsabev/overstate) myself. We'll be doing something similar here — by writing a reusable `
` component from first principles, we can learn a thing or two about *why* libraries are built the way they are, as well as *how* to use them more effectively. Let's go. ## 🍰 The Delicious Demo TL;DR — here's what we'll be building: Now I'm sure you could find your way around the code just fine — still, as part of the learning experience, I'll go over the process in more detail. ## 1️⃣ The Boring Beginning To figure out what problems we're trying to solve, let's first write a standard React form from scratch: ```javascript class App extends React.Component { state = {}; submit = (e) => { e.preventDefault(); // Prevent submitting form to the server window.alert(`Hey, ${this.state.name}! You ordered some ${this.state.food}!${this.state.isDessert ? ' A lovely dessert!' : ''}`); }; setName = (e) => this.setState({ name: e.target.value }); setFood = (e) => this.setState({ food: e.target.value }); setIsDessert = (e) => this.setState({ isDessert: e.target.checked }); render() { return (
); } } ``` This does exactly the same thing as the demo from the previous section — it renders a form with: - Two text inputs - One checkbox - And a submit button. When the form is submitted it shows an alert dialog that "orders" your favorite food. ## 2️⃣ The Refactoring Round One painfully dull thing was that we wrote three methods for collecting values from each input. So if we had 300 inputs would we then have to write 300 methods? Let's change that by using a more generic method instead: ```javascript class App extends React.Component { ... setValue = (e) => { const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value; this.setState({ [e.target.name]: value }); }; render() { return (
); } } ``` The main things here are that we: - Added a `name` attribute to inputs - And handled checkboxes a bit differently from text inputs by using the target's `checked` attribute instead of `value`. Now, whether we have 3 or 300 components in there, we only need to set a `name` attribute and that one `onChange` handler. ## 3️⃣ The Rad Rendering To make this component's behavior reusable across our application, we need to be able to provide *different* inputs as its children, as well as attach `onChange` handlers from the parent component to said children. And how can we both: - Provide children to a parent component - *And* allow children to access functions from their parent? Why, by using the rad [render props](https://reactjs.org/docs/render-props.html) pattern of course! ```javascript class Form extends React.Component { state = {}; submit = (e) => { e.preventDefault(); this.props.onSubmit(this.state); }; setValue = (e) => { const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value; this.setState({ [e.target.name]: value }); }; render({ children, onSubmit, ...props } = this.props) { return (
{children({ setValue: this.setValue })}
); } } ``` The trick here is that the `children` props of our component is actually a function — one that we call with an object exposing some API from the `
` component to its children — in this case just the `setValue` function. We also automatically prevent the form's default behavior of submitting to the server and call the provided `onSubmit` prop with the collected form values instead. ## 4️⃣ The Complete Code Our `` component is now reusable across our entire application, and even across many different React applications if you think about it! So let's use it in our `` component: ```javascript class App extends React.Component { submit = (values) => { window.alert(`Hey, ${values.name}! You ordered some ${values.food}!${values.isDessert ? ' A lovely dessert!' : ''}`); } render() { return ( {({ setValue }) => (
)} ); } } ``` We wrapped our inputs with the `
` component and converted the children into a *function returning children* which exposes the `setValue` method from the `` component. Notice that to avoid listing children as an array we wrapped them in an extra element, more specifically — a fieldset. One cool thing about the fieldset element is that disabling it also automatically disables all of its child inputs too! ## 💯 Bonus We now have a very basic `` component we can use in our React application. As a thought exercise, consider some additional ways we could enhance that component: - Allow setting the initial values and make child inputs [fully controlled](https://reactjs.org/docs/forms.html#controlled-components) - [Define children as a function in `propTypes`](https://reactjs.org/docs/render-props.html#using-props-other-than-render) in order to have a more robust `` component that throws an error when `children` isn't a function - If you want to make `` a `React.PureComponent` to improve performance, you might also want to look into [the implications of using render props with pure components](https://reactjs.org/docs/render-props.html#be-careful-when-using-render-props-with-reactpurecomponent) - If the form is more complex, consider using dot notation in the input's `name` attribute to handle deeply nested data — for example the component `` would return the object `{ name: { first: 'Albert' } }` - Incorporate data validation and error messages in your form by utilizing the `e.target.validationMessage` property and other [HTML5 Form Validation](https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation) features - Try using [JSON schema](https://json-schema.org/understanding-json-schema) to both generate form inputs *and* validate them — this also allows you to share data validation rules with other environments, for example a backend server. ## 🏁 Conclusion Like I said at the beginning, learning often comes through deliberate practice and repetition. And after going through the process of building a simple `` component myself, I think I understand the trade-offs well enough to finally switch to a library like [Formik](https://github.com/jaredpalmer/formik). Hope this leaves you too with plenty of enthusiasm for forms — have fun building! ```` ### Using Angular's Dependency Injection Sparingly - Markdown: https://vdsabev.github.io/using-angulars-dependency-injection-sparingly.md - HTML: https://vdsabev.github.io/using-angulars-dependency-injection-sparingly/ ````markdown --- status: "published" title: "Using Angular's Dependency Injection Sparingly" date: "2018-10-01" description: "How excessive abstraction slowed down our unit tests to a crawl." image: "https://github.com/user-attachments/assets/52b77f8a-0a08-438e-8b01-f4c9b8838d59" imageAlt: "Photo by Ricardo Gomez Angel on Unsplash" author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["angular","dependency injection","software development","testing"] navigationIndex: 0 --- Photo by [Ricardo Gomez Angel](https://unsplash.com/photos/R3K93pdG0t0?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/@ripato?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) Our team had been working on a mid-sized Angular application for the past few months. Unfortunately, we hadn't started with testing in mind at first. As the project kept growing and at the same time we were slowly approaching our goal of > 90% code coverage, we started asking these questions more and more often: - Were the tests we were writing meaningful? - Were they sufficiently decoupled from our code? - Was code coverage really an adequate metric for reliable software? To find an answer, we had an internal team discussion about our unit testing strategy and using Angular's built-in Dependency Injection mechanism in particular. In this post we'll now go over some of the points we touched. ## Requirements - Basic knowledge of unit testing - Familiarity with TypeScript and the [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection#Examples) pattern (DI) used in Angular (specifically, [Constructor Injection](https://en.wikipedia.org/wiki/Dependency_injection#Constructor_injection)) > In the interest of brevity, I'll skip most of the "plumbing" code like imports and `@Injectable` decorators. In doing so, I hope that both those of you who *are* familiar with Angular and those who aren't will be able to follow along. ## Use Case As an example, let's take a very simple service for working with strings: ```typescript export class TextService { startsWith(text: string, query: string): boolean { return text.indexOf(query) === 0; } } ``` Now inject `TextService` into another service: ```typescript export class UrlService { constructor(private textService: TextService) { } isSecure(url: string): boolean { return this.textService.startsWith(url, 'https:'); } } ``` Finally, let's write some tests for our `UrlService`: ```javascript describe(`UrlService`, () => { // ... create the instance of urlService it(`should return true if URL starts with 'https:'`, () => { expect(urlService.isSecure('https://medium.com')).toBe(true); }); it(`should return false if URL starts with 'http:'`, () => { expect(urlService.isSecure('http://medium.com')).toBe(false); }); }); ``` But how was the instance of `urlService` created? There are a few different ways to do it, each with its trade-offs. ### Angular's TestBed This is the approach most often recommended by tutorials and documentation, and the one we started with. Our experience was that there were lots of mysterious bugs using it, for example this issue when calling `fixture.detectChanges()`: ``` SyntaxError at viewWrappedDebugError (node_modules/@angular/core/bundles/core.umd.js:9844:15) at callWithDebugContext (node_modules/@angular/core/bundles/core.umd.js:15146:15) at Object.debugCheckAndUpdateView [as checkAndUpdateView] (node_modules/@angular/core/bundles/core.umd.js:14673:12) at ViewRef_.Object..ViewRef_.detectChanges (node_modules/packages/core/esm5/src/view/refs.js:508:14) at ComponentFixture.Object..ComponentFixture._tick (node_modules/@angular/core/bundles/core-testing.umd.js:220:32) at node_modules/packages/core/esm5/testing/src/component_fixture.js:108:10 at ZoneDelegate.Object..ZoneDelegate.invoke (node_modules/zone.js/dist/zone-node.js:388:26) at ProxyZoneSpec.Object..ProxyZoneSpec.onInvoke (node_modules/zone.js/dist/proxy.js:128:39) at ZoneDelegate.Object..ZoneDelegate.invoke (node_modules/zone.js/dist/zone-node.js:387:32) at Object.onInvoke (node_modules/packages/core/esm5/src/zone/ng_zone.js:594:10) at ZoneDelegate.Object..ZoneDelegate.invoke (node_modules/zone.js/dist/zone-node.js:387:32) at Zone.Object..Zone.run (node_modules/zone.js/dist/zone-node.js:138:43) at NgZone.Object..NgZone.run (node_modules/@angular/core/bundles/core.umd.js:4615:69) at ComponentFixture.Object..ComponentFixture.detectChanges (node_modules/packages/core/esm5/testing/src/component_fixture.js:108:10) ``` You can imagine exactly how much fun debugging that was. **Spoiler alert**: we had forgotten to provide a service when configuring the `TestBed`. A similarly weird issue was caused by passing `{}` instead of `[]` to a component's input. Anyway, let's use the `TestBed` properly: ```javascript describe(`UrlService`, () => { let urlService: UrlService; beforeEach(() => { TestBed.configureTestingModule({ providers: [UrlService] }); urlService = TestBed.get(UrlService); }); it(`should return true if URL starts with 'https:'`, () => { expect(urlService.isSecure('https://medium.com')).toBe(true); }); it(`should return false if URL starts with 'http:'`, () => { expect(urlService.isSecure('http://medium.com')).toBe(false); }); }); ``` After figuring out all other weird issues, it looks simple enough — we configure what the different providers in our application are, then use `TestBed.get(UrlService)` to get a reference to the instance of the injected service. However, after using it for a while, we noticed `TestBed` was painfully slow on Windows — for some of our developers up to 3 times slower than on UNIX systems. We looked for ways to speed the test runs up, but in the end even [switching to Jest](https://www.xfive.co/blog/testing-angular-faster-jest) didn't seem to help much. This was a no go — unit tests should only take a few seconds to run, not 30! If tests are slow they lose their ability to provide rapid feedback during development. So `TestBed` was out of the game. ### Manually injecting dependencies While looking for other approaches besides `TestBed` we found something in the [Angular docs](https://angular.io/guide/testing#testing-without-beforeeach): > There's another school of testing that… prefers to create classes explicitly rather than use the `TestBed`. Here's an example with our `UrlService`: ```javascript describe(`UrlService`, () => { let urlService: UrlService; beforeEach(() => { const textService = new TextService(); urlService = new UrlService(textService); }); it(`should return true if URL starts with 'https:'`, () => { expect(urlService.isSecure('https://medium.com')).toBe(true); }); it(`should return false if URL starts with 'http:'`, () => { expect(urlService.isSecure('http://medium.com')).toBe(false); }); }); ``` Overall, this approach looked straightforward (and it was also running much faster), but it did raise a question — what would happen if we added a dependency to `TextService` itself? ### Injecting secondary dependencies Let's get another level of Dependency Injection going by adding a `SecurityService` that checks the application for some common security errors: ```typescript export class SecurityService { constructor(private urlService: UrlService) { } getSecurityErrors(url: string): boolean { const errors = []; if (!this.urlService.isSecure(url)){ errors.push('This application is insecure!'); } // ... some other security checks return errors; } } ``` Now let's add some tests using the approach where we instantiate services manually — and here's where things get unwieldy: ```javascript describe(`SecurityService`, () => { let securityService: SecurityService; beforeEach(() => { const textService = new TextService(); const urlService = new UrlService(textService); securityService = new SecurityService(urlService); }); it(`should return no errors if URL starts with 'https:'`, () => { expect( securityService.getSecurityErrors('https://medium.com') ).toHaveLength(0); }); it(`should return one error if URL starts with 'http:'`, () => { expect( securityService.getSecurityErrors('http://medium.com') ).toHaveLength(1); }); }); ``` Great, so now we also have to instantiate `TextService` - a piece of code the `SecurityService` doesn't use directly and shouldn't really know anything about?! Me and my colleague called this "mocking the world". Sure, we've gained speed and (at first look) simplicity by going around Angular's `TestBed`, but we'd completely lost any benefits of the inversion of control that Dependency Injection provides! ### No Dependency Injection Of course, we could write all those services without using DI altogether. That's exactly what we ended up doing in many parts of our application that were dealing with simple data processing — replacing the service class with a simple object: ```typescript export const textService = { startsWith(text: string, query: string): boolean { return text.indexOf(query) === 0; }, }; ``` Then our tests wouldn't have to deal with DI at all. Of course, the trade-off is that we can no longer inject any Angular dependencies in those services. Once we start using the DI pattern anywhere in our application, mixing it into code that doesn't becomes hard. ## Conclusion So is it that dependency injection is just bad? I believe as with most other tools it should be used sparingly. DI is meant to make things simpler, and if it doesn't — use something else. For example [Jest mocks](https://jestjs.io/docs/en/mock-functions) virtually eliminate the need for using DI when unit testing. We also probably wouldn't write a unit test to check whether clicking on a button in one of our components calls some method. Perhaps we should invest more time in writing [E2E tests with Cypress](https://www.cypress.io) or [Integration tests](https://blog.kentcdodds.com/write-tests-not-too-many-mostly-integration-5e8c7fff591c) instead. How has your experience with Dependency Injection and unit testing turned out? ```` ### Giving Credit and Taking Responsibility in a Team - Markdown: https://vdsabev.github.io/giving-credit-and-taking-responsibility-in-a-team.md - HTML: https://vdsabev.github.io/giving-credit-and-taking-responsibility-in-a-team/ ```markdown --- status: "published" title: "Giving Credit and Taking Responsibility in a Team" date: "2018-09-12" description: "Whether we succeed or fail, it helps to know how to best represent our team in front of upper management or clients." image: "https://github.com/user-attachments/assets/3e56c987-b2db-47b6-a403-682a78f253a9" imageAlt: "silhouette of people by Tobias Mrzyk on Unsplash" author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["personal responsibility","psychology","teamwork"] navigationIndex: 0 --- "silhouette of people" by [Tobias Mrzyk](https://unsplash.com/@tobiasmrzyk?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com/?utm_source=medium&utm_medium=referral) Speaking from personal experience, it's too easy to fall into the trap of putting our colleagues on a pedestal when they succeed, think less of them when they fail, or otherwise unwittingly participate in creating and sustaining what I like to call "office politics". I actively try to counteract this phenomenon by giving as much credit as possible to **my colleagues**, while framing as a **team effort** those contributions I could otherwise claim to be mine alone. Conversely, I try to **have a teammate's back** when they make a mistake, while taking **personal responsibility** when I myself screw up. ## 📑 Examples Whenever I've done something by myself I try to say this: > *🔴* When **I** make a mistake → "**I** made a mistake" > > *🔵* When **I** accomplish something → "**We** accomplished this" What about a specific colleague of ours — let's call them Alex: > *🔴* When **Alex** makes a mistake → "**We** made a mistake" > > *🔵* When **Alex** accomplishes something → "**Alex** accomplished this" Finally, if we happen to be the team lead we could say something like this: > *🔴* When **the team** makes a mistake → "**I** made a mistake" > > *🔵* When **the team** accomplishes something → "**They** accomplished this" ## 🤔 What good does this do? Whether it comes from inside or outside the team, blame erodes trust. Some people will subconsciously blame others for failures, even when they say they don't. To minimize this affecting our colleagues, we can try to take all negatives upon ourselves — especially in a leadership position, both our team *and* our managers will respect the fact we're taking responsibility for failures instead of throwing someone else under the bus. Similarly, they'll respect the fact we give others (but not ourselves) credit — we all subconsciously know a team's success is also the leader's success anyway, we might as well be humble about it 😉 ## 👣 Further steps In addition to owning the mistakes we make, it's also important to transition from blame to responsibility — blame is in the past, while responsibility is in the present. To do this, we can also outline the *next steps* we'll take to either amend the issue, or prevent it from happening again. > Saying sorry is not enough — make sure it doesn't happen again. Finally, while covering for other people's mistakes in front of management or clients is commendable, giving our team feedback in private is crucial — after all failure is only valuable when we learn from it! ## 💭 Your thoughts? Anything else you'd like to add? Has someone had your back when *you* made a mistake? Or gave you credit when they didn't have to? Did this make a difference for your team? ``` ### React: Injecting Component Props with ES7 Decorators - Markdown: https://vdsabev.github.io/react-injecting-component-props-with-es7-decorators.md - HTML: https://vdsabev.github.io/react-injecting-component-props-with-es7-decorators/ ````markdown --- status: "published" title: "React: Injecting Component Props with ES7 Decorators" date: "2018-08-20" description: "Experimenting with decorators to make writing and maintaining React components more ergonomic (subjectively)." image: "https://github.com/user-attachments/assets/07989c52-ef37-4619-9696-c05f040bb65a" imageAlt: "Photo by Thomas AE on Unsplash" author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["javascript","react","software development"] navigationIndex: 0 --- Photo by [Thomas AE](https://unsplash.com/@thomasae?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com/?utm_source=medium&utm_medium=referral) While I'm sure many prefer to stick to more traditional ways of writing components, I often try to bring some features that make sense from one framework to the other — in this case, Preact's injection of `props` and `state` into a component's `render` method. > **Disclaimer:** after reading through [this GitHub issue](https://github.com/facebook/react/issues/1387) I've decided to stop using the `@Inject` decorator in favor of parameter destructuring: ```javascript class UserLink extends Component { ... render({ user } = this.props, { showTooltip } = this.state) { return (...); } } ``` > This works exactly the same as the decorator while having the benefit of not needing to import an external utility function. > > I think this article still has some value, and at the very least should help people understand the original problem I was trying to solve. If you're still interested, keep reading *😉* ## 🤹‍ Skills To effectively follow along with this article, you'll need knowledge of: - JavaScript — intermediate - React/Preact— intermediate ## ⭕ Stateless Component Let's start with a regular stateless component — in this case, displaying the link to a user. It accepts an object containing the ID, first name, and last name: ```jsx const UserLink = ({ user }) => ( @{user.id}
{user.firstName} {user.lastName}
); ``` If we put a `
` element in the HTML template, we can render the component like this: ```javascript ReactDOM.render( , document.querySelector('main') ); ``` The component displays the user's ID, and on hover also shows the full user name in a tooltip. Here's a CodePen: ## 🔴 Class Component As a component evolves, its requirements change. We often rewrite stateless function components to class components and vice versa. Now our designer comes back with a fresh design idea — instead of on hover, we will show the tooltip when clicking a small button on the side. To do this, we need internal state and so rewrite the component as a class: ```javascript class UserLink extends Component { state = { showTooltip: false, }; toggleTooltip = () => { this.setState((state) => ({ showTooltip: !state.showTooltip })); }; render() { return ( @{this.props.user.id}
{this.props.user.firstName} {this.props.user.lastName}
💬
); } } ``` And here's another CodePen: ## 🔨 Refactoring There are lots of references to `this` in the class component's `render` method which make it significantly more verbose. Also, if we decide to move the user from `this.props` to `this.state` for whatever reason, we would rewrite all those references as well. To ease the transition between stateless and class component, we can use the neat [destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring) syntax. Notice how in the next `render` method references to `this.props` and `this.state` are isolated to the top, while JSX is essentially the same as in the stateless component: ```javascript class UserLink extends Component { ... render() { const { user } = this.props; const { showTooltip } = this.state; return ( @{user.id}
{user.firstName} {user.lastName}
💬
); } } ``` There's also an even neater way to destructure an object with nested properties: ```javascript render() { const { props: { user }, state: { showTooltip }, } = this; return (...); // Same JSX as before } ``` > *📝* Technically, we can destructure objects on a single line, like this: > > `const { props: { user }, state: { showTooltip } } = this;` > > However, [Prettier](https://prettier.io) (which I use in every project) insists on formatting nested destructuring as multiple lines. This already looks better, and yet — could we keep `props` and `state` destructuring in the signature of the `render` method? ## ⚛ Preact That is, in fact, exactly what [Preact](https://preactjs.com) does by default. [Example from the docs](https://github.com/developit/preact/tree/e30cc390d37d5eaaf2a86316ea4086580631a40d#examples): ```javascript class Link extends Component { render(props, state) { return {props.children}; } } ``` Preact calls the component's `render` method with `this.props` as the first argument and `this.state` as the second, saving us some work! However, many projects use React instead of Preact, and will continue doing so. And if we used this Preact feature and then switched to React would we really want to rewrite all of our `render` methods? How do we get the same behavior with minimal effort then? > *📝 While e*xtending the base `*React.Component*` class and overriding the `*render*` method seems like an obvious solution at first, you're likely to bump into weird errors, and [the React docs generally encourage using composition over inheritance](https://reactjs.org/docs/composition-vs-inheritance.html). > > Do let me know if you've seen someone figure this out! ## 💉 Inject While looking for alternatives, I stumbled upon [an article](https://medium.com/@goncalvesjoao/react-es7-decorators-how-to-inject-props-to-your-render-method-27a0a7973106), then [this set of helper functions](https://github.com/goncalvesjoao/relpers/blob/1d122039ad6a7ee8966ff70ac3f13b6eb9b64dfc/src/injectProps.js), which ultimately lead me to an answer: ```javascript const Inject = (...keys) => (target, propertyKey, descriptor) => { const originalMethod = descriptor.value; if (typeof originalMethod !== 'function') { throw new SyntaxError(`@Inject can only be used on class methods, not: ${originalMethod}`); } return { ...descriptor, value(...args) { const values = keys.map((key) => this[key]); return originalMethod.apply(this, [...values, ...args]); }, }; }; export default Inject; ``` This function modifies a class method to call the original method it was applied on with some predefined properties of the instance object `this`. Here's a usage example: ```javascript class UserLink extends Component { ... @Inject('props', 'state') render({ user }, { showTooltip }) { return (...); // Same JSX as before } } ``` Unlike Preact's behavior, the `Inject` decorator is explicit — it can inject any arbitrary property of the class component at any parameter position. In this case it mimics Preact in that it injects `this.props` as the first parameter and `this.state` as the second parameter. Then we can destructure them right there in the method signature! > *📝* If you've ever used Angular, then you know dependency injection, and `Inject` looks a lot like it. ## 🤙 Trade-offs The main advantage here is using the `Inject` decorator makes render code more flexible. This means we can refactor components fearlessly while keeping our views unchanged and managing `props` and `state` at the edges of the `render` function (i.e. in the method signature). The main disadvantage is we have to import the `Inject` decorator everywhere we use it, and it adds a bit of **magic** to every component we write. And for every bit of **magic** we add to our code base, a new developer on the team might have some trouble understanding and using it, even if it's well documented. Also, if we're rewriting an existing Preact code base into React, the decorator isn't an ideal solution, as we still have to inject `props` and `state` into the render method of every single component! Finally, as of the writing of this article, decorators are still a [Stage 2 experimental feature](https://github.com/tc39/proposals), so use them with caution! ## 🏁 Conclusion What do you think — would you use something like the `Inject` decorator, or stick to more stable and straightforward JavaScript features like assignment with destructuring? Do you regularly use any other decorators or utility functions when writing React components? ```` ### Dribbble Designs in Code (part 2 — Editorial Exploration) - Markdown: https://vdsabev.github.io/dribbble-designs-in-code-part-2-editorial-exploration.md - HTML: https://vdsabev.github.io/dribbble-designs-in-code-part-2-editorial-exploration/ ````markdown --- status: "published" title: "Dribbble Designs in Code (part 2 — Editorial Exploration)" date: "2018-06-28" description: "This is the second in a series of posts on coding designs from Dribbble." image: "https://github.com/user-attachments/assets/ed8fdd48-4f81-4a73-9161-1271c9ddbafb" author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["css","design","software development"] navigationIndex: 0 series: "Designs in Code" --- It's close to midnight on a Saturday and I'm halfway through implementing another Dribbble shot for this series. After getting increasingly bored with it for the past few hours, I start browsing around for more inspiration. Ending up back on Dribbble again, I find myself staring at this design when I suddenly realize how much more I like it for part 2: Dribbble Design - Editorial Exploration by Boyan Kostov [Editorial Exploration by Boyan Kostov](https://dribbble.com/shots/4571832-Editorial-Exploration) It has an attractive layout, overlapping images, the promise of a challenge, and is a smooth transition from part 1. Inevitably, a question comes to mind: > *🤔* Should I abandon the hard work done in the past couple of hours for something new and exciting? After a few moments of consideration, I give into the temptation and start a new post. Here it is. ## 🤹‍ Skills In this post, I explore using [CSS grid](https://developer.mozilla.org/en-US/docs/Web/CSS/grid) and [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables) in more detail. If this is your first time working with CSS grid, I recommend watching [Morten Rand-Hendriksen](https://twitter.com/mor10)'s talk: [CSS Grid Changes Everything (About Web Layouts)](https://www.youtube.com/watch?v=txZq7Laz7_4) — he does a great job of defining the basic terminology and gives an overview of the different applications of this tool. If you don't have a code editor set up, prefer an environment where you can start right away, or want to dive straight into the code, [see the Codepen](https://codepen.io/vdsabev/pen/zaRvjE) I've prepared. ## 📈 Analysis Just like in part 1, we'll go through the following steps: - Add content and wrap it with HTML elements - Add SVG icons - Style the content & icons with CSS - Adjust the sizing and spacing to be closer to the expected result This design however is a bit more complex — it has overlapping images, more content, and upon closer look could potentially have several pages worth of vertical space. Is doing a little analysis beforehand in order? ### 📱 Mobile Layout In part 1 we didn't have a mobile design, and so didn't bother implementing one. Let's adopt a better practice starting now — do a linear mobile layout first, then progressively enhance it for larger resolutions. This way the page is usable even if the browser doesn't support CSS grid (which is [increasingly rarer these days](https://caniuse.com/#search=grid)). > *📝* This approach is also something I picked up from Morten Rand-Hendriksen's talk: [CSS Grid Changes Everything (About Web Layouts)](https://www.youtube.com/watch?v=txZq7Laz7_4) —if you haven't seen it yet, it's well worth the watch! ### ⬜ Grid Layout At first look, we have 3 distinct sections: - Navigation — back icon on the left and an icon to show there's more content below - Content in the middle, comprised of logo, article header section, and some text - Overlapping images and a button on the right With multiple rows and columns that are aligned with each other, this looks like a great fit for CSS grid! We can also consider how the content will behave when scrolling down the page — would it maybe make sense for the user to always see the navigation icons? ### 📐 CSS Units The design in part 1 used the `vw` unit to scale content proportionally on any screen regardless of the resolution. In practice, this only worked for desktop browsers in landscape orientation. This time we'll use the `rem` and `em` units. Why not good old pixels, you might say? The short answer is accessibility — some users set a default font-size other than 16px for their browsers, and with pixels text and spacing will probably scale poorly for them. Here's a couple of articles that explain the implications in more detail: - [Pixels vs. Ems: Users DO Change Font Size](https://medium.com/@vamptvo/pixels-vs-ems-users-do-change-font-size-5cfb20831773) - [EM vs REM vs PX — Why you shouldn't "just use pixels"](https://engageinteractive.co.uk/blog/em-vs-rem-vs-px) A short excerpt (emphasis mine): > Set the root HTML font-size as a percentage. That's a **percentage of the user's default browser font-size**. A typical method is to set the HTML font-size to 62.5%. That's because **62.5% of 16px** (typical default browser font-size) is **10px**. That would still make **1.6rem = 16px**. This now means that if the user's default browser font-size is changed to, for example, **20px, 1.6rem would now equal 20px**. So if your user wants bigger fonts, let them. Happy designer. Happy developer. All numbers are still easy to work with. ## 💻 Implementation ### 🔠 Content Our fictitious publication is called the "Travel Times". The text passage is from the exceptional work of Douglas Adams — [The Hitchhiker's Guide to the Galaxy](https://www.goodreads.com/quotes/4637-o-deep-thought-computer-he-said-the-task-we-have). We'll wrap that in a [main](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/main) element and add distinct elements with classes that we can style later: ```html

Travel in time by sea

by Jane Walkinson

Both of the men had been trained for this moment, their lives had been a preparation for it.

Both of the men had been trained for this moment, their lives had been a preparation for it, they had been selected at birth as those who would witness the answer, but even so they found themselves gasping and squirming like excited children.

``` Which looks like this: Initial content Something else we can do is link to the author: ```html ``` The `[rel](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-rel)` attribute of a link denotes its relationship to the original document — in this case, the article's author. > *📝* I've hosted the images on GitHub so that you can easily use them while following along with this post instead of having to start a local server or upload files somewhere. Notice we reserved an empty container for the images right between the header and text sections — since we're doing the mobile layout first, it makes sense to show the images to the user *before* they've gone through all of the text, but *after* they've read the article's title and subtitle so they know what the images represent: ```html
``` That `+` is a placeholder for the icon we'll insert next. ### 🖼 Icons Once again the [Material Design Icons](https://materialdesignicons.com) website can help us find what we're looking for: - `plus-circle-outline` - `arrow-left` - `arrow-down` The `arrow-left` and `arrow-down` icons are part of the navigation and so will fit nicely at the top of the content next to the logo, which in this case is a simple text element. Here's what all of our content looks like if we (temporarily) add a basic styling workaround of `img, svg { max-width: 10rem; }` to make images and SVGs fit on one page: Finished content with images and icons Time to add some styling! ## 💅 Styling First things first — let's add [Normalize.css](https://necolas.github.io/normalize.css) into the `head` tag of our page: ```html ``` This makes styles render consistently across different browsers, for example spacing around `h1` elements. ### 🎨 Color Palette Using a color picker tool (like in Photoshop or [Paint.NET](https://www.getpaint.net)), we can pick out the basic colors from the design and define some CSS variables on the root element where they can be easily found and, if necessary, changed: ```css :root { --foreground: #1d2c2f; --foregroundLight: #384345; --neutral: #adb2b3; --neutralLight: #fcfcfc; --primary: #f8f72c; } ``` Naming colors according to their purpose instead of their value (e.g. `primary` instead of `yellow`) means we can easily modify that value later without also having to change the name. And adding suffixes like `light` / `lighter` / `lightest` or `dark` / `darker` / `darkest` is a simple rule of thumb for maintaining a consistent color palette. > *📝* It's a personal preference of mine to use `--camelCase` when naming CSS variables. > > One reason is it's a bit easier to select and copy the whole name at once. Another is that I will often define CSS variables in [an external script](https://github.com/vdsabev/vdsabev.com/blob/89313aa82344024b106924cc8cc31040e5160b24/src/style.ts) to make them reusable throughout the code or even in [webpack plugins](https://github.com/cmless/cmless/blob/808ba26722bd2c8c1ea05ae7def9c18606c8b50c/src/rules/style.js#L25). > > Feel free to use `--kebab-case` or any other naming convention you like, as long as you're consistent with it. ### 🔠 Fonts There are 2 distinct fonts — one serif for the text content and another sans serif for the logo, images button, and author. Unfortunately, the original design files were lost, so we'll have to do a bit of improvising 😅 The ordinary Arial should work well enough for UI elements, and for the text we'll once again use [Google Fonts](https://fonts.google.com) to find a suitable candidate like [Merriweather](https://fonts.google.com/specimen/Merriweather): ```html ``` We can now add a couple more variables to the root element: ```css --fontText: 'Merriweather', serif; --fontUI: 'Arial', sans-serif; ``` ### 📱 Mobile Layout Using Google Chrome's developer tools, let's switch to responsive mode and select a common mobile device like iPhone 6/7/8 to see how the page looks: Finished content as seen in responsive mode in Chrome's developer tools Since we already have a color palette and fonts, we can use them to style the background, text, and buttons. It's also time to remove the sizing hack for images and SVGs and set a new size. Finally, we'll add some spacing between different elements, for example padding to the `body` element: Styled mobile layout The design is really shaping up now, except the down icon sticks out a bit too much! Let's take care of that. ### 📜 Scrolling In practice, I rarely see designers draw scrollbars —they may cut off some content to indicate overflow, but that doesn't always give specific information about which elements are scrollable. Showing how the page looks with additional content, for example several paragraphs of text, can be beyond a designer's initial goal of creating a quick Dribbble shot. When a design is actually meant to be implemented in code rather than only shown, we need to think about the behavior of the page on different devices and resolutions. One option for us is to use `[position: sticky](https://caniuse.com/#feat=css-sticky)` to stick the back icon to the top, while the down icon, which indicates there is more content below, sticks to the bottom of the page. Since those icons will be hard to make out when displayed over content, we'll also add a neutral background and some border radius: ```css .back-icon, .down-icon { position: sticky; border-radius: 50%; background: var(--neutralLight); } .back-icon { top: 1rem; } .down-icon { bottom: 1rem; } ``` One caveat is that for an element to be positioned properly within its parent, it has to be at the beginning of the content to stick to the top, or at the end to stick to the bottom. This means we should move the HTML for the down icon from *before* the article's title to *after* the article's text. Here's how that looks when the page is scrolled down: Styled mobile layout with improved design for scrolling ### 🤙 Responsive Design with CSS Variables In our case, the font-size and spacing from the original design wouldn't have worked well on mobile devices, so we used smaller values. How do we increase them on wider resolutions? One option is to write media queries targeting the classes whose properties we want to change. Something like this: ```css body { padding: 3rem; } .title { font-size: 5rem; } .subtitle { font-size: 2rem; } @media (min-width: 48em) { body { padding: 3.6rem; } .title { font-size: 7rem; } .subtitle { font-size: 2.2rem; } } @media (min-width: 64em) { body { padding: 6.8rem; } .title { font-size: 10rem; } .subtitle { font-size: 2.4rem; } } ``` This approach is fine in simple use cases, but doesn't scale well for more complex ones, especially when multiple properties depend on each other like a certain element's padding being half of another one's. An especially useful property of CSS variables is that their values cascade down to all children of an element. This means we can write maintainable and concise CSS while overriding variable values for different device resolutions: ```css /* First, define variables and responsive breakpoints */ :root { --spacing: 3rem; --titleFontSize: 5rem; --subtitleFontSize: 2rem; } @media (min-width: 48em) { :root { --spacing: 3.6rem; --titleFontSize: 7rem; --subtitleFontSize: 2.2rem; } } @media (min-width: 64em) { :root { --spacing: 6.8rem; --titleFontSize: 10rem; --subtitleFontSize: 2.4rem; } } /* Now, use the variables in classes without having to think about responsive breakpoints! */ body { padding: var(--spacing); } .title { font-size: var(--titleFontSize); } .subtitle { font-size: var(--subtitleFontSize); } ``` If we now use the `spacing` variable in multiple places (which we'll do in a minute), we can define how its value changes on different resolutions from a single place in our code instead of having to dig around class definitions. ### ⬜ Grid Layout Now that we got our page working on mobile devices and made some of the values responsive using CSS variables, how can we divide the layout into grid cells and position elements in them? Let's first draw some grid tracks on top of the original design: Original design with grid tracks overlaid We have the following rows: - Back icon and logo - Title, author, and subtitle - Article text And the columns, separated by different horizontal space: - Navigation icons - Logo, header, content, and text - Images, spanning across the full height of the page > *📝* It's possible to divide the images into rows and columns and also fit them into the same grid, but that could behave in an unpredictable way if the article's title, subtitle, or the images themselves have a completely different height. > > After all we're not just coding a **drawing** of a design here, but thinking about how this design would behave in a **real-world** online magazine or news publication. > > It's all good, we'll find a way to take care of the later 😉 To achieve all of this, we'll use a combination of empty columns and `grid-template-areas`, but only for resolutions wider than 56em (or 896px if the base font-size of the browser is 16px) to preserve the mobile-first design we started with: ```css @media (min-width: 56em) { main { display: grid; grid-template-rows: auto auto 1fr; grid-template-columns: auto calc(var(--spacing) / 2) 1fr var(--spacing) 37.5%; grid-template-areas: "back-icon . logo . images" ". . header . images" "down-icon . text . images"; } } ``` Notice how empty columns are defined in the `grid-template-areas` with a dot `.`, and in the `grid-template-columns` they take up `spacing / 2` and `spacing` respectively. The navigation icons take up as much space as they need (auto), the images are 37.5% of the grid's width, and the central content takes up all the remaining space (1fr). > *📝* I came up with these numbers by measuring the pixels between elements in the original design and approximating the ratio between them. Whenever possible, reach out to your design team — they might have more insights into the relationship between different elements! Now we need to assign every element to an area in our grid, vertically center the back icon and logo, and push the down icon all the way to the end of its cell: ```css @media (min-width: 56em) { main { ... } /* same as before */ .back-icon { grid-area: back-icon; align-self: center; } .logo { grid-area: logo; align-self: center; } .down-icon { grid-area: down-icon; align-self: flex-end; } header { grid-area: header; } .text { grid-area: text; } .images { grid-area: images; } } ``` And voilà: Styled content with CSS grid layout We're almost there! ### ⬜ Image Grid Layout There are different ways to get elements to overlap — floats, negative margins, absolute positioning — since we already used CSS grid once, let's do it again! Here are the tracks: Images with grid tracks overlaid - First, there's the primary image which takes up one row and both columns. - Then the button occupies the second row and first column. - Finally, the secondary image overlaps the primary one, starting from the end of the second row and extending into the second column. This is achieved with the following CSS: ```css .images { grid-area: images; /* same as before */ display: grid; grid-template-rows: auto auto 1fr; grid-template-columns: 7fr 9fr; grid-template-areas: "primary primary " "more secondary"; } .images__primary { grid-area: primary; } .images__secondary { grid-row: primary / secondary; grid-column: secondary; align-self: flex-end; } .images__more { grid-area: more; } ``` Notice how we define the first two rows as auto, then add one more with size of 1fr to take up the remaining space. There are also other ways to achieve the same effect with CSS grid, for example by using `grid-auto-rows: min-content`. Once again, the ratio of 7fr / 9fr between the columns was approximated by measuring the pixels in the design. Something else to note is how we overlap the two elements— the secondary image takes up both rows and is pushed to the end of the container, so is displayed above the primary image and at the same time aligned with the bottom of the button. ### 🏁 Finish Line A few more adjustments to spacing and sizing and we have our finished design: Finished design Let's see the original once again for comparison: Original design Some notable changes are that spacing, fonts, icons, and images are slightly different as the original design files were lost and we only had a PNG to work with. On the other hand, our design handles mobile devices and scrolling gracefully! Here's the actual code running on CodePen: ## 💭 Conclusion Hey, you made it all the way to the end! Double points if you also read [Dribbble Designs in Code (part 1 — Venue Landing Page)](https://vdsabev.github.io/dribbble-designs-in-code-part-1-venue-landing-page) 😉 In this post: - We went through the process of analyzing a design. - After that, we added the content and icons, applied a mobile-first style, and improved its behavior when scrolling. - Finally, we progressively enhanced the design using CSS grid and even made "gridception" happen by putting a grid within a grid! It took me a while to code this and even more to write the post, and I'm delighted with how it turned out! The original design was never meant to be implemented in code so we had to make many decisions about how it should behave in different situations. Was this post helpful? Would you like there to be another one? Anything different you want to see? There was some great feedback on my previous post — I'd love to hear what you think of this one — say hello on [Twitter](https://twitter.com/vdsabev). ```` ### Dribbble Designs in Code (part 1 — Venue Landing Page) - Markdown: https://vdsabev.github.io/dribbble-designs-in-code-part-1-venue-landing-page.md - HTML: https://vdsabev.github.io/dribbble-designs-in-code-part-1-venue-landing-page/ ````markdown --- status: "published" title: "Dribbble Designs in Code (part 1 — Venue Landing Page)" date: "2018-06-14" description: "This is the first in a series of posts on coding designs from Dribbble." image: "https://github.com/user-attachments/assets/e0e5dec7-7398-44e9-80e3-4dc1e91822aa" author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["css","design","software development"] navigationIndex: 0 series: "Designs in Code" --- Being a web developer, I will admit to occasionally having that anxious feeling when receiving a new design mockup, thinking something like: - "This will take too long to implement with the technology we have today and all the old browsers we have to support" - "This design is messy and inconsistent and won't work even for simple use cases" - "Designers have zero consideration about all the hacks I'll have to add to get that layout to work" I'm assuming that's because of a deep-rooted fear of failure which, if overcome, could ultimately help me become a better professional. Having recently started working more closely with designers, I've come to appreciate their process — and while it can take a bit of compromise on both sides to get a concept to a working state, a creative design can significantly push my abilities as a developer. To dispel my own fears and doubts, I browsed around some of my colleagues' designs with the intention of picking one to implement. It had to be simple enough for me to realistically be able to do it in a few hours, while at the same time interesting enough to learn a thing or two. As this is the first time I've challenged myself this way, I'm starting small with [this design by Boyan Kostov](https://dribbble.com/shots/4658658-Venue-Landing-Page): Venue Landing Page by Boyan Kostov for Perspektiva — Dribbble ### Required Knowledge To effectively follow along with this article, you'll need the following skills: - HTML — basic - SVG — basic - CSS — intermediate If you want to dive straight into the code instead, [you can find it on CodePen](https://codepen.io/vdsabev/pen/oyWZrQ). > *📝* The blending modes used in this example have only been tested in Chrome, Firefox, and Safari, so if you run it in another browser chances are it will look different! ## Content ### Text For me, the first step in implementing a design is adding the content. Generally speaking, if you have the opportunity to copy the data from somewhere (or use the same API), that's great! In my case, I typed it by hand: ``` Underwater Expo 2018 When you're underwater with goggles on, a couple of your senses are taken away, and it becomes this purely visual thing. It's just you and yourself. 7-24 July 2018 Arrow Icon Ticket Icon ``` Which looks somewhat underwhelming: Unstyled content It can be marginally improved if we add some wrapping elements: ```html

Underwater Expo 2018

When you're underwater with goggles on, a couple of your senses are taken away, and it becomes this purely visual thing. It's just you and yourself.

7-24 July 2018
Arrow Icon
Ticket Icon
``` Semantic content Notice the distinct classes `title` and `subtitle` — as the second title line should be blending with the background, we'll need it to be a separate element. Also, the bottom content is all wrapped by an element, because it has a horizontal layout, which we'll need to implement. Something else of note is that the `h1` and `p` elements have additional styling in some browsers that changes the spacing (I'm looking at you, Chrome 🤨), we can add the following rules so we can style our elements using standard margins and padding instead: ```css h1, p { -webkit-margin-before: 0; -webkit-margin-after: 0; } ``` > *📝* As I used CodePen to implement this design, it has an option to reset those styles for me, so I didn't need to include the code above. For more info see [Eric Meyer's Reset CSS](https://meyerweb.com/eric/tools/css/reset). > > Another option is to use [Normalize.css](https://necolas.github.io/normalize.css), which preserves some sane defaults instead of removing all styles like Reset CSS does. For a more detailed discussion on the differences, see [this StackOverflow question](https://stackoverflow.com/questions/6887336/what-is-the-difference-between-normalize-css-and-reset-css). ### Icons Using CodePen's assets feature, which links to the [Material Design Icons](https://materialdesignicons.com), we can easily find what we need by searching for *arrow* and then *ticket*. > *📝* The ones I ended up using are a bit different than those in the design, which is fine, since this is an exercise in styling. Something we can do to get closer to the intended look is change the colors and add a circle to each of the icons. To account for the extra space that the circles would take within the SVG, we'll also change the `viewBox` property. Here's the arrow icon, which is white and has a transparent fill for the circle: ```html
``` As for the ticket button, we have a semi-transparent black icon placed over a circle with a yellowish fill. We can use the `fill-opacity` attribute to achieve that, and also implement a `transform` with rotation. Notice the angle and origin, which rotates the path to the left around its center — `rotate(-45 12,12)`. ```html
``` And here's what that looks like in the browser, with a gray background for the main element and some sizing for the icons so we can see them: The finished content of the design Good, now that we're done with the content we can switch to only using CSS to style the page! ## Styling ### Background The background color of the `main` element is `#0a2066`, and here's the entire image: Their own comfortable photo by Pan Da chuan (@dachuan) on Unsplash [Their own comfortable photo by Pan Da chuan (@dachuan) on Unsplash](https://unsplash.com/photos/dwKd_Epw2GE) To achieve the desired effect, we have to adjust the background image position, repeat, size, and blend mode, and make the text white: ```css .main { background-blend-mode: difference; background-color: #0a2066; background-image: url("https://images.unsplash.com/photo-1488953994029-e6a7ba3348f7"); background-position: center bottom -32vw; background-repeat: no-repeat; background-size: cover; color: #ffffff; } ``` After adding the background image > *📝* While I had access to the original PSD, I didn't have Photoshop, so the easiest way to find the color, image, and blending mode was to ask! > > A 2-minute conversation with the designer can provide both of you with some useful insights, save hours of research, and help you create a better product! ### Bottom The part of the page containing the date and icons is laid out horizontally instead of vertically. This means we can use CSS tools like Flexbox or Grid. > *🙏* Shout out to [Dave Geddes](https://medium.com/u/4c93adcd6fba) for his massively successful online courses on both Flexbox and Grid, respectively [Flexbox Zombies](https://flexboxzombies.com/p/flexbox-zombies) and [Grid Critters](http://www.gridcritters.com). > > Flexbox Zombies helped me master this CSS tool after having used it for years and still hesitating when to use what. No more! I highly recommend it, and it's even free! > > Thanks, Dave! In this case we only need to lay out elements in one direction, which means Flexbox should suffice. We also need to center the arrow icon between two elements. On first look, there's a very simple way to do that by using `justify-content: space-between`. But here's what we actually get: Centering using justify-content: space-between Ignoring the differences in font size and margins, notice how the arrow icon in the screenshot above is off-center compared to the original design below: Centering in the original design It seems that's not quite what we're aiming for. In fact, when the elements on both sides of a flex container have a *different* width, `justify-content: space-between` doesn't place the central element at the exact center of the parent. Instead, the space remaining after subtracting the content's width is divided between the elements. The same goes for using `margin-right: auto` and `margin-left: auto`. What we can do to avoid this is add `text-align: right` to the ticket element, then either use `flex: 1` on the date and ticket elements, or alternatively use Grid on their parent with columns of `1fr auto 1fr`. Here's how the design looks with more accurate centering: Centering using a combination of flex: 1 and text-align: right ### Font The font used is Avenir, which is commercial. Once again, since the goal is not 100% fidelity, we can use Google Fonts and compare side by side with the original design to find a viable replacement: Comparing Google fonts with the original design After typing the title text and adjusting the options, the Lato font with weights of 400 and 900 seems to be close — the edges are a bit more rounded, but letters are thick and imitate the look and feel of the design well enough. ### Sizing and Spacing As this is a large and spacious design (at least on desktop resolutions, which is the only design we have), we can use the [well supported](https://caniuse.com/#search=vw) `vw` (viewport width) unit for sizing and spacing instead of `px` or `rem`. In basic terms, `1vw` represents 1% of the current width of the entire page. We'll use it for the following properties: - Margin - Padding - Font size - Letter spacing - Line height - Width (for the icons) That looks like a lot of guesswork, where do we even start? Fortunately, there's a trick that I really like which makes it much easier to get immediate feedback when implementing a design: ```css body { background-image: url("https://cdn.dribbble.com/users/29468/screenshots/4658658/attachments/1052263/underwater.jpg"); background-repeat: no-repeat; background-size: contain; /* or `cover`, depends on the design */ } .main { opacity: 0.8; } ``` By putting the original design as a background image of the body and making the main element semi-transparent, we can see the differences between the two with every change we make and discuss them with our design team to reach a solution. > *📝* Making sure that differences between the code and the original design are understood and accepted by everyone is important. Even more so when working with a client who may have certain expectations and be surprised with the end result! After tweaking the CSS for a while, it starts to look something like this when overlaid with the original design: Overlayed on top of the original design ### Blending the Subtitle We're really close now! The only thing left is to get the second row of the title blended with the background — so let's apply `mix-blend-mode: overlay;` to it: Our implementation of the blended title text Looks like the title text in the original design is somewhat brighter — and another quick conversation with the designer later, I find out he achieved that by copying the text and layering it twice: Original design blended title text, layered twice While this might work when getting creative with the look and feel of a page in a tool like Photoshop or Sketch, it can be a headache to implement in code. This is why it's important for different professionals to sit down and have a discussion about the way some parts of the design were made. > *📝* In this case, I tried a few different approaches, including using text shadow and pseudo-elements, none of which yielded the results I needed. In the end I was stuck with a somewhat dimmer text — which I think still looks great. ### At the Finish Line Here's the end result from all that hard work: Finished implemented design And, once again, the original design — for comparison: Original design Finally, the actual code running on CodePen: ## Conclusion Congrats, you made it all the way through! 🎈 Personally, after years of focusing on enterprise applications, developer tools, and software architecture, I'm very happy I could flex my HTML & CSS muscles. I've come to appreciate the usefulness of practicing a skill in isolation using little exercises like this one! How about you — did you have any new insights into the process of implementing designs? Or how developers and designers can work better together? ```` ### How I (Foolishly) Gave Up My Google Account Password - Markdown: https://vdsabev.github.io/how-i-foolishly-gave-up-my-google-account-password.md - HTML: https://vdsabev.github.io/how-i-foolishly-gave-up-my-google-account-password/ ```markdown --- status: "published" title: "How I (Foolishly) Gave Up My Google Account Password" date: "2018-04-06" description: "I received a convincing phishing attack on my personal email and gave up my Google password. Here's how it happened, and how to be more careful about it." image: "https://github.com/user-attachments/assets/5519ccb1-7a9b-4825-abc9-2e09f7d21f4c" imageAlt: "Phishing attack illustration" author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["security"] navigationIndex: 0 --- Today, I received a convincing phishing attack on my personal email address. For those not familiar with the term, here’s a definition from [Wikipedia](https://en.wikipedia.org/wiki/Phishing): > *Phishing is the attempt to obtain sensitive information such as usernames, passwords, and credit card details (and money), often for malicious reasons, by disguising as a trustworthy entity in an electronic communication.* ## 🎣 The Hook Here’s the content of the email: Phishing email And for accessibility: > 1 security issue found on your account > We’ve upgraded the Security Checkup to give you specific, personalized recommendations to strengthen the security of your Google Account. > Take the 2-minute checkup today to see the actions you should take to make your account more secure.CHECK YOUR STATUS After opening the link, I got to the following page: Phishing web page The site imitated Google’s very well, and I half expected an email from Google anyway, as I’d been fiddling with my security settings recently. ## 💣 The Kill I entered my username, then against all reason proceeded to type my password. Halfway through, I had a gut feeling, so I stopped and checked the website’s domain and certificate: Connection is 'secure' The domain **login.securityportal.info** should’ve been a dead giveaway, as it wasn’t **google.com**, but I carelessly ignored that — I was kind of distracted with something else, so I continued typing my password, almost automatically. When I tried to login nothing seemed to happen. At that moment I didn’t think much of it and decided to resolve the security issue later. 5 minutes after entering my password though, I received an alert that it had been used to sign in from an unknown device and location: Unknown device sign-in attempt At that point I knew I’d been duped. ## ✅ Safeguarding In the past, there had been several attempts to steal my Google password, but there was always something that stood out and made me double and triple check, and I eventually figured out it was a scam. This time though…I slipped up. After realizing my password had been compromised, I immediately changed it and reported the email as phishing. Here’s more info from Google on how do to that: [https://support.google.com/mail/answer/8253](https://support.google.com/mail/answer/8253) There are a few things I should’ve done before I entered my Google password. ### Check the sender’s address first If I had seen that the email was sent from **support@securityportal.freshdesk.com** instead of a **google.com** domain, I probably would have *immediately* assumed there was something wrong. However, since I saw the familiar Google brand colors and design *first*, I kept thinking it was Google all along, despite the red flags I saw (and ignored). ### Only enter the Google password on google.com domains Assuming SSL means the website is safe to use was a mistake that is obvious in retrospect. > A valid SSL certificate means the connection between you and the website is secure, so the data transmitted can’t be hijacked by a middleman. **It doesn’t mean the website itself is legitimate!** This stands true for other services too — if you receive an email claiming to be from Dropbox, but you click on a link in the email and get to a domain any other than **dropbox.com**, then that’s almost certainly a phishing attack. We should *at the very least* exercise more caution if that happens. ### Take 30 seconds to think before entering the password Typing my password is muscle memory — I’ve done it so many times, it’s as if my fingers move on their own. And if I’m tired and distracted, as I happened to be today, it’s easier to type my password than think about the consequences. ### Use 2-Factor Authentication (2FA) As [John Mimer](https://medium.com/u/c5b396713015) pointed out [in his comment](https://medium.com/@jmimer/some-good-advice-there-afae67ad47dc), 2-factor authentication, also known as 2-step verification, can greatly limit security risks. I had disabled it a few months back, as there was an issue with my Android phone and it could only send me SMS, which I found too noisy at the time. For more details on what 2FA is and how to enable it for your account, see this page by Google: [https://www.google.com/landing/2step](https://www.google.com/landing/2step/) ## 💡 Good Practices For a Strong Password ### Use a specific password only on one website Even though the attackers got my account password, they couldn’t log in with it, as Google prevented them from doing that from an unknown device and location. Fortunately, I don’t use the same password anywhere else on the Internet, which means no one can use it to get into my private documents or bank account, for example. ### Don’t use an easily identifiable pattern A few years ago I would use the same password everywhere, prefixed with the name of the website. For example, if my main password was **super-secret**, my Dropbox password would be **dropbox-super-secret**, my banking password — **bank-super-secret**, and so on. **💀 This is a bad idea! 💀** Reusing the same pattern means once one password has been compromised attackers can potentially identify the pattern and log into other accounts that use it! So if Dropbox had a data breach ([which they did, in 2012](https://motherboard.vice.com/en_us/article/nz74qb/hackers-stole-over-60-million-dropbox-accounts)), you could have easily replaced the **dropbox-** prefix with **bank-** and accessed my bank account (if you knew which one I used)! > You can check whether your email account has been compromised on [https://haveibeenpwned.com](https://haveibeenpwned.com/) which searches through databases of past leaks. ### Create your passwords based on personal experiences When I realized how incredibly dangerous using the same password everywhere is, albeit prefixed with the name of the website, I switched to a different strategy. Remember security questions that ask you for the name of your first teacher, your first pet, or the street you grew up on? Those are deeply personal things that hackers would have a harder time figuring out than, say, your birthday. The same principles can be applied to creating a strong password. Without giving up my specific pattern, it’s a combination of the name of people who are personally important to me, combined with something that has shared meaning to both of us, with some special characters thrown in. Some fictional examples using a similar pattern would be: **I met Martin hiking @ Lake Tahoe** **I saw Jane speak @ a Vienna conference** **I went skiing with Peter @ the Alps** In this case, the password includes some personal story about a person, then `@` + the location. Maybe you’ll use the name of your school teachers over the years, or have had lots of pets and can use their names, combined with a unique memory you have of them. > The idea is to build a story around your password — once you do that, you can more easily remember it and associate it with the specific website. You can also [use a passphrase](https://xkcd.com/936/), which is likely to be much stronger than a regular password, and throw in some special characters for websites that require those. ### Use a password generator for non-essential accounts If you’re finding it impractical to remember more than 5–6 passwords at a time, you should consider using a service like [LastPass](https://www.lastpass.com). When registering for most services, I use [browser-generated passwords](https://support.google.com/chrome/answer/7570435) which are stored encrypted on my device behind a keyring password. This saves me a great deal of time and I only have to remember the 3–4 passwords I created for my most important accounts. ## 🔚 Conclusion Despite my lack of attention and common sense, I got lucky, because Google protected my account. Also, the password was unique and not used anywhere else. I’ve now changed all my most important passwords and re-enabled 2-factor authentication — this experience has helped me take cyber security seriously again. Hopefully this post helped at least one person keep their account safe! Have you had experiences with phishing or other scams? Any useful tips for protecting our online accounts? ``` ### The Simple Difference Between RxJS switchMap and mergeMap - Markdown: https://vdsabev.github.io/the-simple-difference-between-rxjs-switchmap-and-mergemap.md - HTML: https://vdsabev.github.io/the-simple-difference-between-rxjs-switchmap-and-mergemap/ ````markdown --- status: "published" title: "The Simple Difference Between RxJS switchMap and mergeMap" date: "2018-03-19" description: "Trying to figure out whether to use switchMap or mergeMap for a simple HTTP request? I only understood the difference once I tried it out for myself." image: "https://github.com/user-attachments/assets/ef8b2d85-d867-4b3d-9d00-16d9d6da1f02" imageAlt: "RxJS switchMap and mergeMap data flow" author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["angular","rxjs","software development"] navigationIndex: 0 --- Have you been reading about RxJS for the past hour or so, trying to figure out whether you should be using `switchMap` or `mergeMap` to handle a simple HTTP request? That’s exactly what happened to me today, and none of the articles explained the difference in a way that I could understand — there were long paragraphs describing all the details, with many examples using intervals, marble diagrams, and creative analogies. After all that reading, I only started understanding when I tried it out for myself. ## Use case Let’s get right to it — I wanted to write an `@ngrx/effect` to send an HTTP request to a metrics service whenever an action was dispatched to the store. ### `switchMap` Here’s what I came up with initially using `switchMap`: ```typescript import { Injectable } from '@angular/core'; import { of } from 'rxjs/observable/of'; import { catchError } from 'rxjs/operators/catchError'; import { switchMap } from 'rxjs/operators/switchMap';@Injectable() export class MetricsEffects { @Effect({ dispatch: false }) public readonly logToMetrics$ = this.actions$.pipe( switchMap((action) => this.metricsService.log(action).pipe( catchError(of) )) ); constructor( private actions$: Actions, private metricsService: MetricsService, ) { } } ``` However, after making 8 requests in quick succession, here’s what the results looked like: Canceled network calls with switch map You’ll notice some of those requests got cancelled before they finished. Much like `takeLatest` in Redux-Saga, `switchMap` in RxJS only cares about the latest value that the observable emitted, in this case cancelling any previous HTTP requests that were still in progress. But that wasn’t exactly what I needed. ### mergeMap So let’s try running another 8 requests using `mergeMap` instead: ```typescript ... public readonly logToMetrics$ = this.actions$.pipe( mergeMap((action) => this.metricsService.log(action).pipe( catchError(of) )) ); ... ``` The results this time are: Successful network calls with mergeMap Much like `takeEvery` in Redux-Saga, `mergeMap` in RxJS passes all requests through, even when a new request was made *before* a previous one had finished — exactly what I needed! ## Conclusion So here’s the simple difference — `switchMap` cancels previous HTTP requests that are still in progress, while `mergeMap` lets all of them finish. In my case, I needed all requests to go through, as this is a metrics service that’s supposed to log all actions that the user performs on the web page, so I used `mergeMap`. A good use case for `switchMap` would be an autocomplete input, where we should discard all but the latest results from the user’s input. Did this help with understanding when to use which? Let me know in the comments 👇 ```` ### Preparing a Freelance Offer: From 0 to 100% - Markdown: https://vdsabev.github.io/preparing-a-freelance-offer-from-0-to-100.md - HTML: https://vdsabev.github.io/preparing-a-freelance-offer-from-0-to-100/ ```markdown --- status: "published" title: "Preparing a Freelance Offer: From 0 to 100%" date: "2017-10-23" description: "A step-by-step guide to preparing a professional freelance offer - from initial client conversations and market research through feature estimation, pricing packages, and presenting the final document." image: "https://github.com/user-attachments/assets/924499d2-b15d-423e-868b-b9248d345181" imageAlt: "DEV.BG Talk - Preparing a Freelance Offer: From 0 to 100%" author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["freelancing","software development","talk"] navigationIndex: 0 --- > ℹ️ This post has been translated from a talk I gave in Bulgarian in October 2017: https://dev.bg/event/it-freelance-izgotvyane-na-oferta-ot-0-do-100/ Hi! For those who don't know me — I'm Vladimir. I've been working as a freelancer for the past four years, mostly using the approaches most freelancers are familiar with — first by the hour, later on a monthly salary. At a previous dev.bg event, I shared my impressions of those approaches, along with an alternative I've been striving toward — a fixed price per project. But to work that way, I first had to learn how to prepare a proper offer. Before I continue, I want to clarify: I've already graduated, I don't have children, a car, a mortgage, or any other loans. My point is that the way I work doesn't necessarily apply to every person and every situation. So please — keep your own life circumstances in mind before taking professional risks. But in the end, I believe that without taking risks, there's no growth! That's why six months ago I stopped working with my clients, to have the time and freedom to experiment. During this time, I've been studying — and I'm still studying — various technologies and approaches to work. Most of the ideas I want to present today aren't new, and they're not even my own. I've come across them from various sources and in different forms over the years. Recently, for example, I had a series of conversations with a friend of mine who is a design engineer in the construction industry. It turns out that despite the differences, there are also many similarities between the software and construction industries — they also go through the process of researching and preparing an offer for their clients before starting work on a project. ## The Process The steps we'll walk through: - Conversations with the client - Independent research - Defining and estimating features - Pricing packages - Formatting and presenting the offer To keep things from being entirely theoretical, I want us to reproduce the entire process together. Some of you might think you "don't have time for research and estimation," or that this is "some kind of waterfall process" not worth doing — I certainly used to think that way. Now I believe preparing an offer is important precisely because it forces us to do our homework. ### Why Research? During the research phase, we figure out: - Whether we're the right person for this project - Whether we need help from other professionals - Whether the client's budget will be sufficient - What the specific parts of the project are that could trip us up Defining and estimating feature modules, in turn, helps us notice commonalities across the diverse projects we work on over the years — and accordingly develop ready-made solutions for recurring problems. And it helps our clients by providing more transparency, concrete timelines, and choices. Since we have limited time for this presentation, I've prepared the necessary materials in advance. ## Meet Yoana (The Client) To start, let's imagine that Yoana — our fictional client — has reached out to me. Together with several co-founders, Yoana has raised funds to realize their great business idea, and they're looking for a developer to help them. Their goal is to test the idea and then seek additional funding to further develop it. ### HealthBase The idea is a platform called HealthBase — a digital health record accessible to patients, doctors, and medical laboratories. - **Lab results** — Patients have easy access to their lab results, visualized graphically to make them more understandable - **Daily metrics** — They can also enter their health indicators daily, such as blood pressure - **Graphical visualization** — Doctors set reference values or the "norm" for their patients' health indicators and monitor their condition - **Personal reference values** — Laboratories conduct medical tests and enter patient results into the platform The idea sounds amazing — I've dreamed my whole life of working on something like this! In fact, I want this project so much that I'll do everything I can to win it! But what does "everything I can" mean? Is it enough to tell my client Yoana a number, say X BGN per hour, and then hope she picks me because of my great reputation and polished portfolio? Of course, reputation and portfolio are important, and I'll talk about that later. But in my opinion, it's just as important to show how exactly we'll apply our skills to the specific project, and why we're the right person for it. ## Researching the Client's Needs During the research process, the client forms an impression of us as professionals and whether we'll be a good fit. We also have the opportunity to better understand the idea and build trust between us. To do a quick research session for Yoana's health platform, it's best to have a face-to-face conversation with her. ### Key Questions Every project is different, but there are a few key questions I always ask: 1. **What's the problem?** — What problem do they want to solve, and why? 2. **How do they envision the solution?** 3. **How will the business be funded?** 4. **What are the biggest challenges?** 5. **What do they expect from us?** Let's see how Yoana would answer these. ### What's the Problem? - The information in lab results isn't clear - Different systems, reference values, and units of measurement - Patients don't have access to previous test results Yoana thinks that for most patients, current health test results are just a pile of letters and numbers — the information isn't clear. Plus, different labs use different systems, reference values, and measurement units, and the results aren't easily accessible to doctors or patients' relatives. She tells me how she created a Google Sheets table to record her mother's data, who has high blood pressure. ### How Do You Envision the Solution? - Data visualized in an intuitive way - Information structured and standardized - Patients have access to all previous tests and can compare results - Accessible through different devices According to her, a better solution would be a platform where: - Data is visualized intuitively, not just as dry numbers — more graphs and colors - Information is structured and unifies results from different labs - She wants access to all previous tests and the ability to track how certain indicators have changed over time - The system should be accessible from mobile phones and desktop computers ### How Will the Business Be Funded? Yoana isn't sure whether patients, doctors, or labs would pay for such a product, and under what terms — so that's one of the main challenges. ### What Are the Biggest Challenges? - **Funding** — The business model is unclear - **Integrating labs with the platform** — Different lab systems - **Minimal costs** — At least initially ### Research Questions I also have my own questions. Yoana can answer some, but others I need to research myself — and that's what the research phase is all about: - Are there specific legal requirements we need to meet? After all, this is a healthcare product - What's the team like? Is there a medical professional among them? - Do they have a designer? (Since I'm not one) - Have they done any market research? Have they discussed the idea with friends and family? ### Documenting the Conversations There may be more than one meeting — it's good to have notes from each conversation and then send a follow-up email. "To confirm what we discussed — first, second, third, we agreed to do fourth" — so we have everything in writing and avoid misunderstandings on both sides. ### Research Results Let's come back to Yoana and her idea. After the meeting, I spend a few hours researching the questions and find the following: - There are specific requirements from the National Health Insurance Fund — meaning if we want labs to officially use and pay for the system, that complicates this part of the task and might not be suitable for a minimum viable product - However, there is a possibility for integration with at least one lab that I found - Regarding minimizing costs — we can develop a web application that's accessible from mobile devices, instead of separate apps for web, Android, iOS, Windows Phone, and so on ### What If the Client Doesn't Choose Us? What if after all the research, the client doesn't pick us and walks away? There's also the option to agree upfront that the client will pay for the research, after which they can do whatever they want with the information — that's exactly what consulting is. But even if we do the research pro bono, our work hasn't been entirely in vain — we've actually gained valuable information about what the market is looking for and what problems people are trying to solve. ## Defining Feature Modules Once we've gathered the information we need during research, it's time to analyze what we've learned and briefly describe the system. I want to emphasize: this is NOT waterfall! We're spending a few hours putting together a list of notes, not several months writing detailed documentation describing every possible case! For HealthBase, I'll divide the features into three packages right now, based on how important they are to the system. One reason to create packages is to give the client an appropriate price for each package, instead of dozens of individual prices for each feature. The goal here is to avoid the so-called paradox of choice. It turns out that too much choice actually makes it harder to make a decision. The takeaway is: it's good to have choices, but not too many, because it becomes harder for the client to decide — especially when it comes to something more important than a pair of jeans. ### The Three Packages I've already entered everything into [this document](https://goo.gl/tpwKvD) since the platform details are very specific and not the main topic of this presentation. - **Basic** — We usually start with the types of users in the system and figure out the most important things they need - **Plus** — At the next level, we can add features that aren't critical to the system's operation - **Premium** — In the Premium package, we can apply our special skills. For me, that includes: - Internationalization, so any part of the platform can easily be translated into another language if they decide to enter markets outside Bulgaria - The web page can be installed on mobile devices as an app - Push notifications when the user hasn't opened the app on their phone ### What If We Forget Something? If clients later ask for something we haven't discussed, we go through the research and estimation process again — just for that feature — for an additional fee. ## Estimating Feature Modules Since I want to give a price and a deadline for the project, I personally find it important to estimate each feature by two main indicators: **time** and **unpredictability**. - **Time** — The number of hours I expect the feature will take to develop - **Unpredictability** — The degree to which I might exceed that time. For a relatively standard feature like registration or login, this indicator is low. For something like integrating with lab systems, it's high By increasing the Time by the percentage of Unpredictability, we get a rough estimate of the maximum time the feature might take. This helps us determine both the deadline and the price. All of this is very subjective, just like with Agile methodologies — if we estimate the same table twice, we might get two different values. Over time, we learn to be more accurate. Let's estimate a few of these features now. We won't have enough time to go through the entire estimation process since I usually spend a few hours on it, so I've pre-filled the entire table. ### What If We Don't Estimate Correctly? What happens if we don't estimate correctly? We can offer up to 3 revisions for a given feature, with subsequent revisions being paid additionally. This way, the client takes on some of the responsibility for defining their requirements clearly and precisely. How many of you have heard: "I don't know how I want it, but I'll know when I see it"? Otherwise, if we're within the 3 revisions but it's just taking much longer than planned — well, we're behind on our calculations and we take responsibility. But other things will go faster and easier than expected. If we never make mistakes, we can never learn! Finally, if it turns out a task is impossible to complete — that's something we should have caught during research. If it truly is impossible, then we're left with convincing the client that it would take too much time or is too complex, and we should look for another solution to the problem. ## Assembling the Packages | Package | Price | Timeline | |----------|---------|----------| | **Basic** | €3,800 | 5 weeks | | **Plus** | €5,700 | 7 weeks | | **Premium** | €6,700 | 8 weeks | When I first used this method to prepare an offer together with a desk colleague — a designer — we sent the clients an extremely detailed quote with a price list for every feature. Login — €100, Registration — €100, and so on. We've already seen what the paradox of choice is about, so this time we divided the features into several packages from the start. Another reason is that some modules are closely interconnected — for example, you can't have registration without a login, or vice versa — so grouping them into packages is necessary. We already have estimates for each feature, and we can see the total for each package in the table: - **Basic** includes only the most basic functionality the project needs. Total: **€3,800**, deadline: **5 weeks** - **Plus** is a fully functioning system, as the client requested, with all the details. Total: **€5,700**, deadline: **7 weeks** - **Premium** includes our specific unique skills. Total: **€6,700**, deadline: **8 weeks** There's a subtle point about package pricing, though. Consciously or not, we might structure them to steer the client toward a particular choice. If my HealthBase packages ended up looking like this, it wasn't intentional — after all, Yoana doesn't exist! But from now on, when someone offers us several options and one is obviously a bad choice, we might wonder whether they're trying to steer us toward the more expensive option. For me personally, the most important thing is that I'm giving the client clear prices, firm deadlines, and the right to choose. ## Formatting the Document There are many ways to format the actual document — as a presentation, PDF, Google Docs, and so on. Now I'll show you the first offer I prepared with my desk colleague Boyan Kostov, who is a web designer. And here's what Boyan's newer offers look like — over time, his approach became much more visual, adding his own branding and design. Both offers are 6 pages, but one is mostly text, while the other has colors, images, and larger text. For HealthBase, I've prepared a sample offer that's somewhere between the two variants I showed you — 3 pages, not too much text, you can read it in a few minutes: https://goo.gl/EyHxyY ## Presenting the Offer - **In person** — This is the best option - **Video call** — If meeting in person isn't possible, a video call with screen sharing is ideal, so we can answer questions as they come up - **As a last resort** — If we can't connect with the clients at a convenient time for everyone, we record a short video presenting the offer Sending just an email with an attachment makes it much harder to leave a good impression, even if the document is carefully formatted. I've also read recommendations not to surprise the client with the price — meaning we should give them a rough idea of what budget they might need to set aside, in advance. ## What We've Done So Far Let's summarize how we prepared the offer: 1. First, we had a series of conversations with the client, asking clarifying questions and taking notes 2. Then we did deeper research to investigate the specific parts of the project 3. We defined the main features in the system that we'll be developing 4. We estimated them by deadline, unpredictability, priority, and assigned our own price to each feature 5. Finally, we formatted the offer and presented it to the client ## The Lesson Hidden in Every Offer By following this process, with each new offer we learn something more about what works and what doesn't. For example: - **More graphics, less text** — In one of the offers I showed, there was too much text, too many variants and choices, making it much harder for the client to make a decision - **Document the process** — If I had documented my work process by writing one blog post per month, I'd have 12 posts a year as a result. Now I wish I had started 5 years ago! - **Commonalities across projects** — When I started breaking projects into features and estimating them, I began noticing certain tasks that come up in every project. Registration, login, some basic styles, notifications, and so on - **Build a portfolio** — All of this helps us grow our portfolio, attract more and more interesting projects, and develop our own solutions to common problems. I'm not just talking about programmers — many designers develop their own design systems, icon packs, stickers, and fonts ### Real Examples **Boris** (design and animation) — https://noxious.one Boris noticed that some of the companies he works with already have a logo, but need it to be more dynamic — for example, for a video intro or as a loading indicator on their website. He specialized in bringing static logos to life by animating them. **Svilen** (illustrator) — https://sm-artists.com Svilen is an illustrator. He's had many clients who wanted a caricature drawn from a photo — suitable for social media avatars, gifts, or the "About Me" section of their website. The service has clear steps and a fixed price. By the way, here's how he drew me in a hurry. In conclusion, regardless of exactly what direction we've specialized in, if we work as freelancers, it's entirely possible to create our own products and services that generate additional income. ## The Rising Tide Lifts All Boats Let's circle back to offers and why we should bother preparing them. If say, only 10% of freelancers do research and prepare offers, then we stand out from all the rest who just give a single hourly rate. By preparing an offer, we have a competitive advantage. But what happens if 100% of freelancers start doing research and preparing offers? Won't we lose our competitive advantage? Well, yes — probably. But then the entire industry would be at a higher level. And the best among us will come up with new competitive practices that will gradually be adopted, until they too become mainstream. The rising tide lifts all boats. ``` ### Exploring Unidirectional Components in Mithril (part 2 — Redux) - Markdown: https://vdsabev.github.io/exploring-unidirectional-components-in-mithril-part-2-redux.md - HTML: https://vdsabev.github.io/exploring-unidirectional-components-in-mithril-part-2-redux/ ````markdown --- status: "published" title: "Exploring Unidirectional Components in Mithril (part 2 — Redux)" date: "2017-10-09" description: "Continuing the experiment from part 1, this time using Redux and the reducer pattern for unidirectional data flow in Mithril, and comparing it to Hyperapp." image: "https://github.com/user-attachments/assets/1f452536-d907-466e-a848-4c895b1709d0" imageAlt: "Unidirectional data flow" author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["hyperapp","javascript","mithril","redux","software development"] navigationIndex: 0 series: "Exploring Unidirectional Components in Mithril" --- In [part 1 of this post](https://vdsabev.github.io/exploring-unidirectional-components-in-mithril-part-1-hyperapp), I described how we could morph the [Mithril](https://mithril.js.org/) library into a [Hyperapp](https://hyperapp.js.org/) lookalike to achieve unidirectional data flow. This time, I will use [Redux](http://redux.js.org/) with the reducer pattern instead, and compare the resulting code to the Hyperapp architecture. **Disclaimer:** The Redux docs [specifically advise against using multiple Redux stores](http://redux.js.org/docs/faq/StoreSetup.html#store-setup-multiple-stores), which is what we’re doing here. When writing real applications — listen to the docs, folks! When playing with code — break the rules a little bit — it’s where learning happens. A mechanical counter ## Need a Counter? We’ll dive straight into the code and try to explain how exactly the details are implemented behind the scenes a bit later. First of all, let’s take a quick look at the view function for our beloved Counter component, which we’ll reuse in both the Hyperapp and Redux architectures: ```jsx const CounterView = (vnode, { count }, actions) => (

{count}

); ``` *(if this is all new to you, now is the time to catch up with *[*part 1 of this post*](https://vdsabev.github.io/exploring-unidirectional-components-in-mithril-part-1-hyperapp)*)* And here are the components themselves: ### Hyperapp architecture ```javascript const Counter = component({ state: { count: 0 }, actions: { decrement: ({ count }) => ({ count: count - 1 }), increment: ({ count }) => ({ count: count + 1 }) }, view: CounterView }); ``` ### Redux architecture ```javascript const Counter = component({ reducers: { count(state = 0, action) { switch (action.type) { case 'DECREMENT': return state - 1; case 'INCREMENT': return state + 1; } return state; } }, actions: { decrement: () => ({ type: 'DECREMENT' }), increment: () => ({ type: 'INCREMENT' }) }, view: CounterView }); ``` If this looks sоmewhat long and verbose to you, don’t worry — we’ll make it a bit shorter later. Parallel lines ## Parallels and opposites Both the Elm architecture (used in Hyperapp) and Redux have similar goals — reduce complexity, increase code clarity, and avoid direct state manipulation. ### Hyperapp architecture The fundamental difference I can see is that in Hyperapp, actions are more complex functions that handle the logic and return all changes across the state. I find this approach to be particularly useful when the effects of an action are relatively well known in advance — for example, clicking the + or - button of a counter, or rendering different views depending on whether the user is logged in or out. ### Redux architecture In Redux, [actions are meant to be really dumb](http://redux.js.org/docs/basics/Actions.html), returning a simple object with only the minimal data for the reducer, where the real logic lies. In addition to handling actions whose effects are well-known in advance, it also works great for the specific use case where a single action could cause various changes across the whole application. For example, in a real-time application, receiving a notification could do all of the following: 1. Update a counter in the header 2. Show the number of unread notifications in the page title wrapped in parentheses, thus flashing the browser tab (think Gmail) 3. Show a toast notification 4. Show a browser desktop notification (if the user is on another tab) With Redux reducers, we can dispatch an action called `NOTIFICATION_RECEIVED` or `USER_LOGGED_IN`, then handle the specific logic in each reducer, changing the data. With Hyperapp, the best way I could think of is using an event bus. One of the things I love the most about Hyperapp is that it‘s really flexible and allows you to use all kinds of different patterns to write your application! Lost in contemplation ## Refactoring opportunities As always, our initial code can be made more reusable through some simple refactoring. This is where the functional-oriented approach of both architectures shines. ### Hyperapp architecture Here, we can extract the similar counter logic in a function: ```javascript const addToCount = (value) => ({ count }) => ({ count: count + value });... actions: { decrement: addToCount(-1), increment: addToCount(1) }, ... ``` Or use an even more generic approach: ```javascript const add = (key, value) => (state) => ({ [key]: state[key] + value });... actions: { decrement: add('count', -1), increment: add('count', 1) }, ... ``` ### Redux architecture First, we can reduce the boilerplate by avoiding `switch` statements or explicitly returning `state` when the reducer doesn’t match the dispatched action: ```javascript const Counter = component({ reducers: { count: reducer(0, { DECREMENT: (count) => count - 1, INCREMENT: (count) => count + 1 }) }, actions: { decrement: () => ({ type: 'DECREMENT' }), increment: () => ({ type: 'INCREMENT' }) }, view: CounterView }); ``` As you can see, the resulting code is somewhat shorter, easier to read, and close to the Hyperapp example. Here’s what the `reducer` function looks like: ```javascript const reducer = (initialState, handlers) => (state = initialState, action, rootState, actions) => { const handler = handlers && handlers[action.type]; if (handler) { return handler(state, action, rootState, actions); } return state; }; ``` It’s basically a copy of the `createReducer` function described in the [Redux docs](http://redux.js.org/docs/recipes/ReducingBoilerplate.html#generating-reducers) — the first parameter is the initial reducer value, and the second is an object with all actions and the resulting changes. Then, we can create a higher level event called `ADD_TO_COUNT` to utilize the [Action Creator pattern](http://redux.js.org/docs/basics/Actions.html#action-creators): ```javascript const addToCount = (value) => () => ({ type: 'ADD_TO_COUNT', value });const Counter = component({ reducers: { count: reducer(0, { ADD_TO_COUNT: (count, action) => count + action.value }) }, actions: { decrement: addToCount(-1), increment: addToCount(1) }, view: CounterView }); ``` *(actually, that’s an Action Creator Creator, but anyway)* As you can see, both architectures are conducive to breaking the state down into small, predictable, and testable functions. ## Implementation Now that we’ve seen what Mithril and Redux can do together, let’s see how this actually works behind the scenes: ```javascript const component = ({ actions, reducers, events, view }) => (vnode) => { // Create functions which dispatch the actions to the store const actionDispatchers = {}; Object.keys(actions).forEach((key) => { actionDispatchers[key] = (...args) => { store.dispatch(actions[key](...args)); }; }); // Reducer proxies are called with the state and action proxies as additional parameters const reducerProxy = (state = {}, action) => { const newState = {}; Object.keys(reducers).forEach((key) => { newState[key] = reducers[key](state[key], action, state, actionDispatchers); }); return newState; }; // Create store with initial values from component attributes const store = Redux.createStore(reducerProxy, vnode.attrs); // The store conveniently redraws the view when data changes store.subscribe(m.redraw); return { ...events, view: () => view(vnode, store.getState(), actionDispatchers) }; }; ``` ## Conclusion Much like the Hyperapp-like architecture described in [part 1 of this post](https://vdsabev.github.io/exploring-unidirectional-components-in-mithril-part-1-hyperapp), this one has some of the same caveats — it’s still Mithril, not serializable, not completely type safe (if you’re into TypeScript), and not battle-tested. But most of all, like I said in the initial disclaimer, having multiple Redux stores is not advised. So don’t use this in production! *(but if you do, please let me know how it turned out, you little rebel :))* ## Resources - An up to date [implementation of the ](https://codepen.io/vdsabev/pen/EvwNyO)`component`[ function](https://codepen.io/vdsabev/pen/EvwNyO) - A more complex component — [Stopwatch](https://codepen.io/vdsabev/pen/BdwQWp) - [All examples used across this series of posts](https://codepen.io/collection/XRKPpa/) ```` ### Simplify Your Layout (And Your Life) With CSS Grid - Markdown: https://vdsabev.github.io/simplify-your-layout-and-your-life-with-css-grid.md - HTML: https://vdsabev.github.io/simplify-your-layout-and-your-life-with-css-grid/ ````markdown --- status: "published" title: "Simplify Your Layout (And Your Life) With CSS Grid" date: "2017-08-31" description: "Doing layout in CSS is really hard. After years of fighting it, here's how CSS Grid changed the way I approach design and made layouts far simpler." image: "https://github.com/user-attachments/assets/c5ed5bd9-a174-49f6-a324-8860407a5cd8" imageAlt: "A grid of colorful tiles" author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["css","software development"] navigationIndex: 0 --- Over my career as a web developer, I’ve written my fair share of CSS layouts — from common websites through business tools to messenger applications. And most of the time, it has been an endless pit of despair, a teeth-falling-out nightmare, the bane of my existence! This is how I feel about CSS in general Okay, maybe I’m being a bit dramatic, but have you ever found yourself stuck for hours trying to get an element to center vertically, expand to fill its container, or fit properly on smaller devices? If you have, then you probably share my sentiment — doing layout in CSS is **really hard**. Or, at least, it used to be — until I learned about CSS Grid. But to show you the new way, let’s first go through what I used to do before… ## Behold the villain I’ve had to write this specific layout multiple times — a **header**, a **sidebar**, and some **content**. For the purpose of this post, let’s call it **HSC**: Header, sidebar, content layout - full-width sidebar ## Absolute Positioning When I first started with UI development, my weapon of choice for this was absolute positioning — it’s straightforward math where you work with coordinates: ```css #header { position: absolute; top: 0; right: 0; left: 0; height: 75px; }#sidebar { position: absolute; top: 75px; bottom: 0; left: 0; width: 350px; }#content { position: absolute; top: 75px; right: 0; bottom: 0; left: 350px; } ``` Logically, the sidebar and content start where the header ends — at 75px from the top. The sidebar takes up 350px on the left side, and the content starts from that point all the way to the right. It’s not too bad — until you decide to adjust the height of the header or the width of the sidebar, and realize you also have to change it in a bunch of other places. Of course, this issue is easily solved by using [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables) or a scripting language like SASS: ```scss $header-height: 75px; $sidebar-width: 350px; ``` Here’s the final result where I’ve replaced all hardcoded values with variables: But even with the help of variables, any time you want to make changes, you have to start calculating coordinates again. Consider how you would add a new element to that layout —you’d have to consider the top, right, bottom, and left positioning of all other elements, and adjust accordingly. Heaven forbid you wanted to add a 10px gap between all the different areas, or conditionally move or completely remove certain elements on smaller resolutions. ## What about Flexbox? When all major browsers finally added official support for the CSS Flexbox spec, many companies adopted it, [and some even shipped it to production](https://www.reddit.com/r/webdev/comments/6o0d0n/how_many_of_you_are_using_flexbox_in_production/). I, for one, welcomed our new Flexbox overlords, and was quick to proclaim the end of all my CSS woes. Yay for Flexbox! It was great for a while, and I would use it for anything — from building the perfect vertically-centered menu to positioning avatars, chat messages, and buttons inside a card. However, as far as the hallowed HSC layout was concerned, it actually felt more uncomfortable doing it the new (supposedly better) way: ```html
``` No, no, no, I’m not even getting into this — 2 wrapper elements just to use Flexbox? I think I’ll stick with absolute positioning, thank you very much ## Use the Grid, Luke I had heard of CSS Grid before, but it wasn’t until I stumbled upon this talk that I really started paying attention:
Basically, CSS Grid allows you to split a container into rows and columns and define how its children should fit inside. Instead of thinking about the coordinates of individual elements, you can now think of the behavior of their parent. It’s not `table` all over again either, because you don’t need special tags like `tr` and `td` to create rows or columns — the DOM can remain flat if you want it to. Here’s how you could rewrite the HSC layout with CSS Grid: ```css body { display: grid; grid-template-rows: 75px auto; grid-template-columns: 350px auto; }#header { grid-row: 1; grid-column: 1/3; }#sidebar { grid-row: 2; grid-column: 1; }#content { grid-row: 2; grid-column: 2; } ``` After defining the rows and the columns of the grid, you place each element in its appropriate cell: - Header — row 1, columns 1 to 3 (excluding 3, meaning columns 1 & 2; alternatively, you can write `span 2`) - Sidebar — row 2, column 1 - Content — row 2, column 2 And, if your browser supports it, it should look exactly the same way: If you open the developer tools, your friendly neighborhood browser might even show helpful lines to indicate how exactly the grid is distributed. I’ve gone one step further and annotated the row & column of every cell in yellow for you: Gee, thanks, Mr. Browser! This way, you stop thinking about coordinates and start thinking about your application in terms of different areas. And if you do need to adjust the size of the rows and columns, you can do so in a single place — the container element: ```css body { display: grid; grid-template-rows: 100px auto; grid-template-columns: 400px auto; } ``` ## Grid areas There’s something even cooler that CSS Grid allows you to do — instead of placing your elements at specific rows and columns, you can assign a named area to each one: ```css body { display: grid; grid-template-rows: 75px auto; grid-template-columns: 350px auto; grid-template-areas: 'header header ' 'sidebar content'; }#header { grid-area: header; }#sidebar { grid-area: sidebar; }#content { grid-area: content; } ``` This extremely visual, almost ASCII-art-like way of describing your layout, makes it very intuitive to move stuff around. How about making the header only span 1 column, with the content spanning both rows, like this: Header, sidebar, content layout - full-height content ```css body { grid-template-areas: 'header content' 'sidebar content'; } ``` Check. What if you wanted to make the sidebar take up both rows in the right column: Header, sidebar, content layout - full-height sidebar ```css body { grid-template-areas: 'header sidebar' 'content sidebar'; } ``` Check and mate. We only had to switch `sidebar` and `content`. On a scale of 1 to 10, how cool is that? This is how I feel about CSS Grid If you’d like to play more with this design, here’s the pen: ## Responsive design Perhaps you’ve noticed that in the [collection of pens](https://codepen.io/collection/XJZjjV/) I showed you in this post, I’ve also implemented a responsive version of the design, better suited for mobile devices: Header, sidebar, content - responsive Here, the sidebar is hidden to the left, giving way to the content. If we need the sidebar, we can expand it by tapping the > button in the top left corner (also commonly known as the hamburger menu, and portrayed as ☰). You can judge for yourself what the pros and cons of using CSS Grid for responsive design are. In this case, it seems to me that rows and columns work better than grid areas. ## Animation According to [the spec](https://www.w3.org/TR/css3-grid-layout/#track-sizing), CSS transitions should work on `grid-template-rows` and `grid-template-columns`. However, I was unable to get a sweet animation when transitioning the sidebar and content between the wider and narrower responsive designs. Animation worked just fine with the absolute positioning approach. ## Browser support While [most modern browsers support the spec well](http://caniuse.com/#feat=css-grid), older versions of the Android browser don’t, and IE has implemented a previous version of the specification that differs significantly. A great design tip I stumbled upon was that you should start with a linear, mobile-first layout, then incrementally improve it for larger resolutions using CSS Grid. If the browser doesn’t support it, then your website safely falls back to the already great-looking linear design! Overall, you should consider what your users’ devices are before completely switching to grids. Be smart, don’t just fall for all the hype I’m selling you 😉 ## Go explore! There’s a ton more you could do with CSS Grid, like define multiple rows and columns at once using `repeat`, use grids within grids (gridception!), and even add gaps between cells: Try using grid-gap for a super-easy image gallery! I once again encourage you to [watch the video](https://www.youtube.com/watch?v=7kVeCqQCxlk) I posted before, read the [Smashing Magazine article](https://www.smashingmagazine.com/2017/06/building-production-ready-css-grid-layout/) by the speaker, and browse through the [treasure trove of examples](https://gridbyexample.com/examples/) out there. So, congratulations — if doing layouts in CSS was **really hard** before, with `display: grid` it's now just **hard** 😐 But wait, what do *you* think? Is CSS Grid going to solve our most pressing layout troubles? Or become the next proverbial [hammer where every problem looks like a nail](https://en.wikipedia.org/wiki/Law_of_the_instrument)? Or is it just an overhyped fad that doesn’t really change things all that much? Your move. ```` ### Exploring Unidirectional Components in Mithril (part 1 — Hyperapp) - Markdown: https://vdsabev.github.io/exploring-unidirectional-components-in-mithril-part-1-hyperapp.md - HTML: https://vdsabev.github.io/exploring-unidirectional-components-in-mithril-part-1-hyperapp/ ```markdown --- status: "published" title: "Exploring Unidirectional Components in Mithril (part 1 — Hyperapp)" date: "2017-08-18" description: "An experiment in writing Mithril components with a unidirectional data flow, borrowing ideas from Hyperapp instead of the usual component classes." image: "https://github.com/user-attachments/assets/de2a618b-0ba6-48eb-9cc4-899538087c79" imageAlt: "Unidirectional data flow" author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["hyperapp","javascript","mithril","software development"] navigationIndex: 0 series: "Exploring Unidirectional Components in Mithril" --- I’ve wanted to try writing UI components using a unidirectional data flow for a while — mostly as an exercise, but also with the hope that it will be easier to understand and reason about the data. And whether it would work out well or not, doing such an experiment would at the very least be a valuable learning experience. But why wouldn’t we stick with our current approach anyway? ## Everything is a class? When using OOP as front end developers, our work often involves writing and managing a tangled mess of component classes, which we then turn into even more complex applications. By sticking everything in a class, it becomes more difficult to reuse (or test) a single method from the outside — we have to create an instance of the whole class, including all these other methods and properties we don’t need. Or, as Joe Armstrong, creator of [Erlang](https://www.erlang.org/), puts it: > You wanted **a banana** but what you got was **a gorilla holding the banana and the entire jungle**. To make OOP harder to grasp in JavaScript specifically, we also have the mind-bending mystery that is `this`: ## State mutation “considered harmful” There are [ways to tell whether state mutation is appropriate](https://slemgrim.com/mutate-or-not-to-mutate/) for our use case or not. There are also [rules and principles](https://scotch.io/bar-talk/s-o-l-i-d-the-first-five-principles-of-object-oriented-design) we can apply to write object-oriented code correctly. And there are even [tools](http://www.typescriptlang.org/) and [other tools](https://flow.org/) to help with our `this`-related troubles. But many of us silly geese fall into the same trap over and over again — we write convoluted methods that change a bunch of overlapping internal properties. And after calling a series of them several times, we’ve managed to lose track of our component’s state again. Or break it irrevocably. Which, I’m assuming, is why sometimes the best way to get a computer to work is to restart it: Ensuring mutable state integrity ## So what can we do? Presumably, if we treated our data as read-only and returned a new copy of it with the changes we want for every action, it would be easier to keep track of the state, unit test individual functions, use more functional programming approaches in our code, and even do fancy stuff like [Hot Module Reloading / Time Travel Debugging](https://code-cartoons.com/hot-reloading-and-time-travel-debugging-what-are-they-3c8ed2812f35). ## Mithril — all folk desire it! I’ve become fascinated with all the ultralight component-based frameworks that have been spinning off of React in the past couple of years. The simplicity and performance of Virtual DOM, combined with the significantly smaller size of these frameworks, has resulted in them becoming popular among front end developers, with many thousands of stars on GitHub and vibrant communities on Slack and Gitter. [Mithril](https://mithril.js.org) has risen to the top of my development stack for mobile-ready web applications due to its 8 KB size, high performance, and integrated router. With a flexible approach to defining components, it makes a great candidate for exploring unidirectional data flow architectures. ## The stateful quo Let’s start with a typical approach to writing Mithril components. Obligatory implementation of a Counter component: As far as class components go, it’s relatively straightforward — clicking the `+` button increments the counter, and clicking the `-` button decrements it until it reaches 0. However, there is the issue of context that’s also present in React — when binding the `onclick` events of the buttons to the class methods, we have to call them with the correct `this` value. If we put the `decrement` and `increment` methods on the class prototype, then `onclick={this.increment}` would result in the button’s DOM element being used as `this` instead of the class instance, and the counter’s value would not change! To get the right context, we could bind event handlers by either using `onclick={() => this.increment()}`, `onclick={this.increment.bind(this)`, or by defining the class methods as arrow functions, as we’ve done above. Neither of those is really developer-friendly, as it can sometimes trip beginners and experts alike. Fortunately, Mithril also allows us to define components another way — meet the closure component: In addition to avoiding potentially problematic references to `this`, the code is actually much shorter and clearer than the class definition. Still, the developer directly manipulates the value of the `count` variable. In this case, it‘s not really an issue, since the component is quite simple, but for the sake of our experiment, let’s see where else we can get from here… ## Enter Hyperapp [Hyperapp](https://github.com/hyperapp/hyperapp) is another front end framework that pushes the size limit even lower than Mithril, weighing a measly 1.5 KB. Its adoption of [The Elm Architecture](https://guide.elm-lang.org/architecture) is particularly interesting in that it encourages writing pure action functions, thus avoiding direct state mutations. In Hyperapp, our old friend — the Counter component — looks like this: Here’s what’s going on: 1. We first initialize the state with `count: 0` 2. Then, we define the `decrement` and `increment` actions that return the new state with the respective changes — in this case only the `count` property of the state 3. Finally, notice how the view code is exactly the same as the stateful implementation using Mithril, except the state and actions are extracted from the function parameters using ES6 destructuring instead of being defined in the closure Hyperapp turns out to be great for small, lightweight applications. Maintainers are still changing some APIs and concepts as the framework [is gearing towards a stable 1.0 release](https://github.com/hyperapp/hyperapp/issues/244). The main obstacle I ran into was composing multiple components, each one having its own state and actions. If you go through the [hyper-long discussion in this GitHub issue](https://github.com/hyperapp/hyperapp/issues/238), achieving that seems challenging — some people have done it, while others have tried and failed miserably. It would turn out I was of the latter group. ## Conceding defeat Hyperapp easily allows extracting Counter as a stateless component: However, because state and actions have to be defined in the `app({ ... })` call, child component can’t have state or actions of their own — only lifecycle events. This means we have to manually pass values and functions as parameters to our child components — for an example of that, look no further than [the Hyperapp TodoMVC implementation](https://glitch.com/edit/#!/hyperapp-todomvc?path=index.js). It also doesn’t allow us to easily put one Hyperapp-lication (what else would you call it?) inside another, meaning [the architecture isn’t fractal](https://staltz.com/unidirectional-user-interface-architectures.html), as [André Staltz](https://staltz.com/) would say. After getting stuck on component composition and some additional issues with both the Hyperapp router and TypeScript, I decided to keep using Mithril for now. ***UPDATE:**** The team has done a great job at addressing some of the concerns I had by introducing *[*state slices in v0.14.0*](https://github.com/hyperapp/hyperapp/releases/tag/0.14.0#state-slices)*. I was, in fact, so impressed with their solution, that I am now *[*rewriting my portfolio website*](https://github.com/vdsabev/vdsabev.com/pull/2)* with Hyperapp!* ## A new hope That didn’t mean I couldn’t try implementing Hyperapp’s architecture in Mithril! I kind of liked returning the state changes instead of reassigning state variables, and defining state, actions, and lifecycle events separately. Let’s start with our Counter component from before: The goal is to keep the view exactly the same while at the same time avoiding state mutations. To do this, we can write an abstract function that creates the component for us using the closure syntax: Basically, what this does is: 1. Defines a variable that contains the local state, merging the initial value we specified with the `vnode` attributes 2. Creates local proxy actions, giving every function access to the current state and other proxy actions, and merges the value of the `state` with the result of the function, if any 3. Returns an object which includes the Mithril lifecycle events (defined in `events`) and a `view` function that is called with the `vnode`, current state, and proxy actions While inside the `component` function we technically do perform a mutation by reassigning `state`, this is hidden from the end developer using it. We can now write the definition for our Counter component without manipulating the state directly: Hey, that’s actually exactly the same as the Hyperapp code! And we can still compose multiple components (or whole applications) together just like in regular Mithril, while at the same time writing separate definitions for each component! ## Conclusion ### Still Mithril Of course, this is a naive implementation, lacking many features. If you’re already familiar with Hyperapp, you might have noticed certain differences — for example, lifecycle events, or in the `view` function —`vnode` being the first parameter, with `state` and `actions` taking second and third place, respectively. After all, this is still Mithril and we need to follow its basic rules. ### Can’t access state in lifecycle events Thanks to [Maxi Dello Russo](https://medium.com/u/257ca9e04662)’s [comment](https://medium.com/@maxidr/hi-vladimir-5deb964e8048), I realized that lifecycle events like `onupdate` don’t actually have access to the up-to-date state of the component. To remedy this, we can merge the new state into the component’s state by using `Object.assign(vnode.state, state)` in the `setState` function. Alternatively, we can create proxy functions for lifecycle events, similarly to how we handle actions. ### Not serializable The application’s state is not stored in a single object, meaning we can’t easily implement undo / redo or time travel debugging. ### Not completely type-safe Being a heavy TypeScript user myself, I was unsuccessful in creating a completely type-safe version of this function (see [the implementation I have so far](https://codepen.io/vdsabev/pen/qXVvyZ)). Because action proxies have different parameters than the base actions, we need the return type of a function as described in [this proposal](https://github.com/Microsoft/TypeScript/issues/6606). Unfortunately, this feature is not officially available yet, which means action parameter types can’t be enforced reliably. ### Not battle-tested Overall, I like writing components with this architecture, but I will have to use it in more real world scenarios to better gauge its utility. For now, I’m just happy that Mithril is so flexible and doesn’t force me to only do things one way! ### Feedback Have I missed something obvious? Reinvented the wheel? Are you doing something similar — with or without Mithril? Let me know in the comments! ## Resources - An updated (and evolving) [implementation of the ](https://codepen.io/vdsabev/pen/dzRBEj/)`component`[ function](https://codepen.io/vdsabev/pen/dzRBEj/) with support for asynchronous actions - A more complex component — [Stopwatch](https://codepen.io/vdsabev/pen/oewJvP/) - [All examples used across this series of posts](https://codepen.io/collection/XRKPpa/) [Part 2 of this series](https://vdsabev.github.io/exploring-unidirectional-components-in-mithril-part-2-redux) explores using [Redux](http://redux.js.org/) for storing and updating local component state, and compares the resulting architectures. ``` ### How to Price Your Time and Work as a Freelancer - Markdown: https://vdsabev.github.io/how-to-price-your-time-and-work-as-a-freelancer.md - HTML: https://vdsabev.github.io/how-to-price-your-time-and-work-as-a-freelancer/ ```markdown --- status: "published" title: "How to Price Your Time and Work as a Freelancer" date: "2017-07-24" description: "A practical guide to pricing freelance services — moving beyond hourly billing toward value-based pricing." image: "https://github.com/user-attachments/assets/f421418e-8241-46bc-8f89-65a3ce574017" imageAlt: "DEV.BG talk - How to Price Your Time and Work as a Freelancer" author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" tags: ["freelancing","software development","talk"] navigationIndex: 0 --- > ℹ️ This post has been translated from a talk I gave in Bulgarian in July 2017: https://dev.bg/event/kak-da-otsenyavame-vremeto-i-truda-si-kat/ Hi, I'm Vladimir. I've been developing web applications as a freelancer for over four years now. For the past two years, I've been regularly coming to Betahaus to work on my projects. Let me start with a quick poll — how many of you are currently freelancing? How many want to? My goal tonight is to share my personal experience, and also to hear your thoughts if you have any, about: - What it's like to work as a freelancer - How you determine the price of your services - And where our sector is heading as a whole I also want to highlight the different ways we can define our work, even ourselves. On one hand, as efficient executors of the tasks we're given; on the other, as independent, creative professionals. As people who go with the flow, or who set the direction of the flow. As people who sell their time, or who create value for their clients. ## Why Choose Freelancing? First of all — why would we even want to choose a freelance career? Let's look at how some companies currently work. ### The Status Quo What does an average software project need? A designer, web and mobile developers (at least for iOS and Android, of course), server-side developers, databases, testers, and a "manager" to lead the team and communicate with clients. Not to mention supporting departments like accounting, HR, and so on. Clients see the result once every few months after a "giant periodic code deployment," and products only hit the market at the end of a development cycle that can take years. And the vicious cycle repeats over and over with each new project. ### Waterfall Development This is the so-called Waterfall model. Formally, the process is: - 20–40% of the time is spent on requirements, analysis, and creating the design - Then 30–40% on product development - The rest on testing and fixes Outside of software, one example of such waterfall development from the end of the last century is the company Iridium. Founded in 1991 with $5.2 billion in investments, their goal was to create a global satellite phone network that could work everywhere — from the Sahara Desert to a ship in the Pacific. The first step was to purchase 15 rockets from Russia, the USA, and China. They then began launching their satellites into orbit. The catch was that to work reliably, they needed to launch all 72 satellites — and that took seven years. Just nine months after their first official satellite call in 1998, the company went bankrupt. Why? Because during those seven years, other mobile technologies advanced at a staggering pace. Cell phones shrank to pocket size, their price dropped, and coverage improved significantly. Satellite phones, by contrast, remained bulky, expensive, and had poor indoor coverage. Today, years later, Iridium has been revived and even launched new satellites using SpaceX rockets, with about 850,000 subscribers. Still, it's a classic example of how a product developed for too long can become obsolete by the time it reaches the market. ### Agile Development That's why there are other ways to create a new product — for example, developing it in small steps, releasing it to market as quickly as possible after each cycle, gathering feedback, and making changes on the fly. The word "Agile" itself has become very commercial in software circles, with a lot of hype around it — deserved or not. My personal impression is that it's difficult to apply specific practices like Scrum, Extreme Programming, and so on correctly within a team without access to an expert who's well-versed in the principles and strictly monitors their adherence. ### Not Quite Agile... A few years ago, when I worked at a "normal" Bulgarian software company, we used something of a hybrid between Agile and Waterfall. It involved months of writing documentation, followed by a multi-year development and testing phase. We claimed to practice Scrum because we had standup meetings every morning — but the reality was very different. We deployed a new "version" once every few months. We received client feedback only after that big deployment, and we often had to significantly rewrite or even throw away functionality we'd worked on for weeks. The project started in the summer of 2010. The last I heard from former colleagues, the clients abandoned it in early 2016. Throughout all that time, the project was in development, always "unfinished" according to the constantly expanding client criteria, and was never used by real users. ### The Good News Fortunately, the possibilities for developing software products have improved significantly in recent years. Mobile devices are becoming cheaper, more powerful, and more widespread, and an ever-growing volume of data is exchanged between them. The browsers on these devices are extremely capable and increasingly follow common standards. With progressive web apps, for example, we can "install" a regular web page from the browser on our mobile device, just as we'd install an app from the App Store or Google Play. In certain cases, this allows us to adapt a web application to different mobile platforms with relatively little additional resources and effort. This means a single developer (possibly with a designer) could create certain types of projects in much less time than the previously described ten-person team. For example, we created a progressive web app for Betahaus residents. Sometimes the toilet paper simply runs out and we need help — so I made sure it worked well on mobile devices. When we submit a request for a supply item through our mobile device, the server sends a notification to the Betahaus team's Slack channel, but no more than once every 15 minutes to prevent spam. Another feature of progressive web apps is that we can deploy new versions at any time, without waiting for approval from Apple or Google. After all, they're just web pages, and the internet is a very open platform. ### Ship Often Actually, perhaps the most important part of the process is shipping the product. As Joel Spolsky says: "Shipping is a feature. A really important feature. Your product must have it." If we do it more often, we avoid the situation where we accumulate new functionality for months, deploy it all at once, and then find out clients actually wanted something completely different. To have the confidence to show the results of our work several times a day, high code quality is essential. For me, this means: - Tools and processes that catch and correct style errors and code issues - A suite of tests that run automatically on every file change - Code review before changes are deployed You might have read in the Agile Manifesto's principles that we should deliver software at most every two weeks. Let's consult one of the participants in creating that manifesto — Robert Martin, better known as Uncle Bob. When asked, "Don't today's technologies allow us to deploy new software versions several times a day?", he replied: > "At that time, in 2001, we couldn't imagine a shorter period than two weeks. We thought it was the lower limit, and nobody challenged it, and that was clearly shortsighted." He added: "Think of the manifesto as a call to action for that specific moment, not as Scripture to follow." So I quite comfortably deploy new code versions several times a day. And clients get really excited when they see their feedback reflected within the same day or even within minutes. ### So... Why Choose Freelancing? If you've ended up in one of those organizations where you've lost the feeling of flexibility and productivity, you might appreciate being part of a smaller team or even working entirely on your own. When we have all these tools to simplify much of our work, why not use them? Some companies try to adapt to a more flexible way of working. But most take years to adopt and then implement new ideas, if they ever do. So for those who value their time and want to move faster than the status quo, one solution is to work as a freelancer. Personally, I initially had no intention of getting into freelancing. In 2012, I had already worked for over two years at the Bulgarian software company I mentioned earlier. Unexpectedly, they entrusted me with a new project for American clients because I was the only one familiar with the technology they were looking for. The project was an online event management platform, and I was leading a small team of 3-4 people. ## How to Face the Challenges A few months into the project, even though the clients were satisfied, I myself was on the verge of professional and emotional burnout. I felt exhausted from working at the company and had an acute need for change. I wanted to set the direction of the flow myself. That's when I came across Tim Ferriss's book *The 4-Hour Workweek*. I didn't have ambitions to work that few hours — I just wanted to be more independent and free with my time. ### Fear Setting One chapter of the book that particularly impressed me described an exercise called Fear Setting. In short, you start with an action you want to take but feel some hesitation about. To explore the obstacles before us: 1. **What could happen if...?** — For example, "What could happen if I quit my job and go abroad?" List the absolute worst-case scenarios you can imagine. 2. **How can I prevent them?** 3. **How can I fix the consequences** if they happen despite everything? 4. **What could be the benefits** of trying, or even partially succeeding? 5. **What could be the consequences of inaction** over the next 6 months, 1 year, 3 years? 6. Finally, **assess the probability and impact** of all these undesirable events. It seemed simple enough, so I decided to try it. I created a Google Sheet with my analysis. Here's what my assessment looked like: **If I quit my job and went abroad:** - Depleting all my savings — 50% × 4 = 2.0 - Not finding a job in Sofia — 10% × 6 = 0.6 - ... — 40% × 4 = 1.6 - ... — 10% × 3 = 0.3 - ... — 10% × 7 = 0.7 - **Total score: 5.2** **If I stayed at my job:** - Stress — 80% × 5 = 4.0 - Project failure — 10% × 4 = 0.4 - Worsening health — 20% × 8 = 1.6 - Professional stagnation — 40% × 5 = 2.0 - **Total score: 8.0** What I saw was that if I quit, there was a small chance of unpleasant consequences. None were both highly probable and highly impactful. Hypothetical, unlikely, or minor obstacles. Total score: 5.2. On the other hand, if I stayed at my job, there was a high probability that the strongly unpleasant consequences I was already feeling would continue and seriously affect my health. Total score: 8. You might notice we're not evaluating the benefits of the action here — we assume that if we feel the desire to try something new, we sense it would be better than our current situation. ### The Decision? After this exercise, it was much easier to make a decision. I handed in my one-month notice and bought a one-way plane ticket to Sweden. I didn't know why there, or what exactly I would do, but I wanted to try something different. In the end, I traveled for over two months abroad, and in my free time, I continued working on the same project with the American clients, without any discussion of payment. They were happy with my work and wanted me to continue — this time with financial compensation. So a few months later, I registered my own company and issued my first invoice. ## How to Price Your Time and Work To issue that invoice, I had to agree on a price for my services. Negotiating my compensation was entirely new to me, so I chose what seemed most reasonable for a freelancer — by the hour. A few years later, on another project, I worked on a monthly salary. In between, I completed several small fixed-price projects. I want to share my impressions of all three approaches, looking at: - The types of projects I worked on - My working hours - My motivation to seek new ideas and develop my skills ### By the Hour Many freelancers work by the hour. In my case: - **Project type:** Open-ended, startup company - **Working hours:** Flexible, from home - **Tendency:** Getting lost in details - **Compensation:** Limited number of hours The project had an indefinite duration — I expected more than 6 months and less than 6 years, but it wasn't clear exactly how long. The clients had no existing users, only a business idea. My working hours varied enormously from month to month — between 4 and 200 hours. Due to the time difference with California, I often worked late at night or very early in the morning, mostly from home. I frequently caught myself wondering whether I was working or not, and whether I had started the timer tracking my hours. Subconsciously, despite my best intentions, I felt inclined to spend more time on tasks than necessary, to get lost in unnecessary details, and to tweak pixels for ages, accumulating more billable hours. Objectively, the number of hours we all have is limited. When working by the hour, there's an artificial ceiling on your income. If you discover a new technology or approach that lets you do the same work twice as fast, you'd get paid half as much for it. There was no incentive to optimize my work — quite the opposite. ### On Salary For the next project I took on, I wanted to work on a monthly salary: - **Project type:** Open-ended, startup company - **Working hours:** Fixed, in an office - **Tendency:** Getting distracted - **Compensation:** Fixed The project was again long-term with an indefinite duration. The company was a startup, but this time with an established business model — the goal was to bring it online. After my previous experience with fragmented working hours, I wanted to work 7-8 hours at a stretch, preferably in an office space surrounded by other people. That's when I came to Betahaus. However, I found myself interested in doing the minimum sufficient amount of work. I'd read my favorite sites during work hours, extend my lunch break, come in later or leave earlier. In short, I'd find ways to get distracted as long as the client was satisfied. There's an argument that this is perfectly normal, part of modern daily life, and even necessary for creativity. But I have doubts about whether this is the best way to develop my professional skills. And the compensation was fixed every month — again, no incentive to find ways to work more efficiently. ### Per Project In addition to long-term projects, over the past four years I've also worked on several projects for non-profit organizations and two small commercial projects: - **Project type:** Fixed-term, established company - **Working hours:** Flexible but with deadlines - **Tendency:** Working faster - **Compensation:** Theoretically unlimited They had a defined timeline and clearer end goals. The companies had an established business model — the projects served to improve a specific part of their process. I could work whenever and as much as I wanted. Having a rough deadline and periodic meetings with the client helped me work more diligently. I still tracked my hours, but only to know how long each task had taken for future reference. I felt motivated to complete the project faster and more efficiently. For some people, this might backfire and lead to a drop in quality. For me, the effect was that I made measured compromises to finish tasks within a reasonable timeframe. The results were balanced — I neither delved into excessive detail (which I often do) nor rushed through things. Regarding pricing — for the non-profit work, it was pro bono. For the small commercial projects, the hourly rate was relatively low since they were for a Bulgarian company. But I definitely felt that my focus was on achieving the project's goals, not on waiting for time to pass. ### Breaking the Time Barrier This is exactly the approach I want to talk about. I came across a book called *Breaking the Time Barrier*. It's a short story that, in about an hour of reading, explains a simple idea: price your services based on the value they bring to the client. Imagine you own a business and know a feature could bring in an additional 1000 BGN per month. You need a capable developer to build it: | Developer | Price | Timeline | |-----------|-------|----------| | Developer A | 3000 BGN fixed | 1 month | | Developer B | 20 BGN/hour | 1 month | | Developer C | 15 BGN/hour | 1.5 months | Let's say all three are equally capable. Developer A's offer can be seen as an investment rather than an expense — one that pays for itself in 3 months. Plus, Developer A has an incentive to finish faster, which means your business starts generating additional revenue even before the 1-month mark. With Developer B, you might pay less than 3000 BGN, but there's a risk of paying more. Developer B has an inherent interest in working slower to bill more hours. You might be surprised by the final invoice. With Developer C, the risk of delay or budget overrun seems even greater. And the lower rate might give an impression of lower quality. ### Clarifying the Goals What if your client doesn't know exactly what revenue their idea could generate, or how it could be measured? For example, they want a new website design because they think the "old one is ugly." In that case, we can ask clarifying questions: - To establish the starting point — why do they think the site is ugly and needs our services? - What are their long-term business goals? - How will we measure the effect of our work? For instance, if before 50% of visitors liked the design and after our changes 80% do — that's measurable improvement. - How valuable is a new customer to the business? How much do they spend on average over the years? - How valuable is it to retain an existing customer? - How many customers do they expect to attract or retain? Most established companies know their clients well and can answer these questions. I believe this is a much more objective way to price services than thinking about our own rent, car payments, or grocery costs. Clients don't care about that. ### More Than Just Executors In the process of clarifying goals, we transform into something more than just task executors. Using a construction metaphor — executors lay bricks. We can work at another level, offering architect services, giving ideas, and thinking in different directions. This is important because: **The client is always right about the problem they have, but not always about the solution.** Our job is to find the best solution for the problem. Maybe our services would cost more than the client anticipated, but they'd also get more than if someone simply executed their wishes by the hour. And sometimes the solution turns out simpler than they thought. I was recently asked for advice on building a real estate website. My first idea was what I knew best and what's most traditional — a web app, database, server. But after asking a few questions, I realized it could be much simpler: instead of a database, use Google Sheets and load the properties directly into the browser with a small JavaScript library. Most realtors probably already know how to use Excel, or in this case, Google Sheets. So sometimes, starting with well-directed questions, we can find a better solution than the usual one. ### Investment, Not Expense As I experienced first-hand, with hourly or salaried work, there's no incentive to work faster — quite the opposite. If we give a fixed price for the entire project instead of an hourly or monthly rate, the client can predict from the start how much the defined result will cost. And here's the key point — if the client knows their business well, they can think of our services as an investment, not an expense. ### Incentive for Innovation Finally, if we base our service pricing on the value we bring to the client, we avoid a simple paradox: if it takes 100 hours to develop a project for client A, and then we modify the same project for client B in just 10 hours, we don't get paid 10 times less for the same work just because we worked 10 times fewer hours. This gives us an incentive to be innovative, to seek new ideas and approaches. We could even develop a ready-made product or suite of products and services — Software as a Service — that we offer to a wide range of clients. ### We Are More Than the Hours We Work In conclusion, as professionals, we are not the number of hours we work. We are the sum of all the skills and creativity we've accumulated over the years — in school, university, at work, through hobbies and personal projects. I believe our work deserves to be valued as something more than the hours spent in front of a computer, and our clients deserve something more than just executors. We might start with hourly or salaried work while gaining experience, but we have the potential to outgrow that model. The book *Breaking the Time Barrier* has answers to many questions you might have, and probably some you haven't thought of. I recommend everyone read it — it's worth the hour you'll spend. Some people have been approaching their projects this way for years. Personally, I'm working on integrating some of the ideas from the book. Right now, for example, I'm investing a lot of time in developing reusable tools that let me create certain types of web applications from scratch within a few days. ## The Bottom Line — Where Do We Go From Here? So we've put in a lot of effort, had some luck, and achieved everything we wanted. We started freelancing, where we're productive and quickly integrate the latest technologies and ideas. We faced challenges and found the courage to set our own direction. We even figured out the best way to price our time and work while staying motivated and innovative. But what comes next? How much more efficient can we become? What will our work process look like in 5 or 10 years? According to a *New York Times* article, 51% of jobs in the USA have the potential to be automated in the foreseeable future. "But I'm in IT," some of you might say. "Highly qualified specialist, how could this affect me?" Let's start with developers. **pix2code** is a project that generates code from a UI designer's mockup. For now, it doesn't aim to fully replace front-end developers, but rather to reduce the time between creating a design and implementing a prototype. But who knows — maybe someday we won't need as many developers as we do now? **The Grid** generates design based on data you provide. The system is trained with numerous examples created by professional designers. Based on these "good examples," the system automatically generates appropriate design layouts, and when you add new information, the design adapts to present it in the most suitable way. **Generative design** takes this further — instead of designing a product ourselves, we set certain constraints and goals, and the computer creates different variations to choose from: minimum drone weight, minimum cost, minimum material usage, maximum flight stability, ability to modify and recycle the product. The generated designs look incredibly organic — even the most wild human imagination couldn't conceive such forms. Can you imagine the incredible potential of platforms like GitHub automatically suggesting code optimizations and bug fixes, or Dribbble and Behance offering design improvements? This automation process won't happen overnight, everywhere. Especially in IT and other "modern" freelance professions like design, digital marketing, and finance, we feel that "there will always be work" for us. Or that even if much is automated, new professions will emerge that we can't yet imagine. But did people a hundred years ago think the same about horses? That "those automobiles are slow, dirty, noisy, and break down often" — and that horses' jobs weren't at risk? That "even if cars somehow take off, we'll find new work for horses that we can't yet imagine"? Today we know what actually happened. There are still working horses, but nothing like before. The claim that "better technologies will lead to new types of work for horses" sounds pretty silly in retrospect. But if we replace "horses" with "people" in that sentence... suddenly it seems reasonable. Our understanding of work has changed over the years — from agricultural labor and crafts to factory work to corporate offices. A few years from now, who knows — with increasing productivity, freelancing might become even more mainstream. But even as freelancers, what would we do if 50% of our current activities were automated? Which 50% would remain? What would be the true value of our work then? Could we still bill by the hour or by the month, or would we need to adapt in some other way? I'd love to hear your comments, impressions, and experiences. ``` ## Pages ### About me - Markdown: https://vdsabev.github.io/about.md - HTML: https://vdsabev.github.io/about/ ```markdown --- status: "unlisted" title: "About me" date: "2026-07-12" description: "I'm Vlad - a web developer who cares about why we build things the way we do rather than which framework we're using this week." image: "https://github.com/user-attachments/assets/14fa771a-2c72-4afc-b982-06d9125d0c57" imageAlt: "Vlad Sabev's picture" author: "Vlad Sabev" authorUrl: "https://github.com/vdsabev" authorAvatar: "https://github.com/vdsabev.png" navigation: "header" navigationIndex: 0 --- Vlad Sabev's picture I'm Vlad - a web developer who cares about why we build things the way we do rather than which framework we're using this week. I like to explore concepts by building them from scratch - a form library, a state manager, a Dribbble design coded pixel by pixel - and then taking what I've learned and teaching others. I reach for abstractions sparingly, name things for what they do, and try to leave both the codebase and my teammates a little better than I found them. These days I build mostly with _Vue_, _TypeScript_, and _Tailwind_ on _Bun/Node.js_ - but I've shipped production apps with _React_, _Angular_, _PHP_, _Ruby on Rails_, _Three.js_, and more over 15+ years of professional development and open source software. I've learned that the hard part isn't the tool itself, but writing code that's easy to read, composing tests you can actually trust, and encouraging curiosity within a team. I believe work should be engaging, fulfilling, and meaningful - and I'm figuring out what that means one post at a time. ```