--- title: Math Notation from 0 to 1: A Beginner's Guide description: Learn the fundamental mathematical notations that form the building blocks of mathematical communication, from basic symbols to calculus notation. tags: ['mathematics'] ---

# Math Notation from 0 to 1: A Beginner's Guide



## TLDR

Mathematical notation is a universal language that allows precise communication of complex ideas. This guide covers the essential math symbols and conventions you need to know, from basic arithmetic operations to more advanced calculus notation. You'll learn how to read and write mathematical expressions properly, understand the order of operations, and interpret common notations for sets, functions, and sequences. By mastering these fundamentals, you'll be better equipped to understand technical documentation, academic papers, and algorithms in computer science.

## Why Math Notation Matters

Mathematical notation is like a universal language that allows precise communication of ideas. While it might seem intimidating at first, learning math notation will help you:

- Understand textbooks and online resources more easily
- Communicate mathematical ideas clearly
- Solve problems more efficiently
- Build a foundation for more advanced topics

## Basic Symbols

### Arithmetic Operations

Let's start with the four basic operations:

- Addition: $a + b$
- Subtraction: $a - b$
- Multiplication: $a \times b$ or $a \cdot b$ or simply $ab$
- Division: $a \div b$ or $\frac{a}{b}$

In more advanced mathematics, multiplication is often written without a symbol ($ab$ instead of $a \times b$) to save space and improve readability.

### Equality and Inequality

- Equal to: $a = b$
- Not equal to: $a \neq b$
- Approximately equal to: $a \approx b$
- Less than: $a < b$
- Greater than: $a > b$
- Less than or equal to: $a \leq b$
- Greater than or equal to: $a \geq b$

### Parentheses and Order of Operations

Parentheses are used to show which operations should be performed first:

$2 \times (3 + 4) = 2 \times 7 = 14$

Without parentheses, we follow the order of operations (often remembered with the acronym PEMDAS):
- **P**arentheses
- **E**xponents
- **M**ultiplication and **D**ivision (from left to right)
- **A**ddition and **S**ubtraction (from left to right)

Example: $2 \times 3 + 4 = 6 + 4 = 10$

## Exponents and Radicals

### Exponents (Powers)

Exponents indicate repeated multiplication:

$a^n = a \times a \times ... \times a$ (multiplied $n$ times)

Examples:
- $2^3 = 2 \times 2 \times 2 = 8$
- $10^2 = 10 \times 10 = 100$

### Radicals (Roots)

Radicals represent the inverse of exponents:

$\sqrt[n]{a} = a^{1/n}$

Examples:
- $\sqrt{9} = 3$ (because $3^2 = 9$)
- $\sqrt[3]{8} = 2$ (because $2^3 = 8$)

The square root ($\sqrt{}$) is the most common radical and means the same as $\sqrt[2]{}$.

## Vector Notation

Vectors are quantities that have both magnitude and direction. They are commonly represented in several ways:

### Vector Representation

- Bold letters: $\mathbf{v}$ or $\mathbf{a}$
- Arrow notation: $\vec{v}$ or $\vec{a}$
- Component form: $(v_1, v_2, v_3)$ for a 3D vector

### Vector Operations

- Vector addition: $\mathbf{a} + \mathbf{b} = (a_1 + b_1, a_2 + b_2, a_3 + b_3)$
- Vector subtraction: $\mathbf{a} - \mathbf{b} = (a_1 - b_1, a_2 - b_2, a_3 - b_3)$
- Scalar multiplication: $c\mathbf{a} = (ca_1, ca_2, ca_3)$

### Vector Products

- Dot product (scalar product): $\mathbf{a} \cdot \mathbf{b} = a_1b_1 + a_2b_2 + a_3b_3$
  - The dot product produces a scalar
  - If $\mathbf{a} \cdot \mathbf{b} = 0$, the vectors are perpendicular

- Cross product (vector product): $\mathbf{a} \times \mathbf{b} = (a_2b_3 - a_3b_2, a_3b_1 - a_1b_3, a_1b_2 - a_2b_1)$
  - The cross product produces a vector perpendicular to both $\mathbf{a}$ and $\mathbf{b}$
  - Only defined for 3D vectors

### Vector Magnitude

The magnitude or length of a vector $\mathbf{v} = (v_1, v_2, v_3)$ is:

$|\mathbf{v}| = \sqrt{v_1^2 + v_2^2 + v_3^2}$

### Unit Vectors

A unit vector has a magnitude of 1 and preserves the direction of the original vector:

$\hat{\mathbf{v}} = \frac{\mathbf{v}}{|\mathbf{v}|}$

Common unit vectors in the Cartesian coordinate system are:
- $\hat{\mathbf{i}} = (1,0,0)$ (x-direction)
- $\hat{\mathbf{j}} = (0,1,0)$ (y-direction)
- $\hat{\mathbf{k}} = (0,0,1)$ (z-direction)

Any vector can be written as: $\mathbf{v} = v_1\hat{\mathbf{i}} + v_2\hat{\mathbf{j}} + v_3\hat{\mathbf{k}}$

## Fractions and Decimals

### Fractions

A fraction represents division and consists of:
- Numerator (top number)
- Denominator (bottom number)

$\frac{a}{b}$ means $a$ divided by $b$

Examples:
- $\frac{1}{2} = 0.5$
- $\frac{3}{4} = 0.75$

### Decimals and Percentages

Decimals are another way to represent fractions:
- $0.5 = \frac{5}{10} = \frac{1}{2}$
- $0.25 = \frac{25}{100} = \frac{1}{4}$

Percentages represent parts per hundred:
- $50\% = \frac{50}{100} = 0.5$
- $25\% = \frac{25}{100} = 0.25$

## Variables and Constants

### Variables

Variables are symbols (usually letters) that represent unknown or changing values:
- $x$, $y$, and $z$ are commonly used for unknown values
- $t$ often represents time
- $n$ often represents a count or integer

### Constants

Constants are symbols that represent fixed, known values:
- $\pi$ (pi) ≈ 3.14159... (the ratio of a circle's circumference to its diameter)
- $e$ ≈ 2.71828... (the base of natural logarithms)
- $i$ = $\sqrt{-1}$ (the imaginary unit)

## Functions

A function relates an input to an output and is often written as $f(x)$, which is read as "f of x":

$f(x) = x^2$

This means that the function $f$ takes an input $x$ and returns $x^2$.

Examples:
- If $f(x) = x^2$, then $f(3) = 3^2 = 9$
- If $g(x) = 2x + 1$, then $g(4) = 2 \times 4 + 1 = 9$

## Sets and Logic

### Set Notation

Sets are collections of objects, usually written with curly braces:
- $\{1, 2, 3\}$ is the set containing the numbers 1, 2, and 3
- $\{x : x > 0\}$ is the set of all positive numbers (read as "the set of all $x$ such that $x$ is greater than 0")

### Set Operations

- Union: $A \cup B$ (elements in either $A$ or $B$ or both)
- Intersection: $A \cap B$ (elements in both $A$ and $B$)
- Element of: $a \in A$ (element $a$ belongs to set $A$)
- Not element of: $a \notin A$ (element $a$ does not belong to set $A$)
- Subset: $A \subseteq B$ ($A$ is contained within $B$)

### Logic Symbols

- And: $\land$
- Or: $\lor$
- Not: $\lnot$
- Implies: $\Rightarrow$
- If and only if: $\Leftrightarrow$

## Summation and Product Notation

### Summation (Sigma Notation)

The sigma notation represents the sum of a sequence:

$\sum_{i=1}^{n} a_i = a_1 + a_2 + \ldots + a_n$

Example:
$\sum_{i=1}^{4} i^2 = 1^2 + 2^2 + 3^2 + 4^2 = 1 + 4 + 9 + 16 = 30$

### Product (Pi Notation)

The pi notation represents the product of a sequence:

$\prod_{i=1}^{n} a_i = a_1 \times a_2 \times \ldots \times a_n$

Example:
$\prod_{i=1}^{4} i = 1 \times 2 \times 3 \times 4 = 24$

## Calculus Notation

### Limits

Limits describe the behavior of a function as its input approaches a particular value:

$\lim_{x \to a} f(x) = L$

This is read as "the limit of $f(x)$ as $x$ approaches $a$ equals $L$."

### Derivatives

Derivatives represent rates of change and can be written in several ways:

$f'(x)$ or $\frac{d}{dx}f(x)$ or $\frac{df}{dx}$

### Integrals

Integrals represent area under curves and can be definite or indefinite:

- Indefinite integral: $\int f(x) \, dx$
- Definite integral: $\int_{a}^{b} f(x) \, dx$

## Conclusion

Mathematical notation might seem like a foreign language at first, but with practice, it becomes second nature. This guide has covered the basics from 0 to 1, but there's always more to learn. As you continue your mathematical journey, you'll encounter new symbols and notations, each designed to communicate complex ideas efficiently.

Remember, mathematics is about ideas, not just symbols. The notation is simply a tool to express these ideas clearly and precisely. Practice reading and writing in this language, and soon you'll find yourself thinking in mathematical terms!

## Practice Exercises

1. Write the following in mathematical notation:
   - The sum of $x$ and $y$, divided by their product
   - The square root of the sum of $a$ squared and $b$ squared
   - The set of all even numbers between 1 and 10

2. Interpret the following notations:
   - $f(x) = |x|$
   - $\sum_{i=1}^{5} (2i - 1)$
   - $\{x \in \mathbb{R} : -1 < x < 1\}$

Happy calculating!

---

--- title: How to Implement a Cosine Similarity Function in TypeScript for Vector Comparison description: Learn how to build an efficient cosine similarity function in TypeScript for comparing vector embeddings. This step-by-step guide includes code examples, performance optimizations, and practical applications for semantic search and AI recommendation systems tags: ['typescript', 'ai', 'mathematics'] ---

# How to Implement a Cosine Similarity Function in TypeScript for Vector Comparison



To understand how an AI can understand that the word "cat" is similar to "kitten," you must realize cosine similarity. In short, with the help of embeddings, we can represent words as vectors in a high-dimensional space. If the word "cat" is represented as a vector [1, 0, 0], the word "kitten" would be represented as [1, 0, 1]. Now, we can use cosine similarity to measure the similarity between the two vectors. In this blog post, we will break down the concept of cosine similarity and implement it in TypeScript.

<Alert type="note">
I won't explain how embeddings work in this blog post, but only how to use them.
</Alert>

## What Is Cosine Similarity? A Simple Explanation

The cosine similarity formula measures how similar two vectors are by examining the angle between them, not their sizes. Here's how it works in plain English:

1. **What it does**: It tells you if two vectors point in the same direction, opposite directions, or somewhere in between.

2. **The calculation**:
   - First, multiply the corresponding elements of both vectors and add these products together (the dot product)
   - Then, calculate how long each vector is (its magnitude)
   - Finally, divide the dot product by the product of the two magnitudes

3. **The result**:
   - If you get 1, the vectors point in exactly the same direction (perfectly similar)
   - If you get 0, the vectors stand perpendicular to each other (completely unrelated)
   - If you get -1, the vectors point in exactly opposite directions (perfectly dissimilar)
   - Any value in between indicates the degree of similarity

4. **Why it's useful**: 
   - It ignores vector size and focuses only on direction
   - This means you can consider two things similar even if one is much "bigger" than the other
   - For example, a short document about cats and a long document about cats would show similarity, despite their different lengths

5. **In AI applications**:
   - We convert words, documents, images, etc. into vectors with many dimensions
   - Cosine similarity helps us find related items by measuring how closely their vectors align
   - This powers features like semantic search, recommendations, and content matching

## Why Cosine Similarity Matters for Modern Web Development

When you build applications with any of these features, you directly work with vector mathematics:

- **Semantic search**: Finding relevant content based on meaning, not just keywords
- **AI-powered recommendations**: "Users who liked this also enjoyed..."
- **Content matching**: Identifying similar articles, products, or user profiles
- **Natural language processing**: Understanding and comparing text meaning

All of these require you to compare vectors, and cosine similarity offers one of the most effective methods to do so.

## Visualizing Cosine Similarity

### Cosine Similarity Explained

Cosine similarity measures the cosine of the angle between two vectors, showing how similar they are regardless of their magnitude. The value ranges from:

- **+1**: When vectors point in the same direction (perfectly similar)
- **0**: When vectors stand perpendicular (no similarity)
- **-1**: When vectors point in opposite directions (completely dissimilar)

With the interactive visualization above, you can:
1. Move both vectors by dragging the colored circles at their endpoints
2. Observe how the angle between them changes
3. See how cosine similarity relates to this angle
4. Note that cosine similarity depends only on the angle, not the vectors' lengths

## Step-by-Step Example Calculation

Let me walk you through a manual calculation of cosine similarity between two simple vectors. This helps build intuition before we implement it in code.

Given two vectors: $\vec{v_1} = [3, 4]$ and $\vec{v_2} = [5, 2]$

I'll calculate their cosine similarity step by step:

**Step 1**: Calculate the dot product.
$$
\vec{v_1} \cdot \vec{v_2} = 3 \times 5 + 4 \times 2 = 15 + 8 = 23
$$

**Step 2**: Calculate the magnitude of each vector.

$$
||\vec{v_1}|| = \sqrt{3^2 + 4^2} = \sqrt{9 + 16} = \sqrt{25} = 5
$$

$$
||\vec{v_2}|| = \sqrt{5^2 + 2^2} = \sqrt{25 + 4} = \sqrt{29} \approx 5.385
$$

**Step 3**: Calculate the cosine similarity by dividing the dot product by the product of magnitudes.

$$
\cos(\theta) = \frac{\vec{v_1} \cdot \vec{v_2}}{||\vec{v_1}|| \cdot ||\vec{v_2}||}
$$

$$
= \frac{23}{5 \times 5.385} = \frac{23}{26.925} \approx 0.854
$$

Therefore, the cosine similarity between vectors $\vec{v_1}$ and $\vec{v_2}$ is approximately 0.854, which shows that these vectors point in roughly the same direction.

## Building a Cosine Similarity Function in TypeScript

Let's implement an optimized cosine similarity function in TypeScript that combines the functional approach with the more efficient `Math.hypot()` method:

```typescript
/**
 * Calculates the cosine similarity between two vectors
 * @param vecA First vector
 * @param vecB Second vector
 * @returns A value between -1 and 1, where 1 means identical
 */
function cosineSimilarity(vecA: number[], vecB: number[]): number {
  if (vecA.length !== vecB.length) {
    throw new Error("Vectors must have the same dimensions");
  }

  // Calculate dot product: A·B = Σ(A[i] * B[i])
  const dotProduct = vecA.reduce((sum, a, i) => sum + a * vecB[i], 0);
  
  // Calculate magnitudes using Math.hypot()
  const magnitudeA = Math.hypot(...vecA);
  const magnitudeB = Math.hypot(...vecB);
  
  // Check for zero magnitude
  if (magnitudeA === 0 || magnitudeB === 0) {
    return 0;
  }

  // Calculate cosine similarity: (A·B) / (|A|*|B|)
  return dotProduct / (magnitudeA * magnitudeB);
}
```

## Testing Our Implementation

Let's see how our function works with some example vectors:

```typescript

// Example 1: Similar vectors pointing in roughly the same direction
const vecA = [3, 4];
const vecB = [5, 2];
console.log(`Similarity: ${cosineSimilarity(vecA, vecB).toFixed(3)}`);
// Output: Similarity: 0.857

// Example 2: Perpendicular vectors
const vecC = [1, 0];
const vecD = [0, 1];
console.log(`Similarity: ${cosineSimilarity(vecC, vecD).toFixed(3)}`);
// Output: Similarity: 0.000

// Example 3: Opposite vectors
const vecE = [2, 3];
const vecF = [-2, -3];
console.log(`Similarity: ${cosineSimilarity(vecE, vecF).toFixed(3)}`);
// Output: Similarity: -1.000
```

Mathematically, we can verify these results:

For Example 1:
$$\text{cosine similarity} = \frac{3 \times 5 + 4 \times 2}{\sqrt{3^2 + 4^2} \times \sqrt{5^2 + 2^2}} = \frac{15 + 8}{\sqrt{25} \times \sqrt{29}} = \frac{23}{5 \times \sqrt{29}} \approx 0.857$$

For Example 2:
$$\text{cosine similarity} = \frac{1 \times 0 + 0 \times 1}{\sqrt{1^2 + 0^2} \times \sqrt{0^2 + 1^2}} = \frac{0}{1 \times 1} = 0$$

For Example 3:
$$\text{cosine similarity} = \frac{2 \times (-2) + 3 \times (-3)}{\sqrt{2^2 + 3^2} \times \sqrt{(-2)^2 + (-3)^2}} = \frac{-4 - 9}{\sqrt{13} \times \sqrt{13}} = \frac{-13}{13} = -1$$

## Complete TypeScript Solution

Here's a complete TypeScript solution that includes our cosine similarity function along with some utility methods:

```typescript
class VectorUtils {
  /**
   * Calculates the cosine similarity between two vectors
   */
  static cosineSimilarity(vecA: number[], vecB: number[]): number {
    if (vecA.length !== vecB.length) {
      throw new Error(`Vector dimensions don't match: ${vecA.length} vs ${vecB.length}`);
    }
    
    const dotProduct = vecA.reduce((sum, a, i) => sum + a * vecB[i], 0);
    const magnitudeA = Math.hypot(...vecA);
    const magnitudeB = Math.hypot(...vecB);
    
    if (magnitudeA === 0 || magnitudeB === 0) {
      return 0;
    }
    
    return dotProduct / (magnitudeA * magnitudeB);
  }
  
  /**
   * Calculates the dot product of two vectors
   */
  static dotProduct(vecA: number[], vecB: number[]): number {
    if (vecA.length !== vecB.length) {
      throw new Error(`Vector dimensions don't match: ${vecA.length} vs ${vecB.length}`);
    }
    
    return vecA.reduce((sum, a, i) => sum + a * vecB[i], 0);
  }
  
  /**
   * Calculates the magnitude (length) of a vector
   */
  static magnitude(vec: number[]): number {
    return Math.hypot(...vec);
  }
  
  /**
   * Normalizes a vector (converts to unit vector)
   */
  static normalize(vec: number[]): number[] {
    const mag = this.magnitude(vec);
    
    if (mag === 0) {
      return Array(vec.length).fill(0);
    }
    
    return vec.map(v => v / mag);
  }
  
  /**
   * Converts cosine similarity to angular distance in degrees
   */
  static similarityToDegrees(similarity: number): number {
    // Clamp similarity to [-1, 1] to handle floating point errors
    const clampedSimilarity = Math.max(-1, Math.min(1, similarity));
    return Math.acos(clampedSimilarity) * (180 / Math.PI);
  }
}
```

## Using Cosine Similarity in Real Web Applications

When you work with AI in web applications, you'll often need to calculate similarity between vectors. Here's a practical example:

```typescript

// Example: Semantic search implementation
function semanticSearch(queryEmbedding: number[], documentEmbeddings: DocumentWithEmbedding[]): SearchResult[] {
  return documentEmbeddings
    .map(doc => ({
      document: doc,
      relevance: VectorUtils.cosineSimilarity(queryEmbedding, doc.embedding)
    }))
    .filter(result => result.relevance > 0.7) // Only consider relevant results
    .sort((a, b) => b.relevance - a.relevance);
}
```

## Using OpenAI Embedding Models with Cosine Similarity

While the examples above used simple vectors for clarity, real-world AI applications typically use embedding models that transform text and other data into high-dimensional vector spaces.

OpenAI provides powerful embedding models that you can easily incorporate into your applications. These models transform text into vectors with hundreds or thousands of dimensions that capture semantic meaning:

```typescript

// Example of using OpenAI embeddings with our cosine similarity function
async function compareTextSimilarity(textA: string, textB: string): Promise<number> {
  // Get embeddings from OpenAI API
  const responseA = await fetch('https://api.openai.com/v1/embeddings', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: 'text-embedding-3-large',
      input: textA
    })
  });
  
  const responseB = await fetch('https://api.openai.com/v1/embeddings', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: 'text-embedding-3-large',
      input: textB
    })
  });
  
  const embeddingA = (await responseA.json()).data[0].embedding;
  const embeddingB = (await responseB.json()).data[0].embedding;
  
  // Calculate similarity using our function
  return VectorUtils.cosineSimilarity(embeddingA, embeddingB);
}
```

<Alert type="warning">
In a production environment, you should pre-compute embeddings for your content (like blog posts, products, or documents) and store them in a vector database (like Pinecone, Qdrant, or Milvus). Re-computing embeddings for every user request as shown in this example wastes resources and slows performance. A better approach: embed your content once during indexing, store the vectors, and only embed the user's query when performing a search.
</Alert>

OpenAI's latest embedding models like `text-embedding-3-large` have up to 3,072 dimensions, capturing extremely nuanced semantic relationships between words and concepts. These high-dimensional embeddings enable much more accurate similarity measurements than simpler vector representations.

For more information on OpenAI's embedding models, including best practices and implementation details, check out their documentation at [https://platform.openai.com/docs/guides/embeddings](https://platform.openai.com/docs/guides/embeddings).

## Conclusion

Understanding vectors and cosine similarity provides practical tools that empower you to work effectively with modern AI features. By implementing these concepts in TypeScript, you gain a deeper understanding and precise control over calculating similarity in your applications.
The interactive visualizations we've explored help you build intuition about these mathematical concepts, while the TypeScript implementation gives you the tools to apply them in real-world scenarios.
Whether you build recommendation systems, semantic search, or content-matching features, the foundation you've gained here will help you implement more intelligent, accurate, and effective AI-powered features in your web applications.

---

--- title: How I Added llms.txt to My Astro Blog description: I built a simple way to load my blog content into any LLM with one click. This post shows how you can do it too. tags: ['astro', 'ai'] ---

# How I Added llms.txt to My Astro Blog



## TLDR

I created an endpoint in my Astro blog that outputs all posts in plain text format. This lets me copy my entire blog with one click and paste it into any LLM with adequate context window. The setup uses TypeScript and Astro's API routes, making it work with any Astro content collection.

## Why I Built This

I wanted a quick way to ask AI models questions about my own blog content. Copying posts one by one is slow. With this solution, I can give any LLM all my blog posts at once.

## How It Works

The solution creates a special endpoint that:
1. Gets all blog posts
2. Converts them to plain text
3. Formats them with basic metadata
4. Outputs everything as one big text file

## Setting Up the File

First, I created a new TypeScript file in my Astro pages directory:

```ts

// src/pages/llms.txt.ts
// Function to extract the frontmatter as text
const extractFrontmatter = (content: string): string => {
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
  return frontmatterMatch ? frontmatterMatch[1] : '';
};

// Function to clean content while keeping frontmatter
const cleanContent = (content: string): string => {
  // Extract the frontmatter as text
  const frontmatterText = extractFrontmatter(content);
  
  // Remove the frontmatter delimiters
  let cleanedContent = content.replace(/^---\n[\s\S]*?\n---/, '');
  
  // Clean up MDX-specific imports
  cleanedContent = cleanedContent.replace(/import\s+.*\s+from\s+['"].*['"];?\s*/g, '');
  
  // Remove MDX component declarations
  cleanedContent = cleanedContent.replace(/<\w+\s+.*?\/>/g, '');
  
  // Remove Shiki Twoslash syntax like 
  cleanedContent = cleanedContent.replace(/\/\/\s*@noErrors/g, '');
  cleanedContent = cleanedContent.replace(/\/\/\s*@(.*?)$/gm, ''); // Remove other Shiki Twoslash directives
  
  // Clean up multiple newlines
  cleanedContent = cleanedContent.replace(/\n\s*\n\s*\n/g, '\n\n');
  
  // Return the frontmatter as text, followed by the cleaned content
  return frontmatterText + '\n\n' + cleanedContent.trim();
};

export const GET: APIRoute = async () => {
  try {
    // Get all blog posts sorted by date (newest first)
    const posts = await getCollection('blog', ({ data }) => !data.draft);
    const sortedPosts = posts.sort((a, b) => 
      new Date(b.data.pubDatetime).valueOf() - new Date(a.data.pubDatetime).valueOf()
    );

    // Generate the content
    let llmsContent = '';

    for (const post of sortedPosts) {
      // Add post metadata in the format similar to the example
      llmsContent += `--- title: ${post.data.title} description: ${post.data.description} tags: [${post.data.tags.map(tag => `'${tag}'`).join(', ')}] ---\n\n`;
      
      // Add the post title as a heading
      llmsContent += `# ${post.data.title}\n\n`;
      
      // Process the content, keeping frontmatter as text
      const processedContent = cleanContent(post.body);
      llmsContent += processedContent + '\n\n';
      
      // Add separator between posts
      llmsContent += '---\n\n';
    }

    // Return the response as plain text
    return new Response(llmsContent, {
      headers: { "Content-Type": "text/plain; charset=utf-8" },
    });
  } catch (error) {
    console.error('Failed to generate llms.txt:', error);
    return new Response('Error generating llms.txt', { status: 500 });
  }
};
```
This code accomplishes four key functions:

1. It uses Astro's `getCollection` function to grab all published blog posts
2. It sorts them by date with newest first
3. It cleans up each post's content with helper functions
4. It formats each post with its metadata and content
5. It returns everything as plain text

## How to Use It

Using this is simple:

1. Visit `alexop.dev/llms.txt` in your browser
2. Press Ctrl+A (or Cmd+A on Mac) to select all the text
3. Copy it (Ctrl+C or Cmd+C)
4. Paste it into any LLM with adequate context window (like ChatGPT, Claude, Llama, etc.)
5. Ask questions about your blog content

The LLM now has all your blog posts in its context window. You can ask questions such as:
- "What topics have I written about?"
- "Summarize my post about [topic]"
- "Find code examples in my posts that use [technology]"
- "What have I written about [specific topic]?"

## Benefits of This Approach

This approach offers distinct advantages:
- Works with any Astro blog
- Requires a single file to set up
- Makes your content easy to query with any LLM
- Keeps useful metadata with each post
- Formats content in a way LLMs understand well

## Conclusion

By adding one straightforward TypeScript file to your Astro blog, you can create a fast way to chat with your own content using any LLM with adequate context window. This makes it easy to:
- Find information in your old posts
- Get summaries of your content
- Find patterns across your writing
- Generate new ideas based on your past content

Give it a try! The setup takes minutes, and it makes interacting with your blog content much faster.

---

--- title: How to Do Visual Regression Testing in Vue with Vitest? description: Learn how to implement visual regression testing in Vue.js using Vitest's browser mode. This comprehensive guide covers setting up screenshot-based testing, creating component stories, and integrating with CI/CD pipelines for automated visual testing. tags: ['vue', 'testing', 'vitest'] ---

# How to Do Visual Regression Testing in Vue with Vitest?



TL;DR:
Visual regression testing detects unintended UI changes by comparing screenshots. With Vitest's experimental browser mode and Playwright, you can:

- **Run tests in a real browser environment**
- **Define component stories for different states**
- **Capture screenshots and compare them with baseline images using snapshot testing**

In this guide, you'll learn how to set up visual regression testing for Vue components using Vitest.

Our test will generate this screenshot:

<Alert type="definition"> 
Visual regression testing captures screenshots of UI components and compares them against baseline images to flag visual discrepancies. This ensures consistent styling and layout across your design system. 
</Alert>

## Vitest Configuration

Start by configuring Vitest with the Vue plugin:

```typescript

export default defineConfig({
  plugins: [vue()],
})
```

## Setting Up Browser Testing

Visual regression tests need a real browser environment. Install these dependencies:

```bash
npm install -D vitest @vitest/browser playwright
```
You can also use the following command to initialize the browser mode:

```bash
npx vitest init browser
```

First, configure Vitest to support both unit and browser tests using a workspace file, `vitest.workspace.ts`. For more details on workspace configuration, see the [Vitest Workspace Documentation](https://vitest.dev/guide/workspace.html).

<Alert type="tip" title="Pro Tip">
Using a workspace configuration allows you to maintain separate settings for unit and browser tests while sharing common configuration. This makes it easier to manage different testing environments in your project.
</Alert>

```typescript

export default defineWorkspace([
  {
    extends: './vitest.config.ts',
    test: {
      name: 'unit',
      include: ['**/*.spec.ts', '**/*.spec.tsx'],
      exclude: ['**/*.browser.spec.ts', '**/*.browser.spec.tsx'],
      environment: 'jsdom',
    },
  },
  {
    extends: './vitest.config.ts',
    test: {
      name: 'browser',
      include: ['**/*.browser.spec.ts', '**/*.browser.spec.tsx'],
      browser: {
        enabled: true,
        provider: 'playwright',
        headless: true,
        instances: [{ browser: 'chromium' }],
      },
    },
  },
])
```

Add scripts in your `package.json`

```json
{
  "scripts": {
    "test": "vitest",
    "test:unit": "vitest --project unit",
    "test:browser": "vitest --project browser"
  }
}
```

Now we can run tests in separate environments like this:

```bash
npm run test:unit
npm run test:browser
```

## The BaseButton Component

Consider the `BaseButton.vue` component a reusable button with customizable size, variant, and disabled state:

```vue

<template>
  <button
    :class="[
      'button',
      `button--${size}`,
      `button--${variant}`,
      { 'button--disabled': disabled },
    ]"
    :disabled="disabled"
    @click="$emit('click', $event)"
  >
    <slot></slot>
  </button>
</template>

<script setup lang="ts">
interface Props {
  size?: 'small' | 'medium' | 'large'
  variant?: 'primary' | 'secondary' | 'outline'
  disabled?: boolean
}

defineProps<Props>()
defineEmits<{
  (e: 'click', event: MouseEvent): void
}>()
</script>

<style scoped>
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Additional styling available in the GitHub repository */
}

/* Size, variant, and state modifiers available in the GitHub repository */
</style>
```

## Defining Stories for Testing

Create "stories" to showcase different button configurations:

```typescript

const buttonStories = [
  {
    name: 'Primary Medium',
    props: { variant: 'primary', size: 'medium' },
    slots: { default: 'Primary Button' },
  },
  {
    name: 'Secondary Medium',
    props: { variant: 'secondary', size: 'medium' },
    slots: { default: 'Secondary Button' },
  },
  // and much more ...
]
```

Each story defines a name, props, and slot content.

## Rendering Stories for Screenshots

Render all stories in one container to capture a comprehensive screenshot:

```typescript

interface Story<T> {
  name: string
  props: Record<string, any>
  slots: Record<string, string>
}

function renderStories<T>(component: Component, stories: Story<T>[]): HTMLElement {
  const container = document.createElement('div')
  container.style.display = 'flex'
  container.style.flexDirection = 'column'
  container.style.gap = '16px'
  container.style.padding = '20px'
  container.style.backgroundColor = '#ffffff'

  stories.forEach((story) => {
    const storyWrapper = document.createElement('div')
    const label = document.createElement('h3')
    label.textContent = story.name
    storyWrapper.appendChild(label)

    const { container: storyContainer } = render(component, {
      props: story.props,
      slots: story.slots,
    })
    storyWrapper.appendChild(storyContainer)
    container.appendChild(storyWrapper)
  })

  return container
}
```

## Writing the Visual Regression Test

Write a test that renders the stories and captures a screenshot:

```typescript

// [buttonStories and renderStories defined above]

describe('BaseButton', () => {
  describe('visual regression', () => {
    it('should match all button variants snapshot', async () => {
      const container = renderStories(BaseButton, buttonStories)
      document.body.appendChild(container)

      const screenshot = await page.screenshot({
        path: 'all-button-variants.png',
      })

      // this assertion is acutaly not doing anything
      // but otherwise you would get a warning about the screenshot not being taken
      expect(screenshot).toBeTruthy()

      document.body.removeChild(container)
    })
  })
})
```

Use `render` from `vitest-browser-vue` to capture components as they appear in a real browser.

<Alert type="note">
Save this file with a `.browser.spec.ts` extension (e.g., `BaseButton.browser.spec.ts`) to match your browser test configuration. 
</Alert>

## Beyond Screenshots: Automated Comparison

Automate image comparison by encoding screenshots in base64 and comparing them against baseline snapshots:

```typescript

// Helper function to take and compare screenshots
async function takeAndCompareScreenshot(name: string, element: HTMLElement) {
  const screenshotDir = './__screenshots__'
  const snapshotDir = './__snapshots__'
  const screenshotPath = `${screenshotDir}/${name}.png`

  // Append element to body
  document.body.appendChild(element)

  // Take screenshot
  const screenshot = await page.screenshot({
    path: screenshotPath,
    base64: true,
  })

  // Compare base64 snapshot
  await expect(screenshot.base64).toMatchFileSnapshot(`${snapshotDir}/${name}.snap`)

  // Save PNG for reference
  await expect(screenshot.path).toBeTruthy()

  // Cleanup
  document.body.removeChild(element)
}
```

Then update the test:

```typescript

describe('BaseButton', () => {
  describe('visual regression', () => {
    it('should match all button variants snapshot', async () => {
      const container = renderStories(BaseButton, buttonStories)
      await expect(
        takeAndCompareScreenshot('all-button-variants', container)
      ).resolves.not.toThrow()
    })
  })
})
```

<Alert type="note" title="Future improvements"> 
Vitest is discussing native screenshot comparisons in browser mode. Follow and contribute at [github.com/vitest-dev/vitest/discussions/690](https://github.com/vitest-dev/vitest/discussions/690). 
</Alert>

```mermaid
flowchart LR
    A[Render Component] --> B[Capture Screenshot]
    B --> C{Compare with Baseline}
    C -->|Match| D[Test Passes]
    C -->|Difference| E[Review Changes]
    E -->|Accept| F[Update Baseline]
    E -->|Reject| G[Fix Component]
    G --> A
```

## Conclusion

Vitest's experimental browser mode empowers developers to perform accurate visual regression testing of Vue components in real browser environments. 
While the current workflow requires manual review of screenshot comparisons, it establishes a foundation for more automated visual testing in the future.
This approach also strengthens collaboration between developers and UI designers. 
Designers can review visual changes to components before production deployment by accessing the generated screenshots in the component library.
For advanced visual testing capabilities, teams should explore dedicated tools like Playwright or Cypress that offer more features and maturity.
Keep in mind to perform visual regression tests against your Base components.

---

--- title: How to Test Vue Router Components with Testing Library and Vitest description: Learn how to test Vue Router components using Testing Library and Vitest. This guide covers real router integration, mocked router setups, and best practices for testing navigation, route guards, and dynamic components in Vue applications. tags: ['vue', 'testing', 'vue-router', 'vitest', 'testing-library'] ---

# How to Test Vue Router Components with Testing Library and Vitest



## TLDR

This guide shows you how to test Vue Router components using real router integration and isolated component testing with mocks. You'll learn to verify router-link interactions, programmatic navigation, and navigation guard handling.

## Introduction

Modern Vue applications need thorough testing to ensure reliable navigation and component performance. We'll cover testing strategies using Testing Library and Vitest to simulate real-world scenarios through router integration and component isolation.

## Vue Router Testing Techniques with Testing Library and Vitest

Let's explore how to write effective tests for Vue Router components using both real router instances and mocks.

## Testing Vue Router Navigation Components

### Navigation Component Example

```vue

<!-- NavigationMenu.vue -->
<script setup lang="ts">
const router = useRouter()
const goToProfile = () => {
  router.push('/profile')
}
</script>

<template>
  <nav>
    <router-link to="/dashboard" class="nav-link">Dashboard</router-link>
    <router-link to="/settings" class="nav-link">Settings</router-link>
    <button @click="goToProfile">Profile</button>
  </nav>
</template>
```

### Real Router Integration Testing

Test complete routing behavior with a real router instance:

```typescript

describe('NavigationMenu', () => {
  it('should navigate using router links', async () => {
    const router = createRouter({
      history: createWebHistory(),
      routes: [
        { path: '/dashboard', component: { template: 'Dashboard' } },
        { path: '/settings', component: { template: 'Settings' } },
        { path: '/profile', component: { template: 'Profile' } },
        { path: '/', component: { template: 'Home' } },
      ],
    })

    render(NavigationMenu, {
      global: {
        plugins: [router],
      },
    })

    const user = userEvent.setup()
    expect(router.currentRoute.value.path).toBe('/')

    await router.isReady()
    await user.click(screen.getByText('Dashboard'))
    expect(router.currentRoute.value.path).toBe('/dashboard')

    await user.click(screen.getByText('Profile'))
    expect(router.currentRoute.value.path).toBe('/profile')
  })
})
```

### Mocked Router Testing

Test components in isolation with router mocks:

```typescript

const mockPush = vi.fn()
vi.mock('vue-router', () => ({
  useRouter: vi.fn(),
}))

describe('NavigationMenu with mocked router', () => {
  it('should handle navigation with mocked router', async () => {
    const mockRouter = {
      push: mockPush,
      currentRoute: { value: { path: '/' } },
    } as unknown as Router

    vi.mocked(useRouter).mockImplementation(() => mockRouter)

    const user = userEvent.setup()
    render(NavigationMenu)

    await user.click(screen.getByText('Profile'))
    expect(mockPush).toHaveBeenCalledWith('/profile')
  })
})
```

### RouterLink Stub for Isolated Testing

Create a RouterLink stub to test navigation without router-link behavior:

```ts
// test-utils.ts

export const RouterLinkStub: Component = {
  name: 'RouterLinkStub',
  props: {
    to: {
      type: [String, Object],
      required: true,
    },
    tag: {
      type: String,
      default: 'a',
    },
    exact: Boolean,
    exactPath: Boolean,
    append: Boolean,
    replace: Boolean,
    activeClass: String,
    exactActiveClass: String,
    exactPathActiveClass: String,
    event: {
      type: [String, Array],
      default: 'click',
    },
  },
  setup(props) {
    const router = useRouter()
    const navigate = () => {
      router.push(props.to)
    }
    return { navigate }
  },
  render() {
    return h(
      this.tag,
      {
        onClick: () => this.navigate(),
      },
      this.$slots.default?.(),
    )
  },
}
```

Use the RouterLinkStub in tests:

```ts

const mockPush = vi.fn()
vi.mock('vue-router', () => ({
  useRouter: vi.fn(),
}))

describe('NavigationMenu with mocked router', () => {
  it('should handle navigation with mocked router', async () => {
    const mockRouter = {
      push: mockPush,
      currentRoute: { value: { path: '/' } },
    } as unknown as Router

    vi.mocked(useRouter).mockImplementation(() => mockRouter)

    const user = userEvent.setup()
    render(NavigationMenu, {
      global: {
        stubs: {
          RouterLink: RouterLinkStub,
        },
      },
    })

    await user.click(screen.getByText('Dashboard'))
    expect(mockPush).toHaveBeenCalledWith('/dashboard')
  })
})
```

### Testing Navigation Guards

Test navigation guards by rendering the component within a route context:

```vue

<script setup lang="ts">
onBeforeRouteLeave(() => {
  return window.confirm('Do you really want to leave this page?')
})
</script>

<template>
  <div>
    <h1>Route Leave Guard Demo</h1>
    <div>
      <nav>
        <router-link to="/">Home</router-link> |
        <router-link to="/about">About</router-link> |
        <router-link to="/guard-demo">Guard Demo</router-link>
      </nav>
    </div>
  </div>
</template>
```

Test the navigation guard:

```ts

const routes = [
  { path: '/', component: RouteLeaveGuardDemo },
  { path: '/about', component: { template: '<div>About</div>' } },
]

const router = createRouter({
  history: createWebHistory(),
  routes,
})

const App = { template: '<router-view />' }

describe('RouteLeaveGuardDemo', () => {
  beforeEach(async () => {
    vi.clearAllMocks()
    window.confirm = vi.fn()
    await router.push('/')
    await router.isReady()
  })

  it('should prompt when guard is triggered and user confirms', async () => {
    // Set window.confirm to simulate a user confirming the prompt
    window.confirm = vi.fn(() => true)

    // Render the component within a router context
    render(App, {
      global: {
        plugins: [router],
      },
    })

    const user = userEvent.setup()

    // Find the 'About' link and simulate a user click
    const aboutLink = screen.getByRole('link', { name: /About/i })
    await user.click(aboutLink)

    // Assert that the confirm dialog was shown with the correct message
    expect(window.confirm).toHaveBeenCalledWith('Do you really want to leave this page?')

    // Verify that the navigation was allowed and the route changed to '/about'
    expect(router.currentRoute.value.path).toBe('/about')
  })
})
```

### Reusable Router Test Helper

Create a helper function to simplify router setup:

```typescript

// test-utils.ts
// path of the definition of your routes
interface RenderWithRouterOptions extends Omit<RenderOptions<any>, 'global'> {
  initialRoute?: string
  routerOptions?: {
    routes?: typeof routes
    history?: ReturnType<typeof createWebHistory>
  }
}

export function renderWithRouter(Component: any, options: RenderWithRouterOptions = {}) {
  const { initialRoute = '/', routerOptions = {}, ...renderOptions } = options

  const router = createRouter({
    history: createWebHistory(),
    // Use provided routes or import from your router file
    routes: routerOptions.routes || routes,
  })

  router.push(initialRoute)

  return {
    // Return everything from regular render, plus the router instance
    ...render(Component, {
      global: {
        plugins: [router],
      },
      ...renderOptions,
    }),
    router,
  }
}
```

Use the helper in tests:

```typescript

describe('NavigationMenu', () => {
  it('should navigate using router links', async () => {
    const { router } = renderWithRouter(NavigationMenu, {
      initialRoute: '/',
    })

    await router.isReady()
    const user = userEvent.setup()
    
    await user.click(screen.getByText('Dashboard'))
    expect(router.currentRoute.value.path).toBe('/dashboard')
  })
})
```

### Conclusion: Best Practices for Vue Router Component Testing

When we test components that rely on the router, we need to consider whether we want to test the functionality in the most realistic use case or in isolation. In my humble opinion, the more you mock a test, the worse it will get. My personal advice would be to aim to use the real router instead of mocking it. Sometimes, there are exceptions, so keep that in mind.

Also, you can help yourself by focusing on components that don't rely on router functionality. Reserve router logic for view/page components. While keeping our components simple, we will never have the problem of mocking the router in the first place.

---

--- title: How to Use AI for Effective Diagram Creation: A Guide to ChatGPT and Mermaid description: Learn how to leverage ChatGPT and Mermaid to create effective diagrams for technical documentation and communication. tags: ['ai', 'productivity'] ---

# How to Use AI for Effective Diagram Creation: A Guide to ChatGPT and Mermaid



## TLDR
Learn how to combine ChatGPT and Mermaid to quickly create professional diagrams for technical documentation. This approach eliminates the complexity of traditional diagramming tools while maintaining high-quality output.

## Introduction
Mermaid is a markdown-like script language that generates diagrams from text descriptions. When combined with ChatGPT, it becomes a powerful tool for creating technical diagrams quickly and efficiently.

## Key Diagram Types

### Flowcharts
Perfect for visualizing processes:

```plaintext
flowchart LR
  A[Customer selects products] --> B[Customer reviews order]
  B --> C{Payment Successful?}
  C -->|Yes| D[Generate Invoice]
  D --> E[Dispatch goods]
  C -->|No| F[Redirect to Payment]
```

```mermaid
flowchart LR
  A[Customer selects products] --> B[Customer reviews order]
  B --> C{Payment Successful?}
  C -->|Yes| D[Generate Invoice]
  D --> E[Dispatch goods]
  C -->|No| F[Redirect to Payment]
```

### Sequence Diagrams
Ideal for system interactions:

```plaintext
sequenceDiagram
    participant Client
    participant Server
    Client->>Server: Request (GET /resource)
    Server-->>Client: Response (200 OK)
```

```mermaid
sequenceDiagram
    participant Client
    participant Server
    Client->>Server: Request (GET /resource)
    Server-->>Client: Response (200 OK)
```

## Using ChatGPT with Mermaid

1. Ask ChatGPT to explain your concept
2. Request a Mermaid diagram representation
3. Iterate on the diagram with follow-up questions

Example prompt: "Create a Mermaid sequence diagram showing how Nuxt.js performs server-side rendering"

```plaintext
sequenceDiagram
  participant Client as Client Browser
  participant Nuxt as Nuxt.js Server
  participant Vue as Vue.js Application
  participant API as Backend API
  
  Client->>Nuxt: Initial Request
  Nuxt->>Vue: SSR Starts
  Vue->>API: API Calls (if any)
  API-->>Vue: API Responses
  Vue->>Nuxt: Rendered HTML
  Nuxt-->>Client: HTML Content
```

```mermaid
sequenceDiagram
  participant Client as Client Browser
  participant Nuxt as Nuxt.js Server
  participant Vue as Vue.js Application
  participant API as Backend API
  
  Client->>Nuxt: Initial Request
  Nuxt->>Vue: SSR Starts
  Vue->>API: API Calls (if any)
  API-->>Vue: API Responses
  Vue->>Nuxt: Rendered HTML
  Nuxt-->>Client: HTML Content
```

## Quick Setup Guide

### Online Editor
Use [Mermaid Live Editor](https://mermaid.live/) for quick prototyping.

### VS Code Integration
1. Install "Markdown Preview Mermaid Support" extension
2. Create `.md` file with Mermaid code blocks
3. Preview with built-in markdown viewer

### Web Integration
```html
<script src="https://unpkg.com/mermaid/dist/mermaid.min.js"></script>
<script>mermaid.initialize({startOnLoad:true});</script>
<div class="mermaid">
  graph TD
  A-->B
</div>
```

## Conclusion
The combination of ChatGPT and Mermaid streamlines technical diagramming, making it accessible and efficient. Try it in your next documentation project to save time while creating professional diagrams.

---

--- title: Building a Pinia Plugin for Cross-Tab State Syncing description: Learn how to create a Pinia plugin that synchronizes state across browser tabs using the BroadcastChannel API and Vue 3's Script Setup syntax. tags: ['vue', 'pinia'] ---

# Building a Pinia Plugin for Cross-Tab State Syncing



## TLDR
Create a Pinia plugin that enables state synchronization across browser tabs using the BroadcastChannel API. The plugin allows you to mark specific stores for cross-tab syncing and handles state updates automatically with timestamp-based conflict resolution.

## Introduction
In modern web applications, users often work with multiple browser tabs open. When using Pinia for state management, we sometimes need to ensure that state changes in one tab are reflected across all open instances of our application. This post will guide you through creating a plugin that adds cross-tab state synchronization to your Pinia stores.

## Understanding Pinia Plugins
A Pinia plugin is a function that extends the functionality of Pinia stores. Plugins are powerful tools that help:
- Reduce code duplication
- Add reusable functionality across stores
- Keep store definitions clean and focused
- Implement cross-cutting concerns

## Cross-Tab Communication with BroadcastChannel
The BroadcastChannel API provides a simple way to send messages between different browser contexts (tabs, windows, or iframes) of the same origin. It's perfect for our use case of synchronizing state across tabs.

Key features of BroadcastChannel:
- Built-in browser API
- Same-origin security model
- Simple pub/sub messaging pattern
- No need for external dependencies

### How BroadcastChannel Works

The BroadcastChannel API operates on a simple principle: any browsing context (window, tab, iframe, or worker) can join a channel by creating a `BroadcastChannel` object with the same channel name. Once joined:

1. Messages are sent using the `postMessage()` method
2. Messages are received through the `onmessage` event handler
3. Contexts can leave the channel using the `close()` method

## Implementing the Plugin

### Store Configuration
To use our plugin, stores need to opt-in to state sharing through configuration:

```ts twoslash

export const useCounterStore = defineStore(
  'counter',
  () => {
    const count = ref(0)
    const doubleCount = computed(() => count.value * 2)

    function increment() {
      count.value++
    }

    return { count, doubleCount, increment }
  },
  {
    share: {
      enable: true,
      initialize: true,
    },
  },
)
```

The `share` option enables cross-tab synchronization and controls whether the store should initialize its state from other tabs.

### Plugin Registration `main.ts`
Register the plugin when creating your Pinia instance:

```ts twoslash

const pinia = createPinia()
pinia.use(PiniaSharedState)
```

### Plugin Implementation `plugin/plugin.ts`
Here's our complete plugin implementation with TypeScript support:

```ts twoslash

type Serializer<T extends StateTree> = {
  serialize: (value: T) => string
  deserialize: (value: string) => T
}

interface BroadcastMessage {
  type: 'STATE_UPDATE' | 'SYNC_REQUEST'
  timestamp?: number
  state?: string
}

type PluginOptions<T extends StateTree> = {
  enable?: boolean
  initialize?: boolean
  serializer?: Serializer<T>
}

export interface StoreOptions<S extends StateTree = StateTree, G = object, A = object>
  extends DefineStoreOptions<string, S, G, A> {
  share?: PluginOptions<S>
}

// Add type extension for Pinia
declare module 'pinia' {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  export interface DefineStoreOptionsBase<S, Store> {
    share?: PluginOptions<S>
  }
}

export function PiniaSharedState<T extends StateTree>({
  enable = false,
  initialize = false,
  serializer = {
    serialize: JSON.stringify,
    deserialize: JSON.parse,
  },
}: PluginOptions<T> = {}) {
  return ({ store, options }: PiniaPluginContext) => {
    if (!(options.share?.enable ?? enable)) return

    const channel = new BroadcastChannel(store.$id)
    let timestamp = 0
    let externalUpdate = false

    // Initial state sync
    if (options.share?.initialize ?? initialize) {
      channel.postMessage({ type: 'SYNC_REQUEST' })
    }

    // State change listener
    store.$subscribe((_mutation, state) => {
      if (externalUpdate) return

      timestamp = Date.now()
      channel.postMessage({
        type: 'STATE_UPDATE',
        timestamp,
        state: serializer.serialize(state as T),
      })
    })

    // Message handler
    channel.onmessage = (event: MessageEvent<BroadcastMessage>) => {
      const data = event.data
      if (
        data.type === 'STATE_UPDATE' &&
        data.timestamp &&
        data.timestamp > timestamp &&
        data.state
      ) {
        externalUpdate = true
        timestamp = data.timestamp
        store.$patch(serializer.deserialize(data.state))
        externalUpdate = false
      }

      if (data.type === 'SYNC_REQUEST') {
        channel.postMessage({
          type: 'STATE_UPDATE',
          timestamp,
          state: serializer.serialize(store.$state as T),
        })
      }
    }
  }
}
```

The plugin works by:
1. Creating a BroadcastChannel for each store
2. Subscribing to store changes and broadcasting updates
3. Handling incoming messages from other tabs
4. Using timestamps to prevent update cycles
5. Supporting custom serialization for complex state

### Communication Flow Diagram

```mermaid
flowchart LR
    A[User interacts with store in Tab 1] --> B[Store state changes]
    B --> C[Plugin detects change]
    C --> D[BroadcastChannel posts STATE_UPDATE]
    D --> E[Other tabs receive STATE_UPDATE]
    E --> F[Plugin patches store state in Tab 2]
```

## Using the Synchronized Store

Components can use the synchronized store just like any other Pinia store:

```ts twoslash

const counterStore = useCounterStore()
// State changes will automatically sync across tabs
counterStore.increment()
```

## Conclusion
With this Pinia plugin, we've added cross-tab state synchronization with minimal configuration. The solution is lightweight, type-safe, and leverages the built-in BroadcastChannel API. This pattern is particularly useful for applications where users frequently work across multiple tabs and need a consistent state experience.

Remember to consider the following when using this plugin:
- Only enable sharing for stores that truly need it
- Be mindful of performance with large state objects
- Consider custom serialization for complex data structures
- Test thoroughly across different browser scenarios

## Future Optimization: Web Workers

For applications with heavy cross-tab communication or complex state transformations, consider offloading the BroadcastChannel handling to a Web Worker. This approach can improve performance by:

- Moving message processing off the main thread
- Handling complex state transformations without blocking UI
- Reducing main thread load when syncing large state objects
- Buffering and batching state updates for better performance

This is particularly beneficial when:
- Your application has many tabs open simultaneously
- State updates are frequent or computationally intensive
- You need to perform validation or transformation on synced data
- The application handles large datasets that need to be synced

You can find the complete code for this plugin in the [GitHub repository](https://github.com/alexanderop/pluginPiniaTabs).
It also has examples of how to use it with Web Workers.

---

--- title: The Browser That Speaks 200 Languages: Building an AI Translator Without APIs description: Learn how to build a browser-based translator that works offline and handles 200 languages using Vue and Transformers.js tags: ['vue', 'ai'] ---

# The Browser That Speaks 200 Languages: Building an AI Translator Without APIs



## Introduction
Most AI translation tools rely on external APIs. 
This means sending data to servers and paying for each request. But what if you could run translations directly in your browser? This guide shows you how to build a free, offline translator that handles 200 languages using Vue and Transformers.js.

## The Tools

- Vue 3 for the interface
- Transformers.js to run AI models locally
- Web Workers to handle heavy processing
- NLLB-200, Meta's translation model

```mermaid
---
title: Architecture Overview
---

graph LR
    Frontend[Vue Frontend]
    Worker[Web Worker]
    TJS[Transformers.js]
    Model[NLLB-200 Model]
    
    Frontend -->|"Text"| Worker
    Worker -->|"Initialize"| TJS
    TJS -->|"Load"| Model
    Model -->|"Results"| TJS
    TJS -->|"Stream"| Worker
    Worker -->|"Translation"| Frontend

    classDef default fill:#344060,stroke:#AB4B99,color:#EAEDF3
    classDef accent fill:#8A337B,stroke:#AB4B99,color:#EAEDF3
    
    class TJS,Model accent
```

## Building the Translator

![AI Translator](../../assets/images/vue-ai-translate.png)

### 1. Set Up Your Project
Create a new Vue project with TypeScript:

```bash
npm create vite@latest vue-translator -- --template vue-ts
cd vue-translator
npm install
npm install @huggingface/transformers
```

### 2. Create the Translation Worker
The translation happens in a background process. Create `src/worker/translation.worker.ts`:

```typescript

// Singleton pattern for the translation pipeline
class MyTranslationPipeline {
  static task: PipelineType = 'translation';
  // We use the distilled model for faster loading and inference
  static model = 'Xenova/nllb-200-distilled-600M';
  static instance: TranslationPipeline | null = null;

  static async getInstance(progress_callback?: ProgressCallback) {
    if (!this.instance) {
      this.instance = await pipeline(this.task, this.model, { progress_callback }) as TranslationPipeline;
    }
    return this.instance;
  }
}

// Type definitions for worker messages
interface TranslationRequest {
  text: string;
  src_lang: string;
  tgt_lang: string;
}

// Worker message handler
self.addEventListener('message', async (event: MessageEvent<TranslationRequest>) => {
  try {
    // Initialize the translation pipeline with progress tracking
    const translator = await MyTranslationPipeline.getInstance(x => {
      self.postMessage(x);
    });

    // Configure streaming for real-time translation updates
    const streamer = new TextStreamer(translator.tokenizer, {
      skip_prompt: true,
      skip_special_tokens: true,
      callback_function: (text: string) => {
        self.postMessage({
          status: 'update',
          output: text
        });
      }
    });

    // Perform the translation
    const output = await translator(event.data.text, {
      
      tgt_lang: event.data.tgt_lang,
      src_lang: event.data.src_lang,
      streamer,
    });

    // Send the final result
    self.postMessage({
      status: 'complete',
      output,
    });
  } catch (error) {
    self.postMessage({
      status: 'error',
      error: error instanceof Error ? error.message : 'An unknown error occurred'
    });
  }
}); 
```

### 3. Build the Interface
Create a clean interface with two main components:

#### Language Selector (`src/components/LanguageSelector.vue`)

```vue

<script setup lang="ts">
// Language codes follow the ISO 639-3 standard with script codes
const LANGUAGES: Record<string, string> = {
  "English": "eng_Latn",
  "French": "fra_Latn",
  "Spanish": "spa_Latn",
  "German": "deu_Latn",
  "Chinese": "zho_Hans",
  "Japanese": "jpn_Jpan",
  // Add more languages as needed
};
// Strong typing for component props
interface Props {
  type: string;
  modelValue: string;
}

defineProps<Props>();
const emit = defineEmits<{
  (e: 'update:modelValue', value: string): void;
}>();

const onChange = (event: Event) => {
  const target = event.target as HTMLSelectElement;
  emit('update:modelValue', target.value);
};
</script>

<template>
  <div class="language-selector">
    <label>{{ type }}: </label>
    <select :value="modelValue" @change="onChange">
      <option v-for="[key, value] in Object.entries(LANGUAGES)" 
              :key="key" 
              :value="value">
        {{ key }}
      </option>
    </select>
  </div>
</template>

<style scoped>
.language-selector {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

select {
  padding: 0.5rem;
  border-radius: 4px;
  border: 1px solid rgb(var(--color-border));
  background-color: rgb(var(--color-card));
  color: rgb(var(--color-text-base));
  min-width: 200px;
}
</style>
```

#### Progress Bar (`src/components/ProgressBar.vue`)

```vue

<script setup lang="ts">

defineProps< {
  text: string;
  percentage: number;
}>();
</script>

<template>
  <div class="progress-container">
    <div class="progress-bar" :style="{ width: `${percentage}%` }">
      {{ text }} ({{ percentage.toFixed(2) }}%)
    </div>
  </div>
</template>

<style scoped>
.progress-container {
  width: 100%;
  height: 20px;
  background-color: rgb(var(--color-card));
  border-radius: 10px;
  margin: 10px 0;
  overflow: hidden;
  border: 1px solid rgb(var(--color-border));
}

.progress-bar {
  height: 100%;
  background-color: rgb(var(--color-accent));
  transition: width 0.3s ease;
  display: flex;
  align-items: center;
  padding: 0 10px;
  color: rgb(var(--color-text-base));
  font-size: 0.9rem;
  white-space: nowrap;
}

.progress-bar:hover {
  background-color: rgb(var(--color-card-muted));
}
</style> 
```

### 4. Put It All Together

In your main app file:

```vue

<script setup lang="ts">
interface ProgressItem {
  file: string;
  progress: number;
}

// State
const worker = ref<Worker | null>(null);
const ready = ref<boolean | null>(null);
const disabled = ref(false);
const progressItems = ref<Map<string, ProgressItem>>(new Map());

const input = ref('I love walking my dog.');
const sourceLanguage = ref('eng_Latn');
const targetLanguage = ref('fra_Latn');
const output = ref('');

// Computed property for progress items array
const progressItemsArray = computed(() => {
  return Array.from(progressItems.value.values());
});

// Watch progress items
watch(progressItemsArray, (newItems) => {
  console.log('Progress items updated:', newItems);
}, { deep: true });

// Translation handler
const translate = () => {
  if (!worker.value) return;
  
  disabled.value = true;
  output.value = '';
  
  worker.value.postMessage({
    text: input.value,
    src_lang: sourceLanguage.value,
    tgt_lang: targetLanguage.value,
  });
};

// Worker message handler
const onMessageReceived = (e: MessageEvent) => {
  switch (e.data.status) {
    case 'initiate':
      ready.value = false;
      progressItems.value.set(e.data.file, {
        file: e.data.file,
        progress: 0
      });
      progressItems.value = new Map(progressItems.value);
      break;
    
    case 'progress':
      if (progressItems.value.has(e.data.file)) {
        progressItems.value.set(e.data.file, {
          file: e.data.file,
          progress: e.data.progress
        });
        progressItems.value = new Map(progressItems.value);
      }
      break;
    
    case 'done':
      progressItems.value.delete(e.data.file);
      progressItems.value = new Map(progressItems.value);
      break;
    
    case 'ready':
      ready.value = true;
      break;
    
    case 'update':
      output.value += e.data.output;
      break;
    
    case 'complete':
      disabled.value = false;
      break;

    case 'error':
      console.error('Translation error:', e.data.error);
      disabled.value = false;
      break;
  }
};

// Lifecycle hooks
onMounted(() => {
  worker.value = new Worker(
    new URL('./workers/translation.worker.ts', import.meta.url),
    { type: 'module' }
  );
  worker.value.addEventListener('message', onMessageReceived);
});

onUnmounted(() => {
  worker.value?.removeEventListener('message', onMessageReceived);
  worker.value?.terminate();
});
</script>

<template>
  <div class="app">
    <h1>Transformers.js</h1>
    <h2>ML-powered multilingual translation in Vue!</h2>

    <div class="container">
      <div class="language-container">
        <LanguageSelector 
          type="Source"
          v-model="sourceLanguage"
        />
        <LanguageSelector 
          type="Target"
          v-model="targetLanguage"
        />
      </div>

      <div class="textbox-container">
        <textarea 
          v-model="input"
          rows="3"
          placeholder="Enter text to translate..."
        />
        <textarea 
          v-model="output"
          rows="3"
          readonly
          placeholder="Translation will appear here..."
        />
      </div>
    </div>

    <button 
      :disabled="disabled || ready === false"
      @click="translate"
    >
      {{ ready === false ? 'Loading...' : 'Translate' }}
    </button>

    <div class="progress-bars-container">
      <label v-if="ready === false">
        Loading models... (only run once)
      </label>
      <div 
        v-for="item in progressItemsArray"
        :key="item.file"
      >
        <ProgressBar 
          :text="item.file"
          :percentage="item.progress"
        />
      </div>
    </div>
  </div>
</template>

<style scoped>
.app {
  max-width: 800px;
  margin: 0 auto;
  padding: 2rem;
  text-align: center;
}

.container {
  margin: 2rem 0;
}

.language-container {
  display: flex;
  justify-content: center;
  gap: 2rem;
  margin-bottom: 1rem;
}

.textbox-container {
  display: flex;
  gap: 1rem;
}

textarea {
  flex: 1;
  padding: 0.5rem;
  border-radius: 4px;
  border: 1px solid rgb(var(--color-border));
  background-color: rgb(var(--color-card));
  color: rgb(var(--color-text-base));
  font-size: 1rem;
  min-height: 100px;
  resize: vertical;
}

button {
  padding: 0.5rem 2rem;
  font-size: 1.1rem;
  cursor: pointer;
  background-color: rgb(var(--color-accent));
  color: rgb(var(--color-text-base));
  border: none;
  border-radius: 4px;
  transition: background-color 0.3s;
}

button:hover:not(:disabled) {
  background-color: rgb(var(--color-card-muted));
}

button:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.progress-bars-container {
  margin-top: 2rem;
}

h1 {
  color: rgb(var(--color-text-base));
  margin-bottom: 0.5rem;
}

h2 {
  color: rgb(var(--color-card-muted));
  font-size: 1.2rem;
  font-weight: normal;
  margin-top: 0;
}
</style>
```

## Step 5: Optimizing the Build

Configure Vite to handle our Web Workers and TypeScript efficiently:

```typescript

export default defineConfig({
  plugins: [vue()],
  worker: {
    format: 'es',  // Use ES modules format for workers
    plugins: []    // No additional plugins needed for workers
  },
  optimizeDeps: {
    exclude: ['@huggingface/transformers']  // Prevent Vite from trying to bundle Transformers.js
  }
})
```

## How It Works

1. You type text and select languages
2. The text goes to a Web Worker
3. Transformers.js loads the AI model (once)
4. The model translates your text
5. You see the translation appear in real time

The translator works offline after the first run. No data leaves your browser. No API keys needed.

## Try It Yourself

Want to explore the code further? Check out the complete source code on [GitHub](https://github.com/alexanderop/vue-ai-translate-poc).

Want to learn more? Explore these resources:
- [Transformers.js docs](https://huggingface.co/docs/transformers.js)
- [NLLB-200 model details](https://huggingface.co/facebook/nllb-200-distilled-600M)
- [Web Workers guide](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API)

---

--- title: Solving Prop Drilling in Vue: Modern State Management Strategies description: Eliminate prop drilling in Vue apps using Composition API, Provide/Inject, and Pinia. Learn when to use each approach with practical examples. tags: ['vue'] ---

# Solving Prop Drilling in Vue: Modern State Management Strategies



## TL;DR: Prop Drilling Solutions at a Glance
- **Global state**: Pinia (Vue's official state management)  
- **Reusable logic**: Composables  
- **Component subtree sharing**: Provide/Inject  
- **Avoid**: Event buses for state management  

> Click the toggle button to see interactive diagram animations that demonstrate each concept.
---

## The Hidden Cost of Prop Drilling: A Real-World Scenario
Imagine building a Vue dashboard where the user's name needs to be displayed in seven nested components. Every intermediate component becomes a middleman for data it doesn't need. Imagine changing the prop name from `userName` to `displayName`. You'd have to update six components to pass along something they don't use!

**This is prop drilling** – and it creates:
- 🚨 **Brittle code** that breaks during refactors
- 🕵️ **Debugging nightmares** from unclear data flow
- 🐌 **Performance issues** from unnecessary re-renders

---

## Solution 1: Pinia for Global State Management
### When to Use: App-wide state (user data, auth state, cart items)

**Implementation**:
```javascript
// stores/user.js
export const useUserStore = defineStore('user', {
const username = ref(localStorage.getItem('username') || 'Guest');
const isLoggedIn = computed(() => username.value !== 'Guest');
  
function setUsername(newUsername) {
    username.value = newUsername;
    localStorage.setItem('username', newUsername);
}

return {
    username,
    isLoggedIn,
    setUsername
};
});
```

**Component Usage**:
```vue

<!-- DeeplyNestedComponent.vue -->
<script setup>
const user = useUserStore();
</script>

<template>
  <div class="user-info">
    Welcome, {{ user.username }}!
    <button v-if="!user.isLoggedIn" @click="user.setUsername('John')">
      Log In
    </button>
  </div>
</template>
```

✅ **Pros**  
- Centralized state with DevTools support  
- TypeScript-friendly  
- Built-in SSR support  

⚠️ **Cons**  
- Overkill for small component trees  
- Requires understanding of Flux architecture  

---

## Solution 2: Composables for Reusable Logic
### When to Use: Shared component logic (user preferences, form state)

**Implementation with TypeScript**:
```typescript

// composables/useUser.ts
const username = ref(localStorage.getItem('username') || 'Guest');

export function useUser() {
  const setUsername = (newUsername: string) => {
    username.value = newUsername;
    localStorage.setItem('username', newUsername);
  };

  return { 
    username,
    setUsername,
  };
}
```

**Component Usage**:
```vue

<!-- UserProfile.vue -->
<script setup lang="ts">
const { username, setUsername } = useUser();
</script>

<template>
  <div class="user-profile">
    <h2>Welcome, {{ username }}!</h2>
    <button @click="setUsername('John')">
      Update Username
    </button>
  </div>
</template>
```

✅ **Pros**  
- Zero-dependency solution  
- Perfect for logic reuse across components  
- Full TypeScript support  

⚠️ **Cons**  
- Shared state requires singleton pattern  
- No built-in DevTools integration  
- **SSR Memory Leaks**: State declared outside component scope persists between requests
- **Not SSR-Safe**: Using this pattern in SSR can lead to state pollution across requests

## Solution 3: Provide/Inject for Component Tree Scoping
### When to Use: Library components or feature-specific user data

**Type-Safe Implementation**:
```typescript

// utilities/user.ts
interface UserContext {
  username: Ref<string>;
  updateUsername: (name: string) => void;
}

export const UserKey = Symbol('user') as InjectionKey<UserContext>;

// ParentComponent.vue
<script setup lang="ts">
const username = ref<string>('Guest');
const updateUsername = (name: string) => {
  username.value = name;
};

provide(UserKey, { username, updateUsername });
</script>

// DeepChildComponent.vue
<script setup lang="ts">
const { username, updateUsername } = inject(UserKey, {
  username: ref('Guest'),
  updateUsername: () => console.warn('No user provider!'),
});
</script>
```

✅ **Pros**  
- Explicit component relationships  
- Perfect for component libraries  
- Type-safe with TypeScript  

⚠️ **Cons**  
- Can create implicit dependencies  
- Debugging requires tracing providers 

---

## Why Event Buses Fail for State Management

Event buses create more problems than they solve for state management:

1. **Spaghetti Data Flow**  
   Components become invisibly coupled through arbitrary events. When `ComponentA` emits `update-theme`, who's listening? Why? DevTools can't help you track the chaos.

2. **State Inconsistencies**  
   Multiple components listening to the same event often maintain duplicate state:
   ```javascript
   // Two components, two sources of truth
   eventBus.on('login', () => this.isLoggedIn = true)
   eventBus.on('login', () => this.userStatus = 'active')
   ```

3. **Memory Leaks**  
   Forgotten event listeners in unmounted components keep reacting to events, causing bugs and performance issues.

**Where Event Buses Actually Work**
- ✅ Global notifications (toasts, alerts)
- ✅ Analytics tracking
- ✅ Decoupled plugin events

**Instead of Event Buses**: Use Pinia for state, composables for logic, and provide/inject for component trees.

```mermaid
---
title: "Decision Guide: Choosing Your Weapon"
---
graph TD
    A[Need Shared State?] -->|No| B[Props/Events]
    A -->|Yes| C{Scope?}
    C -->|App-wide| D[Pinia]
    C -->|Component Tree| E[Provide/Inject]
    C -->|Reusable Logic| F[Composables]
```

## Pro Tips for State Management Success
1. **Start Simple**: Begin with props, graduate to composables  
2. **Type Everything**: Use TypeScript for stores/injections  
3. **Name Wisely**: Prefix stores (`useUserStore`) and injection keys (`UserKey`)  
4. **Monitor Performance**: Use Vue DevTools to track reactivity  
5. **Test State**: Write unit tests for Pinia stores/composables  

By mastering these patterns, you'll write Vue apps that scale gracefully while keeping component relationships clear and maintainable.

---

--- title: Building Local-First Apps with Vue and Dexie.js description: Learn how to create offline-capable, local-first applications using Vue 3 and Dexie.js. Discover patterns for data persistence, synchronization, and optimal user experience. tags: ['vue', 'dexie', 'indexeddb', 'local-first'] ---

# Building Local-First Apps with Vue and Dexie.js



Ever been frustrated when your web app stops working because the internet connection dropped? That's where local-first applications come in! In this guide, we'll explore how to build robust, offline-capable apps using Vue 3 and Dexie.js. If you're new to local-first development, check out my [comprehensive introduction to local-first web development](https://alexop.dev/posts/what-is-local-first-web-development/) first.

## What Makes an App "Local-First"?

Martin Kleppmann defines local-first software as systems where "the availability of another computer should never prevent you from working." Think Notion's desktop app or Figma's offline mode - they store data locally first and seamlessly sync when online.

Three key principles:
1. Works without internet connection
2. Users stay productive when servers are down
3. Data syncs smoothly when connectivity returns

## The Architecture Behind Local-First Apps

```mermaid
---
title: Local-First Architecture with Central Server
---
flowchart LR
    subgraph Client1["Client Device"]
        UI1["UI"] --> DB1["Local Data"]
    end

    subgraph Client2["Client Device"]
        UI2["UI"] --> DB2["Local Data"]
    end

    subgraph Server["Central Server"]
        SDB["Server Data"]
        Sync["Sync Service"]
    end

    DB1 <--> Sync
    DB2 <--> Sync
    Sync <--> SDB
```

Key decisions:
- How much data to store locally (full vs. partial dataset)
- How to handle multi-user conflict resolution

## Enter Dexie.js: Your Local-First Swiss Army Knife

Dexie.js provides a robust offline-first architecture where database operations run against local IndexedDB first, ensuring responsiveness without internet connection.

```mermaid
---
title: Dexie.js Local-First Implementation
---
flowchart LR
    subgraph Client["Client"]
        App["Application"]
        Dexie["Dexie.js"]
        IDB["IndexedDB"]
        
        App --> Dexie
        Dexie --> IDB
        
        subgraph DexieSync["Dexie Sync"]
            Rev["Revision Tracking"]
            Queue["Sync Queue"]
            Rev --> Queue
        end
    end

    subgraph Cloud["Dexie Cloud"]
        Auth["Auth Service"]
        Store["Data Store"]
        Repl["Replication Log"]
        
        Auth --> Store
        Store --> Repl
    end

    Dexie <--> Rev
    Queue <--> Auth
    IDB -.-> Queue
    Queue -.-> Store
```

### Sync Strategies

1. **WebSocket Sync**: Real-time updates for collaborative apps
2. **HTTP Long-Polling**: Default sync mechanism, firewall-friendly
3. **Service Worker Sync**: Optional background syncing when configured

## Setting Up Dexie Cloud

To enable multi-device synchronization and real-time collaboration, we'll use Dexie Cloud. Here's how to set it up:

1. **Create a Dexie Cloud Account**:
   - Visit [https://dexie.org/cloud/](https://dexie.org/cloud/)
   - Sign up for a free developer account
   - Create a new database from the dashboard

2. **Install Required Packages**:
   ```bash
   npm install dexie-cloud-addon
   ```

3. **Configure Environment Variables**:
   Create a `.env` file in your project root:
   ```env
   VITE_DEXIE_CLOUD_URL=https://db.dexie.cloud/db/<your-db-id>
   ```
   Replace `<your-db-id>` with the database ID from your Dexie Cloud dashboard.

4. **Enable Authentication**:
   Dexie Cloud provides built-in authentication. You can:
   - Use email/password authentication
   - Integrate with OAuth providers
   - Create custom authentication flows

The free tier includes:
- Up to 50MB of data per database
- Up to 1,000 sync operations per day
- Basic authentication and access control
- Real-time sync between devices

## Building a Todo App

Let's implement a practical example with a todo app:

```mermaid
flowchart TD
    subgraph VueApp["Vue Application"]
        App["App.vue"]
        TodoList["TodoList.vue<br>Component"]
        UseTodo["useTodo.ts<br>Composable"]
        Database["database.ts<br>Dexie Configuration"]
        
        App --> TodoList
        TodoList --> UseTodo
        UseTodo --> Database
    end
    
    subgraph DexieLayer["Dexie.js Layer"]
        IndexedDB["IndexedDB"]
        SyncEngine["Dexie Sync Engine"]
        
        Database --> IndexedDB
        Database --> SyncEngine
    end
    
    subgraph Backend["Backend Services"]
        Server["Server"]
        ServerDB["Server Database"]
        
        SyncEngine <-.-> Server
        Server <-.-> ServerDB
    end
```

## Setting Up the Database

```typescript

export interface Todo {
  id?: string
  title: string
  completed: boolean
  createdAt: Date
}

export class TodoDB extends Dexie {
  todos!: Table<Todo>

  constructor() {
    super('TodoDB', { addons: [dexieCloud] })
    
    this.version(1).stores({
      todos: '@id, title, completed, createdAt',
    })
  }

  async configureSync(databaseUrl: string) {
    await this.cloud.configure({
      databaseUrl,
      requireAuth: true,
      tryUseServiceWorker: true,
    })
  }
}

export const db = new TodoDB()

if (!import.meta.env.VITE_DEXIE_CLOUD_URL) {
  throw new Error('VITE_DEXIE_CLOUD_URL environment variable is not defined')
}

db.configureSync(import.meta.env.VITE_DEXIE_CLOUD_URL).catch(console.error)

export const currentUser = db.cloud.currentUser
export const login = () => db.cloud.login()
export const logout = () => db.cloud.logout()
```

## Creating the Todo Composable

```typescript

export function useTodos() {
  const newTodoTitle = ref('')
  const error = ref<string | null>(null)

  const todos = useObservable<Todo[]>(
    from(liveQuery(() => db.todos.orderBy('createdAt').toArray())),
  )

  const completedTodos = computed(() =>
    todos.value?.filter(todo => todo.completed) ?? [],
  )

  const pendingTodos = computed(() =>
    todos.value?.filter(todo => !todo.completed) ?? [],
  )

  const addTodo = async () => {
    try {
      if (!newTodoTitle.value.trim())
        return

      await db.todos.add({
        title: newTodoTitle.value,
        completed: false,
        createdAt: new Date(),
      })

      newTodoTitle.value = ''
      error.value = null
    }
    catch (err) {
      error.value = 'Failed to add todo'
      console.error(err)
    }
  }

  const toggleTodo = async (todo: Todo) => {
    try {
      await db.todos.update(todo.id!, {
        completed: !todo.completed,
      })
      error.value = null
    }
    catch (err) {
      error.value = 'Failed to toggle todo'
      console.error(err)
    }
  }

  const deleteTodo = async (id: string) => {
    try {
      await db.todos.delete(id)
      error.value = null
    }
    catch (err) {
      error.value = 'Failed to delete todo'
      console.error(err)
    }
  }

  return {
    todos,
    newTodoTitle,
    error,
    completedTodos,
    pendingTodos,
    addTodo,
    toggleTodo,
    deleteTodo,
  }
}
```

## Authentication Guard Component

```vue
<script setup lang="ts">

const user = useObservable(currentUser)
const isAuthenticated = computed(() => !!user.value)
const isLoading = ref(false)

async function handleLogin() {
  isLoading.value = true
  try {
    await login()
  }
  finally {
    isLoading.value = false
  }
}
</script>

<template>
  <div v-if="!isAuthenticated" class="flex flex-col items-center justify-center min-h-screen p-4 bg-background">
    <Card class="max-w-md w-full">
      <!-- Login form content -->
    </Card>
  </div>
  <template v-else>
    <div class="sticky top-0 z-20 bg-card border-b">
      <!-- User info and logout button -->
    </div>
    
  </template>
</template>
```

## Better Architecture: Repository Pattern

```typescript

export interface TodoRepository {
  getAll(): Promise<Todo[]>
  add(todo: Omit<Todo, 'id'>): Promise<string>
  update(id: string, todo: Partial<Todo>): Promise<void>
  delete(id: string): Promise<void>
  observe(): Observable<Todo[]>
}

export class DexieTodoRepository implements TodoRepository {
  constructor(private db: TodoDB) {}

  async getAll() {
    return this.db.todos.toArray()
  }

  observe() {
    return from(liveQuery(() => this.db.todos.orderBy('createdAt').toArray()))
  }

  async add(todo: Omit<Todo, 'id'>) {
    return this.db.todos.add(todo)
  }

  async update(id: string, todo: Partial<Todo>) {
    await this.db.todos.update(id, todo)
  }

  async delete(id: string) {
    await this.db.todos.delete(id)
  }
}

export function useTodos(repository: TodoRepository) {
  const newTodoTitle = ref('')
  const error = ref<string | null>(null)
  const todos = useObservable<Todo[]>(repository.observe())

  const addTodo = async () => {
    try {
      if (!newTodoTitle.value.trim()) return
      await repository.add({
        title: newTodoTitle.value,
        completed: false,
        createdAt: new Date(),
      })
      newTodoTitle.value = ''
      error.value = null
    }
    catch (err) {
      error.value = 'Failed to add todo'
      console.error(err)
    }
  }

  return {
    todos,
    newTodoTitle,
    error,
    addTodo,
    // ... other methods
  }
}
```

## Understanding the IndexedDB Structure

When you inspect your application in the browser's DevTools under the "Application" tab > "IndexedDB", you'll see a database named "TodoDB-zy02f1..." with several object stores:

### Internal Dexie Stores (Prefixed with $)
> Note: These stores are only created when using Dexie Cloud for sync functionality.

- **$baseRevs**: Keeps track of base revisions for synchronization
- **$jobs**: Manages background synchronization tasks
- **$logins**: Stores authentication data including your last login timestamp
- **$members_mutations**: Tracks changes to member data for sync
- **$realms_mutations**: Tracks changes to realm/workspace data
- **$roles_mutations**: Tracks changes to role assignments
- **$syncState**: Maintains the current synchronization state
- **$todos_mutations**: Records all changes made to todos for sync and conflict resolution

### Application Data Stores
- **members**: Contains user membership data with compound indexes:
  - `[userId+realmId]`: For quick user-realm lookups
  - `[email+realmId]`: For email-based queries
  - `realmId`: For realm-specific queries
- **realms**: Stores available workspaces
- **roles**: Manages user role assignments
- **todos**: Your actual todo items containing:
  - Title
  - Completed status
  - Creation timestamp

Here's how a todo item actually looks in IndexedDB:

```json
{
  "id": "tds0PI7ogcJqpZ1JCly0qyAheHmcom",
  "title": "test",
  "completed": false,
  "createdAt": "Tue Jan 21 2025 08:40:59 GMT+0100 (Central Europe)",
  "owner": "opalic.alexander@gmail.com",
  "realmId": "opalic.alexander@gmail.com"
}
```

Each todo gets a unique `id` generated by Dexie, and when using Dexie Cloud, additional fields like `owner` and `realmId` are automatically added for multi-user support.

Each store in IndexedDB acts like a table in a traditional database, but is optimized for client-side storage and offline operations. The `$`-prefixed stores are managed automatically by Dexie.js to handle:

1. **Offline Persistence**: Your todos are stored locally
2. **Multi-User Support**: User data in `members` and `roles`
3. **Sync Management**: All `*_mutations` stores track changes
4. **Authentication**: Login state in `$logins`

## Understanding Dexie's Merge Conflict Resolution

```mermaid
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#344360', 'primaryBorderColor': '#ab4b99', 'primaryTextColor': '#eaedf3', 'lineColor': '#ab4b99', 'textColor': '#eaedf3' }}}%%
flowchart LR
    A[Detect Change Conflict] --> B{Different Fields?}
    B -->|Yes| C[Auto-Merge Changes]
    B -->|No| D{Same Field Conflict}
    D --> E[Apply Server Version<br>Last-Write-Wins]
    F[Delete Operation] --> G[Always Takes Priority<br>Over Updates]
```

Dexie's conflict resolution system is sophisticated and field-aware, meaning:

- Changes to different fields of the same record can be merged automatically
- Conflicts in the same field use last-write-wins with server priority
- Deletions always take precedence over updates to prevent "zombie" records

This approach ensures smooth collaboration while maintaining data consistency across devices and users.

## Conclusion

This guide demonstrated building local-first applications with Dexie.js and Vue. For simpler applications like todo lists or note-taking apps, Dexie.js provides an excellent balance of features and simplicity. For more complex needs similar to Linear, consider building a custom sync engine.

Find the complete example code on [GitHub](https://github.com/alexanderop/vue-dexie).

---

--- title: Unlocking Reading Insights: A Guide to Data Analysis with Claude and Readwise description: Discover how to transform your reading data into actionable insights by combining Readwise exports with Claude AI's powerful analysis capabilities tags: ['ai', 'productivity', 'reading'] ---

# Unlocking Reading Insights: A Guide to Data Analysis with Claude and Readwise



Recently, I've been exploring Claude.ai's new CSV analysis feature, which allows you to upload spreadsheet data for automated analysis and visualization. In this blog post, I'll demonstrate how to leverage Claude.ai's capabilities using Readwise data as an example. We'll explore how crafting better prompts can help you extract more meaningful insights from your data. Additionally, we'll peek under the hood to understand the technical aspects of how Claude processes and analyzes this information.

Readwise is a powerful application that syncs and organizes highlights from your Kindle and other reading platforms. While this tutorial uses Readwise data as an example, the techniques demonstrated here can be applied to analyze any CSV dataset with Claude.

## The Process: From Highlights to Insights

### 1. Export and Initial Setup
First things first: export your Readwise highlights as CSV. 
Just login into your Readwise account and go to -> https://readwise.io/export
Scroll down to the bottom and click on "Export to CSV"
![Readwise Export CSV](../../assets/images/readwise_claude_csv/readwise_export_csv.png)

### 2. Upload the CSV into Claude
Drop that CSV into Claude's interface. Yes, it's that simple. No need for complex APIs or coding knowledge.

> Note: The CSV file must fit within Claude's conversation context window. For very large export files, you may need to split them into smaller chunks.

### 3. Use Prompts to analyze the data

#### a) First Approach
First we will use a generic Prompt to see what would happen if we don't even know what to analyze for:

```plaintext
Please Claude, analyze this data for me.
```

<AstroGif 
  src="/images/readwise_claude_csv/claude_first_prompt.gif"
  alt="Claude first prompt response"
  caption="Claude analyzing the initial prompt and providing a structured response"
/>

Claude analyzed my Readwise data and provided a high-level overview:
- Collection stats: 1,322 highlights across 131 books by 126 authors from 2018-2024
- Most highlighted books focused on writing and note-taking, with "How to Take Smart Notes" leading at 102 highlights
- Tag analysis showed "discard" as most common (177), followed by color tags and topical tags like "mental" and "tech"

Claude also offered to dive deeper into highlight lengths, reading patterns over time, tag relationships, and data visualization.
Even with this basic prompt, Claude provides valuable insights and analysis. The initial overview can spark ideas for deeper investigation and more targeted analysis. However, we can craft more specific prompts to extract even more meaningful insights from our data.

### 4. Visualization and Analysis
While our last Prompt did give use some insights, it was not very useful for me.
Also I am a visual person, so I want to see some visualizations.

This is why I created this Prompt to get better Visualization I also added the Colors
from this blog since I love them.

```plaintext
Create a responsive data visualization dashboard for my Readwise highlights using React and Recharts.

Theme Colors (Dark Mode):
- Background: rgb(33, 39, 55)
- Text: rgb(234, 237, 243)
- Accent: rgb(255, 107, 237)
- Card Background: rgb(52, 63, 96)
- Muted Elements: rgb(138, 51, 123)
- Borders: rgb(171, 75, 153)

Color Application:
- Use background color for main dashboard
- Apply text color for all typography
- Use accent color for interactive elements and highlights
- Apply card background for visualization containers
- Use muted colors for secondary information
- Implement borders for section separation

Input Data Structure:
- CSV format with columns:
  - Highlight text
  - Book Title 
  - Book Author
  - Color
  - Tags
  - Location
  - Highlighted Date

Required Visualizations:
1. Reading Analytics:
   - Average reading time per book (calculated from highlight timestamps)
   - Reading patterns by time of day (heatmap using card background and accent colors)
   - Heat map showing active reading days
     - Base: rgb(52, 63, 96)
     - Intensity levels: rgb(138, 51, 123) → rgb(255, 107, 237)

2. Content Analysis:
   - Vertical bar chart: Top 10 most highlighted books
   - Bars: gradient from rgb(138, 51, 123) to rgb(255, 107, 237)
   - Labels: rgb(234, 237, 243)
   - Grid lines: rgba(171, 75, 153, 0.2)

3. Timeline View:
   - Monthly highlighting activity 
   - Line color: rgb(255, 107, 237)
   - Area fill: rgba(255, 107, 237, 0.1)
   - Grid: rgba(171, 75, 153, 0.15)

4. Knowledge Map:
   - Interactive mind map using force-directed graph
   - Node colors: rgb(52, 63, 96)
   - Node borders: rgb(171, 75, 153)
   - Connections: rgba(255, 107, 237, 0.6)
   - Hover state: rgb(255, 107, 237)

5. Summary Statistics Card:
   - Background: rgb(52, 63, 96)
   - Border: rgb(171, 75, 153)
   - Headings: rgb(234, 237, 243)
   - Values: rgb(255, 107, 237)

Design Requirements:
- Typography:
  - Primary font: Light text on dark background
  - Base text: rgb(234, 237, 243)
  - Minimum 16px for body text
  - Headings: rgb(255, 107, 237)

- Card Design:
  - Background: rgb(52, 63, 96)
  - Border: 1px solid rgb(171, 75, 153)
  - Border radius: 8px
  - Box shadow: 0 4px 6px rgba(0, 0, 0, 0.1)

- Interaction States:
  - Hover: Accent color rgb(255, 107, 237)
  - Active: rgb(138, 51, 123)
  - Focus: 2px solid rgb(255, 107, 237)

- Responsive Design:
  - Desktop: Grid layout with 2-3 columns
  - Tablet: 2 columns
  - Mobile: Single column, stacked
  - Gap: 1.5rem
  - Padding: 2rem

Accessibility:
- Ensure contrast ratio ≥ 4.5:1 with text color
- Use rgba(234, 237, 243, 0.7) for secondary text
- Provide focus indicators using accent color
- Include aria-labels for interactive elements
- Support keyboard navigation

Performance:
- Implement CSS variables for theme colors
- Use CSS transitions for hover states
- Optimize SVG rendering for mind map
- Implement virtualization for large datasets
```

<AstroGif 
  src="/images/readwise_claude_csv/readwise_analytics.gif"
  alt="Claude second prompt response"
  caption="Interactive dashboard visualization of Readwise highlights analysis"
/>

The interactive dashboard generated by Claude demonstrates the powerful synergy between generative AI and data analysis.
By combining Claude's natural language processing capabilities with programmatic visualization, we can transform raw reading data into actionable insights. This approach allows us to extract meaningful patterns and trends that would be difficult to identify through manual analysis alone.

Now I want to give you some tips on how to get the best out of claude.

## Writing Effective Analysis Prompts

Here are key principles for crafting prompts that generate meaningful insights:

### 1. Start with Clear Objectives
Instead of vague requests, specify what you want to learn:

```plaintext
Analyze my reading data to identify:
1. Time-of-day reading patterns
2. Most engaged topics
3. Knowledge connection opportunities
4. Potential learning gaps
```

### 2. Use Role-Based Prompting
Give Claude a specific expert perspective:

```plaintext
Act as a learning science researcher analyzing my reading patterns.
Focus on:
- Comprehension patterns
- Knowledge retention indicators
- Learning efficiency metrics
```

### 3. Request Specific Visualizations
Be explicit about the visual insights you need:

```plaintext
Create visualizations showing:
1. Daily reading heatmap
2. Topic relationship network
3. Highlight frequency trends
Use theme-consistent colors for clarity
```

## Bonus: Behind the Scenes - How the Analysis Tool Works

For those curious about the technical implementation, let's peek under the hood at how Claude uses the analysis tool to process your Readwise data:

### The JavaScript Runtime Environment

When you upload your Readwise CSV, Claude has access to a JavaScript runtime environment similar to a browser's console. This environment comes pre-loaded with several powerful libraries:

```javascript
// Available libraries
// For CSV processing
// For data manipulation
// For UI components
// For visualizations
```

### Data Processing Pipeline

The analysis happens in two main stages:

1. **Initial Data Processing:**
```javascript
async function analyzeReadingData() {
    // Read the CSV file
    const fileContent = await window.fs.readFile('readwisedata.csv', { 
        encoding: 'utf8' 
    });
    
    // Parse CSV using Papaparse
    const parsedData = Papa.parse(fileContent, {
        header: true,
        skipEmptyLines: true,
        dynamicTyping: true
    });

    // Analyze time patterns
    const timeAnalysis = parsedData.data.map(row => {
        const date = new Date(row['Highlighted at']);
        return {
            hour: date.getHours(),
            title: row['Book Title'],
            tags: row['Tags']
        };
    });

    // Group and count data using lodash
    const hourlyDistribution = _.countBy(timeAnalysis, 'hour');
    console.log('Reading time distribution:', hourlyDistribution);
}
```

2. **Visualization Component:**
```javascript
const ReadingPatterns = () => {
    const [timeData, setTimeData] = useState([]);
    const [topBooks, setTopBooks] = useState([]);

    useEffect(() => {
        const analyzeData = async () => {
            const response = await window.fs.readFile('readwisedata.csv', {
                encoding: 'utf8'
            });
            
            // Process time data for visualization
            const timeAnalysis = parsedData.data.reduce((acc, row) => {
                const hour = new Date(row['Highlighted at']).getHours();
                acc[hour] = (acc[hour] || 0) + 1;
                return acc;
            }, {});

            // Format data for charts
            const timeDataForChart = Object.entries(timeAnalysis)
                .map(([hour, count]) => ({
                    hour: `${hour}:00`,
                    count
                }));

            setTimeData(timeDataForChart);
        };
        analyzeData();
    }, []);

    return (
        <div className="w-full space-y-8 p-4">
            <ResponsiveContainer width="100%" height="100%">
                <BarChart data={timeData}>

                </BarChart>
            </ResponsiveContainer>
        </div>
    );
};
```

### Key Technical Features

1. **Asynchronous File Handling**: The `window.fs.readFile` API provides async file access, similar to Node.js's fs/promises.

2. **Data Processing Libraries**: 
   - Papaparse handles CSV parsing with options for headers and type conversion
   - Lodash provides efficient data manipulation functions
   - React and Recharts enable interactive visualizations

3. **React Integration**: 
   - Components use hooks for state management
   - Tailwind classes for styling
   - Responsive container adapts to screen size

4. **Error Handling**: The code includes proper error boundaries and async/await patterns to handle potential issues gracefully.

This technical implementation allows Claude to process your reading data efficiently while providing interactive visualizations that help you understand your reading patterns better.

## Conclusion
I hope this blog post demonstrates how AI can accelerate data analysis workflows. What previously required significant time and technical expertise can now be accomplished in minutes. This democratization of data analysis empowers people without coding backgrounds to gain valuable insights from their own data.

---

--- title: The What Why and How of Goal Settings description: A deep dive into the philosophy of goal-setting and personal development, exploring the balance between happiness and meaning while providing practical steps for achieving your goals in 2025. tags: ['personal-development', 'productivity'] ---

# The What Why and How of Goal Settings



There is beauty in having goals and in aiming to achieve them. This idea is perfectly captured by Jim Rohn's quote:
> "Become a millionaire not for the million dollars, but for what it will make of you to achieve it."

This wisdom suggests that humans need goals to reach them and grow and improve through the journey. Yet, this perspective isn't without its critics. Take, for instance, this provocative quote from Fight Club:
> "SELF-IMPROVEMENT IS MASTURBATION, NOW SELF-DESTRUCTION..." - TYLER DURDEN

This counter-view raises an interesting point: focusing too much on self-improvement can become narcissistic and isolating. Rather than connecting with others or making real change, someone might become trapped in an endless cycle of self-focus, similar to the character's own psychological struggles.

Despite these conflicting viewpoints, I find the pursuit of self-improvement invigorating, probably because I grew up watching anime. I have always loved the classic story arc, in which the hero faces a devastating loss, then trains and comes back stronger than before. This narrative speaks to something fundamental about human potential and resilience.

But let's dig deeper into the practical side of goal-setting. If you align more with Jim Rohn's philosophy of continuous improvement, you might wonder how to reach your goals. However, I've found that what's harder than the "how" is actually the "what" and "why." Why do you even want to reach goals? This question becomes especially relevant in our modern Western society, where many people seem settled for working their 9-5, doing the bare minimum, then watching Netflix. Maybe they have a girlfriend or boyfriend, and their only adventure is visiting other countries. Or they just enjoy living in the moment. Or they have a kid, and that child becomes the whole meaning of life.

These are all valid ways to live, but they raise an interesting question about happiness versus meaning. This reminds me of a profound conversation from the series "Heroes":

Mr. Linderman: "You see, I think there comes a time when a man has to ask himself whether he wants a life of happiness or a life of meaning."

Nathan Petrelli: "I'd like to think I have both."

Mr. Linderman: "Can't be done. Two very different paths. I mean, to be truly happy, a man must live absolutely in the present. And with no thought of what's gone before, and no thought of what lies ahead. But, a life of meaning... A man is condemned to wallow in the past and obsess about the future. And my guess is that you've done quite a bit of obsessing about yours these last few days."

This dialogue highlights a fundamental dilemma in goal-setting. If your sole aim is happiness, perhaps the wisest path would be to retreat to Tibet and meditate all day, truly living in the now. But for many of us, pursuing meaning through goals provides its own form of fulfillment.

Before setting any goals, you need to honestly assess what you want. Sometimes, your goal is maintaining what you already have - a good job, house, spouse, and kids. However, this brings up another trap I've encountered personally. I used to think that once I had everything I wanted, I could stop trying, assuming things would stay the same. This is often a fundamental mistake. Even maintaining the status quo requires continuous work and attention.

Once you understand your "why," you can formulate specific goals. You need to develop a clear vision of how you want your life to look in the coming years. Let's use weight loss as an example since it's familiar and easily quantifiable.

Consider this vision: "I want to be healthy and look good by the end of the year. I want to be more self-confident."

Now, let's examine how not to structure your goal. Many people simply say, "My goal is to lose weight." With such a vague objective, you might join the gym in January and countless others. Still, when life throws curveballs your way - illness, work stress, or missed training sessions - your commitment quickly fades because there's no clear target to maintain your focus.

A better approach would be setting a specific goal like "I want to weigh x KG by y date." This brings clarity and measurability to your objective. However, even this improved goal isn't enough on its own. You must build a system - an environment that naturally nudges you toward your goals. As James Clear, author of Atomic Habits, brilliantly puts it:

> "You do not rise to the level of your goals. You fall to the level of your systems."

This insight from one of the most influential books on habit formation reminds us that motivation alone is unreliable. Instead, you need to create sustainable habits that align with your goals. For a weight loss goal of 10kg by May, these habits might include:

- weighing yourself daily
- tracking calories
- walking 10k steps
- going to the gym 3 times per week

Another powerful insight from James Clear concerns the language we use with ourselves. For instance, if you're trying to quit smoking and someone offers you a cigarette, don't say you're trying to stop or that you're an ex-smoker. Instead, firmly state, "I don't smoke," from day one. This simple shift in language helps reprogram your identity - you're not just trying to become a non-smoker, you already are one. Fake it till you make it.

While habit-tracking apps can be helpful tools when starting out, remember to be gentle with yourself. If you miss a day, don't let it unravel your entire journey. This leads to the most important advice: don't do it alone. Despite what some YouTube gurus might suggest about "monk mode" and isolation, finding a community of like-minded individuals can be crucial for success. Share your journey, find accountability partners, and don't hesitate to work out with others.

To summarize the path to reaching your goals:

## Why
Be honest with yourself. Think about your life. Are you happy with it? What kind of meaning do you want to create?

## What
If you're content with your life, what aspects need maintenance? If not, what specific changes would create the life you envision? Think carefully about which goals would elevate your life's quality and meaning.

## How
Once you've identified a meaningful goal that resonates deeply with your values, the implementation becomes clearer:

1. Write down the goal in specific, measurable terms
2. Set a realistic timeline for accomplishment
3. Study and adopt the habits of those who've already achieved similar goals
4. Track your progress consistently
5. Build a supportive community of like-minded people
6. Distance yourself from influences that don't align with your new direction (you know who they are)

Remember, the journey toward your goals is as important as reaching them. Through this process, you'll discover not just what you can achieve but who you can become.

---

--- title: XML-Style Tagged Prompts: A Framework for Reliable AI Responses description: Learn how top AI engineers use XML-style prompts to consistently get structured, accurate responses from ChatGPT, Claude, and other LLMs. Step-by-step guide with real examples tags: ['ai', 'llm', 'prompt-engineering'] ---

# XML-Style Tagged Prompts: A Framework for Reliable AI Responses



## Why Traditional AI Communication Falls Short

Getting consistent, well-structured responses can be challenging when working with LLMs. Traditional prompting often leads to unpredictable results, making relying on AI assistance for complex tasks difficult.
While exploring prompt engineering techniques this year, I discovered an interesting approach: Tagged Prompts. This method has improved my interactions with AI, delivering more thoughtful responses.

## What Are Tagged Prompts? A Simple Guide

Tagged prompts borrow from XML's structured approach to data organization. By wrapping different parts of our prompts in descriptive tags, we create a clear framework for AI responses. Think of it as creating a mental model that guides the AI's thinking process.

### The XML Connection

To understand tagged prompts, let's first look at XML's simple yet powerful structure:

```xml
<book>
  <title>The Great Gatsby</title>
  <author>F. Scott Fitzgerald</author>
</book>
```

This familiar structure provides the foundation for our prompt engineering approach.

## How Tagged Prompts Work: XML-Style Structure

Let's compare how an AI responds to the same question with and without tagged prompts:

### Standard Response
<ChatUI
  question="I want to learn software engineering. What should I do?"
  answer={`I'll help you create a structured path to learn software engineering. Here's a comprehensive approach:

First, start with programming fundamentals:
1. Learn a beginner-friendly programming language like Python or JavaScript
2. Master basic concepts like variables, data types, control flow, functions, and object-oriented programming
3. Practice with small projects to reinforce your learning

Once you have the basics, focus on:

- **Version Control**: Learn Git and GitHub to manage code and collaborate with others
- **Data Structures & Algorithms**: Understanding these is crucial for writing efficient code and passing technical interviews
- **Development Tools**: Get comfortable with an IDE (like VS Code), command line, and debugging tools
- **Web Development Fundamentals**: HTML, CSS, and JavaScript if you're interested in web development
- **Testing**: Learn about unit tests, integration tests, and test-driven development
- **Databases**: Start with SQL basics, then learn about database design

Practical steps you can take:

1. Choose learning resources:
   - Free platforms like freeCodeCamp, The Odin Project, or CS50x
   - Paid platforms like Udemy, Coursera, or boot camps
   - Technical books and documentation

2. Build projects:
   - Start with small, achievable projects
   - Gradually increase complexity
   - Create a portfolio to showcase your work

3. Join the community:
   - Participate in coding forums like Stack Overflow
   - Join local coding meetups
   - Contribute to open source projects

Would you like me to suggest some specific resources for any of these areas? Or would you prefer to focus on a particular aspect of software engineering?`}
/>

## Before vs After: Impact of Tagged Prompts on AI Responses

## Step-by-Step Guide to Implementing Tagged Prompts

Tagged prompts can be implemented in two ways:
1. As a system prompt for ongoing AI interactions
2. As part of individual conversation messages

<CaptionedImage 
  src={systemPromptClaude} 
  alt="Claude System Prompt" 
  caption="Example of how a system prompt with instructions on Claude's project looks like."
/>

### Basic Implementation

Here's a simple but effective system prompt structure:

<SystemPromptUI 
  prompt={`When answering queries:
1. Think through your reasoning in <thinking> tags
2. Provide your answer in <output> tags 
3. If you spot an error, explain and correct it in <reflection> tags`}
/>

### Tagged Response Example

<ChatUI
  question="I want to learn software engineering. What should I do?"
  answer={`<thinking>
Let me break this down systematically:
1. Software engineering is a broad field - need to cover both fundamentals and practical skills
2. Should suggest a structured learning path that's manageable
3. Important to emphasize both theoretical knowledge and hands-on practice
4. Need to include guidance on building a portfolio and joining the community
5. Should consider both formal and self-learning options
</thinking>
<output>
Here's a comprehensive path to get started with software engineering:
Start with Programming Fundamentals:
Begin with Python or JavaScript - they're beginner-friendly and widely used. Focus on basics like variables, control flow, functions, and data structures. Platforms like freeCodeCamp or The Odin Project offer free structured courses.
Build Computer Science Foundation:
Learn core concepts like:

Data structures (arrays, linked lists, trees)
Basic algorithms and their complexity
How computers work at a basic level
Version control with Git

Practice Project-Based Learning:

Start with small projects that teach fundamentals
Gradually increase complexity as you learn
Build projects that solve real problems you encounter
Document your code and maintain a GitHub portfolio

Join the Community:

Participate in coding communities on Discord or Reddit
Contribute to open source projects
Attend local tech meetups or virtual events
Find a mentor or study group

Recommended Learning Path:

Month 1-2: Programming basics and Git
Month 3-4: Web development fundamentals (HTML, CSS, JavaScript)
Month 5-6: Choose a specialization (backend, frontend, mobile, etc.)
Month 7-8: Build portfolio projects
Month 9+: Advanced topics and job preparation

Resources to Consider:

CS50 from Harvard (free on edX) for computer science basics
freeCodeCamp for web development
"Clean Code" by Robert Martin for best practices
LeetCode for coding practice

Would you like me to elaborate on any of these areas or suggest resources for a specific aspect you're most interested in?
</output>
<reflection>
After reviewing my response, I note that while comprehensive, I could have:
1. Emphasized the importance of learning testing early in the process
2. Mentioned the value of understanding databases
3. Included information about different learning styles and approaches
However, these can be addressed in follow-up discussion based on the human's interests and needs.
</reflection>`}
/>

## Advanced Techniques: Taking Tagged Prompts Further

For more sophisticated applications, we can add quality metrics and step tracking:

<SystemPromptUI 
  prompt={`Use <thinking> tags to explore different approaches and viewpoints  
Use <step> tags to break down the solution (20 step budget, request more if needed)  
Add <count> tags after each step to track remaining budget  
Use <reflection> tags to evaluate progress and be self-critical  
Rate quality with <reward> tags (0.0-1.0):

- Greater than or equal to 0.8: Continue approach
- Between 0.5 and 0.7: Minor adjustments
- Less than 0.5: Try new approach

Show all work and calculations explicitly  
Explore multiple solutions when possible  
Summarize final answer in <answer> tags  
End with final reflection and reward score  
Adjust strategy based on reward scores and intermediate results`}
/>

## Tagged Prompts in Production: v0 by Vercel Case Study

Vercel's AI assistant v0 demonstrates how tagged prompts work in production. Their implementation, revealed through a [leaked prompt on Reddit](https://www.reddit.com/r/LocalLLaMA/comments/1gwwyia/leaked_system_prompts_from_v0_vercels_ai/), shows the power of structured prompts in professional tools.

## Essential Resources for Mastering Tagged Prompts

For deeper exploration of tagged prompts and related concepts:
- [Claude Documentation on Structured Outputs](https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/use-xml-tags)
- [Prompt Engineering Guide](https://www.promptingguide.ai/)

## Key Takeaways: Getting Started with Tagged Prompts

This was just a quick overview to explain the basic idea of tagged prompts.
I would suggest trying out this technique for your specific use case.
Compare responses with tags and without tags to see the difference.

---

--- title: How to Use the Variant Props Pattern in Vue description: Learn how to create type-safe Vue components where prop types depend on other props using TypeScript discriminated unions. A practical guide with real-world examples. tags: ['vue', 'typescript'] ---

# How to Use the Variant Props Pattern in Vue



Building Vue components that handle multiple variations while maintaining type safety can be tricky. Let's dive into the Variant Props Pattern (VPP) - a powerful approach that uses TypeScript's discriminated unions with Vue's composition API to create truly type-safe component variants.

## TL;DR

The Variant Props Pattern in Vue combines TypeScript's discriminated unions with Vue's prop system to create type-safe component variants. Instead of using complex type utilities, we explicitly mark incompatible props as never to prevent prop mixing at compile time:

```typescript
// Define base props
type BaseProps = {
  title: string;
}

// Success variant prevents error props
type SuccessProps = BaseProps & {
  variant: 'success';
  message: string;
  errorCode?: never;  // Prevents mixing
}

// Error variant prevents success props
type ErrorProps = BaseProps & {
  variant: 'error';
  errorCode: string;
  message?: never;  // Prevents mixing
}

type Props = SuccessProps | ErrorProps;
```

This pattern provides compile-time safety, excellent IDE support, and reliable vue-tsc compatibility. Perfect for components that need multiple, mutually exclusive prop combinations.

## The Problem: Mixed Props Nightmare

Picture this: You're building a notification component that needs to handle both success and error states. Each state has its own specific properties:

- Success notifications need a `message` and `duration`
- Error notifications need an `errorCode` and a `retryable` flag

Without proper type safety, developers might accidentally mix these props:

```html
<!-- This should fail! -->
<NotificationAlert
  variant="primary"
  title="Data Saved"
  message="Success!"
  errorCode="UPLOAD_001"  <!-- 🚨 Mixing success and error props -->
  :duration="5000"
  @close="handleClose"
/>
```

## The Simple Solution That Doesn't Work

Your first instinct might be to define separate interfaces:

```typescript
interface SuccessProps {
  title: string;
  variant: 'primary' | 'secondary';
  message: string;
  duration: number;
}

interface ErrorProps {
  title: string;
  variant: 'danger' | 'warning';
  errorCode: string;
  retryable: boolean;
}

// 🚨 This allows mixing both types!
type Props = SuccessProps & ErrorProps;
```

The problem? This approach allows developers to use both success and error props simultaneously - definitely not what we want!

## Using Discriminated Unions with `never`

> **TypeScript Tip**: The `never` type is a special type in TypeScript that represents values that never occur. When a property is marked as `never`, TypeScript ensures that value can never be assigned to that property. This makes it perfect for creating mutually exclusive props, as it prevents developers from accidentally using props that shouldn't exist together.
>
> The `never` type commonly appears in TypeScript in several scenarios:
> - Functions that never return (throw errors or have infinite loops)
> - Exhaustive type checking in switch statements
> - Impossible type intersections (e.g., `string & number`)
> - Making properties mutually exclusive, as we do in this pattern

The main trick to make it work with the current implmenation of defineProps is to use `never` to explicitly mark unused variant props.
```typescript

// Base props shared between variants
type BaseProps = {
  title: string;
}

// Success variant
type SuccessProps = BaseProps & {
  variant: 'primary' | 'secondary';
  message: string;
  duration: number;
  // Explicitly mark error props as never
  errorCode?: never;
  retryable?: never;
}

// Error variant
type ErrorProps = BaseProps & {
  variant: 'danger' | 'warning';
  errorCode: string;
  retryable: boolean;
  // Explicitly mark success props as never
  message?: never;
  duration?: never;
}

// Final props type - only one variant allowed!
type Props = SuccessProps | ErrorProps;
```

## Important Note About Vue Components

When implementing this pattern, you'll need to make your component generic due to a current type restriction in `defineComponent`. 
By making the component generic, we can bypass `defineComponent` and define the component as a functional component:

```vue
<script setup lang="ts" generic="T">
// Now our discriminated union props will work correctly
type BaseProps = {
  title: string;
}

type SuccessProps = BaseProps & {
  variant: 'primary' | 'secondary';
  message: string;
  duration: number;
  errorCode?: never;
  retryable?: never;
}

// ... rest of the types
</script>
```

This approach allows TypeScript to properly enforce our prop variants at compile time.

## Putting It All Together

Here's our complete notification component using the Variant Props Pattern:

<VuePlayground
  url="https://play.vuejs.org/#eNqlWN1u2zYUfhXOAWYHiGXFabpUc7KlaYptGNqi6S6GuhhoibLZSKJAUnay1M+xmz3A3mNvsifZ4Y8oyrKyDm2LIOI5/Hj++bEPg8uyDNYVGUSDmYg5LSXKcLE8nw+kmA+QILIqL+YFzUvGJXpAnKRoi1LOcjSEbcNvnewVkzSlMZaUFZcZgRWjFUw6EnWg2jkvYlYIicSKbW6qOCZC+LroXB03krwih6Dd6F5zzvgjmrXuChdJRt4Sye9BY3SIzi/Qw7xASIlZRoKMLUdDLafFErGScA0XBMFQ4Ww7WFcZE8Sa2obs8SFY46wioJriTJB+TO1SF7Hj6V682cSkDhIFH5LkZYYlgS+EZgldozjDQkBK4VSJaUH4fKCFIF5NL1qBvL7DsJuI2QQkRsdqdrJo1hFaj2kK4D0BmA+cHuYUFxJUS05zzO8bkaQyIyB4gSVGN3hNkkaWAyBeKumvrOIohqAtiYDYrQlaEFIgofSRMEenVZZ5wFFSmZTC9tMwDBvJ97EKOyx3E1srTf5/ADr52ud+ojyAHOx6/0uZMZygl5hmfgCIwrxiiVZ58/Pryxe/heGx5yNXBYwXGkO1gOekFjkndaU/HgLtQE8A/FJaVFJCDbtCArFZUqg0vu0vCKhdY6TbidANqKI6+jXexAA+fsC+UbAf3ni2H3w2Ad9Mv9jfZhOvjeBTyPuMIBHDjEhgJXCtZHq1xEkCIyRCU05y6EgoW3w33tBEriL0NAzLO7vIl7SIUIhwJZnqXgW+mhoQIx0vGFiWOyitEth4txQlK70DEyrA3vsIpRkxpy0xyI89FBvAtsVhcAoqtR5CC8YTwsccJ7QSSjxVck8EmOUdgvlJE3RApuQsDY0Ux7dLzqoiidBmRaWaTjBqKy4Y7CkZLSThbVOiFVvXIfS3H6RnKU5jowzTTQX/YnAEdxLEPaXL4KNgBVxZeqOaa3kJPcNfl6oCoCgjA6lkOMvY5ie9puriqF6PVyS+3bP+UdyptfngDSeC8DVUkpNJiDuRRnx984rcwe9OmLOkykD7EeFbAlGrzFhQas/BWTDb09PW/qgvVEjOO3F9J0khaqd0YYPmVuvPB3CNXj3iemPuSfBE74N4QhT33sc9BGBJoMZpDJ8ZWwhIoEcK5H1J0HMsyBvOSnUd6pP1NIuQkBw8cBfeZFL3Nyr8bi1hK1hACfS9xrNaLUg7OiM0tDfHEH1CQ0GgGBL1ZXrL3BPeydATdvpHqKjyha4+b55+B8sE6k+vuiHqrTrTze38uOFap8dsM/G11RvMCzDP2OxMaVntTImg44Cm4MJ3sGV17eA+o1/SAmfaToG0hWPEiuwefpDaNKTrjSRfoX/+/Mv6UbvQJPZrNGpl5ZPnq0+3zFHnKCEpTEYtnumfF6OGv5GcSqdzDR9ipoM1Am+H2vPhYYTWjCbaQb2s7ylveWsAtZeXMEjHK5oQlEKKLAtopQoSRFM00tYFdcAOLcci8h3NCavkyGNekBgwbFSfqw3ZHhn/GoR6lIERP5AMigGlVRHr4pAMHIRmycHHdtmYe0TfonVEoEf9jrxSMsUEXfnoGL4f2u/hB48ibqiMV07VOgVDF3LXNEvkLzZdY5d1vVW8AJEJninNWt+WbgujruEOgq5ns1/Nms+gphbWsgo/VDBojDCqpe/3RcrmtY5AnSX7/cFQmfqa9xmMf9ZYXeekgFnZsI3VycXDg0XTMw1tt8CKT5yG06y9qomg2WPbtUVEZmWDaeUaVU3Tmpi0YvQZ57gZsnOSc1QrgIdaw0wyM3KcKQ5ix5iGczUrju6arW5WNbTST6gWjw1IW8OxONNqtvN9Hc8MhAx1bezqkMPduDWUzvHXHV/cq0h1ecfGHfvsKKjl7ty//6iP8wz6XBLZmgxtVtZPxxwb26GLbkfJBDWXHidwNl0bItblhx8rAeff18UPd1CJY4gEkRt4V2kVnNFlMQYul8PZauNYAKuQ++lly58a1filtoKy07SjJrAjapcBwuZMkcYDEqdJetpHPo/DxbOzLqgbcb2wjl7uhX32BJ8szhys7o7AzMF+RJJO02kvS06fwJ8dRDtF+yHTdEEWfZDp6TMSKql5Qpz4LwP1wIC/htgbcp5CNsaC/g5tfxy4UtGrG0KXK8g+PFNquHIHrW25niU2rwogxTnNoLByVjBdQp3nAF8u8Cg8QvZfEJ6aS9V7hOhXhl/ej7xDXCnTIoMbdrzIWHzrbPRnTve9FAbfOJwveQMVQKM6fh5A3UzTp+bhY9L4ny+hlr29D6Lp6dMTXQxmjz+zutqS4wISwaH99tvcqgbnmo7lylaDais/Qj0uIPhfMxxTCcmAIO61z/fJKduudW+77b8vT+qu"
/>

## Conclusion
The Variant Props Pattern (VPP) provides a robust approach for building type-safe Vue components. While the Vue team is working on improving native support for discriminated unions [in vuejs/core#8952](https://github.com/vuejs/core/issues/8952), this pattern offers a practical solution today:

Unfortunately, what currently is not working is using helper utility types like Xor so that we don't have to 
manually mark unused variant props as never. When you do that, you will get an error from vue-tsc.

Example of a helper type like Xor:

```typescript
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
type XOR<T, U> = T | U extends object
  ? (Without<T, U> & U) | (Without<U, T> & T)
  : T | U;

// Success notification properties
type SuccessProps = {
  title: string;
  variant: 'primary' | 'secondary';
  message: string;
  duration: number;
};

// Error notification properties
type ErrorProps = {
  title: string;
  variant: 'danger' | 'warning';
  errorCode: string;
  retryable: boolean;
};

// Final props type - only one variant allowed! ✨
type Props = XOR<SuccessProps, ErrorProps>;
```

## Video Reference
If you also prefer to learn this in video format, check out this tutorial:

---

--- title: SQLite in Vue: Complete Guide to Building Offline-First Web Apps description: Learn how to build offline-capable Vue 3 apps using SQLite and WebAssembly in 2024. Step-by-step tutorial includes code examples for database operations, query playground implementation, and best practices for offline-first applications. tags: ['vue', 'local-first'] ---

# SQLite in Vue: Complete Guide to Building Offline-First Web Apps



## TLDR
- Set up SQLite WASM in a Vue 3 application for offline data storage
- Learn how to use Origin Private File System (OPFS) for persistent storage
- Build a SQLite query playground with Vue composables
- Implement production-ready offline-first architecture
- Compare SQLite vs IndexedDB for web applications

Looking to add offline capabilities to your Vue application? While browsers offer IndexedDB, SQLite provides a more powerful solution for complex data operations. This comprehensive guide shows you how to integrate SQLite with Vue using WebAssembly for robust offline-first applications.

## 📚 What We'll Build
- A Vue 3 app with SQLite that works offline
- A simple query playground to test SQLite
- Everything runs in the browser - no server needed!

![Screenshot Sqlite Playground](../../assets/images/sqlite-vue/sqlite-playground.png)
*Try it out: Write and run SQL queries right in your browser*

> 🚀 **Want the code?** Get the complete example at [github.com/alexanderop/sqlite-vue-example](https://github.com/alexanderop/sqlite-vue-example)

## 🗃️ Why SQLite?

Browser storage like IndexedDB is okay, but SQLite is better because:
- It's a real SQL database in your browser
- Your data stays safe even when offline
- You can use normal SQL queries
- It handles complex data relationships well

## 🛠️ How It Works

We'll use three main technologies:

1. **SQLite Wasm**: SQLite converted to run in browsers
2. **Web Workers**: Runs database code without freezing your app
3. **Origin Private File System**: A secure place to store your database

Here's how they work together:
<ExcalidrawSVG 
  src={myDiagram} 
  alt="How SQLite works in the browser"
  caption="How SQLite runs in your browser" 
/>

## 📝 Implementation Guide

Let's build this step by step, starting with the core SQLite functionality and then creating a playground to test it.

### Step 1: Install Dependencies

First, install the required SQLite WASM package:

```bash
npm install @sqlite.org/sqlite-wasm
```

### Step 2: Configure Vite

Create or update your `vite.config.ts` file to support WebAssembly and cross-origin isolation:

```ts

export default defineConfig(() => ({
  server: {
    headers: {
      'Cross-Origin-Opener-Policy': 'same-origin',
      'Cross-Origin-Embedder-Policy': 'require-corp',
    },
  },
  optimizeDeps: {
    exclude: ['@sqlite.org/sqlite-wasm'],
  },
}))
```

This configuration is crucial for SQLite WASM to work properly:

- **Cross-Origin Headers**: 
  - `Cross-Origin-Opener-Policy` and `Cross-Origin-Embedder-Policy` headers enable "cross-origin isolation"
  - This is required for using SharedArrayBuffer, which SQLite WASM needs for optimal performance
  - Without these headers, the WebAssembly implementation might fail or perform poorly

- **Dependency Optimization**:
  - `optimizeDeps.exclude` tells Vite not to pre-bundle the SQLite WASM package
  - This is necessary because the WASM files need to be loaded dynamically at runtime
  - Pre-bundling would break the WASM initialization process

### Step 3: Add TypeScript Types

Since `@sqlite.org/sqlite-wasm` doesn't include TypeScript types for Sqlite3Worker1PromiserConfig, we need to create our own. Create a new file `types/sqlite-wasm.d.ts`:

Define this as a d.ts file so that TypeScript knows about it.
```ts

declare module '@sqlite.org/sqlite-wasm' {
  type OnreadyFunction = () => void

  type Sqlite3Worker1PromiserConfig = {
    onready?: OnreadyFunction
    worker?: Worker | (() => Worker)
    generateMessageId?: (messageObject: unknown) => string
    debug?: (...args: any[]) => void
    onunhandled?: (event: MessageEvent) => void
  }

  type DbId = string | undefined

  type PromiserMethods = {
    'config-get': {
      args: Record<string, never>
      result: {
        dbID: DbId
        version: {
          libVersion: string
          sourceId: string
          libVersionNumber: number
          downloadVersion: number
        }
        bigIntEnabled: boolean
        opfsEnabled: boolean
        vfsList: string[]
      }
    }
    'open': {
      args: Partial<{
        filename?: string
        vfs?: string
      }>
      result: {
        dbId: DbId
        filename: string
        persistent: boolean
        vfs: string
      }
    }
    'exec': {
      args: {
        sql: string
        dbId?: DbId
        bind?: unknown[]
        returnValue?: string
      }
      result: {
        dbId: DbId
        sql: string
        bind: unknown[]
        returnValue: string
        resultRows?: unknown[][]
      }
    }
  }

  type PromiserResponseSuccess<T extends keyof PromiserMethods> = {
    type: T
    result: PromiserMethods[T]['result']
    messageId: string
    dbId: DbId
    workerReceivedTime: number
    workerRespondTime: number
    departureTime: number
  }

  type PromiserResponseError = {
    type: 'error'
    result: {
      operation: string
      message: string
      errorClass: string
      input: object
      stack: unknown[]
    }
    messageId: string
    dbId: DbId
  }

  type PromiserResponse<T extends keyof PromiserMethods> =
    | PromiserResponseSuccess<T>
    | PromiserResponseError

  type Promiser = <T extends keyof PromiserMethods>(
    messageType: T,
    messageArguments: PromiserMethods[T]['args'],
  ) => Promise<PromiserResponse<T>>

  export function sqlite3Worker1Promiser(
    config?: Sqlite3Worker1PromiserConfig | OnreadyFunction,
  ): Promiser
}
```

### Step 4: Create the SQLite Composable

The core of our implementation is the `useSQLite` composable. This will handle all database operations:

```ts

const databaseConfig = {
  filename: 'file:mydb.sqlite3?vfs=opfs',
  tables: {
    test: {
      name: 'test_table',
      schema: `
        CREATE TABLE IF NOT EXISTS test_table (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          name TEXT NOT NULL,
          created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        );
      `,
    },
  },
} as const

export function useSQLite() {
  const isLoading = ref(false)
  const error = ref<Error | null>(null)
  const isInitialized = ref(false)

  let promiser: ReturnType<typeof sqlite3Worker1Promiser> | null = null
  let dbId: string | null = null

  async function initialize() {
    if (isInitialized.value) return true

    isLoading.value = true
    error.value = null

    try {
      // Initialize the SQLite worker
      promiser = await new Promise((resolve) => {
        const _promiser = sqlite3Worker1Promiser({
          onready: () => resolve(_promiser),
        })
      })

      if (!promiser) throw new Error('Failed to initialize promiser')

      // Get configuration and open database
      await promiser('config-get', {})
      const openResponse = await promiser('open', {
        filename: databaseConfig.filename,
      })

      if (openResponse.type === 'error') {
        throw new Error(openResponse.result.message)
      }

      dbId = openResponse.result.dbId as string

      // Create initial tables
      await promiser('exec', {
        dbId,
        sql: databaseConfig.tables.test.schema,
      })

      isInitialized.value = true
      return true
    }
    catch (err) {
      error.value = err instanceof Error ? err : new Error('Unknown error')
      throw error.value
    }
    finally {
      isLoading.value = false
    }
  }

  async function executeQuery(sql: string, params: unknown[] = []) {
    if (!dbId || !promiser) {
      await initialize()
    }

    isLoading.value = true
    error.value = null

    try {
      const result = await promiser!('exec', {
        dbId: dbId as DbId,
        sql,
        bind: params,
        returnValue: 'resultRows',
      })

      if (result.type === 'error') {
        throw new Error(result.result.message)
      }

      return result
    }
    catch (err) {
      error.value = err instanceof Error ? err : new Error('Query execution failed')
      throw error.value
    }
    finally {
      isLoading.value = false
    }
  }

  return {
    isLoading,
    error,
    isInitialized,
    executeQuery,
  }
}
```

### Step 5: Create a SQLite Playground Component

Now let's create a component to test our SQLite implementation:

```vue

<script setup lang="ts">
const { isLoading, error, executeQuery } = useSQLite()
const sqlQuery = ref('SELECT * FROM test_table')
const queryResult = ref<any[]>([])
const queryError = ref<string | null>(null)

// Predefined example queries for testing
const exampleQueries = [
  { title: 'Select all', query: 'SELECT * FROM test_table' },
  { title: 'Insert', query: "INSERT INTO test_table (name) VALUES ('New Test Item')" },
  { title: 'Update', query: "UPDATE test_table SET name = 'Updated Item' WHERE name LIKE 'New%'" },
  { title: 'Delete', query: "DELETE FROM test_table WHERE name = 'Updated Item'" },
]

async function runQuery() {
  queryError.value = null
  queryResult.value = []

  try {
    const result = await executeQuery(sqlQuery.value)
    const isSelect = sqlQuery.value.trim().toLowerCase().startsWith('select')

    if (isSelect) {
      queryResult.value = result?.result.resultRows || []
    }
    else {
      // After mutation, fetch updated data
      queryResult.value = (await executeQuery('SELECT * FROM test_table'))?.result.resultRows || []
    }
  }
  catch (err) {
    queryError.value = err instanceof Error ? err.message : 'An error occurred'
  }
}
</script>

<template>
  <div class="max-w-7xl mx-auto px-4 py-6">
    <h2 class="text-2xl font-bold">SQLite Playground</h2>

    <!-- Example queries -->
    <div class="mt-4">
      <h3 class="text-sm font-medium">Example Queries:</h3>
      <div class="flex gap-2 mt-2">
        <button
          v-for="example in exampleQueries"
          :key="example.title"
          class="px-3 py-1 text-sm rounded-full bg-gray-100 hover:bg-gray-200"
          @click="sqlQuery = example.query"
        >
          {{ example.title }}
        </button>
      </div>
    </div>

    <!-- Query input -->
    <div class="mt-6">
      <textarea
        v-model="sqlQuery"
        rows="4"
        class="w-full px-4 py-3 rounded-lg font-mono text-sm"
        :disabled="isLoading"
      />
      <button
        :disabled="isLoading"
        class="mt-2 px-4 py-2 rounded-lg bg-blue-600 text-white"
        @click="runQuery"
      >
        {{ isLoading ? 'Running...' : 'Run Query' }}
      </button>
    </div>

    <!-- Error display -->
    <div
      v-if="error || queryError"
      class="mt-4 p-4 rounded-lg bg-red-50 text-red-600"
    >
      {{ error?.message || queryError }}
    </div>

    <!-- Results table -->
    <div v-if="queryResult.length" class="mt-4">
      <h3 class="text-lg font-semibold">Results:</h3>
      <div class="mt-2 overflow-x-auto">
        <table class="w-full">
          <thead>
            <tr>
              <th
                v-for="column in Object.keys(queryResult[0])"
                :key="column"
                class="px-4 py-2 text-left"
              >
                {{ column }}
              </th>
            </tr>
          </thead>
          <tbody>
            <tr
              v-for="(row, index) in queryResult"
              :key="index"
            >
              <td
                v-for="column in Object.keys(row)"
                :key="column"
                class="px-4 py-2"
              >
                {{ row[column] }}
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>
</template>
```

## 🎯 Real-World Example: Notion's SQLite Implementation

[Notion recently shared](https://www.notion.com/blog/how-we-sped-up-notion-in-the-browser-with-wasm-sqlite) how they implemented SQLite in their web application, providing some valuable insights:

### Performance Improvements
- 20% faster page navigation across all modern browsers
- Even greater improvements for users with slower connections:

### Multi-Tab Architecture
Notion solved the challenge of handling multiple browser tabs with an innovative approach:
1. Each tab has its own Web Worker for SQLite operations
2. A SharedWorker manages which tab is "active"
3. Only one tab can write to SQLite at a time
4. Queries from all tabs are routed through the active tab's Worker

### Key Learnings from Notion
1. **Async Loading**: They load the WASM SQLite library asynchronously to avoid blocking initial page load
2. **Race Conditions**: They implemented a "racing" system between SQLite and API requests to handle slower devices
3. **OPFS Handling**: They discovered that Origin Private File System (OPFS) doesn't handle concurrency well out of the box
4. **Cross-Origin Isolation**: They opted for OPFS SyncAccessHandle Pool VFS to avoid cross-origin isolation requirements

This real-world implementation demonstrates both the potential and challenges of using SQLite in production web applications. Notion's success shows that with careful architecture choices, SQLite can significantly improve web application performance.

## 🎯 Conclusion

You now have a solid foundation for building offline-capable Vue applications using SQLite. This approach offers significant advantages over traditional browser storage solutions, especially for complex data requirements.

---

--- title: Create Dark Mode-Compatible Technical Diagrams in Astro with Excalidraw: A Complete Guide description: Learn how to create and integrate theme-aware Excalidraw diagrams into your Astro blog. This step-by-step guide shows you how to build custom components that automatically adapt to light and dark modes, perfect for technical documentation and blogs tags: ['astro', 'excalidraw'] ---

# Create Dark Mode-Compatible Technical Diagrams in Astro with Excalidraw: A Complete Guide



## Why You Need Theme-Aware Technical Diagrams in Your Astro Blog

Technical bloggers often face a common challenge: creating diagrams seamlessly integrating with their site’s design system. While tools like Excalidraw make it easy to create beautiful diagrams, maintaining their visual consistency across different theme modes can be frustrating. This is especially true when your Astro blog supports light and dark modes.
This tutorial will solve this problem by building a custom solution that automatically adapts your Excalidraw diagrams to match your site’s theme.

## Common Challenges with Technical Diagrams in Web Development
When working with Excalidraw, we face several issues:
- Exported SVGs come with fixed colors
- Diagrams don't automatically adapt to dark mode
- Maintaining separate versions for different themes is time-consuming
- Lack of interactive elements and smooth transitions

## Before vs After: The Impact of Theme-Aware Diagrams

<div class="grid grid-cols-2 gap-8 w-full">
  <div class="w-full">
    <h4 class="text-xl font-bold">Standard Export</h4>
    <p>Here's how a typical Excalidraw diagram looks without any customization:</p>
    <Image 
      src={example} 
      alt="How a excalidraw diagrams looks without our custom component"
      width={400}
      height={300}
      class="w-full h-auto object-cover"
    />
  </div>

  <div class="w-full">
    <h4 class="text-xl font-bold">With Our Solution</h4>
    <p>And here's the same diagram using our custom component:</p>
    
  </div>
</div>

## Building a Theme-Aware Excalidraw Component for Astro

We'll create an Astro component that transforms static Excalidraw exports into dynamic, theme-aware diagrams. Our solution will:
1. Automatically adapt to light and dark modes
2. Support your custom design system colors
3. Add interactive elements and smooth transitions
4. Maintain accessibility standards

💡 Quick Start: Need an Astro blog first? Use [AstroPaper](https://github.com/satnaing/astro-paper) as your starter or build from scratch. This tutorial focuses on the diagram component itself.

## Step-by-Step Implementation Guide

### 1. Implementing the Theme System

First, let's define the color variables that will power our theme-aware diagrams:

```css
html[data-theme="light"] {
  --color-fill: 250, 252, 252;
  --color-text-base: 34, 46, 54;
  --color-accent: 211, 0, 106;
  --color-card: 234, 206, 219;
  --color-card-muted: 241, 186, 212;
  --color-border: 227, 169, 198;
}

html[data-theme="dark"] {
  --color-fill: 33, 39, 55;
  --color-text-base: 234, 237, 243;
  --color-accent: 255, 107, 237;
  --color-card: 52, 63, 96;
  --color-card-muted: 138, 51, 123;
  --color-border: 171, 75, 153;
}
```

### 2. Creating Optimized Excalidraw Diagrams

Follow these steps to prepare your diagrams:

1. Create your diagram at [Excalidraw](https://excalidraw.com/)
2. Export the diagram:
   - Select your diagram
   - Click the export button
   ![How to export Excalidraw diagram as SVG](../../assets/images/excalidraw-astro/how-to-click-export-excalidraw.png)
3. Configure export settings:
   - Uncheck "Background"
   - Choose SVG format
   - Click "Save"
   ![How to hide background and save as SVG](../../assets/images/excalidraw-astro/save-as-svg.png)

### 3. Building the ExcalidrawSVG Component

Here's our custom Astro component that handles the theme-aware transformation:

```astro
---
interface Props {
  src: ImageMetadata | string;
  alt: string;
  caption?: string;
}

const { src, alt, caption } = Astro.props;

const svgUrl = typeof src === 'string' ? src : src.src;
---

<figure class="excalidraw-figure">
  <div class="excalidraw-svg" data-svg-url={svgUrl} aria-label={alt}>
    
  </div>
  {caption && <figcaption>{caption}</figcaption>}
</figure>

<script>
  function modifySvg(svgString: string): string {
    const parser = new DOMParser();
    const doc = parser.parseFromString(svgString, 'image/svg+xml');
    const svg = doc.documentElement;

    svg.setAttribute('width', '100%');
    svg.setAttribute('height', '100%');
    svg.classList.add('w-full', 'h-auto');

    doc.querySelectorAll('text').forEach(text => {
      text.removeAttribute('fill');
      text.classList.add('fill-skin-base');
    });

    doc.querySelectorAll('rect').forEach(rect => {
      rect.removeAttribute('fill');
      rect.classList.add('fill-skin-soft');
    });

    doc.querySelectorAll('path').forEach(path => {
      path.removeAttribute('stroke');
      path.classList.add('stroke-skin-accent');
    });

    doc.querySelectorAll('g').forEach(g => {
      g.classList.add('excalidraw-element');
    });

    return new XMLSerializer().serializeToString(doc);
  }

  function initExcalidrawSVG() {
    const svgContainers = document.querySelectorAll<HTMLElement>('.excalidraw-svg');
    svgContainers.forEach(async (container) => {
      const svgUrl = container.dataset.svgUrl;
      if (svgUrl) {
        try {
          const response = await fetch(svgUrl);
          if (!response.ok) {
            throw new Error(`Failed to fetch SVG: ${response.statusText}`);
          }
          const svgData = await response.text();
          const modifiedSvg = modifySvg(svgData);
          container.innerHTML = modifiedSvg;
        } catch (error) {
          console.error('Error in ExcalidrawSVG component:', error);
          container.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
            <text x="10" y="50" fill="red">Error loading SVG</text>
          </svg>`;
        }
      }
    });
  }

  // Run on initial page load
  document.addEventListener('DOMContentLoaded', initExcalidrawSVG);

  // Run on subsequent navigation
  document.addEventListener('astro:page-load', initExcalidrawSVG);
</script>

<style>
  .excalidraw-figure {
    @apply w-full max-w-full overflow-hidden my-8;
  }
  .excalidraw-svg {
    @apply w-full max-w-full overflow-hidden;
  }
  :global(.excalidraw-svg svg) {
    @apply w-full h-auto;
  }
  :global(.excalidraw-svg .fill-skin-base) {
    @apply fill-[rgb(34,46,54)] dark:fill-[rgb(234,237,243)];
  }
  :global(.excalidraw-svg .fill-skin-soft) {
    @apply fill-[rgb(234,206,219)] dark:fill-[rgb(52,63,96)];
  }
  :global(.excalidraw-svg .stroke-skin-accent) {
    @apply stroke-[rgb(211,0,106)] dark:stroke-[rgb(255,107,237)];
  }
  :global(.excalidraw-svg .excalidraw-element) {
    @apply transition-all duration-300;
  }
  :global(.excalidraw-svg .excalidraw-element:hover) {
    @apply opacity-80;
  }
  figcaption {
    @apply text-center mt-4 text-sm text-skin-base italic;
  }
</style>

```

### 4. Using the Component

Integrate the component into your MDX blog posts:

💡 **Note:** We need to use MDX so that we can use the `ExcalidrawSVG` component in our blog posts. You can read more about MDX [here](https://mdxjs.com/).

```mdx
---
---

# My Technical Blog Post

<ExcalidrawSVG 
  src={myDiagram} 
  alt="Architecture diagram" 
  caption="System architecture overview" 
/>
```

### Best Practices and Tips for Theme-Aware Technical Diagrams

1. **Simplicity and Focus**
   - Keep diagrams simple and focused for better readability
   - Avoid cluttering with unnecessary details

2. **Consistent Styling**
   - Use consistent styling across all diagrams
   - Maintain a uniform look and feel throughout your documentation

3. **Thorough Testing**
   - Test thoroughly in both light and dark modes
   - Ensure diagrams are clear and legible in all color schemes

4. **Accessibility Considerations**
   - Consider accessibility when choosing colors and contrast
   - Ensure diagrams are understandable for users with color vision deficiencies

5. **Smooth Transitions**
   - Implement smooth transitions for theme changes
   - Provide a seamless experience when switching between light and dark modes

## Conclusion

With this custom component, you can now create technical diagrams that seamlessly integrate with your Astro blog's design system.
This solution eliminates the need for maintaining multiple versions of diagrams while providing a superior user experience through smooth transitions and interactive elements.

---

--- title: Frontend Testing Guide: 10 Essential Rules for Naming Tests description: Learn how to write clear and maintainable frontend tests with 10 practical naming rules. Includes real-world examples showing good and bad practices for component testing across any framework. tags: ['testing', 'vitest'] ---

# Frontend Testing Guide: 10 Essential Rules for Naming Tests



## Introduction

The path to better testing starts with something surprisingly simple: how you name your tests. Good test names:

- Make your test suite more maintainable
- Guide you toward writing tests that focus on user behavior
- Improve clarity and readability for your team

In this blog post, we'll explore 10 essential rules for writing better tests that will transform your approach to testing. These principles are:

1. Framework-agnostic
2. Applicable across the entire testing pyramid
3. Useful for various testing tools:
   - Unit tests (Jest, Vitest)
   - Integration tests
   - End-to-end tests (Cypress, Playwright)

By following these rules, you'll create a more robust and understandable test suite, regardless of your chosen testing framework or methodology.

## Rule 1: Always Use "should" + Verb
Every test name should start with "should" followed by an action verb.

```js
// ❌ Bad
it('displays the error message', () => {})
it('modal visibility', () => {})
it('form validation working', () => {})

// ✅ Good
it('should display error message when validation fails', () => {})
it('should show modal when trigger button is clicked', () => {})
it('should validate form when user submits', () => {})
```

**Generic Pattern:** `should [verb] [expected outcome]`

## Rule 2: Include the Trigger Event
Specify what causes the behavior you're testing.

```js
// ❌ Bad
it('should update counter', () => {})
it('should validate email', () => {})
it('should show dropdown', () => {})

// ✅ Good
it('should increment counter when plus button is clicked', () => {})
it('should show error when email format is invalid', () => {})
it('should open dropdown when toggle is clicked', () => {})
```

**Generic Pattern:** `should [verb] [expected outcome] when [trigger event]`

## Rule 3: Group Related Tests with Descriptive Contexts
Use describe blocks to create clear test hierarchies.

```js
// ❌ Bad
describe('AuthForm', () => {
  it('should test empty state', () => {})
  it('should test invalid state', () => {})
  it('should test success state', () => {})
})

// ✅ Good
describe('AuthForm', () => {
  describe('when form is empty', () => {
    it('should disable submit button', () => {})
    it('should not show any validation errors', () => {})
  })

  describe('when submitting invalid data', () => {
    it('should show validation errors', () => {})
    it('should keep submit button disabled', () => {})
  })
})
```

**Generic Pattern:** 
```js
describe('[Component/Feature]', () => {
  describe('when [specific condition]', () => {
    it('should [expected behavior]', () => {})
  })
})
```

## Rule 4: Name State Changes Explicitly
Clearly describe the before and after states in your test names.

```js
// ❌ Bad
it('should change status', () => {})
it('should update todo', () => {})
it('should modify permissions', () => {})

// ✅ Good
it('should change status from pending to approved', () => {})
it('should mark todo as completed when checkbox clicked', () => {})
it('should upgrade user from basic to premium', () => {})
```

**Generic Pattern:** `should change [attribute] from [initial state] to [final state]`

## Rule 5: Describe Async Behavior Clearly
Include loading and result states for asynchronous operations.

```js
// ❌ Bad
it('should load data', () => {})
it('should handle API call', () => {})
it('should fetch user', () => {})

// ✅ Good
it('should show skeleton while loading data', () => {})
it('should display error message when API call fails', () => {})
it('should render profile after user data loads', () => {})
```

**Generic Pattern:** `should [verb] [expected outcome] [during/after] [async operation]`

## Rule 6: Name Error Cases Specifically
Be explicit about the type of error and what causes it.

```js
// ❌ Bad
it('should show error', () => {})
it('should handle invalid input', () => {})
it('should validate form', () => {})

// ✅ Good
it('should show "Invalid Card" when card number is wrong', () => {})
it('should display "Required" when password is empty', () => {})
it('should show network error when API is unreachable', () => {})
```

**Generic Pattern:** `should show [specific error message] when [error condition]`

## Rule 7: Use Business Language, Not Technical Terms
Write tests using domain language rather than implementation details.

```js
// ❌ Bad
it('should update state', () => {})
it('should dispatch action', () => {})
it('should modify DOM', () => {})

// ✅ Good
it('should save customer order', () => {})
it('should update cart total', () => {})
it('should mark order as delivered', () => {})
```

**Generic Pattern:** `should [business action] [business entity]`

## Rule 8: Include Important Preconditions
Specify conditions that affect the behavior being tested.

```js
// ❌ Bad
it('should enable button', () => {})
it('should show message', () => {})
it('should apply discount', () => {})

// ✅ Good
it('should enable checkout when cart has items', () => {})
it('should show free shipping when total exceeds $100', () => {})
it('should apply discount when user is premium member', () => {})
```

**Generic Pattern:** `should [expected behavior] when [precondition]`

## Rule 9: Name UI Feedback Tests from User Perspective
Describe visual changes as users would perceive them.

```js
// ❌ Bad
it('should set error class', () => {})
it('should toggle visibility', () => {})
it('should update styles', () => {})

// ✅ Good
it('should highlight search box in red when empty', () => {})
it('should show green checkmark when password is strong', () => {})
it('should disable submit button while processing', () => {})
```

**Generic Pattern:** `should [visual change] when [user action/condition]`

## Rule 10: Structure Complex Workflows Step by Step
Break down complex processes into clear steps.

```js
// ❌ Bad
describe('Checkout', () => {
  it('should process checkout', () => {})
  it('should handle shipping', () => {})
  it('should complete order', () => {})
})

// ✅ Good
describe('Checkout Process', () => {
  it('should first validate items are in stock', () => {})
  it('should then collect shipping address', () => {})
  it('should finally process payment', () => {})
  
  describe('after successful payment', () => {
    it('should display order confirmation', () => {})
    it('should send confirmation email', () => {})
  })
})
```

**Generic Pattern:** 
```js
describe('[Complex Process]', () => {
  it('should first [initial step]', () => {})
  it('should then [next step]', () => {})
  it('should finally [final step]', () => {})

  describe('after [key milestone]', () => {
    it('should [follow-up action]', () => {})
  })
})
```

## Complete Example

Here's a comprehensive example showing how to combine all these rules:

```js
// ❌ Bad
describe('ShoppingCart', () => {
  it('test adding item', () => {})
  it('check total', () => {})
  it('handle checkout', () => {})
})

// ✅ Good
describe('ShoppingCart', () => {
  describe('when adding items', () => {
    it('should add item to cart when add button is clicked', () => {})
    it('should update total price immediately', () => {})
    it('should show item count badge', () => {})
  })

  describe('when cart is empty', () => {
    it('should display empty cart message', () => {})
    it('should disable checkout button', () => {})
  })

  describe('during checkout process', () => {
    it('should validate stock before proceeding', () => {})
    it('should show loading indicator while processing payment', () => {})
    it('should display success message after completion', () => {})
  })
})
```

## Test Name Checklist

Before committing your test, verify that its name:
- [ ] Starts with "should"
- [ ] Uses a clear action verb
- [ ] Specifies the trigger condition
- [ ] Uses business language
- [ ] Describes visible behavior
- [ ] Is specific enough for debugging
- [ ] Groups logically with related tests

## Conclusion

Thoughtful test naming is a fundamental building block in the broader landscape of writing better tests. To maintain consistency across your team:

1. Document your naming conventions in detail
2. Share these guidelines with all team members
3. Integrate the guidelines into your development workflow

For teams using AI tools like GitHub Copilot:
- Incorporate these guidelines into your project documentation
- Link the markdown file containing these rules to Copilot
- This integration allows Copilot to suggest test names aligned with your conventions

For more information on linking documentation to Copilot, see:
[VS Code Experiments Boost AI Copilot Functionality](https://visualstudiomagazine.com/Articles/2024/09/09/VS-Code-Experiments-Boost-AI-Copilot-Functionality.aspx)

By following these steps, you can ensure consistent, high-quality test naming across your entire project.

---

--- title: Create a Native-Like App in 4 Steps: PWA Magic with Vue 3 and Vite description: Transform your Vue 3 project into a powerful Progressive Web App in just 4 steps. Learn how to create offline-capable, installable web apps using Vite and modern PWA techniques. tags: ['vue', 'pwa', 'vite'] ---

# Create a Native-Like App in 4 Steps: PWA Magic with Vue 3 and Vite



## Table of Contents

## Introduction
Progressive Web Apps (PWAs) have revolutionized our thoughts on web applications. PWAs offer a fast, reliable, and engaging user experience by combining the best web and mobile apps. They work offline, can be installed on devices, and provide a native app-like experience without app store distribution.

This guide will walk you through creating a Progressive Web App using Vue 3 and Vite. By the end of this tutorial, you’ll have a fully functional PWA that can work offline, be installed on users’ devices, and leverage modern web capabilities.

## Understanding the Basics of Progressive Web Apps (PWAs)

Before diving into the development process, it's crucial to grasp the fundamental concepts of PWAs:

- **Multi-platform Compatibility**: PWAs are designed for applications that can function across multiple platforms, not just the web.
- **Build Once, Deploy Everywhere**: With PWAs, you can develop an application once and deploy it on Android, iOS, Desktop, and Web platforms.
- **Enhanced User Experience**: PWAs offer features like offline functionality, push notifications, and home screen installation.

For a more in-depth understanding of PWAs, refer to the [MDN Web Docs on Progressive Web Apps](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps).

## Prerequisites for Building a PWA with Vue 3 and Vite

Before you start, make sure you have the following tools installed:
1. Node.js installed on your system
2. Package manager: pnpm, npm, or yarn
3. Basic familiarity with Vue 3

## Step 1: Setting Up the Vue Project

First, we'll set up a new Vue project using the latest Vue CLI. This will give us a solid foundation to build our PWA upon.

1. Create a new Vue project by running the following command in your terminal:
   ```bash
   pnpm create vue@latest
   ```

2. Follow the prompts to configure your project. Here's an example configuration:
   ```shell
   ✔ Project name: … local-first-example
   ✔ Add TypeScript? … Yes
   ✔ Add JSX Support? … Yes
   ✔ Add Vue Router for Single Page Application development? … Yes
   ✔ Add Pinia for state management? … Yes
   ✔ Add Vitest for Unit Testing? … Yes
   ✔ Add an End-to-End Testing Solution? › No
   ✔ Add ESLint for code quality? … Yes
   ✔ Add Prettier for code formatting? … Yes
   ✔ Add Vue DevTools 7 extension for debugging? (experimental) … Yes
   ```

3. Once the project is created, navigate to your project directory and install dependencies:
   ```bash
   cd local-first-example
   pnpm install
   pnpm run dev
   ```

Great! You now have a basic Vue 3 project up and running. Let's move on to adding PWA functionality.

## Step 2: Create the needed assets for the PWA

We need to add specific assets and configurations to transform our Vue app into a PWA.
PWAs can be installed on various devices, so we must prepare icons and other assets for different platforms.

1. First, let's install the necessary packages:
   ```bash
   pnpm add -D vite-plugin-pwa @vite-pwa/assets-generator
   ```

2. Create a high-resolution icon (preferably an SVG or a PNG with at least 512x512 pixels) for your PWA and place it in your `public` directory. Name it something like `pwa-icon.svg` or `pwa-icon.png`.

3. Generate the PWA assets by running:
   ```bash
   npx pwa-asset-generator --preset minimal public/pwa-icon.svg
   ```

This command will automatically generate a set of icons and a web manifest file in your `public` directory. The `minimal` preset will create:

- favicon.ico (48x48 transparent icon for browser tabs)
- favicon.svg (SVG icon for modern browsers)
- apple-touch-icon-180x180.png (Icon for iOS devices when adding to home screen)
- maskable-icon-512x512.png (Adaptive icon that fills the entire shape on Android devices)
- pwa-64x64.png (Small icon for various UI elements)
- pwa-192x192.png (Medium-sized icon for app shortcuts and tiles)
- pwa-512x512.png (Large icon for high-resolution displays and splash screens)

Output will look like this:

```shell
> vue3-pwa-timer@0.0.0 generate-pwa-assets /Users/your user/git2/vue3-pwa-example
> pwa-assets-generator "--preset" "minimal-2023" "public/pwa-icon.svg"

Zero Config PWA Assets Generator v0.2.6
◐ Preparing to generate PWA assets...
◐ Resolving instructions...
✔ PWA assets ready to be generated, instructions resolved
◐ Generating PWA assets from public/pwa-icon.svg image
◐ Generating assets for public/pwa-icon.svg...
✔ Generated PNG file: /Users/your user/git2/vue3-pwa-example/public/pwa-64x64.png
✔ Generated PNG file: /Users/your user/git2/vue3-pwa-example/public/pwa-192x192.png
✔ Generated PNG file: /Users/your user/git2/vue3-pwa-example/public/pwa-512x512.png
✔ Generated PNG file: /Users/your user/git2/vue3-pwa-example/public/maskable-icon-512x512.png
✔ Generated PNG file: /Users/your user/git2/vue3-pwa-example/public/apple-touch-icon-180x180.png
✔ Generated ICO file: /Users/your user/git2/vue3-pwa-example/public/favicon.ico
✔ Assets generated for public/pwa-icon.svg
◐ Generating Html Head Links...
<link rel="icon" href="/favicon.ico" sizes="48x48">
<link rel="icon" href="/pwa-icon.svg" sizes="any" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon-180x180.png">
✔ Html Head Links generated
◐ Generating PWA web manifest icons entry...
{
  "icons": [
    {
      "src": "pwa-64x64.png",
      "sizes": "64x64",
      "type": "image/png"
    },
    {
      "src": "pwa-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "pwa-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    },
    {
      "src": "maskable-icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable"
    }
  ]
}
✔ PWA web manifest icons entry generated
✔ PWA assets generated
```

These steps will ensure your PWA has all the necessary icons and assets to function correctly across different devices and platforms. 
The minimal-2023 preset provides a modern, optimized set of icons that meet the latest PWA requirements.
## Step 3: Configuring Vite for PWA Support

With our assets ready, we must configure Vite to enable PWA functionality. This involves setting up the manifest and other PWA-specific options.

First, update your main HTML file (usually `index.html`) to include important meta tags in the `<head>` section:

```html
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="theme-color" content="#ffffff">
  <link rel="icon" href="/favicon.ico" sizes="48x48">
  <link rel="icon" href="/favicon.svg" sizes="any" type="image/svg+xml">
  <link rel="apple-touch-icon" href="/apple-touch-icon-180x180.png">
</head>
```

Now, update your `vite.config.ts` file with the following configuration:

```typescript

export default defineConfig({
  plugins: [
    vue(),
    VitePWA({
      registerType: 'autoUpdate',
      includeAssets: ['favicon.ico', 'apple-touch-icon-180x180.png', 'maskable-icon-512x512.png'],
      manifest: {
        name: 'My Awesome PWA',
        short_name: 'MyPWA',
        description: 'A PWA built with Vue 3',
        theme_color: '#ffffff',
        icons: [
          {
            src: 'pwa-64x64.png',
            sizes: '64x64',
            type: 'image/png'
          },
          {
            src: 'pwa-192x192.png',
            sizes: '192x192',
            type: 'image/png'
          },
          {
            src: 'pwa-512x512.png',
            sizes: '512x512',
            type: 'image/png',
            purpose: 'any'
          },
          {
            src: 'maskable-icon-512x512.png',
            sizes: '512x512',
            type: 'image/png',
            purpose: 'maskable'
          }
        ]
      },
      devOptions: {
        enabled: true
      }
    })
  ],
})
```

<Aside type="note">
  The `devOptions: { enabled: true }` setting is crucial for testing your PWA on localhost. Normally, PWAs require HTTPS, but this setting allows the PWA features to work on `http://localhost` during development. Remember to remove or set this to `false` for production builds.
</Aside>

This configuration generates a Web App Manifest, a JSON file that tells the browser about your Progressive Web App and how it should behave when installed on the user’s desktop or mobile device. The manifest includes the app’s name, icons, and theme colors.
## PWA Lifecycle and Updates

The `registerType: 'autoUpdate'` option in our configuration sets up automatic updates for our PWA. Here's how it works:

1. When a user visits your PWA, the browser downloads and caches the latest version of your app.
2. On subsequent visits, the service worker checks for updates in the background.
3. If an update is available, it's downloaded and prepared for the next launch.
4. The next time the user opens or refreshes the app, they'll get the latest version.

This ensures that users always have the most up-to-date version of your app without manual intervention.

## Step 4: Implementing Offline Functionality with Service Workers

The real power of PWAs comes from their ability to work offline. We'll use the `vite-plugin-pwa` to integrate Workbox, which will handle our service worker and caching strategies.

Before we dive into the configuration, let's understand the runtime caching strategies we'll be using:

1. **StaleWhileRevalidate** for static resources (styles, scripts, and workers):
   - This strategy serves cached content immediately while fetching an update in the background.
   - It's ideal for frequently updated resources that aren't 100% up-to-date.
   - We'll limit the cache to 50 entries and set an expiration of 30 days.

2. **CacheFirst** for images:
   - This strategy serves cached images immediately without network requests if they're available.
   - It's perfect for static assets that don't change often.
   - We'll limit the image cache to 100 entries and set an expiration of 60 days.

These strategies ensure that your PWA remains functional offline while efficiently managing cache storage.

Now, let's update your `vite.config.ts` file to include service worker configuration with these advanced caching strategies:

```typescript

export default defineConfig({
  plugins: [
    vue(),
    VitePWA({
      devOptions: {
        enabled: true
      },
      registerType: 'autoUpdate',
      includeAssets: ['favicon.ico', 'apple-touch-icon.png', 'masked-icon.svg'],
      manifest: {
        name: 'Vue 3 PWA Timer',
        short_name: 'PWA Timer',
        description: 'A customizable timer for Tabata and EMOM workouts',
        theme_color: '#ffffff',
        icons: [
          {
            src: 'pwa-192x192.png',
            sizes: '192x192',
            type: 'image/png'
          },
          {
            src: 'pwa-512x512.png',
            sizes: '512x512',
            type: 'image/png'
          }
        ]
      },
      workbox: {
        runtimeCaching: [
          {
            urlPattern: ({ request }) => request.destination === 'style' || request.destination === 'script' ||
             request.destination === 'worker',
            handler: 'StaleWhileRevalidate',
            options: {
              cacheName: 'static-resources',
              expiration: {
                maxEntries: 50,
                maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days
              },
            },
          },
          {
            urlPattern: ({ request }) => request.destination === 'image',
            handler: 'CacheFirst',
            options: {
              cacheName: 'images',
              expiration: {
                maxEntries: 100,
                maxAgeSeconds: 60 * 24 * 60 * 60, // 60 days
              },
            },
          },
        ],
      },
    }),
  ],
})
```

## Testing Your PWA

Now that we've set up our PWA, it's time to test its capabilities:

1. Test your PWA locally:
   ```bash
   pnpm run dev
   ```

2. Open Chrome DevTools and navigate to the Application tab.
   - Check the "Manifest" section to ensure your Web App Manifest is loaded correctly.
   - In the "Service Workers" section, verify that your service worker is registered and active.
   [![PWA Service Worker](../../assets/images/pwa/serviceWorker.png)

3. Test offline functionality:
   - Go to the Network tab in DevTools and check the "Offline" box to simulate offline conditions.
   - Refresh the page and verify that your app still works without an internet connection.
   - Uncheck the “Offline” box and refresh to ensure the app works online.

4. Test caching:
   - In the Application tab, go to "Cache Storage" to see the caches created by your service worker.
   - Verify that assets are being cached according to your caching strategies.

5. Test installation:
   - On desktop: Look for the install icon in the address bar or the three-dot menu.
   [![PWA Install Icon](../../assets/images/pwa/desktopInstall.png)](../../assets/images/pwa/desktopInstall.png)
   [![PWA Install Icon](../../assets/images/pwa/installApp.png)](../../assets/images/pwa/installApp.png)

   - On mobile: You should see a prompt to "Add to Home Screen".

6. Test updates:
   - Make a small change to your app and redeploy.
   - Revisit the app and check if the service worker updates (you can monitor this in the Application tab).

By thoroughly testing these aspects, you can ensure that your PWA functions correctly across various scenarios and platforms.

<Aside type="info">
  If you want to see a full-fledged PWA in action, check out [Elk](https://elk.zone/), a nimble Mastodon web client. It's built with Nuxt and is anexcellent example of a production-ready PWA. You can also explore its open-source code on [GitHub](https://github.com/elk-zone/elk) to see how they've implemented various PWA features.
</Aside>

## Conclusion

Congratulations! You've successfully created a Progressive Web App using Vue 3 and Vite. 
Your app can now work offline, be installed on users' devices, and provide a native-like experience.

Refer to the [Vite PWA Workbox documentation](https://vite-pwa-org.netlify.app/workbox/) for more advanced Workbox configurations and features.

The more challenging part is building suitable components with a native-like feel on all the devices you want to support. 
PWAs are also a main ingredient in building local-first applications. 
If you are curious about what I mean by that, check out the following: [What is Local First Web Development](../what-is-local-first-web-development). 

For a complete working example of this Vue 3 PWA, you can check out the complete source code at [full example](https://github.com/alexanderop/vue3-pwa-example). 
This repository contains the finished project, allowing you to see how all the pieces come together in a real-world application.

---

--- title: Atomic Architecture: Revolutionizing Vue and Nuxt Project Structure description: Learn how to implement Atomic Design principles in Vue or Nuxt projects. Improve your code structure and maintainability with this guide tags: ['vue', 'architecture'] ---

# Atomic Architecture: Revolutionizing Vue and Nuxt Project Structure



## Introduction

Clear writing requires clear thinking. The same is valid for coding. 
Throwing all components into one folder may work when starting a personal project. 
But as projects grow, especially with larger teams, this approach leads to problems:

- Duplicated code
- Oversized, multipurpose components
- Difficult-to-test code

Atomic Design offers a solution. Let's examine how to apply it to a Nuxt project.

## What is Atomic Design

![atomic design diagram brad Frost](../../assets/images/atomic/diagram.svg)

Brad Frost developed Atomic Design as a methodology for creating design systems. It is structured into five levels inspired by chemistry:

1. Atoms: Basic building blocks (e.g. form labels, inputs, buttons)
2. Molecules: Simple groups of UI elements (e.g. search forms)
3. Organisms: Complex components made of molecules/atoms (e.g. headers)
4. Templates: Page-level layouts
5. Pages: Specific instances of templates with content

<Aside type='tip' title="Tip">
For a better exploration of Atomic Design principles, I recommend reading Brad Frost's blog post: [Atomic Web Design](https://bradfrost.com/blog/post/atomic-web-design/)
</Aside>

For Nuxt, we can adapt these definitions:

- Atoms: Pure, single-purpose components
- Molecules: Combinations of atoms with minimal logic
- Organisms: Larger, self-contained, reusable components
- Templates: Nuxt layouts defining page structure
- Pages: Components handling data and API calls

<Aside type="info" title="Organisms vs Molecules: What's the Difference?">

Molecules and organisms can be confusing. Here's a simple way to think about them:

- Molecules are small and simple. They're like LEGO bricks that snap together.
  Examples: 
  - A search bar (input + button)
  - A login form (username input + password input + submit button)
  - A star rating (5 star icons + rating number)

- Organisms are bigger and more complex. They're like pre-built LEGO sets.
  Examples:
  - A full website header (logo + navigation menu + search bar)
  - A product card (image + title + price + add to cart button)
  - A comment section (comment form + list of comments)

Remember: Molecules are parts of organisms, but organisms can work independently.
</Aside>

### Code Example: Before and After

#### Consider this non-Atomic Design todo app component:

![Screenshot of ToDo App](../../assets/images/atomic/screenshot-example-app.png)

```vue
<template>
  <div class="container mx-auto p-4">
    <h1 class="text-2xl font-bold mb-4 text-gray-800 dark:text-gray-200">Todo App</h1>
    
    <!-- Add Todo Form -->
    <form @submit.prevent="addTodo" class="mb-4">
      <input
        v-model="newTodo"
        type="text"
        placeholder="Enter a new todo"
        class="border p-2 mr-2 bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded"
      />
      <button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white p-2 rounded transition duration-300">
        Add Todo
      </button>
    </form>
    
    <!-- Todo List -->
    <ul class="space-y-2">
      <li
        v-for="todo in todos"
        :key="todo.id"
        class="flex justify-between items-center p-3 bg-gray-100 dark:bg-gray-700 rounded shadow-sm"
      >
        <span class="text-gray-800 dark:text-gray-200">{{ todo.text }}</span>
        <button
          @click="deleteTodo(todo.id)"
          class="bg-red-500 hover:bg-red-600 text-white p-1 rounded transition duration-300"
        >
          Delete
        </button>
      </li>
    </ul>
  </div>
</template>

<script setup lang="ts">
interface Todo {
  id: number
  text: string
}

const newTodo = ref('')
const todos = ref<Todo[]>([])

const fetchTodos = async () => {
  // Simulating API call
  todos.value = [
    { id: 1, text: 'Learn Vue.js' },
    { id: 2, text: 'Build a Todo App' },
    { id: 3, text: 'Study Atomic Design' }
  ]
}

const addTodo = async () => {
  if (newTodo.value.trim()) {
    // Simulating API call
    const newTodoItem: Todo = {
      id: Date.now(),
      text: newTodo.value
    }
    todos.value.push(newTodoItem)
    newTodo.value = ''
  }
}

const deleteTodo = async (id: number) => {
  // Simulating API call
  todos.value = todos.value.filter(todo => todo.id !== id)
}

onMounted(fetchTodos)
</script>
```

This approach leads to large, difficult-to-maintain components. Let's refactor using Atomic Design:

### This will be the refactored structure

```shell
📐 Template (Layout)
   │
   └─── 📄 Page (TodoApp)
        │
        └─── 📦 Organism (TodoList)
             │
             ├─── 🧪 Molecule (TodoForm)
             │    │
             │    ├─── ⚛️ Atom (BaseInput)
             │    └─── ⚛️ Atom (BaseButton)
             │
             └─── 🧪 Molecule (TodoItems)
                  │
                  └─── 🧪 Molecule (TodoItem) [multiple instances]
                       │
                       ├─── ⚛️ Atom (BaseText)
                       └─── ⚛️ Atom (BaseButton)
```

### Refactored Components

#### Tempalte Default

```vue

<template>
  <div class="min-h-screen bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100 transition-colors duration-300">
    <header class="bg-white dark:bg-gray-800 shadow">
      <nav class="container mx-auto px-4 py-4 flex justify-between items-center">
        <NuxtLink to="/" class="text-xl font-bold">Todo App</NuxtLink>
        
      </nav>
    </header>
    <main class="container mx-auto px-4 py-8">
      
    </main>
  </div>
</template>

<script setup lang="ts">
</script>
```

#### Pages
```vue

<script setup lang="ts">
interface Todo {
  id: number
  text: string
}

const todos = ref<Todo[]>([])

const fetchTodos = async () => {
  // Simulating API call
  todos.value = [
    { id: 1, text: 'Learn Vue.js' },
    { id: 2, text: 'Build a Todo App' },
    { id: 3, text: 'Study Atomic Design' }
  ]
}

const addTodo = async (text: string) => {
  // Simulating API call
  const newTodoItem: Todo = {
    id: Date.now(),
    text
  }
  todos.value.push(newTodoItem)
}

const deleteTodo = async (id: number) => {
  // Simulating API call
  todos.value = todos.value.filter(todo => todo.id !== id)
}

onMounted(fetchTodos)
</script>

<template>
  <div class="container mx-auto p-4">
    <h1 class="text-2xl font-bold mb-4 text-gray-800 dark:text-gray-200">Todo App</h1>
    <TodoList 
      :todos="todos"
      @add-todo="addTodo"
      @delete-todo="deleteTodo"
    />
  </div>
</template>
```

#### Organism (TodoList)

```vue

<script setup lang="ts">
interface Todo {
  id: number
  text: string
}

defineProps<{
  todos: Todo[]
}>()

defineEmits<{
  (e: 'add-todo', value: string): void
  (e: 'delete-todo', id: number): void
}>()
</script>

<template>
  <div>
    
    <ul class="space-y-2">
      <TodoItem
        v-for="todo in todos"
        :key="todo.id"
        :todo="todo"
        @delete-todo="$emit('delete-todo', $event)"
      />
    </ul>
  </div>
</template>
```

#### Molecules (TodoForm and TodoItem)

##### TodoForm.vue:

```vue

<script setup lang="ts">
interface Todo {
  id: number
  text: string
}

defineProps<{
  todos: Todo[]
}>()

defineEmits<{
  (e: 'add-todo', value: string): void
  (e: 'delete-todo', id: number): void
}>()
</script>

<template>
  <div>
    
    <ul class="space-y-2">
      <TodoItem
        v-for="todo in todos"
        :key="todo.id"
        :todo="todo"
        @delete-todo="$emit('delete-todo', $event)"
      />
    </ul>
  </div>
</template>
```

#### TodoItem.vue:

```vue

<script setup lang="ts">
const newTodo = ref('')
const emit = defineEmits<{
  (e: 'add-todo', value: string): void
}>()

const addTodo = () => {
  if (newTodo.value.trim()) {
    emit('add-todo', newTodo.value)
    newTodo.value = ''
  }
}
</script>

<template>
  <form @submit.prevent="addTodo" class="mb-4">
    
    <BaseButton type="submit">Add Todo</BaseButton>
  </form>
</template>
```

#### Atoms (BaseButton, BaseInput, BaseText)

##### BaseButton.vue:

```vue
<script setup lang="ts">
defineProps<{
  variant?: 'primary' | 'danger'
}>()
</script>

<template>
  <button
    :class="[
      'p-2 rounded transition duration-300',
      variant === 'danger'
        ? 'bg-red-500 hover:bg-red-600 text-white'
        : 'bg-blue-500 hover:bg-blue-600 text-white'
    ]"
  >
    <slot></slot>
  </button>
</template>
```
#### BaseInput.vue:

```vue

<script setup lang="ts">
defineProps<{
  modelValue: string
  placeholder?: string
}>()
defineEmits<{
  (e: 'update:modelValue', value: string): void
}>()
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
    type="text"
    :placeholder="placeholder"
    class="border p-2 mr-2 bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded"
  />
</template>
```

<Aside type='info' title="Info">
Want to check out the full example yourself? [click me](https://github.com/alexanderop/todo-app-example)
</Aside>

| Component Level | Job | Examples |
|-----------------|-----|----------|
| Atoms | Pure, single-purpose components |BaseButton BaseInput BaseIcon BaseText|
| Molecules | Combinations of atoms with minimal logic |SearchBar LoginForm StarRating Tooltip|
| Organisms | Larger, self-contained, reusable components. Can perform side effects and complex operations. |TheHeader ProductCard CommentSection NavigationMenu|
| Templates | Nuxt layouts defining page structure |DefaultLayout BlogLayout DashboardLayout AuthLayout|
| Pages | Components handling data and API calls |HomePage UserProfile ProductList CheckoutPage|

## Summary

Atomic Design offers one path to a more apparent code structure. 
It works well as a starting point for many projects. 
But as complexity grows, other architectures may serve you better. 
Want to explore more options? Read my post on [How to structure vue Projects](../how-to-structure-vue-projects). 
It covers approaches beyond Atomic Design when your project outgrows its initial structure.

---

--- title: Bolt Your Presentations: AI-Powered Slides description: Elevate your dev presentations with AI-powered tools. Learn to leverage Bolt, Slidev, and WebContainers for rapid, code-friendly slide creation. This guide walks developers through 7 steps to build impressive tech presentations using Markdown and browser-based Node.js. Master efficient presentation development with instant prototyping and one-click deployment to Netlify tags: ['productivity', 'ai'] ---

# Bolt Your Presentations: AI-Powered Slides



## Introduction

Presentations plague the middle-class professional. Most bore audiences with wordy slides. But AI tools promise sharper results, faster. Let's explore how.

![Bolt landingpage](../../assets/images/create-ai-presentations-fast/venn.svg)

## The Birth of Bolt

StackBlitz unveiled Bolt at ViteConf 2024. This browser-based coding tool lets developers build web apps without local setup. Pair it with Slidev, a Markdown slide creator, for rapid presentation development.

[![Image Presentation WebContainers & AI: Introducing bolt.new](http://img.youtube.com/vi/knLe8zzwNRA/0.jpg)](https://www.youtube.com/watch?v=knLe8zzwNRA "WebContainers & AI: Introducing bolt.new")

## Tools Breakdown

Three key tools enable this approach:

1. Bolt: AI-powered web app creation in the browser
     ![Bolt landingpage](../../assets/images/create-ai-presentations-fast/bolt-desc.png)

2. Slidev: Markdown-based slides with code support
    ![Slidev Landing page](../../assets/images/create-ai-presentations-fast/slidev-desc.png)

3. Webcontainers: Browser-based Node.js for instant prototyping
    ![WebContainers landing page](../../assets/images/create-ai-presentations-fast/webcontainers-interface.png)

## Seven Steps to AI Presentation Mastery

Follow these steps to craft AI-powered presentations:

1. Open bolt.new in your browser.

2. Tell Bolt to make a presentation on your topic. Be specific. (add use Slidedev for it)
    ![Screenshot for chat Bolt](../../assets/images/create-ai-presentations-fast/initial-interface.png)

3. Review the Bolt-generated slides. Check content and flow.
   ![Screenshot presentation result of bolt](../../assets/images/create-ai-presentations-fast/presentation.png)

4. Edit and refine.
   ![Screenshot for code from Bolt](../../assets/images/create-ai-presentations-fast/code-overview.png)

5. Ask AI for help with new slides, examples, or transitions.

6. Add code snippets and diagrams.

7. Deploy to Netlify with one click.
   ![Screenshot deploy bolt](../../assets/images/create-ai-presentations-fast/deploy-netlify.png)

## Why This Method Works

This approach delivers key advantages:

- Speed: Bolt jumpstarts content creation.
- Ease: No software to install.
- Flexibility: Make real-time adjustments.
- Collaboration: Share works-in-progress.
- Quality: Built-in themes ensure polish.
- Version control: Combine it with GitHub.

## Conclusion

Try this approach for your next talk. You'll create polished slides that engage your audience.

---

--- title: 10 Rules for Better Writing from the Book Economical Writing description: Master 10 key writing techniques from Deirdre McCloskey's 'Economical Writing.' Learn to use active verbs, write clearly, and avoid common mistakes. Ideal for students, researchers, and writers aiming to communicate more effectively. tags: ['book-summary', 'productivity'] ---

# 10 Rules for Better Writing from the Book Economical Writing



<BookCover
  src={bookCoverImage}
  alt="Book cover of Economical Writing"
  title="Economical Writing"
  author="Deirdre N. McCloskey"
  publicationYear={2019}
  genre="Academic Writing"
  rating={5}
  link="https://www.amazon.com/dp/022644807X"
/>

## Introduction

I always look for ways to `improve my writing`. Recently, I found Deirdre McCloskey’s book `Economical Writing` through an Instagram reel. In this post, I share `10 useful rules` from the book, with examples and quotes from McCloskey.

## Rules

### Rule 1: Be Thou Clear; but Seek Joy, Too

> Clarity is a matter of speed directed at The Point.
> Bad writing makes slow reading.

McCloskey emphasizes that `clarity is crucial above all`. When writing about complex topics, give your reader every help possible. I've noticed that even if a text has good content, bad writing makes it hard to understand.

<ExampleComparison
bad="The aforementioned methodology was implemented to facilitate the optimization of resource allocation."
good="We used this method to make the best use of our resources. It was exciting to see how much we could improve!"
/>

### Rule 2: You Will Need Tools

> The next most important tool is a dictionary, or nowadays a site on the internet that is itself a good dictionary. Googling a word is a bad substitute for a good dictionary site. You have to choose the intelligent site over the dreck such as Wiktionary, Google, and Dictionary.com, all useless.

The Writer highlights the significance of `tools` that everyone who is serious about writing should use. The tools could be:

- <a href="https://www.grammarly.com" target="_blank" rel="noopener noreferrer">Spell Checker</a> (Grammarly for example)
- <a href="https://www.oed.com" target="_blank" rel="noopener noreferrer">OED</a> (a real dictionary to look up the origin of words)
- <a href="https://www.thesaurus.com" target="_blank" rel="noopener noreferrer">Thesaurus</a> (shows you similar words)
- <a href="https://www.hemingwayapp.com" target="_blank" rel="noopener noreferrer">Hemingway Editor</a> (improves readability and highlights complex sentences)

### Rule 3: Avoid Boilerplate

McCloskey warns against using `filler language`:

> Never start a paper with that all-purpose filler for the bankrupt imagination, 'This paper . . .'

<ExampleComparison
bad="In this paper, we will explore, examine, and analyze the various factors that contribute to climate change."
good="Climate change stems from several key factors, including rising greenhouse gas emissions and deforestation."
/>

### Rule 4: A Paragraph Should Have a Point

Each paragraph should `focus` on a single topic:

> The paragraph should be a more or less complete discussion of one topic.

<ExampleComparison
bad="The economy is complex. There are many factors involved. Some people think it's improving while others disagree. It's hard to predict what will happen next."
good="The economy's complexity makes accurate predictions challenging, as multiple factors influence its performance in often unpredictable ways."
/>

### Rule 5: Make Your Writing Cohere

Coherence is crucial for readability:

> Make writing hang together. The reader can understand writing that hangs together,
from the level of phrases up to entire books.

<ExampleComparison
bad="The experiment failed. We used new equipment. The results were unexpected."
good="We used new equipment for the experiment. However, it failed, producing unexpected results."
/>

### Rule 6: Avoid Elegant Variation

McCloskey emphasizes that `clarity trumps elegance`:

> People who write so seem to mistake the purpose of writing, believing it to be an opportunity for empty display. The seventh grade, they should realize, is over.

 <ExampleComparison
  bad="The cat sat on the windowsill. The feline then jumped to the floor. The domestic pet finally curled up in its bed."
  good="The cat sat on the windowsill. It then jumped to the floor and finally curled up in its bed."
/>

### Rule 7: Watch Punctuation

Proper punctuation is more complex than it seems:

> Another detail is punctuation. You might think punctuation would be easy, since English has only seven marks."
> After a comma (,), semicolon (;), or colon (:), put one space before you start something new. After a period (.), question mark (?), or exclamation point (!), put two spaces.
> The colon (:) means roughly “to be specific.” The semicolon (;) means roughly “likewise” or “also.”

 <ExampleComparison
  bad="However we decided to proceed with the project despite the risks."
  good="However, we decided to proceed with the project despite the risks."
/>

### Rule 8: Watch The Order Around Switch Until It Good Sounds

McCloskey advises ending sentences with the main point:

> You should cultivate the habit of mentally rearranging the order of words and phrases of every sentence you write. Rules, as usual, govern the rewriting. One rule or trick is to use so-called auxiliary verbs (should, can, might, had, is, etc.) to lessen clotting in the sentence. “Looking through a lens-shape magnified what you saw.” Tough to read. “Looking through a lens-shape would magnify what you saw” is easier.
> The most important rule of rearrangement of sentences is that the end is the place of emphasis. I wrote the sentence first as “The end of the sentence is the emphatic location,” which put the emphasis on the word location. The reader leaves the sentence with the last word ringing in her mental ears.

<ExampleComparison
bad="Looking through a lens-shape magnified what you saw."
good="Looking through a lens-shape would magnify what you saw."
/>

### Rule 9: Use Verbs, Active Ones

Active verbs make writing more engaging:

> Use active verbs: not “Active verbs should be used,” which is cowardice, hiding the user in the passive voice. Rather: “You should use active verbs.”
> Verbs make English. If you pick out active, accurate, and lively verbs, you will write in an active, accurate, and lively style.

 <ExampleComparison
bad="The decision was made by the committee to approve the proposal."
good="The committee decided to approve the proposal."
/>

### Rule 10: Avoid This, That, These, Those

Vague demonstrative pronouns can obscure meaning:

> Often the plain the will do fine and keep the reader reading. The formula in revision is to ask of every this, these, those whether it might better be replaced by ether plain old the (the most common option) or it, or such (a).

<ExampleComparison
bad="This led to that, which caused these problems."
good="The budget cuts led to staff shortages, which caused delays in project completion."
/>

## Summary

I quickly finished the book, thanks to its excellent writing style. Its most important lesson was that much of what I learned about `good writing` in school is incorrect. Good writing means expressing your thoughts `clearly`. Avoid using complicated words. `Write the way you speak`. The book demonstrates that using everyday words is a strength, not a weakness. I suggest everyone read this book. Think about how you can improve your writing by using its ideas.

---

--- title: TypeScript Tutorial: Extracting All Keys from Nested Object description: Learn how to extract all keys, including nested ones, from TypeScript objects using advanced type manipulation techniques. Improve your TypeScript skills and write safer code. tags: ['typescript'] ---

# TypeScript Tutorial: Extracting All Keys from Nested Object



## What's the Problem?

Let's say you have a big TypeScript object. It has objects inside objects. You want to get all the keys, even the nested ones. But TypeScript doesn't provide this functionality out of the box.

Look at this User object:

```typescript twoslash
type User = {
  id: string;
  name: string;
  address: {
    street: string;
    city: string;
  };
};
```

You want "id", "name", and "address.street". The standard approach is insufficient:

```typescript twoslash
// ---cut-start---
type User = {
  id: string;
  name: string;
  address: {
    street: string;
    city: string;
  };
};
// ---cut-end---
// little helper to prettify the type on hover
type Pretty<T> = {
  [K in keyof T]: T[K];
} & {};

type UserKeys = keyof User;
type PrettyUserKeys = Pretty<UserKeys>;
//   ^?

```

This approach returns the top-level keys, missing nested properties like "address.street".

We need a more sophisticated solution using TypeScript's advanced features:

1. Conditional Types (if-then for types)
2. Mapped Types (change each part of a type)
3. Template Literal Types (make new string types)
4. Recursive Types (types that refer to themselves)

Here's our solution:

```typescript
type ExtractKeys<T> = T extends object
  ? {
      [K in keyof T & string]: 
        | K 
        | (T[K] extends object ? `${K}.${ExtractKeys<T[K]>}` : K);
    }[keyof T & string]
  : never;
```

Let's break down this type definition:

1. We check if T is an object.
2. For each key in the object:
3. We either preserve the key as-is, or
4. If the key's value is another object, we combine the key with its nested keys
5. We apply this to the entire type structure

Now let's use it:

```typescript twoslash
// ---cut-start---
type User = {
  id: string;
  name: string;
  address: {
    street: string;
    city: string;
  };
};

type ExtractKeys<T> = T extends object
  ? {
      [K in keyof T & string]: 
        | K 
        | (T[K] extends object ? `${K}.${ExtractKeys<T[K]>}` : K);
    }[keyof T & string]
  : never;
// ---cut-end---

type UserKeys = ExtractKeys<User>;
//         ^?

```

This gives us all keys, including nested ones.

The practical benefits become clear in this example:

```typescript twoslash

// ---cut-start---
type User = {
  id: string;
  name: string;
  address: {
    street: string;
    city: string;
  };
};

type ExtractKeys<T> = T extends object
  ? {
      [K in keyof T & string]: 
        | K 
        | (T[K] extends object ? `${K}.${ExtractKeys<T[K]>}` : K);
    }[keyof T & string]
  : never;

type UserKeys = ExtractKeys<User>;
// ---cut-end---
const user: User = {
  id: "123",
  name: "John Doe",
  address: {
    street: "Main St",
    city: "Berlin",
  },
};

function getProperty(obj: User, key: UserKeys) {
  const keys = key.split(".");
  let result: any = obj;

  for (const k of keys) {
    result = result[k];
  }

  return result;
}

// This works
getProperty(user, "address.street");

// This gives an error
getProperty(user, "address.country");
```

TypeScript detects potential errors during development.

Important Considerations:

1. This type implementation may impact performance with complex nested objects.
2. The type system enhances development-time safety without runtime overhead.
3. Consider the trade-off between type safety and code readability.

## Wrap-Up

We've explored how to extract all keys from nested TypeScript objects. This technique provides enhanced type safety for your data structures. Consider the performance implications when implementing this in your projects.

---

--- title: TypeScript Snippets in Astro: Show, Don't Tell description: Learn how to add interactive type information and syntax highlighting to TypeScript snippets in your Astro site, enhancing code readability and user experience. tags: ['astro', 'typescript'] ---

# TypeScript Snippets in Astro: Show, Don't Tell



## Elevate Your Astro Code Highlights with TypeScript Snippets

Want to take your Astro code highlights to the next level? This guide will show you how to add TypeScript snippets with hover-over type information, making your code examples more interactive and informative.

## Prerequisites for Astro Code Highlights

Start with an Astro project. Follow the [official Astro quickstart guide](https://docs.astro.build/en/getting-started/) to set up your project.

## Configuring Shiki for Enhanced Astro Code Highlights

Astro includes Shiki for syntax highlighting. Here's how to optimize it for TypeScript snippets:

1. Update your `astro.config.mjs`:

```typescript
export default defineConfig({
  markdown: {
    shikiConfig: {
      themes: { light: "min-light", dark: "tokyo-night" },
      wrap: true,
    },
  },
});
```

2. Add a stylish border to your code blocks:

```css
pre:has(code) {
  @apply border border-skin-line;
}
```

## Adding Type Information to Code Blocks

To add type information to your code blocks, you can use TypeScript's built-in type annotations:

```typescript

interface User {
  name: string;
  age: number;
}

const user: User = {
  name: "John Doe",
  age: "30" // Type error: Type 'string' is not assignable to type 'number'
};

console.log(user.name);
```

You can also show type information inline:

```typescript
interface User {
  name: string;
  age: number;
}

const user: User = {
  name: "John Doe",
  age: 30
};

// The type of user.name is 'string'
const name = user.name;
```

## Benefits of Enhanced Astro Code Highlights

Your Astro site now includes:

- Advanced syntax highlighting
- Type information in code blocks
- Adaptive light and dark mode code blocks

These features enhance code readability and user experience, making your code examples more valuable to readers.

---

--- title: Vue 3.5's onWatcherCleanup: Mastering Side Effect Management in Vue Applications description: Discover how Vue 3.5's new onWatcherCleanup function revolutionizes side effect management in Vue applications tags: ['vue'] ---

# Vue 3.5's onWatcherCleanup: Mastering Side Effect Management in Vue Applications



## Introduction

My team and I discussed Vue 3.5's new features, focusing on the `onWatcherCleanup` function. The insights proved valuable enough to share in this blog post.

## The Side Effect Challenge in Vue

Managing side effects in Vue presents challenges when dealing with:

- API calls
- Timer operations
- Event listener management

These side effects become complex during frequent value changes.

## A Common Use Case: Fetching User Data

To illustrate the power of `onWatcherCleanup`, let's compare the old and new ways of fetching user data.

### The Old Way

```vue
<script setup lang="ts">
const userId = ref<string>('')
const userData = ref<any | null>(null)
let controller: AbortController | null = null

watch(userId, async (newId: string) => {
  if (controller) {
    controller.abort()
  }
  controller = new AbortController()

  try {
    const response = await fetch(`https://api.example.com/users/${newId}`, {
      signal: controller.signal
    })
    if (!response.ok) {
      throw new Error('User not found')
    }
    userData.value = await response.json()
  } catch (error) {
    if (error instanceof Error && error.name !== 'AbortError') {
      console.error('Fetch error:', error)
      userData.value = null
    }
  }
})
</script>

<template>
  <div>
    
    <div v-if="userData">
      <h2>User Data</h2>
      <pre>{{ JSON.stringify(userData, null, 2) }}</pre>
    </div>
    <div v-else-if="userId && !userData">
      User not found
    </div>
  </div>
</template>
```

Problems with this method:

1. External controller management
2. Manual request abortion
3. Cleanup logic separate from effect
4. Easy to forget proper cleanup

## The New Way: onWatcherCleanup

Here's how `onWatcherCleanup` improves the process:

```vue
<script setup lang="ts">
const userId = ref<string>('')
const userData = ref<any | null>(null)

watch(userId, async (newId: string) => {
  const controller = new AbortController()

  onWatcherCleanup(() => {
    controller.abort()
  })

  try {
    const response = await fetch(`https://api.example.com/users/${newId}`, {
      signal: controller.signal
    })
    if (!response.ok) {
      throw new Error('User not found')
    }
    userData.value = await response.json()
  } catch (error) {
    if (error instanceof Error && error.name !== 'AbortError') {
      console.error('Fetch error:', error)
      userData.value = null
    }
  }
})
</script>

<template>
  <div>
    
    <div v-if="userData">
      <h2>User Data</h2>
      <pre>{{ JSON.stringify(userData, null, 2) }}</pre>
    </div>
    <div v-else-if="userId && !userData">
      User not found
    </div>
  </div>
</template>
```

### Benefits of onWatcherCleanup

1. Clearer code: Cleanup logic is right next to the effect
2. Automatic execution
3. Fewer memory leaks
4. Simpler logic
5. Consistent with Vue API
6. Fits seamlessly into Vue's reactivity system

## When to Use onWatcherCleanup

Use it to:

- Cancel API requests
- Clear timers
- Remove event listeners
- Free resources

## Advanced Techniques

### Multiple Cleanups

```ts twoslash

watch(dependency, () => {
  const timer1 = setInterval(() => { /* ... */ }, 1000)
  const timer2 = setInterval(() => { /* ... */ }, 5000)

  onWatcherCleanup(() => clearInterval(timer1))
  onWatcherCleanup(() => clearInterval(timer2))

  // More logic...
})
```

### Conditional Cleanup

```ts twoslash

watch(dependency, () => {
  if (condition) {
    const resource = acquireResource()
    onWatcherCleanup(() => releaseResource(resource))
  }

  // More code...
})
```

### With watchEffect

```ts twoslash

watchEffect((onCleanup) => {
  const data = fetchSomeData()

  onCleanup(() => {
    cleanupData(data)
  })
})
```

## How onWatcherCleanup Works

![Image description](../../assets/images/onWatcherCleanup.png)

Vue uses a WeakMap to manage cleanup functions efficiently. This approach connects cleanup functions with their effects and triggers them at the right time.

### Executing Cleanup Functions

The system triggers cleanup functions in two scenarios:

1. Before the effect re-runs
2. When the watcher stops

This ensures proper resource management and side effect cleanup.

### Under the Hood

The `onWatcherCleanup` function integrates with Vue's reactivity system. It uses the current active watcher to associate cleanup functions with the correct effect, triggering cleanups in the right context.

## Performance

The `onWatcherCleanup` implementation prioritizes efficiency:

- The system creates cleanup arrays on demand
- WeakMap usage optimizes memory management
- Adding cleanup functions happens instantly

These design choices enhance your Vue applications' performance when handling watchers and side effects.

## Best Practices

1. Register cleanups at the start of your effect function
2. Keep cleanup functions simple and focused
3. Avoid creating new side effects within cleanup functions
4. Handle potential errors in your cleanup logic
5. Thoroughly test your effects and their associated cleanups

## Conclusion

Vue 3.5's `onWatcherCleanup` strengthens the framework's toolset for managing side effects. It enables cleaner, more maintainable code by unifying setup and teardown logic. This feature helps create robust applications that handle resource management effectively and prevent side effect-related bugs.

As you incorporate `onWatcherCleanup` into your projects, you'll discover how it simplifies common patterns and prevents bugs related to unmanaged side effects.

---

--- title: How to Build Your Own Vue-like Reactivity System from Scratch description: Learn to build a Vue-like reactivity system from scratch, implementing your own ref() and watchEffect().  tags: ['vue'] ---

# How to Build Your Own Vue-like Reactivity System from Scratch



## Introduction

Understanding the core of modern Frontend frameworks is crucial for every web developer. Vue, known for its reactivity system, offers a seamless way to update the DOM based on state changes. But have you ever wondered how it works under the hood?

In this tutorial, we'll demystify Vue's reactivity by building our own versions of `ref()` and `watchEffect()`. By the end, you'll have a deeper understanding of reactive programming in frontend development.

## What is Reactivity in Frontend Development?

Before we dive in, let's define reactivity:

> **Reactivity: A declarative programming model for updating based on state changes.**[^1]

[^1]: [What is Reactivity](https://www.pzuraq.com/blog/what-is-reactivity) by Pzuraq

This concept is at the heart of modern frameworks like Vue, React, and Angular. Let's see how it works in a simple Vue component:

```vue
<script setup>
const counter = ref(0)

const incrementCounter = () => {
  counter.value++
}
</script>

<template>
<div>
  <h1>Counter: {{ counter }}</h1>
  <button @click="incrementCounter">Increment</button>
</div>
</template>
```

In this example:

1. **State Management:** `ref` creates a reactive reference for the counter.
2. **Declarative Programming:** The template uses `{{ counter }}` to display the counter value. The DOM updates automatically when the state changes.

## Building Our Own Vue-like Reactivity System

To create a basic reactivity system, we need three key components:

1. A method to store data
2. A way to track changes
3. A mechanism to update dependencies when data changes

### Key Components of Our Reactivity System

1. A store for our data and effects
2. A dependency tracking system
3. An effect runner that activates when data changes

### Understanding Effects in Reactive Programming

An `effect` is a function that executes when a reactive state changes. Effects can update the DOM, make API calls, or perform calculations.

```ts twoslash
type Effect = () => void;
```

This `Effect` type represents a function that runs when a reactive state changes.

### The Store

We'll use a Map to store our reactive dependencies:

```ts twoslash
// ---cut-start---
type Effect = () => void;
// ---cut-end---

const depMap: Map<object, Map<string | symbol, Set<Effect>>> = new Map();
```

## Implementing Key Reactivity Functions

### The Track Function: Capturing Dependencies

This function records which effects depend on specific properties of reactive objects. It builds a dependency map to keep track of these relationships.

```ts twoslash
type Effect = () => void;

let activeEffect: Effect | null = null;

const depMap: Map<object, Map<string | symbol, Set<Effect>>> = new Map();

function track(target: object, key: string | symbol): void {
  if (!activeEffect) return;

  let dependenciesForTarget = depMap.get(target);
  if (!dependenciesForTarget) {
    dependenciesForTarget = new Map<string | symbol, Set<Effect>>();
    depMap.set(target, dependenciesForTarget);
  }

  let dependenciesForKey = dependenciesForTarget.get(key);
  if (!dependenciesForKey) {
    dependenciesForKey = new Set<Effect>();
    dependenciesForTarget.set(key, dependenciesForKey);
  }

  dependenciesForKey.add(activeEffect);
}
```

### The Trigger Function: Activating Effects

When a reactive property changes, this function activates all the effects that depend on that property. It uses the dependency map created by the track function.

```ts twoslash
// ---cut-start---
type Effect = () => void;
const depMap: Map<object, Map<string | symbol, Set<Effect>>> = new Map();
// ---cut-end---
function trigger(target: object, key: string | symbol): void {
  const depsForTarget = depMap.get(target);
  if (depsForTarget) {
    const depsForKey = depsForTarget.get(key);
    if (depsForKey) {
      depsForKey.forEach(effect => effect());
    }
  }
}
```

### Implementing ref: Creating Reactive References

This creates a reactive reference to a value. It wraps the value in an object with getter and setter methods that track access and trigger updates when the value changes.

```ts twoslash

class RefImpl<T> {
  private _value: T;

  constructor(value: T) {
    this._value = value;
  }

  get value(): T {
    track(this, 'value');
    return this._value;
  }

  set value(newValue: T) {
    if (newValue !== this._value) {
      this._value = newValue;
      trigger(this, 'value');
    }
  }
}

function ref<T>(initialValue: T): RefImpl<T> {
  return new RefImpl(initialValue);
}
```

### Creating watchEffect: Reactive Computations

This function creates a reactive computation. It executes the provided effect function and re-runs it whenever any reactive values used within the effect change.

```ts twoslash

function watchEffect(effect: Effect): void {
  function wrappedEffect() {
    activeEffect = wrappedEffect;
    effect();
    activeEffect = null;
  }

  wrappedEffect();
}
```

## Putting It All Together: A Complete Example

Let's see our reactivity system in action:

```ts twoslash

const countRef = ref(0);
const doubleCountRef = ref(0);

watchEffect(() => {
  console.log(`Ref count is: ${countRef.value}`);
});

watchEffect(() => {
  doubleCountRef.value = countRef.value * 2;
  console.log(`Double count is: ${doubleCountRef.value}`);
});

countRef.value = 1;
countRef.value = 2;
countRef.value = 3;

console.log('Final depMap:', depMap);
```

## Diagram for the complete workflow

![diagram for reactive workflow](../../assets/images/refFromScratch.png)

check out the full example -> [click](https://www.typescriptlang.org/play/?#code/C4TwDgpgBAogZnCBjYUC8UAUBKdA+KANwHsBLAEwG4BYAKDoBsJUBDFUwieRFALlgTJUAHygA7AK4MG6cVIY16tAPTKoAYQBOEFsGgsoAWRZgowYlADO57VHIRIY+2KSkIlqHGKaoOpAAsoYgAjACshADo6VSgAFX9oY1MAd1JpKH9iBnIgsKEPYH9dKABbEygAawgQAotLZg9iOF9BFEso2iRiMWs7ByT+JIAeEPCUABojEyHrTVIxAHMoUUsQEuCsyYBlZiHuITxD2TEIZKmwHEU6OAkXYFJus002CsxgFk0F5n5RoUmqkD8WbzJYrNYbBjYfgkChQADedCgUFIzUwbHunH2KFwCNoSKRXR6WQgEQYxAWmAA5LFnkgKiCWjxgLxKZN0RwuK1gNgrnj8UxUPYwJYAGLeWIfL6oDBCpIRKVvSXMHmI-EorAAQiFovFSu58NV+L6wrFmgln2Yx1O5xmwDmi2WVnBmygO2Aey5h0uhvxspMEXqwEVFuAk21pvNUpVfKRAF86D6BcadZoANLVWTh3Uh+XMTAA6NG9WYLUOFPpkA4n1IrNpjMYE5nN0epl4b0x31liN6gN5gFhrveCuF-HxpRG2sViIscjkNHsTFc6OqsdjhO0G53B5iJ6kBZfTTBqU-PITSrVIF2hlg9ZZKFEMg5XEE7qWYmk8lUml7g8MiBcjwvB8d4QxZSYQKlSZKQBMDz0rXkXx6QVBzNPVM36f0FQg5VFCRYta0jZUDQ7Qleknetk27HMFQLXC1VRcjK2Io0axQqcgJgNh-Ewf8mXwZiWMQt8mA-ClKRgAAPZAJHuB1eKEWD5OxOjBKUoMRyNWMNKgMc4zoNdOgYFhLA8AAlf8AEkSjABghliAhnygMA5kIXRoAAfVchgJAgfhYgQqBSLtCQUG8TAvJ8vyqw7QpSHaTyWG86AMAiiA6IMpEpSIRKfJwPyBKRO0Xjefw4qg1LKW00j3zJMSAHFmFkpZUtg2L4tS7TtGACRNB3NqIgSpL0vXJFA2ypLMEbAA1HLfLiaKi1RabZqgDU0AwfrBp8haWM21KrWSGahurQLXxqz9KTdJrxsi1lxFOI7tpU-Er33CBDza8rZsq57dJ0-T103dhHm0OA7LbeZSHuRLHrm2J73MuArJs8GBK6nqd0bKBEeRhhMEh6GGFh6MDKB+5HmSXQAixIM1P4Gn7xhJ9VTJ7coGSZ4wEgcgaZwAqoHZRc+IwDmTG5mnnrU9sjUFzlhbkaRhvHdnOfFrl2wMmJJJYaymCgCRLBYL5eHXILTtuYBEdkUHMAABmXTpX0FYgJGCJh1BdsRLf-a3-zth2ta5KAAEZ+AAGXJAoEhu6AmnNr3EboSngGp9W+bQBzVWqkTaswAADK2ugt5FLH4AASOEi4T-8IlS2M85Jh310DviACZ+DdDxyBdt2IA9i2rfMKBgmgbvXb1wpoH2uOq+9uAk6p-xefTzO+TH3v++ruBa5WjBZ8RnekqgAAqKBW7o7OSVzvOABEe712eS-LuF1-dz258Pnz68b3kYm-N77RLEnoyfIdB94132hgYOihwHb0gWfGB78D7wIAMy8nXKbM6OcLoinmIlY0Aw7p+jANGIAA)

## Beyond the Basics: What's Missing?

While our implementation covers the core concepts, production-ready frameworks like Vue offer more advanced features:

1. Handling of nested objects and arrays
2. Efficient cleanup of outdated effects
3. Performance optimizations for large-scale applications
4. Computed properties and watchers
5. Much more...

## Conclusion: Mastering Frontend Reactivity

By building our own `ref` and `watchEffect` functions, we've gained valuable insights into the reactivity systems powering modern frontend frameworks. We've covered:

* Creating reactive data stores with `ref`
* Tracking changes using the `track` function
* Updating dependencies with the `trigger` function
* Implementing reactive computations via `watchEffect`

This knowledge empowers you to better understand, debug, and optimize reactive systems in your frontend projects.

---

--- title: What is Local-first Web Development? description: Explore the power of local-first web development and its impact on modern web applications. Learn how to build offline-capable, user-centric apps that prioritize data ownership and seamless synchronization. Discover the key principles and implementation steps for creating robust local-first web apps using Vue. tags: ['local-first'] ---

# What is Local-first Web Development?



Imagine having complete control over your data in every web app, from social media platforms to productivity tools. Picture using these apps offline with automatic synchronization when you're back online. This is the essence of local-first web development – a revolutionary approach that puts users in control of their digital experience.

As browsers and devices become more powerful, we can now create web applications that minimize backend dependencies, eliminate loading delays, and overcome network errors. In this comprehensive guide, we'll dive into the fundamentals of local-first web development and explore its numerous benefits for users and developers alike.

## The Limitations of Traditional Web Applications

![Traditional Web Application](../../assets/images/what-is-local-first/tradidonal-web-app.png)

Traditional web applications rely heavily on backend servers for most operations. This dependency often results in:

- Frequent loading spinners during data saves
- Potential errors when the backend is unavailable
- Limited or no functionality when offline
- Data storage primarily in the cloud, reducing user ownership

While modern frameworks like Nuxt have improved initial load times through server-side rendering, many apps still suffer from performance issues post-load. Moreover, users often face challenges in accessing or exporting their data if an app shuts down.

## Core Principles of Local-First Development

Local-first development shares similarities with offline-first approaches but goes further in prioritizing user control and data ownership. Here are the key principles that define a true local-first web application:

1. **Instant Access:** Users can immediately access their work without waiting for data to load or sync.
2. **Device Independence:** Data is accessible across multiple devices seamlessly.
3. **Network Independence:** Basic tasks function without an internet connection.
4. **Effortless Collaboration:** The app supports easy collaboration, even in offline scenarios.
5. **Future-Proof Data:** User data remains accessible and usable over time, regardless of software changes.
6. **Built-In Security:** Security and privacy are fundamental design considerations.
7. **User Control:** Users have full ownership and control over their data.

It's important to note that some features, such as account deletion, may still require real-time backend communication to maintain data integrity.

For a deeper dive into local-first software principles, check out [Ink & Switch: Seven Ideals for Local-First Software](https://www.inkandswitch.com/local-first/#seven-ideals-for-local-first-software).

## Cloud vs Local-First Software Comparison

| Feature | Cloud Software 🌥️ | Local-First Software 💻 |
|---------|---------------|---------------------|
| Real-time Collaboration | 😟 Hard to implement | 😊 Built for real-time sync |
| Offline Support | 😟 Does not work offline | 😊 Works offline |
| Service Reliability | 😟 Service shuts down? Lose everything! | 😊 Users can continue using local copy of software + data |
| Service Implementation | 😟 Custom service for each app (infra, ops, on-call rotation, ...) | 😊 Sync service is generic → outsource to cloud vendor |

## Local-First Software Fit Guide

### ✅ Good Fit
* **File Editing** 📝 - text editors, word processors, spreadsheets, slides, graphics, video, music, CAD, Jupyter notebooks
* **Productivity** 📋 - notes, tasks, issues, calendar, time tracking, messaging, bookkeeping
* **Summary**: Ideal for apps where users freely manipulate their data

### ❌ Bad Fit
* **Money** 💰 - banking, payments, ad tracking
* **Physical Resources** 📦 - e-commerce, inventory
* **Vehicles** 🚗 - car sharing, freight, logistics
* **Summary**: Better with centralized cloud/server model for real-world resource management

## Types of Local-First Applications

Local-first applications can be categorized into two main types:

### 1. Local-Only Applications

![Local-Only Applications](../../assets/images/what-is-local-first/local-only.png)

While often mistakenly categorized as local-first, these are actually offline-first applications. They store data exclusively on the user's device without cloud synchronization, and data transfer between devices requires manual export and import processes. This approach, while simpler to implement, doesn't fulfill the core local-first principles of device independence and effortless collaboration. It's more accurately described as an offline-first architecture.

### 2. Sync-Enabled Applications

![Sync-Enabled Applications](../../assets/images/what-is-local-first/sync-enabled-applications.png)

These applications automatically synchronize user data with a cloud database, enhancing the user experience but introducing additional complexity for developers.

## Challenges in Implementing Sync-Enabled Local-First Apps

Developing sync-enabled local-first applications presents unique challenges, particularly in managing data conflicts. For example, in a collaborative note-taking app, offline edits by multiple users can lead to merge conflicts upon synchronization. Resolving these conflicts requires specialized algorithms and data structures, which we'll explore in future posts in this series.

Even for single-user applications, synchronizing local data with cloud storage demands careful consideration and additional logic.

## Building Local-First Web Apps: A Step-by-Step Approach

To create powerful local-first web applications, consider the following key steps, with a focus on Vue.js:

1. **Transform Your Vue SPA into a PWA**
   Convert your Vue Single Page Application (SPA) into a Progressive Web App (PWA) to enable native app-like interactions. For a detailed guide, see [Create a Native-Like App in 4 Steps: PWA Magic with Vue 3 and Vite](../create-pwa-vue3-vite-4-steps).

2. **Implement Robust Storage Solutions**
   Move beyond simple localStorage to more sophisticated storage mechanisms that support offline functionality and data persistence. Learn more in [How to Use SQLite in Vue 3: Complete Guide to Offline-First Web Apps](../sqlite-vue3-offline-first-web-apps-guide).

3. **Develop Syncing and Authentication Systems**
   For sync-enabled apps, implement user authentication and secure data synchronization across devices. Learn how to implement syncing and conflict resolution in [Building Local-First Apps with Vue and Dexie](/posts/building-local-first-apps-vue-dexie/).

4. **Prioritize Security Measures**
   Employ encryption techniques to protect sensitive user data stored in the browser.

We'll delve deeper into each of these topics throughout this series on local-first web development.

## Additional Resources for Local-First Development

To further your understanding of local-first applications, explore these valuable resources:

1. **Website:** [Local First Web](https://localfirstweb.dev/) - An excellent starting point with comprehensive follow-up topics.
2. **Podcast:** [Local First FM](https://www.localfirst.fm/) - An insightful podcast dedicated to local-first development.
3. **Community:** Join the [Local First Discord](https://discord.com/invite/ZRrwZxn4rW) to connect with fellow developers and enthusiasts.

## Conclusion: Embracing the Local-First Revolution

Local-first web development represents a paradigm shift in how we create and interact with web applications. By prioritizing user control, data ownership, and offline capabilities, we can build more resilient, user-centric apps that adapt to the evolving needs of modern users.

This introductory post marks the beginning of an exciting journey into the world of local-first development. Stay tuned for more in-depth articles exploring various aspects of building powerful, local-first web applications with Vue and other modern web technologies.

---

--- title: Vue Accessibility Blueprint: 8 Steps description: Master Vue accessibility with our comprehensive guide. Learn 8 crucial steps to create inclusive, WCAG-compliant web applications that work for all users. tags: ['vue', 'accessibility'] ---

# Vue Accessibility Blueprint: 8 Steps



Creating accessible Vue components is crucial for building inclusive web applications that work for everyone, including people with disabilities. This guide outlines 8 essential steps to improve the accessibility of your Vue projects and align them with Web Content Accessibility Guidelines (WCAG) standards.

## Why Accessibility Matters

Implementing accessible design in Vue apps:

- Expands your potential user base
- Enhances user experience
- Boosts SEO performance
- Reduces legal risks
- Demonstrates social responsibility

Now let's explore the 8 key steps for building accessible Vue components:

## 1. Master Semantic HTML

Using proper HTML structure and semantics provides a solid foundation for assistive technologies. Key actions:

- Use appropriate heading levels (h1-h6)
- Add ARIA attributes  
- Ensure form inputs have associated labels

```html
<header>
  <h1>Accessible Blog</h1>
  <nav aria-label="Main">
    <a href="#home">Home</a>
    <a href="#about">About</a>
  </nav>
</header>

<main>
  <article>
    <h2>Latest Post</h2>
    <p>Content goes here...</p>
  </article>

  <form>
    <label for="comment">Comment:</label>
    <textarea id="comment" name="comment"></textarea>
    <button type="submit">Post</button>
  </form>
</main>
```

Resource: [Vue Accessibility Guide](https://vuejs.org/guide/best-practices/accessibility.html)

## 2. Use eslint-plugin-vue-a11y

Add this ESLint plugin to detect accessibility issues during development:

```shell
npm install eslint-plugin-vuejs-accessibility --save-dev
```

Benefits:

- Automated a11y checks
- Consistent code quality
- Less manual testing needed

Resource: [eslint-plugin-vue-a11y GitHub](https://github.com/vue-a11y/eslint-plugin-vuejs-accessibility)

## 3. Test with Vue Testing Library

Adopt Vue Testing Library to write accessibility-focused tests:

```js
test('renders accessible button', () => {
  render(MyComponent)
  const button = screen.getByRole('button', { name: /submit/i })
  expect(button).toBeInTheDocument()
})
```

Resource: [Vue Testing Library Documentation](https://testing-library.com/docs/vue-testing-library/intro/)

## 4. Use Screen Readers

Test your app with screen readers like NVDA, VoiceOver or JAWS to experience it as visually impaired users do.

## 5. Run Lighthouse Audits

Use Lighthouse in Chrome DevTools or CLI to get comprehensive accessibility assessments.

Resource: [Google Lighthouse Documentation](https://developer.chrome.com/docs/lighthouse/overview/)

## 6. Consult A11y Experts

Partner with accessibility specialists to gain deeper insights and recommendations.

## 7. Integrate A11y in Workflows

Make accessibility a core part of planning and development:

- Include a11y requirements in user stories
- Set a11y acceptance criteria
- Conduct team WCAG training

## 8. Automate Testing with Cypress

Use Cypress with axe-core for automated a11y testing:

```js
describe('Home Page Accessibility', () => {
  beforeEach(() => {
    cy.visit('/')
    cy.injectAxe()
  })

  it('Has no detectable a11y violations', () => {
    cy.checkA11y()
  })
})
```

Resource: [Cypress Accessibility Testing Guide](https://docs.cypress.io/app/guides/accessibility-testing)

By following these 8 steps, you will enhance the accessibility of your Vue applications and create more inclusive web experiences. Remember that accessibility is an ongoing process - continually learn, test, and strive to make your apps usable by everyone.

---

--- title: How to Structure Vue Projects description: Discover best practices for structuring Vue projects of any size, from simple apps to complex enterprise solutions. tags: ['vue', 'architecture'] ---

# How to Structure Vue Projects



## Quick Summary
This post covers specific Vue project structures suited for different project sizes:
- Flat structure for small projects
- Atomic Design for scalable applications
- Modular approach for larger projects
- Feature Sliced Design for complex applications
- Micro frontends for enterprise-level solutions

## Table of Contents

## Introduction

When starting a Vue project, one of the most critical decisions you'll make is how to structure it. The right structure enhances scalability, maintainability, and collaboration within your team. This consideration aligns with **Conway's Law**:

> "Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations."  
> — Mel Conway

In essence, your Vue application's architecture will reflect your organization's structure, influencing how you should plan your project's layout.

![Diagram of Conway's Law](../../assets/images/how-to-structure-vue/conway.png)

Whether you're building a small app or an enterprise-level solution, this guide covers specific project structures suited to different scales and complexities.

---

## 1. Flat Structure: Perfect for Small Projects

Are you working on a small-scale Vue project or a proof of concept? A simple, flat folder structure might be the best choice to keep things straightforward and avoid unnecessary complexity.

```shell
/src
|-- /components
|   |-- BaseButton.vue
|   |-- BaseCard.vue
|   |-- PokemonList.vue
|   |-- PokemonCard.vue
|-- /composables
|   |-- usePokemon.js
|-- /utils
|   |-- validators.js
|-- /layout
|   |-- DefaultLayout.vue
|   |-- AdminLayout.vue
|-- /plugins
|   |-- translate.js
|-- /views
|   |-- Home.vue
|   |-- PokemonDetail.vue
|-- /router
|   |-- index.js
|-- /store
|   |-- index.js
|-- /assets
|   |-- /images
|   |-- /styles
|-- /tests
|   |-- ...
|-- App.vue
|-- main.js
```

### Pros and Cons

<div class="overflow-x-auto">
  <table class="custom-table">
    <thead>
      <tr>
        <th>✅ Pros</th>
        <th>❌ Cons</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Easy to implement</td>
        <td>Not scalable</td>
      </tr>
      <tr>
        <td>Minimal setup</td>
        <td>Becomes cluttered as the project grows</td>
      </tr>
      <tr>
        <td>Ideal for small teams or solo developers</td>
        <td>Lack of clear separation of concerns</td>
      </tr>
    </tbody>
  </table>
</div>

---

## 2. Atomic Design: Scalable Component Organization

![Atomic Design Diagram](../../assets/images/atomic/diagram.svg)

For larger Vue applications, Atomic Design provides a clear structure. This approach organizes components into a hierarchy from simplest to most complex.

### The Atomic Hierarchy

- **Atoms:** Basic elements like buttons and icons.
- **Molecules:** Groups of atoms forming simple components (e.g., search bars).
- **Organisms:** Complex components made up of molecules and atoms (e.g., navigation bars).
- **Templates:** Page layouts that structure organisms without real content.
- **Pages:** Templates filled with real content to form actual pages.

This method ensures scalability and maintainability, facilitating a smooth transition between simple and complex components.

```shell
/src
|-- /components
|   |-- /atoms
|   |   |-- AtomButton.vue
|   |   |-- AtomIcon.vue
|   |-- /molecules
|   |   |-- MoleculeSearchInput.vue
|   |   |-- MoleculePokemonThumbnail.vue
|   |-- /organisms
|   |   |-- OrganismPokemonCard.vue
|   |   |-- OrganismHeader.vue
|   |-- /templates
|   |   |-- TemplatePokemonList.vue
|   |   |-- TemplatePokemonDetail.vue
|-- /pages
|   |-- PageHome.vue
|   |-- PagePokemonDetail.vue
|-- /composables
|   |-- usePokemon.js
|-- /utils
|   |-- validators.js
|-- /layout
|   |-- LayoutDefault.vue
|   |-- LayoutAdmin.vue
|-- /plugins
|   |-- translate.js
|-- /router
|   |-- index.js
|-- /store
|   |-- index.js
|-- /assets
|   |-- /images
|   |-- /styles
|-- /tests
|   |-- ...
|-- App.vue
|-- main.js
```

### Pros and Cons

<div class="overflow-x-auto">
  <table class="custom-table">
    <thead>
      <tr>
        <th>✅ Pros</th>
        <th>❌ Cons</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Highly scalable</td>
        <td>Can introduce overhead in managing layers</td>
      </tr>
      <tr>
        <td>Organized component hierarchy</td>
        <td>Initial complexity in setting up</td>
      </tr>
      <tr>
        <td>Reusable components</td>
        <td>Might be overkill for smaller projects</td>
      </tr>
      <tr>
        <td>Improves collaboration among teams</td>
        <td></td>
      </tr>
    </tbody>
  </table>
</div>

<Aside type='info' title="Want to Learn More About Atomic Design?">
Check out my detailed blog post on [Atomic Design in Vue and Nuxt](../atomic-design-vue-or-nuxt).
</Aside>

---

## 3. Modular Approach: Feature-Based Organization

As your project scales, consider a **Modular Monolithic Architecture**. This structure encapsulates each feature or domain, enhancing maintainability and preparing for potential evolution towards microservices.

```shell
/src
|-- /core
|   |-- /components
|   |   |-- BaseButton.vue
|   |   |-- BaseIcon.vue
|   |-- /models
|   |-- /store
|   |-- /services
|   |-- /views
|   |   |-- DefaultLayout.vue
|   |   |-- AdminLayout.vue
|   |-- /utils
|   |   |-- validators.js
|-- /modules
|   |-- /pokemon
|   |   |-- /components
|   |   |   |-- PokemonThumbnail.vue
|   |   |   |-- PokemonCard.vue
|   |   |   |-- PokemonListTemplate.vue
|   |   |   |-- PokemonDetailTemplate.vue
|   |   |-- /models
|   |   |-- /store
|   |   |   |-- pokemonStore.js
|   |   |-- /services
|   |   |-- /views
|   |   |   |-- PokemonDetailPage.vue
|   |   |-- /tests
|   |   |   |-- pokemonTests.spec.js
|-- /search
|   |-- /components
|   |   |-- SearchInput.vue
|   |-- /models
|   |-- /store
|   |   |-- searchStore.js
|   |-- /services
|   |-- /views
|   |-- /tests
|   |   |-- searchTests.spec.js
|-- /assets
|   |-- /images
|   |-- /styles
|-- /scss
|-- App.vue
|-- main.ts
|-- router.ts
|-- store.ts
|-- /tests
|   |-- ...
|-- /plugins
|   |-- translate.js
```

### Alternative: Simplified Flat Feature Structure

A common pain point in larger projects is excessive folder nesting, which can make navigation and file discovery more difficult. Here's a simplified, flat feature structure that prioritizes IDE-friendly navigation and reduces cognitive load:

```shell
/features
|-- /project
|   |-- project.composable.ts
|   |-- project.data.ts
|   |-- project.store.ts
|   |-- project.types.ts
|   |-- project.utils.ts
|   |-- project.utils.test.ts
|   |-- ProjectList.vue
|   |-- ProjectItem.vue
```

This structure offers key advantages:
- **Quick Navigation**: Using IDE features like "Quick Open" (Ctrl/Cmd + P), you can find any project-related file by typing "project..."
- **Reduced Nesting**: All feature-related files are at the same level, eliminating deep folder hierarchies
- **Clear Ownership**: Each file's name indicates its purpose
- **Pattern Recognition**: Consistent naming makes it simple to understand each file's role
- **Test Colocation**: Tests live right next to the code they're testing

---

## 4. Feature-Sliced Design: For Complex Applications

**Feature-Sliced Design** is ideal for big, long-term projects. This approach breaks the application into different layers, each with a specific role.

![Feature-Sliced Design Diagram](../../assets/images/how-to-structure-vue/feature-sliced.png)

### Layers of Feature-Sliced Design

- **App:** Global settings, styles, and providers.
- **Processes:** Global business processes, like user authentication flows.
- **Pages:** Full pages built using entities, features, and widgets.
- **Widgets:** Combines entities and features into cohesive UI blocks.
- **Features:** Handles user interactions that add value.
- **Entities:** Represents core business models.
- **Shared:** Reusable utilities and components unrelated to specific business logic.

```shell
/src
|-- /app
|   |-- App.vue
|   |-- main.js
|   |-- app.scss
|-- /processes
|-- /pages
|   |-- Home.vue
|   |-- PokemonDetailPage.vue
|-- /widgets
|   |-- UserProfile.vue
|   |-- PokemonStatsWidget.vue
|-- /features
|   |-- pokemon
|   |   |-- CatchPokemon.vue
|   |   |-- PokemonList.vue
|   |-- user
|   |   |-- Login.vue
|   |   |-- Register.vue
|-- /entities
|   |-- user
|   |   |-- userService.js
|   |   |-- userModel.js
|   |-- pokemon
|   |   |-- pokemonService.js
|   |   |-- pokemonModel.js
|-- /shared
|   |-- ui
|   |   |-- BaseButton.vue
|   |   |-- BaseInput.vue
|   |   |-- Loader.vue
|   |-- lib
|   |   |-- api.js
|   |   |-- helpers.js
|-- /assets
|   |-- /images
|   |-- /styles
|-- /router
|   |-- index.js
|-- /store
|   |-- index.js
|-- /tests
|   |-- featureTests.spec.js
```

### Pros and Cons

<div class="overflow-x-auto">
  <table class="custom-table">
    <thead>
      <tr>
        <th>✅ Pros</th>
        <th>❌ Cons</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>High cohesion and clear separation</td>
        <td>Initial complexity in understanding the layers</td>
      </tr>
      <tr>
        <td>Scalable and maintainable</td>
        <td>Requires thorough planning</td>
      </tr>
      <tr>
        <td>Facilitates team collaboration</td>
        <td>Needs consistent enforcement of conventions</td>
      </tr>
    </tbody>
  </table>
</div>

<Aside type='tip' title="Learn More About Feature-Sliced Design">
Visit the [official Feature-Sliced Design documentation](https://feature-sliced.design/) for an in-depth understanding.
</Aside>

---

## 5. Micro Frontends: Enterprise-Level Solution

**Micro frontends** apply the microservices concept to frontend development. Teams can work on distinct sections of a web app independently, enabling flexible development and deployment.

![Micro Frontend Diagram](../../assets/images/how-to-structure-vue/microfrontend.png)

### Key Components

- **Application Shell:** The main controller handling basic layout and routing, connecting all micro frontends.
- **Decomposed UIs:** Each micro frontend focuses on a specific part of the application using its own technology stack.

### Pros and Cons

<div class="overflow-x-auto">
  <table class="custom-table">
    <thead>
      <tr>
        <th>✅ Pros</th>
        <th>❌ Cons</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Independent deployments</td>
        <td>High complexity in orchestration</td>
      </tr>
      <tr>
        <td>Scalability across large teams</td>
        <td>Requires robust infrastructure</td>
      </tr>
      <tr>
        <td>Technology-agnostic approach</td>
        <td>Potential inconsistencies in user experience</td>
      </tr>
    </tbody>
  </table>
</div>

<Aside type='caution' title="Note">
Micro frontends are best suited for large, complex projects with multiple development teams. This approach can introduce significant complexity and is usually not necessary for small to medium-sized applications.
</Aside>

---

## Conclusion

![Conclusion](../../assets/images/how-to-structure-vue/conclusion.png)

Selecting the right project structure depends on your project's size, complexity, and team organization. The more complex your team or project is, the more you should aim for a structure that facilitates scalability and maintainability.

Your project's architecture should grow with your organization, providing a solid foundation for future development.

### Comparison Chart

<div class="overflow-x-auto">
  <table class="custom-table">
    <thead>
      <tr>
        <th>Approach</th>
        <th>Description</th>
        <th>✅ Pros</th>
        <th>❌ Cons</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><strong>Flat Structure</strong></td>
        <td>Simple structure for small projects</td>
        <td>Easy to implement</td>
        <td>Not scalable, can become cluttered</td>
      </tr>
      <tr>
        <td><strong>Atomic Design</strong></td>
        <td>Hierarchical component-based structure</td>
        <td>Scalable, organized, reusable components</td>
        <td>Overhead in managing layers, initial complexity</td>
      </tr>
      <tr>
        <td><strong>Modular Approach</strong></td>
        <td>Feature-based modular structure</td>
        <td>Scalable, encapsulated features</td>
        <td>Potential duplication, requires discipline</td>
      </tr>
      <tr>
        <td><strong>Feature-Sliced Design</strong></td>
        <td>Functional layers and slices for large projects</td>
        <td>High cohesion, clear separation</td>
        <td>Initial complexity, requires thorough planning</td>
      </tr>
      <tr>
        <td><strong>Micro Frontends</strong></td>
        <td>Independent deployments of frontend components</td>
        <td>Independent deployments, scalable</td>
        <td>High complexity, requires coordination between teams</td>
      </tr>
    </tbody>
  </table>
</div>

---

## General Rules and Best Practices

Before concluding, let's highlight some general rules you can apply to every structure. These guidelines are important for maintaining consistency and readability in your codebase.

### Base Component Names

Use a prefix for your UI components to distinguish them from other components.

**Bad:**

```shell
components/
|-- MyButton.vue
|-- VueTable.vue
|-- Icon.vue
```

**Good:**

```shell
components/
|-- BaseButton.vue
|-- BaseTable.vue
|-- BaseIcon.vue
```

### Related Component Names

Group related components together by naming them accordingly.

**Bad:**

```shell
components/
|-- TodoList.vue
|-- TodoItem.vue
|-- TodoButton.vue
```

**Good:**

```shell
components/
|-- TodoList.vue
|-- TodoListItem.vue
|-- TodoListItemButton.vue
```

### Order of Words in Component Names

Component names should start with the highest-level words and end with descriptive modifiers.

**Bad:**

```shell
components/
|-- ClearSearchButton.vue
|-- ExcludeFromSearchInput.vue
|-- LaunchOnStartupCheckbox.vue
```

**Good:**

```shell
components/
|-- SearchButtonClear.vue
|-- SearchInputExclude.vue
|-- SettingsCheckboxLaunchOnStartup.vue
```

### Organizing Tests

Decide whether to keep your tests in a separate folder or alongside your components. Both approaches are valid, but consistency is key.

#### Approach 1: Separate Test Folder

```shell
/vue-project
|-- src
|   |-- components
|   |   |-- MyComponent.vue
|   |-- views
|   |   |-- HomeView.vue
|-- tests
|   |-- components
|   |   |-- MyComponent.spec.js
|   |-- views
|   |   |-- HomeView.spec.js
```

#### Approach 2: Inline Test Files

```shell
/vue-project
|-- src
|   |-- components
|   |   |-- MyComponent.vue
|   |   |-- MyComponent.spec.js
|   |-- views
|   |   |-- HomeView.vue
|   |   |-- HomeView.spec.js
```
---

## Additional Resources

- [Official Vue.js Style Guide](https://vuejs.org/style-guide/)
- [Micro Frontends - Extending Microservice Ideas to Frontend Development](https://micro-frontends.org/)
- [Martin Fowler on Micro Frontends](https://martinfowler.com/articles/micro-frontends.html)
- [Official Feature-Sliced Design Documentation](https://feature-sliced.design/)

---

---

--- title: How to Persist User Data with LocalStorage in Vue description: Learn how to efficiently store and manage user preferences like dark mode in Vue applications using LocalStorage. This guide covers basic operations, addresses common challenges, and provides type-safe solutions for robust development. tags: ['vue'] ---

# How to Persist User Data with LocalStorage in Vue



## Introduction

When developing apps, there's often a need to store data. Consider a simple scenario where your application features a dark mode, and users want to save their preferred setting. Most users might prefer dark mode, but some will want light mode. This raises the question: where should we store this preference? We could use an API with a backend to store the setting. For configurations that affect the client's experience, persisting this data locally makes more sense. LocalStorage offers a straightforward solution. In this blog post, I'll guide you through using LocalStorage in Vue and show you how to handle this data in an elegant and type-safe manner.

## Understanding LocalStorage

LocalStorage is a web storage API that lets JavaScript sites store and access data directly in the browser indefinitely. This data remains saved across browser sessions. LocalStorage is straightforward, using a key-value store model where both the key and the value are strings.

Here's how you can use LocalStorage:

- To **store** data: `localStorage.setItem('myKey', 'myValue')`
- To **retrieve** data: `localStorage.getItem('myKey')`
- To **remove** an item: `localStorage.removeItem('myKey')`
- To **clear** all storage: `localStorage.clear()`

![Diagram that explains LocalStorage](../../assets/images/localstorage-vue/diagram.png)

## Using LocalStorage for Dark Mode Settings

In Vue, you can use LocalStorage to save a user's preference for dark mode in a component.

![Picture that shows a button where user can toggle dark mode](../../assets/images/localstorage-vue/picture-dark-mode.png)

```vue

<template>
  <button class="dark-mode-toggle" @click="toggleDarkMode">
    {{ isDarkMode ? 'Switch to Light Mode' : 'Switch to Dark Mode' }}
    
  </button>
</template>

<script setup lang="ts">
const isDarkMode = ref(JSON.parse(localStorage.getItem('darkMode') ?? 'false'))

const styleProperties = computed(() => ({
  '--background-color': isDarkMode.value ? '#333' : '#FFF',
  '--text-color': isDarkMode.value ? '#FFF' : '#333'
}))

const sunIcon = `<svg some svg </svg>`

const moonIcon = `<svg some svg </svg>`

function applyStyles () {
  for (const [key, value] of Object.entries(styleProperties.value)) {
    document.documentElement.style.setProperty(key, value)
  }
}

function toggleDarkMode () {
  isDarkMode.value = !isDarkMode.value
  localStorage.setItem('darkMode', JSON.stringify(isDarkMode.value))
  applyStyles()
}

// On component mount, apply the stored or default styles
onMounted(applyStyles)
</script>

<style scoped>
.dark-mode-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 20px;
  font-size: 16px;
  color: var(--text-color);
  background-color: var(--background-color);
  border: 1px solid var(--text-color);
  border-radius: 5px;
  cursor: pointer;
}

.icon {
  display: inline-block;
  margin-left: 10px;
}

:root {
  --background-color: #FFF;
  --text-color: #333;
}

body {
  background-color: var(--background-color);
  color: var(--text-color);
  transition: background-color 0.3s, color 0.3s;
}
</style>

```

## Addressing Issues with Initial Implementation

The basic approach works well for simple cases, but larger applications face these key challenges:

1. **Type Safety and Key Validation**: Always check and handle data from LocalStorage to prevent errors.
2. **Decoupling from LocalStorage**: Avoid direct LocalStorage interactions in your components. Instead, use a utility service or state management for better code maintenance and testing.
3. **Error Handling**: Manage exceptions like browser restrictions or storage limits properly as LocalStorage operations can fail.
4. **Synchronization Across Components**: Use event-driven communication or shared state to keep all components updated with changes.
5. **Serialization Constraints**: LocalStorage stores data as strings, making serialization and deserialization challenging with complex data types.

## Solutions and Best Practices for LocalStorage

To overcome these challenges, consider these solutions:

- **Type Definitions**: Use TypeScript to enforce type safety and help with autocompletion.

```ts twoslash
// types/localStorageTypes.ts
export type UserSettings = {name: string}

export type LocalStorageValues = {
    darkMode: boolean,
    userSettings: UserSettings,
    lastLogin: Date,
}

export type LocalStorageKeys = keyof LocalStorageValues
```

- **Utility Classes**: Create a utility class to manage all LocalStorage operations.

```ts twoslash
// ---cut-start---
export type UserSettings = {name: string}

export type LocalStorageValues = {
    darkMode: boolean,
    userSettings: UserSettings,
    lastLogin: Date,
}

export type LocalStorageKeys = keyof LocalStorageValues
// ---cut-end---
// utils/LocalStorageHandler.ts
// export class LocalStorageHandler {
  static getItem<K extends LocalStorageKeys>(
    key: K
  ): LocalStorageValues[K] | null {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) as LocalStorageValues[K] : null;
    } catch (error) {
      console.error(`Error retrieving item from localStorage: ${error}`);
      return null;
    }
  }

  static setItem<K extends LocalStorageKeys>(
    key: K,
    value: LocalStorageValues[K]
  ): void {
    try {
      const item = JSON.stringify(value);
      localStorage.setItem(key, item);
    } catch (error) {
      console.error(`Error setting item in localStorage: ${error}`);
    }
  }

  static removeItem(key: LocalStorageKeys): void {
    localStorage.removeItem(key);
  }

  static clear(): void {
    localStorage.clear();
  }
}
```

- **Composables**: Extract logic into Vue composables for better reusability and maintainability

```ts

// composables/useDarkMode.ts
export function useDarkMode() {
    const isDarkMode = ref(LocalStorageHandler.getItem('darkMode') ?? false);

    watch(isDarkMode, (newValue) => {
        LocalStorageHandler.setItem('darkMode', newValue);
    });

    return { isDarkMode };
}
```

![Diagram that shows how component and localStorage work together](../../assets/images/localstorage-vue/diagram-local-storage-and-component.png)

You can check the full refactored example out here

[Play with Vue on Vue Playground](https://play.vuejs.org/#eNq9WG1v20YS/itz6gGSAXFFUu88O7m0lyBpnKY49/qlKhCaXEmMKS6xXEqWff7vfXZJSiSluAkKVLEYcnZenpmdnRnqsfMqTdk25x2vc6n4Jo19xV8sEqLL21wpkVAQ+1l2teiEvryzNiLklhKrVcwXHfp3EEfBHdYKyn/A8QEMi45RQPT4SFFWUekldW92kQrWpARdR6u1Ik3vkldf0Owl/empUHOZpf4RRxSIBLa31lptYv1ct7ARInkHBujMcnMH1kHhz6BwCA+Xg5qneMwCGaWKMq7ylGI/WWmXMuNGtEmFVPRIgdikueJhn0TyQeQJbumJllJsqIvwdWuseXYIxYGFDWrU7r+0WYDLNHvNgSe6qkv3Lo58mdrH/GcpUi5VxDMwVoh6vQu6ekG9R+1l17Ju/eBuJQExtAIRC9n1aibY1o9zsxffDYdDE/vv3rx50+2Xworfq+fFNLcR0/KL5OmiDrKIOcB9usy2K7rfxInes7VSqTcY7HY7thsyIVcD17btAViwPbsoVGswuSM8rLlOjOppGcV6i4NcSp6oHzQsUKtMuI3oNrJgU6dDxHffi3tQbbLJmeCvTMPL1FdrCrHyYUYjNnL8IRvPyVzAiX82TZkzKyglWS/YliY/QMrx2ZiNS7K+3TpsXKNZYP4VpFc1Nkg9bHDjfoXs1mrSwGex8cNmYk3a0usWJ75vnVFTYyltOS7ZdUguzd62pC3n7QnAh82cDecTGjPHbtqf2jOyY4fZCC8u1RpiaOk1/Y3hij0xl6YhvfjwYcic0QRBno1Hp5qvR2zujGB3fFb1dSEMZycNzKVuoNZa5sydN10qdCNIGjYSoG7523C3pfE9yp4NibmYiJ2oLnA9LDq6PF3qs/Di0/EkHQrZ33mUtNGvPUs66YbkOAh3wGY28piNXBcb61oIFoLqTF1rxCbOyGKT6VxnuAnCfDSxXDaezsA2moxxPx/W9gsBH09mzJ06r8bMdofIBn01SzTH7k7HATAx22HD6Qg38yGbT4fksok7q6lBJk+mUGPSaTgrr8XSiLnjKQzbE/hwtOtOptiu+emOLPMkUBH2wk/TeH+jC3FGKLqm4C6FpF6xZ7/d8X2fTKX8ncSSPt5+5oFiCLdExe61KnhRUi9KNUShCPINeFl18zrm5tnIMTSnUnbfO9pB7SVCm8RfDWezHR+gnpTzK/pHm6b5YhH48Y0S0l8Zu+/QLXtd3f9N8+rTjzcffwIsGSWraLnvtXXojkD1YOk+ZhAOBvQRnRyNSyRwDVmORtovWEmtObqckGisiGnIl34el30vWySHrturKYZe7JPp3mUn12TKAgQqBIW1h5YiEGGUofvvPVrG/B69GGDjaJVYERzNPAoAjUtD/5xnCi6iI4KUKEwVqR9w65arHeeJYUn9MEQgPHLs9J5cXAx5CQkrix44FiYlzfRVDzsne/VOe2EW22274mvTS24hQw4eBzYzEUfhl7QaPkv6YZTDtXGFJJeZNpGKqPTV7A/Tw1UrRlESRwlcRlbcGdmNL1dRYsV8iXhopytpTwqBgUbznML2SE8ORkEdJciYIyoNtyLcFwq+LRrPBlZJP8kifTC8E7Vks2HWL+TNfYEESaUTCRnU6WMSRFCW0Yp9zkSCMdngQyVFFkcxlx9TrRrToled5EXHj2Ox+9HQlMy5Ga6MzJoHd2fon7N7TVt0fpY843KLEfqwphBurorl1zc/wbnaIlI716P4M4v/5ciPXGMs2L6H84Bd4zNo35npFYn8S/b6HrmeVU5poKbKGP5FB8PuD8+4foSL+l5VJ0TxulZUftmnqH8qQzD5vRmaFSj0P7h+w5UGoefbx8Tf4PQUdcakR525ru9XXXWMSFlKy3KE/RYi5n5SuorR+mDAa5grGdAN1bVAdnt4D1F6f561+57vtVWUY1T7U0Atr9/6JvCF34eXhba+/jnPjm8RJ2Es3iVKhKadNxSURqvIZMpXUUDYIV3UL98TMoYnYVNGw3ihm4xH7y+8M3h+e/87/Z+SPI4rvfqjZHl2j5+iL+qyijA12kqJQFspTunxI/EaJpNC6mXRa1JfZrynKRfkN8EeEXkGUU3ZEwW+fqvscSlRDM6BQ3Yws9r79Fr/p42jWW+REwUAE/c6co/++Wgknj59AXgbRXFrEqm2BWVf/YotKFv9FzYCG7QVKP/fsBGt9l307JYvZ2cAM3eYXfiLUYZCfewKQFHyNQH+Qhgl34gtr9A1Y6SDeCY8Ddea8pXBtpUARUT2/kxXyXXUoete7XW+dfIlX/ZpZ2JX/yEB4meLQ3WSz9eCcrVRDQ7zYOMnhQp+mRLHHx+uNKLeuYpVHdbjDHhBL1/S0o8zkziFQuNKbRjsUy/hO5Oo5geKWtjOGTk3aB7kq5gerZWHrfnzie7enac/AMi2358=)

## Conclusion

This post explained the effective use of LocalStorage in Vue to manage user settings such as dark mode. We covered its basic operations, addressed common issues, and provided solutions to ensure robust and efficient application development. With these strategies, developers can create more responsive applications that effectively meet user needs.

---

--- title: How to Write Clean Vue Components description: There are many ways to write better Vue components. One of my favorite ways is to separate business logic into pure functions. tags: ['vue', 'architecture'] ---

# How to Write Clean Vue Components



## Table of Contents

## Introduction

Writing code that's both easy to test and easy to read can be a challenge, with Vue components. In this blog post, I'm going to share a design idea that will make your Vue components better. This method won't speed up your code, but it will make it simpler to test and understand. Think of it as a big-picture way to improve your Vue coding style. It's going to make your life easier when you need to fix or update your components.

Whether you're new to Vue or have been using it for some time, this tip will help you make your Vue components cleaner and more straightforward.

---

## Understanding Vue Components

A Vue component is like a reusable puzzle piece in your app. It has three main parts:

1. **View**: This is the template section where you design the user interface.
2. **Reactivity**: Here, Vue's features like `ref` make the interface interactive.
3. **Business Logic**: This is where you process data or manage user actions.

![Architecture](../../assets/images/how-to-write-clean-vue-components/architecture.png)

---

## Case Study: `snakeGame.vue`

Let's look at a common Vue component, `snakeGame.vue`. It mixes the view, reactivity, and business logic, which can make it complex and hard to work with.

### Code Sample: Traditional Approach

```vue
<template>
  <div class="game-container">
    <canvas ref="canvas" width="400" height="400"></canvas>
  </div>
</template>

<script setup lang="ts">
const canvas = ref<HTMLCanvasElement | null>(null);
const ctx = ref<CanvasRenderingContext2D | null>(null);
let snake = [{ x: 200, y: 200 }];
let direction = { x: 0, y: 0 };
let lastDirection = { x: 0, y: 0 };
let food = { x: 0, y: 0 };
const gridSize = 20;
let gameInterval: number | null = null;

onMounted(() => {
  if (canvas.value) {
    ctx.value = canvas.value.getContext("2d");
    resetFoodPosition();
    gameInterval = window.setInterval(gameLoop, 100);
  }
  window.addEventListener("keydown", handleKeydown);
});

onUnmounted(() => {
  if (gameInterval !== null) {
    window.clearInterval(gameInterval);
  }
  window.removeEventListener("keydown", handleKeydown);
});

function handleKeydown(e: KeyboardEvent) {
  e.preventDefault();
  switch (e.key) {
    case "ArrowUp":
      if (lastDirection.y !== 0) break;
      direction = { x: 0, y: -gridSize };
      break;
    case "ArrowDown":
      if (lastDirection.y !== 0) break;
      direction = { x: 0, y: gridSize };
      break;
    case "ArrowLeft":
      if (lastDirection.x !== 0) break;
      direction = { x: -gridSize, y: 0 };
      break;
    case "ArrowRight":
      if (lastDirection.x !== 0) break;
      direction = { x: gridSize, y: 0 };
      break;
  }
}

function gameLoop() {
  updateSnakePosition();
  if (checkCollision()) {
    endGame();
    return;
  }
  checkFoodCollision();
  draw();
  lastDirection = { ...direction };
}

function updateSnakePosition() {
  for (let i = snake.length - 2; i >= 0; i--) {
    snake[i + 1] = { ...snake[i] };
  }
  snake[0].x += direction.x;
  snake[0].y += direction.y;
}

function checkCollision() {
  return (
    snake[0].x < 0 ||
    snake[0].x >= 400 ||
    snake[0].y < 0 ||
    snake[0].y >= 400 ||
    snake
      .slice(1)
      .some(segment => segment.x === snake[0].x && segment.y === snake[0].y)
  );
}

function checkFoodCollision() {
  if (snake[0].x === food.x && snake[0].y === food.y) {
    snake.push({ ...snake[snake.length - 1] });
    resetFoodPosition();
  }
}

function resetFoodPosition() {
  food = {
    x: Math.floor(Math.random() * 20) * gridSize,
    y: Math.floor(Math.random() * 20) * gridSize,
  };
}

function draw() {
  if (!ctx.value) return;
  ctx.value.clearRect(0, 0, 400, 400);
  drawGrid();
  drawSnake();
  drawFood();
}

function drawGrid() {
  if (!ctx.value) return;
  ctx.value.strokeStyle = "#ddd";
  for (let i = 0; i <= 400; i += gridSize) {
    ctx.value.beginPath();
    ctx.value.moveTo(i, 0);
    ctx.value.lineTo(i, 400);
    ctx.value.stroke();
    ctx.value.moveTo(0, i);
    ctx.value.lineTo(400, i);
    ctx.value.stroke();
  }
}

function drawSnake() {
  if (!ctx.value) return;
  ctx.value.fillStyle = "green";
  snake.forEach(segment => {
    ctx.value?.fillRect(segment.x, segment.y, gridSize, gridSize);
  });
}

function drawFood() {
  if (!ctx.value) return;
  ctx.value.fillStyle = "red";
  ctx.value.fillRect(food.x, food.y, gridSize, gridSize);
}

function endGame() {
  if (gameInterval !== null) {
    window.clearInterval(gameInterval);
  }
  alert("Game Over");
}
</script>

<style>
.game-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
}
</style>
```

### Screenshot from the game

![Snake Game Screenshot](./../../assets/images/how-to-write-clean-vue-components/snakeGameImage.png)

### Challenges with the Traditional Approach

When you mix the view, reactivity, and business logic all in one file, the component becomes bulky and hard to maintain. Unit tests become more complex, requiring integration tests for comprehensive coverage.

---

## Introducing the Functional Core, Imperative Shell Pattern

To solve these problems in Vue, we use the "Functional Core, Imperative Shell" pattern. This pattern is key in software architecture and helps you structure your code better:

> **Functional Core, Imperative Shell Pattern**: In this design, the main logic of your app (the 'Functional Core') stays pure and without side effects, making it testable. The 'Imperative Shell' handles the outside world, like the UI or databases, and talks to the pure core.

![Functional core Diagram](./../../assets/images/how-to-write-clean-vue-components/functional-core-diagram.png)

### What Are Pure Functions?

In this pattern, **pure functions** are at the heart of the 'Functional Core'. A pure function is a concept from functional programming, and it has two key characteristics:

1. **Predictability**: If you give a pure function the same inputs, it always gives back the same output.
2. **No Side Effects**: Pure functions don't change anything outside them. They don't alter external variables, call APIs, or do any input/output.

Pure functions simplify testing, debugging, and code comprehension. They form the foundation of the Functional Core, keeping your app's business logic clean and manageable.

---

### Applying the Pattern in Vue

In Vue, this pattern has two parts:

- **Imperative Shell** (`useGameSnake.ts`): This part handles the Vue-specific reactive bits. It's where your components interact with Vue, managing operations like state changes and events.
- **Functional Core** (`pureGameSnake.ts`): This is where your pure business logic lives. It's separate from Vue, which makes it easier to test and think about your app's main functions, independent of the UI.

---

### Implementing `pureGameSnake.ts`

The `pureGameSnake.ts` file encapsulates the game's business logic without any Vue-specific reactivity. This separation means easier testing and clearer logic.

```typescript
export const gridSize = 20;

interface Position {
  x: number;
  y: number;
}

type Snake = Position[];

export function initializeSnake(): Snake {
  return [{ x: 200, y: 200 }];
}

export function moveSnake(snake: Snake, direction: Position): Snake {
  return snake.map((segment, index) => {
    if (index === 0) {
      return { x: segment.x + direction.x, y: segment.y + direction.y };
    }
    return { ...snake[index - 1] };
  });
}

export function isCollision(snake: Snake): boolean {
  const head = snake[0];
  return (
    head.x < 0 ||
    head.x >= 400 ||
    head.y < 0 ||
    head.y >= 400 ||
    snake.slice(1).some(segment => segment.x === head.x && segment.y === head.y)
  );
}

export function randomFoodPosition(): Position {
  return {
    x: Math.floor(Math.random() * 20) * gridSize,
    y: Math.floor(Math.random() * 20) * gridSize,
  };
}

export function isFoodEaten(snake: Snake, food: Position): boolean {
  const head = snake[0];
  return head.x === food.x && head.y === food.y;
}
```

### Implementing `useGameSnake.ts`

In `useGameSnake.ts`, we manage the Vue-specific state and reactivity, leveraging the pure functions from `pureGameSnake.ts`.

```typescript

interface Position {
  x: number;
  y: number;
}

type Snake = Position[];

interface GameState {
  snake: Ref<Snake>;
  direction: Ref<Position>;
  food: Ref<Position>;
  gameState: Ref<"over" | "playing">;
}

export function useGameSnake(): GameState {
  const snake: Ref<Snake> = ref(GameLogic.initializeSnake());
  const direction: Ref<Position> = ref({ x: 0, y: 0 });
  const food: Ref<Position> = ref(GameLogic.randomFoodPosition());
  const gameState: Ref<"over" | "playing"> = ref("playing");
  let gameInterval: number | null = null;

  const startGame = (): void => {
    gameInterval = window.setInterval(() => {
      snake.value = GameLogic.moveSnake(snake.value, direction.value);

      if (GameLogic.isCollision(snake.value)) {
        gameState.value = "over";
        if (gameInterval !== null) {
          clearInterval(gameInterval);
        }
      } else if (GameLogic.isFoodEaten(snake.value, food.value)) {
        snake.value.push({ ...snake.value[snake.value.length - 1] });
        food.value = GameLogic.randomFoodPosition();
      }
    }, 100);
  };

  onMounted(startGame);

  onUnmounted(() => {
    if (gameInterval !== null) {
      clearInterval(gameInterval);
    }
  });

  return { snake, direction, food, gameState };
}
```

### Refactoring `gameSnake.vue`

Now, our `gameSnake.vue` is more focused, using `useGameSnake.ts` for managing state and reactivity, while the view remains within the template.

```vue
<template>
  <div class="game-container">
    <canvas ref="canvas" width="400" height="400"></canvas>
  </div>
</template>

<script setup lang="ts">

const { snake, direction, food, gameState } = useGameSnake();
const canvas = ref<HTMLCanvasElement | null>(null);
const ctx = ref<CanvasRenderingContext2D | null>(null);
let lastDirection = { x: 0, y: 0 };

onMounted(() => {
  if (canvas.value) {
    ctx.value = canvas.value.getContext("2d");
    draw();
  }
  window.addEventListener("keydown", handleKeydown);
});

onUnmounted(() => {
  window.removeEventListener("keydown", handleKeydown);
});

watch(gameState, state => {
  if (state === "over") {
    alert("Game Over");
  }
});

function handleKeydown(e: KeyboardEvent) {
  e.preventDefault();
  switch (e.key) {
    case "ArrowUp":
      if (lastDirection.y !== 0) break;
      direction.value = { x: 0, y: -gridSize };
      break;
    case "ArrowDown":
      if (lastDirection.y !== 0) break;
      direction.value = { x: 0, y: gridSize };
      break;
    case "ArrowLeft":
      if (lastDirection.x !== 0) break;
      direction.value = { x: -gridSize, y: 0 };
      break;
    case "ArrowRight":
      if (lastDirection.x !== 0) break;
      direction.value = { x: gridSize, y: 0 };
      break;
  }
  lastDirection = { ...direction.value };
}

watch(
  [snake, food],
  () => {
    draw();
  },
  { deep: true }
);

function draw() {
  if (!ctx.value) return;
  ctx.value.clearRect(0, 0, 400, 400);
  drawGrid();
  drawSnake();
  drawFood();
}

function drawGrid() {
  if (!ctx.value) return;
  ctx.value.strokeStyle = "#ddd";
  for (let i = 0; i <= 400; i += gridSize) {
    ctx.value.beginPath();
    ctx.value.moveTo(i, 0);
    ctx.value.lineTo(i, 400);
    ctx.value.stroke();
    ctx.value.moveTo(0, i);
    ctx.value.lineTo(400, i);
    ctx.value.stroke();
  }
}

function drawSnake() {
  ctx.value.fillStyle = "green";
  snake.value.forEach(segment => {
    ctx.value.fillRect(segment.x, segment.y, gridSize, gridSize);
  });
}

function drawFood() {
  ctx.value.fillStyle = "red";
  ctx.value.fillRect(food.value.x, food.value.y, gridSize, gridSize);
}
</script>

<style>
.game-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
}
</style>
```

---

## Advantages of the Functional Core, Imperative Shell Pattern

The Functional Core, Imperative Shell pattern enhances the **testability** and **maintainability** of Vue components. By separating the business logic from the framework-specific code, this pattern offers key advantages:

### Simplified Testing

Business logic combined with Vue's reactivity and component structure makes testing complex. Traditional unit testing becomes challenging, leading to integration tests that lack precision. By extracting the core logic into pure functions (as in `pureGameSnake.ts`), we write focused unit tests for each function. This isolation streamlines testing, as each piece of logic operates independently of Vue's reactivity system.

### Enhanced Maintainability

The Functional Core, Imperative Shell pattern creates a clear **separation of concerns**. Vue components focus on the user interface and reactivity, while the pure business logic lives in separate, framework-agnostic files. This separation improves code readability and understanding. Maintenance becomes straightforward as the application grows.

### Framework Agnosticism

A key advantage of this pattern is the **portability** of your business logic. The pure functions in the Functional Core remain independent of any UI framework. If you need to switch from Vue to another framework, or if Vue changes, your core logic remains intact. This flexibility protects your code against changes and shifts in technology.

## Testing Complexities in Traditional Vue Components vs. Functional Core, Imperative Shell Pattern

### Challenges in Testing Traditional Components

Testing traditional Vue components, where view, reactivity, and business logic combine, presents specific challenges. In such components, unit tests face these obstacles:

- Tests function more like integration tests, reducing precision
- Vue's reactivity system creates complex mocking requirements
- Test coverage must span reactive behavior and side effects

These challenges reduce confidence in tests and component stability.

### Simplified Testing with Functional Core, Imperative Shell Pattern

The Functional Core, Imperative Shell pattern transforms testing:

- **Isolated Business Logic**: Pure functions in the Functional Core enable direct unit tests without Vue's reactivity or component states.
- **Predictable Outcomes**: Pure functions deliver consistent outputs for given inputs.
- **Clear Separation**: The reactive and side-effect code stays in the Imperative Shell, enabling focused testing of Vue interactions.

This approach creates a modular, testable codebase where each component undergoes thorough testing, improving reliability.

---

### Conclusion

The Functional Core, Imperative Shell pattern strengthens Vue applications through improved testing and maintenance. It prepares your code for future changes and growth. While restructuring requires initial effort, the pattern delivers long-term benefits, making it valuable for Vue developers aiming to enhance their application's architecture and quality.

![Blog Conclusion Diagram](./../../assets/images/how-to-write-clean-vue-components/conclusionDiagram.png)

---

--- title: The Problem with as in TypeScript: Why It's a Shortcut We Should Avoid description: Learn why as can be a Problem in Typescript tags: ['typescript'] ---

# The Problem with as in TypeScript: Why It's a Shortcut We Should Avoid



### Introduction: Understanding TypeScript and Its Challenges

TypeScript enhances JavaScript by adding stricter typing rules. While JavaScript's flexibility enables rapid development, it can also lead to runtime errors such as "undefined is not a function" or type mismatches. TypeScript aims to catch these errors during development.

The as keyword in TypeScript creates specific challenges with type assertions. It allows developers to override TypeScript's type checking, reintroducing the errors TypeScript aims to prevent. When developers assert an any type with a specific interface, runtime errors occur if the object doesn't match the interface. In codebases, frequent use of as indicates underlying design issues or incomplete type definitions.

The article will examine the pitfalls of overusing as and provide guidelines for more effective TypeScript development, helping developers leverage TypeScript's strengths while avoiding its potential drawbacks. Readers will explore alternatives to as, such as type guards and generics, and learn when type assertions make sense.

### Easy Introduction to TypeScript's `as` Keyword

TypeScript is a special version of JavaScript. It adds rules to make coding less error-prone and clearer. But there's a part of TypeScript, called the `as` keyword, that's tricky. In this article, I'll talk about why `as` can be a problem.

#### What is `as` in TypeScript?

`as` in TypeScript changes data types. For example:

```typescript twoslash
let unknownInput: unknown = "Hello, TypeScript!";
let asString = unknownInput as string;
//  ^? 
```

#### The Problem with `as`

The best thing about TypeScript is that it finds mistakes in your code before you even run it. But when you use `as`, you can skip these checks. It's like telling the computer, "I'm sure this is right," even if we might be wrong.

Using `as` too much is risky. It can cause errors in parts of your code where TypeScript could have helped. Imagine driving with a blindfold; that's what it's like.

#### Why Using `as` Can Be Bad

- **Skipping Checks**: TypeScript is great because it checks your code. Using `as` means you skip these helpful checks.
- **Making Code Unclear**: When you use `as`, it can make your code hard to understand. Others (or even you later) might not know why you used `as`.
- **Errors Happen**: If you use `as` wrong, your program will crash.

#### Better Ways Than `as`

- **Type Guards**: TypeScript has type guards. They help you check types.

```typescript twoslash
// Let's declare a variable of unknown type
let unknownInput: unknown;

// Now we'll use a type guard with typeof
if (typeof unknownInput === "string") {
    // TypeScript now knows unknownInput is a string
    console.log(unknownInput.toUpperCase());
} else {
    // Here, TypeScript still considers it unknown
    console.log(unknownInput);
}
```

- **Better Type Definitions**: Developers reach for `as` because of incomplete type definitions. Improving type definitions eliminates this need.
- **Your Own Type Guards**: For complicated types, you can make your own checks.

```typescript

// Define our type guard function
function isValidString(unknownInput: unknown): unknownInput is string {
    return typeof unknownInput === "string" && unknownInput.trim().length > 0;
}

// Example usage
const someInput: unknown = "Hello, World!";
const emptyInput: unknown = "";
const numberInput: unknown = 42;

if (isValidString(someInput)) {
    console.log(someInput.toUpperCase());
} else {
    console.log("Input is not a valid string");
}

if (isValidString(emptyInput)) {
    console.log("This won't be reached");
} else {
    console.log("Empty input is not a valid string");
}

if (isValidString(numberInput)) {
    console.log("This won't be reached");
} else {
    console.log("Number input is not a valid string");
}

// Hover over `result` to see the inferred type
const result = [someInput, emptyInput, numberInput].filter(isValidString);
//    ^?
```

### Cases Where Using `as` is Okay

The `as` keyword fits specific situations:

1. **Integrating with Non-Typed Code**: When working with JavaScript libraries or external APIs without types, `as` helps assign types to external data. Type guards remain the better choice, offering more robust type checking that aligns with TypeScript's goals.

2. **Casting in Tests**: In unit tests, when mocking or setting up test data, `as` helps shape data into the required form.

In these situations, verify that `as` solves a genuine need rather than masking improper type handling.

![Diagram as typescript inference](../../assets/images/asTypescript.png)

#### Conclusion

`as` serves a purpose in TypeScript, but better alternatives exist. By choosing proper type handling over shortcuts, we create clearer, more reliable code. Let's embrace TypeScript's strengths and write better code.

---

--- title: Exploring the Power of Square Brackets in TypeScript description: TypeScript, a statically-typed superset of JavaScript, implements square brackets [] for specific purposes. This post details the essential applications of square brackets in TypeScript, from array types to complex type manipulations, to help you write type-safe code. tags: ['typescript'] ---

# Exploring the Power of Square Brackets in TypeScript



## Introduction

TypeScript, the popular statically-typed superset of JavaScript, offers advanced type manipulation features that enhance development with strong typing. Square brackets `[]` serve distinct purposes in TypeScript. This post details how square brackets work in TypeScript, from array types to indexed access types and beyond.

## 1. Defining Array Types

Square brackets in TypeScript define array types with precision.

```typescript
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ["hello", "world"];
```

This syntax specifies that `numbers` contains numbers, and `strings` contains strings.

## 2. Tuple Types

Square brackets define tuples - arrays with fixed lengths and specific types at each index.

```typescript
type Point = [number, number];
let coordinates: Point = [12.34, 56.78];
```

In this example, `Point` represents a 2D coordinate as a tuple.

## 3. The `length` Property

Every array in TypeScript includes a `length` property that the type system recognizes.

```typescript
type LengthArr<T extends Array<any>> = T["length"];

type foo = LengthArr<["1", "2"]>;
//    ^?
```

TypeScript recognizes `length` as the numeric size of the array.

## 4. Indexed Access Types

Square brackets access specific index or property types.

```typescript
type Point = [number, number];
type FirstElement = Point[0];
//    ^?
```

Here, `FirstElement` represents the first element in the `Point` tuple: `number`.

## 5. Creating Union Types from Tuples

Square brackets help create union types from tuples efficiently.

```typescript
type Statuses = ["active", "inactive", "pending"];
type CurrentStatus = Statuses[number];
//    ^?
```

`Statuses[number]` creates a union from all tuple elements.

## 6. Generic Array Types and Constraints

Square brackets define generic constraints and types.

```typescript
function logArrayElements<T extends any[]>(elements: T) {
  elements.forEach(element => console.log(element));
}
```

This function accepts any array type through the generic constraint `T`.

## 7. Mapped Types with Index Signatures

Square brackets in mapped types define index signatures to create dynamic property types.

```typescript
type StringMap<T> = { [key: string]: T };
let map: StringMap<number> = { a: 1, b: 2 };
```

`StringMap` creates a type with string keys and values of type `T`.

## 8. Advanced Tuple Manipulation

Square brackets enable precise tuple manipulation for extracting or omitting elements.

```typescript
type WithoutFirst<T extends any[]> = T extends [any, ...infer Rest] ? Rest : [];
type Tail = WithoutFirst<[1, 2, 3]>;
//    ^?
```

`WithoutFirst` removes the first element from a tuple.

### Conclusion

Square brackets in TypeScript provide essential functionality, from basic array definitions to complex type manipulations. These features make TypeScript code reliable and maintainable. The growing adoption of TypeScript demonstrates the practical benefits of its robust type system.

The [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html) provides comprehensive documentation of these features. [TypeHero](https://typehero.dev/) offers hands-on practice through interactive challenges to master TypeScript concepts, including square bracket techniques for type manipulation. These resources will strengthen your command of TypeScript and expand your programming capabilities.

---

--- title: How to Test Vue Composables: A Comprehensive Guide with Vitest description: Learn how to effectively test Vue composables using Vitest. Covers independent and dependent composables, with practical examples and best practices. tags: ['vue', 'testing'] ---

# How to Test Vue Composables: A Comprehensive Guide with Vitest



## Introduction

Hello, everyone; in this blog post, I want to help you better understand how to test a composable in Vue. Nowadays, much of our business logic or UI logic is often encapsulated in composables, so I think it’s important to understand how to test them.

## Definitions

Before discussing the main topic, it’s important to understand some basic concepts regarding testing. This foundational knowledge will help clarify where testing Vue compostables fits into the broader landscape of software testing.

### Composables

**Composables** in Vue are reusable composition functions that encapsulate and manage reactive states and logic. They allow a flexible way to organize and reuse code across components, enhancing modularity and maintainability.

### Testing Pyramid

The **Testing Pyramid** is a conceptual metaphor that illustrates the ideal balance of different types of testing. It recommends a large base of unit tests, supplemented by a smaller set of integration tests and capped with an even smaller set of end-to-end tests. This structure ensures efficient and effective test coverage.

### Unit Testing and How Testing a Composable Would Be a Unit Test

**Unit testing** refers to the practice of testing individual units of code in isolation. In the context of Vue, testing a composable is a form of unit testing. It involves rigorously verifying the functionality of these isolated, reusable code blocks, ensuring they function correctly without external dependencies.

---

## Testing Composables

Composables in Vue are essentially functions, leveraging Vue's reactivity system. Given this unique nature, we can categorize composables into different types. On one hand, there are `Independent Composables`, which can be tested directly due to their standalone nature. On the other hand, we have `Dependent Composables`, which only function correctly when integrated within a component.In the sections that follow, I'll delve into these distinct types, provide examples for each, and guide you through effective testing strategies for both.

---

### Independent Composables

An Independent Composable exclusively uses Vue's Reactivity APIs. These composables operate independently of Vue component instances, making them straightforward to test.

#### Example & Testing Strategy

Here is an example of an independent composable that calculates the sum of two reactive values:

```ts

function useSum(a: Ref<number>, b: Ref<number>): ComputedRef<number> {
  return computed(() => a.value + b.value)
}
```

To test this composable, you would directly invoke it and assert its returned state:

Test with Vitest:

```ts

describe("useSum", () => {
  it("correctly computes the sum of two numbers", () => {
    const num1 = ref(2);
    const num2 = ref(3);
    const sum = useSum(num1, num2);

    expect(sum.value).toBe(5);
  });
});
```

This test directly checks the functionality of useSum by passing reactive references and asserting the computed result.

---

### Dependent Composables

`Dependent Composables` are distinguished by their reliance on Vue's component instance. They often leverage features like lifecycle hooks or context for their operation. These composables are an integral part of a component and necessitate a distinct approach for testing, as opposed to Independent Composables.

#### Example & Usage

An exemplary Dependent Composable is `useLocalStorage`. This composable facilitates interaction with the browser's localStorage and harnesses the `onMounted` lifecycle hook for initialization:

```ts

function useLocalStorage<T>(key: string, initialValue: T) {
  const value = ref<T>(initialValue);

  function loadFromLocalStorage() {
    const storedValue = localStorage.getItem(key);
    if (storedValue !== null) {
      value.value = JSON.parse(storedValue);
    }
  }

  onMounted(loadFromLocalStorage);

  watch(value, newValue => {
    localStorage.setItem(key, JSON.stringify(newValue));
  });

  return { value };
}

export default useLocalStorage;
```

This composable can be utilised within a component, for instance, to create a persistent counter:

![Counter Ui](../../assets/images/how-to-test-vue-composables/counter-ui.png)

```vue

<script setup lang="ts">
// ... script content ...
</script>

<template>
  <div>
    <h1>Counter: {{ count }}</h1>
    <button @click="increment">Increment</button>
  </div>
</template>
```

The primary benefit here is the seamless synchronization of the reactive `count` property with localStorage, ensuring persistence across sessions.

### Testing Strategy

To effectively test `useLocalStorage`, especially considering the `onMounted` lifecycle, we initially face a challenge. Let's start with a basic test setup:

```ts

// ---cut-start---
function useLocalStorage<T>(key: string, initialValue: T) {
  const value = ref<T>(initialValue);

  function loadFromLocalStorage() {
    const storedValue = localStorage.getItem(key);
    if (storedValue !== null) {
      value.value = JSON.parse(storedValue);
    }
  }

  onMounted(loadFromLocalStorage);

  watch(value, newValue => {
    localStorage.setItem(key, JSON.stringify(newValue));
  });

  return { value };
}
// ---cut-end---
describe("useLocalStorage", () => {
  it("should load the initialValue", () => {
    const { value } = useLocalStorage("testKey", "initValue");
    expect(value.value).toBe("initValue");
  });

  it("should load from localStorage", async () => {
    localStorage.setItem("testKey", JSON.stringify("fromStorage"));
    const { value } = useLocalStorage("testKey", "initialValue");
    expect(value.value).toBe("fromStorage");
  });
});
```

Here, the first test will pass, asserting that the composable initialises with the given `initialValue`. However, the second test, which expects the composable to load a pre-existing value from localStorage, fails. The challenge arises because the `onMounted` lifecycle hook is not triggered during testing. To address this, we need to refactor our composable or our test setup to simulate the component mounting process.

---

### Enhancing Testing with the `withSetup` Helper Function

To facilitate easier testing of composables that rely on Vue's lifecycle hooks, we've developed a higher-order function named `withSetup`. This utility allows us to create a Vue component context programmatically, focusing primarily on the setup lifecycle function where composables are typically used.

#### Introduction to `withSetup`

`withSetup` is designed to simulate a Vue component's setup function, enabling us to test composables in an environment that closely mimics their real-world use. The function accepts a composable and returns both the composable's result and a Vue app instance. This setup allows for comprehensive testing, including lifecycle and reactivity features.

```ts

export function withSetup<T>(composable: () => T): [T, App] {
  let result: T;
  const app = createApp({
    setup() {
      result = composable();
      return () => {};
    },
  });
  app.mount(document.createElement("div"));
  return [result, app];
}
```

In this implementation, `withSetup` mounts a minimal Vue app and executes the provided composable function during the setup phase. This approach allows us to capture and return the composable's output alongside the app instance for further testing.

#### Utilizing `withSetup` in Tests

With `withSetup`, we can enhance our testing strategy for composables like `useLocalStorage`, ensuring they behave as expected even when they depend on lifecycle hooks:

```ts

// ---cut-start---
export function withSetup<T>(composable: () => T): [T, App] {
  let result: T;
  const app = createApp({
    setup() {
      result = composable();
      return () => {};
    },
  });
  app.mount(document.createElement("div"));
  return [result, app];
}

function useLocalStorage<T>(key: string, initialValue: T) {
  const value = ref<T>(initialValue);

  function loadFromLocalStorage() {
    const storedValue = localStorage.getItem(key);
    if (storedValue !== null) {
      value.value = JSON.parse(storedValue);
    }
  }

  onMounted(loadFromLocalStorage);

  watch(value, newValue => {
    localStorage.setItem(key, JSON.stringify(newValue));
  });

  return { value };
}
// ---cut-end---

it("should load the value from localStorage if it was set before", async () => {
  localStorage.setItem("testKey", JSON.stringify("valueFromLocalStorage"));
  const [result] = withSetup(() => useLocalStorage("testKey", "testValue"));
  expect(result.value.value).toBe("valueFromLocalStorage");
});
```

This test demonstrates how `withSetup` enables the composable to execute as if it were part of a regular Vue component, ensuring the `onMounted` lifecycle hook is triggered as expected. Additionally, the robust TypeScript support enhances the development experience by providing clear type inference and error checking.

---

### Testing Composables with Inject

Another common scenario is testing composables that rely on Vue's dependency injection system using `inject`. These composables present unique challenges as they expect certain values to be provided by ancestor components. Let's explore how to effectively test such composables.

#### Example Composable with Inject

Here's an example of a composable that uses inject:

```ts

export const MessageKey: InjectionKey<string> = Symbol('message')

export function useMessage() {
  const message = inject(MessageKey)

  if (!message) {
    throw new Error('Message must be provided')
  }

  const getUpperCase = () => message.toUpperCase()
  const getReversed = () => message.split('').reverse().join('')

  return {
    message,
    getUpperCase,
    getReversed,
  }
}
```

#### Creating a Test Helper

To test composables that use inject, we need a helper function that creates a testing environment with the necessary providers. Here's a utility function that makes this possible:

```ts

type InstanceType<V> = V extends { new (...arg: any[]): infer X } ? X : never
type VM<V> = InstanceType<V> & { unmount: () => void }

interface InjectionConfig {
  key: InjectionKey<any> | string
  value: any
}

export function useInjectedSetup<TResult>(
  setup: () => TResult,
  injections: InjectionConfig[] = [],
): TResult & { unmount: () => void } {
  let result!: TResult

  const Comp = defineComponent({
    setup() {
      result = setup()
      return () => h('div')
    },
  })

  const Provider = defineComponent({
    setup() {
      injections.forEach(({ key, value }) => {
        provide(key, value)
      })
      return () => h(Comp)
    },
  })

  const mounted = mount(Provider)

  return {
    ...result,
    unmount: mounted.unmount,
  } as TResult & { unmount: () => void }
}

function mount<V>(Comp: V) {
  const el = document.createElement('div')
  const app = createApp(Comp as any)
  const unmount = () => app.unmount()
  const comp = app.mount(el) as any as VM<V>
  comp.unmount = unmount
  return comp
}
```

#### Writing Tests

With our helper function in place, we can now write comprehensive tests for our inject-dependent composable:

```ts

describe('useMessage', () => {
  it('should handle injected message', () => {
    const wrapper = useInjectedSetup(
      () => useMessage(),
      [{ key: MessageKey, value: 'hello world' }],
    )

    expect(wrapper.message).toBe('hello world')
    expect(wrapper.getUpperCase()).toBe('HELLO WORLD')
    expect(wrapper.getReversed()).toBe('dlrow olleh')

    wrapper.unmount()
  })

  it('should throw error when message is not provided', () => {
    expect(() => {
      useInjectedSetup(() => useMessage(), [])
    }).toThrow('Message must be provided')
  })
})
```

The `useInjectedSetup` helper creates a testing environment that:
1. Simulates a component hierarchy
2. Provides the necessary injection values
3. Executes the composable in a proper Vue context
4. Returns the composable's result along with an unmount function

This approach allows us to:
- Test composables that depend on inject
- Verify error handling when required injections are missing
- Test the full functionality of methods that use injected values
- Properly clean up after tests by unmounting the test component

Remember to always unmount the test component after each test to prevent memory leaks and ensure test isolation.

---

## Summary

| Independent Composables 🔓 | Dependent Composables 🔗 |
|----------------------------|---------------------------|
| - ✅ can be tested directly | - 🧪 need a component to test |
| - 🛠️ uses everything beside of lifecycles and provide / inject | - 🔄 uses Lifecycles or Provide / Inject |

In our exploration of testing Vue composables, we uncovered two distinct categories: **Independent Composables** and **Dependent Composables**. Independent Composables stand alone and can be tested akin to regular functions, showcasing straightforward testing procedures. Meanwhile, Dependent Composables, intricately tied to Vue's component system and lifecycle hooks, require a more nuanced approach. For these, we learned the effectiveness of utilizing a helper function, such as `withSetup`, to simulate a component context, enabling comprehensive testing.

I hope this blog post has been insightful and useful in enhancing your understanding of testing Vue composables. I'm also keen to learn about your experiences and methods in testing composables within your projects. Your insights and approaches could provide valuable perspectives and contribute to the broader Vue community's knowledge.

---

--- title: Robust Error Handling in TypeScript: A Journey from Naive to Rust-Inspired Solutions description: Learn to write robust, predictable TypeScript code using Rust's Result pattern. This post demonstrates practical examples and introduces the ts-results library, implementing Rust's powerful error management approach in TypeScript. tags: ['typescript'] ---

# Robust Error Handling in TypeScript: A Journey from Naive to Rust-Inspired Solutions



## Introduction

In software development, robust error handling forms the foundation of reliable software. Even the best-written code encounters unexpected challenges in production. This post explores how to enhance TypeScript error handling with Rust's Result pattern—creating more resilient and explicit error management.

## The Pitfalls of Overlooking Error Handling

Consider this TypeScript division function:

```typescript
const divide = (a: number, b: number) => a / b;
```

This function appears straightforward but fails when `b` is zero, returning `Infinity`. Such overlooked cases can lead to illogical outcomes:

```typescript
const divide = (a: number, b: number) => a / b;
// ---cut---
const calculateAverageSpeed = (distance: number, time: number) => {
  const averageSpeed = divide(distance, time);
  return `${averageSpeed} km/h`;
};

// will be "Infinity km/h"
console.log("Average Speed: ", calculateAverageSpeed(50, 0));
```

## Embracing Explicit Error Handling

TypeScript provides powerful error management techniques. The Rust-inspired approach enhances code safety and predictability.

### Result Type Pattern: A Rust-Inspired Approach in TypeScript

Rust excels at explicit error handling through the `Result` type. Here's the pattern in TypeScript:

```typescript
type Success<T> = { kind: "success"; value: T };
type Failure<E> = { kind: "failure"; error: E };
type Result<T, E> = Success<T> | Failure<E>;

function divide(a: number, b: number): Result<number, string> {
  if (b === 0) {
    return { kind: "failure", error: "Cannot divide by zero" };
  }
  return { kind: "success", value: a / b };
}
```

### Handling the Result in TypeScript

```typescript
// ---cut-start---
type Success<T> = { kind: "success"; value: T };
type Failure<E> = { kind: "failure"; error: E };
type Result<T, E> = Success<T> | Failure<E>;

function divide(a: number, b: number): Result<number, string> {
  if (b === 0) {
    return { kind: "failure", error: "Cannot divide by zero" };
  }
  return { kind: "success", value: a / b };
}
// ---cut-end---

const handleDivision = (result: Result<number, string>) => {
  if (result.kind === "success") {
    console.log("Division result:", result.value);
  } else {
    console.error("Division error:", result.error);
  }
};

const result = divide(10, 0);
handleDivision(result);
```

### Native Rust Implementation for Comparison

In Rust, the `Result` type is an enum with variants for success and error:

```rust

fn divide(a: i32, b: i32) -> std::result::Result<i32, String> {
    if b == 0 {
        std::result::Result::Err("Cannot divide by zero".to_string())
    } else {
        std::result::Result::Ok(a / b)
    }
}

fn main() {
    match divide(10, 2) {
        std::result::Result::Ok(result) => println!("Division result: {}", result),
        std::result::Result::Err(error) => println!("Error: {}", error),
    }
}

```

### Why the Rust Way?

1. **Explicit Handling**: Forces handling of both outcomes, enhancing code robustness.
2. **Clarity**: Makes code intentions clear.
3. **Safety**: Reduces uncaught exceptions.
4. **Functional Approach**: Aligns with TypeScript's functional programming style.

## Leveraging ts-results for Rust-Like Error Handling

For TypeScript developers, the [ts-results](https://github.com/vultix/ts-results) library is a great tool to apply Rust's error handling pattern, simplifying the implementation of Rust's `Result` type in TypeScript.

## Conclusion

Implementing Rust's `Result` pattern in TypeScript, with tools like ts-results, enhances error handling strategies. This approach creates robust applications that handle errors while maintaining code integrity and usability.

Let's embrace these practices to craft software that withstands the tests of time and uncertainty.

---

--- title: Mastering Vue 3 Composables: A Comprehensive Style Guide description: Did you ever struggle how to write better composables in Vue? In this Blog post I try to give some tips how to do that tags: ['vue'] ---

# Mastering Vue 3 Composables: A Comprehensive Style Guide



## Introduction

The release of Vue 3 brought a transformational change, moving from the Options API to the Composition API. At the heart of this transition lies the concept of "composables" — modular functions that leverage Vue's reactive features. This change enhanced the framework's flexibility and code reusability. The inconsistent implementation of composables across projects often leads to convoluted and hard-to-maintain codebases.

This style guide harmonizes coding practices around composables, focusing on producing clean, maintainable, and testable code. While composables represent a new pattern, they remain functions at their core. The guide bases its recommendations on time-tested principles of good software design.

This guide serves as a comprehensive resource for both newcomers to Vue 3 and experienced developers aiming to standardize their team's coding style.

## Table of Contents

## File Naming

### Rule 1.1: Prefix with `use` and Follow PascalCase

```ts

// Good
useCounter.ts;
useApiRequest.ts;

// Bad
counter.ts;
APIrequest.ts;
```

---

## Composable Naming

### Rule 2.1: Use Descriptive Names

```ts

// Good
export function useUserData() {}

// Bad
export function useData() {}
```

---

## Folder Structure

### Rule 3.1: Place in composables Directory

```plaintext
src/
└── composables/
    ├── useCounter.ts
    └── useUserData.ts
```

---

## Argument Passing

### Rule 4.1: Use Object Arguments for Four or More Parameters

```ts

// Good: For Multiple Parameters
useUserData({ id: 1, fetchOnMount: true, token: "abc", locale: "en" });

// Also Good: For Fewer Parameters
useCounter(1, true, "session");

// Bad
useUserData(1, true, "abc", "en");
```

---

## Error Handling

### Rule 5.1: Expose Error State

```ts

// Good
const error = ref(null);
try {
  // Do something
} catch (err) {
  error.value = err;
}
return { error };

// Bad
try {
  // Do something
} catch (err) {
  console.error("An error occurred:", err);
}
return {};
```

---

## Avoid Mixing UI and Business Logic

### Rule 6.2: Decouple UI from Business Logic in Composables

Composables should focus on managing state and business logic, avoiding UI-specific behavior like toasts or alerts. Keeping UI logic separate from business logic will ensure that your composable is reusable and testable.

```ts

// Good
export function useUserData(userId) {
  const user = ref(null);
  const error = ref(null);

  const fetchUser = async () => {
    try {
      const response = await axios.get(`/api/users/${userId}`);
      user.value = response.data;
    } catch (e) {
      error.value = e;
    }
  };

  return { user, error, fetchUser };
}

// In component
setup() {
  const { user, error, fetchUser } = useUserData(userId);

  watch(error, (newValue) => {
    if (newValue) {
      showToast("An error occurred.");  // UI logic in component
    }
  });

  return { user, fetchUser };
}

// Bad
export function useUserData(userId) {
  const user = ref(null);

  const fetchUser = async () => {
    try {
      const response = await axios.get(`/api/users/${userId}`);
      user.value = response.data;
    } catch (e) {
      showToast("An error occurred."); // UI logic inside composable
    }
  };

  return { user, fetchUser };
}
```

---

## Anatomy of a Composable

### Rule 7.2: Structure Your Composables Well

A well-structured composable improves understanding, usage, and maintenance. It consists of these components:

- **Primary State**: The main reactive state that the composable manages.
- **State Metadata**: States that hold values like API request status or errors.
- **Methods**: Functions that update the Primary State and State Metadata. These functions can call APIs, manage cookies, or integrate with other composables.

Following this structure makes your composables more intuitive and improves code quality across your project.

```ts

// Good Example: Anatomy of a Composable
// Well-structured according to Anatomy of a Composable
export function useUserData(userId) {
  // Primary State
  const user = ref(null);

  // Supportive State
  const status = ref("idle");
  const error = ref(null);

  // Methods
  const fetchUser = async () => {
    status.value = "loading";
    try {
      const response = await axios.get(`/api/users/${userId}`);
      user.value = response.data;
      status.value = "success";
    } catch (e) {
      status.value = "error";
      error.value = e;
    }
  };

  return { user, status, error, fetchUser };
}

// Bad Example: Anatomy of a Composable
// Lacks well-defined structure and mixes concerns
export function useUserDataAndMore(userId) {
  // Muddled State: Not clear what's Primary or Supportive
  const user = ref(null);
  const count = ref(0);
  const message = ref("Initializing...");

  // Methods: Multiple responsibilities and side-effects
  const fetchUserAndIncrement = async () => {
    message.value = "Fetching user and incrementing count...";
    try {
      const response = await axios.get(`/api/users/${userId}`);
      user.value = response.data;
    } catch (e) {
      message.value = "Failed to fetch user.";
    }
    count.value++; // Incrementing count, unrelated to user fetching
  };

  // More Methods: Different kind of task entirely
  const setMessage = newMessage => {
    message.value = newMessage;
  };

  return { user, count, message, fetchUserAndIncrement, setMessage };
}
```

---

## Functional Core, Imperative Shell

### Rule 8.2: (optional) use functional core imperative shell pattern

Structure your composable such that the core logic is functional and devoid of side effects, while the imperative shell handles the Vue-specific or side-effecting operations. Following this principle makes your composable easier to test, debug, and maintain.

#### Example: Functional Core, Imperative Shell

```ts

// good
// Functional Core
const calculate = (a, b) => a + b;

// Imperative Shell
export function useCalculatorGood() {
  const result = ref(0);

  const add = (a, b) => {
    result.value = calculate(a, b); // Using the functional core
  };

  // Other side-effecting code can go here, e.g., logging, API calls

  return { result, add };
}

// wrong
// Mixing core logic and side effects
export function useCalculatorBad() {
  const result = ref(0);

  const add = (a, b) => {
    // Side-effect within core logic
    console.log("Adding:", a, b);
    result.value = a + b;
  };

  return { result, add };
}
```

---

## Single Responsibility Principle

### Rule 9.1: Use SRP for composables

A composable should follow the Single Responsibility Principle: one reason to change. This means each composable handles one specific task. Following this principle creates composables that are clear, maintainable, and testable.

```ts

// Good
export function useCounter() {
  const count = ref(0);

  const increment = () => {
    count.value++;
  };

  const decrement = () => {
    count.value--;
  };

  return { count, increment, decrement };
}

// Bad

export function useUserAndCounter(userId) {
  const user = ref(null);
  const count = ref(0);

  const fetchUser = async () => {
    try {
      const response = await axios.get(`/api/users/${userId}`);
      user.value = response.data;
    } catch (error) {
      console.error("An error occurred while fetching user data:", error);
    }
  };

  const increment = () => {
    count.value++;
  };

  const decrement = () => {
    count.value--;
  };

  return { user, fetchUser, count, increment, decrement };
}
```

---

## File Structure of a Composable

### Rule 10.1: Rule: Consistent Ordering of Composition API Features

Your team should establish and follow a consistent order for Composition API features throughout the codebase.

Here's a recommended order:

1. Initializing: Setup logic
2. Refs: Reactive references
3. Computed: Computed properties
4. Methods: Functions for state manipulation
5. Lifecycle Hooks: onMounted, onUnmounted, etc.
6. Watch

Pick an order that works for your team and apply it consistently across all composables.

```ts

// Example in useCounter.ts
export default function useCounter() {
  // Initializing
  // Initialize variables, make API calls, or any setup logic
  // For example, using a router
  // ...

  // Refs
  const count = ref(0);

  // Computed
  const isEven = computed(() => count.value % 2 === 0);

  // Methods
  const increment = () => {
    count.value++;
  };

  const decrement = () => {
    count.value--;
  };

  // Lifecycle
  onMounted(() => {
    console.log("Counter is mounted");
  });

  return {
    count,
    isEven,
    increment,
    decrement,
  };
}
```

## Conclusion

These guidelines provide best practices for writing clean, testable, and efficient Vue 3 composables. They combine established software design principles with practical experience, though they aren't exhaustive.

Programming blends art and science. As you develop with Vue, you'll discover patterns that match your needs. Focus on maintaining a consistent, scalable, and maintainable codebase. Adapt these guidelines to fit your project's requirements.

Share your ideas, improvements, and real-world examples in the comments. Your input helps evolve these guidelines into a better resource for the Vue community.

---

--- title: Best Practices for Error Handling in Vue Composables description: Error handling can be complex, but it's crucial for composables to manage errors consistently. This post explores an effective method for implementing error handling in composables. tags: ['vue'] ---

# Best Practices for Error Handling in Vue Composables



## Introduction

Navigating the complex world of composables presented a significant challenge. Understanding this powerful paradigm required effort when determining the division of responsibilities between a composable and its consuming component. The strategy for error handling emerged as a critical aspect that demanded careful consideration.

In this blog post, we aim to clear the fog surrounding this intricate topic. We'll delve into the concept of **Separation of Concerns**, a fundamental principle in software engineering, and how it provides guidance for proficient error handling within the scope of composables. Let's delve into this critical aspect of Vue composables and demystify it together.

> "Separation of Concerns, even if not perfectly possible, is yet the only available technique for effective ordering of one's thoughts, that I know of." -- Edsger W. Dijkstra

## The `usePokemon` Composable

Our journey begins with the creation of a custom composable, aptly named `usePokemon`. This particular composable acts as a liaison between our application and the Pokémon API. It boasts three core methods — `load`, `loadSpecies`, and `loadEvolution` — each dedicated to retrieving distinct types of data.

A straightforward approach would allow these methods to propagate errors directly. Instead, we take a more robust approach. Each method catches potential exceptions internally and exposes them via a dedicated error object. This strategy enables more sophisticated and context-sensitive error handling within the components that consume this composable.

Without further ado, let's delve into the TypeScript code for our `usePokemon` composable:

## Dissecting the `usePokemon` Composable

Let's break down our `usePokemon` composable step by step, to fully grasp its structure and functionality.

### The `ErrorRecord` Interface and `errorsFactory` Function

```ts
interface ErrorRecord {
  load: Error | null;
  loadSpecies: Error | null;
  loadEvolution: Error | null;
}

const errorsFactory = (): ErrorRecord => ({
  load: null,
  loadSpecies: null,
  loadEvolution: null,
});
```

First, we define a `ErrorRecord` interface that encapsulates potential errors from our three core methods. This interface ensures that each method can store a `Error` object or `null` if no error has occurred.

The `errorsFactory` function creates these ErrorRecord objects. It returns an ErrorRecord with all values set to null, indicating no errors have occurred yet.

### Initialising Refs

```ts
// ---cut-start---
interface ErrorRecord {
  load: Error | null;
  loadSpecies: Error | null;
  loadEvolution: Error | null;
}

const errorsFactory = (): ErrorRecord => ({
  load: null,
  loadSpecies: null,
  loadEvolution: null,
});
// ---cut-end---

const pokemon: Ref<any | null> = ref(null);
const species: Ref<any | null> = ref(null);
const evolution: Ref<any | null> = ref(null);
const error: Ref<ErrorRecord> = ref(errorsFactory());
```

Next, we create the `Ref` objects that store our data (`pokemon`, `species`, and `evolution`) and our error information (error). We use the errorsFactory function to set up the initial error-free state.

### The `load`, `loadSpecies`, and `loadEvolution` Methods

Each of these methods performs a similar set of operations: it fetches data from a specific endpoint of the Pokémon API, assigns the returned data to the appropriate `Ref` object, and handles any potential errors.

```ts

const load = async (id: number) => {
  try {
    const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
    pokemon.value = await response.json();
    error.value.load = null;
  } catch (err) {
    error.value.load = err as Error;
  }
};
```

For example, in the `load` method, we fetch data from the `pokemon` endpoint using the provided ID. A successful fetch updates `pokemon.value` with the returned data and clears any previous error by setting `error.value.load` to null. When an error occurs during the fetch, we catch it and store it in error.value.load.

The `loadSpecies` and `loadEvolution` methods operate similarly, but they fetch from different endpoints and store their data and errors in different Ref objects.

### The Return Object

The composable returns an object providing access to the Pokémon, species, and evolution data, as well as the three load methods. It exposes the error object as a computed property. This computed property updates whenever any of the methods sets an error, allowing consumers of the composable to react to errors.

```ts

return {
  pokemon,
  species,
  evolution,
  load,
  loadSpecies,
  loadEvolution,
  error: computed(() => error.value),
};
```

### Full Code

```ts

interface ErrorRecord {
  load: Error | null;
  loadSpecies: Error | null;
  loadEvolution: Error | null;
}

const errorsFactory = (): ErrorRecord => ({
  load: null,
  loadSpecies: null,
  loadEvolution: null,
});

export default function usePokemon() {
  const pokemon: Ref<any | null> = ref(null);
  const species: Ref<any | null> = ref(null);
  const evolution: Ref<any | null> = ref(null);
  const error: Ref<ErrorRecord> = ref(errorsFactory());

  const load = async (id: number) => {
    try {
      const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
      pokemon.value = await response.json();
      error.value.load = null;
    } catch (err) {
      error.value.load = err as Error;
    }
  };

  const loadSpecies = async (id: number) => {
    try {
      const response = await fetch(
        `https://pokeapi.co/api/v2/pokemon-species/${id}`
      );
      species.value = await response.json();
      error.value.loadSpecies = null;
    } catch (err) {
      error.value.loadSpecies = err as Error;
    }
  };

  const loadEvolution = async (id: number) => {
    try {
      const response = await fetch(
        `https://pokeapi.co/api/v2/evolution-chain/${id}`
      );
      evolution.value = await response.json();
      error.value.loadEvolution = null;
    } catch (err) {
      error.value.loadEvolution = err as Error;
    }
  };

  return {
    pokemon,
    species,
    evolution,
    load,
    loadSpecies,
    loadEvolution,
    error: computed(() => error.value),
  };
}
```

## The Pokémon Component

Next, let's look at a Pokémon component that uses our `usePokemon` composable:

```vue

<template>
  <div>
    <div v-if="pokemon">
      <h2>Pokemon Data:</h2>
      <p>Name: {{ pokemon.name }}</p>
    </div>

    <div v-if="species">
      <h2>Species Data:</h2>
      <p>Name: {{ species.base_happiness }}</p>
    </div>

    <div v-if="evolution">
      <h2>Evolution Data:</h2>
      <p>Name: {{ evolution.evolutionName }}</p>
    </div>

    <div v-if="loadError">
      An error occurred while loading the pokemon: {{ loadError.message }}
    </div>

    <div v-if="loadSpeciesError">
      An error occurred while loading the species:
      {{ loadSpeciesError.message }}
    </div>

    <div v-if="loadEvolutionError">
      An error occurred while loading the evolution:
      {{ loadEvolutionError.message }}
    </div>
  </div>
</template>

<script lang="ts" setup>
const { load, loadSpecies, loadEvolution, pokemon, species, evolution, error } =
  usePokemon();

const loadError = computed(() => error.value.load);
const loadSpeciesError = computed(() => error.value.loadSpecies);
const loadEvolutionError = computed(() => error.value.loadEvolution);

const pokemonId = ref(1);
const speciesId = ref(1);
const evolutionId = ref(1);

load(pokemonId.value);
loadSpecies(speciesId.value);
loadEvolution(evolutionId.value);
</script>
```

The above code uses the usePokemon composable to fetch and display Pokémon, species, and evolution data. The component shows errors to users when fetch operations fail.

## Conclusion

Wrapping the `fetch` operations in a try-catch block in the `composable` and surfacing errors through a reactive error object keeps the component clean and focused on its core responsibilities - presenting data and handling user interaction.

This approach promotes `separation of concerns` - the composable manages error handling logic independently, while the component responds to the provided state. The component remains focused on presenting the data effectively.

The error object's reactivity integrates seamlessly with Vue's template system. The system tracks changes automatically, updating relevant template sections when the error state changes.

This pattern offers a robust approach to error handling in composables. By centralizing error-handling logic in the composable, you create components that maintain clarity, readability, and maintainability.

---

--- title: How to Improve Accessibility with Testing Library and jest-axe for Your Vue Application description: Use Jest axe to have automatic tests for your vue application tags: ['vue', 'accessibility'] ---

# How to Improve Accessibility with Testing Library and jest-axe for Your Vue Application



Accessibility is a critical aspect of web development that ensures your application serves everyone, including people with disabilities. Making your Vue apps accessible fulfills legal requirements and enhances the experience for all users. In this post, we'll explore how to improve accessibility in Vue applications using Testing Library and jest-axe.

## Prerequisites

Before we dive in, make sure you have the following installed in your Vue project:

- @testing-library/vue
- jest-axe

You can add them with:

```bash
npm install --save-dev @testing-library/vue jest-axe
```

## Example Component

Let's look at a simple Vue component that displays an image and some text:

```vue
<template>
  <div>
    <h2>{{ title }}</h2>
    <p>{{ description }}</p>
    
  </div>
</template>

<script setup lang="ts">
defineProps({
  title: String,
  description: String
})
</script>
```

Developers should include alt text for images to ensure accessibility, but how can we verify this in production?

## Testing with jest-axe

This is where jest-axe comes in. Axe is a leading accessibility testing toolkit used by major tech companies.

To test our component, we can create a test file like this:

```js
expect.extend(toHaveNoViolations);

describe('MyComponent', () => {
  it('has no accessibility violations', async () => {
    const { container } = render(MyComponent, {
      props: {
        title: 'Sample Title',
        description: 'Sample Description',
      },
    });

    const results = await axe(container);
    expect(results).toHaveNoViolations();
  });
});
```

When we run this test, we'll get an error like:

```shell
FAIL  src/components/MyComponent.spec.ts > MyComponent > has no accessibility violations

Error: expect(received).toHaveNoViolations(expected)

Expected the HTML found at $('img') to have no violations:

<img src="sample_image.jpg">

Received:

"Images must have alternate text (image-alt)"

Fix any of the following:
  Element does not have an alt attribute
  aria-label attribute does not exist or is empty
  aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty
  Element has no title attribute
  Element's default semantics were not overridden with role="none" or role="presentation"
```

This tells us we need to add an alt attribute to our image. We can fix the component and re-run the test until it passes.

## Conclusion

By integrating accessibility testing with tools like Testing Library and jest-axe, we catch accessibility issues during development. This ensures our Vue applications remain usable for everyone. Making accessibility testing part of our CI pipeline maintains high standards and delivers a better experience for all users.

---

--- title: Mastering TypeScript: Looping with Types description: Did you know that TypeScript is Turing complete? In this post, I will show you how you can loop with TypeScript. tags: ['typescript'] ---

# Mastering TypeScript: Looping with Types



## Introduction

Loops play a pivotal role in programming, enabling code execution without redundancy. JavaScript developers might be familiar with `foreach` or `do...while` loops, but TypeScript offers unique looping capabilities at the type level. This blog post delves into three advanced TypeScript looping techniques, demonstrating their importance and utility.

## Mapped Types

Mapped Types in TypeScript allow the transformation of object properties. Consider an object requiring immutable properties:

```typescript
type User = {
  id: string;
  email: string;
  age: number;
};
```

We traditionally hardcode it to create an immutable version of this type. To maintain adaptability with the original type, Mapped Types come into play. They use generics to map each property, offering flexibility to transform property characteristics. For instance:

```typescript
type ReadonlyUser<T> = {
  readonly [P in keyof T]: T[P];
};
```

This technique is extensible. For example, adding nullability:

```typescript
type Nullable<T> = {
  [P in keyof T]: T[P] | null;
};
```

Or filtering out certain types:

```typescript
type ExcludeStrings<T> = {
  [P in keyof T as T[P] extends string ? never : P]: T[P];
};
```

Understanding the core concept of Mapped Types opens doors to creating diverse, reusable types.

## Recursion

Recursion is fundamental in TypeScript's type-level programming since state mutation is not an option. Consider applying immutability to all nested properties:

```typescript
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
```

Here, TypeScript's compiler recursively ensures every property is immutable, demonstrating the language's depth in handling complex types.

## Union Types

Union Types represent a set of distinct types, such as:

```typescript
const hi = "Hello";
const msg = `${hi}, world`;
//    ^?
```

Creating structured types from unions involves looping over each union member. For instance, constructing a type where each status is an object:

```typescript
type Status = "Failure" | "Success";
type StatusObject = Status extends infer S ? { status: S } : never;
```

## Conclusion

TypeScript's advanced type system transcends static type checking, providing sophisticated tools for type transformation and manipulation. Mapped Types, Recursion, and Union Types are not mere features but powerful instruments that enhance code maintainability, type safety, and expressiveness. These techniques underscore TypeScript's capability to handle complex programming scenarios, affirming its status as more than a JavaScript superset but a language that enriches our development experience.

---

