# Cappuccino in 2026: a Cocoa-style web framework waiting on a community merge

> Cappuccino builds desktop-class browser applications in Objective-J, a strict superset of JavaScript, and its current state is defined less by the framework than by a pending refactor of AppKit whose merge is blocked until users confirm their applications survive it.

**cappuccino/cappuccino** — Web Application Framework in JavaScript and Objective-J

- Repository: https://github.com/cappuccino/cappuccino
- Website: https://cappuccino.dev/
- Stars: 2,266 · Forks: 333
- Language: Objective-J
- License: LGPL-2.1
- Published: 2026-09-30 · Updated: 2026-09-30 · Language: en
- Canonical page: https://hysenlabs.com/projects/cappuccino-cappuccino

## Objective-J as a strict superset, which makes migration a non-issue

The design decision that shapes everything else is that Objective-J extends JavaScript rather than replacing it. Every line of JavaScript is already valid Objective-J, which means an existing script-heavy page can be adopted file by file rather than all at once, and pure JavaScript and Objective-J can sit in the same file during a migration. The two syntaxes are the same expression in different costume:

```objc
NSString *greeting = [NSString stringWithFormat:@"Hello, %@!", name];
```

```objj
var greeting = [CPString stringWithFormat:@"Hello, %@!", name];
```

One difference is worth noticing. The Objective-C version declares a type, while the Objective-J version does not. That single removal is the practical consequence of sitting on a JavaScript engine rather than a compiled Objective-C runtime, and it is also why the framework can run its output directly in a browser during development instead of requiring a compile step to see a change.

The classes themselves are Cocoa-shaped, with an AppKit directory and a Foundation directory at the top of the repository alongside the Objective-J implementation directory. What that buys a developer is the parts of a desktop toolkit that a line-of-business tool actually leans on: a full set of UI controls, keyboard navigation and focus management, and multi-level undo and redo. The project points at a live showcase application and a cookbook site as evidence, and the undo support is the feature most often absent from browser toolkits, where it has to be built from scratch per application.

Deployment is not a native binary story. Development happens on macOS, Windows or Linux, and the output is a web application that runs in any modern browser, which is the whole argument for a framework modeled on Cocoa in a world where the alternative is assembling desktop behaviour out of DOM calls.

## The project status block is the most important page in the repository

Most framework READMEs bury status at the bottom. Cappuccino puts it at the top, and that turns out to be correct, because the current state of the project is not a feature list.

Four claims define it. The first is that a v1.5.0 baseline has been established, with the next release being v2.0.0. The second is that v2.0.0 will move the entire toolchain to Golang and to the platform-native binaries it produces, leaving Node.js and npm behind. The third is a named escape hatch: a branch called legacy-1.4.0 provides an unambiguous freeze point for anyone who wants to avoid the coming work, with the explicit warning that it will not receive future fixes. The fourth is that active work is aimed at v2.0.0.

Read together, those four statements say the JavaScript and npm toolchain is the thing being retired, not the framework itself. That reframes the quick start instructions. Installing through npm is the correct path for the baseline and the legacy branch, and it is a path with an end date.

For a project that has been in development since 2008, the interesting part is the shape of the transition rather than its timing. Nothing indicates a rewrite of AppKit, only a rewrite of the build and packaging, which is a far smaller commitment. The Aristo3 work described below is a separate matter and is scoped as a theme, not an architecture change.

## Aristo3 is a real refactor with a real merge gate

The pending work item is a resolution-independent theme called Aristo3, and the project describes it plainly as a non-trivial refactoring of the AppKit UI classes. Unit tests pass. That is stated as insufficient, and the project asks for something specific in return: testing against real applications, by people who have real applications.

The instructions are concrete. Check out the aristo3 branch, build the frameworks, run your existing application against that branch, and open the browser developer console. What to watch for is a short list of failure classes, and the list is a decent proxy for what a theme refactor actually breaks:

- Uncaught `CPException` objects or JavaScript errors
- Infinite layout loops, visible as a freezing browser or a crashing tab
- Broken responder chains or keyboard event handling
- Key-value observing or binding failures, or `valueForThemeAttribute:` resolving to `nil` when it should not
- View hierarchy corruption, where subviews disappear or fail to clip

The stated tolerance is worth noting too. Minor visual breakage and cosmetic regressions are explicitly not the concern at this stage; structural failures are. That is a sensible priority for a resolution independence change, since the whole point of such a theme is to stop hard-coding pixel dimensions, and the failure mode of a half-finished version of that is layout loops rather than a slightly wrong corner radius.

The merge condition is unusual enough to be the most quotable line in the repository. A response of thumbs up on the Aristo3 pull request is required before the branch can be merged into main, and the reasoning given is that no community member should be left behind by a merge. A pull request number, 3038, is given directly, along with instructions to comment with a stack trace if something breaks. The practical consequence for an evaluator is that the fastest route to understanding the theme's stability is to read what people have reported on that pull request, and the fastest route to contributing is to run one application against the branch and say so.

## Package metadata that trails the stated baseline

The manifest and the documentation do not agree about which version is current. The manifest reads 1.3.1, while the status block describes a v1.5.0 baseline as established and v2.0.0 as upcoming. Both can be true at once if the baseline was tagged on a branch rather than published, and the repository confirms there are no GitHub releases, so there is no tag list to arbitrate the question.

What the manifest does establish is the shape of the installed package. It is scoped under the organisation that also owns the Objective-J runtime, declares an LGPL licence, requires Node 14 or newer, and ships exactly two directories, the built distribution and the Jake directory. Everything else in the repository, including the tests, the tools and the two Python maintenance scripts at the root, stays out of the published tarball.

The binary list is longer than most framework packages manage. Twelve executables are exposed, and they divide into three groups. The user-facing one is `capp`, the generator. The build layer is represented by `jake`, with a dependency on a separately published Jake package pinned to a floating latest version. The rest are diagnostics and conversion tools inherited from the Apple toolchain: a compiled interface bundle dumper, a flattener, font and image size inspectors, a nib to cib converter, a dependency checker for Objective-J, a static asset compressor, the Objective-J compiler, the test runner and a skeleton generator.

Two of those entries are worth calling out. A dependency checker implies the framework has a real import graph that can go wrong, and the presence of a nib converter implies that Interface Builder output is a supported input format. Neither fact is advertised in the feature list, and both tell you what kind of user the project expects: someone porting a Cocoa codebase, not someone starting a blank page.

## The five minute quick start, and where it will stop working

The installation path is deliberately ordinary. Node with npm, an optional step to redirect global installs into a home directory so that a system-owned npm prefix does not cause permission errors, then a single global install:

```bash
npm set prefix ~/.npm

export PATH="~/.npm/bin:$PATH"
```

```bash
npm install -g @objj/cappuccino
```

Generating and running a first application is then three commands. The generator creates a directory, you move into it, and any static web server will do since the output is a web application:

```bash
capp gen HelloWorld

cd HelloWorld

python3 -m http.server 8000
```

The example uses Python for the server precisely because the framework does not care, which is the point of the deployment model. Nothing here compiles, nothing needs a native toolchain, and the browser is the runtime.

The gap between this and a production setup is where a reader should be most careful. The quick start says nothing about how the built output is produced for deployment, and with the toolchain scheduled to move to Golang for v2.0.0, the build steps a team memorises now are the ones most likely to be rewritten. The same applies to the floating dependency versions in the manifest, where the build tool and the test runner are both declared as latest rather than pinned. For a framework whose selling point is stability inherited from decades of API design, a build that resolves to whatever the newest published build tool happens to be is the sharpest edge in the packaging, and it is worth pinning locally before the next release lands.

## Who the LGPL suits and who should walk away

The licence is LGPL-2.1-or-later, which is a deliberate choice for a framework that wants adoption without wanting to be copied. For a team building an internal line-of-business tool, the practical question is what happens when you modify the framework itself rather than your own application code, and the answer depends on how you distribute the result. For a team shipping a hosted product with no intention of touching AppKit, the obligation is light in practice.

What the licence does not change is the maintenance arithmetic. A framework whose toolchain is being replaced, whose current baseline is described as a holding point, and whose pending theme change has not yet been merged into the main branch is asking its users to run a pre-release in exchange for a narrower feature set than the stable branch offers. The project is candid about this, which is better than the alternative, but candour does not convert a migration project into a stable dependency.

The honest summary is that Cappuccino is a mature framework in the middle of a generational change, and that the two facts are not in conflict. The API design it offers is decades old and proven, the ecosystem around it is stable for the same reason, and the new work is happening in the toolchain and the theme layer rather than in the object model. Anyone evaluating it should treat the Aristo3 branch as the product to test and the main branch as the product to admire, and anyone adopting it should pin to a specific commit on the legacy branch rather than tracking a default branch that is about to move.

## Conclusion

Cappuccino suits a team that already writes Objective-C style code and wants Cocoa conventions in a browser tab without adopting a modern component framework first. It does not suit anyone starting a new product in 2026, because the current toolchain is on its way out, the published package version trails the baseline the documentation describes, and there is no tagged release history to pin against. If you evaluate it, check out the aristo3 branch first and run your own application against it with the developer console open, since that is the test the project itself is asking the community to perform before main moves.

## FAQ

### What is Objective-J in Cappuccino and is plain JavaScript still valid?

Objective-J is a strict superset of JavaScript, so all JavaScript code is valid Objective-J and the two can be mixed in the same file. It adds message passing and class based inheritance borrowed from Smalltalk and Objective-C, without requiring type declarations.

### What has to happen before Cappuccino's Aristo3 theme merges into main?

Community members are asked to check out the aristo3 branch, run their own applications against it with the developer console open, and report whether they hit structural failures such as uncaught exceptions, layout loops or responder chain problems. A response of thumbs up on the pull request is required before the merge.

### Which version of Cappuccino should a new project use?

The documentation describes a v1.5.0 baseline with v2.0.0 upcoming, and a legacy-1.4.0 branch is offered as a freeze point that will not receive future fixes. Since no GitHub releases are published, pinning to a specific commit rather than tracking a branch is the safer approach.

### How do I install Cappuccino and start a first application?

Install Node with npm, optionally point npm at a home directory prefix to avoid permission errors, then run npm install -g @objj/cappuccino. Use the capp generator to create a project, move into the directory, and serve it with any static web server such as python3 -m http.server 8000.

### What is changing in Cappuccino v2.0.0?

The toolchain is moving to Golang and to the platform-native binaries it produces, leaving Node.js and npm behind. The framework API itself is not described as changing, so the migration concerns building and packaging rather than application code.

## Sources

- [cappuccino/cappuccino on GitHub](https://github.com/cappuccino/cappuccino)
- [Issues](https://github.com/cappuccino/cappuccino/issues)
- [License: LGPL-2.1](https://github.com/cappuccino/cappuccino/blob/main/LICENSE)
- [Project website](https://cappuccino.dev/)
- [README](https://github.com/cappuccino/cappuccino/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/cappuccino-cappuccino
