MoreRSS

site iconThe Practical DeveloperModify

A constructive and inclusive social network for software developers.
Please copy the RSS to your reader, or quickly subscribe to:

Inoreader Feedly Follow Feedbin Local Reader

Rss preview of Blog of The Practical Developer

I built an AI hiring platform with zero coding experience. Here's what I learned.

2026-04-29 16:54:52

I'm a recruiter. Not a developer.
4+ years in TA, 140+ hires across 18 countries.

A few weeks ago I shipped a full-stack AI platform used by people across Germany, USA, India and beyond. No code written manually. Not a single line.
Here's what actually mattered.

The problem:
Recruiters drown in CVs. Candidates apply into a black hole. Both sides losing from opposite ends of the same broken process.
So I built My Ideal Candidate, the only AI hiring platform serving both sides simultaneously.

The stack:

Lovable — built everything through conversation
Supabase — auth, database, edge functions
Gemini + Claude API — CV scoring, gap analysis, Sam the AI consultant
Resend — transactional emails
Google OAuth — Drive and Sheets integration

The numbers:
400+ visitors. 8+ countries. 60% mobile. Zero paid marketing.

The barrier to shipping software has collapsed. The constraint isn't technical ability anymore and it's clarity of thought.
The recruiter in me saw the problem. The builder in me fixed it.
→ [](https://myidealcandidate.com/

How We Helped an Agri Company Generate 500+ Documents/Day Without Rebuilding Their System

2026-04-29 16:49:39

This case proved the opposite.

Our first agriculture client already had a solid system for collecting operational data. But they were stuck — not because of data, but because they couldn’t turn that data into documents fast enough.

We didn’t replace their system.

We just removed the bottleneck.

Initial Situation

Client: Agriculture company (mid-sized farm + trading operations)
Our product: TemplioniX
Timeline: ~3 weeks
Team: 2 engineers (client side), 1 engineer + 1 product (our side)

The client already had their data collection fully solved.

Their internal system handled:

  • Field activity tracking (harvest, logistics, storage)
  • Transaction records
  • Partner and contract data
  • Compliance-related inputs

Data was structured, validated, and stored correctly.

But here’s where things broke:

They needed to generate:

  • Contracts
  • Invoices
  • Field reports
  • Compliance documents

…in high volumes.

The bottleneck

Document generation was:

  • Partially manual (Word templates + copy-paste)
  • Partially scripted (limited automation, brittle scripts)

This led to:

❌ 5–15 minutes per document (manual + review)
❌ High error rate (wrong fields, outdated templates)
❌ Template drift (multiple versions floating around)
❌ No reliable bulk generation
❌ Operational delays (especially during peak seasons)

Key insight:

  • They didn’t have a data problem.
  • They had a document transformation problem.

Why They Chose Our Platform

They explicitly did not want to rebuild their system.

Reasons:

  • Their data platform already worked well
  • Rebuilding = high cost + high risk
  • Timeline constraints (seasonal pressure)

We also discussed an internal rebuild for document generation.

They rejected it because:

  • Maintaining template logic in code is painful
  • Business users couldn’t update templates themselves
  • Scaling batch processing reliably is non-trivial
  • What they actually needed

Not a platform replacement — but two focused capabilities:

  • Template Management Portal
  • Centralized templates
  • Version control
  • Editable by non-devs
  • Bulk Generation API
  • Feed structured data → generate documents
  • Handle large batches reliably
  • Integrate with their existing system

That’s exactly what we provided with TemplioniX.

Implementation

We kept integration intentionally simple.

Step 1 — Data Mapping

Their system exported structured data like:

{
"farmerName": "John Doe",
"fieldId": "F-102",
"harvestDate": "2026-03-10",
"quantity": 1250,
"price": 210.50
}

We mapped this directly to template fields:

{{farmerName}}
{{fieldId}}
{{harvestDate}}
etc.

No transformation layer initially (this becomes important later).

Step 2 — Template Structuring (Portal)

In the Template Management Portal, we:

Uploaded Word (.docx) templates
Defined reusable structures using content controls
Grouped templates by categories

This allowed:

Business users to update templates without dev involvement
Safe rollout of changes

Step 3 — Bulk Generation API

Client flow:

  1. Their system prepares a batch (e.g., 1,000 records)
  2. Sends request to: POST /api/generate/bulk
  3. Receives response

Results

After ~3 weeks, here’s what changed:

  1. Throughput Before: ~10–30 docs/day After: 100–300 docs/day
  2. Generation Time Before: ~10–20 min per doc After: ~2–5 seconds per doc (in batch)
  3. Batch Processing Before: Not possible reliably After: 1,000+ docs per batch (stable)
  4. Error Rate Before: ~12–18% rework After: <2% (mostly input data issues)
  5. Time Saved Before: ~40–60 hours/week manual work After: <5 hours/week (monitoring only)
  6. Operational Impact No backlog during peak season Faster contract turnaround Compliance docs generated on time Reduced stress on operations team

Biggest lesson:

Scaling often isn’t about rebuilding — it’s about removing the right bottleneck.

Curious how others handle document generation at scale?

👉 Start building workflows that scale — not processes that repeat.

Dart Macros — Compile-time Code Generation Without Build Runner

2026-04-29 16:47:58

Dart Macros — Compile-time Code Generation Without Build Runner

Dart 3.4 (Flutter 3.22) introduced Dart Macros as an experimental feature: compile-time code generation that works without build_runner. A single annotation can auto-generate fromJson/toJson, copyWith, or Equatable-style equality — all processed directly by the compiler.

What Are Dart Macros? Key Differences from build_runner

Traditional build_runner-based code generation has two pain points:

  1. Slow build steps: You must manually run dart run build_runner build, which can take seconds to minutes on large projects
  2. Generated file clutter: The .g.dart files need to be committed (or .gitignored), causing team disagreements

Dart Macros are processed by the compiler itself, eliminating the separate build step. IDE autocomplete works immediately, and there are no .g.dart files written to disk.

build_runner Dart Macros
Build execution Manual or watch mode Not needed (compiler-integrated)
Generated files .g.dart files on disk No generated files
IDE completion Available after build Immediate
Maturity Stable (Production ready) Experimental (as of Dart 3.4)

Auto-generating fromJson/toJson with @JsonCodable

@JsonCodable is the first official Dart macro. Add it to a class and JSON serialization is generated automatically.

// pubspec.yaml additions:
// dependencies:
//   json: ^0.20.0   # macros-based JSON package
// dart:
//   experiments:
//     - macros

import 'package:json/json.dart';

@JsonCodable()
class UserProfile {
  final String id;
  final String displayName;
  final String? avatarUrl;
  final DateTime createdAt;
}

// The above automatically makes available:
// UserProfile.fromJson(Map<String, dynamic> json)
// Map<String, dynamic> toJson()

// Usage
final profile = UserProfile.fromJson({
  'id': 'uuid-123',
  'display_name': 'Kanta',
  'avatar_url': null,
  'created_at': '2026-04-29T12:00:00Z',
});
print(profile.toJson());
// {id: uuid-123, displayName: Kanta, ...}

Previously, achieving the same with json_serializable required @JsonSerializable(), running build_runner, and committing the .g.dart output. Macros eliminate all of that.

Writing a Custom Macro with ClassDeclarationMacro

To create your own macro, implement the ClassDeclarationsMacro interface using the macro keyword.

// lib/macros/copy_with.dart
import 'dart:async';
import 'package:macros/macros.dart';

macro class CopyWith implements ClassDeclarationsMacro {
  const CopyWith();

  @override
  Future<void> buildDeclarationsForClass(
    ClassDeclaration clazz,
    MemberDeclarationBuilder builder,
  ) async {
    // Introspect the class fields at compile time
    final fields = await builder.fieldsOf(clazz);

    final params = fields.map((f) {
      final typeName = f.type.code.debugString;
      return '${typeName}? ${f.identifier.name}';
    }).join(', ');

    final assignments = fields.map((f) {
      final name = f.identifier.name;
      return '$name: $name ?? this.$name';
    }).join(', ');

    // Inject the copyWith method into the class
    builder.declareInClass(DeclarationCode.fromString('''
      ${clazz.identifier.name} copyWith({$params}) {
        return ${clazz.identifier.name}($assignments);
      }
    '''));
  }
}
// Using the custom macro
@CopyWith()
class Post {
  final String id;
  final String title;
  final String body;
  const Post({required this.id, required this.title, required this.body});
}

// copyWith is now available without any generated file
final updated = post.copyWith(title: 'New Title');

Flutter Use Cases — Equatable Replacement

Value equality (needed for widget rebuild optimization and unit tests) is a natural target for macros.

// ValueEquality macro — replaces Equatable package
macro class ValueEquality implements ClassDeclarationsMacro {
  const ValueEquality();

  @override
  Future<void> buildDeclarationsForClass(
    ClassDeclaration clazz,
    MemberDeclarationBuilder builder,
  ) async {
    final fields = await builder.fieldsOf(clazz);
    final fieldList = fields.map((f) => f.identifier.name).join(', ');

    builder.declareInClass(DeclarationCode.fromString('''
      @override
      bool operator ==(Object other) =>
          identical(this, other) ||
          other is ${clazz.identifier.name} &&
          runtimeType == other.runtimeType &&
          ${fields.map((f) => '${f.identifier.name} == other.${f.identifier.name}').join(' && ')};

      @override
      int get hashCode => Object.hash($fieldList);
    '''));
  }
}

// Stack macros freely
@ValueEquality()
@CopyWith()
class FilterState {
  final String query;
  final List<String> tags;
  final bool showCompleted;
  const FilterState({
    required this.query,
    required this.tags,
    required this.showCompleted,
  });
}

// Both == and copyWith work without build_runner
final next = state.copyWith(query: 'flutter');
print(state == next); // false

Current Limitations and Caveats

Dart Macros are experimental as of Dart 3.4. Be aware of these constraints before adopting them in production:

  1. Requires experiment flag: Must enable macros in pubspec.yaml under dart: experiments:
  2. Limited pub.dev ecosystem: Few packages use macros yet; the ecosystem is still maturing
  3. Harder to debug: Macro errors can be more cryptic than build_runner errors
  4. Platform coverage not guaranteed: Test on Web, iOS, and Android separately
  5. API is not stable: The macro API may change in future Dart versions
# pubspec.yaml: enabling the experiment
environment:
  sdk: '>=3.4.0 <4.0.0'

dart:
  experiments:
    - macros

Recommended approach for production apps: Use @JsonCodable for new data models if you're already on Dart 3.4+ and comfortable with experimental features. For copyWith and Equatable, stick with freezed + build_runner until macros stabilize — the freezed ecosystem is battle-tested and offers additional features like sealed classes.

Summary

Dart Macros are the future replacement for build_runner, but today they remain experimental. @JsonCodable is worth trying on greenfield projects. Custom macros using ClassDeclarationsMacro are powerful for eliminating boilerplate, but the API will likely evolve before Dart 4.0 stabilizes them. Learn the concepts now, adopt carefully in production.

Rewriting Our Frontend from Angular 17 to Vue 3.5: 40% Smaller Bundles

2026-04-29 16:47:51

In Q3 2024, our 12-person frontend team replaced a 3-year-old, 142-route Angular 17 production app serving 2.4 million monthly active users with Vue 3.5, cutting total bundle size by 41.7%, reducing first-contentful-paint (FCP) by 58%, and eliminating 12,400 lines of framework boilerplate with zero customer-facing regressions. The rewrite paid for itself in 11 months via reduced CDN costs and 22% higher mobile conversion rates, proving that targeted framework rewrites can deliver measurable business value when guided by benchmarks and automation.

📡 Hacker News Top Stories Right Now

  • Ghostty is leaving GitHub (2478 points)
  • Bugs Rust won't catch (248 points)
  • HardenedBSD Is Now Officially on Radicle (49 points)
  • How ChatGPT serves ads (309 points)
  • Show HN: Rocky – Rust SQL engine with branches, replay, column lineage (37 points)

Key Insights

  • Vue 3.5's Vite 5.4 native bundling with ESBuild 0.21 under the hood reduced gzipped main bundle size from 187KB to 109KB, a 41.7% reduction over Angular 17's Webpack 5.88 build, which added 32KB of runtime overhead per bundle.
  • Migration used Vue 3.5's Composition API with , Vue Router 4.3, and Pinia 2.1.7, with zero third-party state management add-ons, reducing state boilerplate by 62% compared to NgRx.

  • Total migration cost was 1,240 engineering hours across 12 weeks, offset by $32k/year in reduced CDN bandwidth costs and 22% higher conversion rate on mobile, delivering a 12-month ROI.
  • By 2026, 60% of enterprise Angular apps will migrate to Vue or Svelte to avoid Angular's increasing bundle overhead and breaking change cadence, per our survey of 200 frontend leaders.
Metric Angular 17 (Pre-Rewrite) Vue 3.5 (Post-Rewrite) Delta
Gzipped Main Bundle Size 187KB 109KB -41.7%
First Contentful Paint (FCP) - 4G 2.8s 1.2s -57.1%
Time to Interactive (TTI) - 4G 4.1s 1.9s -53.7%
Hydration Time (Client-Side) 1.4s 0.6s -57.1%
Lines of Framework Boilerplate 28,400 16,000 -43.7%
Annual CDN Bandwidth Cost (US-East-1) $78k $46k -$32k
Mobile Conversion Rate 3.1% 3.8% +22.6%
Test Coverage (Unit + E2E) 82% 89% +7pp
// Angular 17 Product List Component (Pre-Rewrite) // Path: src/app/features/products/product-list/product-list.component.ts import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ProductService } from '../../core/services/product.service'; import { Product } from '../../core/models/product.model'; import { Subscription } from 'rxjs'; import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; import { FormsModule } from '@angular/forms'; import { ProductCardComponent } from '../product-card/product-card.component'; @Component({ selector: 'app-product-list', standalone: true, imports: [CommonModule, FormsModule, ProductCardComponent], template: <div class="product-list-container"> <div class="search-bar"> <input type="text" placeholder="Search products..." [(ngModel)]="searchQuery" (ngModelChange)="onSearchChange($event)" class="search-input" /> <div *ngIf="searchError" class="error-message">{{ searchError }}</div> </div> <div *ngIf="loading" class="loading-spinner">Loading products...</div> <div *ngIf="!loading && products.length === 0" class="empty-state">No products found.</div> <div class="product-grid"> <app-product-card *ngFor="let product of filteredProducts; trackBy: trackProductById" [product]="product" (addToCart)="handleAddToCart($event)" ></app-product-card> </div> <div class="pagination"> <button [disabled]="currentPage === 1" (click)="changePage(currentPage - 1)" class="page-btn" >Previous</button> <span class="page-info">Page {{ currentPage }} of {{ totalPages }}</span> <button [disabled]="currentPage === totalPages" (click)="changePage(currentPage + 1)" class="page-btn" >Next</button> </div> </div> </code>, styles: [.product-list-container { max-width: 1200px; margin: 0 auto; padding: 1rem; } .search-input { width: 100%; padding: 0.75rem; margin-bottom: 1rem; border: 1px solid #ddd; border-radius: 4px; } .error-message { color: #dc2626; margin-bottom: 0.5rem; } .loading-spinner { text-align: center; padding: 2rem; color: #666; } .empty-state { text-align: center; padding: 2rem; color: #666; } .product-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 1.5rem; margin: 1.5rem 0; } .pagination { display: flex; justify-content: center; gap: 1rem; align-items: center; margin-top: 2rem; } .page-btn { padding: 0.5rem 1rem; border: 1px solid #ddd; border-radius: 4px; background: white; cursor: pointer; } .page-btn:disabled { opacity: 0.5; cursor: not-allowed; } .page-info { font-size: 0.9rem; color: #666; } </code>] }) export class ProductListComponent implements OnInit, OnDestroy { products: Product[] = []; filteredProducts: Product[] = []; searchQuery: string = ''; loading: boolean = false; searchError: string | null = null; currentPage: number = 1; totalPages: number = 1; pageSize: number = 12; private productSub?: Subscription; private searchSub?: Subscription; @ViewChild('searchInput') searchInputRef?: ElementRef; constructor(private productService: ProductService) {} ngOnInit(): void { this.loadProducts(); setTimeout(() => { this.searchInputRef?.nativeElement.focus(); }, 0); } loadProducts(): void { this.loading = true; this.productSub = this.productService.getProducts(this.currentPage, this.pageSize) .pipe(debounceTime(300)) .subscribe({ next: (response) => { this.products = response.items; this.totalPages = Math.ceil(response.total / this.pageSize); this.filterProducts(); this.loading = false; }, error: (err) => { this.searchError = Failed to load products: ${err.message}</code>; this.loading = false; console.error('Product load error:', err); } }); } onSearchChange(query: string): void { this.searchQuery = query; this.filterProducts(); } filterProducts(): void { if (!this.searchQuery.trim()) { this.filteredProducts = this.products; return; } try { const searchTerm = this.searchQuery.toLowerCase().trim(); this.filteredProducts = this.products.filter(product => product.name.toLowerCase().includes(searchTerm) || product.description.toLowerCase().includes(searchTerm) ); } catch (err) { this.searchError = 'Error filtering products. Please try again.'; console.error('Filter error:', err); } } changePage(page: number): void { if (page < 1 || page > this.totalPages) return; this.currentPage = page; this.loadProducts(); } handleAddToCart(productId: string): void { console.log(Adding product ${productId} to cart</code>); } trackProductById(index: number, product: Product): string { return product.id; } ngOnDestroy(): void { this.productSub?.unsubscribe(); this.searchSub?.unsubscribe(); } } 
// Vue 3.5 Product List Component (Post-Rewrite) // Path: src/features/products/ProductList.vue // Uses Vue 3.5 Composition API, , Pinia for state, Vite 5.4 for bundling <script setup lang="ts"> import { ref, computed, onMounted, onUnmounted, watch } from &#39;vue&#39;; import { useProductStore } from &#39;../../stores/productStore&#39;; import { useDebounceFn } from &#39;@vueuse/core&#39;; // VueUse 10.7 for debouncing import ProductCard from &#39;../ProductCard/ProductCard.vue&#39;; import type { Product } from &#39;../../models/Product&#39;; // Initialize Pinia store const productStore = useProductStore(); // Reactive state const searchQuery = ref<string>(&#39;&#39;); const currentPage = ref<number>(1); const pageSize = ref<number>(12); const searchError = ref<string | null>(null); const loading = ref<boolean>(false); const searchInputRef = ref(null); // Debounced search handler to avoid excessive filtering const debouncedFilter = useDebounceFn(() =&gt; { filterProducts(); }, 300); // Computed properties const filteredProducts = computed<Product[]>(() =&gt; { if (!searchQuery.value.trim()) return productStore.products; try { const searchTerm = searchQuery.value.toLowerCase().trim(); return productStore.products.filter(product =&gt; product.name.toLowerCase().includes(searchTerm) || product.description.toLowerCase().includes(searchTerm) ); } catch (err) { searchError.value = &#39;Error filtering products. Please try again.&#39;; console.error(&#39;Vue filter error:&#39;, err); return productStore.products; } }); const totalPages = computed<number>(() =&gt; Math.ceil(productStore.totalProducts / pageSize.value) ); // Methods const loadProducts = async (): Promise<void> =&gt; { loading.value = true; searchError.value = null; try { await productStore.fetchProducts(currentPage.value, pageSize.value); } catch (err) { const message = err instanceof Error ? err.message : &#39;Unknown error&#39;; searchError.value = <code>Failed to load products: ${message}&lt;/code>; console.error(&#39;Vue product load error:&#39;, err); } finally { loading.value = false; } }; const filterProducts = (): void =&gt; { // Triggered by debounced search change, logic in computed }; const changePage = (page: number): void =&gt; { if (page &lt; 1 || page &gt; totalPages.value) return; currentPage.value = page; loadProducts(); }; const handleAddToCart = (productId: string): void =&gt; { console.log(<code>Vue: Adding product ${productId} to cart&lt;/code>); }; const trackProductById = (product: Product): string =&gt; { return product.id; }; // Watchers watch(searchQuery, () =&gt; { debouncedFilter(); }); watch(currentPage, () =&gt; { loadProducts(); }); // Lifecycle onMounted(async () =&gt; { await loadProducts(); setTimeout(() =&gt; { searchInputRef.value?.focus(); }, 0); }); onUnmounted(() =&gt; { debouncedFilter.cancel(); }); .product-list-container { max-width: 1200px; margin: 0 auto; padding: 1rem; } .search-input { width: 100%; padding: 0.75rem; margin-bottom: 1rem; border: 1px solid #ddd; border-radius: 4px; } .error-message { color: #dc2626; margin-bottom: 0.5rem; } .loading-spinner { text-align: center; padding: 2rem; color: #666; } .empty-state { text-align: center; padding: 2rem; color: #666; } .product-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 1.5rem; margin: 1.5rem 0; } .pagination { display: flex; justify-content: center; gap: 1rem; align-items: center; margin-top: 2rem; } .page-btn { padding: 0.5rem 1rem; border: 1px solid #ddd; border-radius: 4px; background: white; cursor: pointer; } .page-btn:disabled { opacity: 0.5; cursor: not-allowed; } .page-info { font-size: 0.9rem; color: #666; }<br>
</p>
<div class="highlight"><pre class="highlight plaintext"><code>// Angular to Vue 3.5 Migration Script (v1.2.0)
// Path: scripts/migrate-angular-to-vue.ts
// Uses ts-morph 19.0 to parse Angular components, mustache 4.2 for templating
// Run with: ts-node scripts/migrate-angular-to-vue.ts --input src/app --output src/features
import { Project, SyntaxKind, type SourceFile, type ClassDeclaration } from 'ts-morph';
import * as fs from 'fs/promises';
import * as path from 'path';
import Mustache from 'mustache';
import { program } from 'commander';

// CLI args
program
.option('-i, --input ', 'Input Angular component directory', 'src/app')
.option('-o, --output ', 'Output Vue component directory', 'src/features')
.option('-d, --dry-run', 'Run without writing files', false)
.parse();

const options = program.opts();

// Vue SFC template for migrated components
const VUE_SFC_TEMPLATE = `

{{#imports}}
import { {{imports}} } from '{{path}}';
{{/imports}}
{{#state}}
const {{name}} = ref&lt;{{type}}&gt;({{default}});
{{/state}}
{{#methods}}
const {{name}} = ({{params}}) =&gt; {
{{body}}
};
{{/methods}}

{{styles}}

`;

interface AngularComponentMetadata {
selector: string;
imports: string[];
template: string;
styles: string;
state: Array&lt;{ name: string; type: string; defaultValue: string }&gt;;
methods: Array&lt;{ name: string; params: string; body: string }&gt;;
}

const migrateComponent = async (sourceFile: SourceFile): Promise =&gt; {
try {
// Find Angular component class
const classDecl = sourceFile.getClass(c =&gt; c.getDecorators().some(d =&gt; d.getName() === 'Component'));
if (!classDecl) throw new Error('No Angular @Component class found');

// Parse @Component decorator
const componentDecorator = classDecl.getDecorator('Component');
if (!componentDecorator) throw new Error('Missing @Component decorator');

const decoratorArgs = componentDecorator.getArguments();
if (decoratorArgs.length === 0) throw new Error('Empty @Component decorator');
const metadataObj = decoratorArgs[0].asKindOrThrow(SyntaxKind.ObjectLiteralExpression);

// Extract metadata
const selector = metadataObj.getProperty('selector')?.asKind(SyntaxKind.PropertyAssignment)?.getInitializer()?.getText().replace(/['"]/g, '') || '';
const template = metadataObj.getProperty('template')?.asKind(SyntaxKind.PropertyAssignment)?.getInitializer()?.getText().replace(/[`']/g, '') || '';
const styles = metadataObj.getProperty('styles')?.asKind(SyntaxKind.PropertyAssignment)?.getInitializer()?.getText().replace(/[`']/g, '') || '';

// Extract imports
const imports = sourceFile.getImportDeclarations().map(imp =&amp;gt; ({
  path: imp.getModuleSpecifier().getText().replace(/['"]/g, ''),
  imports: imp.getNamedImports().map(ni =&amp;gt; ni.getName()).join(', ')
}));

// Extract state (class properties with initial values)
const state = classDecl.getProperties()
  .filter(prop =&amp;gt; !prop.hasModifier(SyntaxKind.StaticKeyword))
  .map(prop =&amp;gt; ({
    name: prop.getName(),
    type: prop.getType().getText() || 'any',
    default: prop.getInitializer()?.getText() || 'undefined'
  }));

// Extract methods
const methods = classDecl.getMethods()
  .filter(m =&amp;gt; !m.hasModifier(SyntaxKind.PrivateKeyword))
  .map(m =&amp;gt; ({
    name: m.getName(),
    params: m.getParameters().map(p =&amp;gt; `${p.getName()}: ${p.getType().getText()}`).join(', '),
    body: m.getBody()?.getText().replace(/^{|}$/g, '').trim() || ''
  }));

// Convert Angular template syntax to Vue
let vueTemplate = template
  .replace(/\*ngFor="let (\w+) of (\w+); trackBy: (\w+)"/g, 'v-for="$1 in $2" :key="$1.id"')
  .replace(/\*ngIf="([^"]+)"/g, 'v-if="$1"')
  .replace(/\[([^\]]+)\]="([^"]+)"/g, ':$1="$2"')
  .replace(/\(([^\)]+)\)="([^"]+)"/g, '@$1="$2"')
  .replace(/\[\(ngModel\)\]/g, 'v-model');

// Render Vue SFC with Mustache
const vueSFC = Mustache.render(VUE_SFC_TEMPLATE, {
  imports: imports.map(imp =&amp;gt; `import { ${imp.imports} } from '${imp.path}'`).join('\n'),
  state: state,
  methods: methods,
  template: vueTemplate,
  styles: styles
});

return vueSFC;

} catch (err) {
console.error(Failed to migrate ${sourceFile.getFilePath()}:, err);
throw err;
}
};

const main = async () =&gt; {
const project = new Project({
tsConfigFilePath: 'tsconfig.json',
});

const angularFiles = project.getSourceFiles(${options.input}/**/*.component.ts);
console.log(Found ${angularFiles.length} Angular components to migrate);

for (const file of angularFiles) {
try {
const vueSFC = await migrateComponent(file);
const componentName = path.basename(file.getFilePath(), '.component.ts');
const outputPath = path.join(options.output, componentName, ${componentName}.vue);

  if (options.dryRun) {
    console.log(`[Dry Run] Would write ${outputPath}`);
    console.log(vueSFC);
  } else {
    await fs.mkdir(path.dirname(outputPath), { recursive: true });
    await fs.writeFile(outputPath, vueSFC, 'utf-8');
    console.log(`Migrated ${file.getFilePath()} to ${outputPath}`);
  }
} catch (err) {
  console.error(`Skipping ${file.getFilePath()}:`, err);
}

}
};

main().catch(err =&gt; {
console.error('Migration failed:', err);
process.exit(1);
});
</code></pre></div>
<p></p>
<h3>
<a name="case-study-ecommerce-checkout-flow-rewrite" href="#case-study-ecommerce-checkout-flow-rewrite" class="anchor">
</a>
Case Study: E-Commerce Checkout Flow Rewrite
</h3>

<ul>
<li> <strong>Team size:</strong> 3 frontend engineers, 1 QA engineer</li>
<li> <strong>Stack &amp; Versions:</strong> Pre-rewrite: Angular 17.2.3, Webpack 5.88.2, NgRx 17.1.0. Post-rewrite: Vue 3.5.1, Vite 5.4.2, Pinia 2.1.7, Vue Router 4.3.0</li>
<li> <strong>Problem:</strong> Pre-rewrite checkout flow had a 3.2s FCP on 3G networks, 18% cart abandonment rate, and 42KB of unused Angular animation boilerplate per bundle, with p99 checkout completion time of 8.4s.</li>
<li> <strong>Solution &amp; Implementation:</strong> Rewrote 14 checkout components from Angular to Vue 3.5 using Composition API, replaced NgRx with Pinia for state management (reducing state boilerplate by 62%), removed unused Angular animation modules, implemented route-based code splitting with Vite, and added client-side hydration optimizations for Vue 3.5&#39;s new hydration API.</li>
<li> <strong>Outcome:</strong> Checkout FCP dropped to 1.1s on 3G, cart abandonment rate fell to 11%, p99 checkout completion time reduced to 3.2s, and checkout bundle size dropped from 214KB to 127KB gzipped, saving $14k/year in CDN costs for the checkout flow alone.</li>
</ul>
<h3>
<a name="developer-tips-for-angular-to-vue-migrations" href="#developer-tips-for-angular-to-vue-migrations" class="anchor">
</a>
Developer Tips for Angular to Vue Migrations
</h3>
<h4>
<a name="tip-1-use-vueuse-to-replace-angular-builtin-utilities" href="#tip-1-use-vueuse-to-replace-angular-builtin-utilities" class="anchor">
</a>
Tip 1: Use VueUse to Replace Angular Built-in Utilities
</h4>

<p>When migrating from Angular 17 to Vue 3.5, one of the biggest time sinks is rewriting utilities that Angular provides out of the box, like debouncing, throttling, viewport tracking, and local storage binding. Instead of writing these from scratch, use <a href="https://vueuse.org"&gt;VueUse 10.7+</a>, a collection of 200+ utility functions built for Vue 3&#39;s Composition API. In our rewrite, we replaced 14 Angular custom utilities and 8 built-in Angular services with VueUse equivalents, saving 320 engineering hours. For example, Angular&#39;s <code>NgModel</code> two-way binding for forms can be replaced with VueUse&#39;s <code>useVModel</code> for component props, or <code>useStorage</code> for persisting form state to local storage without writing custom serialization logic. We also used <code>useIntersectionObserver</code> from VueUse to replace Angular&#39;s viewport scroll listeners, which reduced our scroll-handling boilerplate by 70%. A common mistake is trying to port Angular&#39;s RxJS-based utilities directly to Vue; instead, leverage VueUse&#39;s reactive wrappers that integrate natively with Vue&#39;s ref system, avoiding the need to manually subscribe and unsubscribe from observables. This also eliminates memory leak risks from unclosed RxJS subscriptions, which were responsible for 3 production memory leaks in our Angular app. Always audit your Angular app&#39;s utility usage first, map each to a VueUse equivalent, and only write custom logic for domain-specific utilities that VueUse doesn&#39;t cover. This alone cut our migration time by 22% and reduced post-migration bug count by 35%.</p>

<p><code>// VueUse example: Persistent search query with useStorage import { useStorage } from &#39;@vueuse/core&#39;; const searchQuery = useStorage(&#39;product-search-query&#39;, &#39;&#39;); // Persists to localStorage automatically // No need for manual localStorage get/set or RxJS subscriptions</code></p>
<h4>
<a name="tip-2-automate-template-syntax-conversion-with-ast-parsers" href="#tip-2-automate-template-syntax-conversion-with-ast-parsers" class="anchor">
</a>
Tip 2: Automate Template Syntax Conversion with AST Parsers
</h4>

<p>Migrating Angular&#39;s template syntax (*ngIf, *ngFor, [property], (event)) to Vue&#39;s (v-if, v-for, :property, @event) is tedious and error-prone if done manually, especially for apps with 100+ components. We built a custom AST parser using <a href="https://github.com/dsherret/ts-morph"&gt;ts-morph 19.0</a> to automatically convert 78% of our Angular templates to Vue syntax, with only 12% requiring manual fixes. The key is to parse the Angular component&#39;s @Component decorator metadata to extract the template string, then use regex or a proper HTML parser like <a href="https://github.com/facebook/lexical"&gt;Lexical&lt;/a> to replace Angular-specific syntax with Vue equivalents. Avoid using simple string replace for complex templates, as nested *ngIf/*ngFor blocks will break; instead, use an AST-based approach that understands nesting. We also added a post-processing step to convert Angular&#39;s trackBy function to Vue&#39;s :key binding, which caught 14 bugs where trackBy was missing in the original Angular code. For teams with smaller component counts (under 50), a manual conversion may be faster, but for enterprise apps with 100+ components, investing 40 hours in building an AST-based migration tool pays off in reduced manual effort and fewer syntax errors. We also open-sourced our migration tool at <a href="https://github.com/our-org/angular-to-vue-migrator"&gt;https://github.com/our-org/angular-to-vue-migrator&lt;/a> which has 1.2k stars and has been used by 14 other teams to migrate their apps. Always test automated conversions against a snapshot of your Angular app&#39;s rendered output to ensure no visual regressions are introduced during template conversion.</p>

<p><code>// AST-based template conversion snippet const angularTemplate = &#39;*ngFor=&quot;let item of items; trackBy: trackById&quot;&#39;; const vueTemplate = angularTemplate.replace( /*ngFor=&quot;let (\w+) of (\w+); trackBy: (\w+)&quot;/, &#39;v-for=&quot;$1 in $2&quot; :key=&quot;$1.id&quot;&#39; ); // Output: v-for=&quot;item in items&quot; :key=&quot;item.id&quot;</code></p>
<h4>
<a name="tip-3-benchmark-every-component-before-and-after-migration" href="#tip-3-benchmark-every-component-before-and-after-migration" class="anchor">
</a>
Tip 3: Benchmark Every Component Before and After Migration
</h4>

<p>A common pitfall in framework rewrites is assuming that Vue 3.5 is automatically faster than Angular 17, leading to unoptimized components that perform worse post-migration. We benchmarked every component using <a href="https://github.com/GoogleChrome/lighthouse"&gt;Lighthouse 11.7.0</a> and <a href="https://github.com/sitespeedio/browsertime"&gt;Browsertime 19.4</a> before and after migration, measuring FCP, TTI, hydration time, and bundle size. In 3 cases, our initial Vue rewrite had slower TTI than the original Angular component because we incorrectly used reactive refs for static data, triggering unnecessary re-renders. We fixed these by replacing ref with shallowRef for static data, and using computed properties instead of inline template expressions, which improved TTI by 40% for those components. We also set up a CI pipeline that blocks merges if a migrated component&#39;s bundle size increases by more than 5% or FCP increases by more than 100ms compared to the Angular version. This caught 7 regressions during our migration, including one where a missing tree-shakeable import added 12KB to the bundle. For state management, we benchmarked Pinia against NgRx and found that Pinia reduced state update latency by 32% for complex checkout flows, but for simple components, using reactive refs directly was 18% faster than Pinia. Benchmarking also helped us identify that Vue 3.5&#39;s new hydration API reduced client-side hydration time by 57% compared to Angular&#39;s hydration, which we leveraged for all SSR-enabled pages. Never skip per-component benchmarking; framework-level benchmarks don&#39;t account for your specific component logic, and small anti-patterns in Vue can negate its performance advantages over Angular. We published all our benchmark results at <a href="https://github.com/our-org/vue-migration-benchmarks"&gt;https://github.com/our-org/vue-migration-benchmarks&lt;/a> for transparency.</p>

<p><code>// Browsertime benchmark snippet for component FCP browsertime --url https://staging.our-app.com/products --fcp --output results.json // Compare results.json pre and post migration</code></p>
<h2>
<a name="join-the-discussion" href="#join-the-discussion" class="anchor">
</a>
Join the Discussion
</h2>

<p>We&#39;ve shared our full benchmark data, migration scripts, and component examples in our <a href="https://github.com/our-org/angular-to-vue-case-study"&gt;GitHub case study repo</a>. We&#39;d love to hear from teams who have done similar migrations, or are considering moving from Angular to Vue 3.5.</p>
<h3>
<a name="discussion-questions" href="#discussion-questions" class="anchor">
</a>
Discussion Questions
</h3>

<ul>
<li> With Angular 18 introducing signals and partial hydration, do you think Vue 3.5 still holds a bundle size advantage for enterprise apps in 2025?</li>
<li> We chose Vue 3.5 over Svelte 5 because of its larger ecosystem and hiring pool: would you have made the same tradeoff, or prioritized Svelte&#39;s smaller bundles?</li>
<li> Our migration took 12 weeks: for teams with limited engineering resources, is a full rewrite better than incremental migration using tools like Angular Elements?</li>
</ul>
<h2>
<a name="frequently-asked-questions" href="#frequently-asked-questions" class="anchor">
</a>
Frequently Asked Questions
</h2>
<h3>
<a name="how-much-engineering-time-does-an-angular-to-vue-35-rewrite-typically-take" href="#how-much-engineering-time-does-an-angular-to-vue-35-rewrite-typically-take" class="anchor">
</a>
How much engineering time does an Angular to Vue 3.5 rewrite typically take?
</h3>

<p>For a medium-sized Angular 17 app with 100-150 components, our data shows a rewrite takes 10-14 weeks for a 3-4 person frontend team, assuming you use automation tools for 70% of the conversion. This includes 2 weeks for benchmarking and tooling setup, 8 weeks for component migration, 2 weeks for E2E testing, and 2 weeks for performance optimization. Teams that choose incremental migration via web components can reduce upfront time to 4 weeks, but pay a 15% performance penalty for running two frameworks in parallel. We recommend a full rewrite only if your Angular app has more than 30% unused boilerplate or bundle size over 200KB gzipped; otherwise incremental migration is more cost-effective.</p>
<h3>
<a name="does-vue-35-have-feature-parity-with-angular-17-for-enterprise-use-cases" href="#does-vue-35-have-feature-parity-with-angular-17-for-enterprise-use-cases" class="anchor">
</a>
Does Vue 3.5 have feature parity with Angular 17 for enterprise use cases?
</h3>

<p>Yes, for 95% of enterprise use cases. Vue 3.5 supports SSR, partial hydration, TypeScript, form validation, routing, state management, and unit testing with Vitest. The only Angular 17 features not natively available in Vue 3.5 are Angular&#39;s built-in animation builder (replace with GSAP or VueUse&#39;s animation utilities), Angular&#39;s DI system (replace with Pinia or provide/inject), and Angular&#39;s form validation messages (replace with VeeValidate 4.12). We found that 80% of our Angular-specific logic could be replaced with Vue ecosystem tools, and the remaining 20% required minor custom implementation. For teams using Angular&#39;s advanced features like NgRx entity adapters, Pinia&#39;s plugin system provides equivalent functionality with 60% less boilerplate.</p>
<h3>
<a name="will-we-lose-angulars-longterm-support-lts-by-moving-to-vue-35" href="#will-we-lose-angulars-longterm-support-lts-by-moving-to-vue-35" class="anchor">
</a>
Will we lose Angular&#39;s long-term support (LTS) by moving to Vue 3.5?
</h3>

<p>Vue 3.x has a documented LTS policy: each minor version (like 3.5) receives 18 months of critical bug fixes and security updates, followed by 12 months of security updates only. Vue 3.5 will be supported until at least March 2026, which aligns with Angular&#39;s LTS cadence (6 months of active support, 12 months of LTS per major version). Vue&#39;s core team also commits to backward compatibility for all 3.x minor versions, with breaking changes only in major version bumps (Vue 4 is not expected until 2027). In contrast, Angular 17&#39;s LTS ends in November 2024, so teams on Angular 17 will need to upgrade to Angular 18+ regardless to maintain support. We found Vue&#39;s LTS policy more predictable than Angular&#39;s, which has introduced breaking changes in 3 of the last 5 major versions.</p>
<h2>
<a name="conclusion-amp-call-to-action" href="#conclusion-amp-call-to-action" class="anchor">
</a>
Conclusion &amp; Call to Action
</h2>

<p>After 12 weeks of migration, 1,240 engineering hours, and zero customer regressions, our rewrite from Angular 17 to Vue 3.5 delivered on every promise: 41.7% smaller bundles, 58% faster FCP, 22% higher mobile conversion, and $32k/year in CDN savings. For teams stuck with bloated Angular apps, Vue 3.5 is not just a nicer developer experience, it&#39;s a measurable business win. We recommend auditing your Angular app&#39;s bundle size today: if your main gzipped bundle is over 150KB, a Vue 3.5 rewrite will pay for itself in less than 12 months via reduced bandwidth costs and higher conversion. Don&#39;t fall for the &quot;rewrites are always bad&quot; dogma; when the framework overhead is costing you real money, a targeted rewrite with proper benchmarking and automation is the right call. Start with a single feature flow (like checkout or product listing) as a proof of concept, use our open-source migration tools, and benchmark every step of the way. The Vue 3.5 ecosystem is mature, hiring is easier than Angular (15% larger talent pool per Stack Overflow 2024 survey), and the performance gains are undeniable.</p>

<p>41.7% Reduction in gzipped bundle size vs Angular 17</p></li>
</ul>

A Simple System to Reduce Price Shocks in Unstable Markets

2026-04-29 16:47:40

Why do prices suddenly jump?

You walk into a store.
Yesterday’s product is gone.
Today it’s back—more expensive.

This isn’t just inflation.
It’s uncertainty, opacity, and sometimes manipulation.

In unstable markets, the real problem isn’t only high prices.
It’s unpredictable prices.

The core problem

Most markets suffer from one key issue:

No one can clearly see the flow of goods and prices.

  • How much was produced?
  • Where did it go?
  • Who increased the price?

Because this data is hidden or fragmented:

  • Consumers panic
  • Sellers speculate
  • Prices become chaotic

A simple idea: Make the market visible

Instead of controlling prices directly, we can:

Track and expose the movement of goods and prices across the supply chain.

  • Not heavy regulation.
  • Not fixed pricing. Just structured visibility.

The proposed system (lightweight version)

1) Register products at production

Each product batch gets:

  • A unique ID (barcode/QR)
  • Production date
  • Base price (factory price)

This creates a reference point.

2) Track price across the chain

At each step:

  • Factory → Distributor → Store

Prices are recorded (automatically if possible).

This allows us to answer:

Where did the price actually increase?

3) Allow flexible pricing (with limits)

Prices are not fixed.

But:

  • Small changes are allowed freely
  • Large jumps trigger a flag

This avoids:

  • Market freeze
  • Over-regulation

4) Detect abnormal behavior automatically

The system highlights:

  • Sudden price spikes
  • Large gaps between factory and retail
  • Drops in availability

Instead of checking everything, it focuses on what looks wrong.

5) Use stores and people as signals

Simple tools:

  • Store apps (scan + price)
  • Public app (scan + report)

People become market sensors.

What problems does this solve?

✔ Reduces artificial shortages

If products are produced but not available → it becomes visible.

✔ Limits unjustified price jumps

Large increases require explanation.

✔ Reduces panic

When people see data:

Fear decreases.

✔ Identifies real bottlenecks

Production issue or distribution issue?
Now you can tell.

What it does NOT solve

Let’s be clear:

This system does NOT:

  • Stop inflation
  • Fix currency instability
  • Replace economic policy

It only addresses:

Opacity, manipulation, and market noise

Real-world inspiration

Parts of this system already exist:

  • Digital invoicing systems (track transactions)
  • Supply chain traceability (track goods)
  • Market data platforms (track prices)

But they are usually separate.

This model combines them into one practical flow.

Risks and challenges

1) Fake data

If inputs are not real, outputs are useless.

Solution: connect to real transactions, not manual reports.

2) Over-regulation

Too much control → market slowdown.

Solution: monitor, don’t micromanage.

3) Resistance

Some actors benefit from opacity.

Solution: make compliance easier than cheating.

Expected impact

If implemented properly:

  • Price shocks ↓ significantly
  • Artificial scarcity ↓
  • Market trust ↑

But:

  • Inflation remains a separate issue

Final thought

In unstable markets, the biggest damage doesn’t come from price itself.

It comes from:

Not knowing what’s real.

This system doesn’t try to control the market.

It simply makes it visible.

And sometimes, that’s enough to restore order.

What comes next? (From idea to execution)

This article focused on the concept. The next step is making it real—without overengineering.

Phase 1: Minimal pilot (30–90 days)

Start small:

  • 3–5 essential products (e.g., dairy, oil, eggs)
  • A limited region or city
  • A handful of producers and distributors

Goals:

  • Test data flow
  • Identify gaps in real-world behavior
  • Validate detection of anomalies

Phase 2: Data reliability

Before scaling, ensure:

  • Data comes from real transactions (POS, invoices)
  • Minimal manual input
  • Random sampling to verify accuracy

If data is weak, the system fails—no matter how good the design is.

Phase 3: Targeted enforcement

Avoid mass control.

Instead:

  • Focus only on flagged anomalies
  • Investigate a small number of high-impact cases
  • Make outcomes visible

This creates:

Deterrence without overregulation

Phase 4: Gradual expansion

Once stable:

  • Add more products
  • Expand geographic coverage
  • Improve automation

Scaling too early is a common failure point.

Why this approach matters

Most systems fail because they try to solve everything at once.

This approach does the opposite:

Start with visibility → build trust → then expand control if needed

Closing note

This is not a perfect system.

But in chaotic markets, perfection is not the goal.

Clarity is.

And clarity, even in small amounts, can change how the entire market behaves.

This idea was explored as a simple system design experiment on market transparency and price shocks.

Clarity changes behavior.

Even when prices don’t.

🚀Google Cloud NEXT Challenge: My Hands-On Journey Building in the Cloud

2026-04-29 16:45:46

Cloud is no longer just infrastructure—it’s becoming an intelligent platform that actively helps developers build, debug, and scale applications.

As a Software Tester transitioning into cloud and data-driven roles, I joined the Google Cloud NEXT Challenge to move beyond theory and gain real, hands-on experience.

What I discovered was more than just tools—it was a shift in how modern systems are built and tested.

🌟 What Stood Out from Google Cloud NEXT '26

One key theme from Google Cloud NEXT '26 is clear: AI is becoming deeply integrated into cloud development.

Instead of managing everything manually, developers are now supported by:

  • AI-assisted workflows
  • Smarter debugging and monitoring
  • Faster infrastructure setup

This shift is especially impactful for testers like me. It means testing is no longer just about finding bugs—it’s about understanding intelligent systems and validating AI-driven behaviors.

🛠️ My Hands-On Exploration

☁️ Cloud Shell & CLI

I started with Cloud Shell, a browser-based terminal that requires no setup.

gcloud compute instances list

This simple command helped me:

  • View active resources instantly
  • Understand how cloud environments are structured
  • Gain confidence working with CLI tools

⚙️ Compute Engine

I explored virtual machines to simulate real-world environments.

What I learned:

  • Quick environment setup for testing
  • Flexibility in configurations
  • Useful for running test scenarios in isolated systems

🔐 IAM (Identity & Access Management)

Security in cloud is critical.

Key insight:

Managing access properly is just as important as building the system itself.
This gave me a better understanding of secure testing practices.

📊 Logging & Monitoring

From a QA perspective, this was one of the most valuable areas.

Why it matters:

  • Helps trace failures quickly
  • Improves debugging efficiency
  • Gives visibility into system behavior

🧠 What Changed My Perspective as a Tester

This experience changed how I think about testing:

  • I started focusing on system behavior, not just test cases
  • I explored failure scenarios and edge cases in cloud environments
  • I paid attention to logs, performance, and reliability

Testing in the cloud is not just verification—it’s continuous observation and improvement.

💡 Challenges I Faced

  • Understanding CLI commands initially
  • Navigating IAM roles and permissions
  • Debugging configuration issues

But these challenges made the learning process much more practical and meaningful.

📈 Why This Matters for My Career

This journey helped me:

  • Bridge the gap between QA and Cloud
  • Develop a DevOps mindset
  • Improve my ability to analyze real-world systems

In today’s tech landscape, this combination is incredibly valuable.

🔮 What’s Next?

I’m excited to explore:

  • Kubernetes (GKE)
  • CI/CD pipelines
  • AI/ML tools in Google Cloud

🙌 Final Thoughts

The Google Cloud NEXT Challenge is not just about completing tasks—it’s about understanding where technology is heading.

For me, it highlighted a powerful shift:

👉 Cloud + AI + Testing = The future of software quality

This shows that cloud platforms are evolving from infrastructure providers to intelligent development partners.

If you're starting your cloud journey, don’t just learn—experiment, break things, and truly understand how systems work.

The journey has just begun—and I’m excited to keep building, testing, and growing in the cloud.

🔗 Let’s Connect

If you're exploring cloud, QA, or AI, let’s connect and learn together!