Por qué usamos una API para validar NIF, NIE y CIF en vez de implementarlo nosotros mismos

Hace unos meses estaba integrando validación de identificadores fiscales en un proyecto. Busqué en Google, encontré una función de 30 líneas en JavaScript, la copié, la probé con cuatro casos y funcionó. La metí en el código y la olvidé.

Tres meses después, un usuario nos mandó un email: su CIF no validaba. Era un CIF perfectamente válido.

Ese día entendí que hay una diferencia entre implementar la validación y mantenerla correctamente.

El problema no es que sea difícil

Validar un NIF es sencillo en apariencia: 8 dígitos, una letra, módulo 23. Cualquier dev lo implementa en diez minutos.

El problema aparece cuando rascas un poco:

NIF: el algoritmo básico funciona, pero hay formatos especiales — NIFs K, L y M para casos específicos que muchas implementaciones directamente ignoran.

NIE: empieza por X, Y o Z, luego sigue el algoritmo del NIF con sustitución. La mayoría de regex que encontrarás online aceptan tanto 7 como 8 dígitos indistintamente, cuando el formato correcto depende del prefijo.

CIF: aquí es donde casi todo falla. El dígito de control puede ser una letra o un número dependiendo del tipo de entidad (primera letra del CIF). La mayoría de implementaciones lo aceptan indistintamente para todos los tipos, lo cual es incorrecto. Además, cuando (10 - suma % 10) % 10 === 0, el carácter de control debería ser J en ciertos casos — un edge case que genera bugs silenciosos muy difíciles de detectar.

IBAN: el algoritmo MOD-97 requiere aritmética con números muy grandes. En JavaScript necesitas BigInt, y en entornos donde no está disponible o se implementa mal, falla sin error visible.

Ninguno de estos bugs explota en desarrollo. Explotan en producción, con identificadores reales de usuarios reales.

El bug silencioso es el peor tipo de bug

Cuando una validación falla de forma visible — lanza una excepción, devuelve un error — lo detectas y lo corriges. El problema real con los bugs de validación fiscal es que aceptan identificadores inválidos sin avisar.

Un usuario introduce un CIF con el dígito de control incorrecto. Tu código dice “válido”. El dato entra en base de datos. Meses después, cuando intentas hacer una operación fiscal con ese dato, el problema emerge — pero ya no sabes de dónde viene.

El mantenimiento que nadie contabiliza

Aunque implementes todo correctamente hoy, hay otro problema: la normativa puede cambiar.

España ha actualizado el formato de los identificadores fiscales en el pasado. Los formatos de NIE han evolucionado. Cuando eso pasa, alguien tiene que actualizar el código — y ese alguien podría no ser la misma persona que lo escribió, ni estar en el mismo equipo, ni recordar por qué se tomó cada decisión.

Con una API, ese problema es de quien mantiene la API. Tú consumes el endpoint, ellos mantienen los algoritmos actualizados.

Lo que hicimos

Construimos Valix — una API REST que valida NIF, NIE, CIF e IBAN usando los algoritmos oficiales, con detección automática del tipo de identificador y respuesta JSON estructurada.

import requests

response = requests.post(
    "https://api.getvalix.io/v1/validate/trial",
    json={
        "items": [
            {"value": "12345678Z", "type": "AUTO"},
            {"value": "X1234567L", "type": "AUTO"},
            {"value": "A12345674", "type": "AUTO"},
            {"value": "ES9121000418450200051332", "type": "AUTO"}
        ]
    }
)

resultado = response.json()
for item in resultado["results"]:
    estado = "válido" if item["valid"] else f"inválido: {', '.join(item['errors'])}"
    print(f"{item['value']} → {item['detected_type']} — {estado}")

Salida:

12345678Z → NIF — válido
X1234567L → NIE — válido
A12345674 → CIF — válido
ES9121000418450200051332 → IBAN — válido

El endpoint trial no requiere registro ni API key — 50 validaciones diarias por IP para que puedas probarlo antes de comprometerte con nada.

¿Cuándo tiene sentido implementarlo tú mismo?

Siendo honestos: si tu volumen de validaciones es muy bajo, el identificador que validas es siempre el mismo tipo y tienes tests exhaustivos con casos reales, implementarlo internamente puede tener sentido.

Pero si validas identificadores de usuarios de producción, mezclas tipos (NIF + NIE + CIF + IBAN), o simplemente no quieres dedicar tiempo a mantener esto — una API es la opción más sensata.

Recursos

Disclaimer: soy el autor de Valix.

When a Memory Pool Actually Helps in Go Logging

When you build a high-throughput log pipeline in Go, the garbage collector quickly becomes one of your biggest bottlenecks. Every log line means new allocations: buffers, temporary structs, parsed JSON trees, and so on. At some point, you start wondering: is it time to use a memory pool?

In this post I’ll walk through a simple pattern using sync.Pool and explain when it is (and is not) a good idea for log pre-processing.

The basic pattern

For log processing, the most common thing to pool is a reusable byte buffer or struct used per log line.


var logBufferPool = sync.Pool{
    New: func() any {
        buf := make([]byte, 0, 64*1024) // 64KB buffer
        return buf
    },
}

func handleLog(raw []byte) {
    // 1. Take a buffer from the pool
    buf := logBufferPool.Get().([]byte)

    // 2. Use it for parsing / masking / rewriting
    buf = buf[:0]          // reset length, keep capacity
    buf = append(buf, raw) // do your processing on buf

    // 3. Return it to the pool
    buf = buf[:0]
    logBufferPool.Put(buf)
}

The key detail is buf = buf[:0]: this does not allocate a new array. It just resets the slice length to zero while keeping the underlying capacity. Combined with sync.Pool, this lets you reuse the same backing arrays across many log lines instead of calling make([]byte, …) thousands of times per second.

In a log pre-processor that parses, reshapes, and masks JSONL lines, this pattern can eliminate a large portion of per-line heap allocations and directly reduce GC pressure.

When a pool is a good idea

You don’t need a pool everywhere. It shines in a few specific scenarios:

High and steady throughput
If your service is processing tens of thousands of log lines per second, even small per-line allocations add up quickly and trigger frequent GC cycles.

Same shape, repeated many times
You repeatedly allocate the same “shape” of object: for example, []byte buffers of similar capacity, or small structs used during parsing.

Short‑lived scratch objects
The pooled objects are temporary scratch space inside a single request/log handling path, and you fully reset them before reuse.

You’ve profiled, and GC is the problem
Profiling shows a hot path dominated by allocations, and reducing those allocations produces measurable throughput or latency improvement.

A log pre-processing engine is almost a textbook case: each log line goes through the same transformation pipeline, uses similar-sized buffers and temporary objects, and then discards them. A pool lets you recycle that working set instead of constantly asking the runtime for new memory.

When you should avoid it

There are also clear cases where sync.Pool is not worth the cost:

Low QPS or batch tools
If your program processes a small batch of logs once in a while, the extra complexity of pooling won’t pay off.

Long‑lived objects
Pools are for scratch space, not for objects that live across requests or goroutines.

Highly variable sizes
If your buffers/objects have wildly different sizes, you either waste memory or end up implementing a complex “pool of pools”. At that point, a custom allocator or a simpler design might be better.

You haven’t measured anything
sync.Pool is not a free optimization. If the code is not allocation-bound, you’re just making it harder to read for no gain.

It’s also important to remember that sync.Pool is not a strict cache. The runtime is free to drop items from the pool on any GC cycle, so you cannot use it as a reliable long‑term free list.

Practical tips

A few practical rules that have worked well for log-style workloads:

Start simple, then optimize
First write straightforward code using bytes.Buffer or plain slices, then add a pool only around the clearly hot pieces.

Always reset before reuse
For slices, use buf = buf[:0]. For structs, add a Reset method that clears all fields. This avoids leaking data between log lines.

Return objects promptly
Use defer pool.Put(x) near the Get call when possible, to avoid forgetting to put it back in some branches.

Re‑profile after adding the pool
Confirm that the change actually reduced allocations and GC time. If the improvement is negligible, delete the pool and keep the simpler code.

Closing thoughts

Memory pools are not a silver bullet, but in a high-volume logging or log pre-processing system they can be the difference between a GC-bound pipeline and a stable, predictable one. If your log engine spends most of its time allocating the same buffers over and over, pooling those buffers is one of the lowest‑hanging fruits you can pick.

Why I moved Viconic to a new domain (and what’s new)

Over the past few weeks, I’ve been quietly working on improving Viconic — a project I’ve been building to make working with icons simpler and more enjoyable.

Originally, the project lived at viconic.io.vn. It worked well, especially for local usage. But since I’m based in Vietnam, I often discuss projects with friends and other developers here — and one piece of advice kept coming up:

“If you want better SEO and a more global presence, you should move to a .dev domain.”

At first, I didn’t think it would make a huge difference. But the more I looked into it — branding, trust, and discoverability — the more it made sense.

So I decided to make the switch 👉 https://viconic.dev

It’s a small change on the surface, but it feels like a step toward building something more global.

A Better UI with Multiple Themes

One thing I’ve always cared about is how tools feel to use — not just what they do.

So in this update, I introduced multiple color themes to make the experience more flexible and personal.

Available themes include:

  • Blue
  • Red
  • Orange
  • Amber
  • Teal
  • Purple
  • Rose

Instead of being locked into a single look, users can now choose what fits their style or mood.

In the image, you can see how switching themes instantly changes the tone of the interface. It’s a small detail, but it makes the product feel more alive.

Viconic Plugin (Early but Promising)

Alongside the website, I’ve also started working on the Viconic Plugin — currently focused on integrating with Figma.

Right now, it’s still in a very early stage, but it already supports:

Browsing icon collections
Searching icons quickly
Clicking an icon to instantly insert it into Figma

The goal is simple: reduce friction.

Instead of downloading SVGs, copying code, or switching between tabs — everything should happen in one smooth flow.

There’s still a lot to improve, but this is the first step.

What I Learned From This Update

This update isn’t just about features — it’s also about learning:

  • Small branding decisions matter (like choosing the right domain)
  • User experience is more than functionality
  • Start simple, iterate fast — especially with plugins

🤔 What Should I Build Next?

I’m still exploring the direction for Viconic, and I’d genuinely love to hear your thoughts:

  • Should I focus more on the plugin ecosystem or keep improving the web platform?
  • What would make this tool part of your daily workflow?
  • Would you prefer deeper Figma integration or support for tools like VS Code?
  • Do UI themes actually matter to you, or are they just a nice bonus?

Check it out

If you’re curious or want to try it out, feel free to visit:

👉 https://viconic.dev

Any feedback, ideas, or even criticism would mean a lot — this project is still evolving, and your input can help shape it.

960. Delete Columns to Make Sorted III

In the last post, I discussed how recognizing patterns is important in solving coding challenges and discussed the Longest Increasing Subsequence. I shall talk about applying the basics in this problem to solve one variation of LIS. Follow me here, on Twitter, LinkedIn or Github for more.

Problem
You are given an array of n strings strs, all of the same length.

We may choose any deletion indices, and we delete all the characters in those indices for each string.

For example, if we have strs = [“abcdef”,”uvwxyz”] and deletion indices {0, 2, 3}, then the final array after deletions is [“bef”, “vyz”].

Suppose we chose a set of deletion indices answer such that after deletions, the final array has every string (row) in lexicographic order. (i.e., (strs[0][0] <= strs[0][1] <= … <= strs[0][strs[0].length – 1]), and (strs[1][0] <= strs[1][1] <= … <= strs[1][strs[1].length – 1]), and so on). Return the minimum possible value of answer.length.

Example 1:

Input: strs = [“babca”,”bbazb”]
Output: 3
Explanation: After deleting columns 0, 1, and 4, the final array is strs = [“bc”, “az”].
Both these rows are individually in lexicographic order (ie. strs[0][0] <= strs[0][1] and strs[1][0] <= strs[1][1]).
Note that strs[0] > strs[1] – the array strs is not necessarily in lexicographic order.
Example 2:

Input: strs = [“edcba”]
Output: 4
Explanation: If we delete less than 4 columns, the only row will not be lexicographically sorted.
Example 3:

Input: strs = [“ghi”,”def”,”abc”]
Output: 0
Explanation: All rows are already lexicographically sorted.

Constraints:

n == strs.length
1 <= n <= 100
1 <= strs[i].length <= 100
strs[i] consists of lowercase English letters.

Discussion

We apply the same principle in LIS with the double for loops.The only difference is we check all the characters in one column are less than or equal to all the characters in the other. We do this as we are trying to maximize the number of columns to be deleted.

Solution

Java

class Solution {
    public int minDeletionSize(String[] strs) {
     int length=strs[0].length();      
     int [] dp=new  int[length];          
     Arrays.fill(dp,1);
     for(int col=1;col<length;col++){       
      for(int j=0;j<col;j++){
        int numOfRows=0; 
        for(int i=0;i<strs.length;i++){
         char curr=strs[i].charAt(col);   
         char prev=strs[i].charAt(j);
         if(prev>curr) break;
         else numOfRows++;  
         }
         if(numOfRows==strs.length && dp[col]<=dp[j])dp[col]=dp[j]+1;          
        }
      }  
     Arrays.sort(dp);
     return length-dp[dp.length-1]; 
    }
}

C#

public class Solution {
    public int MinDeletionSize(string[] strs) {
      int length=strs[0].Length;      
     int [] dp=new  int[length];          
     Array.Fill(dp,1);
     for(int col=1;col<length;col++){       
      for(int j=0;j<col;j++){
        int numOfRows=0; 
        for(int i=0;i<strs.Length;i++){
         char curr=strs[i][col];   
         char prev=strs[i][j];
         if(prev>curr) break;
         else numOfRows++;  
         }
         if(numOfRows==strs.Length && dp[col]<=dp[j])dp[col]=dp[j]+1;          
        }
      }  
     Array.Sort(dp);
     return length-dp[dp.Length-1];    
    }
}

C++

class Solution {
public:
    int minDeletionSize(vector<string>& strs) {
     string first=strs[0];
     int length=first.size();      
     vector<int> dp;          
     for(int i=0;i<length;i++)dp.push_back(1);
     for(int col=1;col<length;col++){       
      for(int j=0;j<col;j++){
        int numOfRows=0; 
        for(int i=0;i<strs.size();i++){
         string str= strs[i];  
         char curr=str[col];   
         char prev=str[j];
         if(prev>curr) break;
         else numOfRows++;  
         }
         if(numOfRows==strs.size() && dp[col]<=dp[j])dp[col]=dp[j]+1;          
        }
      }  
     std::sort(dp.begin(),dp.end());
     return length-dp[dp.size()-1];    
    }
};

Join our April Fools Challenge for a chance at TEA-RRIFIC prizes!!!

Hello, and let’s get this out of the way right now: this is no joke!!

Tired of building world-changing, hyper-optimized production apps? Sick of reading about clean architecture, scalability, and practicality for a moment? Then we have the perfect challenge for you! It’s time to lean into the chaos of the internet: Welcome to the DEV April Fools Challenge. 🦄

Our Prompt: HTCPCP IYKYK

Your mandate for this challenge is to build something completely useless or silly. We want software that solves zero real-world problems. A CSS-only site that un-centers divs? Great. A volume slider that requires you to shake your phone to turn up the music? Awesome. Your only goal is to make us laugh, scratch our heads, or ask Why on earth did they build this? Make a fool of yourself and have fun with it!

Additional Prize Categories:

  • Best Google AI Usage: Tell us how you leveraged Google AI for this submission. Did you build with Antigravity, experiment in Google AI Studio, or use the Gemini CLI? Or is there a Google AI product embedded in your build such as the Gemini API or any Google Cloud product such as Cloud Run? Tell us all about your experience!

  • Best Ode to Larry Masinter: We’re talking teapots and intentional 418s.

  • Community Favorite: Awarded to one of the most popular submissions based on comments, reactions, overall quality, and final judge discretion.

Judging Criteria and Prizes

All submissions will be judged on the following:

  • Anti-Value Proposition: How delightfully useless, silly, or over-engineered is this project?
  • Creativity: Is the concept original or a hilariously bad take on a familiar problem?
  • Technical Execution: Ironically, building bad software sometimes takes good engineering. Is it functional in its dysfunction?
  • Writing Quality: Is the submission post clear, and does it tell a funny, compelling story about your project?

Prizes

We’ll select two winners for the overall prompt, and one winner for each additional prize category. Our five total winners will each receive:

  • A surprise teapot or coffee maker with appropriate fixings (approx. $50 USD value)
  • DEV++ Membership
  • Exclusive DEV Badge

All participants with a valid submission will receive a completion badge.

How To Participate

Publish a post on DEV using the submission template below. Your post should give us an overview of the community you built for, what you made, and a demo of your project.

April Fools Challenge Submission Template

Please review our judging criteria, rules, guidelines, and FAQ page before submitting so you understand our participation guidelines and official contest rules such as eligibility requirements.

Important Dates

  • April 1: April Fools Challenge begins!
  • April 12 at 11:59pm PT: Submissions Due
  • April 16: Winners Announced

We can’t wait to see what crazy projects you come up with. Questions about the challenge? Drop them in the comments below.

Good luck!!

PRD Template Structure: A Practical Checklist + AI-Ready Format

prd template showing sections like problem, features, and metrics<br>

prd template showing sections like problem, features, and metrics<br>

Full guide + resources.

Most PRD templates fail in one predictable way:

They describe features, but they don’t describe behavior.

That gap creates:

  • engineering questions
  • QA confusion
  • rework loops

This post fixes that with a usable PRD template structure + checklist you can apply immediately.

What a usable PRD template structure actually looks like

A working PRD template is not just sections.

It is a set of answers.

Section Question it answers Example
Problem What is broken? Users drop at checkout
Users Who is affected? First-time buyers
Features What happens step by step? Payment flow
Metrics How do we know it works? Completed orders

If any row is vague, execution breaks.

PRD template checklist (before sharing)

Run this before sending your PRD:

1. Structure check

  • Problem is clearly defined
  • Users are specific (not generic)
  • Features are broken into steps
  • Metrics are measurable

2. Flow check

Every feature should answer:

  • What happens first?
  • What happens next?
  • What happens if it fails?

Example:

  • user enters password
  • system validates
  • error if wrong
  • reset flow if needed

3. Edge case check

Include failure paths:

  • wrong password
  • payment failure
  • network issue

If edge cases are missing, QA will find them later.

4. Metrics check

Avoid vague metrics:

Bad:

  • improve experience

Good:

  • successful login rate
  • completed checkout count

Copy-paste PRD template (minimal version)

Use this directly:

1. Problem
What is broken?

2. Users
Who is this for?

3. Feature Flow
Step-by-step behavior

4. Edge Cases
What can fail?

5. Metrics
How success is measured

Example: login feature (filled template)

Problem:
Users cannot log in when they forget passwords

Users:
Returning users

Feature Flow:
- enter email + password
- validate credentials
- error if incorrect
- reset password flow
- return to login

Edge Cases:
- wrong password
- expired reset link

Metrics:
- successful logins
- reset completion rate

AI PRD template: what changes

When building an AI PRD template, add two things:

1. Input / Output definition

  • Input: what data goes in
  • Output: what result comes out

Example:

  • input: user question
  • output: generated answer

2. Failure handling

AI systems are not always correct.

Define:

  • what happens if output is wrong
  • fallback behavior
  • retry logic

3. Limits and boundaries

Define what the system should NOT do.

Example:

  • do not generate unsafe responses
  • do not hallucinate unknown data

Common PRD template mistakes (and fixes)

Mistake 1: Writing features as labels

Bad:

  • add checkout

Fix:

  • select product
  • click buy
  • enter payment
  • confirm order

Mistake 2: No execution flow

Bad:

  • enable login

Fix:

  • enter credentials
  • validate
  • error or success

Mistake 3: No edge cases

Bad:

  • only happy path defined

Fix:

  • include failure scenarios

Mistake 4: Metrics without meaning

Bad:

  • improve engagement

Fix:

  • track completed actions

Engineer review checklist

Before implementation, check:

  • Can this be built without asking questions?
  • Are all steps defined clearly?
  • Are failure cases covered?
  • Can this be tested easily?

If not, the PRD is not ready.

Quick rule to validate any PRD

Ask one question:

Does this document show what happens step by step?

If the answer is no, it needs work.

Final takeaway

A PRD template works only when:

  • features are broken into steps
  • flows are clear
  • metrics are measurable
  • edge cases are defined

Everything else is optional.

For the complete breakdown, deeper examples, and full explanation of each section.

MUS Serialization Format

MUS (Marshal, Unmarshal, Size) is a serialization format founded on the “Keep It Simple” principle. If you are engineering high-performance systems, MUS offers three mechanical advantages that traditional formats don’t:

  • Extreme Density: By eliminating almost all metadata, MUS payloads are often the smallest possible representation of your data.
| NAME     | Bytes  |
|----------|--------|
| MUS      | 58.00  |
| Protobuf | 69.00  |
| JSON     | 150.00 |

Full benchmarks https://github.com/ymz-ncnk/go-serialization-benchmarks
  • The Size Function: The ability to calculate the exact memory required before allocating a buffer, which allows you to:
    • Reuse buffers effectively to minimize GC pressure.
    • Avoid expensive append calls and re-allocations during marshaling.
  • DTM (Data Type Metadata): A one-byte solution for “heavy” features like versioning and interface serialization.

Untyped Serialization

In most cases, the sender (marshal) and the receiver (unmarshal) already agree on the data type they are communicating with. For example, with dedicated REST endpoints like:

http://example.com/api/v1/foo
http://example.com/api/v1/bar

Both sides know exactly which type is being transferred. In this scenario, any type-hinting or metadata in the payload is a redundant “tax”. Knowing the type is enough, even for structured data, because the type definition itself provides the required order of fields.

Example: Struct to Bytes

Consider a simple User struct. In MUS, we don’t store field names like id or age. We only store the raw values in the order they appear in the code.

type User {
  id    int    // Field 1
  age   int    // Field 2
  admin bool   // Field 3
}

// Data: id=42, age=30, admin=true
// MUS Payload (Binary): [84 60 1]

Because the receiver is using the same User definition, it knows that the first Varint it reads is the id, the second is the age, and the final byte is the admin flag. By offloading the “schema” to the application code, the payload remains 100% data and 0% overhead.

Typed Serialization (DTM)

While shared context covers most scenarios, there are cases where we simply don’t know the data type in advance. This is common when the receiver is expecting to receive:

  • One of several completely different types (e.g., an event stream that could be a LoginEvent or a LogoutEvent).
  • One of several versions of the same data structure (facilitating smooth schema migrations).
  • A concrete implementation of an interface.

In these dynamic cases, we use DTM (Data Type Metadata) — a simple prefix added to your payload:

DTM + Data = Typed Data

By reading the DTM first, the receiver can determine exactly which type is coming next. The beauty of this approach is that it maps complex architectural problems onto simple switch logic.

1. Different Types

When incoming data (from a network, a database, or a file) can represent different structures, the DTM acts as the router.

type Foo
type Bar

const FooDTM
const BarDTM

func UnmarshalDifferentTypes(...) {
  dtm = UnmarshalDTM(...)
  switch dtm {
    case FooDTM:
      // unmarshal and handle Foo
    case BarDTM:
      // unmarshal and handle Bar
  }
}

2. Data Versioning

Instead of adding optional fields that bloat your struct, MUS uses explicit versioning. By using DTMs to distinguish between versions, you keep your current data structures clean and move migration logic into an unmarshaling step.

type Foo = FooV2 // current version
type FooV1
type FooV2

// DTMs represent both the type and the version.
const FooV1DTM
const FooV2DTM

func UnmarshalWithVersioning(...) Foo {
  var foo Foo
  dtm = UnmarshalDTM(...)
  switch dtm {
    case FooV1DTM:
      // unmarshal FooV1, migrate to FooV2, assign to foo
    case FooV2DTM:
      // unmarshal FooV2, assign to foo
  }
  return foo
}

3. Interface Serialization

To serialize an interface, each concrete implementation encodes itself as typed data. During unmarshaling, the DTM tells you which specific struct to instantiate.

type Interface
type Impl1
type Impl2

const Impl1DTM
const Impl2DTM

func UnmarshalInterface(...) Interface {
  var intr Interface
  dtm = UnmarshalDTM(...)
  switch dtm {
    case Impl1DTM:
      // unmarshal Impl1, assign to intr
    case Impl2DTM:
      // unmarshal Impl2, assign to intr
  }
  return intr
}

Streaming Support

MUS is naturally streaming-ready. Because the format is a deterministic sequence of fields where every type carries its own size, you don’t need to know the total data length in advance. You can start writing to the wire immediately, field by field.

Processing on the Fly

Once the receiver identifies the type, via DTM or protocol context, it can begin decoding as soon as the first bytes arrive, consuming exactly the right number of bytes per field without any envelope. This enables non-blocking, low-RAM processing for high-throughput systems.

Implementations

At the moment, the MUS ecosystem is focused on the Go programming language. This includes the core serializer, the DTM support modules, and their respective streaming variants.

Because the format is built on a minimalist specification, it is highly portable. If you’re interested in bringing the “no-tax” philosophy to other languages like Rust, C++, or Java, the spec is ready for implementation.

Resources

Conclusion

MUS isn’t trying to be a “JSON killer.” It’s a tool for when you need high performance, minimal payloads, and absolute control over your memory allocations. By moving metadata out of the payload and making “Size” a first-class citizen, MUS lets you write more efficient code without the overhead of heavy frameworks.

What if Spring Boot had Kotlin-style null-safety without migrating to Kotlin?

I’ve been working on JADEx (Java Advanced Development Extension) which is a safety layer that makes Java safer by adding Null-Safety and Final-by-Default semantics without rewriting Java codes and modifying the JVM.

Quick recap of what JADEx adds to Java:

  • String? nullable type declaration
  • ?. null-safe access operator
  • ?: Elvis operator
  • apply readonly final-by-default mode per file

Today I’m sharing three things that just landed.

1. Lombok support

This was the most requested thing. JADEx now integrates with Lombok via a Delombok pipeline internally. The key motivation: JADEx’s nullability checker needs to see Lombok-generated code (getters, builders, constructors) to avoid blind spots. Without Delombok, nullable fields could silently pass through generated methods unchecked.

@Data
@Builder
@Entity
public class User {
    private String name;
    private String? email;      // @Nullable propagated to getter + builder param
    private Address? address;   // @Nullable propagated to getter + builder param
}

After Delombok, JADEx sees and analyzes the generated code:

// Lombok-generated — JADEx propagates @Nullable into these
@Nullable
public String getEmail() { return this.email; }

public UserBuilder email(@Nullable final String email) { ... }
public UserBuilder address(@Nullable final Address address) { ... }

2. Gradle plugin published

The JADEx Gradle plugin is now on Maven Central and the Gradle Plugin Portal.

plugins {
    id 'io.github.nieuwmijnleven.jadex' version '0.628'
}

jadex {
    sourceDir = 'src/main/jadex'
}

That’s the only change needed to an existing Spring Boot project. Everything else (compilation, Delombok pipeline, .java generation) is handled automatically.

3. JADEx Spring Boot example project

We highly welcome your feedback on JADEx.

Thank you.

I built a UX co-pilot for Claude Code — 376 rules, live preview, zero AI slop

The problem

Every time I asked an AI to design a UI, I got the same output:

  • Inter font
  • Purple gradient on white background
  • Centered hero with a subtitle and a button
  • “Here’s V1, V2, and V3”

It looked… fine. But it looked like everything else. No personality, no UX thinking, no understanding of what the product actually needed.

## The idea

What if the AI could ask questions first before generating anything?

What if it understood that a fintech dashboard needs different UX rules than a restaurant landing page? That a dev tool should use monospace fonts and dark mode, not pastel gradients?

That’s what I built.

## Meet ux-pilot

ux-pilot is a free, open-source plugin for Claude Code that acts as a senior UX designer inside your terminal.

Instead of generating code directly, it runs a structured discovery flow — asks about your product, users, business model, and design preferences — then applies the right rules from a
library of 376 UX rules.

### The 4 phases

1. Discovery
The plugin asks questions one at a time (ABCD choices + free text). It adapts — if you’re building a CLI tool, it skips pricing/funnel questions. If you give detailed answers, it infers
what it can and moves faster.

Output: a structured UX Brief.

2. Audit
Already have code? The plugin scans it. It detects your framework (Next.js, React, Vue, Svelte), checks routes, HTML structure, forms, accessibility, SEO, mobile. Produces a scored report
with findings by severity.

3. Preview
Opens a local server with hot reload (SSE). Generates screens in vanilla HTML/CSS. For each screen, proposes 2-3 named versions — not V1/V2/V3, but descriptive names like “Classic”,
“Bold”, “Minimal”. You approve, reject, or comment.

4. Export
Generates a complete UX spec in markdown. Optionally converts approved screens to React, Svelte, or Vue components.

## 376 rules, loaded on-demand

The rules are organized in 6 categories:

  • UX Patterns (10 files) — accessibility, forms, navigation, layout, typography
  • Conversion & Funnel (7 files) — CTA, pricing, signup, 34 landing patterns
  • SEO & AEO (5 files) — structure, schema.org, AI citation optimization
  • Psychology (4 files) — social proof, cognitive load, trust, persuasion
  • Aesthetics (4 files) — anti-AI-slop, typography craft, 67 UI styles
  • Product Type (1 file) — 30 product-specific recommendations

Key design decision: rules are never loaded all at once. When you’re designing a landing page, only landing-patterns, cta, seo, aesthetics, and social-proof rules are loaded. This
saves tokens and improves accuracy.

## The anti “AI slop” feature

This was the main motivation. The plugin has explicit rules against generic AI output:

Banned:

  • Inter, Roboto, Arial, system fonts
  • Purple gradients on white backgrounds
  • Cookie-cutter layouts
  • V1/V2/V3 version naming

Instead:

  • Distinctive fonts with weight extremes (200 vs 800)
  • 3x+ size jumps between heading and body
  • Gradient meshes, noise textures
  • Descriptive version names

## Built-in data

Beyond rules, the plugin ships with curated design data:

  • 161 color palettes — industry-specific
  • 57 font pairings — Google Fonts with mood tags
  • 67 UI styles — with use cases and CSS hints
  • 30 product types — with landing patterns and anti-patterns

## The meta flex

The landing page for ux-pilot was designed using ux-pilot itself. The plugin ran the discovery, chose the typography (Newsreader + Space Grotesk + JetBrains
Mono), picked the terracotta accent, and generated the structure.

## Try it


bash
  /plugin marketplace add Sakaax/ux-pilot
  /plugin install ux-pilot@ux-pilot
  /ux-pilot

  - Landing: https://ux-pilot.sakaax.com
  - Demo: https://ux-pilot.sakaax.com/demo.html
  - GitHub: https://github.com/Sakaax/ux-pilot
  - Product Hunt: https://www.producthunt.com/products/ux-pilot-2

  Free, MIT licensed, open source. Feedback welcome — especially on the rule set and the discovery flow.

Contract-Fed Scheduler Rails for the TD2 SDL Port

Contract-Fed Scheduler Rails for the TD2 SDL Port

This checkpoint closes an important bootstrap gap in the SDL port.

The runtime already had three promoted scheduler rails:

  • intro_noinput
  • menu_gameplay_entry
  • gameplay_live_race_mid

But two of them were still encoded as hardcoded C branches. That was good enough to prove the path, but not good enough to treat the rails as reusable contracts.

What changed

The non-intro rails now load from a shared contract file:

  • rom_analysis/docs/scheduler_rail_contracts.jsonc

That file carries the validated frame windows for:

  • the menu handoff corridor from 1500 -> 2050
  • the reproducible gameplay seed over 0 -> 11

So td2_scheduler no longer needs embedded anchor tables for menu/gameplay playback. The runtime still uses the callback model for intro, but menu and gameplay now come from versioned contract data.

I also tightened the scheduler smoke so it proves more than field values. It now asserts:

  • scheduler.contract_loaded
  • segment_count
  • scheduler_contract as the live state source

That pushes the smoke from 156 checks to 175, while keeping the same three rails green.

Wiki automation fix

The second part of the checkpoint is operational, but it matters.

The repo already had a post-push wrapper that rebuilt the curated docs wiki. The old safety rule was to skip the wiki auto-commit whenever the main worktree had unrelated dirty files. That avoided mixing work, but it also meant the wiki was often left stale.

The wrapper now rebuilds the wiki in a temporary isolated git worktree at the pushed checkpoint. If the generated wiki changes, the follow-up refresh commit is created and pushed from there.

That means:

  • unrelated local dirty files no longer block wiki publication
  • the wiki refresh commit stays isolated
  • local generated wiki output can be cleaned back to the pushed state afterward

Why this matters

This is still bootstrap infrastructure, not full gameplay execution. But it moves the port in the right direction:

  • runtime rails are becoming data-backed instead of branch-backed
  • validation is proving source and handoff shape, not just static values
  • the docs/wiki side is finally reliable enough to keep up with the checkpoint flow

The next step is not more rail hardcoding. The next step is mutating menu_gameplay_entry and gameplay_live_race_mid under real input on top of these contracts.