mnfst 0.5.62 → 0.5.64

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.
@@ -404,7 +404,7 @@ document.addEventListener('alpine:init', ensureDropdownPluginInitialized);
404
404
  // If Alpine is already initialized when this script loads, initialize immediately
405
405
  if (window.Alpine && typeof window.Alpine.directive === 'function') {
406
406
  setTimeout(ensureDropdownPluginInitialized, 0);
407
- } else if (document.readyState === 'complete') {
407
+ } else {
408
408
  // If document is already loaded but Alpine isn't ready yet, wait for it
409
409
  const checkAlpine = setInterval(() => {
410
410
  if (window.Alpine && typeof window.Alpine.directive === 'function') {
@@ -154,7 +154,7 @@ document.addEventListener('alpine:init', ensureIconPluginInitialized);
154
154
  if (window.Alpine && typeof window.Alpine.directive === 'function') {
155
155
  // Small delay to ensure Alpine is fully initialized
156
156
  setTimeout(ensureIconPluginInitialized, 0);
157
- } else if (document.readyState === 'complete') {
157
+ } else {
158
158
  // If document is already loaded but Alpine isn't ready yet, wait for it
159
159
  const checkAlpine = setInterval(() => {
160
160
  if (window.Alpine && typeof window.Alpine.directive === 'function') {
package/lib/manifest.js CHANGED
@@ -198,7 +198,8 @@
198
198
  'tabs',
199
199
  'slides',
200
200
  'resize',
201
- 'colorpicker'
201
+ 'colorpicker',
202
+ 'url-parameters'
202
203
  ];
203
204
 
204
205
  // Appwrite integration plugins (opt-in only, never auto-loaded)
@@ -248,20 +249,16 @@
248
249
  let _pluginBase = null;
249
250
  function setPluginBase(b) { _pluginBase = b || null; }
250
251
  function getPluginUrl(pluginName, version = DEFAULT_VERSION) {
252
+ // Map hyphenated plugin API names to their dotted file names.
253
+ // `appwrite-auth` → `manifest.appwrite.auth.js`
254
+ // `url-parameters` → `manifest.url.parameters.js`
255
+ const fileName = pluginName.replace(/-/g, '.');
251
256
  if (_pluginBase) {
252
257
  const base = _pluginBase.replace(/\/$/, '');
253
- if (pluginName.startsWith('appwrite-')) {
254
- const appwriteName = pluginName.replace('appwrite-', 'appwrite.');
255
- return `${base}/manifest.${appwriteName}.js`;
256
- }
257
- return `${base}/manifest.${pluginName}.js`;
258
+ return `${base}/manifest.${fileName}.js`;
258
259
  }
259
260
  const base = getBaseUrl(version);
260
- if (pluginName.startsWith('appwrite-')) {
261
- const appwriteName = pluginName.replace('appwrite-', 'appwrite.');
262
- return `${base}/manifest.${appwriteName}.min.js`;
263
- }
264
- return `${base}/manifest.${pluginName}.min.js`;
261
+ return `${base}/manifest.${fileName}.min.js`;
265
262
  }
266
263
 
267
264
  // Resolve Alpine CDN URL from a data-alpine value (version tag or full URL)
@@ -696,7 +696,7 @@ if (document.readyState === 'loading') {
696
696
  // If Alpine is already initialized when this script loads, initialize immediately
697
697
  if (window.Alpine && typeof window.Alpine.magic === 'function') {
698
698
  setTimeout(ensureLocalizationPluginInitialized, 0);
699
- } else if (document.readyState === 'complete') {
699
+ } else {
700
700
  const checkAlpine = setInterval(() => {
701
701
  if (window.Alpine && typeof window.Alpine.magic === 'function') {
702
702
  clearInterval(checkAlpine);
@@ -6,6 +6,17 @@ let markedPromise = null;
6
6
  // Cache for fetched markdown files to prevent duplicate requests
7
7
  const markdownCache = new Map();
8
8
 
9
+ // Invalidate the markdown fetch cache when mnfst-run signals a data file
10
+ // changed on disk. Without this, a saved .md file is re-read by the data
11
+ // plugin but x-markdown still serves the old content from cache; combined
12
+ // with the lastProcessedContent short-circuit in the directive's effect,
13
+ // the article appears blank until the user manually reloads.
14
+ if (typeof window !== 'undefined') {
15
+ window.addEventListener('manifest:dev-reload', () => {
16
+ markdownCache.clear();
17
+ });
18
+ }
19
+
9
20
  // Load marked.js from CDN
10
21
  async function loadMarkedJS() {
11
22
  if (typeof marked !== 'undefined') {
@@ -527,8 +538,17 @@ async function initializeMarkdownPlugin() {
527
538
  }
528
539
  }
529
540
 
530
- // Skip if content hasn't changed (prevents unnecessary re-renders)
541
+ // Skip re-render if content hasn't changed, but still restore
542
+ // visibility — during a dev-reload the data plugin briefly
543
+ // clears its source cache, which makes the expression
544
+ // resolve to undefined and pushes opacity to 0; if we
545
+ // early-return here without restoring it, the article stays
546
+ // hidden even though innerHTML is intact.
531
547
  if (markdownContent === lastProcessedContent) {
548
+ if (el.innerHTML && el.innerHTML.trim() !== '') {
549
+ hasContent = true;
550
+ el.style.opacity = '1';
551
+ }
532
552
  return;
533
553
  }
534
554
  lastProcessedContent = markdownContent;
@@ -431,7 +431,7 @@ document.addEventListener('alpine:init', ensureResizePluginInitialized);
431
431
  // If Alpine is already initialized when this script loads, initialize immediately
432
432
  if (window.Alpine && typeof window.Alpine.directive === 'function') {
433
433
  setTimeout(ensureResizePluginInitialized, 0);
434
- } else if (document.readyState === 'complete') {
434
+ } else {
435
435
  const checkAlpine = setInterval(() => {
436
436
  if (window.Alpine && typeof window.Alpine.directive === 'function') {
437
437
  clearInterval(checkAlpine);
@@ -162,7 +162,7 @@ document.addEventListener('alpine:init', ensureSlidesPluginInitialized);
162
162
  // If Alpine is already initialized when this script loads, initialize immediately
163
163
  if (window.Alpine && typeof window.Alpine.directive === 'function') {
164
164
  setTimeout(ensureSlidesPluginInitialized, 0);
165
- } else if (document.readyState === 'complete') {
165
+ } else {
166
166
  const checkAlpine = setInterval(() => {
167
167
  if (window.Alpine && typeof window.Alpine.directive === 'function') {
168
168
  clearInterval(checkAlpine);
@@ -97,7 +97,7 @@ document.addEventListener('alpine:init', ensureThemePluginInitialized);
97
97
  // If Alpine is already initialized when this script loads, initialize immediately
98
98
  if (window.Alpine && typeof window.Alpine.directive === 'function') {
99
99
  setTimeout(ensureThemePluginInitialized, 0);
100
- } else if (document.readyState === 'complete') {
100
+ } else {
101
101
  // If document is already loaded but Alpine isn't ready yet, wait for it
102
102
  const checkAlpine = setInterval(() => {
103
103
  if (window.Alpine && typeof window.Alpine.directive === 'function') {
@@ -273,7 +273,7 @@ document.addEventListener('alpine:init', ensureToastPluginInitialized);
273
273
  // If Alpine is already initialized when this script loads, initialize immediately
274
274
  if (window.Alpine && typeof window.Alpine.directive === 'function') {
275
275
  setTimeout(ensureToastPluginInitialized, 0);
276
- } else if (document.readyState === 'complete') {
276
+ } else {
277
277
  // If document is already loaded but Alpine isn't ready yet, wait for it
278
278
  const checkAlpine = setInterval(() => {
279
279
  if (window.Alpine && typeof window.Alpine.directive === 'function') {
@@ -300,7 +300,7 @@ document.addEventListener('alpine:init', ensureTooltipPluginInitialized);
300
300
 
301
301
  if (window.Alpine && typeof window.Alpine.directive === 'function') {
302
302
  setTimeout(ensureTooltipPluginInitialized, 0);
303
- } else if (document.readyState === 'complete') {
303
+ } else {
304
304
  const checkAlpine = setInterval(() => {
305
305
  if (window.Alpine && typeof window.Alpine.directive === 'function') {
306
306
  clearInterval(checkAlpine);
@@ -0,0 +1,236 @@
1
+ /* Manifest URL Parameters */
2
+
3
+ function initializeUrlParametersPlugin() {
4
+ // Initialize empty parameters store
5
+ Alpine.store('urlParams', {
6
+ current: {},
7
+ _initialized: false
8
+ });
9
+
10
+ // Cache for debounced updates
11
+ const updateTimeouts = new Map();
12
+ const DEBOUNCE_DELAY = 300;
13
+
14
+ // Helper to parse query string
15
+ function parseQueryString(queryString) {
16
+ const params = new URLSearchParams(queryString);
17
+ const result = {};
18
+
19
+ for (const [key, value] of params.entries()) {
20
+ // Handle array values (comma-separated)
21
+ if (value.includes(',')) {
22
+ result[key] = value.split(',').filter(Boolean);
23
+ } else {
24
+ result[key] = value;
25
+ }
26
+ }
27
+
28
+ return result;
29
+ }
30
+
31
+ // Helper to stringify query object
32
+ function stringifyQueryObject(query) {
33
+ const params = new URLSearchParams();
34
+
35
+ for (const [key, value] of Object.entries(query)) {
36
+ if (Array.isArray(value)) {
37
+ params.set(key, value.filter(Boolean).join(','));
38
+ } else if (value != null && value !== '') {
39
+ params.set(key, value);
40
+ }
41
+ }
42
+
43
+ return params.toString();
44
+ }
45
+
46
+ // Helper to ensure value is in array format
47
+ function ensureArray(value) {
48
+ if (Array.isArray(value)) return value;
49
+ if (value == null || value === '') return [];
50
+ return [value];
51
+ }
52
+
53
+ // Update URL with new query parameters
54
+ async function updateURL(updates, action = 'set') {
55
+
56
+ const url = new URL(window.location.href);
57
+ const currentParams = parseQueryString(url.search);
58
+
59
+ // Apply updates based on action
60
+ for (const [key, value] of Object.entries(updates)) {
61
+ switch (action) {
62
+ case 'add':
63
+ const currentAdd = ensureArray(currentParams[key]);
64
+ const newValues = ensureArray(value);
65
+ currentParams[key] = [...new Set([...currentAdd, ...newValues])];
66
+ break;
67
+
68
+ case 'remove':
69
+ const currentRemove = ensureArray(currentParams[key]);
70
+ const removeValue = ensureArray(value)[0]; // Take first value to remove
71
+ currentParams[key] = currentRemove.filter(v => v !== removeValue);
72
+ if (currentParams[key].length === 0) {
73
+ delete currentParams[key];
74
+ }
75
+ break;
76
+
77
+ case 'set':
78
+ default:
79
+ if (value == null || value === '') {
80
+ delete currentParams[key];
81
+ } else {
82
+ currentParams[key] = value;
83
+ }
84
+ break;
85
+ }
86
+ }
87
+
88
+ // Update URL
89
+ const newQueryString = stringifyQueryObject(currentParams);
90
+ url.search = newQueryString ? `?${newQueryString}` : '';
91
+
92
+ // Update URL using pushState to ensure changes are visible
93
+ window.history.pushState({}, '', url.toString());
94
+
95
+ // Update store
96
+ Alpine.store('urlParams', {
97
+ current: currentParams,
98
+ _initialized: true
99
+ });
100
+
101
+ // Dispatch event
102
+ document.dispatchEvent(new CustomEvent('url-updated', {
103
+ detail: { updates, action }
104
+ }));
105
+
106
+ return currentParams;
107
+ }
108
+
109
+ // Add $url magic method
110
+ Alpine.magic('url', () => {
111
+ const store = Alpine.store('urlParams');
112
+
113
+ return new Proxy({}, {
114
+ get(target, prop) {
115
+ // Handle special keys
116
+ if (prop === Symbol.iterator || prop === 'then' || prop === 'catch' || prop === 'finally') {
117
+ return undefined;
118
+ }
119
+
120
+ // Get current value
121
+ const value = store.current[prop];
122
+
123
+ // Return a proxy for the value
124
+ return new Proxy({}, {
125
+ get(target, key) {
126
+ if (key === 'value') {
127
+ // Ensure arrays are returned as arrays, not strings
128
+ if (Array.isArray(value)) return value;
129
+ if (typeof value === 'string' && value.includes(',')) {
130
+ return value.split(',').filter(Boolean);
131
+ }
132
+ // Return undefined/null values as they are (for proper falsy checks)
133
+ return value;
134
+ }
135
+ if (key === 'set') return (newValue) => {
136
+ clearTimeout(updateTimeouts.get(prop));
137
+ const timeout = setTimeout(() => {
138
+ updateURL({ [prop]: newValue }, 'set');
139
+ }, DEBOUNCE_DELAY);
140
+ updateTimeouts.set(prop, timeout);
141
+ };
142
+ if (key === 'add') return (newValue) => {
143
+ clearTimeout(updateTimeouts.get(prop));
144
+ const timeout = setTimeout(() => {
145
+ updateURL({ [prop]: newValue }, 'add');
146
+ }, DEBOUNCE_DELAY);
147
+ updateTimeouts.set(prop, timeout);
148
+ };
149
+ if (key === 'remove') return (value) => {
150
+ clearTimeout(updateTimeouts.get(prop));
151
+ const timeout = setTimeout(() => {
152
+ updateURL({ [prop]: value }, 'remove');
153
+ }, DEBOUNCE_DELAY);
154
+ updateTimeouts.set(prop, timeout);
155
+ };
156
+ if (key === 'clear') return () => {
157
+ clearTimeout(updateTimeouts.get(prop));
158
+ const timeout = setTimeout(() => {
159
+ updateURL({ [prop]: null }, 'set');
160
+ }, DEBOUNCE_DELAY);
161
+ updateTimeouts.set(prop, timeout);
162
+ };
163
+ return undefined;
164
+ },
165
+ set(target, key, newValue) {
166
+ if (key === 'value') {
167
+ // Make value settable for x-model compatibility
168
+ clearTimeout(updateTimeouts.get(prop));
169
+ const timeout = setTimeout(() => {
170
+ updateURL({ [prop]: newValue }, 'set');
171
+ }, DEBOUNCE_DELAY);
172
+ updateTimeouts.set(prop, timeout);
173
+ return true;
174
+ }
175
+ return false;
176
+ }
177
+ });
178
+ }
179
+ });
180
+ });
181
+
182
+ // Initialize with current URL parameters
183
+ const initialParams = parseQueryString(window.location.search);
184
+ Alpine.store('urlParams', {
185
+ current: initialParams,
186
+ _initialized: true
187
+ });
188
+
189
+ // Listen for popstate events
190
+ window.addEventListener('popstate', () => {
191
+ const params = parseQueryString(window.location.search);
192
+ Alpine.store('urlParams', {
193
+ current: params,
194
+ _initialized: true
195
+ });
196
+ });
197
+ }
198
+
199
+ // Track initialization to prevent duplicates
200
+ let urlParametersPluginInitialized = false;
201
+
202
+ function ensureUrlParametersPluginInitialized() {
203
+ if (urlParametersPluginInitialized) return;
204
+ if (!window.Alpine || typeof window.Alpine.directive !== 'function') return;
205
+
206
+ urlParametersPluginInitialized = true;
207
+ initializeUrlParametersPlugin();
208
+ }
209
+
210
+ // Expose on window for loader to call if needed
211
+ window.ensureUrlParametersPluginInitialized = ensureUrlParametersPluginInitialized;
212
+
213
+ // Handle both DOMContentLoaded and alpine:init
214
+ if (document.readyState === 'loading') {
215
+ document.addEventListener('DOMContentLoaded', ensureUrlParametersPluginInitialized);
216
+ }
217
+
218
+ document.addEventListener('alpine:init', ensureUrlParametersPluginInitialized);
219
+
220
+ // If Alpine is already initialized when this script loads, initialize immediately.
221
+ // Otherwise ALWAYS poll until Alpine is available — the previous logic gated the
222
+ // polling on `document.readyState === 'complete'`, which produced a dead window
223
+ // when the loader injected this plugin script after DOMContentLoaded but before
224
+ // document complete: alpine:init had already fired, the readyState gate failed,
225
+ // and the plugin sat unregistered for the lifetime of the page.
226
+ if (window.Alpine && typeof window.Alpine.directive === 'function') {
227
+ setTimeout(ensureUrlParametersPluginInitialized, 0);
228
+ } else {
229
+ const checkAlpine = setInterval(() => {
230
+ if (window.Alpine && typeof window.Alpine.directive === 'function') {
231
+ clearInterval(checkAlpine);
232
+ ensureUrlParametersPluginInitialized();
233
+ }
234
+ }, 10);
235
+ setTimeout(() => clearInterval(checkAlpine), 5000);
236
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst",
3
- "version": "0.5.62",
3
+ "version": "0.5.64",
4
4
  "private": false,
5
5
  "workspaces": [
6
6
  "templates/starter",
@@ -28,6 +28,7 @@
28
28
  "publish:starter": "cd packages/create-starter && npm publish --auth-type=web",
29
29
  "publish:run": "cd packages/run && npm publish --auth-type=web",
30
30
  "publish:render": "cd packages/render && npm publish --auth-type=web",
31
+ "publish:claude": "cd packages/create-claude && npm publish --auth-type=web",
31
32
  "prepublishOnly": "npm run build",
32
33
  "test": "vitest run",
33
34
  "lint": "echo 'No linting configured'"
@@ -66,4 +67,4 @@
66
67
  "bugs": {
67
68
  "url": "https://github.com/andrewmatlock/manifest/issues"
68
69
  }
69
- }
70
+ }