Skip to content

Embed presentation decks in Markdown plans - #230

Open
HamptonMakes wants to merge 13 commits into
mainfrom
hampton-embedded-presentation-regions
Open

HamptonMakes wants to merge 13 commits into
mainfrom
hampton-embedded-presentation-regions

Conversation

@HamptonMakes

@HamptonMakes HamptonMakes commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

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

A plan with prose and an embedded deck in light theme

Testing

  • Added JavaScript-enabled browser specs for independent decks, slide navigation, direct links, and presentation behavior.
  • Added service, helper, and request specs for region parsing and global references.
  • Ran bundle exec rspec after the follow-up review fixes: 2,278 examples, 0 failures.
  • Checked the changed UI in both themes. Browser screenshots of the embedded slide sometimes show a blank canvas despite normal DOM layout and computed colors; this visual issue needs follow-up.
  • Attached a feature screenshot.

Generated with Codex

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-27T01:35:55.784443Z 299a4ea Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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")) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.

@HamptonMakes

Copy link
Copy Markdown
Collaborator Author

🤖 @codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +42 to +44
const target = document.getElementById(id)
const slide = target?.closest(".deck-slide")
if (slide && this.element.contains(slide)) this.show(Number(slide.dataset.slide) - 1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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|

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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) %>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Fixed in a3abfef. Historical versions now use the mixed-region renderer with read-only controls. A request spec covers a historical deck.

@HamptonMakes

Copy link
Copy Markdown
Collaborator Author

🤖 @codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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) %>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.

@HamptonMakes

Copy link
Copy Markdown
Collaborator Author

🤖 @codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +124 to +127
const slide = mark.closest(".deck-region .deck-slide")
if (slide) slide.dispatchEvent(new CustomEvent("coplan:deck-reveal", {
bubbles: true, detail: { slide: slide.dataset.slide }
}))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.

Comment on lines +34 to +37
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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.

@HamptonMakes

Copy link
Copy Markdown
Collaborator Author

🤖 @codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +97 to +98
render_markdown(preamble + region.source, interactive:, footnotes: :exclude, source_comments: true,
line_offset: region.start_line - 1 - preamble.count("\n"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.

@HamptonMakes

Copy link
Copy Markdown
Collaborator Author

🤖 @codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.

@HamptonMakes

Copy link
Copy Markdown
Collaborator Author

🤖 @codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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) %>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Fixed in 43790a4. Historical footnotes now sit inside a .markdown-rendered root, with a request assertion for the styled structure.

@HamptonMakes

Copy link
Copy Markdown
Collaborator Author

🤖 @codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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 })

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.

@HamptonMakes

Copy link
Copy Markdown
Collaborator Author

🤖 @codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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?) %>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.

@HamptonMakes

Copy link
Copy Markdown
Collaborator Author

🤖 @codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.

@HamptonMakes

Copy link
Copy Markdown
Collaborator Author

🤖 @codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +37 to +39
function deckContent(region) {
return Array.from(region.querySelectorAll(":scope > .deck > .deck-slide"), slide =>
slide.textContent.replace(/\s+/g, " ").trim()).join("\0")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.

Comment on lines +4 to +6
const regions = root.matches?.(".markdown-rendered") ? [root] :
Array.from(root.querySelectorAll(".markdown-rendered"))
return regions.flatMap(region => Array.from(region.children))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.

@HamptonMakes

Copy link
Copy Markdown
Collaborator Author

🤖 @codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +21 to +25
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))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant