@invisibleloop/pulse 0.2.13 → 0.2.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- 0.2.11
1
+ 0.2.14
package/docs/server.js CHANGED
@@ -1,7 +1,15 @@
1
+ import fs from 'fs'
1
2
  import { createServer } from '../src/server/index.js'
3
+ import { initLayoutManifest } from './src/lib/layout.js'
2
4
  import { metrics } from './src/lib/stats.js'
3
5
  import { metricsStore } from './src/lib/metrics-store.js'
4
6
  metricsStore.current = metrics
7
+
8
+ // Populate hashed asset paths in layout.js before any page renders
9
+ try {
10
+ const raw = fs.readFileSync(new URL('./public/dist/manifest.json', import.meta.url), 'utf8')
11
+ initLayoutManifest(JSON.parse(raw))
12
+ } catch { /* dev: no manifest yet, layout falls back to unhashed paths */ }
5
13
  import home from './src/pages/home.js'
6
14
  import howItWorks from './src/pages/how-it-works.js'
7
15
  import faq from './src/pages/faq.js'
@@ -8,6 +8,12 @@
8
8
  import { NAV } from './nav.js'
9
9
  import pkg from '../../../package.json' with { type: 'json' }
10
10
 
11
+ // Resolve static asset paths through the build manifest (hashed in prod, raw in dev).
12
+ // Populated by initLayoutManifest() in server.js before the server starts.
13
+ let _manifest = {}
14
+ export function initLayoutManifest(m) { _manifest = m }
15
+ const asset = href => _manifest[href] || href
16
+
11
17
  const { version } = pkg
12
18
 
13
19
  function esc(s) {
@@ -90,8 +96,8 @@ export function renderLayout({ currentHref, content, prev = null, next = null })
90
96
  ${prevNextBar(prev, next)}
91
97
  </main>
92
98
  </div>
93
- <script src="/menu.js"></script>
94
- <script src="/pulse-ui.js"></script>`
99
+ <script src="${asset('/menu.js')}" defer></script>
100
+ <script src="${asset('/pulse-ui.js')}" defer></script>`
95
101
  }
96
102
 
97
103
  /**
@@ -62,14 +62,6 @@ export const NAV = [
62
62
  { label: 'Deployment', href: '/deploy' },
63
63
  ],
64
64
  },
65
- {
66
- section: 'Integrations',
67
- items: [
68
- { label: 'Supabase', href: '/supabase' },
69
- { label: 'Auth (Auth0)', href: '/auth' },
70
- { label: 'Payments (Stripe)', href: '/stripe' },
71
- ],
72
- },
73
65
  {
74
66
  section: 'Reference',
75
67
  items: [
@@ -147,6 +139,14 @@ export const NAV = [
147
139
  { label: 'Stack', href: '/components/stack' },
148
140
  ],
149
141
  },
142
+ {
143
+ section: 'Integrations',
144
+ items: [
145
+ { label: 'Supabase', href: '/supabase' },
146
+ { label: 'Auth (Auth0)', href: '/auth' },
147
+ { label: 'Payments (Stripe)', href: '/stripe' },
148
+ ],
149
+ },
150
150
  ]
151
151
 
152
152
  export function prevNext(currentHref) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@invisibleloop/pulse",
3
- "version": "0.2.13",
3
+ "version": "0.2.15",
4
4
  "type": "module",
5
5
  "description": "AI-first frontend framework. The spec is the source of truth.",
6
6
  "license": "MIT",
@@ -1 +1 @@
1
- 0.2.13
1
+ 0.2.15
package/scripts/build.js CHANGED
@@ -404,6 +404,12 @@ fs.rmSync(TMP_DIR, { recursive: true })
404
404
 
405
405
  await purgeCssStep(pages, manifest, ROOT, OUT_DIR)
406
406
 
407
+ // ---------------------------------------------------------------------------
408
+ // JS static assets — minify + hash any .js files directly in public/
409
+ // ---------------------------------------------------------------------------
410
+
411
+ await jsAssetsStep(ROOT, OUT_DIR, manifest)
412
+
407
413
  // ---------------------------------------------------------------------------
408
414
  // Report
409
415
  // ---------------------------------------------------------------------------
@@ -420,6 +426,39 @@ for (const [src, bundle] of Object.entries(manifest)) {
420
426
  }
421
427
  console.log('\n✓ manifest written to public/dist/manifest.json\n')
422
428
 
429
+ // ---------------------------------------------------------------------------
430
+ // JS static assets implementation
431
+ // ---------------------------------------------------------------------------
432
+
433
+ async function jsAssetsStep(root, outDir, manifest) {
434
+ const publicDir = path.join(root, 'public')
435
+ if (!fs.existsSync(publicDir)) return
436
+
437
+ const jsFiles = fs.readdirSync(publicDir).filter(f => f.endsWith('.js'))
438
+ if (jsFiles.length === 0) return
439
+
440
+ console.log('⚡ Hashing static JS assets...\n')
441
+
442
+ for (const file of jsFiles) {
443
+ const srcPath = path.join(publicDir, file)
444
+ const source = fs.readFileSync(srcPath, 'utf8')
445
+ const { code } = await esbuild.transform(source, { minify: true, target: 'es2018' })
446
+ const hash = createHash('sha256').update(code).digest('hex').slice(0, 8)
447
+ const name = path.basename(file, '.js')
448
+ const outName = `${name}-${hash}.js`
449
+
450
+ fs.writeFileSync(path.join(outDir, outName), code)
451
+ manifest[`/${file}`] = `/dist/${outName}`
452
+
453
+ const origKb = (Buffer.byteLength(source) / 1024).toFixed(1)
454
+ const minKb = (Buffer.byteLength(code) / 1024).toFixed(1)
455
+ console.log(` /${file.padEnd(24)} → /dist/${outName} (${minKb} kB from ${origKb} kB)`)
456
+ }
457
+
458
+ fs.writeFileSync(path.join(outDir, 'manifest.json'), JSON.stringify(manifest, null, 2))
459
+ console.log('\n✓ JS asset entries added to manifest\n')
460
+ }
461
+
423
462
  // ---------------------------------------------------------------------------
424
463
  // CSS Purge implementation
425
464
  // ---------------------------------------------------------------------------
@@ -1315,6 +1315,13 @@ function resolveSpec(spec, hydrateMap) {
1315
1315
  }
1316
1316
  }
1317
1317
 
1318
+ if (Array.isArray(spec.meta?.scripts)) {
1319
+ const resolvedScripts = spec.meta.scripts.map(href => hydrateMap[href] || href)
1320
+ if (resolvedScripts.some((s, i) => s !== spec.meta.scripts[i])) {
1321
+ resolved = { ...resolved, meta: { ...resolved.meta, scripts: resolvedScripts } }
1322
+ }
1323
+ }
1324
+
1318
1325
  return resolved
1319
1326
  }
1320
1327
 
package/src/ui/charts.js CHANGED
@@ -117,7 +117,7 @@ export function barChart({
117
117
  const ariaLabel = `Bar chart: ${data.map(d => `${d.label ?? ''} ${d.value}`).join(', ')}`
118
118
  const clsAttr = cls ? ` class="${e(cls)}"` : ''
119
119
 
120
- return `<svg${clsAttr} viewBox="0 0 ${IW} ${height}" preserveAspectRatio="none" height="${height}" style="display:block;width:100%" role="img" aria-label="${e(ariaLabel)}">${grid}${baseline}${bars}</svg>`
120
+ return `<div${clsAttr} style="width:100%;aspect-ratio:${IW}/${height}"><svg viewBox="0 0 ${IW} ${height}" width="100%" height="100%" style="display:block" role="img" aria-label="${e(ariaLabel)}">${grid}${baseline}${bars}</svg></div>`
121
121
  }
122
122
 
123
123
  // ─── Line chart ───────────────────────────────────────────────────────────────
@@ -193,7 +193,7 @@ export function lineChart({
193
193
  const ariaLabel = `Line chart: ${data.map(d => `${d.label ?? ''} ${d.value}`).join(', ')}`
194
194
  const clsAttr = cls ? ` class="${e(cls)}"` : ''
195
195
 
196
- return `<svg${clsAttr} viewBox="0 0 ${IW} ${height}" preserveAspectRatio="none" height="${height}" style="display:block;width:100%" role="img" aria-label="${e(ariaLabel)}">${grid}${areaPath}${line}${dots}${xlabels}</svg>`
196
+ return `<div${clsAttr} style="width:100%;aspect-ratio:${IW}/${height}"><svg viewBox="0 0 ${IW} ${height}" width="100%" height="100%" style="display:block" role="img" aria-label="${e(ariaLabel)}">${grid}${areaPath}${line}${dots}${xlabels}</svg></div>`
197
197
  }
198
198
 
199
199
  // ─── Donut chart ──────────────────────────────────────────────────────────────
@@ -321,7 +321,9 @@ export function sparkline({
321
321
  }
322
322
 
323
323
  const clsAttr = cls ? ` class="${e(cls)}"` : ''
324
- const dims = fluid ? `preserveAspectRatio="none" height="${height}" style="display:block;width:100%"` : `width="${width}" height="${height}"`
324
+ if (fluid) {
325
+ return `<div${clsAttr} style="width:100%;aspect-ratio:${width}/${height}"><svg viewBox="0 0 ${width} ${height}" width="100%" height="100%" style="display:block" aria-hidden="true">${areaPath}<polyline points="${points}" fill="none" stroke="${c}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></div>`
326
+ }
325
327
 
326
- return `<svg${clsAttr} viewBox="0 0 ${width} ${height}" ${dims} aria-hidden="true">${areaPath}<polyline points="${points}" fill="none" stroke="${c}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`
328
+ return `<svg${clsAttr} viewBox="0 0 ${width} ${height}" width="${width}" height="${height}" aria-hidden="true">${areaPath}<polyline points="${points}" fill="none" stroke="${c}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`
327
329
  }