<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Timur Brachkow</title><description>Personal blog of Timur Brachkow</description><link>https://www.brachkow.com/</link><item><title>Mise solved my tool version management fatigue</title><link>https://www.brachkow.com/notes/mise-solved-my-tool-version-management-fatigue/</link><guid isPermaLink="false">hKHgPFuz6NLx7muWVS5CJ6Ip2bjMKpi9U</guid><description>Personal blog of Timur Brachkow</description><pubDate>Sat, 14 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Tool version management seemed like an unresolved issue to me.&lt;/p&gt;
&lt;p&gt;My use case is simple — I need to ensure that for each project Node and pnpm versions are always the same on my and other developers&apos; machines.&lt;/p&gt;
&lt;p&gt;There are a lot of tools to do so: &lt;a href=&quot;https://github.com/nvm-sh/nvm&quot;&gt;nvm&lt;/a&gt;, &lt;a href=&quot;https://volta.sh/&quot;&gt;volta&lt;/a&gt;, &lt;a href=&quot;https://asdf-vm.com/&quot;&gt;asdf&lt;/a&gt;. Also, there is corepack, which is both &lt;a href=&quot;https://nodejs.org/dist/latest/docs/api/corepack.html&quot;&gt;deprecated&lt;/a&gt; and &lt;a href=&quot;https://github.com/nodejs/corepack&quot;&gt;not&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All of them can do the job, but with a lot of asterisks: some use strange formats, some can&apos;t install pnpm, some are just hard to set up.&lt;/p&gt;
&lt;h2&gt;Finally, we have a good one — mise&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://mise.jdx.dev/walkthrough.html&quot;&gt;Mise&lt;/a&gt; is a tool version manager done right:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://mise.jdx.dev/registry.html&quot;&gt;It isn&apos;t tied to one language ecosystem&lt;/a&gt;, e.g., it can handle pnpm, Node and Ruby&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mise.jdx.dev/configuration.html#mise-toml&quot;&gt;It has its own version format&lt;/a&gt;, and &lt;a href=&quot;https://mise.jdx.dev/lang/node.html#nvmrc-and-node-version-support&quot;&gt;supports standard version formats&lt;/a&gt; — chances are you can add it to an existing project, without bothering your colleagues with migrating&lt;/li&gt;
&lt;li&gt;It enables the needed tool version as you navigate the filesystem without doing any bash-gymnastics&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Apart from managing versions on your computer, it&apos;s helpful in a few other use cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jdx/mise-action&quot;&gt;You can use it to install tools in GitHub Actions&lt;/a&gt;. It is way simpler than googling a setup action for each tool, and then thinking how to pass version numbers from repo to action&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mise.jdx.dev/mise-cookbook/docker.html&quot;&gt;You can use it to install tools in Docker images&lt;/a&gt;. I don&apos;t think it gives the best build times, but you will have a single source of truth and a simple way to install all build tools you need&lt;/li&gt;
&lt;li&gt;It has prebuilt binaries that may not be available via your system package managers. For example, I was able to install an up-to-date Neovim version on Ubuntu 24, which I would otherwise need to build myself. You can check all the tools you can manage with mise &lt;a href=&quot;https://mise-versions.jdx.dev/&quot;&gt;here&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;P.S. The Mise author also has quite a nice secrets manager — &lt;a href=&quot;https://fnox.jdx.dev/&quot;&gt;fnox&lt;/a&gt;. So you don&apos;t store your API keys as plaintext in &lt;code&gt;$HOME&lt;/code&gt;, you know…&lt;/p&gt;
&lt;p&gt;P.P.S. When writing this post, I found that &lt;code&gt;mise&lt;/code&gt; can also &lt;a href=&quot;https://mise.jdx.dev/environments/&quot;&gt;do environment variable management&lt;/a&gt; and can act as a &lt;a href=&quot;https://mise.jdx.dev/tasks/&quot;&gt;task runner&lt;/a&gt;. Haven&apos;t tried any of these features yet.&lt;/p&gt;
</content:encoded></item><item><title>Use Zod to validate and document your environment variables</title><link>https://www.brachkow.com/notes/use-zod-to-validate-and-document-your-enviroment-variables/</link><guid isPermaLink="false">h1zBwyXXjcSuv8knuNsG1G2ShI8WMH6Z5</guid><description>Personal blog of Timur Brachkow</description><pubDate>Sun, 08 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;One of the most annoying errors in TypeScript is when your app crashes because you asserted the type of an environment variable, but forgot to set it.&lt;/p&gt;
&lt;p&gt;This is an especially frequent issue with various serverless tools, which tend to have a lot of environments. Each of these environments comes with its own secrets that need to be typed manually into the UI, which is easy to forget.&lt;/p&gt;
&lt;p&gt;Tired of such crashes, I decided to always validate environment variables, and found Zod to be a great solution, not only to this, but also to many other validation problems:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Types are inferred from the validation result, not asserted by hand. Forgetting to set variable will result in error being thrown.&lt;/li&gt;
&lt;li&gt;This validation serves as documentation for environment variable usage. This type of documentation is much more useful than what&apos;s written in &lt;code&gt;.env.example&lt;/code&gt; or &lt;code&gt;readme.md&lt;/code&gt;. Since you rely on the &lt;code&gt;zod.parse&lt;/code&gt; output, you won&apos;t forget to update it.&lt;/li&gt;
&lt;li&gt;Zod can perform transforms, which is useful because environment variables are always strings. For example, transform &lt;code&gt;&apos;1&apos;&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt;, or &lt;code&gt;one,two&lt;/code&gt; to &lt;code&gt;[&apos;one&apos;,&apos;two&apos;]&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here is an example of how to use Zod to validate environment variables:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import &apos;dotenv/config&apos;;
import { z } from &apos;zod&apos;;

const envSchema = z.object({
  BOT_TOKEN: z.string().min(1, &apos;BOT_TOKEN is not set&apos;),
  OPENROUTER_API_KEY: z.string().min(1, &apos;OPENROUTER_API_KEY is not set&apos;),
  WEBHOOK_URL: z.string().optional(),
});

export type EnvConfig = z.infer&amp;lt;typeof envSchema&amp;gt;;

export const env = envSchema.parse(process.env);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I describe the shape of &lt;code&gt;process.env&lt;/code&gt; as a Zod schema, and then use this Zod schema to parse, and then export valid and typed env values, or throw if something is wrong.&lt;/p&gt;
&lt;p&gt;Run this code as early as possible inside your app, or execute it as part of your CI or build script.&lt;/p&gt;
</content:encoded></item><item><title>Why I use Vue</title><link>https://www.brachkow.com/notes/what-i-like-in-vue/</link><guid isPermaLink="false">h2fvlePozCDFVeSR8e8wKimHLFcQh0Ppt</guid><description>I use Vue as my main framework since 2019. In this post I will explain the reasons behind my choice.</description><pubDate>Wed, 29 Oct 2025 19:03:45 GMT</pubDate><content:encoded>&lt;p&gt;I use Vue as my main framework since 2019. While Vue is &lt;a href=&quot;https://npmtrends.com/@angular/cli-vs-react-vs-vue&quot;&gt;one of the &quot;big three&quot; by adoption just alongside React and Angular&lt;/a&gt; people are still usually surprised when I tell it to them. In this post I will explain the reasons behind my choice.&lt;/p&gt;
&lt;p&gt;To put it short, I like Vue because it has an emphasis on DX, doesn&apos;t overcomplicate stuff and has everything you need included (and yes, &lt;a href=&quot;https://krausest.github.io/js-framework-benchmark/current.html#eyJmcmFtZXdvcmtzIjpbImtleWVkL2FuZ3VsYXItY2YiLCJrZXllZC9hbmd1bGFyLWNmLW5ldy1ub3pvbmUiLCJrZXllZC9hbmd1bGFyLWNmLW5vem9uZSIsImtleWVkL2FuZ3VsYXItY2Ytc2lnbmFscyIsImtleWVkL2FuZ3VsYXItY2Ytc2lnbmFscy1ub3pvbmUiLCJrZXllZC9hbmd1bGFyLW5nZm9yIiwia2V5ZWQvcmVhY3QtY2xhc3NlcyIsImtleWVkL3JlYWN0LWNvbXBpbGVyLWhvb2tzIiwia2V5ZWQvcmVhY3QtaG9va3MiLCJrZXllZC9yZWFjdC1ob29rcy11c2UtdHJhbnNpdGlvbiIsImtleWVkL3JlYWN0LWtyLW9ic2VydmFibGUiLCJrZXllZC9yZWFjdC1tbHluIiwia2V5ZWQvcmVhY3QtbW9iWCIsImtleWVkL3JlYWN0LW5hdGl2ZS1vbnl4Iiwia2V5ZWQvcmVhY3QtcmVkdXgiLCJrZXllZC9yZWFjdC1yZWR1eC1ob29rcyIsImtleWVkL3JlYWN0LXJlZHV4LWhvb2tzLWltbXV0YWJsZSIsImtleWVkL3JlYWN0LXJlZHV4LXJlbWF0Y2giLCJrZXllZC9yZWFjdC1yeGpzIiwia2V5ZWQvcmVhY3QtdGFnZ2VkLXN0YXRlIiwia2V5ZWQvcmVhY3QtdHJhY2tlZCIsImtleWVkL3JlYWN0LXp1c3RhbmQiLCJrZXllZC9zdmVsdGUiLCJrZXllZC9zdmVsdGUtY2xhc3NpYyIsImtleWVkL3Z1ZSIsImtleWVkL3Z1ZS1qc3giLCJrZXllZC92dWUtanN4LXZhcG9yIiwia2V5ZWQvdnVlLXBpbmlhIiwia2V5ZWQvdnVlLXZhcG9yIiwia2V5ZWQvdnVlcngtanN4Il0sImJlbmNobWFya3MiOlsiMDFfcnVuMWsiLCIwMl9yZXBsYWNlMWsiLCIwM191cGRhdGUxMHRoMWtfeDE2IiwiMDRfc2VsZWN0MWsiLCIwNV9zd2FwMWsiLCIwNl9yZW1vdmUtb25lLTFrIiwiMDdfY3JlYXRlMTBrIiwiMDhfY3JlYXRlMWstYWZ0ZXIxa194MiIsIjA5X2NsZWFyMWtfeDgiLCIyMV9yZWFkeS1tZW1vcnkiLCIyMl9ydW4tbWVtb3J5IiwiMjNfdXBkYXRlNS1tZW1vcnkiLCIyNV9ydW4tY2xlYXItbWVtb3J5IiwiMjZfcnVuLTEway1tZW1vcnkiLCI0MV9zaXplLXVuY29tcHJlc3NlZCIsIjQyX3NpemUtY29tcHJlc3NlZCIsIjQzX2ZpcnN0LXBhaW50Il0sImRpc3BsYXlNb2RlIjoxfQ==&quot;&gt;it is still very fast&lt;/a&gt;)!&lt;/p&gt;
&lt;p&gt;Let&apos;s see some examples:&lt;/p&gt;
&lt;h3&gt;You write js in &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;, css in &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; and html in &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Vue components are written as &lt;a href=&quot;https://vuejs.org/guide/scaling-up/sfc.html&quot;&gt;Single File Components&lt;/a&gt;. You write CSS in &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt;, html with template engine in &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; and javascript in &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;. It feels like writing a simple html file — a cornerstone of the web. Almost anyone in your team will understand how to make edits in such files.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-vue&quot;&gt;&amp;lt;script setup&amp;gt;
import { ref } from &apos;vue&apos;
const count = ref(0)
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;button @click=&quot;count++&quot;&amp;gt;Count: {{ count }}&amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;style scoped&amp;gt;
button { color: blue; }
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Non JSX templates&lt;/h3&gt;
&lt;p&gt;Templates are written as html with attributes like &lt;code&gt;v-for&lt;/code&gt;, &lt;code&gt;v-if&lt;/code&gt;, &lt;code&gt;v-else&lt;/code&gt;. You don&apos;t mess JS with HTML, and in result get more readable code than JSX (ask non-js people!).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-vue&quot;&gt;&amp;lt;template&amp;gt;
  &amp;lt;div v-if=&quot;user&quot;&amp;gt;
    &amp;lt;h1&amp;gt;Hello, {{ user.name }}!&amp;lt;/h1&amp;gt;
    &amp;lt;ul&amp;gt;
      &amp;lt;li v-for=&quot;item in items&quot; :key=&quot;item.id&quot;&amp;gt;
        {{ item.title }}
      &amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;p v-else&amp;gt;Please log in&amp;lt;/p&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;You write CSS as CSS&lt;/h3&gt;
&lt;p&gt;You can write regular CSS and just add &lt;code&gt;scoped&lt;/code&gt; attribute to &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; to encapsulate it. No need for CSS modules, CSS-in-JS and other stuff. Just write CSS and everything works.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-vue&quot;&gt;&amp;lt;style scoped&amp;gt;
.card { 
  padding: 1rem;
  border-radius: 8px;
}
/* Won&apos;t affect other components */
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Reactivity tools are reasonably named and framework has performance optimizations built-in&lt;/h3&gt;
&lt;p&gt;So you mark values as reactive with &lt;code&gt;ref&lt;/code&gt;, compute values with &lt;code&gt;computed&lt;/code&gt; and watch values with &lt;code&gt;watch&lt;/code&gt;. Usually you don&apos;t need to think about optimizing these.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// `ref` is the way to mark value as reactive, like useState
const count = ref(0)

// `computed` is value that was computed. It won&apos;t be recomputed if all dependent values are the same. Same as useMemo in react
const doubled = computed(() =&amp;gt; count.value * 2) 

// `watch` watches value, just like useEffect in react
watch(count, (newValue) =&amp;gt; {
  console.log(`Count changed to ${newValue}`)
}) 
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Components communicate via events&lt;/h3&gt;
&lt;p&gt;Components communicate with each other via events that go up. This results in much cleaner code without a need of callback prop drilling / overreliance on stores or resolving shadowing issues.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-vue&quot;&gt;&amp;lt;!-- Child.vue --&amp;gt;
&amp;lt;button @click=&quot;$emit(&apos;update&apos;, newValue)&quot;&amp;gt;Update&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-vue&quot;&gt;&amp;lt;!-- Parent.vue --&amp;gt;
&amp;lt;Child @update=&quot;handleUpdate&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Vue ecosystem is great&lt;/h3&gt;
&lt;p&gt;For most cases you can find exactly one great solution made by someone close to Vue or Nuxt teams. Few examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You may know &lt;code&gt;vite&lt;/code&gt; and &lt;code&gt;vitest&lt;/code&gt; that started as part of the Vue ecosystem, and now they are integral (and the least annoying) parts of the frontend toolchain.&lt;/li&gt;
&lt;li&gt;In Vue&apos;s default state manager &lt;a href=&quot;https://pinia.vuejs.org/introduction.html&quot;&gt;pinia&lt;/a&gt; you can update state like &lt;code&gt;store.count++&lt;/code&gt;. In redux you would need to have at least a PhD in computer science to do that.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Attributes passed to component&lt;/h3&gt;
&lt;p&gt;Anything that is not a declared prop is passed to component as an attribute. Same works for classes which will be joined in the expected way. Why this is not default behaviour in other frameworks is a mystery for me.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-vue&quot;&gt;&amp;lt;!-- MyButton.vue - only declares &apos;label&apos; prop --&amp;gt;
&amp;lt;template&amp;gt;
  &amp;lt;button class=&quot;btn&quot;&amp;gt;{{ label }}&amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;!-- Using the component --&amp;gt;
&amp;lt;MyButton label=&quot;Click&quot; class=&quot;primary large&quot; data-test=&quot;btn&quot; /&amp;gt;
&amp;lt;!-- Renders: &amp;lt;button class=&quot;btn primary large&quot; data-test=&quot;btn&quot;&amp;gt;Click&amp;lt;/button&amp;gt; --&amp;gt;
&amp;lt;!-- Classes are automatically joined! --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;And what I dislike…&lt;/h2&gt;
&lt;p&gt;I also have a lot of experience with other frameworks (mostly React and Svelte), so I can highlight some downsides.&lt;/p&gt;
&lt;h3&gt;Occasional quirks with lifecycle and reactivity&lt;/h3&gt;
&lt;p&gt;In comparison with modern React where component lifecycle is just a function lifecycle, Vue lifecycle and reactivity are more complicated. This usually doesn&apos;t cause any problems, but sometimes I don&apos;t clearly understand how it executes stuff in the correct order.&lt;/p&gt;
&lt;h3&gt;Tests require special tooling&lt;/h3&gt;
&lt;p&gt;You must rely on &lt;code&gt;vue-test-utils&lt;/code&gt; to write unit tests, which has a learning curve.&lt;/p&gt;
&lt;h3&gt;Nuxt&lt;/h3&gt;
&lt;p&gt;You can easily build SPA&apos;s of any size or complexity with just Vue, but for SSR you will need to rely on a framework.&lt;/p&gt;
&lt;p&gt;Unlike React which has plenty of SSR frameworks, or Svelte which has first-party SSR framework, Vue has only Nuxt (and few obscure DIY solutions).&lt;/p&gt;
&lt;p&gt;Nuxt is a third-party framework, which had its own business model but recently it was acquired by Vercel.&lt;/p&gt;
&lt;p&gt;Nuxt is the most widespread and straightforward way to do SSR in Vue. But apart from SSR, the framework comes with all kinds of very opinionated magic. Unlike magic in Vue, this magic in Nuxt breaks every time you walk away from examples.&lt;/p&gt;
&lt;p&gt;Another problem is Nuxt&apos;s overreliance on community-maintained modules. These modules usually quickly become unmaintained, while being promoted by all Nuxt resources as the official solution. With almost everything you will find yourself in a loop that looks like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;— I want to integrate X library into Nuxt. How can I do it?&lt;/p&gt;
&lt;p&gt;— Just run &lt;code&gt;nuxi module add everything-is-done-for-you&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;{trying module}&lt;/p&gt;
&lt;p&gt;{Module doesn&apos;t work. Installation guide is wrong. Repo has three commits and the last one was made two years ago by dependabot}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That said, making SSR apps is quite a frustrating process.&lt;/p&gt;
&lt;p&gt;I think Vue should have a first-party, non-commercial framework to do SSR, like Svelte does.&lt;/p&gt;
&lt;h2&gt;To sum up&lt;/h2&gt;
&lt;p&gt;I think even with its high adoption Vue is still a very undervalued framework.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It has great DX and it&apos;s accessible for programmers of all backgrounds&lt;/li&gt;
&lt;li&gt;It has great performance, without a need to constantly optimize it&lt;/li&gt;
&lt;li&gt;It&apos;s widely adopted so even quite a big company can find enough people who have hands-on experience with Vue&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, don&apos;t be afraid to try Vue!&lt;/p&gt;
</content:encoded></item><item><title>Selfhost privacy-friendly website analytics in one click</title><link>https://www.brachkow.com/notes/setting-privacy-friendly-analytics-for-free/</link><guid isPermaLink="false">hF23hXwmi3174qJojbcdSWveCuo7d6Dm7</guid><description>You can selfhost GDPR friendly Umami analytics on Railway using one-click template, and it will fit in free tier</description><pubDate>Sat, 01 Feb 2025 19:31:57 GMT</pubDate><content:encoded>&lt;p&gt;You can &lt;a href=&quot;https://railway.com/template/umami-analytics&quot;&gt;set up selfhosted instance of Umami Analytics on Railway with one click&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Umami is GDPR friendly, not limited by sites when selfhosted and has a &lt;a href=&quot;https://umami.is/features&quot;&gt;pretty much any feature you may want from analytics&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Free tier on Railway includes 5$ of free usage (which is more than enough for Umami), and pay-as-you-go after that.&lt;/p&gt;
&lt;p&gt;Interesting benefit — because your instance of Umami not hosted on Umami servers it is not cut by adblock.&lt;/p&gt;
</content:encoded></item><item><title>Guessing user location with Cloudflare Workers</title><link>https://www.brachkow.com/notes/guessing-user-location-with-cloudflare-workers/</link><guid isPermaLink="false">hEhU3aJb9RNWK2XFrAGAEWvA69aJRtxzI</guid><description>Personal blog of Timur Brachkow</description><pubDate>Sun, 26 Jan 2025 13:47:51 GMT</pubDate><content:encoded>&lt;p&gt;Recently I was building a landing page that needs some information relevant to user location displayed.&lt;/p&gt;
&lt;p&gt;Using browser&apos;s &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API&quot;&gt;Geolocation API&lt;/a&gt; is obvious solution, but who wants to give precise geolocation to a random website on the internet. Also I believe only the fact of such ask will make customers uncomfortable.&lt;/p&gt;
&lt;p&gt;Then I found out that cloudflare has access to unprecise location of any request going to the cloudflare worker and it&apos;s accessible to read.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// this code lives in your cloudflare page worker function
// for example functions/api/location.ts
// which is your example.com/api/location

import type { PagesFunction } from &apos;@cloudflare/workers-types&apos;;

export const onRequest: PagesFunction = async (context) =&amp;gt; {
  const { latitude, longitude, city, country } = context.request.cf || {};

  const coordinates = {
    latitude,
    longitude,
    city,
    country,
  };

  return new Response(JSON.stringify(coordinates), {
    headers: { &apos;Content-Type&apos;: &apos;application/json&apos; },
  });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you are not using Cloudflare Workers, there are services that matching ip&apos;s with locations.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.cloudflare.com/pages/functions/typescript/&quot;&gt;Using typescript in page worker functions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Sharing JavaScript code without headache</title><link>https://www.brachkow.com/notes/sharing-javascript-code-without-headache/</link><guid isPermaLink="false">hvBgvGWaGxdcBa3tRYSVl8AkRuVijlTDJ</guid><description>How to share code without the burden of private npm packages</description><pubDate>Mon, 07 Oct 2024 16:36:20 GMT</pubDate><content:encoded>&lt;p&gt;Sometimes you have multiple apps that share a lot of common code.&lt;/p&gt;
&lt;p&gt;For example, you have an SPA written in Vue and a Blog running on Eleventy, and you want to reuse your Tailwind design system between them.&lt;/p&gt;
&lt;p&gt;The obvious solution is to create a package with common code and publish it as a private NPM package. This usually leads to frustration: publishing packages is not a very straightforward or failure-tolerant process, which is completely reasonable for public packages, but mostly a waste of time for private packages and small-to-medium teams.&lt;/p&gt;
&lt;p&gt;I see this approach as overkill for most projects — there are much simpler alternatives:&lt;/p&gt;
&lt;h2&gt;Use Monorepo&lt;/h2&gt;
&lt;p&gt;The first one is to store code in a monorepo. Using the &lt;a href=&quot;https://pnpm.io/pnpm-workspace_yaml&quot;&gt;workspace&lt;/a&gt; feature, you will be able to install any package inside the monorepo.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.
├── .git
├── apps/
│   ├── spa/
│   │   ├── …
│   │   └── package.json
│   └── blog/
│       ├── …
│       └── package.json
├── packages/
│   └── some-package/
│       ├── …
│       └── package.json
├── pnpm-workspace.yaml
└── package.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you add a special file called &lt;code&gt;pnpm-workspace.yaml&lt;/code&gt; that allows you to install folders as packages.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;packages:
  - &apos;packages/*&apos;
  - &apos;apps/*&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then you can use them like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;name&quot;: &quot;spa&quot;,
  &quot;dependencies&quot;: {
    &quot;some-package&quot;: &quot;workspace:*&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Due to the fact that code is stored in one repo, you don&apos;t need to think about versioning — code revisions are coupled via commits.&lt;/p&gt;
&lt;h2&gt;Installing Packages from GitHub URLs&lt;/h2&gt;
&lt;p&gt;If you don&apos;t want to use a monorepo, you can install packages via package GitHub repo URLs.&lt;/p&gt;
&lt;p&gt;This way is almost the same as working with standard npm packages, but with one twist — you use a git repo as a package. That means you have a very forgiving publishing flow where you can revert any of your actions.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;pnpm i https://github.com/someorg/some-package#1.2.3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you see — tags can be used to achieve reproducible builds.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&quot;some-package&quot;: &quot;github:someorg/some-package#1.2.3&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you need to build some files — you can hook up a build script to the &lt;code&gt;postinstall&lt;/code&gt; script in &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;name&quot;: &quot;some-package&quot;,
  &quot;main&quot;: &quot;dist/main.css&quot;,
  &quot;scripts&quot;: {
    &quot;postinstall&quot;: &quot;pnpm i; postcss -i src/main.css -o dist/main.css&quot;
  },
  &quot;dependencies&quot;: {
    &quot;postcss&quot;: &quot;8.4.38&quot; // PostCSS is declared in dependencies, not devDependencies because we need it to be accessible from the postinstall script
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just keep in mind that you must move &lt;strong&gt;build dependencies&lt;/strong&gt; to &lt;code&gt;dependencies&lt;/code&gt; instead of &lt;code&gt;devDependencies&lt;/code&gt;. As this is a private package, this is not an issue, especially when you are using pnpm.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;As your project grows, you might hit the ceiling with this approach and you might need to switch to standard private packages, but your project needs to grow to this point and before that, you can save a lot of time!&lt;/p&gt;
&lt;p&gt;Monorepo, on the other hand, is future-proof. But you need to have a monorepo.&lt;/p&gt;
&lt;p&gt;Usually, I use a monorepo on all code I write by myself. When I fork someone&apos;s code, I install it via URL to keep the same project structure as the original repo.&lt;/p&gt;
</content:encoded></item><item><title>CORS proxy for Cloudflare Pages</title><link>https://www.brachkow.com/notes/cors-proxy-for-cloudflare-pages/</link><guid isPermaLink="false">hWePUjJE8Vg2GSr2O3abSqdgSzm1reMPE</guid><description>How to set up a CORS proxy on Cloudflare Pages using middleware functions</description><pubDate>Mon, 20 May 2024 16:36:20 GMT</pubDate><content:encoded>&lt;p&gt;Recently I moved all my projects from Vercel to Cloudflare Pages, because Cloudflare offers the same, but without bizarre per-seat pricing.&lt;/p&gt;
&lt;p&gt;Some of my projects have frontend and backend on different endpoints, and usually, this causes CORS errors that can be easily resolved via proxying using rewrites defined in the rewrites field of the platform config file.&lt;/p&gt;
&lt;p&gt;Cloudflare &lt;a href=&quot;https://developers.cloudflare.com/pages/configuration/redirects/&quot;&gt;has such a file&lt;/a&gt;, but to my surprise — it doesn&apos;t allow to proxy between different domains.&lt;/p&gt;
&lt;p&gt;Happily, &lt;a href=&quot;https://developers.cloudflare.com/pages/functions/middleware/&quot;&gt;Cloudflare allows us to do it in kinda backend-ish way by adding custom middleware to our frontend repo&lt;/a&gt;. For example — the code below proxies will proxy our request to &lt;code&gt;https://our-frontend.com/foo&lt;/code&gt; to &lt;code&gt;https://external-api.com/foo&lt;/code&gt; and we will have no CORS error.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// ~/functions/_middleware.ts

// Fun fact:
// If you place it at ~/functions/api/_middleware.ts
// this will proxy only https://our-frontend.com/api/* requests

import { PagesFunction } from &apos;@cloudflare/workers-types&apos;;

const API_URL = &apos;https://external-api.com&apos;;

export const onRequestOptions: PagesFunction = async () =&amp;gt; {
  return new Response(null, {
    status: 204,
    headers: {
      &apos;Access-Control-Allow-Origin&apos;: &apos;*&apos;,
      &apos;Access-Control-Allow-Headers&apos;: &apos;*&apos;,
      &apos;Access-Control-Allow-Methods&apos;: &apos;GET, OPTIONS&apos;,
      &apos;Access-Control-Max-Age&apos;: &apos;86400&apos;,
    },
  });
};

export const onRequest: PagesFunction = async (context) =&amp;gt; {
  const targetUrl = API_URL + new URL(context.request.url).pathname;

  const immutableResponse = await fetch(
    new Request(targetUrl, context.request),
  );

  const response = new Response(immutableResponse.body, immutableResponse);

  response.headers.set(&apos;Access-Control-Allow-Origin&apos;, &apos;*&apos;);
  response.headers.set(&apos;Access-Control-Max-Age&apos;, &apos;86400&apos;);
  return response;
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>How to preview files in browser</title><link>https://www.brachkow.com/notes/preview-files-in-browser/</link><guid isPermaLink="false">hQZQalK0qW98gUk01JH5r28W70Qh9wwXu</guid><description>Cheatsheet for previewing different file types (images, CSV, PDF, Office docs) directly in the browser, with implementation tips and limitations</description><pubDate>Wed, 10 Apr 2024 16:36:20 GMT</pubDate><content:encoded>&lt;p&gt;Recently I was building an in-browser document preview. It turned out to be a quirky task because browsers usually assume that users handle opening files on their own machine.&lt;/p&gt;
&lt;p&gt;This is what I found out:&lt;/p&gt;
&lt;h2&gt;Images&lt;/h2&gt;
&lt;p&gt;That one is obvious — just use the &lt;code&gt;&amp;lt;img/&amp;gt;&lt;/code&gt; tag&lt;/p&gt;
&lt;h2&gt;CSV&lt;/h2&gt;
&lt;p&gt;It&apos;s trivial to parse CSV in the browser — you just split the file into rows by &lt;code&gt;\n&lt;/code&gt; and then split them into columns by a separator (which can be &lt;code&gt;,&lt;/code&gt;, &lt;code&gt;;&lt;/code&gt;, &lt;code&gt;space&lt;/code&gt;, etc.).&lt;/p&gt;
&lt;p&gt;Since separators introduce some caveats, I recommend using readymade solutions like &lt;a href=&quot;https://www.npmjs.com/package/papaparse&quot;&gt;papaparse&apos;s&lt;/a&gt; &lt;code&gt;Papa.parse(csvAsString)&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;PDF&lt;/h2&gt;
&lt;p&gt;Browsers can preview PDFs with &lt;code&gt;&amp;lt;embed src=&quot;example.pdf&quot;/&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Sadly, mobile Safari can&apos;t, so if this is crucial for you, you might need to use one of the many libraries for that. In my experience, such libraries have tons of glitches — keep that in mind.&lt;/p&gt;
&lt;h2&gt;Office documents&lt;/h2&gt;
&lt;p&gt;For office documents like ppt, pptx, doc, docx, xls, and xlsx, there are two types of solutions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You can use some quirky library made by unknown geniuses (&lt;a href=&quot;https://501351981.github.io/vue-office/examples/dist/#/docx&quot;&gt;example&lt;/a&gt;), but since proprietary formats are complicated, results may vary&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can use an iframe from &quot;format owners&quot; like Microsoft and Google. There is a nice &lt;a href=&quot;https://gist.github.com/tzmartin/1cf85dc3d975f94cfddc04bc0dd399be&quot;&gt;gist about these on GitHub&lt;/a&gt;. In my experience, Google&apos;s iframe is very unreliable, so don&apos;t be tempted to use it because of the huge variety of formats — just go with Microsoft&apos;s one. Of course, if you go with this option, you must keep in mind the privacy implications of sending documents to someone&apos;s API.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Caveats of using macOS with an external monitor</title><link>https://www.brachkow.com/notes/macos-monitor/</link><guid isPermaLink="false">hSoLidchdM2tZAkjipAqhtgUUsbjiHksV</guid><description>Things I learned about using external monitors with macOS: how to fix blurry LG monitors, control your monitor without reaching for buttons, and why your cat can no longer wake up your Mac.</description><pubDate>Thu, 04 Jan 2024 16:36:20 GMT</pubDate><content:encoded>&lt;h2&gt;Fixing LG Monitors&lt;/h2&gt;
&lt;p&gt;LG produces good monitors, but they pack them with &quot;Smart Features&quot; that make picture quality worse.&lt;/p&gt;
&lt;p&gt;Go to the monitor menu and:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Disable &lt;strong&gt;«Super Resolution»&lt;/strong&gt; to prevent everything from becoming blurry.&lt;/li&gt;
&lt;li&gt;Disable &lt;strong&gt;«Smart Energy Saving»&lt;/strong&gt; to prevent the screen from self-managing brightness.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Controlling Monitor from macOS&lt;/h2&gt;
&lt;p&gt;By default, you can&apos;t control the monitor from your Mac; you will need to reach your monitor by hand. This is especially annoying with sound. There are apps for controlling monitors from the OS instead of the menu.&lt;/p&gt;
&lt;p&gt;There is a free app called &lt;a href=&quot;https://github.com/MonitorControl/MonitorControl&quot;&gt;MonitorControl&lt;/a&gt; that allows you to control brightness and sound the same way you do it on a MacBook.&lt;/p&gt;
&lt;h2&gt;Waking up Mac&lt;/h2&gt;
&lt;p&gt;If you ever used Windows you know: if your cat jumps on computer&apos;s keyboard  — it will wake up.&lt;/p&gt;
&lt;p&gt;On macOS — forget about that! To wake Macbook when using external monitor you need to swipe on Magic Trackpad or shake mouse.&lt;/p&gt;
</content:encoded></item><item><title>How to make Firefox sane and private</title><link>https://www.brachkow.com/notes/firefox/</link><guid isPermaLink="false">h8pCkNXO1BoYYxkJuqFJaxYV9aShZChvO</guid><description>Essential Firefox privacy tweaks, recommended security extensions</description><pubDate>Fri, 06 Oct 2023 16:36:20 GMT</pubDate><content:encoded>&lt;p&gt;Nowadays Firefox is the only good browser.&lt;/p&gt;
&lt;p&gt;Apple ruined Safari by moving extensions into AppStore&apos;s walled garden, the company behind Brave &lt;a href=&quot;https://www.theverge.com/2020/6/8/21283769/brave-browser-affiliate-links-crypto-privacy-ceo-apology&quot;&gt;has a reputation for questionable decisions&lt;/a&gt;, and all other browsers are just Chrome (spyware).&lt;/p&gt;
&lt;p&gt;Being non-chrome and non-apple comes with a price — Firefox is kinda quirky, and you still have some work to do for more privacy.&lt;/p&gt;
&lt;p&gt;Here are my instructions for sane and private Firefox.&lt;/p&gt;
&lt;h2&gt;Flags&lt;/h2&gt;
&lt;p&gt;Enter &lt;code&gt;about:config&lt;/code&gt; in the search bar. Through the search, find the necessary settings and set the desired values. Here&apos;s what needs to be changed and why:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;extensions.pocket.enabled = false&lt;/code&gt; — removes Pocket integration.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dom.private-attribution.submission.enabled&lt;/code&gt; — removes ad activity reports&lt;/li&gt;
&lt;li&gt;&lt;code&gt;network.captive-portal-service.enabled = false&lt;/code&gt; and &lt;code&gt;browser.selfsupport.url = false&lt;/code&gt; — disables &lt;a href=&quot;https://spyware.neocities.org/articles/firefox.html&quot;&gt;browser usage data sending&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;apz.allow_zooming = true&lt;/code&gt; — enables &quot;pinch to zoom&quot; (like in Chrome or Safari).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;general.smoothScroll.mouseWheel.durationMaxMS = 200&lt;/code&gt; — sets scrolling speed as in Chrome.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Extensions for better privacy&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/ru/firefox/addon/happy-bonobo-disable-webrtc/&quot;&gt;Disable WebRTC&lt;/a&gt; — disables WebRTC by default, allowing it to be enabled when needed (like where audio-video communication is present). WebRTC can reveal your IP address even through VPN.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/ru/firefox/addon/uaswitcher/&quot;&gt;User-Agent Switcher&lt;/a&gt; — automatically switches the User-Agent. Turn on random mode to prevent fingerprinting.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/clearurls/&quot;&gt;Clear URLs&lt;/a&gt; — clears URL of trackers.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/&quot;&gt;uBlock Origin&lt;/a&gt; — internet era antivirus.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/ru/firefox/addon/privacy-badger17/&quot;&gt;Privacy Badger&lt;/a&gt; — cleans up trackers that uBlock missed.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/ru/firefox/addon/https-everywhere/&quot;&gt;HTTPS Everywhere&lt;/a&gt; — forces websites to use encrypted HTTPS instead of unencrypted HTTP.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/firefox/addon/decentraleyes/&quot;&gt;Decentraleyes&lt;/a&gt; — protects against tracking via CDNs.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/ru/firefox/addon/facebook-container/&quot;&gt;Facebook Container&lt;/a&gt; — isolates Meta products from other browser tabs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Separate bookmarks and passwords from the browser&lt;/h2&gt;
&lt;p&gt;Switching from one browser to another is a good time to realize that storing passwords and bookmarks in a browser is a bad idea. Upon logging in, Firefox will try to offer its synchronization service — I recommend refusing it.&lt;/p&gt;
&lt;p&gt;Next, you need to turn off password saving. To do this, go to &lt;code&gt;Preferences → Privacy and Security → Logins and Passwords&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For bookmark synchronization, I recommend &lt;a href=&quot;https://raindrop.io/&quot;&gt;Raindrop.io&lt;/a&gt;, and for passwords, &lt;a href=&quot;https://bitwarden.com/&quot;&gt;Bitwarden&lt;/a&gt;. Both are decent, free of charge, and work on all devices and browsers.&lt;/p&gt;
</content:encoded></item><item><title>Using Sublime Text as IDE</title><link>https://www.brachkow.com/notes/sublime-text/</link><guid isPermaLink="false">h2tx6XW260KzoUykbLt0dfby7scfMd8uw</guid><description>How to turn Sublime Text into a modern IDE — with LSP, linting, Copilot, while retaining the same performance and energy efficiency</description><pubDate>Wed, 06 Sep 2023 16:36:20 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;../../assets/sublime-text-ide.png&quot; alt=&quot;Sublime Text&quot;&gt;&lt;/p&gt;
&lt;p&gt;I have been using Sublime Text as my primary IDE for all my development needs for more than 2 years now.&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;it is sustainable (just like Vim)&lt;/li&gt;
&lt;li&gt;it is very fast (just like Vim)&lt;/li&gt;
&lt;li&gt;it is energy efficient (just like Vim)&lt;/li&gt;
&lt;li&gt;it is easy to use (just like VS Code)&lt;/li&gt;
&lt;li&gt;it is easy to configure (just like VS Code)&lt;/li&gt;
&lt;li&gt;it has an active community so finding plugins is not a problem (but sadly, it&apos;s more like Vim than VS Code)&lt;/li&gt;
&lt;li&gt;it can be used for free if you can&apos;t pay for it (just like WinRAR)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If it is so good why do so many people prefer VS Code / WebStorm?&lt;/p&gt;
&lt;p&gt;Because Sublime Text is a text editor, not an IDE.&lt;/p&gt;
&lt;p&gt;Sublime Text comes from the time when you could build your cool website from any text editor (like NotePad), but time has changed since then. Some modern technologies, like TypeScript, are hard to use without IDE features.&lt;/p&gt;
&lt;p&gt;With time VSCode which started as a text editor incorporated all important IDE features (at the cost of becoming a performance blackhole and battery killer).&lt;/p&gt;
&lt;p&gt;But Sublime Text chose another way — it remained as a text editor that users could tailor to their needs. Unlike Vim or Emacs Sublime Text isn&apos;t a cult, so tweaking settings is not considered cool by anyone. Because of this, most people don&apos;t even think about Sublime Text as an option.&lt;/p&gt;
&lt;p&gt;But Sublime Text is an option.&lt;/p&gt;
&lt;p&gt;In the following guide, I will tell you how to turn Sublime Text into a modern IDE — with LSP, linting, and even Copilot, while retaining the same performance and energy efficiency. And unlike Vim, it won&apos;t take forever.&lt;/p&gt;
&lt;h2&gt;Before we start&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The easiest way to navigate around the editor is the command palette. There are actually two of them: one for files can be summoned via &lt;code&gt;command + P&lt;/code&gt; and one for actions is summoned via &lt;code&gt;command + shift + P&lt;/code&gt;. Use the least one to find the settings of your editor and plugins.&lt;/li&gt;
&lt;li&gt;You can find editor preferences at &lt;code&gt;Sublime Text / Settings / Settings&lt;/code&gt;, and plugin preferences at &lt;code&gt;Sublime Text / Settings / Package Settings&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;There is no package manager or plugin store preinstalled. To install plugins you need to install &lt;a href=&quot;https://packagecontrol.io/installation&quot;&gt;Package Control&lt;/a&gt;.
Search for plugins at &lt;a href=&quot;https://packagecontrol.io/&quot;&gt;Package Control website&lt;/a&gt; and install them via &lt;code&gt;command + shift + P&lt;/code&gt; then &lt;code&gt;Package Control: Install&lt;/code&gt; then enter the plugin name.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Bringing IDE features with LSP&lt;/h2&gt;
&lt;p&gt;To bring IDE-like features to your text editor you need to use &lt;a href=&quot;https://github.com/sublimelsp/LSP#installation&quot;&gt;LSP&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You will need to add support for languages you need separately. Here is the list of &lt;a href=&quot;https://lsp.sublimetext.io/language_servers/&quot;&gt;languages with installation instructions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Apart from autocomplete and type features. LSP also brings you a lot of other IDE features like code actions, go to definition, find all references, rename symbol, etc. You can find them in right click menu.&lt;/p&gt;
&lt;p&gt;Annoying window with errors can be disabled by adding &lt;code&gt;&quot;log_server&quot;: false&lt;/code&gt; to LSP settings.&lt;/p&gt;
&lt;h2&gt;Linting&lt;/h2&gt;
&lt;p&gt;All linting works via &lt;a href=&quot;https://packagecontrol.io/packages/SublimeLinter&quot;&gt;SublimeLinter&lt;/a&gt; plugin. By itself it does nothing — you need to add plugins for linters to use. Just search their names in package control like &lt;code&gt;SublimeLinter-eslint&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For prettier that runs on save use &lt;a href=&quot;https://packagecontrol.io/packages/JsPrettier&quot;&gt;jsPrettier&lt;/a&gt;. To enable add &lt;code&gt;auto_format_on_save: true&lt;/code&gt; to jsPrettier Preferences.&lt;/p&gt;
&lt;h2&gt;Other plugins you will need&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://packagecontrol.io/packages/A%20File%20Icon&quot;&gt;A File Icon&lt;/a&gt; — file icons for everything&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://packagecontrol.io/packages/SideBarEnhancements&quot;&gt;Sidebar Enhancements&lt;/a&gt; — useful actions for file explorer&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://packagecontrol.io/packages/Emmet&quot;&gt;Emmet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Git&lt;/h2&gt;
&lt;p&gt;Sublime Text shows inline diffs but has no integrated Git GUI. I recommend using &lt;a href=&quot;https://www.sublimemerge.com/&quot;&gt;Sublime Merge&lt;/a&gt; for that. You will be also able to access it&apos;s features via Sublime Text&apos;s right-click menu.&lt;/p&gt;
&lt;h2&gt;Integrated Terminal&lt;/h2&gt;
&lt;p&gt;Sublime Text has no integrated terminal — you should use a dedicated terminal app for that.
If you can&apos;t live without an integrated terminal, you can use &lt;a href=&quot;https://packagecontrol.io/packages/Terminus&quot;&gt;Terminus&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;GitHub Copilot&lt;/h2&gt;
&lt;p&gt;There is an &lt;a href=&quot;https://github.com/TerminalFi/LSP-copilot&quot;&gt;LSP for Copilot&lt;/a&gt; that allows you to use AI autocomplete and code suggestions.&lt;/p&gt;
&lt;h2&gt;Painless Vim&lt;/h2&gt;
&lt;p&gt;To use Vim (or fight Vim FOMO) you can use &lt;a href=&quot;https://www.sublimetext.com/docs/vintage.html&quot;&gt;Vintage Mode&lt;/a&gt; that ships with the editor. It has a lot of Vim stuff included.&lt;/p&gt;
&lt;p&gt;I especially like that with Vintage Mode enabled your initial editing mode is still &lt;code&gt;INSERT MODE&lt;/code&gt;, so if you are not in the mood for Vim you will be not annoyed by pressing &lt;code&gt;i&lt;/code&gt; each time you open the editor.&lt;/p&gt;
&lt;h2&gt;Tips&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;control + shift + P&lt;/code&gt; to view scope name. Useful when configuring plugin scopes or building your own color schemes.&lt;/li&gt;
&lt;li&gt;There is an «Arithmetics» tool in the command palette that allows you to use mathematical expressions on selected text.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;command + K&lt;/code&gt; then &lt;code&gt;command + B&lt;/code&gt; to toggle the sidebar and make more room for an editor. Most of the time you don&apos;t need it as &lt;code&gt;command + P&lt;/code&gt; is a way much faster to open files.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;options + command + S&lt;/code&gt; to save all opened files.&lt;/li&gt;
&lt;li&gt;Click editor tabs or files with &lt;code&gt;shift&lt;/code&gt; to temporary split window between them. Very useful to keep reference info in front of your eyes.&lt;/li&gt;
&lt;li&gt;In menu bar go to &lt;code&gt;Selection&lt;/code&gt; and check selection shortcuts.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item></channel></rss>