@office-kit/pptx-dev 0.5.0 → 0.6.0

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 CHANGED
@@ -75,6 +75,20 @@ CLI. Navigation while Claude is replying does not change that turn's context.
75
75
  Stale input is rejected with a retry message. If your Claude settings or managed
76
76
  policy disable hooks, automatic context attachment is unavailable; enable the
77
77
  preview hook in Claude Code to use it. No project settings file is rewritten.
78
+
79
+ Use **Shift+Enter** to insert a newline in the embedded Claude prompt. On completion,
80
+ the preview checks the actual deck build. Build errors are returned to Claude's
81
+ Stop hook or to the Codex conversation for up to three repair attempts per user
82
+ request. If repair fails, the error stays visible and the last successful preview
83
+ is retained. Stopping the agent cancels further repair attempts.
84
+
85
+ Both agents receive guidance to check text against its actual background, including
86
+ inherited theme colors, and to use native `Text` paragraphs or mapped rows for lists
87
+ and agendas. `Bullets` and `TableOfContents` are not built-in DSL components; define
88
+ or import project helpers if using those names. See the
89
+ [TSX authoring reference](../../skill/references/tsx.md#lists-and-tables-of-contents)
90
+ for a complete agenda example. Contrast guidance still requires visual review;
91
+ a successful build is not a visual accessibility check.
78
92
  Dependency paths are source candidates, not an exact slide-to-file mapping.
79
93
  “This slide” requests a focused patch; requests about another slide or the
80
94
  whole deck can edit other files or shared styling.
package/dist/cli.mjs CHANGED
@@ -308,10 +308,16 @@ byId('chat-provider').onchange();
308
308
  void refreshChat();
309
309
  <\/script><script type="module" src="/terminal.js"><\/script></html>`;
310
310
  //#endregion
311
+ //#region src/authoring-guidance.ts
312
+ /** Shared guidance for agents editing a deck through the preview. */
313
+ const authoringGuidance = `For lists and tables of contents, use Text paragraphs/bullets or mapped Text rows. Bullets and TableOfContents are not built-in DSL components: inspect installed exports, import existing local helpers or define them. Do not use Raw for these layouts.
314
+ Choose foreground and actual background colors together. Explicitly set readable text colors on slides, cards, tables, chart labels and footnotes; check inherited theme styles. Aim for 4.5:1 contrast for normal text and 3:1 for large text (18pt or 14pt bold). Use a solid backing over images or gradients if needed. Inspect rendered slides; compilation alone cannot verify contrast.`;
315
+ //#endregion
311
316
  //#region src/terminal.ts
312
317
  const REQUEST_LIMIT = 128e3;
313
318
  /** A real interactive Claude session, shared across reloads of its owning tab. */
314
- function createTerminal(entry, busy = () => false, base = "") {
319
+ function createTerminal(entry, busy = () => false, base = "", verify = async () => null) {
320
+ let repairs = 0;
315
321
  let process;
316
322
  let owner = "";
317
323
  let output = "";
@@ -352,17 +358,48 @@ function createTerminal(entry, busy = () => false, base = "") {
352
358
  response.writeHead(code, { "Content-Type": "application/json" });
353
359
  response.end(JSON.stringify(value));
354
360
  };
355
- if (request.url === "/terminal/context") {
361
+ if (request.url === "/terminal/context" || request.url === "/terminal/verify") {
356
362
  if (request.method !== "POST" || request.headers.authorization !== `Bearer ${token}`) {
357
363
  json(403, { error: "Invalid hook credentials" });
358
364
  return;
359
365
  }
360
366
  request.resume();
367
+ if (request.url === "/terminal/verify") {
368
+ if (closing || stopping || !process) {
369
+ json(200, {});
370
+ return;
371
+ }
372
+ let error;
373
+ try {
374
+ error = await verify();
375
+ } catch (cause) {
376
+ error = cause instanceof Error ? cause.message : String(cause);
377
+ }
378
+ if (closing || stopping || !process) {
379
+ json(200, {});
380
+ return;
381
+ }
382
+ if (error && repairs < 3) {
383
+ repairs++;
384
+ status = `Repairing preview (${repairs}/3)…`;
385
+ json(200, {
386
+ decision: "block",
387
+ reason: `The actual preview build failed. Repair the source before finishing (attempt ${repairs}/3). Preserve unrelated edits, inspect the installed DSL exports and local components before changing imports. Do not hide the error or remove requested content. No Raw is needed for lists or an agenda: use Text paragraphs/bullets or mapped Text rows. Build error:\n${error.slice(0, 16e3)}`
388
+ });
389
+ } else {
390
+ status = error ? "Preview still has errors after 3 repair attempts." : "Preview build verified";
391
+ json(200, {});
392
+ }
393
+ emit("state", snapshot());
394
+ return;
395
+ }
396
+ repairs = 0;
361
397
  json(200, { hookSpecificOutput: {
362
398
  hookEventName: "UserPromptSubmit",
363
399
  additionalContext: `You are editing the local PowerPoint TSX project at ${resolve(entry)}.
364
400
  Read the project's authoring instructions. Make focused source patches and preserve unrelated work.
365
- The preview rebuilds on save; do not start another dev server.
401
+ The preview rebuilds on save; do not start another dev server. A Stop hook checks the actual build and returns errors for repair.
402
+ ${authoringGuidance}
366
403
  The focused slide is context, NOT a restriction. For "this slide", edit only its relevant source. For other slides or deck-wide requests, locate the relevant source or shared theme.
367
404
  Slide numbers are 1-based. Verify source before editing; dependency paths are candidates, not an exact slide-to-file mapping.
368
405
  Preview context captured with the latest terminal input: ${JSON.stringify(context)}`
@@ -423,12 +460,20 @@ Preview context captured with the latest terminal input: ${JSON.stringify(contex
423
460
  }
424
461
  const pty = await import("node-pty");
425
462
  if (process || closing) throw new Error("Terminal state changed. Try again.");
426
- const settings = { hooks: { UserPromptSubmit: [{ hooks: [{
427
- type: "http",
428
- url: `${origin}${base}/terminal/context`,
429
- headers: { Authorization: `Bearer ${token}` },
430
- timeout: 5
431
- }] }] } };
463
+ const settings = { hooks: {
464
+ Stop: [{ hooks: [{
465
+ type: "http",
466
+ url: `${origin}${base}/terminal/verify`,
467
+ headers: { Authorization: `Bearer ${token}` },
468
+ timeout: 40
469
+ }] }],
470
+ UserPromptSubmit: [{ hooks: [{
471
+ type: "http",
472
+ url: `${origin}${base}/terminal/context`,
473
+ headers: { Authorization: `Bearer ${token}` },
474
+ timeout: 5
475
+ }] }]
476
+ } };
432
477
  const env = { ...globalThis.process.env };
433
478
  delete env.CLAUDECODE;
434
479
  process = pty.spawn("claude", ["--settings", JSON.stringify(settings)], {
@@ -495,7 +540,7 @@ const markdown = new MarkdownIt({
495
540
  });
496
541
  markdown.renderer.rules.image = (tokens, index) => markdown.utils.escapeHtml(tokens[index]?.content ?? "");
497
542
  /** One shared conversation and one editing process per dev server. */
498
- function createChat(entry, notify, busy = () => false) {
543
+ function createChat(entry, notify, busy = () => false, verify = async () => null) {
499
544
  let messages = [];
500
545
  let provider = "codex";
501
546
  let child;
@@ -517,6 +562,10 @@ function createChat(entry, notify, busy = () => false) {
517
562
  stopping = true;
518
563
  status = "Stopping…";
519
564
  const processToStop = child;
565
+ if (processToStop.exitCode !== null || processToStop.signalCode !== null) {
566
+ notify();
567
+ return;
568
+ }
520
569
  const kill = (signal) => {
521
570
  try {
522
571
  if (process.platform !== "win32" && processToStop.pid) process.kill(-processToStop.pid, signal);
@@ -527,7 +576,7 @@ function createChat(entry, notify, busy = () => false) {
527
576
  killTimer = setTimeout(() => kill("SIGKILL"), 2e3);
528
577
  notify();
529
578
  }
530
- function start(text, selected, context) {
579
+ function start(text, selected, context, repairs = 0) {
531
580
  provider = selected;
532
581
  const user = {
533
582
  role: "user",
@@ -541,6 +590,7 @@ function createChat(entry, notify, busy = () => false) {
541
590
  const prompt = `You are editing a local PowerPoint TSX project in ${dirname(resolve(entry))}.
542
591
  Read the project's authoring instructions. Make focused patches to the TSX source, preserving unrelated work.
543
592
  The preview rebuilds automatically on save; do not start another dev server.
593
+ ${authoringGuidance}
544
594
  The attached focus is context, NOT a restriction: use it for references such as "this slide". For other slides or deck-wide requests, find the relevant source or shared theme instead. Do not rewrite the whole deck for a local edit.
545
595
  Slide numbers are 1-based and refer to the last successful preview at send time. Source may have changed since then; verify it before editing. Files are dependency candidates, not an exact slide-to-source mapping.
546
596
  Respond in the user's language, briefly describing edits or answering their question. If permissions prevent an action, explain that instead of claiming success.
@@ -616,10 +666,31 @@ Current request and preview context (JSON): ${JSON.stringify(user)}`;
616
666
  running.on("error", (error) => {
617
667
  failure = `Could not run ${selected}. Install it and log in from your terminal first. ${error.message}`;
618
668
  });
619
- running.on("close", (code) => {
669
+ running.on("close", async (code) => {
620
670
  if (buffer.trim()) consume(buffer);
621
671
  clearTimeout(killTimer);
622
672
  status = stopping ? "Stopped. Saved edits remain in the preview." : failure || (code !== 0 ? stderr || `${selected} exited with code ${code}` : !sawResult ? "CLI exited without a completed result." : "Done");
673
+ if (status === "Done") {
674
+ status = "Verifying preview…";
675
+ notify();
676
+ let buildError;
677
+ try {
678
+ buildError = await verify();
679
+ } catch (cause) {
680
+ buildError = cause instanceof Error ? cause.message : String(cause);
681
+ }
682
+ if (!stopping && buildError && repairs < 3) {
683
+ child = void 0;
684
+ start(`The actual preview build failed. Repair the source and preserve unrelated work (attempt ${repairs + 1}/3). Do not remove requested content or hide the error.\n${buildError.slice(0, 16e3)}`, selected, {
685
+ ...context,
686
+ buildError
687
+ }, repairs + 1);
688
+ await completion;
689
+ done();
690
+ return;
691
+ }
692
+ status = stopping ? "Stopped. Saved edits remain in the preview." : buildError ? "Preview still has errors after 3 repair attempts.\n" + buildError.slice(0, 16e3) : "Done";
693
+ }
623
694
  if (status !== "Done") append(status);
624
695
  child = void 0;
625
696
  notify();
@@ -718,8 +789,8 @@ async function serveDeck(entry, port = 4173) {
718
789
  if (!result) {
719
790
  const chat = createChat(entry, () => {
720
791
  for (const client of clients) client.write("data: chat\n\n");
721
- }, () => terminal.isRunning());
722
- const terminal = createTerminal(entry, () => chat.isRunning(), id ? "/agents/" + id : "");
792
+ }, () => terminal.isRunning(), verify);
793
+ const terminal = createTerminal(entry, () => chat.isRunning(), id ? "/agents/" + id : "", verify);
723
794
  result = {
724
795
  chat,
725
796
  terminal,
@@ -736,7 +807,7 @@ async function serveDeck(entry, port = 4173) {
736
807
  return;
737
808
  }
738
809
  response.setHeader("Cache-Control", "no-store");
739
- const match = request.url?.match(/^\/agents\/([\w-]{1,64})(\/(?:chat(?:\/(?:stop|reset))?|terminal\/(?:start|input|resize|stop|events|context)|close))?$/);
810
+ const match = request.url?.match(/^\/agents\/([\w-]{1,64})(\/(?:chat(?:\/(?:stop|reset))?|terminal\/(?:start|input|resize|stop|events|context|verify)|close))?$/);
740
811
  if (request.url?.startsWith("/agents/") && !match) {
741
812
  response.writeHead(404).end();
742
813
  return;
@@ -840,6 +911,15 @@ async function serveDeck(entry, port = 4173) {
840
911
  response.end(page);
841
912
  } else response.writeHead(404).end();
842
913
  });
914
+ async function verify() {
915
+ rebuild();
916
+ const deadline = Date.now() + 3e4;
917
+ while (!closed && (building || timer || pending)) {
918
+ if (Date.now() >= deadline) return "Preview verification timed out. Inspect the build before claiming success.";
919
+ await new Promise((done) => setTimeout(done, 50));
920
+ }
921
+ return closed ? "Preview server closed." : error;
922
+ }
843
923
  async function rebuild() {
844
924
  if (closed) return;
845
925
  if (building) {