@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 +265 -0
- package/dist/front/index.d.ts +42 -0
- package/dist/front/index.js +1159 -0
- package/dist/shared/index.d.ts +17 -0
- package/dist/shared/index.js +265 -0
- package/dist/types-Dt_A9RNg.d.ts +57 -0
- package/package.json +78 -0
- package/skills/deck-authoring/SKILL.md +73 -0
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 };
|