The AI Cost of Marionette
When we introduced Marionette 5, one reply summed up the general mood: ‘Everybody who knows what marionette is has stiff knees and creaky backs.’ Fair enough. By the vibes, Marionette lost a long time ago. In that post
When we introduced Marionette 5, one reply summed up the general mood: ‘Everybody who knows what marionette is has stiff knees and creaky backs.’ Fair enough. By the vibes, Marionette lost a long time ago.
In that post we described how we decided whether Marionette was still worth investing in. The usual advice was about Backbone’s age, community size and hiring pools, and all of it assumed a human would write the code. So we asked the agents which ecosystem they’d want to work in. They liked Regions, predictable lifecycles and clear ownership. But that was the agents’ opinion, and opinions are vibes too.
Then Nolan Lawson asked, ‘Did anyone think to ask the agents?’ He had an agent build the same app in twelve stacks and measured what each one cost and shipped. Marionette wasn’t one of them.
So we ran his benchmark task with Codex, Marionette included, and published the results. Last time we asked the agents what they thought. This time we measured what they did.
What we ran.
The same small Pokémon app: listings, search, filters and detail pages. Four independent Codex builds in each of six stacks: Marionette, Vanilla, React Router, SvelteKit, Nuxt and Angular. This is our Codex cohort, not an extension of Nolan’s model results.
A small task gives us something concrete to repeat: the same requirements, four independently generated apps per stack, and a common acceptance suite. It also leaves room for a closer look at how each agent chose to organize the work.
Agents inherit the internet’s preferences along with its examples, good and bad. Popularity may influence what they reach for; this experiment does not measure their training data or prove that explanation. It measures what they built here.
We keep Nolan’s results order: cost, time, tokens, delivered quality and source size. Charts show all four applications, their median and their full range. This DEV edition includes the full data tables below; the original article has interactive charts with sorting and per-build values.
01 / COST
What did the agent use?
Hypothetical API proxy per build
USD proxy. Four independent builds per framework. Median (minimum–maximum); ranges are observations, not confidence intervals.
Calculated under Standard, short-context API assumptions. Not measured cost, subscription spending or an invoice. Actual dollar charges are unavailable.
| Framework | Median (range) | b1 | b2 | b3 | b4 |
|---|---|---|---|---|---|
| Vanilla | 0.2512 (0.2465–0.2856) | 0.2554 | 0.2465 | 0.2470 | 0.2856 |
| Marionette | 0.3107 (0.2887–0.3337) | 0.3311 | 0.2903 | 0.2887 | 0.3337 |
| React Router | 0.3470 (0.2635–0.4943) | 0.4943 | 0.2635 | 0.3200 | 0.3740 |
| Nuxt | 0.4150 (0.2875–0.4827) | 0.4215 | 0.2875 | 0.4085 | 0.4827 |
| SvelteKit | 0.4171 (0.3862–0.4909) | 0.3983 | 0.4359 | 0.3862 | 0.4909 |
| Angular | 0.4478 (0.3823–0.5413) | 0.5105 | 0.3850 | 0.5413 | 0.3823 |
At the stated token prices, Marionette’s median equivalent is about 31 cents. Vanilla is about 25 cents, React Router 35, SvelteKit and Nuxt 42, and Angular 45. This is a hypothetical token-only comparison, not what our subscription charged. The distinction matters more than the pennies.
What the dollar estimate does and does not mean
The calculation uses $2 per million uncached input tokens, $0.10 cached and $10 output, the previously verified Standard short-context tariff for the reported model. It assumes those rates apply; per-request context lengths, effective service tier, extra tool fees and separate child usage are not established. Actual subscription debits are unavailable. It is not an invoice.
Generation time per build
minutes. Four independent builds per framework. Median (minimum–maximum); ranges are observations, not confidence intervals.
| Framework | Median (range) | b1 | b2 | b3 | b4 |
|---|---|---|---|---|---|
| Vanilla | 4.66 (4.36–4.93) | 4.93 | 4.79 | 4.36 | 4.53 |
| Marionette | 5.07 (4.12–5.57) | 5.57 | 4.12 | 4.84 | 5.30 |
| React Router | 5.55 (4.45–6.55) | 6.55 | 4.45 | 4.82 | 6.27 |
| SvelteKit | 6.08 (5.33–7.19) | 6.20 | 7.19 | 5.96 | 5.33 |
| Nuxt | 6.92 (4.51–7.26) | 6.74 | 4.51 | 7.10 | 7.26 |
| Angular | 6.97 (5.88–8.01) | 7.40 | 6.54 | 8.01 | 5.88 |
Marionette’s median generation took 5.07 minutes, between Vanilla’s 4.66 and React Router’s 5.55. The full ranges overlap. The timer spans the native agent turn, including its tools and self-tests, but not setup, independent acceptance or Lighthouse. It does not establish a framework’s intrinsic development speed.
Tokens per build
million tokens. Four independent builds per framework. Median (minimum–maximum); ranges are observations, not confidence intervals.
| Framework | Median (range) | b1 | b2 | b3 | b4 |
|---|---|---|---|---|---|
| Vanilla | 0.961 (0.903–1.057) | 0.987 | 0.936 | 0.903 | 1.057 |
| Marionette | 1.118 (0.991–1.383) | 1.383 | 0.998 | 0.991 | 1.238 |
| React Router | 1.509 (0.924–1.859) | 1.859 | 0.924 | 1.287 | 1.730 |
| Angular | 1.681 (1.414–2.556) | 1.921 | 1.442 | 2.556 | 1.414 |
| SvelteKit | 1.819 (1.688–2.065) | 1.941 | 2.065 | 1.698 | 1.688 |
| Nuxt | 2.145 (1.113–2.542) | 2.254 | 1.113 | 2.036 | 2.542 |
Token totals are final cumulative root-thread input plus output. Cached input is already part of input and is not counted twice. Reasoning detail is not added again. A large cached context and fresh output carry different assumed prices, so token count and the dollar proxy are not interchangeable rankings.
02 / QUALITY
Four working apps.
All four Marionette applications passed all 54 independent acceptance cases. So did the retained peer applications. The comparison starts with working software: listing, searching, filtering and navigating the same Pokémon dataset.
Measurement note: The route and scoring weights were the same for every app. SSR builds could benefit from server warmups, while client-side requests were subject to browser throttling. The results also reflect how each generated app loads its data.
Loading measurements
| Framework | CLS | Performance | Accessibility |
|---|---|---|---|
| Marionette | 0.111 | 96 | 100 |
| Vanilla | 0.1465 | 92.5 | 100 |
| React Router | 0 | 98 | 100 |
| SvelteKit | 0 | 100 | 100 |
| Nuxt | 0 | 99 | 100 |
| Angular | 0.109 | 79 | 100 |
Home JavaScript transferred
KiB. Four independent builds per framework. Median (minimum–maximum); ranges are observations, not confidence intervals.
| Framework | Median (range) | b1 | b2 | b3 | b4 |
|---|---|---|---|---|---|
| Vanilla | 4.48 (4.30–4.66) | 4.30 | 4.46 | 4.66 | 4.50 |
| Marionette | 25.56 (23.75–25.73) | 25.61 | 23.75 | 25.52 | 25.73 |
| SvelteKit | 36.67 (36.48–36.85) | 36.85 | 36.71 | 36.48 | 36.62 |
| Angular | 76.72 (49.00–95.75) | 95.75 | 75.93 | 49.00 | 77.52 |
| Nuxt | 82.57 (76.01–82.69) | 76.01 | 82.58 | 82.69 | 82.56 |
| React Router | 115.66 (114.31–115.73) | 115.62 | 114.31 | 115.73 | 115.69 |
Marionette transferred a median 25.6 KiB of home JavaScript, smaller than the framework peers and larger than Vanilla.
Home Total Blocking Time (TBT)
ms. Four independent builds per framework. Median (minimum–maximum); ranges are observations, not confidence intervals.
| Framework | Median (range) | b1 | b2 | b3 | b4 |
|---|---|---|---|---|---|
| SvelteKit | 0.000 (0.000–0.000) | 0.000 | 0.000 | 0.000 | 0.000 |
| Vanilla | 0.000 (0.000–224.000) | 0.000 | 224.000 | 0.000 | 0.000 |
| Marionette | 4.500 (1.000–13.000) | 1.000 | 8.000 | 1.000 | 13.000 |
| React Router | 5.500 (0.500–51.000) | 5.000 | 51.000 | 0.500 | 6.000 |
| Nuxt | 45.750 (8.000–78.000) | 8.000 | 78.000 | 60.500 | 31.000 |
| Angular | 870.428 (206.000–1,031.500) | 983.073 | 757.783 | 206.000 | 1,031.500 |
Home Largest Contentful Paint (LCP)
ms. Four independent builds per framework. Median (minimum–maximum); ranges are observations, not confidence intervals.
| Framework | Median (range) | b1 | b2 | b3 | b4 |
|---|---|---|---|---|---|
| Vanilla | 789.801 (773.069–848.322) | 805.136 | 848.322 | 773.069 | 774.466 |
| Angular | 1,646.265 (1,438.886–1,989.485) | 1,545.782 | 1,746.748 | 1,989.485 | 1,438.886 |
| SvelteKit | 1,685.932 (1,223.039–1,693.380) | 1,223.039 | 1,693.380 | 1,683.142 | 1,688.722 |
| Marionette | 1,883.280 (1,864.987–1,893.796) | 1,893.796 | 1,884.787 | 1,864.987 | 1,881.773 |
| Nuxt | 2,003.539 (1,849.128–2,086.339) | 2,086.339 | 1,849.128 | 1,985.076 | 2,022.003 |
| React Router | 2,197.548 (2,117.151–2,236.093) | 2,230.330 | 2,164.766 | 2,117.151 | 2,236.093 |
Home performance was 96 [92–97]; every route’s automated accessibility median was 100. Those scores are not a complete accessibility audit. Home CLS was 0.111. These charts cover home loading. All six routes are retained in the downloadable data.
All six routes.
Median [minimum–maximum] across four application medians. Each application contributes five measurements per route. The warm-server/cold-browser warning above applies throughout these loading comparisons.
Performance score
| Framework | Home | Filtered home | Pokémon | Move | Ability | Type |
|---|---|---|---|---|---|---|
| Marionette | 96 [92–97] | 96 [92–97] | 75 [74–81] | 96 [95–97] | 96.5 [96–97] | 96.5 [96–97] |
| Vanilla | 92.5 [91–97] | 94.5 [91–97] | 94 [82–96] | 96.5 [95–97] | 97 [97–97] | 97 [97–97] |
| React Router | 98 [97–99] | 98.5 [98–99] | 87 [67–90] | 99.5 [98–100] | 99 [99–99] | 99 [99–99] |
| SvelteKit | 100 [100–100] | 100 [100–100] | 98 [72–98] | 99 [99–100] | 100 [100–100] | 100 [100–100] |
| Nuxt | 99 [97–99] | 99 [99–99] | 82.5 [69–97] | 95.5 [92–98] | 99.5 [99–100] | 98.5 [97–99] |
| Angular | 79 [73–93] | 95 [85–96] | 73.5 [49–80] | 73.5 [70–95] | 95.5 [95–96] | 83.5 [75–96] |
LCP (ms)
| Framework | Home | Filtered home | Pokémon | Move | Ability | Type |
|---|---|---|---|---|---|---|
| Marionette | 1,883.3 [1,865–1,893.8] | 1,891.4 [1,872.1–1,908.2] | 6,118.6 [4,410.6–6,360.8] | 1,903.2 [1,093.9–1,949.4] | 1,547.1 [1,098.2–1,994.8] | 1,874.8 [1,185.9–1,978.7] |
| Vanilla | 789.8 [773.1–848.3] | 801.1 [771.2–839.9] | 2,529.6 [2,294.9–4,275.3] | 800.6 [779–1,077.1] | 1,256.9 [804.8–2,014.6] | 801.2 [782.5–801.4] |
| React Router | 2,197.5 [2,117.2–2,236.1] | 2,112.1 [1,941.8–2,197.7] | 2,858.3 [2,842.2–2,999] | 1,360.5 [1,193.9–1,620.5] | 2,015.3 [1,989.6–2,058.5] | 1,904.3 [1,843.5–1,917.3] |
| SvelteKit | 1,685.9 [1,223–1,693.4] | 1,677.7 [1,150.6–1,687.3] | 2,286.3 [2,274.1–3,545.4] | 1,882.5 [1,148.7–1,968.1] | 1,682.6 [1,113.5–1,704.2] | 1,682.2 [1,092.1–1,749.6] |
| Nuxt | 2,003.5 [1,849.1–2,086.3] | 1,842.4 [1,608.1–1,983.9] | 3,679.9 [2,424.9–4,932.4] | 2,233.2 [2,144.7–2,251.5] | 1,754.8 [1,385.6–1,841.5] | 1,846.4 [1,822.8–1,996.7] |
| Angular | 1,646.3 [1,438.9–1,989.5] | 1,678.5 [1,426.6–1,979.2] | 4,949.9 [2,595.3–7,242.2] | 1,767.9 [1,427.6–2,261.8] | 1,483.4 [1,397.8–1,917.7] | 1,527.8 [1,406.7–1,981] |
TBT (ms)
| Framework | Home | Filtered home | Pokémon | Move | Ability | Type |
|---|---|---|---|---|---|---|
| Marionette | 4.5 [1–13] | 0 [0–0] | 37 [18–72] | 98 [55–137] | 0 [0–0] | 29 [15.5–29.5] |
| Vanilla | 0 [0–224] | 0 [0–4.5] | 0 [0–115] | 100.5 [53–160.5] | 0 [0–0] | 0 [0–9] |
| React Router | 5.5 [0.5–51] | 0 [0–12.5] | 287.1 [152.5–805.5] | 45.5 [43–54] | 0 [0–6] | 0 [0–0] |
| SvelteKit | 0 [0–0] | 0 [0–0] | 2.5 [0–575] | 36.5 [5.5–65] | 0 [0–0] | 6.5 [0–34] |
| Nuxt | 45.8 [8–78] | 40.5 [16–63.5] | 145.5 [81–191] | 129.5 [42.5–274] | 44 [9–89.5] | 63 [32–161.5] |
| Angular | 870.4 [206–1,031.5] | 126.4 [90–255] | 456.5 [76–1,783.3] | 1,527.9 [112.5–2,343.2] | 82.5 [45–138.5] | 573.4 [71–921.8] |
CLS
| Framework | Home | Filtered home | Pokémon | Move | Ability | Type |
|---|---|---|---|---|---|---|
| Marionette | 0.111 [0.103–0.172] | 0.111 [0.103–0.172] | 0.111 [0.103–0.111] | 0.111 [0.103–0.111] | 0.111 [0.103–0.111] | 0.111 [0.103–0.111] |
| Vanilla | 0.1465 [0.103–0.193] | 0.1465 [0.103–0.193] | 0.102 [0.101–0.111] | 0.107 [0.103–0.111] | 0.107 [0.103–0.111] | 0.107 [0.103–0.111] |
| React Router | 0 [0–0] | 0 [0–0] | 0 [0–0] | 0 [0–0] | 0 [0–0] | 0 [0–0] |
| SvelteKit | 0 [0–0] | 0 [0–0] | 0 [0–0] | 0 [0–0] | 0 [0–0] | 0 [0–0] |
| Nuxt | 0 [0–0] | 0 [0–0] | 0 [0–0] | 0 [0–0] | 0 [0–0] | 0 [0–0] |
| Angular | 0.109 [0.008–0.134] | 0.1145 [0.107–0.204] | 0.1075 [0.004–0.118] | 0.0595 [0–0.115] | 0.113 [0.107–0.118] | 0.113 [0.107–0.118] |
JavaScript transfer (KiB)
| Framework | Home | Filtered home | Pokémon | Move | Ability | Type |
|---|---|---|---|---|---|---|
| Marionette | 25.56 [23.75–25.73] | 25.56 [23.77–25.73] | 25.56 [23.75–25.73] | 25.56 [23.75–25.73] | 25.56 [23.75–25.73] | 25.56 [23.75–25.73] |
| Vanilla | 4.48 [4.3–4.66] | 4.48 [4.3–4.66] | 4.48 [4.3–4.66] | 4.48 [4.3–4.66] | 4.48 [4.3–4.66] | 4.48 [4.3–4.66] |
| React Router | 115.66 [114.31–115.73] | 115.68 [114.36–115.73] | 115.66 [115.3–115.73] | 115.63 [114.41–115.78] | 115.63 [114.36–115.78] | 115.66 [114.36–115.78] |
| SvelteKit | 36.67 [36.48–36.85] | 36.66 [36.52–36.85] | 36.65 [36.48–36.85] | 36.67 [36.48–36.85] | 36.68 [36.52–36.85] | 36.68 [36.48–36.85] |
| Nuxt | 82.57 [76.01–82.69] | 82.52 [75.96–82.69] | 82.52 [75.96–82.69] | 82.52 [75.96–82.69] | 82.52 [75.96–82.69] | 82.52 [75.96–82.69] |
| Angular | 76.72 [49–95.75] | 76.72 [49–95.75] | 76.72 [49–97.64] | 76.72 [49–79.61] | 76.72 [49–79.61] | 76.72 [49–79.61] |
03 / SOURCE SIZE
Maintainability
Formatter-normalized nonblank source lines
lines. Four independent builds per framework. Median (minimum–maximum); ranges are observations, not confidence intervals.
| Framework | Median (range) | b1 | b2 | b3 | b4 |
|---|---|---|---|---|---|
| Vanilla | 820.0 (771.0–865.0) | 825.0 | 771.0 | 815.0 | 865.0 |
| SvelteKit | 952.5 (940.0–981.0) | 940.0 | 946.0 | 959.0 | 981.0 |
| Nuxt | 993.0 (981.0–1,031.0) | 997.0 | 1,031.0 | 981.0 | 989.0 |
| Marionette | 1,012.5 (950.0–1,053.0) | 998.0 | 950.0 | 1,027.0 | 1,053.0 |
| React Router | 1,050.0 (1,009.0–1,167.0) | 1,029.0 | 1,071.0 | 1,167.0 | 1,009.0 |
| Angular | 1,551.5 (1,343.0–1,890.0) | 1,890.0 | 1,343.0 | 1,392.0 | 1,711.0 |
Marionette’s median was 1,012.5 nonblank lines, ranging from 950 to 1,053. It sits close to Nuxt and React Router in this cohort, with Vanilla and SvelteKit below it and Angular above. The chart’s table includes every build.
How source lines were counted
All 24 applications were formatted with Prettier 3.9.10 and Svelte plugin 4.1.1 under one file-selection policy. Counts include comments and exclude blank lines, generated output and supplied fixture files. Original source was not modified.
SvelteKit b2 is 946 lines under this policy. Counting its supplied design-token stylesheet would add 69 lines; that stylesheet is excluded consistently, including its whitespace-only variant. This is a formatted source-size comparison, not a complexity measure.
A little less guessing.
Codex built four working Marionette applications. The median generation took just over five minutes, with a token-only API equivalent of about 31 cents. That gives us a more concrete answer than a framework’s age, popularity, or an agent’s opinion about it.
For this task, Marionette took less median agent time and a lower estimated token cost than each framework peer, while delivering the same tested behavior. Vanilla was leaner still. For a framework that supposedly lost on vibes, that is a useful showing.
Stiff knees, maybe. But the agents didn’t seem to mind. We’re still considering launching Buy Me an Ibuprofen.
Data and method.
Download all verified per-build metrics (JSON) · Download the full six-route assessment and methodology
The downloads below contain the verified per-build assessment. The full evidence package on GitHub includes all 24 applications, their timing and usage receipts, acceptance results, all 720 Lighthouse samples, and the normalized source analysis.
Four applications per framework is a small sample. Each route metric is the median of five mobile Lighthouse samples per application; repeated measurements do not create more independent apps. Codex CLI 0.166.0 reported gpt-6.1-sol, with effort and tier overrides unset, under 4 CPU / 8 GiB container limits.
Collection dates and dependency versions differ. Hosted Marionette documentation changed between the first and second builds, though it stayed stable within each build. No MCP tool invocation was observed, but that does not establish identical exposure. Host activity, model variation and generated architecture limit causal conclusions.
The same route, browser settings and scoring weights apply to every app. Two HTTP warmups create a warm-server/cold-browser measurement: SSR loaders can warm server API caches, while fetching a client-only shell does not run its JavaScript. All SSR apps expose artwork in initial HTML. These are measurements of the generated loading architectures, not isolated framework overhead. The local API hostname mapping also differs in some retained peer measurements, with unquantified impact. Peer values remain unchanged.
Task and results sequence: Nolan Lawson · original benchmark source. The full results and application sources and methodology are available on GitHub.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.