@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.
package/README.md ADDED
@@ -0,0 +1,265 @@
1
+ # @hachej/boring-deck
2
+
3
+ Front-only markdown deck plugin for Boring workspace.
4
+
5
+ ## What it ships
6
+
7
+ `@hachej/boring-deck` opens markdown slide decks stored in normal workspace
8
+ files.
9
+
10
+ v1 includes:
11
+ - one deck panel
12
+ - read / edit / present flows
13
+ - clean full-page presentation mode with keyboard navigation
14
+ - `workspace.open.path` resolution for deck markdown paths
15
+ - generic widget injection for app-owned components
16
+ - one bundled Pi skill at `skills/deck-authoring/`
17
+ - Pi system prompt guidance for opening decks through `exec_ui openSurface`
18
+
19
+ ## Intentionally out of scope
20
+
21
+ This package does **not** ship:
22
+ - `boring.server`
23
+ - deck-specific HTTP routes
24
+ - a custom storage abstraction
25
+ - macro/domain widgets such as `TimeSeries` or `TimeSeriesGrid`
26
+ - MDX or raw-HTML eval
27
+ - per-slide canvas sizing in v1
28
+
29
+ ## Installation
30
+
31
+ Add the package to your app and register it like any other front plugin:
32
+
33
+ ```tsx
34
+ import { createDeckPlugin } from "@hachej/boring-deck/front"
35
+
36
+ const deckPlugin = createDeckPlugin()
37
+ ```
38
+
39
+ ```tsx
40
+ <WorkspaceProvider plugins={[deckPlugin]}>
41
+ <IdeLayout />
42
+ </WorkspaceProvider>
43
+ ```
44
+
45
+ ## Public API
46
+
47
+ `@hachej/boring-deck/front` (also re-exported from `@hachej/boring-deck`) ships:
48
+
49
+ - `createDeckPlugin(options?)` — normal workspace plugin entrypoint; installs the
50
+ deck panel, the default `workspace.open.path` resolver, and the required file
51
+ provider for file-backed decks.
52
+ - `DeckPane` — the deck pane component. Use this when you want the deck UI
53
+ without going through plugin registration.
54
+ - `StandaloneDeckRoute` — full-page wrapper around `DeckPane` for standalone
55
+ routes or embeds.
56
+ - `createDeckSurfaceResolver(pathPrefix)` — builds a
57
+ `workspace.open.path` surface resolver for markdown files under the given
58
+ prefix.
59
+ - `deckSurfaceResolver` — the default resolver instance for `deck/`.
60
+
61
+ `@hachej/boring-deck/shared` ships the shared types and parser/path helpers,
62
+ including `DeckWidgetDefinition`, `DeckError`, `parseDeckMarkdown(...)`, and
63
+ deck path helpers.
64
+
65
+ ### Plugin options
66
+
67
+ ```ts
68
+ export interface CreateDeckPluginOptions {
69
+ pathPrefix?: string
70
+ widgets?: DeckWidgetDefinition[]
71
+ theme?: DeckThemeOptions
72
+ onError?: (error: DeckError) => void
73
+ }
74
+
75
+ export interface DeckThemeOptions {
76
+ aspectRatio?: "16:9" | "4:3"
77
+ className?: string
78
+ slideClassName?: string
79
+ }
80
+ ```
81
+
82
+ Rules:
83
+ - plugin id, panel id, label, and resolver wiring are fixed in v1
84
+ - default `pathPrefix = "deck/"`
85
+ - panel + surface resolver are built in
86
+ - no built-in command in v1
87
+ - full-page presentation hides deck controls by default; pass panel params
88
+ `{ controls: "visible" }` to opt controls back in
89
+
90
+ ### Widget API
91
+
92
+ ```ts
93
+ export interface DeckWidgetDefinition<TAttrs = Record<string, string>> {
94
+ name: string
95
+ display?: "block" | "inline"
96
+ parse?: (attrs: Record<string, string>) => TAttrs
97
+ render: (props: DeckWidgetRenderProps<TAttrs>) => ReactNode
98
+ }
99
+
100
+ export interface DeckWidgetRenderProps<TAttrs = Record<string, string>> {
101
+ attrs: TAttrs
102
+ rawAttrs: Record<string, string>
103
+ context: DeckWidgetRenderContext
104
+ }
105
+
106
+ export interface DeckWidgetRenderContext {
107
+ path?: string
108
+ slideIndex: number
109
+ slideCount: number
110
+ mode: "read" | "edit" | "present"
111
+ }
112
+ ```
113
+
114
+ What each field means:
115
+ - `name` must match the widget name used in markdown, for example
116
+ `{{Badge text="draft"}}`.
117
+ - `display` overrides placement. Omit it to follow the parsed segment position:
118
+ inline widgets stay inline, and block-position widgets render as their own
119
+ block.
120
+ - `parse` is optional. Use it to convert raw string attrs into a typed shape for
121
+ your widget.
122
+ - `render` receives parsed `attrs`, the original string attrs as `rawAttrs`, and
123
+ a `context` describing the deck path, current slide index, slide count, and
124
+ whether the deck is in read, edit, or present mode.
125
+
126
+ Deck preserves host-owned widget syntax. If a workspace already uses custom
127
+ widgets, keep the same widget names and attrs in markdown rather than rewriting
128
+ content into another format.
129
+
130
+ ### Error API
131
+
132
+ ```ts
133
+ export interface DeckError {
134
+ type: "storage" | "parse" | "render" | "widget" | "conflict"
135
+ path?: string
136
+ message: string
137
+ cause?: unknown
138
+ }
139
+ ```
140
+
141
+ `onError` receives deck-level failures without changing the canonical workspace
142
+ file semantics:
143
+ - `storage` — file load/save/provider failures
144
+ - `parse` — invalid deck markdown that prevents deck parsing
145
+ - `conflict` — optimistic-concurrency overwrite/reload conflicts
146
+
147
+ Notes:
148
+ - widget parse/render failures stay local to the widget and render a visible
149
+ placeholder instead of calling `onError`
150
+ - unknown widgets also render placeholders locally
151
+ - `render` / `widget` remain part of `DeckError` for future deck-level paths,
152
+ but the current implementation does not emit them through `onError`
153
+
154
+ ### Exported surfaces and provider requirements
155
+
156
+ - `createDeckPlugin(...)` is the easiest path. It already installs
157
+ `WorkspaceFilesProvider`, so file-backed decks opened by path work out of the
158
+ box.
159
+ - `DeckPane` accepts either `content` for standalone rendering or `params.path`
160
+ for file-backed rendering. If you pass `params.path`, mount it under
161
+ `WorkspaceFilesProvider` (or an equivalent provider supplying the same
162
+ workspace file contexts).
163
+ - `StandaloneDeckRoute` wraps `DeckPane` in a full-page shell and starts in
164
+ present mode. It follows the same rule: inline `content` needs no file
165
+ provider, file-backed `path` does.
166
+ - `createDeckSurfaceResolver(pathPrefix)` and `deckSurfaceResolver` only match
167
+ markdown files under the configured deck prefix and route them to the deck
168
+ panel via `workspace.open.path`.
169
+
170
+ ## Canonical file-state reuse
171
+
172
+ Deck deliberately reuses the canonical workspace file-state seam instead of
173
+ inventing deck-local storage APIs.
174
+
175
+ The deck panel runs on top of:
176
+ - `WorkspaceFilesProvider`
177
+ - `useFilePane(...)`
178
+ - canonical workspace optimistic-concurrency behavior (`mtimeMs` /
179
+ `expectedMtimeMs`)
180
+
181
+ If you mount deck components outside the plugin wrapper, they must still run
182
+ under `WorkspaceFilesProvider` (or an equivalent provider that supplies the same
183
+ workspace file contexts).
184
+
185
+ ## Markdown deck format
186
+
187
+ - deck files live under `deck/*.md` by default
188
+ - `---` on its own line splits slides
189
+ - widgets use moustache syntax
190
+ - inline widgets stay inline; block widgets render as their own block
191
+
192
+ Example:
193
+
194
+ ```md
195
+ # Quarterly update
196
+
197
+ Welcome {{Badge text="draft"}}
198
+
199
+ ---
200
+
201
+ ## Metrics
202
+
203
+ {{Kpi label="Revenue" value="$12.4M"}}
204
+ ```
205
+
206
+ ## Widget injection
207
+
208
+ Widgets stay app-owned. The generic package only provides the registry and
209
+ rendering contract.
210
+
211
+ ```tsx
212
+ import { createDeckPlugin } from "@hachej/boring-deck/front"
213
+ import type { DeckWidgetDefinition } from "@hachej/boring-deck/shared"
214
+
215
+ const badgeWidget: DeckWidgetDefinition = {
216
+ name: "Badge",
217
+ display: "inline",
218
+ render: ({ attrs }) => <span className="rounded-full border px-2 py-0.5 text-xs">{attrs.text}</span>,
219
+ }
220
+
221
+ const deckPlugin = createDeckPlugin({
222
+ widgets: [badgeWidget],
223
+ theme: {
224
+ className: "my-deck-theme",
225
+ slideClassName: "my-deck-slide",
226
+ },
227
+ })
228
+ ```
229
+
230
+ Unknown widgets render a visible placeholder instead of crashing the whole deck.
231
+ Widget parse/render failures stay local to the widget.
232
+
233
+ ## Theming
234
+
235
+ Theme customization is intentionally small:
236
+ - `aspectRatio`
237
+ - shell class name
238
+ - slide frame class name
239
+
240
+ Use normal app/workspace CSS tokens and classes for colors/typography rather
241
+ than deck-specific color/font props.
242
+
243
+ ## Skill and agent UI opening
244
+
245
+ The bundled `deck-authoring` skill teaches:
246
+ - where deck files live
247
+ - how to split slides
248
+ - how to keep slides concise
249
+ - widget syntax and host-widget preservation rules
250
+ - how to open a deck through `exec_ui`:
251
+ `{ kind: "openSurface", params: { kind: "workspace.open.path", target: "deck/intro.md" } }`
252
+
253
+ For the Pi prompt/skill to be active in an app, include `@hachej/boring-deck`
254
+ in the app's `package.json#boring.defaultPluginPackages` or otherwise pass its
255
+ `package.json#pi` contributions to `createWorkspaceAgentApp()`.
256
+
257
+ ## Validation
258
+
259
+ Typical package checks:
260
+
261
+ ```bash
262
+ pnpm --filter @hachej/boring-deck typecheck
263
+ pnpm --filter @hachej/boring-deck test
264
+ pnpm --filter @hachej/boring-deck build
265
+ ```
@@ -0,0 +1,42 @@
1
+ import { BoringFrontSurfaceResolverRegistration, BoringFrontFactoryWithId } from '@hachej/boring-workspace/plugin';
2
+ import { D as DeckThemeOptions, a as DeckWidgetDefinition, b as DeckError, C as CreateDeckPluginOptions } from '../types-Dt_A9RNg.js';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+ import { PaneProps } from '@hachej/boring-workspace';
5
+ import 'react';
6
+
7
+ interface DeckPaneParams {
8
+ path?: string;
9
+ controls?: "hidden" | "visible";
10
+ showControls?: boolean;
11
+ }
12
+ interface DeckPaneProps {
13
+ params?: DeckPaneParams;
14
+ api?: PaneProps<DeckPaneParams>["api"];
15
+ containerApi?: PaneProps<DeckPaneParams>["containerApi"];
16
+ content?: string;
17
+ pathPrefix?: string;
18
+ theme?: DeckThemeOptions;
19
+ widgets?: DeckWidgetDefinition[];
20
+ onError?: (error: DeckError) => void;
21
+ initialMode?: "read" | "edit" | "present";
22
+ getPresentHref?: (path: string) => string;
23
+ }
24
+ declare function DeckPane(props: DeckPaneProps): react_jsx_runtime.JSX.Element;
25
+
26
+ interface StandaloneDeckRouteProps {
27
+ path?: string;
28
+ content?: string;
29
+ theme?: DeckThemeOptions;
30
+ widgets?: DeckWidgetDefinition[];
31
+ onError?: (error: DeckError) => void;
32
+ getPresentHref?: (path: string) => string;
33
+ }
34
+ declare function StandaloneDeckRoute({ path, content, theme, widgets, onError, getPresentHref }: StandaloneDeckRouteProps): react_jsx_runtime.JSX.Element;
35
+
36
+ declare function createDeckSurfaceResolver(pathPrefix: string): BoringFrontSurfaceResolverRegistration;
37
+ declare const deckSurfaceResolver: BoringFrontSurfaceResolverRegistration;
38
+
39
+ declare function createDeckPlugin(options?: CreateDeckPluginOptions): BoringFrontFactoryWithId;
40
+ declare const deckPlugin: BoringFrontFactoryWithId;
41
+
42
+ export { DeckPane, StandaloneDeckRoute, createDeckPlugin, createDeckSurfaceResolver, deckSurfaceResolver, deckPlugin as default };