@commonpub/layer 0.72.0 → 0.72.2

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.
@@ -141,6 +141,8 @@ function formatCount(n: number | undefined): string {
141
141
  border: var(--border-width-default) solid var(--border);
142
142
  overflow: hidden;
143
143
  transition: transform 0.15s, box-shadow 0.15s;
144
+ -webkit-backdrop-filter: var(--surface-backdrop, none);
145
+ backdrop-filter: var(--surface-backdrop, none);
144
146
  }
145
147
 
146
148
  .cpub-cc:hover {
@@ -114,6 +114,8 @@ useFocusTrap(dialogRef, () => props.open, close);
114
114
  background: var(--surface);
115
115
  border: var(--border-width-default) solid var(--border);
116
116
  box-shadow: var(--shadow-xl);
117
+ -webkit-backdrop-filter: var(--surface-backdrop, none);
118
+ backdrop-filter: var(--surface-backdrop, none);
117
119
  width: 520px;
118
120
  max-width: 90vw;
119
121
  max-height: 70vh;
@@ -193,6 +193,8 @@ useFocusTrap(dialogRef, () => props.show, handleClose);
193
193
  display: flex;
194
194
  flex-direction: column;
195
195
  max-height: 80vh;
196
+ -webkit-backdrop-filter: var(--surface-backdrop, none);
197
+ backdrop-filter: var(--surface-backdrop, none);
196
198
  }
197
199
 
198
200
  .cpub-import-header {
@@ -52,6 +52,8 @@ useFocusTrap(dialogRef, () => props.show, () => emit('dismiss'));
52
52
  max-width: 420px;
53
53
  width: 100%;
54
54
  box-shadow: var(--shadow-md);
55
+ -webkit-backdrop-filter: var(--surface-backdrop, none);
56
+ backdrop-filter: var(--surface-backdrop, none);
55
57
  }
56
58
 
57
59
  .cpub-publish-errors-title {
@@ -87,6 +87,8 @@ useFocusTrap(dialogRef, () => open.value, close);
87
87
  background: var(--bg); border: var(--border-width-default) solid var(--border);
88
88
  width: 100%; max-width: 420px; padding: 24px;
89
89
  box-shadow: var(--shadow-md);
90
+ -webkit-backdrop-filter: var(--surface-backdrop, none);
91
+ backdrop-filter: var(--surface-backdrop, none);
90
92
  }
91
93
  .cpub-rfd-header {
92
94
  display: flex; align-items: center; justify-content: space-between;
@@ -100,6 +100,8 @@ async function handleShare(): Promise<void> {
100
100
  background: var(--surface);
101
101
  border: var(--border-width-default) solid var(--border);
102
102
  box-shadow: var(--shadow-lg);
103
+ -webkit-backdrop-filter: var(--surface-backdrop, none);
104
+ backdrop-filter: var(--surface-backdrop, none);
103
105
  padding: 24px;
104
106
  max-width: 420px;
105
107
  width: 90vw;
@@ -150,6 +150,8 @@ async function readFile(file: File): Promise<void> {
150
150
  box-shadow: var(--shadow-xl);
151
151
  display: flex; flex-direction: column;
152
152
  max-height: 80vh;
153
+ -webkit-backdrop-filter: var(--surface-backdrop, none);
154
+ backdrop-filter: var(--surface-backdrop, none);
153
155
  }
154
156
 
155
157
  .md-import-header {
@@ -323,6 +323,8 @@ const userUsername = computed(() => user.value?.username ?? '');
323
323
  background: var(--surface); border: var(--border-width-default) solid var(--border);
324
324
  box-shadow: var(--shadow-md); z-index: 200; display: flex; flex-direction: column; padding: 4px 0;
325
325
  margin-top: 4px;
326
+ -webkit-backdrop-filter: var(--surface-backdrop, none);
327
+ backdrop-filter: var(--surface-backdrop, none);
326
328
  }
327
329
  :deep(.cpub-nav-panel-item) {
328
330
  display: flex; align-items: center; gap: 8px; padding: 8px 14px;
@@ -363,7 +365,7 @@ const userUsername = computed(() => user.value?.username ?? '');
363
365
  .cpub-user-avatar { width: 28px; height: 28px; border-radius: 50%; background: var(--purple-bg); border: var(--border-width-default) solid var(--purple); display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700; color: var(--purple); font-family: var(--font-mono); overflow: hidden; }
364
366
  .cpub-user-avatar-img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
365
367
  .cpub-user-menu-wrapper { position: relative; }
366
- .cpub-user-dropdown { position: absolute; top: calc(100% + 6px); right: 0; min-width: 180px; background: var(--surface); border: var(--border-width-default) solid var(--border); box-shadow: var(--shadow-md); z-index: 200; display: flex; flex-direction: column; padding: 4px 0; }
368
+ .cpub-user-dropdown { position: absolute; top: calc(100% + 6px); right: 0; min-width: 180px; background: var(--surface); border: var(--border-width-default) solid var(--border); box-shadow: var(--shadow-md); z-index: 200; display: flex; flex-direction: column; padding: 4px 0; -webkit-backdrop-filter: var(--surface-backdrop, none); backdrop-filter: var(--surface-backdrop, none); }
367
369
  .cpub-dropdown-item { display: flex; align-items: center; gap: 8px; padding: 8px 16px; font-size: 12px; color: var(--text-dim); text-decoration: none; background: none; border: none; cursor: pointer; font-family: inherit; width: 100%; text-align: left; transition: all 0.15s; }
368
370
  .cpub-dropdown-item:hover { background: var(--surface2); color: var(--text); }
369
371
  .cpub-dropdown-item i { width: 14px; text-align: center; font-size: 11px; }
@@ -375,7 +377,7 @@ const userUsername = computed(() => user.value?.username ?? '');
375
377
 
376
378
  .cpub-mobile-toggle { display: none; width: 32px; height: 32px; background: none; border: var(--border-width-default) solid transparent; color: var(--text-dim); font-size: 16px; cursor: pointer; align-items: center; justify-content: center; }
377
379
  .cpub-mobile-menu { display: none; position: fixed; inset: 0; top: var(--cpub-topbar-height, 48px); z-index: 99; background: var(--color-surface-overlay-light); }
378
- :deep(.cpub-mobile-nav) { background: var(--surface); border-bottom: var(--border-width-default) solid var(--border); padding: 8px 0; display: flex; flex-direction: column; box-shadow: var(--shadow-md); }
380
+ :deep(.cpub-mobile-nav) { background: var(--surface); border-bottom: var(--border-width-default) solid var(--border); padding: 8px 0; display: flex; flex-direction: column; box-shadow: var(--shadow-md); -webkit-backdrop-filter: var(--surface-backdrop, none); backdrop-filter: var(--surface-backdrop, none); }
379
381
  :deep(.cpub-mobile-link) { display: flex; align-items: center; gap: 10px; padding: 10px 20px; font-size: 13px; color: var(--text-dim); text-decoration: none; transition: background 0.1s; }
380
382
  :deep(.cpub-mobile-link:hover) { background: var(--surface2); color: var(--text); }
381
383
  :deep(.cpub-mobile-link i) { width: 16px; text-align: center; font-size: 12px; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonpub/layer",
3
- "version": "0.72.0",
3
+ "version": "0.72.2",
4
4
  "type": "module",
5
5
  "main": "./nuxt.config.ts",
6
6
  "files": [
@@ -53,16 +53,16 @@
53
53
  "vue": "^3.4.0",
54
54
  "vue-router": "^4.3.0",
55
55
  "zod": "^4.3.6",
56
- "@commonpub/auth": "0.8.0",
57
- "@commonpub/config": "0.21.0",
58
- "@commonpub/server": "2.84.1",
59
- "@commonpub/ui": "0.13.0",
60
- "@commonpub/learning": "0.5.2",
61
- "@commonpub/theme-studio": "0.6.0",
62
56
  "@commonpub/docs": "0.6.3",
63
- "@commonpub/schema": "0.40.0",
64
57
  "@commonpub/editor": "0.7.11",
58
+ "@commonpub/config": "0.21.0",
59
+ "@commonpub/schema": "0.40.1",
60
+ "@commonpub/auth": "0.8.0",
65
61
  "@commonpub/protocol": "0.13.0",
62
+ "@commonpub/theme-studio": "0.6.1",
63
+ "@commonpub/learning": "0.5.2",
64
+ "@commonpub/ui": "0.13.1",
65
+ "@commonpub/server": "2.84.1",
66
66
  "@commonpub/explainer": "0.7.15"
67
67
  },
68
68
  "devDependencies": {
@@ -3,7 +3,7 @@
3
3
  // Users only toggle light/dark within that family.
4
4
 
5
5
  import { eq } from 'drizzle-orm';
6
- import { instanceSettings } from '@commonpub/schema';
6
+ import { instanceSettings, isSafeBgImageValue } from '@commonpub/schema';
7
7
  import {
8
8
  getCustomTokenOverrides,
9
9
  listCustomThemes,
@@ -15,6 +15,21 @@ import { THEME_TO_FAMILY, FAMILY_VARIANTS, IS_DARK, VALID_THEME_IDS } from '../.
15
15
 
16
16
  const CACHE_TTL = 60_000; // 1 minute, admin changes propagate fast
17
17
 
18
+ /**
19
+ * Sink-side guard for the one token whose VALUE can fetch when rendered:
20
+ * `--bg-image` feeds `background-image`, so a `url(...)` is a beacon/exfil
21
+ * channel. The themes POST/PUT already reject unsafe values, but the token
22
+ * map has other write paths (the generic admin settings route writes
23
+ * `instance_settings` keys wholesale), so the render sink enforces the same
24
+ * allowlist: a non-gradient bg-image is dropped, never injected.
25
+ */
26
+ export function sanitizeRenderTokens(tokens: Record<string, string>): Record<string, string> {
27
+ const v = tokens['bg-image'];
28
+ if (v === undefined || isSafeBgImageValue(v)) return tokens;
29
+ const { 'bg-image': _dropped, ...rest } = tokens;
30
+ return rest;
31
+ }
32
+
18
33
  interface CachedThemeState {
19
34
  /** The admin's chosen default theme (built-in id, custom data-attr, or registered id) */
20
35
  defaultTheme: string;
@@ -138,7 +153,7 @@ export async function resolveThemeContext(
138
153
  const sib = state.customByAttr.get(sibAttr);
139
154
  if (sib) members.push({ attr: sibAttr, rec: sib });
140
155
  }
141
- themeVariants = members.map((m) => ({ attr: m.attr, tokens: m.rec.tokens }));
156
+ themeVariants = members.map((m) => ({ attr: m.attr, tokens: sanitizeRenderTokens(m.rec.tokens) }));
142
157
  const lightM = members.find((m) => !m.rec.isDark);
143
158
  const darkM = members.find((m) => m.rec.isDark);
144
159
  if (members.length === 2 && lightM && darkM) {
@@ -167,7 +182,7 @@ export async function resolveThemeContext(
167
182
  instanceTheme: admin,
168
183
  isDark,
169
184
  themeVariants,
170
- overrides: { ...state.tokenOverrides },
185
+ overrides: sanitizeRenderTokens({ ...state.tokenOverrides }),
171
186
  pair,
172
187
  fontHref,
173
188
  };
package/theme/layouts.css CHANGED
@@ -283,6 +283,9 @@
283
283
  border: var(--border-width-default) solid var(--border);
284
284
  overflow: hidden;
285
285
  transition: transform var(--transition-fast), box-shadow var(--transition-fast);
286
+ /* Glass treatment hook — `none` default is a true no-op. */
287
+ -webkit-backdrop-filter: var(--surface-backdrop, none);
288
+ backdrop-filter: var(--surface-backdrop, none);
286
289
  }
287
290
 
288
291
  .cpub-card:hover {