@commonpub/layer 0.21.4 → 0.21.6

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.
@@ -34,7 +34,7 @@ const visible = computed(() => !hasConsented.value && hasNonEssentialCookies.val
34
34
  z-index: var(--z-toast);
35
35
  background: var(--surface);
36
36
  border-top: var(--border-width-default) solid var(--border);
37
- box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.1);
37
+ box-shadow: 0 -2px 0 var(--border);
38
38
  }
39
39
 
40
40
  .cpub-consent-inner {
@@ -79,14 +79,14 @@ useFocusTrap(dialogRef, () => open.value, close);
79
79
  <style scoped>
80
80
  .cpub-rfd-overlay {
81
81
  position: fixed; inset: 0; z-index: 9999;
82
- background: rgba(0, 0, 0, 0.5); display: flex;
82
+ background: var(--color-surface-overlay, rgba(0, 0, 0, 0.5)); display: flex;
83
83
  align-items: center; justify-content: center;
84
84
  padding: 16px;
85
85
  }
86
86
  .cpub-rfd-dialog {
87
87
  background: var(--bg); border: var(--border-width-default) solid var(--border);
88
88
  width: 100%; max-width: 420px; padding: 24px;
89
- box-shadow: 4px 4px 0 var(--shadow);
89
+ box-shadow: var(--shadow-md);
90
90
  }
91
91
  .cpub-rfd-header {
92
92
  display: flex; align-items: center; justify-content: space-between;
@@ -156,7 +156,7 @@ function removeTag(tag: string): void {
156
156
  padding: 7px 10px;
157
157
  font-size: 12px;
158
158
  color: var(--text);
159
- font-family: system-ui, -apple-system, sans-serif;
159
+ font-family: var(--font-sans);
160
160
  outline: none;
161
161
  margin-bottom: 6px;
162
162
  transition: border-color 0.15s;
@@ -14,6 +14,14 @@ const { data: userHubs } = useLazyFetch<{ items: Array<{ id: string; name: strin
14
14
  const sharing = ref(false);
15
15
  const selectedHub = ref('');
16
16
 
17
+ // Parent mounts/unmounts this modal via v-if, so it's always "open" while
18
+ // mounted. A local ref flipped on mount drives useFocusTrap's watcher
19
+ // (false -> true), activating the trap, Esc handler and scroll lock.
20
+ const contentRef = ref<HTMLElement | null>(null);
21
+ const visible = ref(false);
22
+ onMounted(() => { visible.value = true; });
23
+ useFocusTrap(contentRef, () => visible.value, () => emit('close'));
24
+
17
25
  async function handleShare(): Promise<void> {
18
26
  if (!selectedHub.value) return;
19
27
  sharing.value = true;
@@ -35,9 +43,9 @@ async function handleShare(): Promise<void> {
35
43
 
36
44
  <template>
37
45
  <div class="cpub-modal-backdrop" @click.self="emit('close')">
38
- <div class="cpub-modal-content">
46
+ <div ref="contentRef" class="cpub-modal-content" role="dialog" aria-modal="true" aria-labelledby="cpub-share-hub-title">
39
47
  <div class="cpub-modal-header">
40
- <h3 class="cpub-modal-title">Share to Hub</h3>
48
+ <h3 id="cpub-share-hub-title" class="cpub-modal-title">Share to Hub</h3>
41
49
  <button class="cpub-modal-close" aria-label="Close" @click="emit('close')"><i class="fa-solid fa-xmark"></i></button>
42
50
  </div>
43
51
 
@@ -10,6 +10,9 @@ const emit = defineEmits<{
10
10
  import: [md: string, mode: 'append' | 'replace'];
11
11
  }>();
12
12
 
13
+ const dialogRef = ref<HTMLElement | null>(null);
14
+ useFocusTrap(dialogRef, () => props.show, () => emit('close'));
15
+
13
16
  const activeTab = ref<'paste' | 'file'>('paste');
14
17
  const markdownText = ref('');
15
18
  const mode = ref<'append' | 'replace'>('append');
@@ -58,9 +61,9 @@ async function readFile(file: File): Promise<void> {
58
61
  <template>
59
62
  <Teleport to="body">
60
63
  <div v-if="show" class="md-import-overlay" @click.self="emit('close')">
61
- <div class="md-import-dialog">
64
+ <div ref="dialogRef" class="md-import-dialog" role="dialog" aria-modal="true" aria-labelledby="md-import-title">
62
65
  <div class="md-import-header">
63
- <h2><i class="fa-brands fa-markdown"></i> Import Markdown</h2>
66
+ <h2 id="md-import-title"><i class="fa-brands fa-markdown"></i> Import Markdown</h2>
64
67
  <button class="md-import-close" @click="emit('close')"><i class="fa-solid fa-xmark"></i></button>
65
68
  </div>
66
69
 
@@ -21,7 +21,7 @@ const features = useFeatures();
21
21
 
22
22
  function isFeatureEnabled(featureGate?: string): boolean {
23
23
  if (!featureGate) return true;
24
- return (features.features as unknown as Record<string, boolean>)?.[featureGate] ?? true;
24
+ return (features.features.value as unknown as Record<string, boolean>)?.[featureGate] ?? true;
25
25
  }
26
26
 
27
27
  /** Section types that render in the full-width zone (above the 2-column layout) */
package/error.vue CHANGED
@@ -125,7 +125,7 @@ function handleBack(): void {
125
125
 
126
126
  .cpub-error-btn-primary {
127
127
  background: var(--accent, #5b9cf6);
128
- color: #fff;
128
+ color: var(--color-text-inverse, #fff);
129
129
  box-shadow: var(--shadow-md);
130
130
  }
131
131
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonpub/layer",
3
- "version": "0.21.4",
3
+ "version": "0.21.6",
4
4
  "type": "module",
5
5
  "main": "./nuxt.config.ts",
6
6
  "files": [
@@ -50,16 +50,16 @@
50
50
  "vue": "^3.4.0",
51
51
  "vue-router": "^4.3.0",
52
52
  "zod": "^4.3.6",
53
- "@commonpub/editor": "0.7.9",
54
- "@commonpub/protocol": "0.9.10",
55
- "@commonpub/schema": "0.16.0",
56
53
  "@commonpub/docs": "0.6.3",
54
+ "@commonpub/config": "0.12.0",
55
+ "@commonpub/learning": "0.5.2",
57
56
  "@commonpub/auth": "0.6.0",
58
57
  "@commonpub/explainer": "0.7.12",
59
- "@commonpub/learning": "0.5.2",
60
- "@commonpub/ui": "0.8.5",
61
- "@commonpub/config": "0.12.0",
62
- "@commonpub/server": "2.53.0"
58
+ "@commonpub/schema": "0.16.0",
59
+ "@commonpub/editor": "0.7.9",
60
+ "@commonpub/protocol": "0.9.10",
61
+ "@commonpub/server": "2.53.1",
62
+ "@commonpub/ui": "0.8.5"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@testing-library/jest-dom": "^6.9.1",
@@ -68,5 +68,7 @@
68
68
  "jsdom": "^25.0.1",
69
69
  "vitest": "^3.2.4"
70
70
  },
71
- "scripts": {}
71
+ "scripts": {
72
+ "test": "vitest run"
73
+ }
72
74
  }
package/pages/search.vue CHANGED
@@ -512,7 +512,7 @@ const { data: relatedCommunities } = await useFetch('/api/hubs', {
512
512
  font-size: 15px;
513
513
  font-weight: 500;
514
514
  color: var(--text);
515
- font-family: system-ui, -apple-system, sans-serif;
515
+ font-family: var(--font-sans);
516
516
  outline: none;
517
517
  transition: border-color 0.15s, box-shadow 0.15s;
518
518
  }
@@ -679,7 +679,7 @@ async function handleReport(): Promise<void> {
679
679
  background: none;
680
680
  cursor: pointer;
681
681
  border-bottom: 3px solid transparent;
682
- font-family: system-ui, -apple-system, sans-serif;
682
+ font-family: var(--font-sans);
683
683
  display: flex;
684
684
  align-items: center;
685
685
  gap: 6px;
@@ -47,6 +47,23 @@ export default defineEventHandler(async (event) => {
47
47
  }
48
48
  userConnections.set(userId, current + 1);
49
49
 
50
+ // Decrement exactly once on disconnect. Registered at handler scope (not
51
+ // inside ReadableStream.start) because start() is invoked lazily when the
52
+ // runtime begins pulling the stream — if the client aborts before that,
53
+ // start() never runs and an in-start cleanup would never fire, leaking
54
+ // the slot permanently and eventually 429-locking the user for the
55
+ // process lifetime. cleanup() also calls release(); the guard makes it
56
+ // idempotent so there is no double-decrement.
57
+ let released = false;
58
+ const release = (): void => {
59
+ if (released) return;
60
+ released = true;
61
+ const next = (userConnections.get(userId) ?? 1) - 1;
62
+ if (next <= 0) userConnections.delete(userId);
63
+ else userConnections.set(userId, next);
64
+ };
65
+ event.node.req.on('close', release);
66
+
50
67
  const encoder = new TextEncoder();
51
68
  const stream = new ReadableStream({
52
69
  async start(controller) {
@@ -65,9 +82,7 @@ export default defineEventHandler(async (event) => {
65
82
  unsubscribe = null;
66
83
  }
67
84
  try { controller.close(); } catch { /* already closed */ }
68
- const next = (userConnections.get(userId) ?? 1) - 1;
69
- if (next <= 0) userConnections.delete(userId);
70
- else userConnections.set(userId, next);
85
+ release();
71
86
  }
72
87
 
73
88
  async function sendCounts(): Promise<void> {