Twelve LLMs, one prompt: build a PWA app for an offer. We looked at code quality, how much manual refinement was needed, and how long it took. Cloud models ran through Opencode, except for GLM 5.2 and GPT 5.6 Sol, while local models ran on DGX Spark
Nine completed the task: DeepSeek V4 Flash, DeepSeek V4 Pro, Kimi 2.7 Code, Claude Sonnet 5, Claude Opus 4.8, GPT 5.6 Sol, the 27-billion-parameter local Qwen3.6-27, official local Qwen3.8 27b, and uncensored Qwen3.8 27b. GPT 5.5 refused to complete the task. GLM 5.2 produced code, but it did not work without manual refinement. The 35-billion-parameter local Qwen3.6-35b finished a working app after the second prompt.
Previously, we compared how three neural networks, Kimi, DeepSeek, and GLM, handle translating and localizing a landing page for a specified GEO: https://cpa.rip/ai/landing-page-adaptation-test/.
How We Conducted the Test
We used: DeepSeek V4 Flash, DeepSeek V4 Pro, Kimi 2.7 Code, GLM 5.2, Claude Sonnet 5, Claude Opus 4.8, GPT 5.5, and GPT 5.6 Sol. We also used the local LLMs Qwen3.6-27B Dense and Qwen3.6-35b A3B, official Qwen3.8 27b, and uncensored Qwen3.8 27b
Each model received the same working prompt from the previous article on building a PWA app with Claude Sonnet 4.6. The prompt was not changed or expanded.
After generation was complete, we checked:
- whether the app worked;
- how fully the functionality was implemented;
- whether the interface met 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, GPT 5.6 Sol, Qwen3.6-27B, official Qwen3.8 27b, and uncensored Qwen3.8 27b. All nine models created a working PWA app without requiring further refinement.
Partially completed the task: GLM 5.2 refused to do it in the Zcode app, but generated the main code through web chat. However, it did not create the project files and skipped the WebView installation step. Resubmitting the prompt in web chat resulted in another refusal. Qwen3.6-35b produced a working app after the second prompt: “It doesn’t work, check and fix it”
Did not complete the task: GPT 5.5 declined to do it, citing security restrictions.
The fastest model was DeepSeek V4 Flash, which took about 3 minutes to generate a finished app.
The interface closest to the original was produced by Claude Sonnet 5, Claude Opus 4.8, GPT 5.6 Sol, Qwen3.6-27B, and uncensored Qwen3.8 27b.
Cloud Neural Networks
| Model | Time | Functionality | Refinements | 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* | ⚠️ Partially | Yes | ⭐⭐⭐⭐ |
| GPT 5.6 Sol | 18 min | ✅ | No | ⭐⭐⭐⭐⭐ |
| Claude Opus 4.8 | 41 min | ✅ | No | ⭐⭐⭐⭐⭐ |
| GPT 5.5 | – | 🚫 Refused | – | – |
Local LLMs on DGX Spark
| Model | Time | Functionality | Refinements | Interface |
| Qwen3.6-35b (Local LLM) | 10 min | ✅ | No | ⭐⭐⭐⭐ |
| Qwen3.6-27B (local LLM) | 16 min | ✅ | No | ⭐⭐⭐⭐⭐ |
| Qwen3.8 27b (local LLM) off. | 40 min | ✅ | No | ⭐⭐⭐ |
| Qwen3.8 27b (local LLM) unc. | 96 minutes | ✅ | No | ⭐⭐⭐⭐⭐ |
Results
GLM 5.2
Completion time: about 20 minutes + approximately 10 minutes of manual refinement.
The model wrote the app code but did not create the required project files. You had to create them manually before launch according to the instructions.

Testing also showed that GLM skipped the step of installing the WebView app on the device, so the app could not be considered fully ready for use without additional refinement.
GLM 5.2 refused to perform the assigned task through the Zcode app:

Final Result
![]() | ![]() | ![]() |
GPT-5.5 and GPT-5.6 Sol
GPT-5.5 did not perform the task using the provided prompt, while GPT-5.6 Sol successfully generated a fully working app through Codex in 18 minutes. The PWA app interface was as close to the original as possible.
![]() | ![]() | ![]() | ![]() |
Kimi 2.7 Code
Completion time: about 15 minutes.
Kimi fully completed the assigned task. The app launched and worked successfully.
Final Result
![]() | ![]() | ![]() | ![]() |
Local LLMs Qwen3.6-27B Dense, Qwen3.6-35b A3B, official Qwen3.8 27b, and uncensored Qwen3.8 27b
All four models completed the assigned task, although Qwen3.6-35b A3B did so in 2 prompts. PWA apps generated in Qwen3.6-27B Dense and Qwen3.6-35b A3B function successfully on a device running Android 16, but do not launch on Android 11.
The app generated by official Qwen3.8 27b and uncensored Qwen3.8 27b also worked on Android 11. At the same time, official Qwen3.8 27b refused to create a copy of an app from Google Play: it did not make the original icons and also refused to add fake reviews and an artificially inflated rating.
Final Result: Qwen3.6-27B Dense
![]() | ![]() | ![]() |
Final Result: Qwen3.6-35b A3B
![]() | ![]() | ![]() | ![]() |
Final Result: official Qwen3.8 27b
![]() | ![]() | ![]() | ![]() |
Final Result: uncensored Qwen3.8 27b
![]() | ![]() | ![]() | ![]() |
Claude Sonnet 5 and Opus 4.8
Both Sonnet 5 and Opus 4.8 successfully completed the assigned task and created fully working PWA apps without requiring manual refinement. They also delivered the strongest interface, as close as possible to the original app.
The main difference between the models was generation speed. Claude Sonnet 5 completed the task in approximately 9 minutes, while Claude Opus 4.8 took about 41 minutes, the longest result among all tested cloud models.
Final Result: Sonnet 5
![]() | ![]() | ![]() | ![]() |
Final Result: Opus 4.8
![]() | ![]() | ![]() | ![]() |
DeepSeek V4 Flash and V4 Pro
Comparing DeepSeek V4 Flash and V4 Pro showed the difference between fast and more powerful models. Flash completed generation in just three minutes, but its interface quality was the weakest among the successfully completed projects. V4 Pro took nearly seven times longer, but its result was significantly closer to the original app.
Final Result: V4 Flash
![]() | ![]() | ![]() | ![]() |
Final Result: V4 Pro
![]() | ![]() | ![]() | ![]() |
PWA Generation Prompt
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























































































