konpeki 0.2.0 → 0.3.1
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/AGENTS.md +1 -1
- package/CONTRIBUTING.md +36 -0
- package/README.md +81 -28
- package/SECURITY.md +22 -0
- package/SETUP.md +128 -51
- package/composition/README.md +16 -3
- package/composition/compile.ts +1 -1
- package/composition/schema.json +9 -0
- package/composition/schema.ts +3 -1
- package/composition/types.ts +3 -1
- package/design/themes/README.md +25 -7
- package/design/themes/index.ts +15 -4
- package/docs/development.md +79 -11
- package/docs/workflow.md +11 -4
- package/index.html +17 -1
- package/package.json +10 -2
- package/plugin.json +22 -0
- package/public/og.png +0 -0
- package/runtime/konpeki.mjs +41 -13
- package/skills/konpeki/SKILL.md +186 -0
- package/skills/konpeki/assets/blank.json +23 -0
- package/skills/konpeki/scripts/ensure-runtime.mjs +69 -0
- package/skills/konpeki/scripts/prepare-document.mjs +40 -0
- package/slides/README.md +35 -132
- package/slides/drawing-references.md +32 -0
- package/slides/github-cover/PROMPT.md +21 -0
- package/slides/github-cover/README.md +21 -0
- package/slides/github-cover/author.ts +67 -0
- package/slides/github-cover/composition.json +595 -0
- package/slides/introducing-konpeki/PROMPT.md +25 -14
- package/slides/introducing-konpeki/README.md +32 -42
- package/slides/introducing-konpeki/SOURCE.md +13 -14
- package/slides/introducing-konpeki/author.ts +44 -42
- package/slides/introducing-konpeki/composition.json +135 -135
- package/src/app/App.tsx +149 -56
- package/src/components/Canvas.tsx +9 -3
- package/src/components/InspectorPanel.tsx +104 -67
- package/src/components/RevisionNotes.tsx +6 -7
- package/src/components/RightPanel.tsx +42 -10
- package/src/components/VectorOverflowWarning.tsx +35 -8
- package/src/components/WorkspaceChrome.tsx +125 -10
- package/src/components/ui.tsx +95 -1
- package/src/lib/storage.ts +38 -10
- package/src/lib/use-file-session.ts +60 -38
- package/src/main.tsx +3 -0
- package/src/styles/base.css +7 -0
- package/src/styles/chrome.css +188 -25
- package/src/styles/feedback.css +101 -49
- package/src/styles/left-panel.css +27 -6
- package/src/styles/right-panel.css +114 -30
- package/src/styles/shell.css +96 -33
- package/.agents/skills/authoring-visuals/SKILL.md +0 -93
|
@@ -1,54 +1,44 @@
|
|
|
1
1
|
# Introducing Konpeki
|
|
2
2
|
|
|
3
|
-
A six-
|
|
4
|
-
|
|
5
|
-
Paper
|
|
6
|
-
|
|
7
|
-
1. **Meet Konpeki** — one shared canvas for people and coding agents.
|
|
8
|
-
2. **A round-trip workflow** — draft, download, agent revision, open and present.
|
|
9
|
-
3. **Five semantic components** — Text block, Diagram, Chart, Image and Table.
|
|
10
|
-
4. **Keep the detail editable** — stable vector elements inside their owners.
|
|
11
|
-
5. **What works today** — capabilities alongside practical boundaries.
|
|
12
|
-
6. **Try a real brief** — setup and an example request.
|
|
3
|
+
A six-page introduction covering creation, shared editing, components, revision
|
|
4
|
+
notes, browser/file sessions and a first brief. Uses default authoring mode and
|
|
5
|
+
Precision Paper with theme-linked colors and fonts.
|
|
13
6
|
|
|
14
7
|
## Open and revise
|
|
15
8
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
9
|
+
Open the [playground example](https://vcfgdev.github.io/konpeki/?example=introducing-konpeki)
|
|
10
|
+
and select **Present**. Edits save to a separate browser-local copy; use
|
|
11
|
+
**Browser → Download JSON** to keep them. **Reset example** restores the bundled
|
|
12
|
+
version, so download your work first. Browser storage is not a backup or agent sync.
|
|
13
|
+
|
|
14
|
+
For agent revisions, follow [setup](../../SETUP.md) and open a copy of
|
|
15
|
+
[composition.json](composition.json) in a file-backed preview. Notes, **Build it**
|
|
16
|
+
and **Copy prompt** work there, not in the standalone playground. See the
|
|
17
|
+
[canvas workflow](../../docs/workflow.md) for handoff details.
|
|
18
|
+
|
|
19
|
+
The JSON is the editable source: double-click text to edit it; diagrams contain
|
|
20
|
+
editable vector elements. [author.ts](author.ts) reconstructs the original deck:
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
ordinary text to edit it directly; Content intent remains separate agent guidance.
|
|
25
|
-
This is not a second React deck runtime or a collection of page images.
|
|
22
|
+
```sh
|
|
23
|
+
mise exec -- node slides/introducing-konpeki/author.ts
|
|
24
|
+
```
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
it **replaces manual canvas revisions**, so do not rerun it over revised work.
|
|
30
|
-
See [the public brief excerpt](PROMPT.md) and [source facts / provenance](SOURCE.md).
|
|
26
|
+
Run from the repository root. **This overwrites canvas edits.**
|
|
27
|
+
[Brief](PROMPT.md) · [Sources](SOURCE.md) · [Reviewed screenshots](screenshots/)
|
|
31
28
|
|
|
32
29
|
## Review
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
Pass output directory, theme label and mode after the base URL to `review.mjs`.
|
|
47
|
-
`node scripts/check-themes.mjs` exercises all eight palettes in both modes,
|
|
48
|
-
fixed overrides, overflow warnings, linking, undo/redo, Prompt guidance and
|
|
49
|
-
autosave/reload/presentation. Literal imported artwork is intentionally fixed.
|
|
50
|
-
- Final reviewed presentation captures: [screenshots](screenshots/).
|
|
51
|
-
- Chart values are illustrative; diagrams are explanatory artwork, not screenshots.
|
|
52
|
-
- No PDF/PPTX export, native mobile or cross-application fidelity was tested.
|
|
31
|
+
With a dev/preview server and `agent-browser`, run from the repository root:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
mise exec -- node slides/introducing-konpeki/review.mjs <preview-url> /tmp/konpeki-intro Precision paper
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The script checks text bounds and vector alignment at 1920×1080 and 1024×768,
|
|
38
|
+
DPR2. Repeat with `Precision night` and `Editorial paper`, then inspect captures.
|
|
39
|
+
An optional final font ID overrides the palette's default, e.g. `Green paper plex-serif`.
|
|
40
|
+
These 36 states passed the original visual review, alongside validation, typecheck,
|
|
41
|
+
tests and build. Browser review does not verify agent-client installation,
|
|
42
|
+
PDF/PPTX, mobile, reduced motion, focus styling or cross-application fidelity.
|
|
53
43
|
|
|
54
44
|

|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
# Source facts and provenance
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Product claims come from repository documentation, not a customer case study.
|
|
4
4
|
|
|
5
5
|
| Claim | Repository evidence |
|
|
6
6
|
| --- | --- |
|
|
7
|
-
|
|
|
8
|
-
|
|
|
9
|
-
|
|
|
10
|
-
|
|
|
11
|
-
|
|
|
12
|
-
|
|
|
13
|
-
|
|
|
7
|
+
| Covers, social graphics, charts, diagrams and presentations share one canvas | [Product](../../README.md); [authoring](../../AUTHORING.md) |
|
|
8
|
+
| Skill-led creation; init optional; Node.js 24+, npm, file/command access and a browser required | [User setup](../../SETUP.md) |
|
|
9
|
+
| Browser and agent edit the same JSON; file sessions support notes, Build it and Copy prompt | [File-backed workflow](../../docs/workflow.md#file-backed-editing-with-an-agent) |
|
|
10
|
+
| Text block, Diagram, Chart, Image and Table; editable vectors with stable IDs | [Contract](../../composition/README.md); [types](../../composition/types.ts) |
|
|
11
|
+
| Direct text, shape and layout editing with undo/redo | [Operations](../../composition/document.ts) |
|
|
12
|
+
| Diagram, Chart and Table defaults are structural drafts; finished artwork needs source data | [Exports](../../docs/workflow.md#documents-and-exports); [fidelity rules](../../AUTHORING.md#requirements) |
|
|
13
|
+
| Playground: independent local copies, JSON import/download, blank/reset, PNG and Present; no agent sync, connected AI or cloud storage | [Hosting](../../docs/development.md#development-server-and-demo-hosting); [storage](../../src/lib/storage.ts); [controls](../../src/components/WorkspaceChrome.tsx) |
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
is claimed.
|
|
15
|
+
Drawings are original explanatory artwork, not screenshots. Chart values and
|
|
16
|
+
the announcement brief are illustrative, not product measurements or customer
|
|
17
|
+
results. No third-party images are used. Precision defaults to bundled Noto Sans;
|
|
18
|
+
text follows the selected font roles. See the [review scope](README.md#review)
|
|
19
|
+
for verification limits.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Authoring source. composition.json is the editable deck of record.
|
|
2
2
|
// Re-running this script replaces it; preserve canvas revisions before rebuilding.
|
|
3
3
|
import { writeFile } from "node:fs/promises";
|
|
4
|
+
import { canonicalJSON } from "../../composition/compile.ts";
|
|
4
5
|
import { createComponent } from "../../composition/document.ts";
|
|
5
6
|
import { validateComposition } from "../../composition/validate.ts";
|
|
6
7
|
import {
|
|
@@ -24,6 +25,7 @@ function arrow(id: string, x: number, y: number, end: number) {
|
|
|
24
25
|
}
|
|
25
26
|
function component(id: string, kind: CompositionComponent["kind"], x: number, y: number, width: number, height: number, description: string, elements: VectorElement[]): CompositionComponent {
|
|
26
27
|
const base = createComponent(kind, 1);
|
|
28
|
+
if (base.kind === "diagram" || base.kind === "chart") base.appearance.selection = "explicit";
|
|
27
29
|
return { ...base, id, slotIds: [`${id}-content`], preferredRect: { x, y, width, height }, intent: description,
|
|
28
30
|
customVisual: { format: "vector", viewBox: { x: 0, y: 0, width, height }, fit: "contain", description, elements } };
|
|
29
31
|
}
|
|
@@ -52,40 +54,40 @@ function heading(id: string, title: string, subtitle: string) {
|
|
|
52
54
|
const slides: CompositionSlide[] = [];
|
|
53
55
|
slides.push(slide("introduction", "Meet Konpeki", "What is Konpeki?", [
|
|
54
56
|
copy("brand", 112, 140, 820, ["Konpeki"], 112, blue, 600),
|
|
55
|
-
copy("promise", 112, 328, 960, ["
|
|
56
|
-
copy("summary", 112, 548, 870, ["
|
|
57
|
+
copy("promise", 112, 328, 960, ["Create clear visuals", "with your coding agent."], 56, ink, 500),
|
|
58
|
+
copy("summary", 112, 548, 870, ["Bring a brief. Get an editable result.", "Keep revising the same document."], 36, muted),
|
|
57
59
|
component("shared-canvas", "diagram", 1090, 185, 700, 590, "A single editable composition contains a headline, diagram and supporting text; people and agents revise it.", [
|
|
58
60
|
rect("canvas-frame", 8, 25, 670, 395),
|
|
59
|
-
text("canvas-title", "
|
|
61
|
+
text("canvas-title", "From brief to visual", 45, 93, 36, ink, 500),
|
|
60
62
|
path("headline-rule", "M45 120H640", line, 2),
|
|
61
63
|
rect("source-node", 50, 183, 160, 90, blue), rect("result-node", 310, 183, 320, 90, blue),
|
|
62
|
-
text("source-label", "
|
|
64
|
+
text("source-label", "Brief", 98, 239, 28), text("result-label", "Editable visual", 374, 239, 28),
|
|
63
65
|
arrow("connection", 228, 228, 292),
|
|
64
66
|
path("body-lines", "M50 323H395M50 351H525", line, 8),
|
|
65
67
|
text("document-label", "composition.json", 178, 484, 32, blue, 500),
|
|
66
68
|
text("participants", "Human edits + agent revisions", 116, 548, 28, muted),
|
|
67
69
|
]),
|
|
68
|
-
copy("intro-note", 112, 870, 1696, ["
|
|
70
|
+
copy("intro-note", 112, 870, 1696, ["Covers, social graphics, charts, diagrams and presentations."], 34),
|
|
69
71
|
]));
|
|
70
72
|
|
|
71
|
-
slides.push(slide("workflow", "
|
|
72
|
-
...heading("workflow", "
|
|
73
|
-
component("roundtrip", "diagram", 112, 342, 1696, 470, "
|
|
73
|
+
slides.push(slide("workflow", "Create and revise together", "How do a person and an agent work together?", [
|
|
74
|
+
...heading("workflow", "Start with a brief. Keep editing the result.", "In the agent-opened editor, you and your agent edit the same file."),
|
|
75
|
+
component("roundtrip", "diagram", 112, 342, 1696, 470, "Share a brief and materials. The agent creates a visual and opens its file-backed preview. Edit that canvas, then ask for revisions in the same conversation. The agent updates the same file; the loop returns to editing, not a new brief.", [
|
|
74
76
|
...[0, 1, 2, 3].flatMap((index) => {
|
|
75
77
|
const x = index * 438;
|
|
76
78
|
return [text(`number-${index}`, `0${index + 1}`, x, 48, 26, blue, 500),
|
|
77
|
-
text(`step-${index}`, ["
|
|
78
|
-
text(`detail-a-${index}`, ["
|
|
79
|
-
text(`detail-b-${index}`, ["Describe the
|
|
79
|
+
text(`step-${index}`, ["Brief", "Generate", "Edit", "Revise"][index], x, 122, 42, ink, 500),
|
|
80
|
+
text(`detail-a-${index}`, ["Share your materials.", "Agent creates a visual.", "Edit the open canvas.", "Ask in the same chat."][index], x, 190, 29, muted),
|
|
81
|
+
text(`detail-b-${index}`, ["Describe the result.", "Opens its file preview.", "Review the details.", "Agent updates that file."][index], x, 234, 29, muted),
|
|
80
82
|
...(index < 3 ? [arrow(`handoff-${index}`, x + 338, 109, x + 398)] : [])];
|
|
81
83
|
}),
|
|
82
|
-
path("revision-loop", "M1510
|
|
83
|
-
text("loop-label", "
|
|
84
|
+
path("revision-loop", "M1510 285V365H991V285M979 299L991 285L1003 299", blue),
|
|
85
|
+
text("loop-label", "Both edit the same composition.json", 490, 431, 32, blue),
|
|
84
86
|
]),
|
|
85
|
-
copy("workflow-note", 112, 870, 1696, ["
|
|
87
|
+
copy("workflow-note", 112, 870, 1696, ["Prefer to sketch first? Add a component, describe its intent, then choose Build it."], 34, ink),
|
|
86
88
|
]));
|
|
87
89
|
|
|
88
|
-
slides.push(slide("components", "Five semantic components", "What can a
|
|
90
|
+
slides.push(slide("components", "Five semantic components", "What can a page contain?", [
|
|
89
91
|
...heading("components", "Five components. One composition.", "Each component keeps its meaning, outer geometry and editable interior."),
|
|
90
92
|
component("text-specimen", "text-block", 112, 360, 296, 360, "Text block: headlines, body text, captions and footnotes.", [
|
|
91
93
|
text("label", "Text block", 0, 40, 38, ink, 500), text("sample", "Aa", 0, 166, 96, blue),
|
|
@@ -111,14 +113,14 @@ slides.push(slide("components", "Five semantic components", "What can a slide co
|
|
|
111
113
|
text("label", "Table", 0, 40, 38, ink, 500),
|
|
112
114
|
text("col-a", "Stage", 0, 120, 26, ink, 500), text("col-b", "Owner", 157, 120, 26, ink, 500),
|
|
113
115
|
path("rules", "M0 136H285M0 190H285M0 244H285", line, 2),
|
|
114
|
-
text("row-a", "
|
|
116
|
+
text("row-a", "Create", 0, 174, 25), text("row-a-owner", "Agent", 157, 174, 25), text("row-b", "Review", 0, 228, 25), text("row-b-owner", "You", 157, 228, 25),
|
|
115
117
|
text("detail", "Structured detail", 0, 317, 28, muted),
|
|
116
118
|
]),
|
|
117
|
-
copy("component-note", 112,
|
|
119
|
+
copy("component-note", 112, 848, 1696, ["Diagram, Chart and Table previews are structural drafts.", "Give the agent real data and sources for finished artwork."], 32, ink),
|
|
118
120
|
]));
|
|
119
121
|
|
|
120
122
|
slides.push(slide("editing", "Keep the detail editable", "What remains editable after an agent finishes?", [
|
|
121
|
-
...heading("editing", "
|
|
123
|
+
...heading("editing", "Keep editing—or ask your agent", "Change text, shapes and layout directly. Undo and redo stay available."),
|
|
122
124
|
component("editable-diagram", "diagram", 112, 340, 980, 475, "Explanatory illustration: a selected Review node inside its owning Diagram component. The shape keeps stable ID review-node.", [
|
|
123
125
|
rect("owner", 5, 5, 965, 390), text("owner-label", "Diagram · review-flow", 35, 53, 27, muted),
|
|
124
126
|
rect("draft", 55, 158, 220, 120), rect("review-node", 385, 158, 220, 120, blue), rect("present", 715, 158, 220, 120),
|
|
@@ -127,37 +129,37 @@ slides.push(slide("editing", "Keep the detail editable", "What remains editable
|
|
|
127
129
|
...[[380,153],[600,153],[380,273],[600,273]].map(([x,y], i) => rect(`handle-${i}`,x,y,10,10,blue)),
|
|
128
130
|
text("stable-id", "Selected element: review-node", 36, 450, 32, blue),
|
|
129
131
|
]),
|
|
130
|
-
copy("editing-actions-title", 1210, 343, 580, ["
|
|
131
|
-
copy("editing-actions", 1210, 440, 580, ["
|
|
132
|
-
copy("editing-note", 112,
|
|
132
|
+
copy("editing-actions-title", 1210, 343, 580, ["Ask for a targeted revision"], 35, ink, 500),
|
|
133
|
+
copy("editing-actions", 1210, 440, 580, ["Select a component or element.", "Add a Revision note in Notes.", "Choose Build it to submit.", "Agent revises the same file."], 31, muted),
|
|
134
|
+
copy("editing-note", 112, 852, 1696, ["Notes and Build it are available in the agent-opened editor, not the browser playground.", "Build it cannot wake an idle agent. Use Copy prompt to resume it."], 32, ink),
|
|
133
135
|
]));
|
|
134
136
|
|
|
135
|
-
slides.push(slide("today", "
|
|
136
|
-
...heading("today", "
|
|
137
|
-
copy("available-heading", 112, 350, 790, ["
|
|
138
|
-
copy("available-1", 112, 451, 790, ["
|
|
139
|
-
copy("available-2", 112, 605, 790, ["
|
|
140
|
-
copy("available-3", 112, 790, 790, ["
|
|
141
|
-
copy("limits-heading", 1020, 350, 788, ["
|
|
142
|
-
copy("limits-1", 1020, 451, 788, ["
|
|
143
|
-
copy("limits-2", 1020, 605, 788, ["
|
|
144
|
-
copy("limits-3", 1020, 790, 788, ["
|
|
137
|
+
slides.push(slide("today", "Browser or agent editor", "What can I do here, and what needs a coding agent?", [
|
|
138
|
+
...heading("today", "Try the browser. Continue with your agent.", "The same editable format, with different saving and generation paths."),
|
|
139
|
+
copy("available-heading", 112, 350, 790, ["Browser playground"], 42, blue, 500),
|
|
140
|
+
copy("available-1", 112, 451, 790, ["Edit an example or start blank.", "Import editable composition JSON."], 33),
|
|
141
|
+
copy("available-2", 112, 605, 790, ["Saved only in this browser.", "No connected agent or cloud sync."], 33),
|
|
142
|
+
copy("available-3", 112, 790, 790, ["Download JSON to keep your work.", "Export PNG or use Present."], 32, ink),
|
|
143
|
+
copy("limits-heading", 1020, 350, 788, ["With your coding agent"], 42, ink, 500),
|
|
144
|
+
copy("limits-1", 1020, 451, 788, ["The agent creates and revises visuals.", "Its preview saves to a local JSON file."], 33),
|
|
145
|
+
copy("limits-2", 1020, 605, 788, ["Bring downloaded JSON to your agent.", "Continue in its file-backed editor."], 33),
|
|
146
|
+
copy("limits-3", 1020, 790, 788, ["Playground and file copies do not sync.", "Review generated facts and visuals."], 32, ink),
|
|
145
147
|
]));
|
|
146
148
|
|
|
147
149
|
slides.push(slide("start", "Try a real brief", "How do I start?", [
|
|
148
|
-
...heading("start", "
|
|
149
|
-
copy("setup-heading", 112, 350, 710, ["
|
|
150
|
+
...heading("start", "Install the skill. Bring a real brief.", "Ask for a cover, social graphic, chart, diagram or presentation."),
|
|
151
|
+
copy("setup-heading", 112, 350, 710, ["Install the Konpeki skill"], 40, ink, 500),
|
|
150
152
|
copy("repository", 112, 435, 850, ["github.com/vcfgdev/konpeki"], 38, blue, 500),
|
|
151
|
-
copy("commands", 112, 543, 840, ["
|
|
152
|
-
copy("requirements", 112, 696, 780, ["Node.js 24+ ·
|
|
153
|
-
copy("brief-heading", 1020, 350, 788, ["
|
|
154
|
-
copy("brief", 1020, 450, 788, ["“
|
|
155
|
-
copy("next-step", 1020,
|
|
156
|
-
copy("closing", 112, 906, 1696, ["
|
|
153
|
+
copy("commands", 112, 543, 840, ["Use your agent’s skill installer.", "Choose skills/konpeki in the repo."], 34),
|
|
154
|
+
copy("requirements", 112, 696, 780, ["Node.js 24+ · npm", "An agent with file + command access", "A browser for the editable preview"], 32, ink),
|
|
155
|
+
copy("brief-heading", 1020, 350, 788, ["Then give it one sentence"], 40, ink, 500),
|
|
156
|
+
copy("brief", 1020, 450, 788, ["“Use Konpeki to turn these launch", "notes into a product announcement.”"], 35),
|
|
157
|
+
copy("next-step", 1020, 696, 788, ["Need an empty canvas? Ask for init.", "A creation brief selects generate."], 32, blue, 500),
|
|
158
|
+
copy("closing", 112, 906, 1696, ["Attach your materials; the agent opens and checks the editable result. No separate init needed."], 30, muted),
|
|
157
159
|
]));
|
|
158
160
|
|
|
159
|
-
const document: CompositionDocument = { schema: compositionSchema, title: "Introducing Konpeki", authoringMode: "default", theme: { id: "
|
|
161
|
+
const document: CompositionDocument = { schema: compositionSchema, title: "Introducing Konpeki", authoringMode: "default", theme: { id: "precision", mode: "paper" }, slides };
|
|
160
162
|
const result = validateComposition(document);
|
|
161
163
|
if (!result.ok) throw new Error(JSON.stringify(result.issues, null, 2));
|
|
162
|
-
await writeFile(new URL("./composition.json", import.meta.url),
|
|
164
|
+
await writeFile(new URL("./composition.json", import.meta.url), canonicalJSON(result.document) + "\n");
|
|
163
165
|
console.log(`Authored ${slides.length} editable slides.`);
|