@veluai/velu 0.2.46 → 0.2.48

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veluai/velu",
3
- "version": "0.2.46",
3
+ "version": "0.2.48",
4
4
  "type": "module",
5
5
  "bin": "./dist/cli.js",
6
6
  "publishConfig": {
@@ -3,6 +3,7 @@ import React, {
3
3
  isValidElement,
4
4
  useCallback,
5
5
  useEffect,
6
+ useId,
6
7
  useRef,
7
8
  useState,
8
9
  } from 'react';
@@ -152,9 +153,165 @@ function CopyButton({ code }) {
152
153
  );
153
154
  }
154
155
 
155
- function CodeBody({ code, language, lineNumbers, highlight }) {
156
+ const REPORT_REASONS = [
157
+ 'This code is incorrect',
158
+ 'This example is outdated',
159
+ "This example doesn't run",
160
+ 'Something else',
161
+ ];
162
+
163
+ function reportBody({ code, language, label, reason, detail }) {
164
+ const pageUrl =
165
+ typeof window !== 'undefined' ? window.location.href.split('#')[0] : '';
166
+ return [
167
+ reason,
168
+ detail,
169
+ label ? `Example: ${label}` : '',
170
+ language && language !== 'text' ? `Language: ${language}` : '',
171
+ pageUrl,
172
+ code ? `\`\`\`${language && language !== 'text' ? language : ''}\n${code}\n\`\`\`` : '',
173
+ ]
174
+ .filter(Boolean)
175
+ .join('\n\n');
176
+ }
177
+
178
+ function ReportIssue({ open, onOpen, onClose }) {
179
+ return (
180
+ <button
181
+ type="button"
182
+ className="velu-code-block__report-btn"
183
+ data-component="code-block-report-button"
184
+ aria-label={open ? 'Close report' : 'Report issue'}
185
+ title="Report issue"
186
+ aria-expanded={open}
187
+ onClick={() => (open ? onClose() : onOpen())}
188
+ >
189
+ {resolveIcon(open ? 'x' : 'flag', { size: '1em' })}
190
+ </button>
191
+ );
192
+ }
193
+
194
+ function ReportForm({ code, language, label, onClose }) {
195
+ const reasonName = useId();
196
+ const [reason, setReason] = useState('');
197
+ const [detail, setDetail] = useState('');
198
+ const [sent, setSent] = useState(false);
199
+ const canSubmit = Boolean(reason || detail.trim());
200
+
201
+ useEffect(() => {
202
+ const onKey = (e) => {
203
+ if (e.key === 'Escape') onClose();
204
+ };
205
+ document.addEventListener('keydown', onKey);
206
+ return () => document.removeEventListener('keydown', onKey);
207
+ }, [onClose]);
208
+
209
+ useEffect(() => {
210
+ if (!sent) return;
211
+ const t = setTimeout(onClose, 1600);
212
+ return () => clearTimeout(t);
213
+ }, [sent, onClose]);
214
+
215
+ function submit(e) {
216
+ e.preventDefault();
217
+ if (!canSubmit || sent) return;
218
+ const snippet = normalize(code);
219
+ const payload = {
220
+ code: snippet,
221
+ language,
222
+ label,
223
+ reason,
224
+ detail: detail.trim(),
225
+ };
226
+ payload.body = reportBody(payload);
227
+ if (typeof window !== 'undefined') {
228
+ window.dispatchEvent(new CustomEvent('velu:report-code', { detail: payload }));
229
+ }
230
+ setSent(true);
231
+ }
232
+
233
+ return (
234
+ <form className="velu-code-block__report" onSubmit={submit}>
235
+ {sent ? (
236
+ <div className="velu-code-block__report-thanks" role="status">
237
+ {resolveIcon('check', { size: '1.1em' })}
238
+ <span>Thanks — we&apos;ll take a look.</span>
239
+ </div>
240
+ ) : (
241
+ <>
242
+ <p className="velu-code-block__report-title">
243
+ Report an issue with this example
244
+ </p>
245
+ <fieldset className="velu-code-block__report-reasons">
246
+ <legend className="velu-code-block__report-legend">
247
+ What&apos;s wrong?
248
+ </legend>
249
+ {REPORT_REASONS.map((r) => (
250
+ <label key={r} className="velu-code-block__report-reason">
251
+ <input
252
+ type="radio"
253
+ name={reasonName}
254
+ value={r}
255
+ checked={reason === r}
256
+ onChange={() => setReason(r)}
257
+ />
258
+ <span>{r}</span>
259
+ </label>
260
+ ))}
261
+ </fieldset>
262
+ <textarea
263
+ className="velu-code-block__report-detail"
264
+ placeholder="What should we fix?"
265
+ rows={3}
266
+ value={detail}
267
+ onChange={(e) => setDetail(e.target.value)}
268
+ />
269
+ <div className="velu-code-block__report-actions">
270
+ <button
271
+ type="button"
272
+ className="velu-code-block__report-cancel"
273
+ onClick={onClose}
274
+ >
275
+ Cancel
276
+ </button>
277
+ <button
278
+ type="submit"
279
+ className="velu-code-block__report-submit"
280
+ disabled={!canSubmit}
281
+ >
282
+ Submit
283
+ </button>
284
+ </div>
285
+ </>
286
+ )}
287
+ </form>
288
+ );
289
+ }
290
+
291
+ function useCodeReportsEnabled() {
292
+ const [enabled, setEnabled] = useState(false);
293
+ useEffect(() => {
294
+ // `velu dev` is a local preview — reports only exist on the published site.
295
+ if (import.meta.env.DEV) return undefined;
296
+ const sync = () => {
297
+ setEnabled(
298
+ typeof document !== 'undefined' &&
299
+ document.documentElement.dataset.veluCodeReports === 'on',
300
+ );
301
+ };
302
+ sync();
303
+ window.addEventListener('velu:code-reports-enabled', sync);
304
+ return () => window.removeEventListener('velu:code-reports-enabled', sync);
305
+ }, []);
306
+ return enabled;
307
+ }
308
+
309
+ function CodeBody({ code, language, lineNumbers, highlight, label }) {
310
+ const reportsEnabled = useCodeReportsEnabled();
156
311
  const canonical = resolveLang(language) || 'text';
157
312
  const highlightSet = parseHighlight(highlight);
313
+ const [reportOpen, setReportOpen] = useState(false);
314
+ const closeReport = useCallback(() => setReportOpen(false), []);
158
315
 
159
316
  // Initial render uses 'text' (no highlighting) — this guarantees SSR
160
317
  // and the first client render produce identical HTML regardless of
@@ -175,45 +332,67 @@ function CodeBody({ code, language, lineNumbers, highlight }) {
175
332
  }, [canonical]);
176
333
 
177
334
  return (
178
- <div className="velu-code-block__body">
179
- <CopyButton code={normalize(code)} />
180
- <Highlight
181
- code={normalize(code)}
182
- language={renderLang}
183
- theme={NO_THEME}
184
- >
185
- {({ tokens, getLineProps, getTokenProps }) => (
186
- <pre className="velu-code-block__pre">
187
- {tokens.map((line, i) => {
188
- const lineProps = getLineProps({ line });
189
- const isHi = highlightSet?.has(i + 1);
190
- return (
191
- <div
192
- key={i}
193
- {...lineProps}
194
- className={`velu-code-block__line ${
195
- lineProps.className || ''
196
- } ${isHi ? 'velu-code-block__line--highlight' : ''}`.trim()}
197
- >
198
- {lineNumbers && (
199
- <span
200
- className="velu-code-block__line-number"
201
- aria-hidden="true"
202
- >
203
- {i + 1}
204
- </span>
205
- )}
206
- <span className="velu-code-block__line-code">
207
- {line.map((token, j) => (
208
- <span key={j} {...getTokenProps({ token })} />
209
- ))}
210
- </span>
211
- </div>
212
- );
213
- })}
214
- </pre>
335
+ <div
336
+ className={`velu-code-block__body${
337
+ reportOpen ? ' velu-code-block__body--report' : ''
338
+ }`}
339
+ >
340
+ <div className="velu-code-block__actions">
341
+ {reportsEnabled && (
342
+ <ReportIssue
343
+ open={reportOpen}
344
+ onOpen={() => setReportOpen(true)}
345
+ onClose={closeReport}
346
+ />
215
347
  )}
216
- </Highlight>
348
+ {(!reportOpen || !reportsEnabled) && <CopyButton code={normalize(code)} />}
349
+ </div>
350
+ {reportsEnabled && reportOpen ? (
351
+ <ReportForm
352
+ code={code}
353
+ language={language}
354
+ label={label}
355
+ onClose={closeReport}
356
+ />
357
+ ) : (
358
+ <Highlight
359
+ code={normalize(code)}
360
+ language={renderLang}
361
+ theme={NO_THEME}
362
+ >
363
+ {({ tokens, getLineProps, getTokenProps }) => (
364
+ <pre className="velu-code-block__pre">
365
+ {tokens.map((line, i) => {
366
+ const lineProps = getLineProps({ line });
367
+ const isHi = highlightSet?.has(i + 1);
368
+ return (
369
+ <div
370
+ key={i}
371
+ {...lineProps}
372
+ className={`velu-code-block__line ${
373
+ lineProps.className || ''
374
+ } ${isHi ? 'velu-code-block__line--highlight' : ''}`.trim()}
375
+ >
376
+ {lineNumbers && (
377
+ <span
378
+ className="velu-code-block__line-number"
379
+ aria-hidden="true"
380
+ >
381
+ {i + 1}
382
+ </span>
383
+ )}
384
+ <span className="velu-code-block__line-code">
385
+ {line.map((token, j) => (
386
+ <span key={j} {...getTokenProps({ token })} />
387
+ ))}
388
+ </span>
389
+ </div>
390
+ );
391
+ })}
392
+ </pre>
393
+ )}
394
+ </Highlight>
395
+ )}
217
396
  </div>
218
397
  );
219
398
  }
@@ -254,6 +433,7 @@ export function CodeBlock({
254
433
  language={language}
255
434
  lineNumbers={lineNumbers}
256
435
  highlight={highlight}
436
+ label={label}
257
437
  />
258
438
  </code-block>
259
439
  );
@@ -333,6 +513,8 @@ export function CodeGroup({
333
513
  lineNumbers = false,
334
514
  highlight,
335
515
  children: code,
516
+ title,
517
+ filename,
336
518
  } = block;
337
519
  const cls = `velu-code-block velu-code-block--group ${className}`.trim();
338
520
 
@@ -391,6 +573,7 @@ export function CodeGroup({
391
573
  language={language}
392
574
  lineNumbers={lineNumbers}
393
575
  highlight={highlight}
576
+ label={title || filename}
394
577
  />
395
578
  </code-group>
396
579
  );
@@ -107,21 +107,31 @@
107
107
  }
108
108
 
109
109
  /* Body: the code area, monospace, horizontally scrollable for long lines.
110
- Positioned so the copy button can absolutely anchor to its top-left. */
110
+ Positioned so the action cluster can absolutely anchor to its top-right. */
111
111
  .velu-code-block__body {
112
112
  position: relative;
113
113
  background: var(--page-bg);
114
114
  padding: var(--s0);
115
+ padding-inline-end: calc(var(--s0) + 4.25em);
115
116
  overflow-x: auto;
116
117
  }
118
+ .velu-code-block__body--report {
119
+ overflow: visible;
120
+ padding-inline-end: var(--s0);
121
+ }
117
122
 
118
- /* Copy button — always visible. Frosted background so it remains
119
- legible over whatever code sits beneath it. */
120
- .velu-code-block__copy {
123
+ /* Copy + report — frosted cluster so they stay legible over the code. */
124
+ .velu-code-block__actions {
121
125
  position: absolute;
122
126
  inset-block-start: var(--s-2);
123
127
  inset-inline-end: var(--s-2);
124
- z-index: 1;
128
+ z-index: 2;
129
+ display: flex;
130
+ align-items: center;
131
+ gap: var(--s-3);
132
+ }
133
+ .velu-code-block__copy,
134
+ .velu-code-block__report-btn {
125
135
  display: inline-flex;
126
136
  align-items: center;
127
137
  justify-content: center;
@@ -136,8 +146,117 @@
136
146
  color: var(--muted-color);
137
147
  cursor: pointer;
138
148
  }
139
- .velu-code-block__copy:hover {
149
+ .velu-code-block__copy:hover,
150
+ .velu-code-block__report-btn:hover {
151
+ color: var(--text-color);
152
+ }
153
+
154
+ /* Report-issue panel replaces the snippet so a wrong example can be flagged
155
+ without leaving the block. */
156
+ .velu-code-block__report {
157
+ display: flex;
158
+ flex-direction: column;
159
+ gap: var(--s-2);
160
+ min-block-size: 8rem;
161
+ padding-inline-end: 2em;
162
+ margin: 0;
163
+ border: 0;
164
+ }
165
+ .velu-code-block__report-title {
166
+ margin: 0;
167
+ font-size: var(--f-h5);
168
+ font-weight: var(--weight-medium);
169
+ line-height: var(--lh-h5);
170
+ color: var(--text-color);
171
+ }
172
+ .velu-code-block__report-reasons {
173
+ display: flex;
174
+ flex-direction: column;
175
+ gap: var(--s-3);
176
+ margin: 0;
177
+ padding: 0;
178
+ border: 0;
179
+ }
180
+ .velu-code-block__report-legend {
181
+ padding: 0;
182
+ margin-block-end: var(--s-3);
183
+ font-size: var(--f-h6);
184
+ color: var(--muted-color);
185
+ }
186
+ .velu-code-block__report-reason {
187
+ display: flex;
188
+ align-items: center;
189
+ gap: var(--s-2);
190
+ font-size: var(--f-h6);
191
+ line-height: var(--lh-h6);
192
+ color: var(--text-color);
193
+ cursor: pointer;
194
+ }
195
+ .velu-code-block__report-reason input {
196
+ margin: 0;
197
+ accent-color: var(--accent-color);
198
+ }
199
+ .velu-code-block__report-detail {
200
+ display: block;
201
+ width: 100%;
202
+ resize: vertical;
203
+ min-block-size: 4.5em;
204
+ padding: var(--s-2) var(--s-1);
205
+ background: var(--surface-color);
206
+ border: var(--border-width) solid var(--border-color);
207
+ border-radius: var(--radius-sm);
208
+ color: var(--text-color);
209
+ font: inherit;
210
+ font-size: var(--f-h6);
211
+ line-height: var(--lh-h6);
212
+ }
213
+ .velu-code-block__report-detail:focus {
214
+ outline: 2px solid var(--accent-color);
215
+ outline-offset: -1px;
216
+ }
217
+ .velu-code-block__report-detail::placeholder {
218
+ color: var(--muted-color);
219
+ }
220
+ .velu-code-block__report-actions {
221
+ display: flex;
222
+ justify-content: flex-end;
223
+ gap: var(--s-2);
224
+ }
225
+ .velu-code-block__report-cancel,
226
+ .velu-code-block__report-submit {
227
+ padding: var(--s-3) var(--s-1);
228
+ border-radius: var(--radius-sm);
229
+ font: inherit;
230
+ font-size: var(--f-h6);
231
+ font-weight: var(--weight-medium);
232
+ line-height: var(--lh-h6);
233
+ cursor: pointer;
234
+ }
235
+ .velu-code-block__report-cancel {
236
+ background: transparent;
237
+ border: var(--border-width) solid var(--border-color);
238
+ color: var(--text-color);
239
+ }
240
+ .velu-code-block__report-submit {
241
+ background: var(--accent-color);
242
+ border: var(--border-width) solid var(--accent-color);
243
+ color: var(--page-bg);
244
+ }
245
+ .velu-code-block__report-submit:disabled {
246
+ opacity: 0.45;
247
+ cursor: not-allowed;
248
+ }
249
+ .velu-code-block__report-thanks {
250
+ display: flex;
251
+ align-items: center;
252
+ gap: var(--s-2);
253
+ min-block-size: 6rem;
140
254
  color: var(--text-color);
255
+ font-size: var(--f-h5);
256
+ font-weight: var(--weight-medium);
257
+ }
258
+ .velu-code-block__report-thanks svg {
259
+ color: var(--accent-color);
141
260
  }
142
261
 
143
262
  .velu-code-block__pre {
@@ -49,7 +49,9 @@ export function createPageFeedback({ apiBase, host } = {}) {
49
49
  // sensible default when the reader didn't pick a reason (e.g. on "Yes").
50
50
  reason_text:
51
51
  (reason && String(reason).trim()) || (helpful ? 'Helpful' : 'Not helpful'),
52
- details: detail ? String(detail) : null,
52
+ details: detail
53
+ ? String(detail).slice(0, 16000)
54
+ : null,
53
55
  };
54
56
  try {
55
57
  await fetch(url, {
@@ -66,7 +68,25 @@ export function createPageFeedback({ apiBase, host } = {}) {
66
68
  }
67
69
  }
68
70
 
69
- return { submit };
71
+ /**
72
+ * Paid-plan flag for the code-block Report issue control. Fail closed.
73
+ */
74
+ async function config() {
75
+ const cfgUrl = `${base}/api/v1/public/feedback/config`;
76
+ try {
77
+ const res = await fetch(cfgUrl, {
78
+ credentials: 'include',
79
+ headers: { 'X-Velu-Site-Host': siteHost(host) },
80
+ });
81
+ if (!res.ok) return { codeReportsEnabled: false };
82
+ const data = await res.json();
83
+ return { codeReportsEnabled: !!data.code_reports_enabled };
84
+ } catch {
85
+ return { codeReportsEnabled: false };
86
+ }
87
+ }
88
+
89
+ return { submit, config };
70
90
  }
71
91
 
72
92
  export default createPageFeedback;
@@ -304,7 +304,9 @@
304
304
  :is([data-site-theme='thulir'],[data-site-theme='aalam'],[data-site-theme='nila'],[data-site-theme='kadhir']) .velu-code-block { border-radius: 16px; border-color: rgb(from var(--vepa-gray-950) r g b / 10%); }
305
305
  :is([data-site-theme='thulir'],[data-site-theme='aalam'],[data-site-theme='nila'],[data-site-theme='kadhir']) .velu-code-block :is(pre,code) { font: 400 14px/24px var(--font-mono); }
306
306
  :is([data-site-theme='thulir'],[data-site-theme='aalam'],[data-site-theme='nila'],[data-site-theme='kadhir']) .velu-code-block__body { padding: 16px; border-radius: 16px; }
307
- :is([data-site-theme='thulir'],[data-site-theme='aalam'],[data-site-theme='nila'],[data-site-theme='kadhir']) .velu-code-block__copy { border: 0; background: transparent; width: 24px; height: 24px; top: 12px; right: 12px; }
307
+ :is([data-site-theme='thulir'],[data-site-theme='aalam'],[data-site-theme='nila'],[data-site-theme='kadhir']) .velu-code-block__actions { top: 12px; right: 12px; }
308
+ :is([data-site-theme='thulir'],[data-site-theme='aalam'],[data-site-theme='nila'],[data-site-theme='kadhir']) .velu-code-block__copy,
309
+ :is([data-site-theme='thulir'],[data-site-theme='aalam'],[data-site-theme='nila'],[data-site-theme='kadhir']) .velu-code-block__report-btn { border: 0; background: transparent; width: 24px; height: 24px; }
308
310
  :is([data-site-theme='thulir'],[data-site-theme='aalam'],[data-site-theme='nila'],[data-site-theme='kadhir']) .velu-code-block__header { height: 36px; padding: 0 12px; align-items: stretch; }
309
311
  :is([data-site-theme='thulir'],[data-site-theme='aalam'],[data-site-theme='nila'],[data-site-theme='kadhir']) .velu-code-block__tabs { gap: 16px; }
310
312
  :is([data-site-theme='thulir'],[data-site-theme='aalam'],[data-site-theme='nila'],[data-site-theme='kadhir']) .velu-code-block__tab { padding: 0 6px; border-radius: 0; font: 400 12px/20px var(--font-sans); height: 36px; }
@@ -320,6 +320,7 @@
320
320
  "group": { "type": "string", "description": "Sidebar section heading." },
321
321
  "icon": { "type": "string", "description": "lucide icon id." },
322
322
  "expanded": { "type": "boolean", "description": "Start expanded." },
323
+ "public": { "type": "boolean", "description": "Allow unauthenticated access to this group. Per-page group restrictions still apply." },
323
324
  "root": { "type": "string", "description": "Landing page path for the group." },
324
325
  "openapi": { "$ref": "#/$defs/openapiRef" },
325
326
  "api": { "$ref": "#/$defs/api" },
package/src/navigation.js CHANGED
@@ -179,6 +179,7 @@ function normalizeGroup(g) {
179
179
  if (g.root) pages.unshift({ kind: 'page', pagePath: g.root });
180
180
  return {
181
181
  kind: 'group',
182
+ ...(g.public !== undefined ? { public: g.public } : {}),
182
183
  label: g.group,
183
184
  icon: g.icon,
184
185
  expanded: Boolean(g.expanded),
@@ -89,6 +89,8 @@ function docsAuthActionUrl(action) {
89
89
  // Resolve it to a relative base. Do not read `window` here: prerender runs
90
90
  // this useMemo during renderToString in Node.
91
91
  function resolvePublicApiBase(configuredBase) {
92
+ // Private readers must reach the router that verifies and forwards their session.
93
+ if (site.readerPrivate) return BASE_PATH;
92
94
  if (!configuredBase) return null;
93
95
  if (configuredBase !== '__VELU_SAME_ORIGIN__') return configuredBase;
94
96
  return BASE_PATH;
@@ -801,7 +803,7 @@ function DocsPage() {
801
803
  // URL path — e.g. essentials/markdown.mdx lives under the "Writing Content"
802
804
  // group). Page rows show the path to the page; anchor rows include the page.
803
805
  const searchDocs = React.useCallback(async (q) => {
804
- const results = await pagefindSearch(q);
806
+ const results = site.readerRestricted ? await fetch((import.meta.env.BASE_URL || '/') + 'api/v1/public/search?q=' + encodeURIComponent(q), {credentials:'same-origin',cache:'no-store'}).then(r=>r.ok?r.json():[]) : await pagefindSearch(q);
805
807
  if (!Array.isArray(results)) return results;
806
808
  return results.map((r) => {
807
809
  const res = resolve(r.href.split('#')[0], navigation, pages);
@@ -847,6 +849,31 @@ function DocsPage() {
847
849
  return createPageFeedback({ apiBase, host: site.assistant?.host });
848
850
  }, []);
849
851
 
852
+ React.useEffect(() => {
853
+ if (IS_DEV_PREVIEW) return undefined;
854
+ const onReport = (e) => {
855
+ const d = e.detail || {};
856
+ pageFeedback?.submit({
857
+ helpful: false,
858
+ reason: 'Incorrect code example',
859
+ detail: d.body || d.detail || '',
860
+ });
861
+ };
862
+ window.addEventListener('velu:report-code', onReport);
863
+ return () => window.removeEventListener('velu:report-code', onReport);
864
+ }, [pageFeedback]);
865
+
866
+ React.useEffect(() => {
867
+ if (IS_DEV_PREVIEW || !pageFeedback?.config) return undefined;
868
+ let alive = true;
869
+ pageFeedback.config().then((cfg) => {
870
+ if (!alive || !cfg.codeReportsEnabled) return;
871
+ document.documentElement.dataset.veluCodeReports = 'on';
872
+ window.dispatchEvent(new Event('velu:code-reports-enabled'));
873
+ }).catch(() => {});
874
+ return () => { alive = false; };
875
+ }, [pageFeedback]);
876
+
850
877
  const frontmatter = entry?.frontmatter ?? {};
851
878
  const PageComponent = entry?.Component ?? null;
852
879
  const pageToc = entry?.toc ?? [];
@@ -1196,7 +1223,11 @@ function DocsPage() {
1196
1223
  headers: { 'content-type': 'application/json' },
1197
1224
  body: '{}',
1198
1225
  });
1199
- if (response.ok) window.location.reload();
1226
+ if (response.ok) {
1227
+ const result = response.status === 204 ? null : await response.json();
1228
+ if (result?.redirect) window.location.assign(result.redirect);
1229
+ else window.location.reload();
1230
+ }
1200
1231
  }, []);
1201
1232
 
1202
1233
  const navActions = [];
@@ -1,9 +1,9 @@
1
1
  import './dev-warnings.js';
2
2
  import React from 'react';
3
- import { hydrateRoot } from 'react-dom/client';
3
+ import { createRoot, hydrateRoot } from 'react-dom/client';
4
4
  import { BrowserRouter } from 'react-router-dom';
5
5
  import 'velu-ui/styles.css';
6
- import { site } from 'virtual:velu-site';
6
+ import { site, initializeReader } from 'virtual:velu-site';
7
7
  import App from './App.jsx';
8
8
 
9
9
  // Subpath hosting: Vite sets BASE_URL from the build's `base` (e.g. "/docs/").
@@ -31,7 +31,9 @@ function CustomScripts() {
31
31
  return null;
32
32
  }
33
33
 
34
- hydrateRoot(
34
+ function mount() {
35
+ const renderRoot = site.guarded ? (_, children) => createRoot(document.getElementById('root')).render(children) : hydrateRoot;
36
+ renderRoot(
35
37
  document.getElementById('root'),
36
38
  <BrowserRouter basename={basename}>
37
39
  <App />
@@ -39,6 +41,10 @@ hydrateRoot(
39
41
  </BrowserRouter>
40
42
  );
41
43
 
44
+ }
45
+ if (site.guarded) initializeReader().then(mount).catch(()=>{ document.getElementById("root").textContent="Unable to load documentation. Reload to sign in again."; });
46
+ else mount();
47
+
42
48
  // Dev only: the SSR-inlined critical CSS (<style data-velu-ssr>) exists just
43
49
  // to avoid FOUC on first paint. By now Vite has injected its own HMR-managed
44
50
  // stylesheet from the `velu-ui/styles.css` import above, so drop the stale