Building NecroOS: A Horror-Themed Windows 95 Simulator with Kiro AI

Published: (December 2, 2025 at 10:25 PM EST)
5 min read
Source: Dev.to

Source: Dev.to

Features

  • Authentic retro UI using the react95 component library
  • Multiple applications: Notepad, Minesweeper, Portfolio Manager, Terminal, and some… darker surprises
  • Progressive horror mechanics through a haunt level system
  • Visual effects: CRT monitor simulation, glitch effects, and screen distortions
  • Audio atmosphere: Ambient sounds and effects that intensify with interaction
  • Corrupted Clippy: Everyone’s favorite assistant, but something’s very wrong

Technical Stack

  • React 19
  • TypeScript (strict mode)
  • Zustand for state management
  • Vite for blazing‑fast development

Defining the Project with Kiro

My first interaction with Kiro set the tone for the entire project. Instead of diving straight into code, I used Kiro’s spec system to formalize requirements and design. This turned out to be a game‑changer.

Specification Document

The spec outlined:

  • Acceptance criteria for each feature
  • Correctness properties that needed to be maintained
  • Implementation tasks broken down into manageable chunks

Kiro helped iterate on these specs, catching edge cases I hadn’t considered. For example, when designing the window‑management system, Kiro suggested properties like:

  • “Windows should never overlap the taskbar”
  • “Minimized windows should restore to their previous position”

These details would have caused bugs later.

Property‑Based Testing with Fast‑Check

One of Kiro’s most impressive capabilities was its understanding of property‑based testing using fast‑check. Rather than writing dozens of individual test cases, Kiro helped define properties that should always hold true.

import * as fc from 'fast-check';

fc.assert(
  fc.property(
    fc.array(windowArbitrary, { minLength: 1, maxLength: 10 }),
    (windows) => {
      // Property: focused window should always have highest z-index
      const focusedWindow = windows.find(w => w.isFocused);
      if (focusedWindow) {
        expect(focusedWindow.zIndex).toBeGreaterThanOrEqual(
          Math.max(...windows.map(w => w.zIndex))
        );
      }
    }
  ),
  { numRuns: 100 }
);

Kiro generated similar property tests for nearly every component, catching edge cases that traditional unit tests would have missed. The glitch system, audio management, and haunt‑level orchestration all benefited from this approach.

Steering Documents

Early on, I discovered Kiro’s steering system—markdown files that provide context and conventions for the entire project. I created three steering documents:

  • tech.md: Defined the technology stack, common commands, and TypeScript configuration
  • structure.md: Outlined directory organization, naming conventions, and import patterns
  • product.md: Described the core concept and key features

With these in place, Kiro consistently generated code that matched my project’s conventions without needing repetitive prompts.

Development Rhythm with Kiro

  1. Define the feature in natural language
  2. Kiro generates implementation with proper TypeScript types, error handling, and tests
  3. Review and refine using Kiro’s diagnostic tools
  4. Move to the next feature

Haunting Orchestrator

For the haunting orchestrator—a complex service that manages progressive horror effects—Kiro generated:

  • The core service class with proper TypeScript interfaces
  • Unit tests covering all methods
  • Property‑based tests for state transitions
  • Error‑boundary integration
  • Performance optimizations

All in a single iteration that would have taken hours of manual coding and testing.

Glitch System

The glitch system needed to:

  • Apply visual distortions to windows based on haunt level
  • Coordinate with audio effects
  • Handle performance constraints
  • Provide smooth animations across browsers

Kiro produced the following component:

import React, { useMemo } from 'react';
import { useStore } from '@/store';

export const GlitchableWindow: React.FC = ({
  children,
  windowId,
  intensity = 0,
}) => {
  const glitchLevel = useStore(state => state.glitchLevel);
  const effectiveIntensity = Math.min(intensity + glitchLevel * 0.1, 1);

  // Proper memoization for performance
  const glitchStyle = useMemo(() => ({
    filter: `hue-rotate(${effectiveIntensity * 180}deg) 
             contrast(${1 + effectiveIntensity * 0.5})`,
    animation: effectiveIntensity > 0.5
      ? `glitch ${2 - effectiveIntensity}s infinite`
      : 'none',
  }), [effectiveIntensity]);

  return (
    <div style={glitchStyle}>
      {children}
    </div>
  );
};

Property tests verified that intensity values always stayed within bounds and that performance remained smooth even with multiple glitching windows.

Audio Bug Fix

When audio wouldn’t play in certain browsers, I used Kiro’s context system to share the error. Kiro immediately:

  • Identified the issue as an autoplay‑policy restriction
  • Generated a robust audio service with user‑interaction detection
  • Added fallback mechanisms
  • Created comprehensive error handling and tests

The fix was production‑ready, not a quick hack.

Results After Two Weeks

  • 15+ React components, each with unit and property‑based tests
  • 95%+ test coverage across the codebase
  • Zero TypeScript errors in strict mode
  • Comprehensive error handling with graceful degradation
  • Performance optimizations for smooth 60 fps animations
  • Cross‑browser compatibility with proper fallbacks

The codebase is maintainable, well‑documented, and follows consistent patterns throughout. This isn’t just a hackathon project—it’s production‑quality code.

Lessons Learned

  • Structured development: The spec system keeps complex projects organized
  • Property‑based testing: Catches edge cases traditional tests miss
  • Consistency: Steering rules ensure uniform code across the project
  • Context awareness: Kiro understands the entire project, not just individual files
  • Best practices: Generated code follows modern patterns and conventions
  • Creative direction: Kiro implements; you define the vision

Practical Tips

  1. Start with specs – Invest time in requirements and design documents
  2. Create steering rules early – Teach Kiro your preferences upfront
  3. Embrace property‑based testing – Let Kiro generate comprehensive test suites
  4. Use context liberally – Share errors, file structures, and related code
  5. Iterate in conversation – Kiro learns from your feedback

Conclusion

Building NecroOS with Kiro transformed my development process. I shipped a complex, well‑tested application in a fraction of the time it would have taken solo, without sacrificing code quality. The combination of AI assistance and human creativity is powerful—Kiro handles the tedious implementation details while I focus on the experience and architecture.

If you’re skeptical about AI‑assisted development, I get it. I was too. But Kiro isn’t trying to replace developers—it amplifies what we can build. For a solo developer tackling an ambitious project, that amplification is the difference between “maybe someday” and “shipped today.”

Try NecroOS if you dare:
Live Demo
GitHub Repository

Back to Blog

Related posts

Read more »