← Back to Blog

Smart Assembly Size Filtering: From User Request to Production in 45 Minutes

"Can you add size filtering?" A simple question from a user in Discord. Four words that, on the surface, sound trivial. But when you trace the data flow behind the Smart Assemblies panel, you realize it touches everything: a Docker container reading from a PostgreSQL database indexed from blockchain data, Cloudflare KV snapshots, and a React frontend with Three.js visualization.

We shipped it in 45 minutes. This is the story of how—and why the tooling and documentation investment made it possible.

The Request

EVE Frontier has different sizes of deployable structures. SSUs (Smart Storage Units), Hangars, Shipyards, Printers, and Refineries all come in Small, Medium, and Large variants. The Smart Assemblies panel already let users filter by type (show only SSUs) and status (show only online structures). But there was no way to filter by size.

A user asked: "Can I see only Large SSUs?" Reasonable request. Let's trace what needs to change.

The Architecture (Why This Seems Hard)

Here's the data flow for Smart Assemblies:

  1. Primordium (pg-indexer) reads blockchain events and writes to PostgreSQL
  2. Snapshot Exporter (Docker container) queries Postgres, aggregates counts by system/type/status
  3. Cloudflare KV stores the snapshot JSON (cached globally)
  4. Worker API serves the snapshot to the frontend
  5. React Frontend reads the snapshot, renders filter UI, colors stars on the Three.js map

Adding size filtering means:

Seven distinct changes across four different systems. Sounds like a multi-day project, right?

The 45-Minute Timeline

0:00 - Investigation

First question: does the data even exist? Checked the Assembly API response—yes, type_id is present and maps to specific sizes. Created the mapping table.

0:05 - Backend SQL

Added size classification SQL to structure_snapshot_exporter.js. A CASE statement maps type_id to S/M/L.

0:12 - Snapshot Schema

Extended the snapshot to include sizes[type][size][status] alongside existing counts[type][status]. Parallel structure, no breaking changes.

0:18 - TypeScript Types

Added SmartAssemblySize type, constants, labels, and defaults to smartAssembly.ts.

0:22 - Hook State

Extended useSmartAssemblyManagement.ts with size state and localStorage persistence.

0:28 - Panel UI

Added size filter pills to SmartAssembliesPanel.tsx, matching existing type/status pill styling.

0:32 - App.tsx Wiring

Connected size state to panel props, modified smartAssemblyFiltered useMemo to use the new sizes data.

0:35 - Build & Verify

TypeScript compilation passed. Vite build succeeded.

0:38 - Deploy Backend

Rebuilt Docker image, restarted container, manually triggered snapshot export. Verified KV now contains sizes field.

0:42 - Deploy Frontend

Deployed preview to Cloudflare Pages. Tested in browser—size filtering works.

0:45 - Production

Committed, pushed, deployed to production. User confirmed feature works.

The Type ID Mapping

The key insight: every Smart Assembly variant has a unique type_id in the blockchain data. We created a comprehensive mapping:

Type Size Type ID
SSULarge77917
SSUMedium88083
SSUSmall88082
HangarLarge88094
HangarMedium88093
ShipyardLarge88071
ShipyardMedium88070
ShipyardSmall88069
PrinterLarge87120
PrinterMedium88067
PrinterSmall87119
RefineryLarge88064
RefineryMedium88063

This mapping became a SQL CASE statement in the snapshot exporter:

const sizeClassificationSql = `
  CASE type_id
    WHEN 77917 THEN 'L'  -- SSU Large
    WHEN 88083 THEN 'M'  -- SSU Medium
    WHEN 88082 THEN 'S'  -- SSU Small
    WHEN 88094 THEN 'L'  -- Hangar Large
    WHEN 88093 THEN 'M'  -- Hangar Medium
    -- ... etc
    ELSE 'M'  -- Default to Medium for unknown types
  END as size_class
`;

The Snapshot Schema Extension

The existing snapshot structure stored counts like this:

{
  "systems": {
    "30000142": {
      "counts": {
        "ssu": { "online": 5, "offline": 2 },
        "hangar": { "online": 3, "offline": 1 }
      }
    }
  }
}

We added a parallel sizes structure:

{
  "systems": {
    "30000142": {
      "counts": { ... },
      "sizes": {
        "ssu": {
          "L": { "online": 2, "offline": 1 },
          "M": { "online": 2, "offline": 1 },
          "S": { "online": 1, "offline": 0 }
        },
        "hangar": {
          "L": { "online": 2, "offline": 0 },
          "M": { "online": 1, "offline": 1 }
        }
      }
    }
  }
}

This parallel structure means:

The Frontend Changes

TypeScript types make refactoring safe. We defined:

export const SMART_ASSEMBLY_SIZES = ['S', 'M', 'L'] as const;
export type SmartAssemblySize = typeof SMART_ASSEMBLY_SIZES[number];

export const SMART_ASSEMBLY_SIZE_LABELS: Record<SmartAssemblySize, string> = {
  S: 'Small',
  M: 'Medium',
  L: 'Large',
};

// Which types support size variants
export const SIZED_ASSEMBLY_TYPES: SmartAssemblyType[] = [
  'ssu', 'hangar', 'shipyard', 'smartPrinter', 'refinery'
];

The hook gained new state with localStorage persistence:

const [smartAssemblySizes, setSmartAssemblySizes] = useState<Record<SmartAssemblySize, boolean>>(
  () => {
    const stored = localStorage.getItem('smart-assembly-sizes');
    return stored ? JSON.parse(stored) : SMART_ASSEMBLY_DEFAULT_SIZES;
  }
);

// Persist changes
useEffect(() => {
  localStorage.setItem('smart-assembly-sizes', JSON.stringify(smartAssemblySizes));
}, [smartAssemblySizes]);

The panel UI added a new row of filter pills, styled identically to the existing type and status filters. We fixed an inconsistency during development—all selected states now use var(--accent) for visual consistency.

Why 45 Minutes Was Possible

The Real Investment

This wasn't magic. It was the compound return on months of infrastructure investment: comprehensive documentation, consistent patterns, typed everything, and an AI assistant that understood the codebase because it helped build it.

1. Documentation-First Development

Every component has clear documentation. The vibe coding methodology we use means the AI assistant has access to:

2. Consistent Patterns

The new size filter follows exactly the same pattern as the existing type and status filters:

When you establish patterns and stick to them, new features become fill-in-the-blank exercises.

3. The AI Understands the Code

This is the non-obvious part. The AI assistant (GitHub Copilot in agent mode) helped build this codebase over months. When asked to add size filtering, it:

There was no "let me read through the codebase" phase. The context was already there.

4. End-to-End Ownership

One person (with AI assistance) owns the entire stack. No handoffs. No "waiting for the backend team." No pull request review queues. When you can modify the blockchain indexer, snapshot exporter, Worker API, and React frontend in one session, iteration speed is bounded only by typing and deployment time.

The Bigger Picture

This 45-minute feature represents something important about modern development workflows. The traditional estimate for this feature would be:

We did it in 45 minutes because:

  1. Documentation compounds. Every hour spent documenting patterns pays back 10x in faster future work.
  2. Typed languages catch errors at compile time, not in production.
  3. AI assistants work best on codebases they helped build. The context is implicit.
  4. Full-stack ownership eliminates coordination overhead. No meetings, no handoffs, no waiting.
  5. Preview deployments enable confident shipping. Test in isolation, then promote to production.

The Technical Debt We Paid Down

Along the way, we fixed a styling inconsistency. The original panel had:

Three different colors for essentially the same interaction pattern. We unified everything to use var(--accent)—now all selected filters use the same theme-aware accent color. Small polish, but it's the kind of thing that makes a UI feel cohesive.

Docker Container Versioning

After the feature was live, we followed our container versioning workflow:

  1. Committed the snapshot exporter changes
  2. Tagged as snapshot-exporter-v1.1.0
  3. Pushed tag to trigger GitHub Actions → GHCR build
  4. Updated docker-compose to use the new version

This ensures we can roll back to v1.0.0 if anything breaks, and the container image is immutably versioned in GitHub Container Registry.

Lessons for Your Projects

Invest in Documentation Early

It feels slow at first. "I could just write the code." But documentation creates leverage. It lets AI assistants understand your patterns. It lets future-you (or future-AI) work faster.

Establish Patterns and Stick to Them

Every new feature that follows an existing pattern is nearly free. Every feature that invents a new pattern costs the "pattern tax" forever.

Own the Full Stack

Coordination overhead is the hidden killer of velocity. If you can modify everything from database to UI, you can iterate in minutes instead of days.

Trust the AI With Context

AI coding assistants aren't magic. They're pattern matchers. If your patterns are clear, consistent, and documented, the AI becomes a force multiplier. If your code is a mess, the AI will generate more mess.

Try It

The size filtering is live now on EF-Map. Open the Smart Assemblies panel, and you'll see filter pills for Small, Medium, and Large. Combine them with type and status filters to find exactly the structures you're looking for.

45 minutes from user request to production. That's what good tooling, documentation, and AI assistance can achieve.

Related Posts

smart assemblies size filtering rapid development docker cloudflare kv blockchain indexing eve frontier vibe coding llm development full stack