Nine LLMs, one prompt: build a PWA app for an offer. We looked at code quality, how much manual tweaking was required, and how long it took. The LLMs ran through Opencode, except for GLM 5.2 and GPT 5.6 Sol.
Six models pulled it off: DeepSeek V4 Flash, DeepSeek V4 Pro, Kimi 2.7 Code, Claude Sonnet 5, Claude Opus 4.8, and GPT 5.6 Sol. GPT 5.5 refused to complete the task. The local Qwen 3.6 with 35 billion parameters and GLM 5.2 both produced code, but it didn’t work without manual fixes.
Previously, we compared how three AI models — Kimi, DeepSeek, and GLM — handle translating and localizing a landing page for a given geo: https://cpa.rip/ai/landing-page-adaptation-test/.
How We Ran the Test
We used: DeepSeek V4 Flash, DeepSeek V4 Pro, Kimi 2.7 Code, the local LLM Qwen 3.6 with 35 billion parameters, GLM 5.2, Claude Sonnet 5, Claude Opus 4.8, GPT 5.5, and GPT 5.6 Sol.
Each model was given the same working prompt from our previous article about building a PWA app using Claude Sonnet 4.6. The prompt was not modified or supplemented.
After generation was complete, we checked:
- whether the app worked;
- how completely the functionality was implemented;
- whether the interface matched the requirements;
- how long the task took.
Key Findings
Fully completed the task: DeepSeek V4 Flash, DeepSeek V4 Pro, Claude Sonnet 5, Claude Opus 4.8, Kimi 2.7 Code, and GPT 5.6 Sol — all five models built a working PWA app with no need for further fixes.
Partially completed: GLM 5.2 refused to do the task in the Zcode app, but via web chat it generated the core code — though it didn’t create the project files and skipped the WebView installation step. Resubmitting the prompt in the web chat then resulted in a refusal.
Failed to complete the task: Qwen 3.6 generated only an app shell, while GPT 5.5 declined to perform the task, citing safety restrictions.
The fastest model was DeepSeek V4 Flash, which took about 3 minutes to generate the finished app.
The interface closest to the original was produced by Claude Sonnet 5, Claude Opus 4.8, and GPT 5.6 Sol.
| Model | Time | Functionality | Fixes needed | Interface |
| DeepSeek V4 Flash | 3 min | ✅ | No | ⭐⭐⭐ |
| DeepSeek V4 Pro | 22 min | ✅ | No | ⭐⭐⭐⭐ |
| Claude Sonnet 5 | 9 min | ✅ | No | ⭐⭐⭐⭐⭐ |
| Kimi 2.7 Code | 15 min | ✅ | No | ⭐⭐⭐⭐ |
| GLM 5.2 | 30 min* | ⚠️ Partial | Yes | ⭐⭐⭐⭐ |
| GPT 5.6 Sol | 18 min | ✅ | No | ⭐⭐⭐⭐⭐ |
| Claude Opus 4.8 | 41 min | ✅ | No | ⭐⭐⭐⭐⭐ |
| Qwen 3.6 (local) | 5 min | ❌ | Needs rework | — |
| GPT 5.5 | — | 🚫 Refused | — | — |
GLM 5.2
Execution time: about 20 minutes + roughly 10 minutes of manual fixes.
The model wrote the app code but did not create the necessary project files. They had to be created manually before launch, following the instructions.

During testing, it also turned out that GLM skipped the step of installing the WebView app on the device, meaning the app couldn’t be considered fully ready for use without additional work.
GLM 5.2, via the Zcode app, refused to perform the assigned task:

Final Result
![]() | ![]() | ![]() |
GPT-5.5 and GPT-5.6 Sol
GPT-5.5 declined to complete the task using the given prompt, while GPT-5.6 Sol successfully generated a fully working app in 18 minutes via Codex. The PWA app’s interface ended up looking very close to the original.
![]() | ![]() | ![]() | ![]() |
Kimi 2.7 Code
Execution time: about 15 minutes.
Kimi fully completed the assigned task. The app launched and worked successfully.
Final Result
![]() | ![]() | ![]() | ![]() |
Qwen 3.6
The model failed to complete the assigned task.
Instead of a full PWA app, only an interface shell was created. After the build, the Install button didn’t do anything, so the app can’t be considered functional.
Final Result

Claude Sonnet 5 and Opus 4.8
Both Sonnet 5 and Opus 4.8 successfully completed the assigned task and produced fully functional PWA apps with no manual fixes required. They also delivered the most polished interface, staying as close as possible to the original app.
The main difference between the models was generation speed. Claude Sonnet 5 completed the task in about 9 minutes, while Claude Opus 4.8 took around 41 minutes, the longest time among all the models tested.
Final Result — Sonnet 5
![]() | ![]() | ![]() | ![]() |
Final Result — Opus 4.8
![]() | ![]() | ![]() | ![]() |
DeepSeek V4 Flash and V4 Pro
Comparing DeepSeek V4 Flash and V4 Pro highlighted the difference between fast and more powerful models. Flash finished generating in just three minutes, but its interface quality was the weakest among the successfully completed projects. V4 Pro took nearly seven times longer, but the result ended up much closer to the original app.
Final Result — V4 Flash
![]() | ![]() | ![]() | ![]() |
Final Result — V4 Pro
![]() | ![]() | ![]() | ![]() |
Prompt for Generating a PWA
Pwa prompt english · MDCopyPWA Traffic Arbitrage — Compact Prompt
PARAMETERS
APP_NAME : Generate randomly
SHORT_NAME : Generate randomly
DEVELOPER : SkyNet Entertainment Ltd.
CATEGORY : Casino · Games
LANGUAGE : es
AFFILIATE_LINK : link
RATING : 4.8 | REVIEWS: 12K | DOWNLOADS: 500K+
SIZE : 4.2 MB | AGE: 18+
SPLASH_SECONDS : 10
SCREENSHOT_COUNT: 3
DESCRIPTION: …
REVIEWS (name / date / text):
1: Generate randomly / Generate randomly / Generate randomly
2: Generate randomly / Generate randomly / Generate randomly ?
3: Generate randomly / Generate randomly / Generate randomly
SCREENSHOTS (theme / emoji / bg-color / badge):
1: Slots / ? / dark purple / "⭐️ TOP"
2: Bonus / ? / dark green / "? Bonus"
3: Jackpot / ? / dark orange / "? Jackpot"
ZIP CONTENTS (all files in root, no subfolders)
index.html, app.html, manifest.json, sw.js,
icon-192.png, icon-512.png,
screenshot1.png … screenshot{SCREENSHOT_COUNT}.png
index.html
First lines immediately after <meta charset>:
html<script>
if (window.matchMedia("(display-mode:standalone)").matches || navigator.standalone)
{ document.documentElement.style.cssText="background:#fff;visibility:hidden";
window.location.replace("./app.html"); }
</script>
<style>body{visibility:hidden}html.ready body{visibility:visible}</style>
Page sections (Google Play style):
Sticky header — Google Play logo (4-color SVG) + search/profile icons
App block — 80×80px icon, app name, developer, category, badges
Statistics — star rating ★, downloads, editor's choice medal, age rating
Install button — color #01875f, 100% width, height 40px, border-radius 8px
Progress bar — below the button, hidden by default
Screenshot carousel — 130×230px thumbnails, border-radius 10px, horizontal scroll; show only screenshot1…N
Description — with "More ▾" toggle
Reviews — avatar (first letter), name, date, stars, text
Info grid 2×2 — size / Android version / downloads / developer
Fixed bottom bar — icon + app name + button
Button logic (in-memory variables only, ⛔️ no localStorage/cookies):
jsvar deferredPrompt = null, installed = false, animationPlayed = false;
addEventListener('beforeinstallprompt', e => { e.preventDefault(); deferredPrompt = e; });
addEventListener('appinstalled', () => {
if (animationPlayed) return;
installed = true; deferredPrompt = null;
runInstallAnimation(() => setOpenState());
});
// On "Install" button click:
// • deferredPrompt exists → prompt() → accepted: run animation → setOpenState; dismissed: restore "Install" label
// • no prompt + iOS → show modal with Safari instruction
// • no prompt + not iOS → wait 4s → prompt()
runInstallAnimation(callback):
jsfunction runInstallAnimation(cb) {
if (animationPlayed) { cb?.(); return; }
animationPlayed = true;
// button → spinner + "Installing…", bg #e8f5e9, color #01875f
// progress bar steps: [10, 25, 45, 65, 80, 92, 100] with pauses between
// after reaching 100% → wait 500ms → call cb()
}
setOpenState / openApp:
jsfunction setOpenState() {
installed = true; // in-memory only — do NOT write to localStorage
// both buttons (main + bottom bar) → "✓ Open", onclick = openApp
}
function openApp() { window.location.href = './app.html'; }
End of script:
jsdocument.documentElement.classList.add('ready');
if ('serviceWorker' in navigator) navigator.serviceWorker.register('./sw.js');
// + add ripple animation to all buttons
app.html — Splash Screen + Redirect
Splash screen (white background, all content centered):
icon-512.png — 112×112px, border-radius 24px, box-shadow
App name — 26px, font-weight 400, color #202124
Spinner — border-top-color #01875f
Entrance animation: scale(0.75→1) + opacity(0→1), duration 0.5s
Progress bar — position: fixed; bottom: 0, height 3px, color #01875f; fills evenly over SPLASH_SECONDS seconds, jumps to 100% 400ms before the end
Redirect (fires 300ms after page load):
jsvar TARGET = 'AFFILIATE_LINK';
var host = new URL(TARGET).host, path = TARGET.replace(/^https?://[^/]+/, '');
var intent = 'intent://' + host + path
+ '#Intent;scheme=https;package=com.android.chrome'
+ ';S.browser_fallback_url=' + encodeURIComponent(TARGET) + ';end';
window.location.href = intent;
setTimeout(() => window.location.replace(TARGET), 1500); // fallback for non-Android
manifest.json
json{
"name": "APP_NAME",
"short_name": "SHORT_NAME",
"start_url": "./index.html",
"scope": "./",
"display": "standalone",
"orientation": "portrait",
"background_color": "#ffffff",
"theme_color": "#01875f",
"icons": [
{"src":"icon-192.png","sizes":"192x192","type":"image/png","purpose":"any"},
{"src":"icon-512.png","sizes":"512x512","type":"image/png","purpose":"maskable"}
]
}
⛔️ Do NOT add a "screenshots" field — Chrome renders them inside the install dialog, which breaks UX.
sw.js
jsconst CACHE = 'app-v1';
const STATIC = [
'./manifest.json', './icon-192.png', './icon-512.png',
...Array.from({ length: SCREENSHOT_COUNT }, (_, i) => `./screenshot${i + 1}.png`)
];
// install → caches.open(CACHE).addAll(STATIC); self.skipWaiting()
// activate → delete old caches; self.clients.claim()
// fetch rules:
// cross-origin request → pass through (do not intercept)
// index.html / app.html / "/"→ network-first; fallback: cache(index.html)
// everything else → cache-first → on miss: fetch → store in cache
Icons (Python Pillow)
icon-192.png — 192×192px
icon-512.png — 512×512px
Background: vertical gradient #1a0a3e → #0d0628
First letter of SHORT_NAME, white, bold, centered
Screenshots (Playwright Chromium)
Generate exactly SCREENSHOT_COUNT files (screenshot1.png … screenshotN.png)
Viewport: 260×460px, save as PNG
Each screenshot structure:
Header — app name + colored badge (from SCREENSHOT_N params)
Center — large emoji ~80px with CSS drop-shadow filter
Bottom panel — background: rgba(0,0,0,0.65) + backdrop-filter: blur; title in #ffd700; description text; 3 decorative emojis; "Play Now" button in #01875f








































































