feat(slides): import slidey markdown decks#587
Conversation
Captures grammar additions (per-slide frontmatter, ## Notes, FA shortcodes, mermaid fences, columns, ::boxes::/::arrows::), AST shape, layout mapping, asset pipeline (jsDelivr SVG-direct, mmdc PNG), and test strategy for the upcoming gog slides create-from-markdown rework. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Breaks the spec into 20 bite-sized TDD tasks: AST scaffold, frontmatter splitter, inline/block parsers, layout helpers, asset pipeline (FA fetch + mmdc + Drive upload + cleanup), renderer per layout, end-to-end fixture, docs + CHANGELOG. Each task is self-contained with failing-test, implementation, verification, and commit steps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces Slide, Block, Inline, IconRef, ImageRef and per-block types in slides_markdown_ast.go. Replaces the legacy types/parser/renderer in slides_markdown.go and slides_formatter.go with compilable stubs so the package builds; Tasks 2-8 fill the parser, Tasks 15-18 fill the renderer. Updates slides.go call site to match new ParseMarkdownToSlides signature. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-review feedback: Go convention drops type-prefix repetition on struct fields. Field has zero callers yet, so rename is risk-free. Plan updated to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements the §4.1 disambiguation rule: a "---" opens frontmatter only when followed by a YAML key line; otherwise it is a slide separator. Unclosed frontmatter is a fatal error with a line-numbered message. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-review hardening: strip \r\n at the entry point so downstream TrimSpace and yaml.Unmarshal calls don't see lingering \r bytes from Windows-authored decks. Pure addition; tests unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tokenizes a line of markdown into TextRun and IconRef inlines. Style derivation matches §4.6 (fa→default, fas→solid, far→regular, fab→brands, fal/fad→solid with free-tier substitution). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Walks slide body line-by-line and emits Block AST nodes. Inline parsing delegates to parseInlines from Task 3. Column, boxes/arrows, and mermaid marker handling is added in subsequent tasks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rkers Recursive call into parseBlocks per column body. Accepts ::right:: as a synonym for ::col2:: (slidey allows both for the 2-column case). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each row may begin with a leading FA shortcode (boxes) or a heading prefix (arrows). Trailing text becomes the row label. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Other languages remain CodeBlock. Each diagram gets a stable ID so the asset pipeline (Task 11) can pair it with an uploaded image. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ParseMarkdownToSlides now: splits frontmatter blocks, parses each slide's body via parseBlocks, hoists the first h1 (or h2 fallback) as the slide title for non-hero/title/statement layouts, extracts the trailing "## Notes" section as raw speaker notes (FA shortcodes stripped). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MapSlideyLayout collapses the slidey frontmatter values into a small LayoutKind enum. ColumnBoxes/SingleBodyBox/TitleBox compute box rectangles in points from the presentation page size. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AssetMap pairs AST IconRefs/DiagramBlocks with their Drive ImageRefs. DefaultAssetPipelineConfig sets a 30s HTTP timeout and the standard mmdc binary. The FA URL builder targets the FA Free 6.x jsDelivr path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fetchFAIconFromURL performs a GET against jsDelivr (or any URL injected in tests via httptest) and returns the SVG bytes. 404/non-200 are wrapped errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
mmdcCommandArgs builds the standardized invocation; renderMermaidWithBinary writes a temp .mmd, runs mmdc with a transparent background and 2x scale, and returns the rendered PNG. Temp files are cleaned up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AssetPipeline.Resolve walks all slides for unique IconRefs and DiagramBlocks, fetches/renders each, and uploads via the Uploader abstraction. Per-asset failures warn-and-skip unless Strict. Cleanup deletes the tracked Drive files unless KeepTempImages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DriveUploader wraps drive.Service to upload bytes, set anyone-reader permission, and return the WebContentLink — mirrors the pattern in slides_add_slide.go. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
RenderSlides emits CreateSlide + title box + body box for default and center layouts. Returns a SlideNotesPlan slice consumed by the second BatchUpdate (Task 18). Body text is the legacy "flatten blocks to one string" form; columns and image insertion follow in Tasks 16/17. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SectionHeader-kind layouts emit one centered body box with the first line styled at 44pt bold. two-cols/three-cols emit N side-by-side body boxes positioned via the geometry helpers from Task 9. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diagram blocks render as full-width images below the title. Bullets whose first inline is an IconRef get a small (18pt) icon image rendered to the left of the body box, vertically aligned to the bullet line. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CreatePresentationFromMarkdownV2 runs the full pipeline: create presentation → derive geometry → resolve assets → first BatchUpdate → re-fetch → second BatchUpdate for notes → cleanup. SlidesCreateFromMarkdownCmd gains --fa-style, --mmdc, --strict, --keep-temp-images, --no-notes. Existing --dry-run prints the would-be BatchUpdate JSON without any network for fetch/render. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copies univrs/slidey/slides/index.md into testdata and asserts the parser produces all expected layout kinds, notes, icons, and diagrams, then runs the renderer with a fake asset map. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the synthetic 30-slide fallback fixture with the actual univrs/slidey/slides/index.md deck (969 lines). Tests still pass — this gives the e2e check real-world content shape. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds reference for per-slide frontmatter, ## Notes, FA shortcodes, mermaid blocks, columns, and ::boxes::/::arrows::. Updates the flag table for the new --fa-style/--mmdc/--strict/--keep-temp-images/ --no-notes flags. CHANGELOG entry under 0.17.0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drive uploader now best-effort deletes the uploaded file when Permissions.Create or Files.Get fails, so a permission failure no longer orphans assets in the user's Drive. - Drop the legacy CreatePresentationFromMarkdown stub and the unused slideElementTitle const; the CLI now calls CreatePresentationFromMarkdownV2 directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three issues surfaced by running the import against a live Google
account on the univrs/slidey/slides/index.md deck:
- Notes BatchUpdate failed with "Invalid requests[0].deleteText: The
startIndex 0 must be less than the endIndex 0" because freshly-created
slides have empty notes boxes and DeleteText{ALL} rejects that. Drop
the leading DeleteText and just InsertText — notes are always empty
on a slide we just created.
- Font Awesome shortcodes like `:fa-dev:` 404 because "dev" is only
published under brands/, not solid/. Add a fallback that re-tries
the brands/regular/solid styles when the requested style 404s. Other
errors (network, 5xx) still fail immediately without guessing.
- mmdc errors only surfaced as bare exit codes ("exit status 1"). Use
CombinedOutput so stderr (puppeteer chromium download issues, mermaid
syntax errors, etc.) ends up in the warning message.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Nicholas Reid <njreid@gmail.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 535633b941
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (layout == LayoutKindDefault || layout == LayoutKindCenter) && explicitColumnCount > 0 { | ||
| if explicitColumnCount == 2 { |
There was a problem hiding this comment.
Preserve third column content when layout is forced to two-cols
RenderSlides only upgrades to LayoutKindThreeCols when the frontmatter layout is default or center, so a slide declared as layout: two-cols but containing an explicit 3-column block keeps n=2. That causes the third parsed column to be dropped by findColumnsBlock (it truncates to n), silently losing user content whenever ::col3:: is present with mismatched frontmatter. Please either honor explicit column count regardless of base layout or fail fast on the mismatch.
Useful? React with 👍 / 👎.
| if columnStart < len(lines) && headingRE.MatchString(lines[columnStart]) { | ||
| columnStart++ |
There was a problem hiding this comment.
Recognize non-ATX headings before shorthand column wrapping
normalizeShorthandColumns treats only headingRE matches as the pre-column title line, which misses valid Markdown title forms like indented ATX headings and setext headings. In two-cols/three-cols shorthand slides, those titles get wrapped inside ::cols::, and later hoistTitle cannot extract them because it only inspects top-level blocks, leaving slides without the expected title box text.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 41cb77912e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for i, b := range blocks { | ||
| out[i%n] = append(out[i%n], b) | ||
| } |
There was a problem hiding this comment.
Keep fallback two-column content in source order
When a slide uses layout: two-cols/three-cols but has no explicit ColumnsBlock, this fallback round-robins blocks with out[i%n]. That interleaves sequential content across columns (for example, paragraphs 1/3 in col1 and 2/4 in col2), which reorders the author’s narrative and can separate related blocks unexpectedly. The fallback should preserve order (e.g., keep content in the first column unless explicit markers exist) or return a validation error instead of repartitioning by index.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4e3e14ac84
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if !inFence && isBareDelimiter(lines[i]) { | ||
| break |
There was a problem hiding this comment.
Ignore indented code when splitting on slide delimiters
splitMarkdownIntoSlideBlocks treats any trimmed --- as a slide boundary unless it is inside a fenced block, but it does not account for indented code blocks. In vanilla Markdown, a line like --- is code content, yet this logic will split the slide there, which can truncate code snippets and create unintended extra slides whenever indented code examples include ---.
Useful? React with 👍 / 👎.
| t := strings.TrimSpace(line) | ||
| if t == "## Notes" || t == "### Notes" { | ||
| b := strings.Join(lines[:i], "\n") |
There was a problem hiding this comment.
Skip notes heading detection inside indented code blocks
splitOutNotes only suppresses ## Notes detection while inside fenced code, so an indented code line such as ## Notes is still matched after TrimSpace and incorrectly treated as the start of speaker notes. That removes the rest of the slide body and moves code content into notes for decks that use indented code blocks.
Useful? React with 👍 / 👎.
| if v.Ordered { | ||
| fmt.Fprintf(&b, "%d. ", j+1) | ||
| } else { |
There was a problem hiding this comment.
Preserve ordered list start numbers in rendered text
Ordered list numbering is always regenerated as j+1 during rendering, so Markdown lists that intentionally start at another number (for example 3., 4.) lose their original numbering in the produced slide text. Because the parser also drops the list start value, this behavior is deterministic and affects any non-1 ordered list input.
Useful? React with 👍 / 👎.
Summary:
slides create-from-markdownTesting:
make cigo test ./internal/cmd -run 'Test(ParseMarkdownToSlides|RenderSlides|AssetPipeline|Build|SlidesToAPIRequests|Layout|Blocks|Inlines|Frontmatter|Slidey)'go test ./internal/cmd -run 'TestRenderSlide_CenterLayoutWithOnlyTitleDoesNotStyleEmptyBody|TestRenderSlide_HeroLayoutLargeTitleNoTitleBox|TestRenderSlide_HeroStyleRangeUsesUTF16|TestParseMarkdownToSlides_ShorthandColumnsWithColsMentionInsideFence' -count=1bin/gog --account clawdbot@gmail.com --json --no-input slides create-from-markdown 'Slidey Dry Run Smoke' --content-file testdata/slidey/index.md --dry-run --mmdc ''produced 35 slides, 15 image requests, 15 placeholder URLsLive E2E:
clawdbot@gmail.com; blocked by local OAuth refresh token failure:invalid_grantfrom Google before deck creationCredits:
Co-authored-by: Nicholas Reid njreid@gmail.com