Embed presentation decks in Markdown plans - #230
HamptonMakes wants to merge 13 commits into
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3aabc7dfa5
ℹ️ 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".
|
|
||
| .deck-region { margin-block: var(--space-lg); outline: none; } | ||
| .deck-region:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 3px; } | ||
| .deck-region .deck-slide:not(.deck-slide--current) { display: none; } |
There was a problem hiding this comment.
Preserve the current slide when presentation stops
When any embedded deck exits presentation mode, stop() runs _teardown(), which removes .deck-slide--current from every slide in deck_presenter_controller.js. This new selector then matches every slide and sets it to display: none, so the deck disappears from reading mode until the user clicks a step control or reloads. Restore the reader's current-slide class after teardown, or stop removing it.
AGENTS.md reference: AGENTS.md:L87-L89
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🤖 Fixed in e47262e. Presenter teardown now leaves the reader’s current-slide class in place, and the browser spec verifies the slide stays visible after Escape.
| const fragment = this.templateContent | ||
|
|
||
| if (hasDirtyDrafts()) { | ||
| if (target.querySelector(".deck--presenting")) { |
There was a problem hiding this comment.
Retain reader position across live updates
When a collaborator broadcasts an edit while the user is reading slide 2 or later, this condition protects only a deck actively presenting; ordinary reading falls through to applyContent, which replaces the region controllers. The incoming server HTML always marks slide 1 current, and the old wrapper's data-current-slide is discarded, so the reader is silently reset to the first slide. Capture and reapply each region's current slide across the swap.
AGENTS.md reference: AGENTS.md:L87-L89
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🤖 Fixed in e47262e. Live body swaps now carry each deck’s current slide forward by region ID or deck order, clamp it to the new slide count, and restore it before Stimulus reconnects. A browser spec covers the swap.
| this.start() | ||
| return | ||
| } | ||
| if (!this.presenting) return |
There was a problem hiding this comment.
Restore the advertised
p presentation shortcut
For every non-presenting deck, the shortcut dispatcher reaches this unconditional return, so pressing p can no longer call start(). The binding is still published as “Start presenting” in engine/config/keyboard_shortcuts.json, and the show view still documents p as an entry point, making the advertised keyboard action a no-op. Route the command to the focused/current deck or remove the binding and documentation.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🤖 Fixed in e47262e. The p shortcut now starts the focused deck, or the first visible deck when none is focused. The multi-deck browser spec covers starting from the second deck.
|
🤖 @codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e47262ec7c
ℹ️ 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".
| const target = document.getElementById(id) | ||
| const slide = target?.closest(".deck-slide") | ||
| if (slide && this.element.contains(slide)) this.show(Number(slide.dataset.slide) - 1) |
There was a problem hiding this comment.
Scroll to fragment targets after revealing their slide
When an initial URL or ordinary hash link targets slide 2 or later, the browser attempts fragment navigation while that slide is still display: none; this delayed handler reveals the slide but never scrolls to target, so the URL and counter update while the reader can remain elsewhere on the page. Scroll to the target after show() (the outline path already does both operations explicitly).
AGENTS.md reference: AGENTS.md:L87-L89
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🤖 Fixed in a3abfef. After revealing the target slide, the reader scrolls the fragment into view. A browser test checks the linked heading lands in the viewport.
|
|
||
| definitions = Slideshows::Split.call(result.canonical_source).definition_blocks | ||
| deck_number = 0 | ||
| fragments = result.regions.map do |region| |
There was a problem hiding this comment.
Track changed sections across every rendered region
For a mixed plan with prose after a deck or with multiple decks, this creates multiple .markdown-rendered roots, but both changed_sections_controller.js:23 and live_update_controller.js:174 select only the first root and walk only its children. Consequently, initial “changed since you last looked” bands and live-update flashes/cues silently omit every changed section in later regions; the section walkers need to aggregate all rendered roots in document order.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🤖 Fixed in a3abfef. Change bands and live diff flashes now walk every rendered Markdown region in document order, with global section slugs. Browser tests cover changes after a deck.
|
|
||
| .deck-region { margin-block: var(--space-lg); outline: none; } | ||
| .deck-region:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 3px; } | ||
| .deck-region .deck-slide:not(.deck-slide--current) { display: none; } |
There was a problem hiding this comment.
Exclude hidden slide headings from scroll tracking
Once a deck reaches the scroll threshold, headings on non-current slides still remain in content_nav_controller's _headings, while this rule gives them zero-sized rectangles whose top is 0. _updateActiveFromScroll() therefore treats those invisible headings as already passed and can mark the last hidden slide heading active; changing slides also triggers no recomputation. Filter tracking to the current slide or notify the outline whenever show() changes visibility.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🤖 Fixed in a3abfef. Scroll tracking ignores hidden slides and recomputes when a reader changes slides. A browser test checks the active outline entry.
| <%= render_markdown(plan.current_content, footnotes: :exclude, source_comments: true) %> | ||
| <% end %> | ||
| <% cache ["coplan/plan-content-body", CoPlan::MarkdownHelper::RENDER_CACHE_VERSION, plan.id, plan.current_plan_version&.content_sha256 || plan.current_revision], skip_digest: true do %> | ||
| <%= render_content_regions(plan.current_content) %> |
There was a problem hiding this comment.
Render version snapshots with presentation-region semantics
The new region renderer is wired only into the current-content partial, while engine/app/views/coplan/plan_versions/show.html.erb:40 still calls render_markdown directly. Opening “View full version” for any newly created embedded-deck revision therefore exposes the ::: {.presentation} delimiters as paragraphs and renders --- as rules instead of showing the recorded deck, so historical snapshots no longer match the document that was versioned.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🤖 Fixed in a3abfef. Historical versions now use the mixed-region renderer with read-only controls. A request spec covers a historical deck.
|
🤖 @codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a3abfef610
ℹ️ 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".
| content = plan.current_content.to_s | ||
| next if content.empty? || CoPlan::ContentRegions::Split.call(content).regions.any? { |region| region.kind == :presentation } | ||
|
|
||
| CoPlan::Plans::ReplaceContent.call( |
There was a problem hiding this comment.
Avoid enforcing user edit leases during the migration
If any presentation plan has an unexpired edit lease when this migration runs, ReplaceContent.call invokes EditLease.enforce! without a lease_token and raises CoPlan::EditLease::Conflict, aborting the entire deployment migration. Since leases persist for five minutes, this can happen even after application traffic is stopped; perform this system backfill without the interactive lease path, or explicitly handle active leases before dropping behavior.
AGENTS.md reference: AGENTS.md:L147-L147
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🤖 Fixed in ec522a3. The migration now locks each plan, clears any persisted edit lease inside that transaction, then creates the new immutable version. An in-flight editor will see a stale revision.
|
|
||
| <div class="plan-layout__content card" data-coplan--content-nav-target="content"> | ||
| <%= render_markdown(@version.content_markdown, interactive: false) %> | ||
| <%= render_content_regions(@version.content_markdown, interactive: false) %> |
There was a problem hiding this comment.
Render footnotes in historical snapshots
For any version containing Markdown footnotes, this replaces the previous inline-footnote render with render_content_regions, whose paths all call render_markdown(..., footnotes: :exclude). The current plan page supplies a separate References section, but this historical-version page does not, so “View full version” now shows citation markers whose definitions and backlink targets are entirely missing, including for ordinary non-deck versions.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🤖 Fixed in ec522a3. Historical snapshots append the document-wide footnote section after rendering the mixed regions. A request spec checks both reference links and definitions.
|
🤖 @codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ec522a3afb
ℹ️ 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".
| const slide = mark.closest(".deck-region .deck-slide") | ||
| if (slide) slide.dispatchEvent(new CustomEvent("coplan:deck-reveal", { | ||
| bubbles: true, detail: { slide: slide.dataset.slide } | ||
| })) |
There was a problem hiding this comment.
Reveal source-comment slides before dispatching
When j/k reaches a structural comment badge on a non-current slide (for example, a table-cell or diagram discussion), the data-source-badge branch returns before this reveal logic runs. source_comments_controller#openThread then calls scrollIntoView and positions the popover against the still-hidden, zero-sized anchor, so the thread opens without displaying its slide or highlight. Reveal the containing slide before dispatching coplan:source-thread.
AGENTS.md reference: AGENTS.md:L187-L190
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🤖 Fixed in 4439454. Comment navigation now reveals the containing slide before either the structural-badge or text-highlight path. A browser spec exercises the hidden structural badge branch.
| def down | ||
| add_column :coplan_plan_types, :behavior, :string, limit: 20, null: false, default: "document" | ||
| # Versions are immutable; removing a later user edit on rollback would | ||
| # corrupt history. The new renderer can read the wrapped content as-is. |
There was a problem hiding this comment.
Restore presentation behaviors on rollback
When this migration is rolled back as part of reverting the release, the restored column assigns document to every plan type, including all types that were previously presentations. The preceding application version gates deck rendering on PlanType#behavior == "presentation", so after the code rollback those plans render the newly inserted ::: delimiters and --- separators as ordinary Markdown instead of slides. The down path needs to preserve or reconstruct which types had presentation behavior.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🤖 Fixed in 4439454. The migration records each former presentation type and original template in metadata. Rollback restores both, and reverses the migration-created content version when it is still current.
|
🤖 @codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4439454232
ℹ️ 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".
| render_markdown(preamble + region.source, interactive:, footnotes: :exclude, source_comments: true, | ||
| line_offset: region.start_line - 1 - preamble.count("\n")) |
There was a problem hiding this comment.
Sign structural comment targets against the full plan
When a plan contains a presentation region, ordinary prose is rendered one fragment at a time here. make_checkboxes_interactive only invokes SourceTargets when line_offset is zero and signs against the passed fragment, so prose after a deck receives no table/Mermaid targets, while prose before a deck can emit tokens whose digest covers only that fragment; CommentThread verifies those tokens against plan.current_content, causing comment submission to return 422. Annotate the assembled rendering against the original complete content while translating fragment source positions.
AGENTS.md reference: AGENTS.md:L99-L103
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🤖 Fixed in 605cfb9. Mixed regions now retain translated source positions until the assembled DOM is annotated against the complete plan, then strip those positions. A helper spec verifies table and Mermaid tokens across regions against the full Markdown.
|
🤖 @codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 605cfb9b02
ℹ️ 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".
|
|
||
| def initialize(content) | ||
| @content = content | ||
| @content = content.to_s.include?("::: {.presentation") ? ContentRegions::Split.call(content).canonical_source : content |
There was a problem hiding this comment.
Strip presentation fences from plan previews
When a presentation plan has no generated summary, the workspace row falls back to plan_content_preview, which calls markdown_to_plain_text rather than this region-aware extractor (plans_helper.rb:133-148). Commonmarker treats ::: {.presentation} and the closing ::: as ordinary paragraph text, so newly created and migrated decks expose implementation syntax in their preview, typically beginning with ::: {.presentation}. Apply the same canonical-region normalization to this plain-text preview path.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🤖 Fixed in d5c7c49. Plain-text previews now parse the canonical Markdown with presentation delimiters removed, and the cache version is bumped so existing preview entries refresh. A helper spec covers the mixed preview.
|
🤖 @codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d5c7c49243
ℹ️ 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".
| <div class="plan-layout__content card" data-coplan--content-nav-target="content"> | ||
| <%= render_markdown(@version.content_markdown, interactive: false) %> | ||
| <%= render_content_regions(@version.content_markdown, interactive: false) %> | ||
| <%= render_markdown(@version.content_markdown, interactive: false, footnotes: :only) %> |
There was a problem hiding this comment.
Keep historical footnotes inside the styled Markdown root
When a historical version contains footnotes, footnotes: :only returns the bare <section data-footnotes> before render_markdown adds its .markdown-rendered wrapper (markdown_helper.rb:71-74). The footnote presentation rules are all scoped beneath .markdown-rendered (application.css:2341-2361), so every version-history References section now loses its separator, spacing, muted typography, heading sizing, and backlink styling. Wrap this output in a Markdown-rendered container or render the references through a partial with equivalent styling.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🤖 Fixed in 43790a4. Historical footnotes now sit inside a .markdown-rendered root, with a request assertion for the styled structure.
|
🤖 @codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 43790a47ee
ℹ️ 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".
|
|
||
| .deck-region { margin-block: var(--space-lg); outline: none; } | ||
| .deck-region:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 3px; } | ||
| .deck-region .deck-slide:not(.deck-slide--current) { display: none; } |
There was a problem hiding this comment.
Reveal slides for structural thread deep links
When a ?thread= link targets a table-cell or Mermaid discussion on any non-current slide, _openLinkedThread() dispatches coplan:source-thread before calling any deck-reveal path, and source_comments_controller#openThread immediately scrolls to and positions its panel against the now-display: none target. Because openThread then prevents the event, the retry is cleared and the reader remains on the wrong slide with a misplaced discussion panel; reveal the target's containing slide before handling this structural-thread path.
AGENTS.md reference: AGENTS.md:L87-L89
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🤖 Fixed in 37fd762. Structural-thread opening now reveals the target slide before scrolling or positioning the discussion. A browser test follows a deep link to a table-cell comment on slide 2.
| presentation_types = CoPlan::PlanType.where(behavior: "presentation") | ||
| presentation_types.find_each do |type| | ||
| template = type.template_content.to_s | ||
| metadata = type.metadata.to_h.merge(PRESENTATION_MARKER => { "template_content" => type.template_content }) |
There was a problem hiding this comment.
Preserve original templates across migration retries
On MySQL, if this migration fails after updating presentation types but before completing the plan backfill, those DML writes remain while the migration itself is unrecorded. A retry sees the already-wrapped template and overwrites the marker here with that wrapped value, so a later rollback restores ::: {.presentation} syntax into the previous app version's legacy presentation renderer instead of restoring the original template. Keep an existing marker rather than replacing it on retry; the copied host migration has the same issue.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🤖 Fixed in 37fd762. The migration keeps an existing original-template marker on retry, including in the copied host migration, so rollback restores the first saved template.
|
🤖 @codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 37fd762bdf
ℹ️ 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".
|
|
||
| </div> | ||
| <% if allowed_to?(@plan, :edit_content?) && !@plan.presentation? %> | ||
| <% if allowed_to?(@plan, :edit_content?) %> |
There was a problem hiding this comment.
Preserve deck slide state when closing the inline editor
When a reader advances a deck beyond slide 1 and uses the newly enabled inline editor, closing the editor replaces #plan-content-body with fresh server HTML (inline_editor_controller.js:73-86) that has no data-current-slide; each reconnected deck reader therefore defaults to slide 1. The viewport restoration can also target the now-hidden prior slide and cause a scroll jump. Capture and reapply each deck's current slide around this editor refresh, as the live-update path already does.
AGENTS.md reference: AGENTS.md:L76-L89
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🤖 Fixed in 1bbfb3f. Inline-editor refresh now captures and restores every deck reader position before returning to read mode. Live updates use the same helper, and a browser spec checks editor close on slide 3.
|
🤖 @codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1bbfb3f311
ℹ️ 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".
| root.querySelectorAll(".deck-region").forEach((region, index) => { | ||
| const slides = Array.from(region.querySelectorAll(":scope > .deck > .deck-slide")) | ||
| if (!slides.length) return | ||
| const previous = byId.get(region.id) || positions[index]?.slide || 1 |
There was a problem hiding this comment.
Match id-less decks before restoring their slide positions
When an edit or live update inserts, removes, or reorders an id-less deck, this ordinal fallback applies each old position to a different deck; because IDs are optional and the default presentation template omits one, a reader can land on an unrelated slide or have the viewport anchor restored into a newly hidden slide. The prior reset was fixed, but this fallback is fresh evidence that structural deck updates still misrestore reader state; match id-less regions by a stable content identity, or only use the ordinal fallback when the deck sequence is unchanged.
AGENTS.md reference: AGENTS.md:L76-L80
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🤖 Fixed in 9d29a36. Id-less decks now restore state by exact content or a unique heading signature, with no ordinal fallback. A browser spec reorders two decks and verifies each keeps its own slide.
|
🤖 @codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9d29a361b1
ℹ️ 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".
| function deckContent(region) { | ||
| return Array.from(region.querySelectorAll(":scope > .deck > .deck-slide"), slide => | ||
| slide.textContent.replace(/\s+/g, " ").trim()).join("\0") |
There was a problem hiding this comment.
Derive deck identity from stable source
For an id-less deck whose DOM has undergone client rendering—most clearly a Mermaid slide, where mermaid_controller replaces the source <pre> with an SVG—this records transformed textContent, while incoming live-update/editor HTML still contains the Markdown source. Exact matching then fails; if the deck has no h1–h3 heading or its heading signature is not unique, the fallback also fails and the unchanged deck resets to slide 1. Fresh evidence beyond the previous state-matching finding is that the identity is computed from DOM that Mermaid mutates; derive it from preserved source or immutable server data instead.
AGENTS.md reference: AGENTS.md:L87-L89
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🤖 Fixed in 299a4ea. Each deck now carries a digest of its source Markdown, so Mermaid or other DOM transforms do not change its identity. A browser test refreshes a heading-free deck after its DOM is replaced.
| const regions = root.matches?.(".markdown-rendered") ? [root] : | ||
| Array.from(root.querySelectorAll(".markdown-rendered")) | ||
| return regions.flatMap(region => Array.from(region.children)) |
There was a problem hiding this comment.
Traverse headings nested by split-slide layout
For a split slide whose heading is not its first block—for example, a paragraph followed by ## Results, body text, and an image—wrap_split_slides moves that heading under .deck-body, but this helper exposes only direct .markdown-rendered children. Both changed_sections_controller and live_update_controller therefore never switch to the server's results section key, so the section receives neither its changed-since-read band nor its live-update flash/cue. The earlier all-roots fix still misses these nested layout wrappers; walk the logical Markdown blocks rather than only each root's immediate children.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🤖 Fixed in 299a4ea. Section traversal now descends through split-slide body and media wrappers while keeping block order. A browser test verifies changed-section banding under a nested split-slide heading.
|
🤖 @codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 299a4eaeba
ℹ️ 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".
| match = positions.find((position, index) => !position.id && position.content === content && !used.has(index)) | ||
| } | ||
| if (!match && !region.id) { | ||
| const headings = deckHeadings(region) | ||
| const candidates = positions.filter((position, index) => !position.id && headings && position.headings === headings && !used.has(index)) |
There was a problem hiding this comment.
Preserve state when an id-less deck's identity changes
For an id-less deck, any edit changes the source digest, and editing a heading also changes the only fallback signature; a heading-free deck fails the fallback on every real content edit because headings is empty. Thus, when a collaborator updates the sole/default id-less deck while its reader is on slide 2+, both live updates and inline-editor refreshes silently reset it to slide 1. Unlike the earlier DOM-mutation case, the fresh failure here is that the immutable digest intentionally changes with the source itself; retain the position when the unmatched old/new deck is otherwise unambiguous, or provide a stable server identity independent of editable content.
AGENTS.md reference: AGENTS.md:L76-L80
Useful? React with 👍 / 👎.
What changed and why
Plans now use one Markdown document for prose and embedded slide decks. A presentation plan no longer needs a special plan type behavior, and a plan can contain more than one deck.
The
::: {.presentation}region uses the existing slide renderer inside the document. Each deck has its own reader controls and Present button. Link definitions, footnotes, and heading links still resolve across the full plan. The migration wraps existing presentation content in a new version and removes the old behavior column.Evidence
Testing
bundle exec rspecafter the follow-up review fixes: 2,278 examples, 0 failures.Generated with Codex