@hachej/boring-deck 0.1.20

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.
@@ -0,0 +1,1159 @@
1
+ // src/front/index.tsx
2
+ import {
3
+ WorkspaceFilesProvider,
4
+ useHasWorkspaceFilesProvider
5
+ } from "@hachej/boring-workspace";
6
+ import { definePlugin } from "@hachej/boring-workspace/plugin";
7
+
8
+ // src/shared/constants.ts
9
+ var DECK_PLUGIN_ID = "deck";
10
+ var DECK_PANEL_ID = "deck";
11
+ var DECK_LABEL = "Deck";
12
+ var DECK_PATH_PREFIX = "deck/";
13
+
14
+ // src/shared/path.ts
15
+ function normalizeDeckPath(path) {
16
+ const trimmed = path.trim().replace(/\\/g, "/");
17
+ const noLeadingDot = trimmed.replace(/^\.\//, "");
18
+ return noLeadingDot.replace(/\/+/g, "/");
19
+ }
20
+ function isDeckMarkdownPath(path, pathPrefix = "deck/") {
21
+ const normalized = normalizeDeckPath(path);
22
+ const normalizedPathPrefix = normalizeDeckPath(pathPrefix);
23
+ const normalizedPrefix = normalizedPathPrefix.endsWith("/") ? normalizedPathPrefix : `${normalizedPathPrefix}/`;
24
+ if (!normalized.endsWith(".md")) return false;
25
+ if (normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized)) return false;
26
+ if (normalized.split("/").some((segment) => segment === "..")) return false;
27
+ return normalized.startsWith(normalizedPrefix);
28
+ }
29
+
30
+ // src/shared/parser.ts
31
+ var TITLE_RX = /^##\s+title:\s*(.+)$/i;
32
+ var YAML_TITLE_RX = /^title:\s*(.+)$/i;
33
+ var YAML_KV_RX = /^[A-Za-z][\w-]*:\s*.*$/;
34
+ var FENCE_RX = /^(```|~~~)/;
35
+ var WIDGET_OPEN = "{{";
36
+ var WIDGET_CLOSE = "}}";
37
+ var WIDGET_NAME_RX = /^[A-Za-z][\w-]*$/;
38
+ var ATTR_KEY_RX = /^[A-Za-z][\w-]*$/;
39
+ function splitSlides(input) {
40
+ const slides = [];
41
+ let current = [];
42
+ let fenceMarker = null;
43
+ for (const line of input.split("\n")) {
44
+ const trimmed = line.trim();
45
+ const fence = FENCE_RX.exec(trimmed);
46
+ if (fence) {
47
+ if (fenceMarker === fence[1]) fenceMarker = null;
48
+ else if (fenceMarker === null) fenceMarker = fence[1];
49
+ }
50
+ if (fenceMarker === null && trimmed === "---") {
51
+ slides.push(current.join("\n").trim());
52
+ current = [];
53
+ continue;
54
+ }
55
+ current.push(line);
56
+ }
57
+ slides.push(current.join("\n").trim());
58
+ const nonEmpty = slides.filter((slide) => slide.length > 0);
59
+ if (nonEmpty.length > 0) return nonEmpty;
60
+ return [""];
61
+ }
62
+ function parseWidgetAttrs(raw) {
63
+ const parsed = tryParseWidgetAttrs(raw);
64
+ if (parsed == null) throw new Error(`Malformed widget attrs: ${raw}`);
65
+ return parsed;
66
+ }
67
+ function parseDeckMarkdown(input) {
68
+ const lines = input.split("\n");
69
+ let start = 0;
70
+ while (start < lines.length && lines[start].trim() === "") start += 1;
71
+ let title = stripYamlFrontmatter(lines, start);
72
+ while (start < lines.length && lines[start].trim() === "") start += 1;
73
+ if (!title && lines[start]?.trim() === "---") {
74
+ lines.splice(start, 1);
75
+ }
76
+ let sawFence = false;
77
+ for (let i = 0; i < lines.length; i += 1) {
78
+ const trimmed = lines[i].trim();
79
+ if (FENCE_RX.test(trimmed)) sawFence = !sawFence;
80
+ if (!sawFence && trimmed === "---") break;
81
+ const match = TITLE_RX.exec(trimmed);
82
+ if (match) {
83
+ title = title ?? stripQuotes(match[1].trim());
84
+ lines.splice(i, 1);
85
+ if (lines[i]?.trim() === "") lines.splice(i, 1);
86
+ break;
87
+ }
88
+ }
89
+ const slides = splitSlides(lines.join("\n")).map((raw, index, all) => ({
90
+ index,
91
+ raw,
92
+ segments: tokenize(raw, index, all.length)
93
+ }));
94
+ return title ? { title, slides } : { slides };
95
+ }
96
+ function stripYamlFrontmatter(lines, start) {
97
+ if (lines[start]?.trim() !== "---") return void 0;
98
+ let end = -1;
99
+ for (let i = start + 1; i < lines.length; i += 1) {
100
+ if (lines[i].trim() === "---") {
101
+ end = i;
102
+ break;
103
+ }
104
+ }
105
+ if (end === -1) return void 0;
106
+ const fields = lines.slice(start + 1, end);
107
+ if (fields.length === 0 || !fields.every((line) => YAML_KV_RX.test(line.trim()))) return void 0;
108
+ const titleLine = fields.find((line) => YAML_TITLE_RX.test(line.trim()));
109
+ const title = titleLine?.trim().match(YAML_TITLE_RX)?.[1]?.trim();
110
+ lines.splice(start, end - start + 1);
111
+ while (start < lines.length && lines[start]?.trim() === "") lines.splice(start, 1);
112
+ return title ? stripQuotes(title) : void 0;
113
+ }
114
+ function tokenize(markdown, slideIndex, slideCount) {
115
+ const segments = [];
116
+ const lines = markdown.split("\n");
117
+ let fenceMarker = null;
118
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
119
+ const line = lines[lineIndex];
120
+ const trimmed = line.trim();
121
+ const fence = FENCE_RX.exec(trimmed);
122
+ if (fence) {
123
+ if (fenceMarker === fence[1]) fenceMarker = null;
124
+ else if (fenceMarker === null) fenceMarker = fence[1];
125
+ pushMarkdown(segments, line);
126
+ if (lineIndex < lines.length - 1) pushMarkdown(segments, "\n");
127
+ continue;
128
+ }
129
+ if (fenceMarker) {
130
+ pushMarkdown(segments, line);
131
+ if (lineIndex < lines.length - 1) pushMarkdown(segments, "\n");
132
+ continue;
133
+ }
134
+ for (const segment of tokenizeLine(line, slideIndex, slideCount)) {
135
+ if (segment.type === "markdown") pushMarkdown(segments, segment.text);
136
+ else segments.push(segment);
137
+ }
138
+ if (lineIndex < lines.length - 1) pushMarkdown(segments, "\n");
139
+ }
140
+ return segments.length > 0 ? segments : [{ type: "markdown", text: "" }];
141
+ }
142
+ function tokenizeLine(line, _slideIndex, _slideCount) {
143
+ const segments = [];
144
+ let markdown = "";
145
+ let i = 0;
146
+ while (i < line.length) {
147
+ if (line[i] === "`") {
148
+ const tickCount = countRepeated(line, i, "`");
149
+ const close = line.indexOf("`".repeat(tickCount), i + tickCount);
150
+ if (close === -1) {
151
+ markdown += line.slice(i);
152
+ break;
153
+ }
154
+ markdown += line.slice(i, close + tickCount);
155
+ i = close + tickCount;
156
+ continue;
157
+ }
158
+ if (line.startsWith(WIDGET_OPEN, i)) {
159
+ const close = line.indexOf(WIDGET_CLOSE, i + WIDGET_OPEN.length);
160
+ if (close === -1) {
161
+ markdown += line.slice(i);
162
+ break;
163
+ }
164
+ const raw = line.slice(i, close + WIDGET_CLOSE.length);
165
+ const parsed = parseWidget(raw, line);
166
+ if (!parsed) {
167
+ markdown += raw;
168
+ i = close + WIDGET_CLOSE.length;
169
+ continue;
170
+ }
171
+ if (markdown) {
172
+ segments.push({ type: "markdown", text: markdown });
173
+ markdown = "";
174
+ }
175
+ segments.push(parsed);
176
+ i = close + WIDGET_CLOSE.length;
177
+ continue;
178
+ }
179
+ markdown += line[i];
180
+ i += 1;
181
+ }
182
+ if (markdown) segments.push({ type: "markdown", text: markdown });
183
+ return segments;
184
+ }
185
+ function parseWidget(raw, line) {
186
+ const inner = raw.slice(WIDGET_OPEN.length, -WIDGET_CLOSE.length).trim();
187
+ if (!inner) return null;
188
+ const firstSpace = inner.search(/\s/);
189
+ const name = firstSpace === -1 ? inner : inner.slice(0, firstSpace);
190
+ const attrsRaw = firstSpace === -1 ? "" : inner.slice(firstSpace).trim();
191
+ if (!WIDGET_NAME_RX.test(name)) return null;
192
+ let attrs;
193
+ try {
194
+ attrs = attrsRaw ? parseWidgetAttrs(attrsRaw) : {};
195
+ } catch {
196
+ return null;
197
+ }
198
+ return {
199
+ type: "widget",
200
+ name,
201
+ attrs,
202
+ raw,
203
+ position: line.trim() === raw.trim() ? "block" : "inline"
204
+ };
205
+ }
206
+ function tryParseWidgetAttrs(raw) {
207
+ const attrs = {};
208
+ let i = 0;
209
+ while (i < raw.length) {
210
+ while (i < raw.length && /\s/.test(raw[i])) i += 1;
211
+ if (i >= raw.length) break;
212
+ const keyStart = i;
213
+ while (i < raw.length && /[A-Za-z0-9_-]/.test(raw[i])) i += 1;
214
+ const key = raw.slice(keyStart, i);
215
+ if (!ATTR_KEY_RX.test(key)) return null;
216
+ while (i < raw.length && /\s/.test(raw[i])) i += 1;
217
+ if (raw[i] !== "=") return null;
218
+ i += 1;
219
+ while (i < raw.length && /\s/.test(raw[i])) i += 1;
220
+ if (raw[i] !== '"') return null;
221
+ i += 1;
222
+ let value = "";
223
+ let closed = false;
224
+ while (i < raw.length) {
225
+ const char = raw[i];
226
+ if (char === "\\") {
227
+ const next = raw[i + 1];
228
+ if (next === void 0) return null;
229
+ value += next;
230
+ i += 2;
231
+ continue;
232
+ }
233
+ if (char === '"') {
234
+ i += 1;
235
+ closed = true;
236
+ break;
237
+ }
238
+ value += char;
239
+ i += 1;
240
+ }
241
+ if (!closed) return null;
242
+ attrs[key] = value;
243
+ }
244
+ while (i < raw.length && /\s/.test(raw[i])) i += 1;
245
+ if (i !== raw.length) return null;
246
+ return attrs;
247
+ }
248
+ function pushMarkdown(segments, text) {
249
+ if (text.length === 0) return;
250
+ const last = segments[segments.length - 1];
251
+ if (last?.type === "markdown") last.text += text;
252
+ else segments.push({ type: "markdown", text });
253
+ }
254
+ function stripQuotes(value) {
255
+ return value.replace(/^['"]|['"]$/g, "");
256
+ }
257
+ function countRepeated(value, start, needle) {
258
+ let count = 0;
259
+ while (value[start + count] === needle) count += 1;
260
+ return count;
261
+ }
262
+
263
+ // src/front/DeckPane.tsx
264
+ import {
265
+ cn as cn2,
266
+ MarkdownEditor,
267
+ useFilePane,
268
+ useFullPagePanelHref,
269
+ useIsFullPagePanel
270
+ } from "@hachej/boring-workspace";
271
+ import { Button as Button2 } from "@hachej/boring-ui-kit";
272
+ import { ExternalLink } from "lucide-react";
273
+ import { Fragment, useEffect, useMemo, useState } from "react";
274
+ import ReactMarkdown from "react-markdown";
275
+ import remarkGfm from "remark-gfm";
276
+
277
+ // src/front/components.tsx
278
+ import { cn } from "@hachej/boring-workspace";
279
+ import { Button, SegmentedControl, SegmentedControlItem, Separator } from "@hachej/boring-ui-kit";
280
+ import { ChevronLeft, ChevronRight, Maximize2, Minimize2 } from "lucide-react";
281
+ import { jsx, jsxs } from "react/jsx-runtime";
282
+ function DeckScaffoldState({ children }) {
283
+ return /* @__PURE__ */ jsx("div", { className: "p-4 text-sm text-muted-foreground", children });
284
+ }
285
+ function DeckErrorState({ title, description }) {
286
+ return /* @__PURE__ */ jsx(
287
+ "div",
288
+ {
289
+ className: "flex min-h-0 flex-1 items-center justify-center p-6",
290
+ "data-testid": "deck-error-state",
291
+ children: /* @__PURE__ */ jsxs("div", { className: "max-w-lg rounded-xl border border-destructive/20 bg-destructive/5 p-4 text-sm", children: [
292
+ /* @__PURE__ */ jsx("div", { className: "font-medium text-foreground", children: title }),
293
+ /* @__PURE__ */ jsx("div", { className: "mt-1 text-muted-foreground", children: description })
294
+ ] })
295
+ }
296
+ );
297
+ }
298
+ function DeckShell({ children, theme, presentMode = false }) {
299
+ return /* @__PURE__ */ jsxs(
300
+ "div",
301
+ {
302
+ className: cn(
303
+ "deck-root flex min-h-0 flex-1 flex-col bg-background text-foreground",
304
+ presentMode && "min-h-screen bg-background",
305
+ theme?.className
306
+ ),
307
+ "data-testid": presentMode ? "deck-shell-present" : "deck-shell-read",
308
+ children: [
309
+ children,
310
+ /* @__PURE__ */ jsx("style", { children: `
311
+ .deck-root { --deck-accent: oklch(0.62 0.14 65); }
312
+ .dark .deck-root { --deck-accent: oklch(0.76 0.16 68); }
313
+ .deck-slide-frame-shell {
314
+ animation: deck-slide-enter 240ms cubic-bezier(0.22, 1, 0.36, 1);
315
+ }
316
+ @keyframes deck-slide-enter {
317
+ from { opacity: 0; transform: translateY(6px); }
318
+ to { opacity: 1; transform: translateY(0); }
319
+ }
320
+ @media (prefers-reduced-motion: reduce) {
321
+ .deck-slide-frame-shell { animation: none; }
322
+ }
323
+ ` })
324
+ ]
325
+ }
326
+ );
327
+ }
328
+ function DeckSlideFrame({ children, theme }) {
329
+ const aspectRatio = theme?.aspectRatio === "4:3" ? "4 / 3" : "16 / 9";
330
+ return /* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1 items-center justify-center p-3 sm:p-6", children: /* @__PURE__ */ jsx(
331
+ "div",
332
+ {
333
+ className: cn(
334
+ "deck-slide-frame-shell flex w-full max-w-6xl overflow-hidden rounded-2xl border border-border/70 bg-card shadow-[0_1px_0_oklch(from_var(--foreground)_l_c_h/0.04),0_24px_60px_-30px_oklch(from_var(--foreground)_l_c_h/0.45)]",
335
+ theme?.slideClassName
336
+ ),
337
+ "data-testid": "deck-slide-frame",
338
+ style: { aspectRatio },
339
+ children: /* @__PURE__ */ jsx("div", { className: "min-h-0 min-w-0 flex-1 overflow-auto p-6 sm:p-10", children })
340
+ }
341
+ ) });
342
+ }
343
+ function DeckToolbar({
344
+ title,
345
+ path,
346
+ mode,
347
+ onModeChange,
348
+ presentMode,
349
+ slideIndex,
350
+ slideCount,
351
+ onTogglePresentMode,
352
+ actions
353
+ }) {
354
+ return /* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between gap-3 border-b border-border/60 bg-background/60 px-4 py-2 backdrop-blur-[2px]", children: [
355
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-baseline gap-3", children: [
356
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] uppercase tracking-[0.22em] text-[color:var(--deck-accent)]", children: "Deck" }),
357
+ /* @__PURE__ */ jsx("span", { className: "truncate text-[13px] font-medium tracking-tight text-foreground", children: title || "Deck" }),
358
+ path ? /* @__PURE__ */ jsx("span", { className: "hidden truncate font-mono text-[11px] text-muted-foreground/70 sm:inline", children: path }) : null
359
+ ] }),
360
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
361
+ onModeChange ? /* @__PURE__ */ jsx(SegmentedControl, { "aria-label": "Deck mode", className: "bg-background", children: ["read", "edit"].map((nextMode) => /* @__PURE__ */ jsx(
362
+ SegmentedControlItem,
363
+ {
364
+ type: "button",
365
+ selected: mode === nextMode,
366
+ onClick: () => onModeChange(nextMode),
367
+ "aria-pressed": mode === nextMode,
368
+ className: "px-2.5 py-1 text-[11px]",
369
+ "data-testid": nextMode === "read" ? "deck-mode-read" : "deck-mode-edit",
370
+ children: nextMode === "read" ? "Read" : "Edit"
371
+ },
372
+ nextMode
373
+ )) }) : null,
374
+ actions,
375
+ slideCount > 0 ? /* @__PURE__ */ jsxs("span", { className: "hidden font-mono text-[11px] tabular-nums tracking-tight text-muted-foreground sm:inline", children: [
376
+ "Slide ",
377
+ slideIndex + 1,
378
+ " of ",
379
+ slideCount
380
+ ] }) : null,
381
+ onTogglePresentMode ? /* @__PURE__ */ jsx(
382
+ Button,
383
+ {
384
+ type: "button",
385
+ variant: "ghost",
386
+ size: "icon-xs",
387
+ onClick: onTogglePresentMode,
388
+ "aria-label": presentMode ? "Exit present mode" : "Present",
389
+ title: presentMode ? "Exit present mode" : "Present",
390
+ "data-testid": "deck-toggle-present",
391
+ children: presentMode ? /* @__PURE__ */ jsx(Minimize2, { className: "size-3.5" }) : /* @__PURE__ */ jsx(Maximize2, { className: "size-3.5" })
392
+ }
393
+ ) : null
394
+ ] })
395
+ ] });
396
+ }
397
+ function DeckSlideRail({
398
+ slideIndex,
399
+ slideCount,
400
+ canGoPrevious,
401
+ canGoNext,
402
+ onPrevious,
403
+ onNext,
404
+ onSelect
405
+ }) {
406
+ if (slideCount <= 1) return null;
407
+ return /* @__PURE__ */ jsxs("footer", { className: "flex shrink-0 items-center gap-3 border-t border-border/60 bg-background/60 px-4 py-2", children: [
408
+ /* @__PURE__ */ jsxs(
409
+ Button,
410
+ {
411
+ type: "button",
412
+ variant: "ghost",
413
+ size: "xs",
414
+ onClick: onPrevious,
415
+ disabled: !canGoPrevious,
416
+ "aria-label": "Previous slide",
417
+ "data-testid": "deck-prev",
418
+ children: [
419
+ /* @__PURE__ */ jsx(ChevronLeft, { className: "size-3.5" }),
420
+ "Prev"
421
+ ]
422
+ }
423
+ ),
424
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-1 items-center justify-center gap-2.5", children: [
425
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] tabular-nums tracking-tight text-foreground", children: String(slideIndex + 1).padStart(2, "0") }),
426
+ /* @__PURE__ */ jsx(
427
+ "div",
428
+ {
429
+ role: "group",
430
+ "aria-label": "Slide navigation",
431
+ className: "flex max-w-[60%] flex-1 items-center gap-0.5 overflow-x-auto",
432
+ children: Array.from({ length: slideCount }, (_, index) => {
433
+ const isActive = index === slideIndex;
434
+ const label = `Slide ${index + 1}`;
435
+ return /* @__PURE__ */ jsxs(
436
+ Button,
437
+ {
438
+ type: "button",
439
+ variant: "ghost",
440
+ size: "icon-xs",
441
+ "aria-current": isActive ? "true" : void 0,
442
+ "aria-label": label,
443
+ onClick: () => onSelect?.(index),
444
+ className: "group flex flex-1 min-w-[28px] max-w-[56px] cursor-pointer items-center justify-center px-0.5 py-2.5",
445
+ children: [
446
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: label }),
447
+ /* @__PURE__ */ jsx(
448
+ "span",
449
+ {
450
+ "aria-hidden": true,
451
+ className: cn(
452
+ "block h-1 w-full rounded-full transition-colors",
453
+ isActive ? "bg-[color:var(--deck-accent)]" : "bg-border group-hover:bg-muted-foreground/40 group-focus-visible:bg-muted-foreground/40"
454
+ )
455
+ }
456
+ )
457
+ ]
458
+ },
459
+ index
460
+ );
461
+ })
462
+ }
463
+ ),
464
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] tabular-nums tracking-tight text-muted-foreground", children: String(slideCount).padStart(2, "0") })
465
+ ] }),
466
+ /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "!h-5" }),
467
+ /* @__PURE__ */ jsxs(
468
+ Button,
469
+ {
470
+ type: "button",
471
+ variant: "ghost",
472
+ size: "xs",
473
+ onClick: onNext,
474
+ disabled: !canGoNext,
475
+ "aria-label": "Next slide",
476
+ "data-testid": "deck-next",
477
+ children: [
478
+ "Next",
479
+ /* @__PURE__ */ jsx(ChevronRight, { className: "size-3.5" })
480
+ ]
481
+ }
482
+ )
483
+ ] });
484
+ }
485
+ function DeckNotice({
486
+ title,
487
+ description,
488
+ actions,
489
+ tone = "warning",
490
+ testId
491
+ }) {
492
+ return /* @__PURE__ */ jsxs(
493
+ "div",
494
+ {
495
+ className: cn(
496
+ "mx-3 mt-3 rounded-xl border p-3 text-sm",
497
+ tone === "error" ? "border-destructive/20 bg-destructive/5" : "border-amber-500/20 bg-amber-500/5"
498
+ ),
499
+ "data-testid": testId,
500
+ children: [
501
+ /* @__PURE__ */ jsx("div", { className: "font-medium text-foreground", children: title }),
502
+ /* @__PURE__ */ jsx("div", { className: "mt-1 text-muted-foreground", children: description }),
503
+ actions ? /* @__PURE__ */ jsx("div", { className: "mt-3 flex items-center gap-2", children: actions }) : null
504
+ ]
505
+ }
506
+ );
507
+ }
508
+
509
+ // src/front/widgets.tsx
510
+ import { Component } from "react";
511
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
512
+ function validateDeckWidgets(widgets) {
513
+ const seen = /* @__PURE__ */ new Set();
514
+ for (const widget of widgets) {
515
+ if (seen.has(widget.name)) {
516
+ throw new Error(`Duplicate deck widget name: ${widget.name}`);
517
+ }
518
+ seen.add(widget.name);
519
+ }
520
+ return widgets;
521
+ }
522
+ function indexDeckWidgets(widgets) {
523
+ const indexed = /* @__PURE__ */ new Map();
524
+ for (const widget of validateDeckWidgets(widgets)) indexed.set(widget.name, widget);
525
+ return {
526
+ get(name) {
527
+ return indexed.get(name);
528
+ },
529
+ entries() {
530
+ return indexed.entries();
531
+ }
532
+ };
533
+ }
534
+ function DeckWidgetSlot({ segment, widgets, context }) {
535
+ const widget = widgets.get(segment.name);
536
+ if (!widget) {
537
+ return /* @__PURE__ */ jsx2(DeckWidgetPlaceholder, { name: segment.name, position: segment.position, reason: "Unknown widget" });
538
+ }
539
+ const display = widget.display ?? segment.position;
540
+ try {
541
+ const attrs = widget.parse ? widget.parse(segment.attrs) : segment.attrs;
542
+ return /* @__PURE__ */ jsx2(DeckWidgetErrorBoundary, { name: segment.name, position: display, children: /* @__PURE__ */ jsx2(DeckWidgetFrame, { position: display, children: widget.render({ attrs, rawAttrs: segment.attrs, context }) }) });
543
+ } catch (error) {
544
+ return /* @__PURE__ */ jsx2(
545
+ DeckWidgetPlaceholder,
546
+ {
547
+ name: segment.name,
548
+ position: display,
549
+ reason: error instanceof Error ? error.message : "Widget failed"
550
+ }
551
+ );
552
+ }
553
+ }
554
+ function DeckWidgetFrame({ position, children }) {
555
+ if (position === "inline") {
556
+ return /* @__PURE__ */ jsx2("span", { "data-testid": "deck-widget-inline", children });
557
+ }
558
+ return /* @__PURE__ */ jsx2("div", { "data-testid": "deck-widget-block", children });
559
+ }
560
+ function DeckWidgetPlaceholder({
561
+ name,
562
+ position,
563
+ reason
564
+ }) {
565
+ const content = /* @__PURE__ */ jsxs2("span", { className: "text-xs text-muted-foreground", "data-testid": "deck-widget-placeholder", children: [
566
+ reason,
567
+ ": ",
568
+ name
569
+ ] });
570
+ return position === "inline" ? /* @__PURE__ */ jsx2("span", { children: content }) : /* @__PURE__ */ jsx2("div", { children: content });
571
+ }
572
+ var DeckWidgetErrorBoundary = class extends Component {
573
+ state = { error: null };
574
+ static getDerivedStateFromError(error) {
575
+ return { error };
576
+ }
577
+ componentDidCatch(_error, _info) {
578
+ }
579
+ componentDidUpdate(prevProps) {
580
+ if (this.state.error && prevProps.children !== this.props.children) {
581
+ this.setState({ error: null });
582
+ }
583
+ }
584
+ render() {
585
+ if (this.state.error) {
586
+ return /* @__PURE__ */ jsx2(
587
+ DeckWidgetPlaceholder,
588
+ {
589
+ name: this.props.name,
590
+ position: this.props.position,
591
+ reason: this.state.error.message || "Widget failed"
592
+ }
593
+ );
594
+ }
595
+ return this.props.children;
596
+ }
597
+ };
598
+
599
+ // src/front/DeckPane.tsx
600
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
601
+ var DEFAULT_SCAFFOLD_CONTENT = `# Deck scaffold
602
+
603
+ Deck rendering shell is ready.`;
604
+ function useDeckKeyboardNavigation({
605
+ enabled,
606
+ canGoPrevious,
607
+ canGoNext,
608
+ onPrevious,
609
+ onNext
610
+ }) {
611
+ useEffect(() => {
612
+ if (!enabled) return;
613
+ const onKeyDown = (event) => {
614
+ if (event.metaKey || event.ctrlKey || event.altKey) return;
615
+ const target = event.target;
616
+ if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable)) {
617
+ return;
618
+ }
619
+ if ((event.key === "ArrowRight" || event.key === "PageDown") && canGoNext) {
620
+ event.preventDefault();
621
+ onNext();
622
+ return;
623
+ }
624
+ if ((event.key === "ArrowLeft" || event.key === "PageUp") && canGoPrevious) {
625
+ event.preventDefault();
626
+ onPrevious();
627
+ }
628
+ };
629
+ window.addEventListener("keydown", onKeyDown);
630
+ return () => window.removeEventListener("keydown", onKeyDown);
631
+ }, [enabled, canGoNext, canGoPrevious, onNext, onPrevious]);
632
+ }
633
+ function DeckPane(props) {
634
+ if (props.content === void 0 && props.params?.path) {
635
+ return /* @__PURE__ */ jsx3(FileBackedDeckPane, { ...props });
636
+ }
637
+ return /* @__PURE__ */ jsx3(
638
+ DeckRenderedPane,
639
+ {
640
+ ...props,
641
+ content: props.content ?? DEFAULT_SCAFFOLD_CONTENT,
642
+ initialMode: props.initialMode === "edit" ? "read" : props.initialMode ?? "read"
643
+ }
644
+ );
645
+ }
646
+ function DeckRenderedPane({
647
+ params,
648
+ content,
649
+ pathPrefix = "deck/",
650
+ theme,
651
+ widgets = [],
652
+ onError,
653
+ initialMode = "read"
654
+ }) {
655
+ const isFullPagePanel = useIsFullPagePanel();
656
+ const resolvedInitialMode = isFullPagePanel ? "present" : initialMode;
657
+ const [mode, setMode] = useState(resolvedInitialMode);
658
+ const [slideIndex, setSlideIndex] = useState(0);
659
+ const indexedWidgets = useMemo(() => indexDeckWidgets(widgets), [widgets]);
660
+ const parsed = useMemo(() => parseDeckContent(content, params?.path), [content, params?.path]);
661
+ useEffect(() => {
662
+ if (!parsed.ok) onError?.(parsed.error);
663
+ }, [onError, parsed]);
664
+ useEffect(() => {
665
+ setMode(resolvedInitialMode);
666
+ }, [resolvedInitialMode]);
667
+ useEffect(() => {
668
+ setSlideIndex(0);
669
+ }, [content, params?.path]);
670
+ if (!content) {
671
+ return /* @__PURE__ */ jsx3(DeckShell, { theme, presentMode: mode === "present", children: /* @__PURE__ */ jsxs3(DeckScaffoldState, { children: [
672
+ "Deck shell for ",
673
+ pathPrefix,
674
+ params?.path ? ` (${params.path})` : ""
675
+ ] }) });
676
+ }
677
+ if (!parsed.ok) {
678
+ return /* @__PURE__ */ jsx3(DeckShell, { theme, presentMode: mode === "present", children: /* @__PURE__ */ jsx3(DeckErrorState, { title: "Failed to render deck", description: parsed.error.message }) });
679
+ }
680
+ const slides = parsed.deck.slides;
681
+ const safeIndex = Math.min(Math.max(slideIndex, 0), Math.max(slides.length - 1, 0));
682
+ const currentSlide = slides[safeIndex];
683
+ const title = parsed.deck.title ?? params?.path ?? "Deck";
684
+ const presentMode = mode === "present";
685
+ const hidePresentationControls = shouldHidePresentationControls({
686
+ isFullPagePanel,
687
+ params,
688
+ presentMode
689
+ });
690
+ useDeckKeyboardNavigation({
691
+ enabled: true,
692
+ canGoPrevious: safeIndex > 0,
693
+ canGoNext: safeIndex < slides.length - 1,
694
+ onPrevious: () => setSlideIndex((current) => Math.max(current - 1, 0)),
695
+ onNext: () => setSlideIndex((current) => Math.min(current + 1, slides.length - 1))
696
+ });
697
+ return /* @__PURE__ */ jsxs3(DeckShell, { theme, presentMode, children: [
698
+ !hidePresentationControls ? /* @__PURE__ */ jsx3(
699
+ DeckToolbar,
700
+ {
701
+ title,
702
+ path: params?.path,
703
+ presentMode,
704
+ slideIndex: safeIndex,
705
+ slideCount: slides.length,
706
+ onTogglePresentMode: () => setMode((current) => current === "present" ? "read" : "present")
707
+ }
708
+ ) : null,
709
+ /* @__PURE__ */ jsx3(DeckSlideFrame, { theme, children: /* @__PURE__ */ jsx3(
710
+ "article",
711
+ {
712
+ className: cn2("prose prose-slate max-w-none dark:prose-invert", presentMode && "text-base"),
713
+ "data-testid": "deck-slide-content",
714
+ children: /* @__PURE__ */ jsx3(
715
+ DeckSlideContent,
716
+ {
717
+ slide: currentSlide,
718
+ slideCount: slides.length,
719
+ path: params?.path,
720
+ mode,
721
+ widgets: indexedWidgets
722
+ }
723
+ )
724
+ }
725
+ ) }),
726
+ !hidePresentationControls ? /* @__PURE__ */ jsx3(
727
+ DeckSlideRail,
728
+ {
729
+ slideIndex: safeIndex,
730
+ slideCount: slides.length,
731
+ canGoPrevious: safeIndex > 0,
732
+ canGoNext: safeIndex < slides.length - 1,
733
+ onPrevious: () => setSlideIndex((current) => Math.max(current - 1, 0)),
734
+ onNext: () => setSlideIndex((current) => Math.min(current + 1, slides.length - 1)),
735
+ onSelect: setSlideIndex
736
+ }
737
+ ) : null
738
+ ] });
739
+ }
740
+ function FileBackedDeckPane({
741
+ params,
742
+ api,
743
+ theme,
744
+ widgets = [],
745
+ onError,
746
+ initialMode = "read",
747
+ getPresentHref
748
+ }) {
749
+ const path = params?.path ?? "";
750
+ const hasSelectedPath = /\S/.test(path);
751
+ const isFullPagePanel = useIsFullPagePanel();
752
+ const resolvedInitialMode = isFullPagePanel ? "present" : initialMode;
753
+ const [mode, setMode] = useState(resolvedInitialMode);
754
+ const [slideIndex, setSlideIndex] = useState(0);
755
+ const indexedWidgets = useMemo(() => indexDeckWidgets(widgets), [widgets]);
756
+ const {
757
+ content,
758
+ conflict,
759
+ error,
760
+ fileName,
761
+ isLoading,
762
+ onOverwrite,
763
+ onReloadFromServer,
764
+ setContent,
765
+ tabTitle
766
+ } = useFilePane({ path, panelId: api?.id });
767
+ const parsed = useMemo(() => content == null ? null : parseDeckContent(content, path), [content, path]);
768
+ useEffect(() => {
769
+ if (error) {
770
+ onError?.({
771
+ type: "storage",
772
+ path,
773
+ message: error.message,
774
+ cause: error
775
+ });
776
+ }
777
+ }, [error, onError, path]);
778
+ useEffect(() => {
779
+ if (conflict) {
780
+ onError?.({
781
+ type: "conflict",
782
+ path,
783
+ message: conflict.message,
784
+ cause: conflict
785
+ });
786
+ }
787
+ }, [conflict, onError, path]);
788
+ useEffect(() => {
789
+ if (parsed && !parsed.ok) onError?.(parsed.error);
790
+ }, [onError, parsed]);
791
+ useEffect(() => {
792
+ setMode(resolvedInitialMode);
793
+ }, [resolvedInitialMode]);
794
+ useEffect(() => {
795
+ setSlideIndex(0);
796
+ }, [content, path]);
797
+ useEffect(() => {
798
+ if (api && tabTitle) {
799
+ api.setTitle(tabTitle);
800
+ }
801
+ }, [api, tabTitle]);
802
+ const deck = parsed && parsed.ok ? parsed.deck : null;
803
+ const fallbackContent = content ?? "";
804
+ const slides = deck?.slides ?? [{ index: 0, raw: fallbackContent, segments: [{ type: "markdown", text: fallbackContent }] }];
805
+ const slideCount = slides.length;
806
+ const safeIndex = Math.min(Math.max(slideIndex, 0), Math.max(slideCount - 1, 0));
807
+ const currentSlide = slides[safeIndex];
808
+ const title = deck?.title ?? fileName ?? path;
809
+ const canNavigateSlides = mode !== "edit";
810
+ const fullPageHref = useFullPagePanelHref({
811
+ componentId: DECK_PANEL_ID,
812
+ params: path ? { path } : void 0
813
+ });
814
+ const presentHref = isFullPagePanel ? null : path ? getPresentHref?.(path) ?? fullPageHref : null;
815
+ const hidePresentationControls = shouldHidePresentationControls({
816
+ isFullPagePanel,
817
+ params,
818
+ presentMode: mode === "present"
819
+ });
820
+ useDeckKeyboardNavigation({
821
+ enabled: hasSelectedPath && !error && content != null && mode !== "edit",
822
+ canGoPrevious: safeIndex > 0,
823
+ canGoNext: safeIndex < slideCount - 1,
824
+ onPrevious: () => setSlideIndex((current) => Math.max(current - 1, 0)),
825
+ onNext: () => setSlideIndex((current) => Math.min(current + 1, slideCount - 1))
826
+ });
827
+ if (!hasSelectedPath) {
828
+ return /* @__PURE__ */ jsx3(DeckShell, { theme, children: /* @__PURE__ */ jsx3(DeckScaffoldState, { children: "No deck file selected." }) });
829
+ }
830
+ if (isLoading && content == null) {
831
+ return /* @__PURE__ */ jsx3(DeckShell, { theme, children: /* @__PURE__ */ jsx3(DeckScaffoldState, { children: "Loading deck\u2026" }) });
832
+ }
833
+ if (error || content == null) {
834
+ return /* @__PURE__ */ jsx3(DeckShell, { theme, children: /* @__PURE__ */ jsx3(DeckErrorState, { title: "Failed to load deck", description: error?.message ?? "Preview unavailable." }) });
835
+ }
836
+ return /* @__PURE__ */ jsxs3(DeckShell, { theme, presentMode: mode === "present", children: [
837
+ !hidePresentationControls ? /* @__PURE__ */ jsx3(
838
+ DeckToolbar,
839
+ {
840
+ title,
841
+ path,
842
+ mode: mode === "present" ? "read" : mode,
843
+ onModeChange: isFullPagePanel ? void 0 : setMode,
844
+ presentMode: mode === "present",
845
+ slideIndex: safeIndex,
846
+ slideCount,
847
+ onTogglePresentMode: mode === "edit" ? void 0 : () => setMode((current) => current === "present" ? "read" : "present"),
848
+ actions: /* @__PURE__ */ jsx3(Fragment2, { children: presentHref ? /* @__PURE__ */ jsx3(
849
+ Button2,
850
+ {
851
+ variant: "ghost",
852
+ size: "icon-xs",
853
+ asChild: true,
854
+ "aria-label": "Open in new tab",
855
+ title: "Open deck in new tab",
856
+ children: /* @__PURE__ */ jsx3("a", { href: presentHref, target: "_blank", rel: "noopener noreferrer", "data-testid": "deck-open-present", children: /* @__PURE__ */ jsx3(ExternalLink, { className: "size-3.5" }) })
857
+ }
858
+ ) : null })
859
+ }
860
+ ) : null,
861
+ conflict ? /* @__PURE__ */ jsx3(
862
+ DeckNotice,
863
+ {
864
+ title: "Deck changed on disk",
865
+ description: "Choose whether to reload the server version or overwrite it with your current draft.",
866
+ testId: "deck-conflict-notice",
867
+ actions: /* @__PURE__ */ jsxs3(Fragment2, { children: [
868
+ /* @__PURE__ */ jsx3(
869
+ Button2,
870
+ {
871
+ type: "button",
872
+ variant: "outline",
873
+ size: "xs",
874
+ onClick: () => void onReloadFromServer(),
875
+ "data-testid": "deck-reload",
876
+ children: "Reload"
877
+ }
878
+ ),
879
+ /* @__PURE__ */ jsx3(
880
+ Button2,
881
+ {
882
+ type: "button",
883
+ variant: "outline",
884
+ size: "xs",
885
+ onClick: () => void onOverwrite(),
886
+ "data-testid": "deck-overwrite",
887
+ children: "Overwrite"
888
+ }
889
+ )
890
+ ] })
891
+ }
892
+ ) : null,
893
+ mode === "edit" ? /* @__PURE__ */ jsxs3("div", { className: "min-h-0 flex-1 overflow-hidden", "data-testid": "deck-edit-mode", children: [
894
+ parsed && !parsed.ok ? /* @__PURE__ */ jsx3(
895
+ DeckNotice,
896
+ {
897
+ title: "Deck markdown has parse errors",
898
+ description: parsed.error.message,
899
+ tone: "error",
900
+ testId: "deck-parse-notice"
901
+ }
902
+ ) : null,
903
+ /* @__PURE__ */ jsx3(
904
+ MarkdownEditor,
905
+ {
906
+ content,
907
+ onChange: setContent,
908
+ documentPath: path,
909
+ className: "min-h-0 h-full"
910
+ }
911
+ )
912
+ ] }) : parsed && !parsed.ok ? /* @__PURE__ */ jsx3(DeckErrorState, { title: "Failed to render deck", description: parsed.error.message }) : /* @__PURE__ */ jsx3(DeckSlideFrame, { theme, children: /* @__PURE__ */ jsx3(
913
+ "article",
914
+ {
915
+ className: cn2("prose prose-slate max-w-none dark:prose-invert", mode === "present" && "text-base"),
916
+ "data-testid": "deck-slide-content",
917
+ children: /* @__PURE__ */ jsx3(
918
+ DeckSlideContent,
919
+ {
920
+ slide: currentSlide,
921
+ slideCount,
922
+ path,
923
+ mode: mode === "present" ? "present" : "read",
924
+ widgets: indexedWidgets
925
+ }
926
+ )
927
+ }
928
+ ) }),
929
+ mode !== "edit" && !hidePresentationControls ? /* @__PURE__ */ jsx3(
930
+ DeckSlideRail,
931
+ {
932
+ slideIndex: safeIndex,
933
+ slideCount,
934
+ canGoPrevious: canNavigateSlides && safeIndex > 0,
935
+ canGoNext: canNavigateSlides && safeIndex < slideCount - 1,
936
+ onPrevious: () => setSlideIndex((current) => Math.max(current - 1, 0)),
937
+ onNext: () => setSlideIndex((current) => Math.min(current + 1, slideCount - 1)),
938
+ onSelect: setSlideIndex
939
+ }
940
+ ) : null
941
+ ] });
942
+ }
943
+ function shouldHidePresentationControls({
944
+ isFullPagePanel,
945
+ params,
946
+ presentMode
947
+ }) {
948
+ if (!presentMode) return false;
949
+ if (params?.showControls === true || params?.controls === "visible") return false;
950
+ if (params?.showControls === false || params?.controls === "hidden") return true;
951
+ return isFullPagePanel;
952
+ }
953
+ function DeckSlideContent({ slide, slideCount, path, mode, widgets }) {
954
+ const blocks = [];
955
+ let inlineRun = [];
956
+ const flushInlineRun = () => {
957
+ if (inlineRun.length === 0) return;
958
+ blocks.push(/* @__PURE__ */ jsx3("p", { children: inlineRun }, `inline-run-${slide.index}-${blocks.length}`));
959
+ inlineRun = [];
960
+ };
961
+ slide.segments.forEach((segment, index) => {
962
+ const context = {
963
+ path,
964
+ slideIndex: slide.index,
965
+ slideCount,
966
+ mode
967
+ };
968
+ if (segment.type === "markdown") {
969
+ if (isInlineCompatibleMarkdown(segment.text)) {
970
+ inlineRun.push(
971
+ /* @__PURE__ */ jsx3(InlineMarkdownSegment, { text: segment.text }, `inline-markdown-${slide.index}-${index}`)
972
+ );
973
+ return;
974
+ }
975
+ flushInlineRun();
976
+ blocks.push(/* @__PURE__ */ jsx3(MarkdownSegment, { text: segment.text }, `markdown-${slide.index}-${index}`));
977
+ return;
978
+ }
979
+ const widget = widgets.get(segment.name);
980
+ const display = widget?.display ?? segment.position;
981
+ if (display === "inline") {
982
+ inlineRun.push(
983
+ /* @__PURE__ */ jsx3(
984
+ DeckWidgetSlot,
985
+ {
986
+ segment,
987
+ widgets,
988
+ context
989
+ },
990
+ `widget-${slide.index}-${index}`
991
+ )
992
+ );
993
+ return;
994
+ }
995
+ flushInlineRun();
996
+ blocks.push(
997
+ /* @__PURE__ */ jsx3(
998
+ DeckWidgetSlot,
999
+ {
1000
+ segment,
1001
+ widgets,
1002
+ context
1003
+ },
1004
+ `widget-${slide.index}-${index}`
1005
+ )
1006
+ );
1007
+ });
1008
+ flushInlineRun();
1009
+ return /* @__PURE__ */ jsx3("div", { className: "space-y-4", "data-testid": `deck-slide-${slide.index}`, children: blocks });
1010
+ }
1011
+ function MarkdownSegment({ text }) {
1012
+ return /* @__PURE__ */ jsx3(ReactMarkdown, { remarkPlugins: [remarkGfm], children: text });
1013
+ }
1014
+ function InlineMarkdownSegment({ text }) {
1015
+ return /* @__PURE__ */ jsx3("span", { className: "whitespace-pre-wrap", children: /* @__PURE__ */ jsx3(
1016
+ ReactMarkdown,
1017
+ {
1018
+ remarkPlugins: [remarkGfm],
1019
+ components: {
1020
+ p: ({ children }) => /* @__PURE__ */ jsx3(Fragment, { children })
1021
+ },
1022
+ children: text
1023
+ }
1024
+ ) });
1025
+ }
1026
+ function isInlineCompatibleMarkdown(text) {
1027
+ const trimmed = text.trim();
1028
+ if (!trimmed) return false;
1029
+ if (trimmed.includes("\n\n")) return false;
1030
+ return !/^(#{1,6}\s|[-*+]\s|\d+\.\s|>|```|~~~|\|)|^---$/m.test(trimmed);
1031
+ }
1032
+ function parseDeckContent(content, path) {
1033
+ try {
1034
+ return { ok: true, deck: parseDeckMarkdown(content) };
1035
+ } catch (cause) {
1036
+ return {
1037
+ ok: false,
1038
+ error: {
1039
+ type: "parse",
1040
+ path,
1041
+ message: cause instanceof Error ? cause.message : "Failed to parse deck",
1042
+ cause
1043
+ }
1044
+ };
1045
+ }
1046
+ }
1047
+
1048
+ // src/front/StandaloneDeckRoute.tsx
1049
+ import { jsx as jsx4 } from "react/jsx-runtime";
1050
+ function StandaloneDeckRoute({ path, content, theme, widgets, onError, getPresentHref }) {
1051
+ return /* @__PURE__ */ jsx4("div", { className: "min-h-screen bg-background text-foreground", children: /* @__PURE__ */ jsx4(
1052
+ DeckPane,
1053
+ {
1054
+ params: path ? { path } : {},
1055
+ content,
1056
+ theme,
1057
+ widgets,
1058
+ onError,
1059
+ getPresentHref,
1060
+ initialMode: "present"
1061
+ }
1062
+ ) });
1063
+ }
1064
+
1065
+ // src/front/surfaceResolver.ts
1066
+ import { WORKSPACE_OPEN_PATH_SURFACE_KIND } from "@hachej/boring-workspace/plugin";
1067
+ function basename(path) {
1068
+ const normalized = normalizeDeckPath(path);
1069
+ return normalized.split("/").pop() ?? path;
1070
+ }
1071
+ function createDeckSurfaceResolver(pathPrefix) {
1072
+ const normalizedPrefix = normalizeDeckPath(pathPrefix);
1073
+ return {
1074
+ id: "deck.open-path",
1075
+ kind: WORKSPACE_OPEN_PATH_SURFACE_KIND,
1076
+ source: "app",
1077
+ resolve: (request) => {
1078
+ if (request.kind !== WORKSPACE_OPEN_PATH_SURFACE_KIND) return null;
1079
+ const target = normalizeDeckPath(request.target);
1080
+ if (!isDeckMarkdownPath(target, normalizedPrefix)) return null;
1081
+ return {
1082
+ component: DECK_PANEL_ID,
1083
+ title: basename(target),
1084
+ params: { path: target },
1085
+ score: 100
1086
+ };
1087
+ }
1088
+ };
1089
+ }
1090
+ var deckSurfaceResolver = createDeckSurfaceResolver("deck/");
1091
+
1092
+ // src/front/index.tsx
1093
+ import { Fragment as Fragment3, jsx as jsx5 } from "react/jsx-runtime";
1094
+ function DeckFilesProvider({
1095
+ apiBaseUrl,
1096
+ authHeaders,
1097
+ onAuthError,
1098
+ apiTimeout,
1099
+ children
1100
+ }) {
1101
+ const hasWorkspaceFilesProvider = useHasWorkspaceFilesProvider();
1102
+ if (hasWorkspaceFilesProvider) {
1103
+ return /* @__PURE__ */ jsx5(Fragment3, { children });
1104
+ }
1105
+ return /* @__PURE__ */ jsx5(
1106
+ WorkspaceFilesProvider,
1107
+ {
1108
+ apiBaseUrl,
1109
+ authHeaders,
1110
+ onAuthError,
1111
+ timeout: apiTimeout,
1112
+ children
1113
+ }
1114
+ );
1115
+ }
1116
+ function createDeckPlugin(options = {}) {
1117
+ const pathPrefix = normalizeDeckPath(options.pathPrefix ?? DECK_PATH_PREFIX);
1118
+ validateDeckWidgets(options.widgets ?? []);
1119
+ return definePlugin({
1120
+ id: DECK_PLUGIN_ID,
1121
+ label: DECK_LABEL,
1122
+ providers: [
1123
+ {
1124
+ id: "deck-files",
1125
+ component: DeckFilesProvider
1126
+ }
1127
+ ],
1128
+ panels: [
1129
+ {
1130
+ id: DECK_PANEL_ID,
1131
+ label: DECK_LABEL,
1132
+ component: (props) => /* @__PURE__ */ jsx5(
1133
+ DeckPane,
1134
+ {
1135
+ ...props,
1136
+ pathPrefix,
1137
+ theme: options.theme,
1138
+ widgets: options.widgets,
1139
+ onError: options.onError
1140
+ }
1141
+ ),
1142
+ placement: "center",
1143
+ source: "app",
1144
+ supportsFullPage: true
1145
+ }
1146
+ ],
1147
+ surfaceResolvers: [createDeckSurfaceResolver(pathPrefix)]
1148
+ });
1149
+ }
1150
+ var deckPlugin = createDeckPlugin({ pathPrefix: DECK_PATH_PREFIX });
1151
+ var front_default = deckPlugin;
1152
+ export {
1153
+ DeckPane,
1154
+ StandaloneDeckRoute,
1155
+ createDeckPlugin,
1156
+ createDeckSurfaceResolver,
1157
+ deckSurfaceResolver,
1158
+ front_default as default
1159
+ };