• #Introduction

    Welcome to the my tiny wiki!

    This is a place where I keep some of the code snippets that I want to have stored somewhere. If you spot mistakes I'd be glad to here them.

    Patrick.

    #Trim Bash History

    # Trim trailing empty continuation lines from zsh history entries.
    # Backs up the original to ~/.zsh_history.bak.
    trim-history() {
      local file="${HISTFILE:-$HOME/.zsh_history}"
      fc -W
      perl -i.bak -0777 -pe 's/(\\\n)+(\n)(?=: \d+:\d+;|\z)/$2/g' "$file"
      fc -R "$file"
      echo "Cleaned $file (backup at $file.bak)"
    }
    

    #Error Handling

    A guide on how to handle errors. Frontend-flavoured, but most of it applies anywhere, including Python on the backend. The only truly frontend-specific bits are React error boundaries and the toast/banner patterns. Those are called out where they appear.

    The whole thing boils down to: make errors specific, keep them as values, translate them at layer boundaries, handle them as late as possible.


    1. Make specific errors

    Don't throw new Error("something went wrong"). Extend your own error class so the type itself carries meaning, and so you can act on it.

    Errors should be specific enough that the handler can do something useful with them, and they should carry the relevant info as fields.

    class UserNotFoundError extends DomainError {
      readonly kind = "user-not-found" as const;
      constructor(public readonly userId: string, cause?: unknown) {
        super(`User ${userId} not found`, { cause });
      }
    }
    
    class UserNotFoundError(DomainError):
        kind = "user-not-found"
        def __init__(self, user_id: str, cause: Exception | None = None):
            super().__init__(f"User {user_id} not found")
            self.user_id = user_id
            self.__cause__ = cause
    

    The kind (or _tag, whatever you like) field makes these usable as a discriminated union, so the handler can switch on it and the type system helps you stay exhaustive.

    Use cause to capture the underlying error

    Error.cause is standard ES2022. Python has raise ... from ... (or __cause__ directly) for the same purpose. Use it whenever you wrap or translate an error so the original is preserved in the chain. Sentry walks the chain automatically, so you get a full trace without doing anything extra.

    try {
      return await db.query(/* ... */);
    } catch (e) {
      throw new UserRepositoryError("failed to load user", { cause: e });
    }
    
    try:
        return db.query(...)
    except DatabaseError as e:
        raise UserRepositoryError("failed to load user") from e
    

    This ties directly into layer ownership: the cause is how the lower-layer error survives translation without leaking.


    2. Each layer owns its errors

    No layer should simply pass errors on. Errors are implementation details and shouldn't leak across boundaries.

    The point is not that every layer needs its own bespoke wrapper class for every case. It's that the information shouldn't get lost and should still be usable in a switch. How that's achieved depends on context. Sometimes a translated error class is the right call, sometimes a tagged union of errors generated from the OpenAPI schema is enough on its own. As long as the consumer can discriminate the cases without reaching into a lower layer's types, the goal is met.

    Concrete cases:

    • A backend should never expose an SQL error to the client. Cleanliness, but also security: SQL errors leak schema.
    • An API adapter on the frontend shouldn't let raw fetch rejections or HTTP status codes bubble into business logic. The shape of the error is up to you, what matters is that the upstream consumer doesn't have to know about Response.
    • A repository shouldn't let ORM errors reach the use case.

    When a translation does happen, it uses cause so debugging is still possible.

    async function getUser(id: string): Promise<Result<User, GetUserError>> {
      const res = await fetch(`/api/users/${id}`);
      if (res.status === 404) return err(new UserNotFoundError(id));
      if (!res.ok) return err(new ApiError(res.status, { cause: await res.text() }));
      return ok(await res.json());
    }
    

    3. Have an UnknownError

    We will forget to handle something. An explicit UnknownError (or UnexpectedError) for the "we messed up" case is much better than letting raw Error instances escape.

    It serves two purposes:

    • Makes the catch-all in your union explicit, so exhaustive matching (switch plus assertNever) actually works.
    • Gives the generic handler (§7) something predictable to render.
    function assertNever(x: never): never {
      throw new UnknownError("non-exhaustive match", { cause: x });
    }
    

    This is the safety net at the type level. Error boundaries are the safety net at runtime.


    4. Domain errors live in the business layer

    If a layer has complex logic and many failure cases, it gets its own errors. These tend to be the most valuable ones because they encode actual business invariants:

    • InsufficientFundsError
    • OrderAlreadyShippedError
    • EmailAlreadyTakenError

    These map naturally to UI affordances (§7). EmailAlreadyTakenError becomes a field error on the email input, no string-matching required.


    5. Don't throw, return Results

    Throwing is invisible to the type system (TS has no checked exceptions, Python has none at all). Returning a Result<T, E> makes the error part of the signature, and the caller can't accidentally ignore it.

    type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };
    
    type GetUserError = UserNotFoundError | ApiError | UnknownError;
    function getUser(id: string): Promise<Result<User, GetUserError>> { /* ... */ }
    

    Roll your own (it's twenty lines) or pick a library if you want the ergonomics. The pattern works the same on the backend: a Result type or a tagged union return value beats raising for expected failure cases.

    Throw only where an adapter requires it. React renders, framework lifecycle hooks, event handlers wired to libraries that expect throws, FastAPI/Flask exception handlers that translate to HTTP responses. Those are the boundary. Your own code returns Results. The throw at the boundary then gets caught and turned back into a value (or a response).


    6. Handle errors where you can, not earlier

    An error should travel up until it reaches a layer that can actually do something useful with it. Earlier handling either swallows information or forces lower layers to know about UI concerns.

    Example flow for a failed API call:

    1. API adapter sees a 503. It knows this is transient, retries with backoff. Succeeds, done. Fails again, returns err(new ApiUnavailableError(...)).
    2. Use case / query layer has nothing useful to do with ApiUnavailableError. Passes it up unchanged (still as a Result).
    3. UI receives the error. Shows it to the user, logs it, reports it to Sentry.

    The rule of thumb: don't catch unless you're adding value, meaning translating the error, retrying, or actually resolving it. A catch that just re-throws is noise, and worse, often loses the stack.

    Logging follows the same principle. Log at the point of handling, not at every layer the error passes through, otherwise Sentry fills up with multiple copies of the same incident.


    7. Three ways to handle an error

    Once an error reaches a place that can act on it, there are three flavours of handling. They compose; one error can trigger all three.

    Recover (acting)

    The handler does something programmatic. The user may not even see this happen.

    • 401 redirects to login
    • Token expired, refresh and retry
    • Stale cache, invalidate and refetch

    Surface (presenting)

    A specific piece of UI shows context-appropriate feedback. This is where domain errors pay off, because they map to UI states without string-matching.

    • EmailAlreadyTakenError becomes a field error on the email input
    • InsufficientFundsError becomes an inline warning next to the amount
    • OrderAlreadyShippedError disables the cancel button and explains why

    Fallback (generic)

    Always present, because we will never anticipate every error. Start here. Wire up the generic handler before the specific ones, so unhandled errors fail loudly but gracefully.

    The shape depends on what triggered the error:

    • Actions / mutations (user-initiated, transient): error toast. The user did a thing, the thing failed, they want to know and try again. Maps to TanStack Query's mutation.onError.
    • Queries (data-shaped, persistent): inline banner or block element where the data should have been. The screen is broken, not an action. Maps to TanStack Query's query.error rendered inside the component.

    The fallback is also where reporting happens: Sentry call, user-friendly message, optionally a "copy error id" affordance.


    8. Error boundaries

    This is the bulkhead pattern from resilience engineering. A ship is divided into watertight compartments so a breach floods one section, not the whole vessel. Applied to software: an isolation barrier around a region contains a failure to that region, leaving the rest running in a degraded state.

    The pattern is general. A failing widget shouldn't take down the page. A failing background job shouldn't take down the worker. A failing downstream service shouldn't take down the API (circuit breakers are the network-layer version of the same idea).

    In React

    React's ErrorBoundary is the implementation of this pattern for the render tree. It catches errors thrown during render (and in lifecycle methods) and renders a fallback UI instead of letting the whole tree unmount. It does not catch:

    • errors in event handlers
    • errors in async code
    • errors in useEffect callbacks

    That's fine. Those are the cases where §5 applies and you should be returning Results anyway. The boundary is the safety net for "something escaped our model", which ties back to §3.

    Stack them

    One at the page root catches catastrophes. More granular ones around independent regions (sidebar, widget, panel) keep the rest of the page running when one piece breaks.

    <RootBoundary>
      <Header />
      <SidebarBoundary><Sidebar /></SidebarBoundary>
      <MainBoundary>
        <WidgetBoundary><Widget /></WidgetBoundary>
        <WidgetBoundary><Widget /></WidgetBoundary>
      </MainBoundary>
    </RootBoundary>
    

    Always wrap with a named error

    When the boundary catches something, don't log the raw error. Wrap it in a named one with the original as cause. This is what makes the report useful later when you're trying to figure out where it came from.

    class RenderError extends AppError {
      readonly kind = "render-error" as const;
      constructor(public boundary: string, cause: unknown) {
        super(`render failed in ${boundary}`, { cause });
      }
    }
    

    Pair it with logging that includes the boundary name and the component tree. Now Sentry tells you both what went wrong (the cause) and where it was contained (the boundary).


    TL;DR

    • Specific error classes, with cause chains.
    • Translate at layer boundaries; the form depends on context, the goal is that information doesn't get lost and stays usable in a switch.
    • Have an UnknownError for the gaps.
    • Domain errors encode business invariants and map cleanly to UI.
    • Return Results in your own code; throw only where adapters demand it.
    • Handle as late as possible, where the handler can actually do something.
    • Three handling modes: recover, surface, fallback. Always have a fallback.
    • Stack error boundaries (bulkheads) for runtime isolation; wrap caught errors in named ones.

    #Core Resilience Patterns

    Core Resilience Patterns

    Circuit Breaker
    Monitors calls to a service and "trips" (opens the circuit) when failures exceed a threshold, preventing cascading failures. After a timeout, it allows limited requests to test recovery before closing again.

    Retry
    Automatically retries failed requests, often with exponential backoff (waiting longer between each retry) and jitter (randomized delays to avoid thundering herd problems).

    Timeout
    Sets a maximum wait time for any operation. Without timeouts, slow dependencies can exhaust thread pools and bring down the whole system.

    Bulkhead
    Isolates components into separate resource pools (like compartments in a ship's hull) so a failure in one doesn't drain resources from others. For example, giving different services separate connection pools. Aka. Error Boundary

    Fallback
    Defines an alternative behavior when a call fails: returning cached data, a default value, or a degraded but functional response instead of an error.

    Rate Limiting & Throttling
    Controls how many requests a service accepts per time window, protecting it from overload, either self-imposed or enforced on callers.

    Stability Patterns

    Idempotency
    Designing operations so they can be safely retried without side effects (e.g., using idempotency keys for payments).

    Cache-Aside (Read-Through Cache)
    Serving responses from cache when the origin is slow or unavailable, reducing dependency on downstream services.

    Health Checks & Readiness Probes
    Exposing endpoints that load balancers and orchestrators (like Kubernetes) use to route traffic away from unhealthy instances.

    Graceful Degradation
    Continuing to serve core functionality even when non-critical services fail (e.g., showing a product page without recommendations if the recommendation service is down).

    Observability Patterns

    Dead Letter Queue (DLQ)
    Failed messages in async systems are routed to a DLQ for inspection and reprocessing rather than being silently dropped.

    Saga Pattern
    Managing distributed transactions across microservices with compensating transactions to undo partial work on failure.

    #RFC 9457 - Problem Details for HTTP APIs

    A Standard of handling errors in APIs. Reference

    HTTP/1.1 403 Forbidden
    Content-Type: application/problem+json
    Content-Language: en
    
    {
     "status": "403"
     "type": "https://wiki.stillh.art/errors/out-of-luck",
     "title": "You're out of luck :(",
     "detail": "You only had 3 wishes",
     "instance": "/account/12345/msgs/abc",
    }
    

    #Prefetching Timeout

      const queryClient = new QueryClient();
      const prefetchHandler = createPrefetch(queryClient, 500);
    
      await prefetchHandler.prefetch(["transactions", QUANTITY], () =>
        apiFetcher({
          url: `URL`,
        })
      );
    
      // e.g.
      // const dehydratedState = prefetchHandler.dehydrate();
    
    function prefetch() {
      const fetchPromise = queryClient.fetchQuery({
        queryKey,
        queryFn,
        ...options,
      });
    
      const data = (await Promise.race([
        fetchPromise,
        timeout(timeoutDuration),
      ])) as TData;
    
      return {
        type: "data",
        data,
      };
    }
    

    #Resilience: Testing

    Chaos Engineering

    // middleware.ts - Add to introduce random failures
    import { NextResponse } from 'next/server';
    
    export function middleware(request) {
      if (process.env.ENABLE_CHAOS === 'true') {
        // 10% of requests fail
        if (Math.random() < 0.1) {
          return new NextResponse(
            JSON.stringify({ message: 'Chaos monkey error' }),
            { status: 500 }
          );
        }
    
        // 20% of requests are slow
        if (Math.random() < 0.2) {
          return new Promise(resolve => {
            setTimeout(() => resolve(NextResponse.next()), 2000);
          });
        }
      }
    
      return NextResponse.next();
    }
    

    See ChaosMonkey

    #Resilience: Timeout

    Usually for SSR. Fail after a timeout, to prevent blocking finalising for to long.

    function prefetchWithTimeout(url: string, timeout = 300) {
      const controller = new AbortController();
      const timer = setTimeout(() => controller.abort(), timeout);
    
      return fetch(url, { signal: controller.signal })
        .finally(() => clearTimeout(timer));
    }
    

    #Resilience: Retry

    For transient issues, e.g. network errors. Can be improved with exponential back-off and jitter (to prevent Thundering Herd Problem).

    import { useQuery } from '@tanstack/react-query';
    
    export default function ProductPage({ id }) {
      const { data, error } = useQuery({
        queryKey: ['product', id],
        queryFn: () => fetch(`/api/products/${id}`).then(res => res.json()),
        retry: 3,
         /**
         * Calculates the delay before the next retry attempt using exponential backoff.
         *
         * @param {number} attempt - The number of the current retry attempt (starting from 0).
         * @returns {number} The delay in milliseconds before the next retry, capped at 30,000 ms.
         */
        retryDelay: attempt => Math.min(1000 * 2 ** attempt, 30000)
      });
    
      if (error) return <ProductErrorState />;
      if (!data) return <ProductSkeleton />;
    
      return <ProductDetails product={data} />;
    }
    

    #Resilience: Circuit Breaker Pattern

    • closed: normal operation
    • open: block request (after a failure threshold)
    • half: test a few requests
    // Using opossum circuit breaker with Next.js API route
    import CircuitBreaker from 'opossum';
    
    // pages/api/products.js
    export default async function handler(req, res) {
      const breaker = new CircuitBreaker(fetchProducts, {
        timeout: 3000,// ms
        errorThresholdPercentage: 50,
        resetTimeout: 30000// ms
      });
    
      try {
        const data = await breaker.fire();
        res.status(200).json(data);
      } catch (error) {
    // Circuit is open or operation failed
        res.status(503).json({ error: 'Service temporarily unavailable' });
      }
    }
    

    #Overwrite a property in ts

    type Modify<T, R> = Omit<T, keyof R> & R
    

    e.g. overwrite children with a more specific type

    export type Stack = Modify<Group, { children: Element[] }>
    

    #Feature Folder Pattern in nuxt3

    import { readdirSync } from 'fs'
    import { defineNuxtModule, addComponentsDir, addImportsDir, createResolver } from '@nuxt/kit'
    
    const featureFolder = 'features'
    
    export default defineNuxtModule({
      setup() {
        const resolver = createResolver(import.meta.url)
    
        readdirSync(featureFolder).forEach((feature) => {
          addComponentsDir({
            path: resolver.resolve(`../${featureFolder}/${feature}/components`),
            prefix: feature,
          })
    
          addImportsDir(resolver.resolve(`../${featureFolder}/${feature}/composables`))
    
          // we don't want feature utils to be imported in the app
          // addImportsDir(resolver.resolve(`../${featureFolder}/${feature}/utils`))
        })
      },
    })
    

    #Basic oauth flow

    define('SSO_CLIENT_ID', '');
    define('SSO_CLIENT_SECRET', '');
    define('SSO_METADATA', 'https://PROVIDER.COM/.well-known/openid-configuration');
    define('SSO_REDIRECT_URI', URL_PUBLIC . 'sso/callback');
    
    public static function action_signin() {
      AuthUser::load();
      if (AuthUser::isLoggedIn()) {
        redirect('/admin');
        return;
      }
    
      $metadata = self::http(SSO_METADATA);
    
      $_SESSION['state'] = bin2hex(random_bytes(5));
      $_SESSION['code_verifier'] = bin2hex(random_bytes(50));
      $code_challenge = self::base64_urlencode(hash('sha256', $_SESSION['code_verifier'], true));
    
      $authorize_url = $metadata->authorization_endpoint.'?'.http_build_query([
        'response_type' => 'code',
        'client_id' => SSO_CLIENT_ID,
        'redirect_uri' => SSO_REDIRECT_URI,
        'state' => $_SESSION['state'],
        'scope' => 'openid email profile',
        'code_challenge' => $code_challenge,
        'code_challenge_method' => 'S256',
      ]);
    
      redirect($authorize_url);
    }
    
    public static function action_callback() {
      if(!isset($_SESSION['state']) || !isset($_GET['state']) || $_SESSION['state'] != $_GET['state']) {
        die('Authorization server returned an invalid state parameter');
      }
    
      if(isset($_GET['error'])) {
        die('Authorization server returned an error: '.htmlspecialchars($_GET['error']));
      }
    
      $metadata = self::http(SSO_METADATA);
    
      $response = self::http($metadata->token_endpoint, [
        'grant_type' => 'authorization_code',
        'code' => $_GET['code'],
        'redirect_uri' => SSO_REDIRECT_URI,
        'client_id' => SSO_CLIENT_ID,
        'client_secret' => SSO_CLIENT_SECRET,
        'code_verifier' => $_SESSION['code_verifier'],
      ]);
    
      if (isset($response->error)) {
        die('Authorization server returned an error: '.htmlspecialchars($response->error));
      }
    
      $userinfo = self::http($metadata->userinfo_endpoint, [
        'access_token' => $response->access_token,
      ]);
    
      if (!isset($userinfo->sub)) {
        die('Error fetching access token');
      }
    
      $login = self::createOrLogin($userinfo);
      if (!$login) {
        die('Authentication request was not successful');
      }
    
      redirect('/dashboard');
    }
    

    #Feather / Fade to transparency or color in three.js

    Fade to transparency from a point (center).

    export function modAppendFade(shader, background) {
      // assumes: https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderLib/meshbasic.glsl.js
      shader.vertexShader = shader.vertexShader.replace(
        /* glsl */`void main() {`,
        /* glsl */`
          varying vec4 v_position;
          void main() {
            v_position = modelMatrix * vec4(position.xyz, 1.0);
        `
      )
      shader.fragmentShader = /* glsl */`
        varying vec4 v_position;
        ${shader.fragmentShader}
      `.replace(
        /* glsl */`#include <dithering_fragment>`,
        /* glsl */`
            #include <dithering_fragment>
    
            vec3 center = vec3(0.0, 0.0, 0.0);
            vec4 background = vec4(${background || '0.93, 0.93, 0.93, 1.0'});
            float diameter = 800.0;
            float falloff = 0.006;
    
            float vDistance = distance(v_position.xyz, center);
            float factor = clamp((vDistance - diameter) * falloff, 0.0, 1.0);
            gl_FragColor = mix(gl_FragColor, background, factor);
          `
      )
    }
    
    node.material.onBeforeCompile = shader => modAppendFade(shader)
    node.material.transparent = true
    node.material.needsUpdate = true
    

    #Easing functions

    See Easing Functions Cheat Sheet

    function easeInOutCubic(x: number): number {
      return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2;
    }
    
    function easeOutElastic(x: number): number {
      const c4 = (2 * Math.PI) / 3;
            return x === 0
            ? 0
            : x === 1
            ? 1
            : Math.pow(2, -10 * x) * Math.sin((x * 10 - 0.75) * c4) + 1;
    }
    
    function easeInCirc(x: number): number {
      return 1 - Math.sqrt(1 - Math.pow(x, 2));
    }
    

    #Three.js - Raycasting for Object Click

    const raycaster = new THREE.Raycaster();
    const mouse = new THREE.Vector2();
    
    window.addEventListener('click', (event) => {
      mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
      mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
      raycaster.setFromCamera(mouse, camera);
      const intersects = raycaster.intersectObjects(scene.children, true);
      if (intersects.length > 0) {
        console.log('Clicked:', intersects[0].object);
      }
    });
    

    #CSS Scroll Snap for Carousels

    .carousel {
      display: flex;
      overflow-x: auto;
      scroll-snap-type: x mandatory;
    }
    .carousel > * {
      scroll-snap-align: start;
      flex: 0 0 auto;
    }
    

    #Get Scrollbar Width (cross-browser)

    function getScrollbarWidth() {
      const div = document.createElement('div');
      div.style.visibility = 'hidden';
      div.style.overflow = 'scroll';
      div.style.msOverflowStyle = 'scrollbar'; // for Edge
      div.style.width = '50px';
      div.style.height = '50px';
      document.body.appendChild(div);
      const inner = document.createElement('div');
      div.appendChild(inner);
      const scrollbarWidth = div.offsetWidth - inner.offsetWidth;
      div.remove();
      return scrollbarWidth;
    }
    

    #Hide arrows and crosses in inputs with tailwind

    // tailwind 3
    // export default {
      // plugins: [
        // plugin(function ({ addUtilities }) {
    
          addUtilities({
            '.appearance-clean': {
              'appearance': 'none',
              '-moz-appearance': 'textfield',
              '&::-webkit-inner-spin-button': {
                '-webkit-appearance': 'none',
              },
              '&::-webkit-outer-spin-button': {
                '-webkit-appearance': 'none',
              },
            },
          })
    
        // }),
      // ]
    // }
    
    /* tailwind v4 */
    @layer utils {
      .appearance-clean {
        appearance: none;
        -webkit-appearance: none;
        &::-webkit-search-decoration,
        &::-webkit-search-cancel-button,
        &::-webkit-inner-spin-button,
        &::-webkit-outer-spin-button {
          -webkit-appearance: none;
        }
      }
    }
    
    <input type="number" class="appearance-clean" />
    

    #Font Loading

    font-display: auto;
    font-display: block;
    font-display: swap;
    font-display: fallback;
    font-display: optional;
    

    block-period: renders characters invisible
    swap-period: time to swap font-set for an other one

    auto
    The font display strategy is defined by the user agent.

    block
    Gives the font face a short block period and an infinite swap period.

    swap
    Gives the font face an extremely small block period and an infinite swap period.

    fallback
    Gives the font face an extremely small block period and a short swap period.

    optional
    Gives the font face an extremely small block period and no swap period.

    #iOS search input

    iOS Safari adds some ugly styling to search inputs. Fixable with:

    input {   
      appearance: none!important; 
    }
    

    important because Apple adds them with input[type=search] and therefor has a higher priority.

    #Loop continuously with swiper.js

    The swiper needs loop and speed.
    observer is necessary when the 0 index is wrong.

    <swiper
      :slides-per-view="'auto'"
      :loop="true"
      :speed="5000"
      :prevent-interaction-on-transition="true"
      :observer="true"
      :observe-parents="true"
      @swiper="onSwiper"
    >
      <!-- ... -->
    </swiper>
    

    Once swiper js is initialized it can be looped.

    onSwiper(swiper) {
      const loop = () => {
        swiper.slideTo(swiper.slides.length - swiper.loopedSlides * 2);
        swiper.once('transitionEnd', () => {
          swiper.slideTo(0, 0, false);
          setTimeout(loop, 0);
        });
      }
      loop();
    }
    

    #Copy Text

    Copy from an input

    textarea.select(); 
    document.execCommand('copy');
    

    Copy from a node

    const selection = window.getSelection();
    const range = document.createRange();
    range.selectNodeContents(ELEMENT);
    selection.removeAllRanges();
    selection.addRange(range);
    
    document.execCommand('copy');
    
    selection.removeAllRanges();
    

    or

    navigator.clipboard.writeText(CONTENT)
    

    #Get browser Theme

    const browserTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
    

    #px and pt to rem

    @function px($px) { @return round($px * 0.0625rem * 100) / 100; }
    @function pt($pt) { @return px($pt * 1.333); }
    

    #Add Authorization header to axios

    axios.interceptors.request.use(function (config) {
        const token = store.getState().session.token;
        config.headers.Authorization =  token;
        return config;
    });
    

    #Sleep Promise

    const sleep = (t: number) => new Promise(r => setTimeout(r, t));
    

    #Modulo in js

    const mod = (n, m) => ((n % m) + m) % m;
    

    as % (reminder operation) does not work as expected with negative numbers

    #Linear Map

    const map = (value, x1, y1, x2, y2) => ((value - x1) * (y2 - x2)) / (y1 - x1) + x2;
    

    #Basic Animation

    function animate(timing, draw, duration) {
      return new Promise((resolve) => {
        const start = performance.now();
        requestAnimationFrame(function _animate(time) {
          let timeFraction = (time - start) / duration;
          if (timeFraction > 1) timeFraction = 1;
    
          const progress = timing ? timing(timeFraction) : timeFraction;
          draw(progress);
    
          if (timeFraction < 1) {
            requestAnimationFrame(_animate);
          } else {
            resolve();
          }
        });
      });
    }
    

    #CSS Font import

    Basic

    @font-face {
      font-family: "Mechanical";
      src: url("mechanical.otf") format("opentype");
    }
    
    .ttf     format("truetype")
    .woff    format("woff")
    .woff2   format("woff2")
    .otf     format("opentype")
    

    Vue

    @font-face {
        font-family: Mechanical;
        src: url("~@/assets/fonts/Mechanical.otf") format("opentype");
    }
    

    #Run PHP in Docker

    Run PHP inside a Dev-Docker container

    $ docker run -it --mount src="$(pwd)",target=/var/www/html,type=bind -p 3000:80 --name phpdev --sysctl net.ipv4.ip_unprivileged_port_start=0 --rm php:7.2-apache /bin/bash -c 'a2enmod rewrite; apache2-foreground'
    

    Different user prevent the easy manipulation of files, it can be solved quite easily with

    $ git config core.fileMode false
    $ chmod -R 777 .