Article

journal / a11y-cat-10-splitting-core-from-delivery

A11Y-Cat: Splitting the core from the delivery layer was messier than I expected

A11YWeb Apps
Carla February 28, 2026 3 mins read

I thought this would be a cleaner refactor than it was. The history makes it pretty obvious that it was important, but not painless.

Shared-runtime scan results surface

Once the extension existed, the project had a new problem: how many times was I going to maintain the same runtime?

The answer becomes the shared core refactor on April 8. The commit message says it plainly: shared A11Y Cat core for bookmarklet and MV3 extension. The runtime gets moved into shared/a11y-cat-core.js, the bookmarklet gets slimmed down, the extension injects a built core, and the build logic changes to support both paths.

Architecturally, that is the right move. One scanning runtime, two delivery channels. In practice, it also introduces a tension that stays with the repo for the rest of the month: the shared core is committed for compatibility and delivery, but the authoring source moves elsewhere.

That is why the later generated-core discipline matters so much. The project eventually formalizes src/runtime/ as the source of truth and treats shared/a11y-cat-core.js as generated output. There is a script to rebuild it, a script to verify drift, and later hard failures in build and packaging flows if it goes stale.

I think that tells the story pretty clearly. The shared core refactor solved a real duplication problem, but it also created a maintenance hazard. If a big generated runtime artifact stays committed, somebody has to stop it drifting from the modular source. The repo ends up doing that with scripts and policy instead of trust.

There is also a bigger design lesson in this phase. Separating delivery from runtime logic sounds tidy, but only if the runtime already has clear seams. A11Y Cat did not start that way. So the shared-core move comes before the deeper runtime decomposition. That means some of the awkwardness is visible in the history. The project gains the right directional architecture first, then spends the next week or two making that architecture easier to govern.

I do not see that as a mistake. I see it as a pretty normal sequence. You often realise you need a shared core before you have finished building the best authoring structure for that core.

The current repo is much clearer about this than the April 8 code probably felt at the time. ARCHITECTURE.md, docs/runtime-modules.md, scripts/generate-core.js, and scripts/generated-core-discipline.js all exist to make the relationship explicit. That is the kind of cleanup you only add after a refactor proves worth keeping.

Visual evidence

The shared-core refactor is architectural rather than visibly cosmetic, so there is no direct screenshot that proves the split by itself. The nearest visible evidence is the extension scan UI that depends on the shared runtime after the split:

What I was really learning here

I was learning that “shared runtime” is not just a refactor target. It creates a source-of-truth problem too. Once the same code needs to ship through different paths, you need discipline around where the real implementation lives.

Evidence

  • Commits:
    • cf7162d – shared core introduced for bookmarklet and extension
    • cbac50b – generated-core freshness enforced across the build pipeline
    • 2a32f40 – runtime later decomposed into authoring modules
  • Files:
    • ../../shared/a11y-cat-core.js
    • ../../src/runtime/
    • ../../scripts/generate-core.js
    • ../../scripts/generated-core-discipline.js
    • ../../ARCHITECTURE.md