AI is great at solving simple, well-defined problems but bad at integration and maintainability; that's why it'll never truly replace senior engineers.

Like in the title. I see a lot of doomposting regarding AI recently, but I think that AI development shouldn't really affect senior devs. It impacts them mostly indirectly, through misguided management. I didn't see this angle discussed (maybe I missed it), so I'll discuss it here.

It's difficult to argue that AI is not great at quickly coming up with solutions to well-defined, self-contained problems. At the same time, if your prompt is generic enough and the problem complex enough, AI will build an ungodly monstrosity that's impossible to maintain. This is because a simple, well-defined problem becomes an open question, and here the hallucinations begin.

However, even complex issues can be divided into a lot of smaller, well-defined ones. To divide the complex problem into smaller ones, one needs an engineer. The most important part of being a senior engineer is being able to turn a complex issue into a finite number of maintainable and well-defined steps to solve it. This is something that AI is not good at and never will be because turning one task into countless smaller tasks increases the cost and complexity of reasoning exponentially. As long as AI tries to be cost-efficient, and it's forced to do it by competition, it will produce code that's just good enough for marketing but actually bad enough that the actual engineering effort is irreplaceable.

This is why senior engineers will never be replaced, and AI is a tool useful mostly for them. They can define the problem as a set of smaller subproblems that AI is good at solving, and they can use the generated parts to compose the sound product that's easy to maintain.

AI hits the juniors the hardest because before it was often their job. However, in the process it creates the new gap: it will become harder to become a senior engineer, so the value of one will increase in time. When it increases enough, the need for producing new engineers will return eventually. It's just that it will become a more prestigious profession, with an entirely different road and methods of education.

I think this is where we stand now. Personally, I enjoy AI because I always preferred the high-concept work rather than being a coding monkey. I'm glad that AI took this part away from me, but each one's situation is different, so I genuinely understand the uncertainty and fear. Though I think that whoever survives this test will be much better off long-term than before.

reddit.com
u/enador — 18 days ago
▲ 3 r/javascript+2 crossposts

This is a passion project I was theorycrafting and implementing for the last two years.

https://wraplet.dev

https://github.com/wraplet/wraplet

The goal was to make a low-footprint framework that would:

  1. Take full advantage of OOP with declarative dependency structures, where objects' implementations can be freely switched out.
  2. Take full advantage of TypeScript, which infers types based on these structures.
  3. Could work with **any** DOM structure (elements, classes, attributes, etc.: you should be able to bind your behaviors to anything that is out there).
  4. Removes the hassle of lifecycle management.

I wanted to have the flexibility of writing vanilla JS but in an environment of the best programming patterns that would prevent the code from becoming a mess over the long term.

This is not an alternative for React but rather for jQuery. According to w3techs market share of jQuery is still significant: https://w3techs.com/technologies/details/js-jquery

Wraplet allows for a gradual migration to a much more maintainable structure, but it also works for new projects that render HTML server-side (I think Wraplet fits popular CMSes especially well).

I think there was an uncovered middle ground between imperative jQuery code and full SPA solutions. This is what Wraplet covers.

The docs have many interactive examples.

Actually, I made a separate library: `exhibitionjs` just to showcase `wraplet`.

If you also develop online docs and don't like dependency on external sandbox providers, you can look it up at: https://exhibitionjs.wraplet.dev/ Of course, it's wraplet-powered.

Ok, that was me; now it's time for an AI slop, because it's actually pretty decent at readable descriptions, or at least better than me.

AI SLOP STARTS

Introduction

wraplet is a small JavaScript/TypeScript framework for projects that still work directly with the actual DOM (server-rendered apps, jQuery legacy, multipage sites, plain HTML, libraries shipping DOM components, etc.).

Instead of replacing the DOM with a virtual rendering layer, wraplet lets you bind class instances ("wraplets") to real DOM nodes and gives you:

  • a predictable lifecycle (construct -> initialize -> destroy),
  • a typed, declarative dependency system between components (required/optional, single/multiple),
  • automatic event listener cleanup via NodeManager,
  • automatic wraplet creation/destruction when DOM nodes appear/disappear (NodeTreeManager),
  • components that are trivial to unit test - each wraplet is just a class around a node.

Pros:

  • Plays well with backend-rendered HTML. No "the framework owns the page" mindset.
  • TypeScript-first. Types describe component structure, not just APIs.
  • Tests are boring (in a good way). A wraplet is a class with a node. new MyWraplet(element), call methods, assert. No JSDOM-heavy framework setup.
  • Encapsulation by default. Parents talk to children through methods like setError("..."), not by reaching into their internal nodes.
  • Gradual adoption. You can migrate a single jQuery-based widget without touching the rest of the app.

Where it's a good fit:

  • progressive modernization of legacy jQuery / server-rendered UIs (PHP, Rails, Django, Laravel, etc.),
  • libraries that ship reusable DOM-bound components,
  • projects where a full SPA would be overkill,
  • teams that prefer classes, encapsulation, and explicit relationships.

Where it's not a good fit:

Apps already happily built on React/Vue/Svelte - wraplet is not competing with them; it's solving a different problem.

TypeScript as a first class citizen

Most "DOM-binding" libraries treat TypeScript as a coat of paint on top of a runtime API. Wraplet tries to use TypeScript as the actual architecture layer - the place where you express what a component is, what it depends on, and what shape those dependencies have. The runtime then derives behavior from that.

The component is a generic class parameterized by the wrapped DOM node

In the simplest case, the type describes what type of node is wrapped by the wraplet:

import { AbstractWraplet } from "wraplet";

class Button extends AbstractWraplet<HTMLButtonElement> {
  protected async onInitialize() {
    // `this.node` is HTMLButtonElement, not Element, not unknown.
    this.node.disabled = false;

    this.nodeManager.addListener("click", (e) => {
      // `e` is properly typed as MouseEvent.
    });
  }
}

But the real fun begins when we introduce dependencies with the `DependentWraplet`.

The dependency map is the type

A wraplet that has children declares them through a plain object that is also a literal type:

import {
  AbstractDependentWraplet,
  type WrapletDependencyMap,
  type DependencyManager,
} from "wraplet";

const map = {
  submit: {
    selector: "[data-js-form__submit]",
    Class: SubmitButton,
    required: true,
    multiple: false,
  },
  fields: {
    selector: "[data-js-form__field]",
    Class: Field,
    required: true,
    multiple: true,
  },
  errorBox: {
    selector: "[data-js-form__error]",
    Class: ErrorBox,
    required: false,
    multiple: false,
  },
} satisfies WrapletDependencyMap;

class Form extends AbstractDependentWraplet<HTMLFormElement, typeof map> {
  protected async onInitialize() {
    this.d.submit;    // SubmitButton          (required + single)
    this.d.fields;    // WrapletSet<Field>     (required + multiple)
    this.d.errorBox;  // ErrorBox | null       (optional + single)
  }
}

A few things worth highlighting from a TS perspective:

  • satisfies WrapletDependencyMap keeps the literal types (concrete Class references, concrete required/multiple booleans) while still validating the object against the framework's contract.
  • typeof map is then passed as a generic parameter to AbstractDependentWraplet, so the framework can compute the type of this.d per-key:
    • required: true, multiple: false - T
    • required: false, multiple: false - T | null
    • required: true, multiple: true - WrapletSet<T>
    • required: false, multiple: true - WrapletSet<T> (possibly empty)
  • Renaming a key in the map updates the type of this.d.<key> everywhere. Renaming a child wraplet class propagates through the parent's type. "Find usages" actually finds usages.

The dependency map effectively becomes a typed schema of your component tree. The runtime DependencyManager queries the DOM, instantiates the right classes, and hands them back to you typed exactly as the map describes.

Async lifecycle with typed extension points

onInitialize and onDestroy are well-defined hooks; listeners and child wraplets are tied to that lifecycle, so cleanup is automatic. Lifecycle listeners on dependencies are also typed to the dependency they are attached to:

dm.addDependencyInitializedListener("fields", async (field) => {
  // `field` is `Field`, inferred from the map key "fields".
});

Custom injectors - typed too

If for some reason a child shouldn't receive its own DOM node directly (e.g. you want to wrap it in something), you can declare a custom injector on a dependency. The injector's callback is typed against the wraplet's expected constructor input, so a mismatch is a compile error rather than a runtime surprise.

What this enables practically

  • Refactoring with confidence: changing the structure of a component (adding/removing a child, switching from single to multiple, making something optional) is a typed change. The compiler walks you through the consequences.
  • Encapsulation by types, not by convention: parents only see the public methods of their children. There's no implicit "reach into the inner DOM of my child" ? you'd have to add a public method on the child wraplet, which is exactly what you want.
  • Tests are just new MyWraplet(node): no framework runtime to boot, no JSX renderer, no JSDOM gymnastics beyond having a node. Types make sure the test is constructing the component correctly.

AI SLOP ENDS

Writing this framework (and docs with examples) was a bumpy road, but I hope it will be useful for some of you. If you are still reading this, thanks for sticking with me. 😄

u/enador — 1 month ago