Bibhu Mishra Claude Sonnet 4.6 commited on
Commit
6c2eb4a
·
1 Parent(s): 1deac60

feat: add PWA support with manifest, service worker, and icons

Browse files

Adds vite-plugin-pwa (Workbox) for installable PWA experience:
- Web app manifest with brand colors, icons, standalone display mode
- Service worker with NetworkFirst for /api/* and CacheFirst for static assets
- 192x192, 512x512, and 180x180 apple-touch-icon PNGs generated from brand gradient
- PWA meta tags in index.html for iOS and Android home screen installation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

generate-pwa-icons.js ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+ // Generates solid-color PNG icons for PWA manifest using only Node built-ins.
3
+ const zlib = require('zlib');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ function u32be(n) {
8
+ const b = Buffer.alloc(4);
9
+ b.writeUInt32BE(n, 0);
10
+ return b;
11
+ }
12
+
13
+ function crc32(buf) {
14
+ let c = 0xFFFFFFFF;
15
+ const t = [];
16
+ for (let n = 0; n < 256; n++) {
17
+ let v = n;
18
+ for (let k = 0; k < 8; k++) v = (v & 1) ? (0xEDB88320 ^ (v >>> 1)) : (v >>> 1);
19
+ t[n] = v;
20
+ }
21
+ for (let i = 0; i < buf.length; i++) c = (c >>> 8) ^ t[(c ^ buf[i]) & 0xFF];
22
+ return (c ^ 0xFFFFFFFF) >>> 0;
23
+ }
24
+
25
+ function chunk(type, data) {
26
+ const tb = Buffer.from(type, 'ascii');
27
+ return Buffer.concat([u32be(data.length), tb, data, u32be(crc32(Buffer.concat([tb, data])))]);
28
+ }
29
+
30
+ function makePNG(w, h, topR, topG, topB, botR, botG, botB) {
31
+ const sig = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
32
+ const ihdr = chunk('IHDR', Buffer.concat([u32be(w), u32be(h), Buffer.from([8, 2, 0, 0, 0])]));
33
+
34
+ const rows = [];
35
+ for (let y = 0; y < h; y++) {
36
+ const t = y / (h - 1);
37
+ const r = Math.round(topR + (botR - topR) * t);
38
+ const g = Math.round(topG + (botG - topG) * t);
39
+ const b = Math.round(topB + (botB - topB) * t);
40
+ const row = Buffer.alloc(1 + w * 3);
41
+ row[0] = 0;
42
+ for (let x = 0; x < w; x++) { row[1 + x*3] = r; row[2 + x*3] = g; row[3 + x*3] = b; }
43
+ rows.push(row);
44
+ }
45
+ const idat = chunk('IDAT', zlib.deflateSync(Buffer.concat(rows)));
46
+ const iend = chunk('IEND', Buffer.alloc(0));
47
+ return Buffer.concat([sig, ihdr, idat, iend]);
48
+ }
49
+
50
+ // Brand gradient: #1e40af → #1e3a5f (top to bottom)
51
+ const [tR, tG, tB] = [0x1e, 0x40, 0xaf];
52
+ const [bR, bG, bB] = [0x1e, 0x3a, 0x5f];
53
+
54
+ const out = path.join(__dirname, 'ui/public');
55
+ fs.writeFileSync(path.join(out, 'pwa-192x192.png'), makePNG(192, 192, tR, tG, tB, bR, bG, bB));
56
+ fs.writeFileSync(path.join(out, 'pwa-512x512.png'), makePNG(512, 512, tR, tG, tB, bR, bG, bB));
57
+ fs.writeFileSync(path.join(out, 'apple-touch-icon.png'), makePNG(180, 180, tR, tG, tB, bR, bG, bB));
58
+ console.log('PWA icons written to ui/public/');
package-lock.json CHANGED
The diff for this file is too large to render. See raw diff
 
ui/index.html CHANGED
@@ -4,7 +4,13 @@
4
  <meta charset="UTF-8">
5
  <link rel="icon" type="image/svg+xml" href="/favicon.svg">
6
  <link rel="icon" type="image/x-icon" href="/favicon.ico" sizes="32x32">
 
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
 
 
 
 
8
  <meta name="description" content="AlphaForge — AI-Powered Trading Intelligence. Autonomous paper trading with GPT-4o driven market analysis.">
9
  <title>AlphaForge</title>
10
  <link rel="preconnect" href="https://fonts.googleapis.com">
 
4
  <meta charset="UTF-8">
5
  <link rel="icon" type="image/svg+xml" href="/favicon.svg">
6
  <link rel="icon" type="image/x-icon" href="/favicon.ico" sizes="32x32">
7
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png">
8
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
9
+ <meta name="theme-color" content="#1e40af">
10
+ <meta name="mobile-web-app-capable" content="yes">
11
+ <meta name="apple-mobile-web-app-capable" content="yes">
12
+ <meta name="apple-mobile-web-app-status-bar-style" content="default">
13
+ <meta name="apple-mobile-web-app-title" content="AlphaForge">
14
  <meta name="description" content="AlphaForge — AI-Powered Trading Intelligence. Autonomous paper trading with GPT-4o driven market analysis.">
15
  <title>AlphaForge</title>
16
  <link rel="preconnect" href="https://fonts.googleapis.com">
ui/package.json CHANGED
@@ -36,6 +36,7 @@
36
  "oxlint": "~1.60.0",
37
  "typescript": "~6.0.0",
38
  "vite": "^8.0.8",
 
39
  "vite-plugin-vue-devtools": "^8.1.1",
40
  "vue-tsc": "^3.2.6"
41
  },
 
36
  "oxlint": "~1.60.0",
37
  "typescript": "~6.0.0",
38
  "vite": "^8.0.8",
39
+ "vite-plugin-pwa": "^1.3.0",
40
  "vite-plugin-vue-devtools": "^8.1.1",
41
  "vue-tsc": "^3.2.6"
42
  },
ui/public/apple-touch-icon.png ADDED
ui/public/pwa-192x192.png ADDED
ui/public/pwa-512x512.png ADDED
ui/vite.config.ts CHANGED
@@ -4,6 +4,7 @@ import path from 'node:path'
4
  import { defineConfig } from 'vite'
5
  import vue from '@vitejs/plugin-vue'
6
  import vueDevTools from 'vite-plugin-vue-devtools'
 
7
 
8
  // https://vite.dev/config/
9
  export default defineConfig({
@@ -12,6 +13,48 @@ export default defineConfig({
12
  plugins: [
13
  vue(),
14
  vueDevTools(),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  ],
16
  resolve: {
17
  alias: {
 
4
  import { defineConfig } from 'vite'
5
  import vue from '@vitejs/plugin-vue'
6
  import vueDevTools from 'vite-plugin-vue-devtools'
7
+ import { VitePWA } from 'vite-plugin-pwa'
8
 
9
  // https://vite.dev/config/
10
  export default defineConfig({
 
13
  plugins: [
14
  vue(),
15
  vueDevTools(),
16
+ VitePWA({
17
+ registerType: 'autoUpdate',
18
+ includeAssets: ['favicon.svg', 'favicon.ico', 'apple-touch-icon.png'],
19
+ manifest: {
20
+ name: 'AlphaForge',
21
+ short_name: 'AlphaForge',
22
+ description: 'AI-Powered autonomous paper trading intelligence',
23
+ theme_color: '#1e40af',
24
+ background_color: '#f3f4f6',
25
+ display: 'standalone',
26
+ start_url: '/',
27
+ scope: '/',
28
+ icons: [
29
+ { src: 'pwa-192x192.png', sizes: '192x192', type: 'image/png' },
30
+ { src: 'pwa-512x512.png', sizes: '512x512', type: 'image/png' },
31
+ { src: 'pwa-512x512.png', sizes: '512x512', type: 'image/png', purpose: 'any maskable' },
32
+ ],
33
+ },
34
+ workbox: {
35
+ navigateFallback: '/index.html',
36
+ globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
37
+ runtimeCaching: [
38
+ {
39
+ urlPattern: /^.*\/api\/.*/i,
40
+ handler: 'NetworkFirst',
41
+ options: {
42
+ cacheName: 'api-cache',
43
+ networkTimeoutSeconds: 10,
44
+ cacheableResponse: { statuses: [0, 200] },
45
+ },
46
+ },
47
+ {
48
+ urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
49
+ handler: 'CacheFirst',
50
+ options: {
51
+ cacheName: 'google-fonts-cache',
52
+ cacheableResponse: { statuses: [0, 200] },
53
+ },
54
+ },
55
+ ],
56
+ },
57
+ }),
58
  ],
59
  resolve: {
60
  alias: {