slidemind 0.3.13 → 0.3.14

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.
Files changed (49) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/AGENTS.md +86 -21
  4. package/README.md +14 -17
  5. package/bin/slidemind.mjs +65 -4969
  6. package/package.json +1 -1
  7. package/skills/create-slides/SKILL.md +113 -74
  8. package/skills/search/SKILL.md +25 -7
  9. package/src/bridge/build-and-push.mjs +108 -15
  10. package/src/bridge/bundle.mjs +69 -0
  11. package/src/bridge/capture.mjs +19 -0
  12. package/src/bridge/client/brands.mjs +622 -0
  13. package/src/bridge/client/engine-routing.mjs +237 -0
  14. package/src/bridge/client/engine-search.mjs +310 -0
  15. package/src/bridge/client/engine-slides.mjs +435 -0
  16. package/src/bridge/client/pane-ops.mjs +335 -0
  17. package/src/bridge/client/presentations.mjs +587 -0
  18. package/src/bridge/client/render.mjs +317 -0
  19. package/src/bridge/client/transport.mjs +695 -0
  20. package/src/bridge/client.mjs +80 -3452
  21. package/src/bridge/config.mjs +1 -1
  22. package/src/bridge/engine-download.mjs +15 -2
  23. package/src/builders/build.mjs +11 -804
  24. package/src/builders/draft-check.mjs +376 -0
  25. package/src/builders/lint.mjs +38 -2
  26. package/src/builders/preview.html +799 -0
  27. package/src/builders/review.mjs +23 -4
  28. package/src/builders/visual-families.mjs +6 -8
  29. package/src/cli/common.mjs +179 -0
  30. package/src/cli/concepts.mjs +310 -0
  31. package/src/cli/connection.mjs +147 -0
  32. package/src/cli/deck.mjs +608 -0
  33. package/src/cli/designs.mjs +113 -0
  34. package/src/cli/diagnose.mjs +141 -0
  35. package/src/cli/download-bundle.mjs +144 -0
  36. package/src/cli/engine-helpers.mjs +429 -0
  37. package/src/cli/engine-results.mjs +320 -0
  38. package/src/cli/help.mjs +201 -0
  39. package/src/cli/install.mjs +257 -0
  40. package/src/cli/read.mjs +120 -0
  41. package/src/cli/ready.mjs +805 -0
  42. package/src/cli/search.mjs +608 -0
  43. package/src/cli/session.mjs +177 -0
  44. package/src/cli/setup.mjs +282 -0
  45. package/src/cli/slide-reuse.mjs +334 -0
  46. package/src/cli/status.mjs +118 -0
  47. package/src/cli/workspace.mjs +302 -0
  48. package/src/installers/codex.mjs +1 -1
  49. package/src/update-check.mjs +231 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slidemind",
3
- "version": "0.3.13",
3
+ "version": "0.3.14",
4
4
  "description": "Connect Claude Code, Codex & opencode to the slidemind PowerPoint add-in — let your coding agent build and edit native, on-brand PowerPoint decks against your active corporate design, from the terminal.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -43,30 +43,29 @@ later stages (above all the review gate) from being skipped. The stages, as they
43
43
  host, so it can run while `ready` executes)
44
44
  3. Plan sign-off — `slidemind plan <deck>` (every from-scratch request — one slide included)
45
45
  4. Concept mockups — `slidemind concepts <deck>`, then immediately inspect every mockup (anything
46
- authored from an approved plan, a single slide included; see *The create flow* below). **The FILES ON DISK are the
47
- truth, not the command output you saw.** Generation takes 1–2 minutes and your harness may
48
- time the command out, showing you only progress lines while the detached process finishes
49
- writing the PNGs — output that ends mid-progress means TIMED OUT, never "no mockups". The
50
- moment the command returns (complete or cut off): run `ls decks/<slug>/concepts/`. PNGs
51
- listed → re-run `slidemind concepts <deck>` once (it returns the full path list instantly for
52
- already-generated mockups) and Read every file. **Claiming the concepts are missing requires
53
- double evidence**: `asset-plan.md` may say "no mockups" ONLY when BOTH the re-run's output
54
- printed zero paths AND `ls decks/<slug>/concepts/` is empty — quote both in the file.
55
- Authoring a concept-seeded deck without having Read the concept PNGs that exist on disk is the
56
- single largest fidelity failure this workflow has; nothing about a truncated stdout excuses it.
46
+ authored from an approved plan, a single slide included; see *The create flow* below). The
47
+ command **always ends on one line, `CONCEPTS: <n>/<planned> <status>`** (`--json` for the
48
+ payload). Generation takes 1–2 minutes; if your harness cut the command off before that line
49
+ appeared, re-run it — mockups already on disk come back as the path list instantly. Only a
50
+ `CONCEPTS: 0/…` line means there are no mockups; a truncated output never does. Authoring a
51
+ concept-seeded deck without having Read the concept PNGs on disk is the single largest fidelity
52
+ failure this workflow has.
57
53
  **User-provided images** go into this command, classified from the user's words: a sketch or
58
54
  mockup to build ("build it like this") → `--concept <file>[@N,M]` (it becomes the mockup for
59
55
  those slides, no generation); a look to emulate ("in this style") → `--style <file>[@N,M]`
60
56
  (leads the generation; repeatable; `@` omitted = all slides). A photo or logo to place on a slide
61
57
  is not a concept input: copy it into `decks/<slug>/assets/` and list it in `asset-plan.md`.
62
- 5. Source assets — write `decks/<slug>/asset-plan.md` from the concepts, then search + download
63
- every item in it (*The create flow* steps 2–3). **This stage is not optional and not mergeable
58
+ 5. Source assets — fill the `decks/<slug>/asset-plan.md` form `slidemind concepts` seeded (regions /
59
+ organisms / connectors / icons per slide, each with its share of the content area), then search + download
60
+ every icon row in it (*The create flow* steps 2–3). **This stage is not optional and not mergeable
64
61
  into authoring**: it is done when every plan item is checked off with a filename or marked
65
62
  `(no result)` after its search ran. A deck with concept mockups and an empty `assets/` beyond
66
63
  the brand chrome means this stage was skipped, not completed.
67
64
  6. Author the slides
68
- 7. Draft check — `slidemind view-draft`
69
- 8. Review gate — `slidemind review`, the moment the deck builds clean
65
+ 7. Draft check — `slidemind view-draft`, until it reports `DRAFT CHECK: no measured defects` AND each
66
+ concept/render pair matches region by region
67
+ 8. Review gate — `slidemind review`, the moment the deck builds clean (it refuses to open while the
68
+ last draft check's measured defects stand)
70
69
  9. Deliver — `slidemind push` or `slidemind build-pptx`, whichever target was locked in stage 2
71
70
 
72
71
  **Changing slides that already exist runs a shorter list.** When the deck is on disk and the
@@ -115,22 +114,28 @@ Two gate invariants you must never break — one per gate, equally binding:
115
114
 
116
115
  ## After a context summarization (compaction recovery)
117
116
 
118
- Long deck runs can exceed the context window mid-task; the harness then replaces the transcript
119
- with a summary. **Everything you only held in chat — viewed concept images, search results, the
120
- asset inventory, workflow rules you read from the bundle — is gone; the files on disk are not.**
121
- When you notice you are running from a summary (or after any interruption/continuation), do NOT
122
- improvise from the summary and do NOT restart finished work. Recover state from disk, in this
123
- order, then resume at the first incomplete stage:
124
-
125
- 1. `decks/<slug>/plan.md` — the approved story.
126
- 2. `decks/<slug>/asset-plan.md` — per-slide organisms + which assets are downloaded vs. missing
127
- (unchecked items still need their search + fetch).
128
- 3. `ls decks/<slug>/slides/ decks/<slug>/assets/ decks/<slug>/concepts/` — what already exists.
129
- 4. Re-read `<bundle>/workflow/create.md` if you are still before or inside authoring, and reopen
130
- the concept PNGs for the slides not yet authored (batches of ≤3) before writing them.
131
-
132
- The stage todo list still binds after a summary: a summary that says the deck is "mostly done" is
133
- never a reason to skip the remaining stages (assets, draft check, review gate).
117
+ Long deck runs exceed the context window mid-task; the harness then replaces the transcript with a
118
+ summary. **Everything you only held in chat — viewed concept images, search results, the asset
119
+ inventory, the workflow rules you read from the bundle, this skill's own steps — is gone; the files
120
+ on disk are not.** When you notice you are running from a summary (or after any interruption or
121
+ continuation), do NOT improvise from the summary and do NOT restart finished work. Run
122
+ `slidemind ready <deck>` first: it re-materializes and re-binds (idempotent) and ends with a
123
+ **`stage:` line read from disk** (plan / concepts / asset plan / slides / draft check / review) and
124
+ a **`next:` line naming the command or file that is due** (`stage` in `--json`). Resume there, after
125
+ re-reading the set that stage works from:
126
+
127
+ - **plan gate** → *Two UX rules* and the `slidemind plan` row below.
128
+ - **concepts / sourcing** → `<bundle>/workflow/create.md` steps 1–2, `asset-plan.md`, every
129
+ `concepts/concept-slide-NN.png` at full detail.
130
+ - **authoring** → `<bundle>/workflow/create.md` + `authoring.md`, `design.md`,
131
+ `corporate-design.css`, the `layouts/<Layout>.jsx` the plan names, `asset-plan.md`, `plan.md`;
132
+ then, per batch of ≤3, the matching concept PNGs immediately before each write.
133
+ - **draft check** → *The create flow* step 4; **review** → step 5 + *Two UX rules*; **deliver** →
134
+ the deliver row. An **edit** to existing slides → `<bundle>/workflow/edit.md` + the cited files.
135
+
136
+ Two rules bind: the stage todo list still binds after a summary — "mostly done" is never a reason
137
+ to skip the remaining stages — and never author, judge, or deliver from a remembered image; reopen
138
+ the PNG from its path. `<bundle>` is `bundleDir` from `slidemind ready <deck> --json`.
134
139
 
135
140
  ## Bootstrap (set yourself up — don't make the user do it)
136
141
 
@@ -148,6 +153,8 @@ binds the deck to it.
148
153
  no local server. Patch a missing CLI yourself; **connecting** needs the user once (they hold the
149
154
  add-in's identity):
150
155
 
156
+ - **Flags you are unsure of:** `slidemind <command> --help` prints that command's usage block; the
157
+ deck commands take `--json`.
151
158
  - **CLI missing** (`slidemind` not on `PATH`): `npm install -g slidemind` — that alone is the fix.
152
159
  Only run `slidemind init --for <claude|codex|opencode|all>` ((re)deploys this skill) when this
153
160
  skill did **not** come from a plugin: if the `SKILL.md` you are reading sits under a harness
@@ -302,7 +309,7 @@ next to the deck (`paneSuspended` + `fallbackFile` in the result) — nothing is
302
309
  once a window exists. Reserve the full "open PowerPoint…" instruction for when a command actually
303
310
  reports it as the blocker.
304
311
 
305
- `slidemind build`, `review`, and `view-draft` run offline, but only against a **previously
312
+ `view-draft` and `review` run offline, but only against a **previously
306
313
  materialized** bundle — so `ready` (or a prior `ready`) must have succeeded at least once.
307
314
  `build-pptx` additionally needs the backend reachable (the OOXML assembly is server-side), but no
308
315
  PowerPoint window for a bound deck — so *"a `.pptx` file to send around"* is deliverable end-to-end
@@ -319,7 +326,7 @@ with PowerPoint closed.
319
326
  insert, `insert-slide`, `read <presId>`) can't reach the add-in (a connection blocker, a pane that
320
327
  never wakes, the *click the slidemind pane* line with no effect), **stop the workflow** — don't
321
328
  skip the step, work around it, or retry silently. Ask the user to go back into the PowerPoint tab
322
- of presentation **<title/fileName>** (name it from the command output or `slidemind presentations`
329
+ of presentation **<title/fileName>** (name it from the command output or `slidemind ready --diagnose`
323
330
  — never a raw id), then re-run the failed command once they have. Everything before delivery keeps
324
331
  running meanwhile — never park the whole task on a window it doesn't need.
325
332
 
@@ -497,16 +504,24 @@ brand/layout/composition detail until create/edit.**
497
504
 
498
505
  ## The create flow
499
506
 
507
+ Every deck command (`view-draft`, `review`, `concepts`, `build-pptx`, `push`) re-syncs the deck's
508
+ bound design before it runs, so a corporate design that changes mid-session is picked up by itself —
509
+ it prints `design bundle updated` when that happens, and you re-read the bundle files it names. You
510
+ never have to re-run `ready` to get a fresh design.
511
+
500
512
  `slidemind ready` already resolved the design source and materialized its bundle; the deck was bound
501
513
  to it by `slidemind ready <deck>` (continuing) or by `slidemind new <slug>`'s auto-bind (new work).
502
514
  From here:
503
515
 
504
- 1. **Load create context, then inspect the concepts.** Read the create-stage references together —
505
- `workflow/create.md` (the procedure), `workflow/composition.md` (the composition baseline),
506
- `workflow/layouts.md` (palette + working area), and `workflow/authoring.md` (the substrate rules)
507
- — plus the deck's `design.md` and the specific `layouts/*.jsx` the plan uses. (That is the read
508
- set `workflow/create.md` → Context loading prescribes; follow it as written — `workflow/flow.md`
509
- you already read after `ready`.) The approved `plan.md` is a **content contract, not a rendering
516
+ 1. **Load create context, then inspect the concepts.** Read `workflow/create.md` (the procedure),
517
+ `workflow/authoring.md` (the portability contract — **required BEFORE the first slide write**,
518
+ never deferred to a lint repair: it names what CSS ports, the slide file shape, transforms,
519
+ rules and dividers, painted nesting, decorative layers, the Arrow contract), the deck's
520
+ `design.md`, `corporate-design.css`, and the specific `layouts/*.jsx` the plan uses. Nothing
521
+ else up front: `workflow/composition.md` and `workflow/layouts.md` are for slides WITHOUT a
522
+ mockup and for `[STYLE]` questions — open them when a lint message or a missing mockup sends
523
+ you there, not before (the read set is what `workflow/create.md` prescribes; `workflow/flow.md`
524
+ you already read after `ready`). The approved `plan.md` is a **content contract, not a rendering
510
525
  spec** — you drew its storyboards yourself, but approval covered the story and content, not the
511
526
  sketch's geometry; how to interpret the `storyboard-canvas` blocks at author time (design
512
527
  hypothesis: reinterpret freely, never transcribe coordinates, block geometry, or block labels)
@@ -519,22 +534,24 @@ From here:
519
534
  search.** First list the directory and keep the literal repo-relative mapping in the task context:
520
535
  concept `NN` maps to `decks/<slug>/concepts/concept-slide-NN.png` and later to the `NN-*.jsx`
521
536
  slide. These workspace files are the durable source of truth across turns and compaction — never
522
- rely on an earlier rendered attachment. Inventory what each mockup requires — imagery, distinct
523
- pictograms, factual/content verification, annotations, and other visible organisms — and
524
- **persist the inventory to disk the moment you finish it: write `decks/<slug>/asset-plan.md`**,
525
- one section per slide:
526
-
527
- ```
528
- ## Slide NN — <title>
529
- organisms: <the mockup's regions, hero figure, badges, satellites, chips, connectors>
530
- - [ ] icon: <what it depicts> — query: "<search query>"
531
- - [ ] image: <what it shows> — query: "<search query>"
532
- ```
537
+ rely on an earlier rendered attachment. **The mockup is painted onto the layout's own chrome:
538
+ the title, section label, footer and page marks you see are chrome that travels through the
539
+ layout's props; everything else is the body and belongs in the layout's `[CONTENT]` slot**
540
+ (its geometry is in the layout's `PLACEHOLDERS` metadata). Note each region's share of that
541
+ area in percentages as you inspect. `slidemind concepts` seeded
542
+ `decks/<slug>/asset-plan.md` as a form with one section per slide (`regions:` / `organisms:` /
543
+ `connectors:` / `icons:`); **fill every row the moment you finish inspecting a
544
+ mockup, before any slide is written** — one bullet per region, per organism, per arrow or line
545
+ (`from → to, straight|elbow|curved, arrow end|both|none, solid|dashed`), per icon (subject,
546
+ prominence, one engine query). The draft check COUNTS these rows: connectors listed but not drawn,
547
+ an inventory left empty, and fetched assets no slide references each come back as an
548
+ `[inventory:warn]`. (If the form is missing — an older add-in — write it in that shape yourself.)
533
549
 
534
550
  Chat context is evicted when the conversation is summarized; this file is not. Every icon
535
551
  decision you make only in your head is lost at the next compaction — the file is the contract
536
- step 2 executes and step 3 authors from. Check items off (`- [x] … — assets/engine-<id>-<n>.svg`)
537
- as downloads land, so the file always shows what is still missing. This first pass is
552
+ step 2 executes and step 3 authors from. Append the fetched filename to each icon row
553
+ (`… — assets/engine-<id>-<n>.svg`) as downloads land, so the file always shows what is still
554
+ missing. This first pass is
538
555
  deliberately deck-wide and search-oriented; it is not the fresh authoring read required in
539
556
  step 3. Rebuild each mockup under your constraints
540
557
  — the rebuild rule is owned by
@@ -559,12 +576,11 @@ From here:
559
576
  is discovery, not sourcing: this step is done only when every `asset-plan.md` item is checked
560
577
  off with a downloaded filename or marked `(no result)` after its search actually ran. Four
561
578
  mechanics that waste rounds when gotten wrong:
562
- - **Icon results have no preview.** `slidemind view-slide` serves slide-library results only —
563
- on an image/icon search it answers "no image returned" every time, so never call it there and
564
- never retry it. Pick from the `search` output's titles/scores; a download is cheap and
565
- `download-result` prints each asset's size + recolor info — download first, judge from the
566
- saved file, re-search once if it was wrong. At most two search rounds per need; two failed
567
- downloads = mark it `(no result)` and move on.
579
+ - **Icon results have no preview — the download is the preview.** Pick from the `search`
580
+ output's titles/scores, `download-result` the picks (it prints each asset's size + recolor
581
+ info), judge from the saved file, re-search once if it was wrong. At most two search rounds
582
+ per need; two failed downloads = mark it `(no result)` and move on. (`view-slide` is for
583
+ slide-library results only.)
568
584
  - **Batch the downloads:** one `download-result --search <id> --pick <n,n,…>` per search id —
569
585
  never one call per icon.
570
586
  - **Quote hex colors:** `--icon-color "#<hex>"` (a value resolved from a corporate-design.css token) — an unquoted `#` makes the shell drop the
@@ -598,23 +614,46 @@ From here:
598
614
  4. **Look at your own draft first (you, the agent).** This is the **draft check** in
599
615
  `workflow/build-and-export.md` — your pre-check, *before* any human sees the deck. Two rules
600
616
  moved early on purpose — do them NOW, not at the review gate when the run is long and attention
601
- is spent: first run `slidemind build <deck>` once and fix every `[lint:warn]` it prints (a
602
- mechanical pass on files you just wrote); and after any fix round below, one more `slidemind
617
+ is spent: `slidemind view-draft` lints every slide before it renders, so fix every `[lint:warn]`
618
+ it prints (a mechanical pass on files you just wrote — `--fix` applies the deterministic ones);
619
+ and after any fix round below, one more `slidemind
603
620
  view-draft` over the changed slides is mandatory — the verification must appear in your command
604
621
  sequence, not only in prose. Run `slidemind view-draft <deck>`: it renders each authored `slides/*.jsx` to a per-slide PNG and hands the
605
- images back to you — **nothing is pushed, no `.pptx` is written, no review gate opens**. For
606
- every concept-seeded slide, Read its anchored
607
- `decks/<slug>/concepts/concept-slide-NN.png` and matching
608
- `decks/<slug>/.draft-preview/<slide-basename>.png` **together in the same visual inspection**, in
609
- batches of at most 3 pairs. Diff each pair organism by organism; a draft-only inspection does not
610
- satisfy this check. Fix anything visibly wrong **in the JSX** (re-express, never delete), render
611
- again, and reopen the affected anchored concept/draft pairs until each slide is sound. One round
612
- usually suffices. It runs
622
+ images back to you — **nothing is pushed, no `.pptx` is written, no review gate opens** — and
623
+ runs the captured slides through the server's export gate (slot-fit, diagram geometry,
624
+ connector endpoints) so the errors `build-pptx` would stop on surface HERE, before the review,
625
+ not as a 422 after the user approved (skipped with a note when the backend is unreachable;
626
+ `--lint-only` for a fast lint loop, `--json` for a payload whose `reviewCanOpen` says whether
627
+ the gate will open). Two things come back, and both bind:
628
+ - **Measured lines** under each slide — `[fill:warn]` (a stretched region whose organisms pool
629
+ at its top, with the region and the dead band) and `[inventory:warn]` (connectors listed in
630
+ `asset-plan.md` but not drawn, an empty inventory, a fetched asset no slide references) —
631
+ and a closing `DRAFT CHECK: N measured defects` line. Fix every one in the JSX and re-run;
632
+ the review gate refuses to open while they stand for an unchanged slide. A fill band is
633
+ fixed by distributing the organisms (`grid-template-rows` with one track per organism,
634
+ `justify-content: space-between`, a figure that grows), never by deleting content.
635
+ - **The pair**: for every concept-seeded slide, Read its anchored
636
+ `decks/<slug>/concepts/concept-slide-NN.png` and matching
637
+ `decks/<slug>/.draft-preview/<slide-basename>.png` **together in the same visual inspection**,
638
+ in batches of at most 3 pairs, and compare **region by region**: same organisms at the
639
+ same relative positions and shares, every connector present, no region emptier than in the
640
+ concept, nothing clipped, overlapping, or buried. A loop, a hub, a flywheel, a timeline in the
641
+ concept that became a row of cards in the render is a failed rebuild, not a simplification —
642
+ re-express the mechanism. Under the slide's section in `asset-plan.md` write `draft-check:` and
643
+ then one tick line per `organisms:` row: `- [x] <organism>` when it is in the render at the
644
+ concept's position and share, `- [ ] <organism> — <what differs>` when it is not. A bare
645
+ `matches` is not a verdict; the review gate refuses to open while any concept-seeded slide
646
+ has fewer ticks than organisms or a `[ ]` standing. A draft-only inspection does not satisfy
647
+ this check. The measured lines also include `[type:warn]` — most of the text one rung below
648
+ `--fs-body` next to free space; fix by raising the ramp, never by shrinking type to fit.
649
+ Fix anything wrong **in the JSX** (re-express, never delete), render again, and reopen the
650
+ affected concept/draft pairs until each slide matches its image and the measured line reads
651
+ zero. Do not open the review gate from a summary or a memory of an earlier look. It runs
613
652
  locally against the materialized bundle, so it needs no awake pane and no open host once the
614
653
  bundle has been downloaded once. Two faithful limits to know: a slide on a **purpose-built (Mode
615
654
  A) layout** (cover/divider/agenda) and a **reused native `.pptx`** slide have no freeform region
616
655
  to capture, so they come back as a status line, not an image — judge those at the full-deck
617
- `review`/`build` step below. Do this self-check before the human review gate; don't push a draft
656
+ `review` step below. Do this self-check before the human review gate; don't push a draft
618
657
  you haven't looked at.
619
658
  5. **The review gate — the moment the deck builds clean.** This is the **review gate** in
620
659
  `workflow/build-and-export.md`; the CLI's gate command is `slidemind review <deck>`. Run it the
@@ -740,7 +779,7 @@ data table per chart; `slidemind read <file.pptx>` (host-free) reports text only
740
779
 
741
780
  Finding and fetching engine imagery is `slidemind-search`'s job (`slidemind search` → `slidemind
742
781
  download-result`). What follows is **create-only**: how to size and colour a fetched asset so it
743
- reads on-brand in your JSX, honoured in both the `slidemind build` preview and the render.
782
+ reads on-brand in your JSX, honoured in both the local draft preview and the render.
744
783
 
745
784
  Engine results land as files referenced with `<img src="./assets/<file>">`. These facts are specific
746
785
  to the local harness (the bundle's `workflow/*.md` stay harness-neutral):
@@ -757,7 +796,7 @@ to the local harness (the bundle's `workflow/*.md` stay harness-neutral):
757
796
  - **Never open the SVG to find out whether it recolours.** `slidemind download-result` prints a
758
797
  `recolor` descriptor for each asset — `{ recolorable, mode, applied, note }`, where `mode` is
759
798
  `currentColor`, `mono`, `multicolor`, or `raster`. Read it; that *is* the answer.
760
- - **Two ways to colour an icon, both honoured in the `slidemind build` preview *and* the render:**
799
+ - **Two ways to colour an icon, both honoured in the local draft preview *and* the render:**
761
800
  - **Bake it in (prefer this):** pass `--icon-color <hex>` — a hex you resolve from a token in the
762
801
  bundle's `corporate-design.css` — to `slidemind download-result`. Works for any `recolorable`
763
802
  glyph and the colour is in the saved file, so preview and render always agree. The tint is
@@ -787,7 +826,7 @@ Vocabulary. You are the local harness — this is your mapping:
787
826
  | concept mockups (mandatory for every from-scratch set, after plan sign-off) | `slidemind concepts <deck>` — renders one image mockup per approved-plan slide into the durable repo-relative path `decks/<slug>/concepts/concept-slide-NN.png`; immediately Read all of them for image/content search, then after searches reopen them in batches of at most 3 immediately before writing the matching `NN-*.jsx`. Re-list and reopen those paths after any interruption or continuation; never rely on an earlier chat image. The rebuild rule is owned by `workflow/create.md` → Preconditions. It never hard-fails: on an empty result (exit 0 — the add-in can't generate them) author from the storyboards alone, but for anything authored from a plan — one slide included — you always run it. Skip only for targeted work on slides that already exist (edit/rework) |
788
827
  | **read** / **list** the workspace | read the files from the materialized bundle dir (`<bundle>/…`) and your `decks/<slug>/` |
789
828
  | **create / update a slide file** | write `decks/<slug>/slides/NN-<topic>.jsx` on disk with a **zero-padded numeric prefix** (`01-cover.jsx`, `02-agenda.jsx`, …) — over the CLI, "the ordered set you hand to build & push" is expressed as **numeric-aware filename order**; there is no ordered-list channel, so reordering = renaming files. For a concept-seeded create or structural rewrite, the immediately preceding tool action must Read the matching anchored concept path(s) at full detail |
790
- | **draft check** | `slidemind view-draft <deck>` — renders each `slides/*.jsx` to `decks/<slug>/.draft-preview/<slide-basename>.png` and returns the images to **you**; **no push, no `.pptx`, no review gate**. Read each concept/draft path pair together, fix the JSX, render, and pair again. A draft-only inspection does not count. Host-free once the bundle is materialized. Freeform slides only — purpose-built (Mode A) and reused native `.pptx` slides report as a status line (judge those at `review`/`build`). This is your self-check; `slidemind review` is the *human* gate |
829
+ | **draft check** | `slidemind view-draft <deck>` — lints every slide (fix each `[lint:warn]`; `--fix` applies the deterministic ones), then renders each `slides/*.jsx` to `decks/<slug>/.draft-preview/<slide-basename>.png` (clean, no overlays), prints the measured defects (`[fill:warn]`, `[inventory:warn]`, closing `DRAFT CHECK:` line) and returns the images to **you**; **no push, no `.pptx`, no review gate**. Read each concept/draft path pair together, fix the JSX, render, and pair again until the measured count is zero and the pairs match. A draft-only inspection does not count; `slidemind review` refuses to open while measured defects stand for an unchanged slide. Host-free once the bundle is materialized. Freeform slides only — purpose-built (Mode A) and reused native `.pptx` slides report as a status line (judge those at `review`/`build`). This is your self-check; `slidemind review` is the *human* gate |
791
830
  | **build & push** | `slidemind push <deck>` (into the open PowerPoint — the insert needs a window) · `slidemind build-pptx <deck> [--out <file>]` (to a `.pptx` file — **host-free** for a bound deck: no PowerPoint window at all) — deliver whichever target was locked at intake; both render server-side via the bridge and for a bound deck the render itself is pane-free |
792
831
  | **the review gate** | `slidemind review <deck>` — builds the HTML preview (lint-clean or it doesn't open), serves it with a spatial comment overlay and **blocks until the user decides**, then prints `DECISION: approved` or `DECISION: comments` (each comment cited to `slides/<file>.jsx:line:col` + the round it was dropped on). On approve → deliver (`slidemind push` / `build-pptx`); on comments → edit the cited files and re-run `slidemind review` in the same turn (addressed pins show as ghost pins); the user pins as many comments as they like and sends them as one batch (Approve is off while unsent pins exist); expect several comment rounds. CLI delivers happen out of the user's sight, so **every deliver follows an approved review of what it delivers — re-run `review` after any slide change**. `push`/`build-pptx` never pause; only `review` does. |
793
832
  | **inspect the deck** | `slidemind read [presId\|file.pptx] [--mode summary\|raw]` — one verb, two sources: pull the current content of an open presentation (a presId; **host-required** — it reads the live, possibly unsaved file) **or** read a local `.pptx` on disk (a file path; host-free — no PowerPoint, no pairing). Prefer the file path when the deck is saved. `summary` = per-slide outline; `raw` = each slide's plain text — to inspect what's there before editing. Plain text, not authoring markup |
@@ -813,8 +852,8 @@ the rules may have changed.
813
852
  ## Failure modes the render lint can't catch
814
853
 
815
854
  Render-time lint rejects contract violations (unsupported CSS, bad imports, remote `<img>` URLs —
816
- the add-in's capability-table engine; `slidemind build` runs the same engine locally from the
817
- bundle). It can't catch content-shaped failures; check for these **at the draft check** (step 4,
855
+ the add-in's capability-table engine; `slidemind view-draft` runs the same engine locally from the
856
+ bundle before it renders). It can't catch content-shaped failures; check for these **at the draft check** (step 4,
818
857
  before the review gate) — not after delivery:
819
858
 
820
859
  - Layout-default / placeholder text leaking through an unfilled prop.
@@ -96,8 +96,8 @@ commands then carry its `--to`), and a command that can't be re-routed tells you
96
96
  **Add-in unreachable → block and name the tab.** When a host-required command can't reach the add-in
97
97
  (a connection blocker, a pane that never wakes, the *click the slidemind pane* line with no effect),
98
98
  **stop** — don't skip the step, work around it, or retry silently. Ask the user to go back into the
99
- PowerPoint tab of presentation **<title/fileName>** (name it from the command output or `slidemind
100
- presentations` — never a raw id), then re-run the failed command once they have.
99
+ PowerPoint tab of presentation **<title/fileName>** (name it from the command output or `slidemind ready
100
+ --diagnose` — never a raw id), then re-run the failed command once they have.
101
101
 
102
102
  ## Discover → search → pick → reuse
103
103
 
@@ -145,11 +145,12 @@ Follow `workflow/search.md` for the discipline; this is the CLI shape of its loo
145
145
  path, Read that ONE file — every candidate is on it with its selection number burned in. With no
146
146
  contact sheet, previews depend on the result type: **slide-library results** support `slidemind
147
147
  view-slide --search <searchId> --pick <n>` (writes one result's preview to a file you can Read);
148
- **image/icon results have NO preview on this path** — `view-slide` answers "no image returned"
149
- for them, so never call it on an image search, and never retry it hoping for a different answer.
148
+ **image/icon results have NO preview on this path** (the search says so itself) — `view-slide`
149
+ answers "no image returned" for them, so never call it on an image search.
150
150
  For icons, pick by title + score, `download-result` the pick (downloads are cheap), Read the
151
151
  saved file, and re-search once if it was wrong — two failed downloads for the same need means
152
- mark it unresolved and move on. Pick by **selection number(s)**.
152
+ mark it unresolved and move on. Pick by **selection number(s)**, and **cite the sources** (see
153
+ the rule below) whenever you report results back.
153
154
  5. **Read the source deck when the story matters.** A found slide came out of a real presentation,
154
155
  and often the answer the user needs is in the *rest* of it — how the slide was framed, what came
155
156
  before and after, whether that deck already covers more of the ask. `slidemind read-source
@@ -160,6 +161,14 @@ Follow `workflow/search.md` for the discipline; this is the CLI shape of its loo
160
161
  deck — image and collection results do not.
161
162
  6. **Reuse.** Insert live or fold into a deck — see the next section.
162
163
 
164
+ > **Always provide sources.** Every time you report search results to the user — a shortlist, a
165
+ > single pick, a "found it / didn't find it" answer — list, per result, the **presentation it came
166
+ > from** (its title and slide number as printed) and its **link** when the CLI printed a `source:`
167
+ > line (`sourceUrl` in `--json`; SharePoint-indexed results carry the file's browser URL). The user
168
+ > must be able to open the original presentation without asking you for it. A result printed
169
+ > without a `source:` line has no link on record — say so; **never invent, guess, or reconstruct a
170
+ > URL**. This is not optional and does not depend on whether the user asked for sources.
171
+
163
172
  ## Reuse targets — insert live, or fold into a deck
164
173
 
165
174
  A found slide can be reused two ways. Both extract the picked result(s) as native PowerPoint slides
@@ -168,7 +177,7 @@ A found slide can be reused two ways. Both extract the picked result(s) as nativ
168
177
  - **Insert LIVE into an open PowerPoint** — `slidemind insert-slide --search <searchId> --pick
169
178
  <n[,n,…]> --to <presId> [--at <N>]`. **This is the one host-required step:** the picked slides
170
179
  land in that open window (at position `--at`, else appended), so PowerPoint must be open with the
171
- slidemind pane. Name the target window from `slidemind presentations` (or, on the relayed path,
180
+ slidemind pane. Name the target window from `slidemind ready --diagnose` (or, on the relayed path,
172
181
  use the window the search ran on). A window that reconnects mid-run gets a **new** id; the CLI
173
182
  re-finds it and retries once by itself, so a stale id is not your problem. If it still reports the
174
183
  pane asleep, **don't loop** — relay its line, or fold the same pick into a deck (below) and push
@@ -239,7 +248,7 @@ insert live, fold into a deck, engine fetch). You are the local harness — this
239
248
  | Verb (`workflow/search.md`) | Your equivalent (local harness) |
240
249
  |---|---|
241
250
  | **discover engines** ("list what you can reach") | `slidemind ls engines [--type collection\|search\|scope] [--subtype …]` — returns the engine catalog **plus the search scopes** (pick an `id`; never guess one). The catalog is backend/user-scoped; `--to` is accepted but never needed. No window needed where the add-in serves the pane-free routes. |
242
- | **engine search** (with `intent`) | `slidemind search "<query>" --engine <id> --intent <auto\|content\|visual> [--top N] [--deck <deck>]` — leave `--to` off → a `searchId` + numbered results, plus the exact follow-up commands for the path it took (`--deck` routes by that deck's design when several environments are connected) |
251
+ | **engine search** (with `intent`) | `slidemind search "<query>" --engine <id> --intent <auto\|content\|visual> [--top N] [--deck <deck>]` — leave `--to` off → a `searchId` + numbered results, plus the exact follow-up commands for the path it took (`--deck` routes by that deck's design when several environments are connected); each result may carry a `source:` link — **always cite it back** |
243
252
  | **search a scope** (unfiltered corpus, no engine) | `slidemind search "<query>" --engine <scope:all\|scope:sharepoint> --result-type <slide\|image> [--intent …]` — `--result-type` is required for a scope (defaults to `slide`). Results reuse identically to engine results. |
244
253
  | **pull an on-demand thumbnail** for a stored result | `slidemind view-slide --search <searchId> --pick <n>` — SLIDE-library results only; image/icon results have no preview ("no image returned") — download and Read the file instead (add `--to <searchWindow>` only on the relayed path) · or `slidemind search … --json` for the structured payload. |
245
254
  | **read a result's SOURCE deck** (the presentation it came from) | `slidemind read-source --search <searchId> --pick <n> [--from-slide N] [--to-slide N] [--out <file.md>]` — Markdown of the whole source presentation, one section per slide (add `--to <searchWindow>` only on the relayed path). Indexed text, not the live file. Slide results only. |
@@ -259,6 +268,15 @@ from `slidemind ls engines` (live discovery — the bundle carries no engine lis
259
268
  search/reuse craft is in `workflow/search.md`. Read them and follow them. If the corporate design
260
269
  moves mid-task, re-list engines before searching again, since the catalog may have changed.
261
270
 
271
+ **Where the bundle is, and what to do when there isn't one.** Searching itself needs no bundle, but
272
+ `workflow/search.md` lives in one, so a machine that has never materialized a design has nothing to
273
+ read. `slidemind ready --json` prints the materialized bundle's directory as `bundleDir` (run it
274
+ once — it resolves the design and materializes it); for a deck you are reusing into,
275
+ `slidemind ready <deck> --json` uses that deck's own binding. When several designs are at hand and
276
+ nothing says which, `slidemind ls designs` lists every one (cached here and listed by the add-in)
277
+ so the user can choose, and `slidemind download-bundle --brand <presFP>` materializes it without a
278
+ PowerPoint window.
279
+
262
280
  `workflow/search.md` also owns the judgement this skill deliberately does **not** restate: what an
263
281
  engine actually is (a saved filter over a shared corpus, not a library of its own), when to reach
264
282
  for a scope instead, and why zero results are not evidence that the content doesn't exist. Read it
@@ -33,6 +33,7 @@ import {
33
33
  materializeBundle,
34
34
  readDeckBundleRef,
35
35
  resolveBundleDirForDeck,
36
+ resolveWindowlessBrand,
36
37
  writeDeckBundleRef,
37
38
  } from "./bundle.mjs";
38
39
  import { bundleHasCaptureEngine, captureDeckLocally } from "./capture.mjs";
@@ -471,7 +472,10 @@ function mapCapturedSlide(fileName, cap, warnings) {
471
472
  for (const w of cap.render?.warnings ?? []) warnings.push(`${fileName}: ${w}`);
472
473
 
473
474
  if (cap.previewDataUrl) {
474
- return { fileName, status: "ok", dataUri: cap.previewDataUrl };
475
+ // The serialised boxes ride along so view-draft can run the measured
476
+ // checks (fill band, content grid) the add-in's see_draft runs.
477
+ const { root = null, stage = null, boxes = null } = cap.render ?? {};
478
+ return { fileName, status: "ok", dataUri: cap.previewDataUrl, render: { root, stage, boxes } };
475
479
  }
476
480
  // A degraded render is a non-editable raster; push/build-pptx will REJECT it.
477
481
  // Attach it anyway (so the agent isn't blind) but flag the named cause.
@@ -515,7 +519,7 @@ function mapCapturedSlide(fileName, cap, warnings) {
515
519
  // note?}], warnings: [] }.
516
520
  export async function viewDraft(
517
521
  deckDir,
518
- { presentationId = null, onProgress = null } = {},
522
+ { presentationId = null, onProgress = null, only = null, translate = false } = {},
519
523
  ) {
520
524
  if (!deckDir) throw new Error("viewDraft: deckDir is required");
521
525
  const slidesDir = path.join(deckDir, "slides");
@@ -525,12 +529,19 @@ export async function viewDraft(
525
529
  // Authored JSX + reused native .pptx share one play-order namespace, exactly
526
530
  // as the render path sees them — walk them in that order so the draft view
527
531
  // lines up with the deck the user will get.
528
- const all = sortSlideFilenames(
532
+ let all = sortSlideFilenames(
529
533
  fs.readdirSync(slidesDir).filter((f) => f.endsWith(".jsx") || f.endsWith(".pptx")),
530
534
  );
531
535
  if (all.length === 0) {
532
536
  throw new Error(`No .jsx or .pptx slides under ${slidesDir}`);
533
537
  }
538
+ if (Array.isArray(only) && only.length > 0) {
539
+ const missing = only.filter((f) => !all.includes(f));
540
+ if (missing.length > 0) {
541
+ throw new Error(`no such slide file${missing.length > 1 ? "s" : ""} under ${slidesDir}: ${missing.join(", ")}`);
542
+ }
543
+ all = all.filter((f) => only.includes(f));
544
+ }
534
545
  const warnings = [];
535
546
  // Same identity reconcile as the render path — a reorder renames every file,
536
547
  // and a stale sidecar here would mislabel renamed converted slides (Chrome
@@ -553,6 +564,22 @@ export async function viewDraft(
553
564
  // cached engine.
554
565
  let ref = readDeckBundleRef(deckDir);
555
566
  let bundleDir = resolveBundleDirForDeck(deckDir);
567
+ if (hasJsx && !presentationId && (!bundleDir || !bundleHasCaptureEngine(bundleDir))) {
568
+ // Window-free first: the deck's binding or the one design at hand syncs
569
+ // over the brand route; only a miss (old add-in, revoked design) goes on
570
+ // to the registry below.
571
+ const brandId = await resolveWindowlessBrand(deckDir);
572
+ if (brandId) {
573
+ try {
574
+ const mat = await materializeBundle(null, { brandId });
575
+ writeDeckBundleRef(deckDir, { presFP: mat.presFP, etag: mat.etag });
576
+ ref = readDeckBundleRef(deckDir);
577
+ bundleDir = resolveBundleDirForDeck(deckDir);
578
+ } catch (err) {
579
+ if (!isBrandRouteMiss(err)) throw err;
580
+ }
581
+ }
582
+ }
556
583
  if (hasJsx && (!bundleDir || !bundleHasCaptureEngine(bundleDir))) {
557
584
  const registry = await requireRenderHost();
558
585
  const entry = pickPresentationEntry(registry, presentationId, {
@@ -587,19 +614,19 @@ export async function viewDraft(
587
614
  throw new Error(
588
615
  "draft preview needs the corporate design's capture engine, which ships in the " +
589
616
  "materialized bundle. Run `slidemind download-bundle <deck>` against an open " +
590
- "PowerPoint host once, then retry — or use `slidemind build <deck>` for a " +
617
+ "PowerPoint host once, then retry — or use `slidemind view-draft <deck>` for a " +
591
618
  "host-free HTML preview.",
592
619
  );
593
620
  }
594
621
 
595
622
  let captured;
596
623
  try {
597
- captured = await captureDeckLocally({ deckDir, bundleDir, onProgress });
624
+ captured = await captureDeckLocally({ deckDir, bundleDir, onProgress, only });
598
625
  } catch (err) {
599
626
  throw new Error(
600
627
  `local draft capture failed (${err instanceof Error ? err.message : String(err)}). ` +
601
- "It runs a local headless Chrome; if Chrome can't launch here, use " +
602
- "`slidemind build <deck>` for an HTML preview instead." +
628
+ "It runs a local headless Chrome; if Chrome can't launch here, " +
629
+ "`slidemind review <deck>` still builds and serves the HTML preview without it." +
603
630
  (Object.keys(conversions).length > 0 ? ` ${convertedNote}.` : ""),
604
631
  );
605
632
  }
@@ -619,7 +646,67 @@ export async function viewDraft(
619
646
  return mapCapturedSlide(fileName, byFile.get(fileName), warnings);
620
647
  });
621
648
 
622
- return { slides, warnings };
649
+ // --translate: run the captured slides through the server's translate gate
650
+ // (slot-fit, diagram-geometry, connector-endpoints — the checks the local
651
+ // capture cannot make) and hand its verdict back per slide. The returned
652
+ // .pptx is discarded: this is validation, not delivery, so no review gate
653
+ // stands in front of it and a per-slide builder can prove its ONE slide
654
+ // buildable before reporting. Pane-free over the deck's bound design.
655
+ // `translate: "auto"` (the default) runs the gate when it can and reports
656
+ // why not otherwise — an unbound deck or a dead backend must not turn a
657
+ // host-free draft check into a failure; `true` (--translate) insists.
658
+ let translateErrors = null;
659
+ let translateWarnings = [];
660
+ let translateRan = false;
661
+ if (translate && captured?.slides?.length) {
662
+ const skip = (why) => {
663
+ if (translate !== "auto") throw new Error(why);
664
+ warnings.push(`export gate skipped: ${why}`);
665
+ };
666
+ if (!ref?.presFP) {
667
+ skip("the deck is not bound to a design (run `slidemind ready <deck>` first)");
668
+ } else {
669
+ let mat = null;
670
+ try {
671
+ mat = await materializeBundle(null, { brandId: ref.presFP });
672
+ } catch (err) {
673
+ skip(`the design's backend did not answer (${err?.message ?? err})`);
674
+ }
675
+ if (mat) writeDeckBundleRef(deckDir, { presFP: mat.presFP, etag: mat.etag });
676
+ if (mat && !mat.backendURL) {
677
+ skip("the bound design's backend could not be resolved (brand route unavailable)");
678
+ } else if (mat) {
679
+ try {
680
+ const res = await translateDeck({
681
+ brandId: ref.presFP,
682
+ backendURL: mat.backendURL,
683
+ deckName: path.basename(deckDir),
684
+ captured,
685
+ etag: mat.etag,
686
+ });
687
+ translateErrors = new Map();
688
+ translateWarnings = Array.isArray(res?.warnings) ? res.warnings : [];
689
+ translateRan = true;
690
+ } catch (err) {
691
+ if (err?.status === 422) {
692
+ const { parseTranslateErrors } = await import("../builders/draft-check.mjs");
693
+ translateErrors = parseTranslateErrors(err.message);
694
+ translateRan = true;
695
+ } else {
696
+ skip(`the export gate did not answer (${err?.message ?? err})`);
697
+ }
698
+ }
699
+ }
700
+ }
701
+ }
702
+ for (const slide of slides) {
703
+ if (translateErrors && slide.status === "ok") {
704
+ slide.translateErrors = translateErrors.get(slide.fileName) ?? [];
705
+ }
706
+ }
707
+ if (translateErrors?.has("")) warnings.push(...translateErrors.get(""));
708
+
709
+ return { slides, warnings, translateWarnings, translateRan };
623
710
  }
624
711
 
625
712
  // A brand-route miss is definitive for the pane-free path but NOT for the
@@ -631,7 +718,9 @@ function isBrandRouteMiss(err) {
631
718
  err?.code === "brand_route_unavailable" ||
632
719
  err?.code === "unknown_brand" ||
633
720
  err?.code === "no_active_cd" ||
634
- err?.status === 404
721
+ // A 404 WITHOUT a machine code is Express saying "no such route" (an old
722
+ // add-in); a coded 404 is the route itself talking and must surface.
723
+ (err?.status === 404 && !err?.code)
635
724
  );
636
725
  }
637
726
 
@@ -776,8 +865,9 @@ export async function buildAndPush({
776
865
  // backgrounded during the render); the pane matters only for the insert. ---
777
866
  let result = null;
778
867
  let renderEntry = null;
779
- if (!presentationId && deckRef?.presFP) {
780
- const detached = await tryDetachedRender(deckDir, deckRef, onProgress, reconcile);
868
+ const brandId = presentationId ? null : await resolveWindowlessBrand(deckDir);
869
+ if (brandId) {
870
+ const detached = await tryDetachedRender(deckDir, { presFP: brandId }, onProgress, reconcile);
781
871
  if (detached) result = detached.result;
782
872
  }
783
873
  if (!result) {
@@ -983,10 +1073,13 @@ export async function buildPptxFile({
983
1073
  // folded or reported.
984
1074
  const reconcile = collectReconcileInputs({ deckDir, outFile: out, force });
985
1075
 
986
- // Pane-free path. `--to <presId>` names a window explicitly and therefore
987
- // keeps the registry path.
988
- if (!presentationId && deckRef?.presFP) {
989
- const detached = await tryDetachedRender(deckDir, deckRef, onProgress, reconcile);
1076
+ // Pane-free path: the deck's binding, else the one design at hand (cache /
1077
+ // brand list). `--to <presId>` names a window explicitly and therefore keeps
1078
+ // the registry path — reading the design off an open presentation is the one
1079
+ // thing a render legitimately needs a window for.
1080
+ const brandId = presentationId ? null : await resolveWindowlessBrand(deckDir);
1081
+ if (brandId) {
1082
+ const detached = await tryDetachedRender(deckDir, { presFP: brandId }, onProgress, reconcile);
990
1083
  if (detached) {
991
1084
  fs.writeFileSync(out, Buffer.from(detached.result.base64, "base64"));
992
1085
  return {