@invisibleloop/pulse 0.20.1 → 0.20.3

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.
@@ -11,8 +11,11 @@
11
11
 
12
12
  import { validateSpec } from '../spec/schema.js'
13
13
 
14
+ import { existsSync } from 'fs'
15
+
14
16
  const tmpFile = process.argv[2]
15
17
  if (!tmpFile) { process.stdout.write('Invalid: no file path provided'); process.exit(0) }
18
+ if (!existsSync(tmpFile)) { process.stdout.write(`Invalid: file not found — ${tmpFile}`); process.exit(0) }
16
19
 
17
20
  let mod
18
21
  try {
@@ -296,8 +296,15 @@ function bindEvents(el, dispatch, signal) {
296
296
  }
297
297
 
298
298
  // Dialog open — data-dialog-open="dialogId"
299
+ // If the element also carries data-event, fire the mutation first so the view
300
+ // re-renders with updated state before the dialog opens.
299
301
  const dialogOpenTarget = e.target?.closest?.('[data-dialog-open]')
300
302
  if (dialogOpenTarget) {
303
+ const eventAttr = dialogOpenTarget.dataset.event
304
+ if (eventAttr) {
305
+ const [type, name] = parseEventAttr(eventAttr)
306
+ if (type === 'click') { e.preventDefault(); dispatch(name, e) }
307
+ }
301
308
  const dialog = document.getElementById(dialogOpenTarget.dataset.dialogOpen)
302
309
  if (dialog?.showModal) { e.preventDefault(); dialog.showModal() }
303
310
  return
@@ -472,6 +479,8 @@ function morphAttrs(cur, nxt) {
472
479
  }
473
480
  }
474
481
  for (const { name } of Array.from(cur.attributes)) {
482
+ // `open` on <dialog> is managed by the native showModal/close API — never remove it
483
+ if (cur.tagName === 'DIALOG' && name === 'open') continue
475
484
  if (!nxt.hasAttribute(name)) cur.removeAttribute(name)
476
485
  }
477
486
  }
@@ -611,9 +611,25 @@ export async function createServer(entries, options = {}) {
611
611
  const brandCache = new TtlCache(500)
612
612
  brandCache.startEviction(30_000) // brand TTL is 60s, scan every 30s
613
613
 
614
- // Load manifest — maps source hydrate paths to production bundle paths
615
- const hydrateMap = loadManifest(manifest, staticDir)
616
- const runtimeBundle = hydrateMap['_runtime'] || ''
614
+ // Load manifest — maps source hydrate paths to production bundle paths.
615
+ // When staticDir is set and no manifest is explicitly provided, the manifest is
616
+ // re-read from disk on every request so that `pulse build` takes effect immediately
617
+ // without a server restart. The file is small (~1 kB) and the read is synchronous,
618
+ // so the overhead is negligible.
619
+ // When manifest is an explicit object (including {} from the dev server to disable
620
+ // manifest-based hydration), it is used as-is without disk reads. This ensures that
621
+ // passing manifest: {} always means "no manifest" — even when staticDir is set.
622
+ const _manifestStatic = (manifest !== null && typeof manifest === 'object') ? manifest : null
623
+ const _manifestPath = _manifestStatic ? null
624
+ : typeof manifest === 'string' ? manifest
625
+ : (manifest === null && staticDir) ? path.join(staticDir, 'dist', 'manifest.json') : null
626
+ const getHydrateMap = _manifestStatic
627
+ ? () => _manifestStatic
628
+ : () => {
629
+ if (!_manifestPath) return {}
630
+ try { return JSON.parse(fs.readFileSync(_manifestPath, 'utf8')) } catch { return {} }
631
+ }
632
+
617
633
 
618
634
  // Auto-detect favicon in staticDir
619
635
  const faviconPath = staticDir
@@ -905,6 +921,8 @@ export async function createServer(entries, options = {}) {
905
921
  ctx.store = await resolveStoreState(storeDef, ctx)
906
922
  }
907
923
 
924
+ const hydrateMap = getHydrateMap()
925
+ const runtimeBundle = hydrateMap['_runtime'] || ''
908
926
  const spec = resolveSpec(match.spec, hydrateMap)
909
927
 
910
928
  // Per-spec timeout overrides the global default
@@ -1691,33 +1709,6 @@ function mergeCtxHeaders(ctx, headers) {
1691
1709
  // Manifest & spec resolution
1692
1710
  // ---------------------------------------------------------------------------
1693
1711
 
1694
- /**
1695
- * Load a hydrate manifest.
1696
- * Accepts a manifest object, a path to a JSON file, or auto-detects from
1697
- * staticDir/dist/manifest.json when staticDir is set.
1698
- *
1699
- * @param {Object|string|null} manifest
1700
- * @param {string|null} staticDir
1701
- * @returns {Object} map of source paths → bundle paths
1702
- */
1703
- function loadManifest(manifest, staticDir) {
1704
- if (!manifest && !staticDir) return {}
1705
-
1706
- if (manifest && typeof manifest === 'object') return manifest
1707
-
1708
- const manifestPath = typeof manifest === 'string'
1709
- ? manifest
1710
- : staticDir ? path.join(staticDir, 'dist', 'manifest.json') : null
1711
-
1712
- if (!manifestPath) return {}
1713
-
1714
- try {
1715
- return JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
1716
- } catch {
1717
- return {}
1718
- }
1719
- }
1720
-
1721
1712
  /**
1722
1713
  * Return a copy of the spec with the hydrate path and any meta.styles paths
1723
1714
  * resolved to their production bundle paths (if manifest entries exist).