mnfst 0.5.80 → 0.5.82

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.
Files changed (42) hide show
  1. package/LICENSE +1 -1
  2. package/lib/manifest.accordion.css +4 -4
  3. package/lib/manifest.appwrite.auth.js +66 -33
  4. package/lib/manifest.avatar.css +8 -8
  5. package/lib/manifest.button.css +7 -7
  6. package/lib/manifest.checkbox.css +5 -5
  7. package/lib/manifest.code.css +152 -193
  8. package/lib/manifest.code.js +841 -881
  9. package/lib/manifest.code.min.css +1 -1
  10. package/lib/manifest.colorpicker.css +11 -11
  11. package/lib/manifest.components.js +25 -155
  12. package/lib/manifest.css +278 -230
  13. package/lib/manifest.data.js +46 -2
  14. package/lib/manifest.dialog.css +2 -2
  15. package/lib/manifest.divider.css +2 -2
  16. package/lib/manifest.dropdown.css +9 -9
  17. package/lib/manifest.form.css +10 -10
  18. package/lib/manifest.input.css +9 -9
  19. package/lib/manifest.integrity.json +26 -0
  20. package/lib/manifest.js +60 -5
  21. package/lib/manifest.markdown.js +192 -79
  22. package/lib/manifest.min.css +1 -1
  23. package/lib/manifest.radio.css +1 -1
  24. package/lib/manifest.range.css +7 -7
  25. package/lib/manifest.resize.css +1 -1
  26. package/lib/manifest.router.js +49 -76
  27. package/lib/manifest.schema.json +1 -1
  28. package/lib/manifest.sidebar.css +5 -6
  29. package/lib/manifest.slides.css +5 -5
  30. package/lib/manifest.svg.js +75 -5
  31. package/lib/manifest.switch.css +4 -4
  32. package/lib/manifest.table.css +4 -4
  33. package/lib/manifest.theme.css +46 -41
  34. package/lib/manifest.toast.css +7 -7
  35. package/lib/manifest.tooltip.css +3 -3
  36. package/lib/manifest.tooltips.js +41 -0
  37. package/lib/manifest.typography.css +124 -69
  38. package/lib/manifest.utilities.css +48 -54
  39. package/lib/manifest.utilities.js +9 -29
  40. package/package.json +4 -7
  41. package/lib/manifest.export.js +0 -535
  42. package/lib/manifest.virtual.js +0 -319
package/LICENSE CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ---
4
4
 
5
- Copyright © 2025 Andrew Matlock
5
+ Copyright © 2026 Andrew Matlock
6
6
 
7
7
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8
8
 
@@ -29,7 +29,7 @@
29
29
  align-items: center;
30
30
  padding: var(--spacing-field-padding, 0.625rem) 0;
31
31
  font-weight: bold;
32
- color: var(--color-content-stark, oklch(16.6% 0.026 267));
32
+ color: var(--color-content-stark, darkslategray);
33
33
  user-select: none;
34
34
  cursor: pointer;
35
35
  transition: var(--transition, all .05s ease-in-out);
@@ -42,11 +42,11 @@
42
42
  }
43
43
 
44
44
  &:hover {
45
- color: color-mix(in oklch, var(--color-surface-1, oklch(98.17% 0.0005 95.87)) 40%, var(--color-content-stark, oklch(16.6% 0.026 267)))
45
+ color: color-mix(in oklch, var(--color-surface-1, whitesmoke) 40%, var(--color-content-stark, darkslategray))
46
46
  }
47
47
 
48
48
  &:active {
49
- color: color-mix(in oklch, var(--color-surface-1, oklch(98.17% 0.0005 95.87)) 50%, var(--color-content-stark, oklch(16.6% 0.026 267)))
49
+ color: color-mix(in oklch, var(--color-surface-1, whitesmoke) 50%, var(--color-content-stark, darkslategray))
50
50
  }
51
51
 
52
52
  /* Add custom icon */
@@ -55,7 +55,7 @@
55
55
  order: 1;
56
56
  width: 1rem;
57
57
  height: 1rem;
58
- background-color: color-mix(in oklch, var(--color-field-surface, oklch(91.79% 0.0029 264.26)) 50%, var(--color-field-inverse, oklch(16.6% 0.026 267)));
58
+ background-color: color-mix(in oklch, var(--color-field-surface, color-mix(darkslategray 10%, transparent)) 50%, var(--color-field-inverse, darkslategray));
59
59
  -webkit-mask-image: var(--icon-accordion, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 256 256'%3E%3Cpath fill='%23000' d='m184.49 136.49l-80 80a12 12 0 0 1-17-17L159 128L87.51 56.49a12 12 0 1 1 17-17l80 80a12 12 0 0 1-.02 17'/%3E%3C/svg%3E"));
60
60
  mask-image: var(--icon-accordion, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 256 256'%3E%3Cpath fill='%23000' d='m184.49 136.49l-80 80a12 12 0 0 1-17-17L159 128L87.51 56.49a12 12 0 1 1 17-17l80 80a12 12 0 0 1-.02 17'/%3E%3C/svg%3E"));
61
61
  -webkit-mask-repeat: no-repeat;
@@ -8,6 +8,21 @@
8
8
 
9
9
  /* Auth config */
10
10
 
11
+ // Refuse strings that still contain an unresolved ${VAR} reference. The loader
12
+ // runs window.ManifestDataConfig.interpolateManifest at manifest-load time, so
13
+ // by the time we read these fields the env-var substitution has already been
14
+ // applied. Anything still matching ${VAR} is an undefined env var — passing it
15
+ // to Appwrite would either silently fail or, worse, be sent verbatim as an
16
+ // HTTP header value, leaking the env var name. Loud-fail instead.
17
+ function resolvedOrNull(value, fieldName) {
18
+ if (typeof value !== 'string') return value;
19
+ if (/\$\{[^}]+\}/.test(value)) {
20
+ console.error(`[Manifest Auth] manifest.appwrite.${fieldName} references an undefined env var (${value}). Auth disabled.`);
21
+ return null;
22
+ }
23
+ return value;
24
+ }
25
+
11
26
  // Load manifest if not already loaded (loader may set __manifestLoaded / registry.manifest)
12
27
  async function ensureManifest() {
13
28
  if (window.ManifestComponentsRegistry?.manifest) {
@@ -34,13 +49,22 @@ async function getAppwriteConfig() {
34
49
  }
35
50
 
36
51
  const appwriteConfig = manifest.appwrite;
37
- const endpoint = appwriteConfig.endpoint;
38
- const projectId = appwriteConfig.projectId;
39
- const devKey = appwriteConfig.devKey; // Optional dev key to bypass rate limits in development
52
+ const endpoint = resolvedOrNull(appwriteConfig.endpoint, 'endpoint');
53
+ const projectId = resolvedOrNull(appwriteConfig.projectId, 'projectId');
54
+ // Optional dev key to bypass rate limits in development. The schema
55
+ // documents `${VAR_NAME}` interpolation for this field specifically —
56
+ // refuse to forward a literal placeholder as an HTTP header.
57
+ const devKey = appwriteConfig.devKey ? resolvedOrNull(appwriteConfig.devKey, 'devKey') : undefined;
40
58
 
41
59
  if (!endpoint || !projectId) {
42
60
  return null;
43
61
  }
62
+ // devKey is optional: if the user supplied one but it failed to resolve,
63
+ // resolvedOrNull returned null (and logged) — drop the field rather than
64
+ // initialize Appwrite with a literal `${VAR}` header.
65
+ if (appwriteConfig.devKey && devKey === null) {
66
+ return null;
67
+ }
44
68
 
45
69
  // Get auth methods from config (defaults to ["magic", "oauth"] if not specified)
46
70
  const authMethods = appwriteConfig.auth?.methods || ["magic", "oauth"];
@@ -164,6 +188,27 @@ function initializeAuthStore() {
164
188
  // Cross-tab synchronization using localStorage events
165
189
  const STORAGE_KEY = 'manifest:auth:state';
166
190
 
191
+ // Whitelist of Appwrite Session fields safe to mirror across tabs.
192
+ // CRITICALLY excludes `secret` (the bearer credential), `providerAccessToken`,
193
+ // `providerRefreshToken`, and `providerAccessTokenExpiry`. The cookie set
194
+ // on the Appwrite domain is the actual auth of record; this localStorage
195
+ // copy only supports UI cross-tab sync ("someone just logged in here").
196
+ // An XSS on this origin must not be able to lift session secrets out.
197
+ const SAFE_SESSION_FIELDS = [
198
+ '$id', 'userId', 'provider', 'expire', 'current',
199
+ 'clientName', 'osName', 'osCode', 'deviceName',
200
+ 'deviceBrand', 'deviceModel', 'countryCode', 'countryName'
201
+ ];
202
+
203
+ function sanitizeSessionForStorage(session) {
204
+ if (!session || typeof session !== 'object') return session;
205
+ const safe = {};
206
+ for (const f of SAFE_SESSION_FIELDS) {
207
+ if (f in session) safe[f] = session[f];
208
+ }
209
+ return safe;
210
+ }
211
+
167
212
  // Listen for storage events from other tabs
168
213
  window.addEventListener('storage', (e) => {
169
214
  if (e.key === STORAGE_KEY && e.newValue) {
@@ -193,7 +238,7 @@ function initializeAuthStore() {
193
238
  isAuthenticated: store.isAuthenticated,
194
239
  isAnonymous: store.isAnonymous,
195
240
  user: store.user,
196
- session: store.session,
241
+ session: sanitizeSessionForStorage(store.session),
197
242
  magicLinkSent: store.magicLinkSent,
198
243
  magicLinkExpired: store.magicLinkExpired,
199
244
  error: store.error
@@ -5769,13 +5814,11 @@ function initializeMagicLinks() {
5769
5814
  );
5770
5815
 
5771
5816
  if (isRateLimit) {
5772
- // Store callback for retry
5773
- try {
5774
- sessionStorage.setItem('manifest:magic-link:callback', JSON.stringify({ userId, secret }));
5775
- } catch (e) {
5776
- // Ignore
5777
- }
5778
- this.error = 'Rate limit exceeded. Please wait a moment and refresh the page.';
5817
+ // Don't persist {userId, secret} for retry — the secret is a
5818
+ // bearer credential that any same-origin XSS could lift. On
5819
+ // rate limit the user must re-request the magic link from
5820
+ // their email (cleanupUrl has already stripped URL params).
5821
+ this.error = 'Rate limit exceeded. Please wait a moment and request a new magic link.';
5779
5822
  this.isAuthenticated = false;
5780
5823
  this.isAnonymous = false;
5781
5824
  this.magicLinkExpired = false;
@@ -5901,15 +5944,10 @@ function handleMagicLinkCallbacks() {
5901
5944
 
5902
5945
  const callbackInfo = event.detail;
5903
5946
 
5904
- // Store callback for retry if rate limited
5905
- try {
5906
- sessionStorage.setItem('manifest:magic-link:callback', JSON.stringify({
5907
- userId: callbackInfo.userId,
5908
- secret: callbackInfo.secret
5909
- }));
5910
- } catch (e) {
5911
- console.warn('[Manifest Appwrite Auth] Could not store callback:', e);
5912
- }
5947
+ // Note: we deliberately do NOT persist {userId, secret} to sessionStorage
5948
+ // as a retry safety net. The secret is a single-use bearer credential
5949
+ // that any same-origin XSS could read; UX of "refresh to retry on rate
5950
+ // limit" isn't worth the credential-exfil risk.
5913
5951
 
5914
5952
  // Handle the callback
5915
5953
  await store.handleMagicLinkCallback(callbackInfo.userId, callbackInfo.secret);
@@ -6247,16 +6285,11 @@ function initializeCallbacks() {
6247
6285
  const secret = urlParams.get('secret');
6248
6286
  const expire = urlParams.get('expire');
6249
6287
 
6250
- // Check for stored callback (from rate limit retry)
6251
- let storedCallback = null;
6252
- try {
6253
- const stored = sessionStorage.getItem('manifest:magic-link:callback');
6254
- if (stored) {
6255
- storedCallback = JSON.parse(stored);
6256
- }
6257
- } catch (e) {
6258
- // Ignore parse errors
6259
- }
6288
+ // Eagerly clear any stale magic-link credentials left in sessionStorage
6289
+ // by an earlier (pre-fix) Manifest version. We no longer persist them,
6290
+ // and reading stale ones could resurrect a credential that should have
6291
+ // been forgotten.
6292
+ try { sessionStorage.removeItem('manifest:magic-link:callback'); } catch (e) {}
6260
6293
 
6261
6294
  // Check OAuth redirect flag
6262
6295
  const isOAuthCallback = sessionStorage.getItem('manifest:oauth:redirect') === 'true';
@@ -6267,14 +6300,14 @@ function initializeCallbacks() {
6267
6300
  const isTeamInvite = !!(teamId && membershipId && userId && secret);
6268
6301
 
6269
6302
  const callbackInfo = {
6270
- userId: userId || storedCallback?.userId,
6271
- secret: secret || storedCallback?.secret,
6303
+ userId: userId,
6304
+ secret: secret,
6272
6305
  expire: expire,
6273
6306
  teamId: teamId,
6274
6307
  membershipId: membershipId,
6275
6308
  isOAuth: isOAuthCallback,
6276
6309
  isTeamInvite: isTeamInvite,
6277
- hasCallback: !!(userId || storedCallback?.userId) && !!(secret || storedCallback?.secret),
6310
+ hasCallback: !!userId && !!secret,
6278
6311
  hasExpired: !!expire && !userId && !secret
6279
6312
  };
6280
6313
 
@@ -14,8 +14,8 @@
14
14
  font-weight: bold;
15
15
  text-align: center;
16
16
  text-transform: uppercase;
17
- color: var(--color-field-inverse, oklch(16.6% 0.026 267));
18
- background-color: var(--color-field-surface, oklch(91.79% 0.0029 264.26));
17
+ color: var(--color-field-inverse, darkslategray);
18
+ background-color: var(--color-field-surface, color-mix(darkslategray 10%, transparent));
19
19
  background-clip: content-box;
20
20
  background-size: cover;
21
21
  background-position: center;
@@ -23,7 +23,7 @@
23
23
 
24
24
  /* Icon */
25
25
  &[x-icon] {
26
- color: var(--color-content-subtle, oklch(67.4% 0.0318 251.27))
26
+ color: var(--color-content-subtle, darkgray)
27
27
  }
28
28
 
29
29
  /* Profile pic */
@@ -47,8 +47,8 @@
47
47
  width: 9px;
48
48
  height: 9px;
49
49
  z-index: 1;
50
- background-color: var(--color-field-surface, oklch(91.79% 0.0029 264.26));
51
- border: 1px solid var(--color-page, oklch(100% 0 0));
50
+ background-color: var(--color-field-surface, color-mix(darkslategray 10%, transparent));
51
+ border: 1px solid var(--color-page, white);
52
52
  border-radius: 50%
53
53
  }
54
54
  }
@@ -59,7 +59,7 @@
59
59
  background-blend-mode: normal;
60
60
 
61
61
  &:hover {
62
- background-color: var(--color-field-surface-hover, oklch(89.24% 0.0024 12.48));
62
+ background-color: var(--color-field-surface-hover, color-mix(darkslategray 15%, transparent));
63
63
  background-blend-mode: multiply;
64
64
 
65
65
  & img {
@@ -94,7 +94,7 @@
94
94
  padding-inline-end: 1.5ch;
95
95
 
96
96
  &:hover .avatar {
97
- background-color: var(--color-field-surface-hover, oklch(89.24% 0.0024 12.48));
97
+ background-color: var(--color-field-surface-hover, color-mix(darkslategray 15%, transparent));
98
98
  transition: var(--transition, all .05s ease-in-out)
99
99
  }
100
100
  }
@@ -107,7 +107,7 @@
107
107
 
108
108
  :where(.avatar) {
109
109
  margin-inline-end: calc(var(--spacing-field-height, 2.25rem) * -0.3);
110
- box-shadow: 0 0 0 3px var(--color-page, oklch(100% 0 0))
110
+ box-shadow: 0 0 0 3px var(--color-page, white)
111
111
  }
112
112
  }
113
113
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  @layer components {
4
4
 
5
- :where(button:not(.link), [role=button], [type=button], [type=reset], [type=submit], select):not(.unstyle) {
5
+ :where(button:not(.link), [role=button], [type=button], [type=reset], [type=submit], select):not(code, .unstyle) {
6
6
  display: inline-flex;
7
7
  flex-flow: row;
8
8
  justify-content: center;
@@ -17,13 +17,13 @@
17
17
  overflow: hidden;
18
18
  white-space: nowrap;
19
19
  text-overflow: ellipsis;
20
- color: var(--color-field-inverse, oklch(16.6% 0.026 267));
21
- background-color: var(--color-field-surface, oklch(91.79% 0.0029 264.26));
20
+ color: var(--color-field-inverse, darkslategray);
21
+ background-color: var(--color-field-surface, color-mix(darkslategray 10%, transparent));
22
22
  border-width: 0;
23
23
  border-style: solid;
24
24
  border-color: transparent;
25
25
  border-radius: var(--radius, 0.5rem);
26
- outline-color: var(--color-line, oklch(48.3% 0.006422 17.4 / 0.15));
26
+ outline-color: var(--color-line, color-mix(darkslategray 10%, transparent));
27
27
  cursor: pointer;
28
28
  transition: var(--transition, all .05s ease-in-out);
29
29
  appearance: button;
@@ -52,15 +52,15 @@
52
52
  }
53
53
 
54
54
  &:hover {
55
- background-color: var(--color-field-surface-hover, oklch(89.24% 0.0024 12.48))
55
+ background-color: var(--color-field-surface-hover, color-mix(darkslategray 15%, transparent))
56
56
  }
57
57
 
58
58
  &:active {
59
- background-color: var(--color-field-surface-hover, oklch(89.24% 0.0024 12.48))
59
+ background-color: var(--color-field-surface-hover, color-mix(darkslategray 15%, transparent))
60
60
  }
61
61
 
62
62
  &:focus-visible {
63
- background-color: var(--color-field-surface, oklch(91.79% 0.0029 264.26))
63
+ background-color: var(--color-field-surface, color-mix(darkslategray 10%, transparent))
64
64
  }
65
65
  }
66
66
 
@@ -16,13 +16,13 @@
16
16
  &:checked {
17
17
 
18
18
  &:hover {
19
- background-color: var(--color-field-surface-hover, oklch(89.24% 0.0024 12.48));
20
- border-color: var(--color-field-surface-hover, oklch(89.24% 0.0024 12.48))
19
+ background-color: var(--color-field-surface-hover, color-mix(darkslategray 15%, transparent));
20
+ border-color: var(--color-field-surface-hover, color-mix(darkslategray 15%, transparent))
21
21
  }
22
22
 
23
23
  &:active {
24
- background-color: var(--color-field-surface-hover, oklch(89.24% 0.0024 12.48));
25
- border-color: var(--color-field-surface-hover, oklch(89.24% 0.0024 12.48))
24
+ background-color: var(--color-field-surface-hover, color-mix(darkslategray 15%, transparent));
25
+ border-color: var(--color-field-surface-hover, color-mix(darkslategray 15%, transparent))
26
26
  }
27
27
  }
28
28
 
@@ -34,7 +34,7 @@
34
34
  left: 50%;
35
35
  width: 60%;
36
36
  height: 60%;
37
- background-color: var(--color-field-inverse, oklch(16.6% 0.026 267));
37
+ background-color: var(--color-field-inverse, darkslategray);
38
38
  -webkit-mask-image: var(--icon-checkbox, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='currentColor' d='m0 11l2-2l5 5L18 3l2 2L7 18z'/%3E%3C/svg%3E"));
39
39
  mask-image: var(--icon-checkbox, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='currentColor' d='m0 11l2-2l5 5L18 3l2 2L7 18z'/%3E%3C/svg%3E"));
40
40
  -webkit-mask-repeat: no-repeat;