@open-slide/core 0.0.2 → 0.0.3

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/bin.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import './dist/cli/bin.js';
@@ -1,4 +1,4 @@
1
- import { createViteConfig } from "./config-Opp2R1Jf.js";
1
+ import { createViteConfig } from "./config-g-uy_P5U.js";
2
2
  import { build as build$1 } from "vite";
3
3
 
4
4
  //#region src/cli/build.ts
package/dist/cli/bin.js CHANGED
@@ -4,11 +4,11 @@ import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
 
6
6
  //#region src/cli/run.ts
7
- const HELP = `open-slide — author decks, we handle the Vite/React stack
7
+ const HELP = `open-slide — author slides, we handle the Vite/React stack
8
8
 
9
9
  Usage:
10
10
  open-slide dev Start dev server
11
- open-slide build Build a static deck site
11
+ open-slide build Build a static site
12
12
  open-slide preview Preview the production build
13
13
  open-slide --help Show this message
14
14
  open-slide --version Print version
@@ -30,17 +30,17 @@ async function run(argv) {
30
30
  return;
31
31
  }
32
32
  if (cmd === "dev") {
33
- const { dev } = await import("../dev-0SG0ArzD.js");
33
+ const { dev } = await import("../dev-CFmlBbLh.js");
34
34
  await dev();
35
35
  return;
36
36
  }
37
37
  if (cmd === "build") {
38
- const { build } = await import("../build-DJGuOT6x.js");
38
+ const { build } = await import("../build-Cav2jYyI.js");
39
39
  await build();
40
40
  return;
41
41
  }
42
42
  if (cmd === "preview") {
43
- const { preview } = await import("../preview-61Aawrlg.js");
43
+ const { preview } = await import("../preview-CotwHU_d.js");
44
44
  await preview();
45
45
  return;
46
46
  }
@@ -9,7 +9,7 @@ import fg from "fast-glob";
9
9
 
10
10
  //#region src/vite/comments-plugin.ts
11
11
  const MARKER_RE = /\{\/\*\s*@slide-comment\s+id="(c-[a-f0-9]+)"\s+ts="([^"]+)"\s+text="([A-Za-z0-9_-]+={0,2})"\s*\*\/\}/g;
12
- const DECK_ID_RE = /^[a-z0-9_-]+$/i;
12
+ const SLIDE_ID_RE$1 = /^[a-z0-9_-]+$/i;
13
13
  function b64urlEncode(s) {
14
14
  return Buffer.from(s, "utf8").toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
15
15
  }
@@ -17,7 +17,7 @@ function b64urlDecode(s) {
17
17
  const pad = s.length % 4 === 0 ? "" : "=".repeat(4 - s.length % 4);
18
18
  return Buffer.from(s.replace(/-/g, "+").replace(/_/g, "/") + pad, "base64").toString("utf8");
19
19
  }
20
- async function readBody(req) {
20
+ async function readBody$1(req) {
21
21
  return await new Promise((resolve, reject) => {
22
22
  const chunks = [];
23
23
  req.on("data", (c) => chunks.push(c));
@@ -33,15 +33,15 @@ async function readBody(req) {
33
33
  req.on("error", reject);
34
34
  });
35
35
  }
36
- function json(res, status, body) {
36
+ function json$1(res, status, body) {
37
37
  res.statusCode = status;
38
38
  res.setHeader("content-type", "application/json");
39
39
  res.end(JSON.stringify(body));
40
40
  }
41
- function resolveSlidePath(userCwd, slidesDir, deckId) {
42
- if (!DECK_ID_RE.test(deckId)) return null;
41
+ function resolveSlidePath(userCwd, slidesDir, slideId) {
42
+ if (!SLIDE_ID_RE$1.test(slideId)) return null;
43
43
  const slidesRoot = path.resolve(userCwd, slidesDir);
44
- const full = path.resolve(slidesRoot, deckId, "index.tsx");
44
+ const full = path.resolve(slidesRoot, slideId, "index.tsx");
45
45
  if (!full.startsWith(slidesRoot + path.sep)) return null;
46
46
  return full;
47
47
  }
@@ -107,33 +107,33 @@ function commentsPlugin(opts) {
107
107
  const method = req.method ?? "GET";
108
108
  try {
109
109
  if (method === "GET" && url.pathname === "/") {
110
- const deckId = url.searchParams.get("deckId") ?? "";
111
- const file = resolveSlidePath(userCwd, slidesDir, deckId);
112
- if (!file) return json(res, 400, { error: "invalid deckId" });
110
+ const slideId = url.searchParams.get("slideId") ?? "";
111
+ const file = resolveSlidePath(userCwd, slidesDir, slideId);
112
+ if (!file) return json$1(res, 400, { error: "invalid slideId" });
113
113
  let source;
114
114
  try {
115
115
  source = await fs.readFile(file, "utf8");
116
116
  } catch {
117
- return json(res, 404, { error: "deck not found" });
117
+ return json$1(res, 404, { error: "slide not found" });
118
118
  }
119
- return json(res, 200, { comments: parseMarkers(source) });
119
+ return json$1(res, 200, { comments: parseMarkers(source) });
120
120
  }
121
121
  if (method === "POST" && url.pathname === "/add") {
122
- const body = await readBody(req);
123
- const deckId = body.deckId ?? "";
124
- const file = resolveSlidePath(userCwd, slidesDir, deckId);
125
- if (!file) return json(res, 400, { error: "invalid deckId" });
126
- if (!body.line || body.line < 1) return json(res, 400, { error: "invalid line" });
127
- if (!body.text || typeof body.text !== "string") return json(res, 400, { error: "missing text" });
122
+ const body = await readBody$1(req);
123
+ const slideId = body.slideId ?? "";
124
+ const file = resolveSlidePath(userCwd, slidesDir, slideId);
125
+ if (!file) return json$1(res, 400, { error: "invalid slideId" });
126
+ if (!body.line || body.line < 1) return json$1(res, 400, { error: "invalid line" });
127
+ if (!body.text || typeof body.text !== "string") return json$1(res, 400, { error: "missing text" });
128
128
  let source;
129
129
  try {
130
130
  source = await fs.readFile(file, "utf8");
131
131
  } catch {
132
- return json(res, 404, { error: "deck not found" });
132
+ return json$1(res, 404, { error: "slide not found" });
133
133
  }
134
134
  const lines = source.split("\n");
135
135
  const idx = findSafeInsertLine(lines, body.line, body.column);
136
- if (idx === null) return json(res, 422, { error: `could not find a safe JSX boundary near line ${body.line}. Try clicking a different element.` });
136
+ if (idx === null) return json$1(res, 422, { error: `could not find a safe JSX boundary near line ${body.line}. Try clicking a different element.` });
137
137
  const indent = lines[idx].match(/^\s*/)?.[0] ?? "";
138
138
  const id = newId();
139
139
  const ts = new Date().toISOString();
@@ -144,31 +144,205 @@ function commentsPlugin(opts) {
144
144
  const marker = `${indent}{/* @slide-comment id="${id}" ts="${ts}" text="${payload}" */}`;
145
145
  lines.splice(idx, 0, marker);
146
146
  await fs.writeFile(file, lines.join("\n"), "utf8");
147
- return json(res, 200, {
147
+ return json$1(res, 200, {
148
148
  id,
149
149
  line: idx + 1
150
150
  });
151
151
  }
152
152
  if (method === "DELETE" && url.pathname.startsWith("/")) {
153
153
  const id = url.pathname.slice(1);
154
- if (!/^c-[a-f0-9]+$/.test(id)) return json(res, 400, { error: "invalid id" });
155
- const deckId = url.searchParams.get("deckId") ?? "";
156
- const file = resolveSlidePath(userCwd, slidesDir, deckId);
157
- if (!file) return json(res, 400, { error: "invalid deckId" });
154
+ if (!/^c-[a-f0-9]+$/.test(id)) return json$1(res, 400, { error: "invalid id" });
155
+ const slideId = url.searchParams.get("slideId") ?? "";
156
+ const file = resolveSlidePath(userCwd, slidesDir, slideId);
157
+ if (!file) return json$1(res, 400, { error: "invalid slideId" });
158
158
  let source;
159
159
  try {
160
160
  source = await fs.readFile(file, "utf8");
161
161
  } catch {
162
- return json(res, 404, { error: "deck not found" });
162
+ return json$1(res, 404, { error: "slide not found" });
163
163
  }
164
164
  const lines = source.split("\n");
165
165
  const idRe = new RegExp(`\\{\\/\\*\\s*@slide-comment\\s+id="${id}"\\s+ts="[^"]+"\\s+text="[A-Za-z0-9_\\-]+={0,2}"\\s*\\*\\/\\}`);
166
166
  const hit = lines.findIndex((l) => idRe.test(l));
167
- if (hit === -1) return json(res, 404, { error: "marker not found" });
167
+ if (hit === -1) return json$1(res, 404, { error: "marker not found" });
168
168
  lines.splice(hit, 1);
169
169
  await fs.writeFile(file, lines.join("\n"), "utf8");
170
+ return json$1(res, 200, { ok: true });
171
+ }
172
+ next();
173
+ } catch (err) {
174
+ json$1(res, 500, { error: String(err.message ?? err) });
175
+ }
176
+ });
177
+ }
178
+ };
179
+ }
180
+
181
+ //#endregion
182
+ //#region src/vite/folders-plugin.ts
183
+ const FOLDER_ID_RE = /^f-[a-f0-9]{8}$/;
184
+ const SLIDE_ID_RE = /^[a-z0-9_-]+$/i;
185
+ const COLOR_RE = /^#[0-9a-fA-F]{6}$/;
186
+ async function readBody(req) {
187
+ return await new Promise((resolve, reject) => {
188
+ const chunks = [];
189
+ req.on("data", (c) => chunks.push(c));
190
+ req.on("end", () => {
191
+ const raw = Buffer.concat(chunks).toString("utf8");
192
+ if (!raw) return resolve({});
193
+ try {
194
+ resolve(JSON.parse(raw));
195
+ } catch (e) {
196
+ reject(e);
197
+ }
198
+ });
199
+ req.on("error", reject);
200
+ });
201
+ }
202
+ function json(res, status, body) {
203
+ res.statusCode = status;
204
+ res.setHeader("content-type", "application/json");
205
+ res.end(JSON.stringify(body));
206
+ }
207
+ function emptyManifest() {
208
+ return {
209
+ folders: [],
210
+ assignments: {}
211
+ };
212
+ }
213
+ async function readManifest(file) {
214
+ try {
215
+ const raw = await fs.readFile(file, "utf8");
216
+ const parsed = JSON.parse(raw);
217
+ return {
218
+ folders: Array.isArray(parsed.folders) ? parsed.folders : [],
219
+ assignments: parsed.assignments && typeof parsed.assignments === "object" ? parsed.assignments : {}
220
+ };
221
+ } catch (err) {
222
+ if (err.code === "ENOENT") return emptyManifest();
223
+ throw err;
224
+ }
225
+ }
226
+ async function writeManifest(file, manifest) {
227
+ await fs.mkdir(path.dirname(file), { recursive: true });
228
+ await fs.writeFile(file, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
229
+ }
230
+ function newFolderId() {
231
+ return `f-${randomUUID().replace(/-/g, "").slice(0, 8)}`;
232
+ }
233
+ function validateName(v) {
234
+ if (typeof v !== "string") return null;
235
+ const trimmed = v.trim();
236
+ if (trimmed.length < 1 || trimmed.length > 40) return null;
237
+ return trimmed;
238
+ }
239
+ function validateIcon(v) {
240
+ if (!v || typeof v !== "object") return null;
241
+ const icon = v;
242
+ if (icon.type === "emoji") {
243
+ if (typeof icon.value !== "string") return null;
244
+ if (icon.value.length < 1 || icon.value.length > 8) return null;
245
+ return {
246
+ type: "emoji",
247
+ value: icon.value
248
+ };
249
+ }
250
+ if (icon.type === "color") {
251
+ if (typeof icon.value !== "string" || !COLOR_RE.test(icon.value)) return null;
252
+ return {
253
+ type: "color",
254
+ value: icon.value
255
+ };
256
+ }
257
+ return null;
258
+ }
259
+ function foldersPlugin(opts) {
260
+ const userCwd = opts.userCwd;
261
+ const slidesDir = opts.slidesDir ?? "slides";
262
+ const slidesRoot = path.resolve(userCwd, slidesDir);
263
+ const manifestPath = path.join(slidesRoot, ".folders.json");
264
+ return {
265
+ name: "open-slide:folders",
266
+ apply: "serve",
267
+ configureServer(server) {
268
+ server.watcher.add(manifestPath);
269
+ server.watcher.on("change", (p) => {
270
+ if (p === manifestPath) server.ws.send({
271
+ type: "custom",
272
+ event: "open-slide:folders-changed"
273
+ });
274
+ });
275
+ server.middlewares.use("/__folders", async (req, res, next) => {
276
+ const url = new URL(req.url ?? "/", "http://local");
277
+ const method = req.method ?? "GET";
278
+ try {
279
+ if (method === "GET" && url.pathname === "/") {
280
+ const manifest = await readManifest(manifestPath);
281
+ return json(res, 200, manifest);
282
+ }
283
+ if (method === "POST" && url.pathname === "/") {
284
+ const body = await readBody(req);
285
+ const name = validateName(body.name);
286
+ if (!name) return json(res, 400, { error: "invalid name" });
287
+ const icon = validateIcon(body.icon);
288
+ if (!icon) return json(res, 400, { error: "invalid icon" });
289
+ const manifest = await readManifest(manifestPath);
290
+ const folder = {
291
+ id: newFolderId(),
292
+ name,
293
+ icon
294
+ };
295
+ manifest.folders.push(folder);
296
+ await writeManifest(manifestPath, manifest);
297
+ return json(res, 200, folder);
298
+ }
299
+ if (method === "PUT" && url.pathname === "/assign") {
300
+ const body = await readBody(req);
301
+ if (typeof body.slideId !== "string" || !SLIDE_ID_RE.test(body.slideId)) return json(res, 400, { error: "invalid slideId" });
302
+ const slideId = body.slideId;
303
+ let folderId;
304
+ if (body.folderId === null) folderId = null;
305
+ else if (typeof body.folderId === "string" && FOLDER_ID_RE.test(body.folderId)) folderId = body.folderId;
306
+ else return json(res, 400, { error: "invalid folderId" });
307
+ const manifest = await readManifest(manifestPath);
308
+ if (folderId && !manifest.folders.some((f) => f.id === folderId)) return json(res, 404, { error: "folder not found" });
309
+ if (folderId === null) delete manifest.assignments[slideId];
310
+ else manifest.assignments[slideId] = folderId;
311
+ await writeManifest(manifestPath, manifest);
170
312
  return json(res, 200, { ok: true });
171
313
  }
314
+ const idMatch = url.pathname.match(/^\/([^/]+)$/);
315
+ if (idMatch) {
316
+ const id = idMatch[1];
317
+ if (!FOLDER_ID_RE.test(id)) return json(res, 400, { error: "invalid id" });
318
+ if (method === "PATCH") {
319
+ const body = await readBody(req);
320
+ const manifest = await readManifest(manifestPath);
321
+ const folder = manifest.folders.find((f) => f.id === id);
322
+ if (!folder) return json(res, 404, { error: "folder not found" });
323
+ if (body.name !== void 0) {
324
+ const name = validateName(body.name);
325
+ if (!name) return json(res, 400, { error: "invalid name" });
326
+ folder.name = name;
327
+ }
328
+ if (body.icon !== void 0) {
329
+ const icon = validateIcon(body.icon);
330
+ if (!icon) return json(res, 400, { error: "invalid icon" });
331
+ folder.icon = icon;
332
+ }
333
+ await writeManifest(manifestPath, manifest);
334
+ return json(res, 200, folder);
335
+ }
336
+ if (method === "DELETE") {
337
+ const manifest = await readManifest(manifestPath);
338
+ const before = manifest.folders.length;
339
+ manifest.folders = manifest.folders.filter((f) => f.id !== id);
340
+ if (manifest.folders.length === before) return json(res, 404, { error: "folder not found" });
341
+ for (const [slideId, folderId] of Object.entries(manifest.assignments)) if (folderId === id) delete manifest.assignments[slideId];
342
+ await writeManifest(manifestPath, manifest);
343
+ return json(res, 200, { ok: true });
344
+ }
345
+ }
172
346
  next();
173
347
  } catch (err) {
174
348
  json(res, 500, { error: String(err.message ?? err) });
@@ -180,12 +354,12 @@ function commentsPlugin(opts) {
180
354
 
181
355
  //#endregion
182
356
  //#region src/vite/open-slide-plugin.ts
183
- const DECKS_VMOD = "virtual:open-slide/decks";
357
+ const SLIDES_VMOD = "virtual:open-slide/slides";
184
358
  const CONFIG_VMOD = "virtual:open-slide/config";
185
359
  function resolved(id) {
186
360
  return `\0${id}`;
187
361
  }
188
- async function findDecks(userCwd, slidesDir) {
362
+ async function findSlides(userCwd, slidesDir) {
189
363
  const abs = path.resolve(userCwd, slidesDir);
190
364
  if (!existsSync(abs)) return [];
191
365
  const hits = await fg("*/index.{tsx,jsx,ts,js}", {
@@ -199,7 +373,7 @@ function toId(absFile, slidesRoot) {
199
373
  const rel = path.relative(slidesRoot, absFile);
200
374
  return rel.split(path.sep)[0];
201
375
  }
202
- function generateDecksModule(files, slidesRoot, isDev) {
376
+ function generateSlidesModule(files, slidesRoot, isDev) {
203
377
  const entries = files.map((abs) => {
204
378
  const id = toId(abs, slidesRoot);
205
379
  const importPath = isDev ? `/@fs${abs}` : abs;
@@ -210,13 +384,13 @@ function generateDecksModule(files, slidesRoot, isDev) {
210
384
  });
211
385
  const ids = JSON.stringify(entries.map((e) => e.id).sort());
212
386
  const cases = entries.map((e) => ` case ${JSON.stringify(e.id)}: return import(${JSON.stringify(e.importPath)});`).join("\n");
213
- return `// virtual:open-slide/decks — generated
214
- export const deckIds = ${ids};
387
+ return `// virtual:open-slide/slides — generated
388
+ export const slideIds = ${ids};
215
389
 
216
- export async function loadDeck(id) {
390
+ export async function loadSlide(id) {
217
391
  switch (id) {
218
392
  ${cases}
219
- default: throw new Error('Deck not found: ' + id);
393
+ default: throw new Error('Slide not found: ' + id);
220
394
  }
221
395
  }
222
396
  `;
@@ -233,21 +407,21 @@ function openSlidePlugin(opts) {
233
407
  return { server: { fs: { allow: [userCwd] } } };
234
408
  },
235
409
  resolveId(id) {
236
- if (id === DECKS_VMOD) return resolved(DECKS_VMOD);
410
+ if (id === SLIDES_VMOD) return resolved(SLIDES_VMOD);
237
411
  if (id === CONFIG_VMOD) return resolved(CONFIG_VMOD);
238
412
  return null;
239
413
  },
240
414
  async load(id) {
241
- if (id === resolved(DECKS_VMOD)) {
242
- const files = await findDecks(userCwd, slidesDir);
243
- return generateDecksModule(files, slidesRoot, isDev);
415
+ if (id === resolved(SLIDES_VMOD)) {
416
+ const files = await findSlides(userCwd, slidesDir);
417
+ return generateSlidesModule(files, slidesRoot, isDev);
244
418
  }
245
419
  if (id === resolved(CONFIG_VMOD)) return `export default ${JSON.stringify(config)};\n`;
246
420
  return null;
247
421
  },
248
422
  configureServer(server) {
249
423
  const reload = () => {
250
- const mod = server.moduleGraph.getModuleById(resolved(DECKS_VMOD));
424
+ const mod = server.moduleGraph.getModuleById(resolved(SLIDES_VMOD));
251
425
  if (mod) server.moduleGraph.invalidateModule(mod);
252
426
  server.ws.send({ type: "full-reload" });
253
427
  };
@@ -302,6 +476,10 @@ async function createViteConfig(opts) {
302
476
  commentsPlugin({
303
477
  userCwd,
304
478
  slidesDir
479
+ }),
480
+ foldersPlugin({
481
+ userCwd,
482
+ slidesDir
305
483
  })
306
484
  ],
307
485
  resolve: { alias: { "@": APP_ROOT } },
@@ -313,7 +491,8 @@ async function createViteConfig(opts) {
313
491
  "lucide-react",
314
492
  "clsx",
315
493
  "tailwind-merge",
316
- "class-variance-authority"
494
+ "class-variance-authority",
495
+ "emoji-picker-react"
317
496
  ]
318
497
  },
319
498
  server: {
@@ -1,4 +1,4 @@
1
- import { createViteConfig } from "./config-Opp2R1Jf.js";
1
+ import { createViteConfig } from "./config-g-uy_P5U.js";
2
2
  import { createServer } from "vite";
3
3
 
4
4
  //#region src/cli/dev.ts
package/dist/index.d.ts CHANGED
@@ -1,17 +1,15 @@
1
1
  import { ComponentType } from "react";
2
2
 
3
3
  //#region src/app/lib/sdk.d.ts
4
- type SlidePage = ComponentType;
5
- type DeckMeta = {
4
+ type Page = ComponentType;
5
+ type SlideMeta = {
6
6
  title?: string;
7
7
  theme?: 'light' | 'dark';
8
8
  };
9
- type DeckModule = {
10
- default: SlidePage[];
11
- meta?: DeckMeta;
9
+ type SlideModule = {
10
+ default: Page[];
11
+ meta?: SlideMeta;
12
12
  };
13
13
  declare const CANVAS_WIDTH = 1920;
14
- declare const CANVAS_HEIGHT = 1080;
15
-
16
- //#endregion
17
- export { CANVAS_HEIGHT, CANVAS_WIDTH, DeckMeta, DeckModule, SlidePage };
14
+ declare const CANVAS_HEIGHT = 1080; //#endregion
15
+ export { CANVAS_HEIGHT, CANVAS_WIDTH, Page, SlideMeta, SlideModule };
@@ -1,4 +1,4 @@
1
- import { createViteConfig } from "./config-Opp2R1Jf.js";
1
+ import { createViteConfig } from "./config-g-uy_P5U.js";
2
2
  import { preview as preview$1 } from "vite";
3
3
 
4
4
  //#region src/cli/preview.ts
@@ -1,3 +1,3 @@
1
- import { createViteConfig } from "../config-Opp2R1Jf.js";
1
+ import { createViteConfig } from "../config-g-uy_P5U.js";
2
2
 
3
3
  export { createViteConfig };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@open-slide/core",
3
- "version": "0.0.2",
4
- "description": "Runtime and CLI for open-slide — write decks in slides/, we handle the rest.",
3
+ "version": "0.0.3",
4
+ "description": "Runtime and CLI for open-slide — write slides in slides/, we handle the rest.",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": {
@@ -14,9 +14,10 @@
14
14
  }
15
15
  },
16
16
  "bin": {
17
- "open-slide": "./dist/cli/bin.js"
17
+ "open-slide": "./bin.js"
18
18
  },
19
19
  "files": [
20
+ "bin.js",
20
21
  "dist",
21
22
  "src/app",
22
23
  "README.md"
@@ -45,6 +46,7 @@
45
46
  "@vitejs/plugin-react": "^4.3.3",
46
47
  "class-variance-authority": "^0.7.1",
47
48
  "clsx": "^2.1.1",
49
+ "emoji-picker-react": "^4.18.0",
48
50
  "fast-glob": "^3.3.2",
49
51
  "lucide-react": "^1.8.0",
50
52
  "radix-ui": "^1.4.3",
package/src/app/App.tsx CHANGED
@@ -1,13 +1,13 @@
1
1
  import { BrowserRouter, Route, Routes } from 'react-router-dom';
2
2
  import { Home } from './routes/Home';
3
- import { Deck } from './routes/Deck';
3
+ import { Slide } from './routes/Slide';
4
4
 
5
5
  export function App() {
6
6
  return (
7
7
  <BrowserRouter>
8
8
  <Routes>
9
9
  <Route path="/" element={<Home />} />
10
- <Route path="/d/:deckId" element={<Deck />} />
10
+ <Route path="/s/:slideId" element={<Slide />} />
11
11
  </Routes>
12
12
  </BrowserRouter>
13
13
  );
@@ -1,9 +1,9 @@
1
1
  import { useEffect, useRef } from 'react';
2
- import type { SlidePage } from '../lib/sdk';
2
+ import type { Page } from '../lib/sdk';
3
3
  import { SlideCanvas } from './SlideCanvas';
4
4
 
5
5
  type Props = {
6
- pages: SlidePage[];
6
+ pages: Page[];
7
7
  index: number;
8
8
  onIndexChange: (index: number) => void;
9
9
  onExit: () => void;
@@ -51,11 +51,11 @@ export function Player({ pages, index, onIndexChange, onExit }: Props) {
51
51
  return () => window.removeEventListener('keydown', onKey);
52
52
  }, [index, pages.length, onIndexChange, onExit]);
53
53
 
54
- const Page = pages[index];
54
+ const PageComp = pages[index];
55
55
 
56
56
  return (
57
57
  <div ref={rootRef} className="flex h-screen w-screen items-center justify-center bg-black">
58
- <SlideCanvas flat>{Page ? <Page /> : null}</SlideCanvas>
58
+ <SlideCanvas flat>{PageComp ? <PageComp /> : null}</SlideCanvas>
59
59
  </div>
60
60
  );
61
61
  }
@@ -1,11 +1,11 @@
1
1
  import { cn } from '@/lib/utils';
2
2
  import { ScrollArea } from '@/components/ui/scroll-area';
3
- import type { SlidePage } from '../lib/sdk';
3
+ import type { Page } from '../lib/sdk';
4
4
  import { SlideCanvas } from './SlideCanvas';
5
5
  import { CANVAS_WIDTH, CANVAS_HEIGHT } from '../lib/sdk';
6
6
 
7
7
  type Props = {
8
- pages: SlidePage[];
8
+ pages: Page[];
9
9
  current: number;
10
10
  onSelect: (index: number) => void;
11
11
  };
@@ -18,13 +18,13 @@ export function ThumbnailRail({ pages, current, onSelect }: Props) {
18
18
  return (
19
19
  <ScrollArea className="h-full border-r bg-card">
20
20
  <aside className="flex flex-col gap-2.5 p-3">
21
- {pages.map((Page, i) => {
21
+ {pages.map((PageComp, i) => {
22
22
  const active = i === current;
23
23
  return (
24
24
  <button
25
25
  key={i}
26
26
  onClick={() => onSelect(i)}
27
- aria-label={`Go to slide ${i + 1}`}
27
+ aria-label={`Go to page ${i + 1}`}
28
28
  aria-current={active ? 'true' : undefined}
29
29
  className={cn(
30
30
  'flex items-center gap-2.5 rounded-lg border-2 border-transparent p-1.5 text-left transition-colors',
@@ -45,7 +45,7 @@ export function ThumbnailRail({ pages, current, onSelect }: Props) {
45
45
  style={{ width: THUMB_WIDTH, height: THUMB_HEIGHT }}
46
46
  >
47
47
  <SlideCanvas scale={THUMB_SCALE} center={false} flat>
48
- <Page />
48
+ <PageComp />
49
49
  </SlideCanvas>
50
50
  </div>
51
51
  </button>
@@ -6,7 +6,7 @@ import { useInspector } from './InspectorProvider';
6
6
  type Highlight = { rect: DOMRect; hit: SlideSourceHit };
7
7
 
8
8
  export function InspectOverlay() {
9
- const { active, deckId, pending, setPending } = useInspector();
9
+ const { active, slideId, pending, setPending } = useInspector();
10
10
  const overlayRef = useRef<HTMLDivElement>(null);
11
11
  const [hover, setHover] = useState<Highlight | null>(null);
12
12
 
@@ -20,7 +20,7 @@ export function InspectOverlay() {
20
20
  if (pending) return;
21
21
  const el = pickElement(e.clientX, e.clientY);
22
22
  if (!el) return setHover(null);
23
- const hit = findSlideSource(el, deckId);
23
+ const hit = findSlideSource(el, slideId);
24
24
  if (!hit) return setHover(null);
25
25
  setHover({ rect: hit.anchor.getBoundingClientRect(), hit });
26
26
  };
@@ -30,7 +30,7 @@ export function InspectOverlay() {
30
30
  if (e.target instanceof Element && e.target.closest('[data-inspector-ui]')) return;
31
31
  const el = pickElement(e.clientX, e.clientY);
32
32
  if (!el) return;
33
- const hit = findSlideSource(el, deckId);
33
+ const hit = findSlideSource(el, slideId);
34
34
  if (!hit) return;
35
35
  e.preventDefault();
36
36
  e.stopPropagation();
@@ -50,7 +50,7 @@ export function InspectOverlay() {
50
50
  window.removeEventListener('pointermove', onMove, true);
51
51
  window.removeEventListener('click', onClick, true);
52
52
  };
53
- }, [active, deckId, pending, setPending]);
53
+ }, [active, slideId, pending, setPending]);
54
54
 
55
55
  if (!active) return null;
56
56
 
@@ -12,7 +12,7 @@ export type PendingTarget = {
12
12
  };
13
13
 
14
14
  type InspectorCtx = {
15
- deckId: string;
15
+ slideId: string;
16
16
  active: boolean;
17
17
  toggle: () => void;
18
18
  comments: SlideComment[];
@@ -32,10 +32,10 @@ export function useInspector(): InspectorCtx {
32
32
  return v;
33
33
  }
34
34
 
35
- export function InspectorProvider({ deckId, children }: { deckId: string; children: ReactNode }) {
35
+ export function InspectorProvider({ slideId, children }: { slideId: string; children: ReactNode }) {
36
36
  const [active, setActive] = useState(false);
37
37
  const [pending, setPending] = useState<PendingTarget | null>(null);
38
- const { comments, error, refetch, add, remove } = useComments(deckId);
38
+ const { comments, error, refetch, add, remove } = useComments(slideId);
39
39
 
40
40
  const toggle = useCallback(() => {
41
41
  setActive((a) => {
@@ -46,7 +46,7 @@ export function InspectorProvider({ deckId, children }: { deckId: string; childr
46
46
 
47
47
  const value = useMemo<InspectorCtx>(
48
48
  () => ({
49
- deckId,
49
+ slideId,
50
50
  active,
51
51
  toggle,
52
52
  comments,
@@ -57,7 +57,7 @@ export function InspectorProvider({ deckId, children }: { deckId: string; childr
57
57
  pending,
58
58
  setPending,
59
59
  }),
60
- [deckId, active, toggle, comments, error, refetch, add, remove, pending],
60
+ [slideId, active, toggle, comments, error, refetch, add, remove, pending],
61
61
  );
62
62
 
63
63
  return <Ctx.Provider value={value}>{children}</Ctx.Provider>;