@erclx/canon 4.0.0 → 4.1.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "canon",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "4.0.0",
4
+ "version": "4.1.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -22,7 +22,7 @@ Full help: `canon <command> --help`. Behavior notes for the install and sync ver
22
22
  | `canon indexes regen` | Regenerate `index.md` files from sibling frontmatter |
23
23
  | `canon docs [topic]` | Emit toolkit reference docs (`list`, or a topic by name) |
24
24
  | `canon design render` | Render `.claude/DESIGN.md` tokens to HTML and CSS |
25
- | `canon slides render` | Render a `.claude/SLIDES.md` source into a PowerPoint deck |
25
+ | `canon slides render` | Render a `.claude/SLIDES.md` source into a PowerPoint deck, reporting any unrecognized layout name on stderr |
26
26
  | `canon slides list` | List the available slide layouts (`--json` for the catalog) |
27
27
  | `canon feedback` | Write toolkit feedback from stdin to `.claude/review/feedback/`, or open a GitHub issue with `--github` |
28
28
  | `canon transcripts <url>` | Fetch a YouTube transcript with metadata frontmatter (needs `yt-dlp`) |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "4.0.0",
4
+ "version": "4.1.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -48,6 +48,13 @@ export function register(program: Command): void {
48
48
  process.stderr.write(
49
49
  `${GREY}│${NC} ${GREEN}✓${NC} ${result.slideCount} slides\n${GREY}│${NC} ${GREEN}✓${NC} ${result.pptxPath}\n`,
50
50
  )
51
+ const validNames = LAYOUTS.map((layout) => layout.name).join(', ')
52
+ for (const { value, slideNumbers } of result.unrecognizedLayouts) {
53
+ const noun = slideNumbers.length === 1 ? 'slide' : 'slides'
54
+ process.stderr.write(
55
+ `${GREY}│${NC} ${RED}✗${NC} unrecognized layout "${value}" on ${noun} ${slideNumbers.join(', ')}. Valid layouts: ${validNames}\n`,
56
+ )
57
+ }
51
58
  if (result.mirrorPath) {
52
59
  process.stderr.write(
53
60
  `${GREY}│${NC} ${GREEN}✓${NC} mirrored to ${result.mirrorPath}\n`,
@@ -32,6 +32,12 @@ export const LAYOUTS: LayoutInfo[] = [
32
32
  { name: 'quote', description: 'Large pull quote with attribution' },
33
33
  ]
34
34
 
35
+ const LAYOUT_NAMES = new Set(LAYOUTS.map((layout) => layout.name))
36
+
37
+ export function isKnownLayout(name: string): boolean {
38
+ return LAYOUT_NAMES.has(name)
39
+ }
40
+
35
41
  const W = 13.333
36
42
  const H = 7.5
37
43
  const MX = 0.7
@@ -4,16 +4,23 @@ import PptxGenJS from 'pptxgenjs'
4
4
  import {
5
5
  addFooter,
6
6
  type DeckNav,
7
+ isKnownLayout,
7
8
  renderDeckSlide,
8
9
  renderToc,
9
10
  } from '@/slides/layouts'
10
11
  import { type Deck, parseSlidesDoc } from '@/slides/parse'
11
12
  import { buildTheme, type Variant } from '@/slides/styles'
12
13
 
14
+ export interface UnrecognizedLayout {
15
+ value: string
16
+ slideNumbers: number[]
17
+ }
18
+
13
19
  export interface RenderResult {
14
20
  pptxPath: string
15
21
  mirrorPath?: string
16
22
  slideCount: number
23
+ unrecognizedLayouts: UnrecognizedLayout[]
17
24
  }
18
25
 
19
26
  export interface RenderOptions {
@@ -35,16 +42,22 @@ export async function renderSlidesDoc(
35
42
  if (deck.meta.title) pptx.title = deck.meta.title
36
43
 
37
44
  const nav = buildNav(deck)
38
- for (const slide of deck.slides) {
45
+ const unrecognizedSlideNumbers = new Map<string, number[]>()
46
+ deck.slides.forEach((slide, index) => {
39
47
  const target = pptx.addSlide()
40
48
  target.background = { color: theme.background }
49
+ if (!isKnownLayout(slide.layout)) {
50
+ const slideNumbers = unrecognizedSlideNumbers.get(slide.layout) ?? []
51
+ slideNumbers.push(index + 1)
52
+ unrecognizedSlideNumbers.set(slide.layout, slideNumbers)
53
+ }
41
54
  if (slide.layout === 'toc') {
42
55
  renderToc(target, slide, theme, nav)
43
56
  } else {
44
57
  renderDeckSlide(target, slide, theme)
45
58
  }
46
59
  addFooter(target, slide, theme, nav)
47
- }
60
+ })
48
61
 
49
62
  mkdirSync(outDir, { recursive: true })
50
63
  const fileName = outputName(sourcePath)
@@ -54,7 +67,16 @@ export async function renderSlidesDoc(
54
67
  const mirrorPath = options.mirror
55
68
  ? mirrorDeck(pptxPath, options.mirror, fileName)
56
69
  : undefined
57
- return { pptxPath, mirrorPath, slideCount: deck.slides.length }
70
+ const unrecognizedLayouts = Array.from(
71
+ unrecognizedSlideNumbers,
72
+ ([value, slideNumbers]) => ({ value, slideNumbers }),
73
+ )
74
+ return {
75
+ pptxPath,
76
+ mirrorPath,
77
+ slideCount: deck.slides.length,
78
+ unrecognizedLayouts,
79
+ }
58
80
  }
59
81
 
60
82
  function mirrorDeck(