Add experimental Presentation API presenter plugin#1
Conversation
Introduces an opt-in experimental presenter plugin that uses the W3C Presentation API instead of window.open(), letting the browser render the audience view on a secondary display or Cast device and syncing the two views over a PresentationConnection. Includes silent reconnect of the presenter view after a reload via PresentationRequest.reconnect() + a sessionStorage-persisted presentation id. Enable per-deck with the `experimental-presentation-api` body class, which also disables the classic presenter plugin so the two never load together; without the class the classic window.open presenter remains the fallback. The transport-independent presenter UI (notes, next-slide preview, timing) is extracted into a shared presenter/presenter-ui.js module that both the classic and experimental plugins import, keeping behavior identical.
DmitrySharabin
left a comment
There was a problem hiding this comment.
Review
The core design is sound — I verified against @inspirejs/core that:
goto()accepts bare string slide ids ✓slidechangehook fires synchronously insidegoto(), so theapplyingRemoteecho-suppression guard works ✓- All slides get auto-generated ids in
init(), soInspire.currentSlide.idis always a non-empty string ✓ gotoItemalso fires its hook synchronously, so item sync is echo-safe too ✓
The shared presenter-ui.js extraction is clean, and the autoload selector trick is clever.
Two issues worth discussing before merging, one functional and one dead-code.
| console.warn( | ||
| "[presenter2] The Presentation API is not supported in this browser. " + | ||
| "Remove the `experimental-presentation-api` class to use the classic presenter.", | ||
| ); |
There was a problem hiding this comment.
Bug: cancelled picker leaves presenter view stuck
enterPresenterView() runs on line 115 before start() resolves. If the user cancels the display picker (or no display is available), the .catch(() => {}) on line 122 silently swallows the rejection — but the body already has presenter + show-next classes and notes are open, with no audience view and no way to revert short of reloading.
The same gap exists on disconnect: drop() (line 72) nulls transport and clears sessionStorage, but never reverts the presenter UI.
Suggested fix — move enterPresenterView() into the .then() (like the reconnect path already does correctly on line 100), and add a leavePresenterView() counterpart called from drop() when isController:
// keyup handler
new PresentationRequest([location.href]).start()
.then(connection => {
enterPresenterView();
wireConnection(connection, true);
window.focus();
})
.catch(() => {}); // picker cancelled — no UI change needed nowFor drop(), something like:
let drop = () => {
transport = null;
if (isController) {
sessionStorage.removeItem(STORAGE_KEY);
document.body.classList.remove("presenter", "show-next");
}
};| } | ||
|
|
||
| if (Inspire.projector) { | ||
| if (Inspire.projector || document.body.classList.contains("presenter")) { |
There was a problem hiding this comment.
Likely dead code — this check can never be true at evaluation time
This module runs at import time (top-level for loop, no hooks). At that point, neither Inspire.projector nor the presenter body class is set — both are added later by user interaction (Ctrl+P) or by the init-end hook (reconnect path).
The original Inspire.projector check had the same problem, so this isn't a regression — but adding a second never-true condition makes it look intentional. In practice, notes are opened by enterPresenterView() and onPresenterSlidechange() in the presenter plugins, so this line is redundant.
Worth either removing the whole if block (since it's always false) or, if the intent is to support late-loaded plugins where the class might already be set, adding a comment explaining the scenario.
- Move enterPresenterView() into start().then() so cancelling the display picker doesn't leave the page in presenter mode - Tear down presenter classes on connection close/terminate Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Introduces a new experimental presenter plugin (
presenter2) built on the W3C Presentation API as an alternative to the classic window.open-based presenter. This allows presenters to render slides on secondary displays or Cast devices with automatic reconnection support.Key Changes
New
presenter2/plugin.js: Experimental presenter plugin using the Presentation API instead ofwindow.open(). Features include:sessionStorageNew
presenter/presenter-ui.js: Extracted transport-independent presenter UI logic shared between classic and experimental plugins:enterPresenterView(): Initializes presenter view styling and opens speaker notesonPresenterSlidechange(): Handles speaker notes display and timing calculationsUpdated
presenter/plugin.js: Refactored to use shared UI functions frompresenter-ui.js, reducing code duplicationUpdated
plugin-autoload.js: Added conditional autoloading:presenterplugin loads whendetails.notesexist andexperimental-presentation-apiclass is absentpresenter2plugin loads whendetails.notesexist andexperimental-presentation-apiclass is presentUpdated
details-notes/plugin.js: Extended presenter detection to recognize bothInspire.projectorand thepresenterclass on bodyNew
presenter2/README.md: Documentation covering usage, benefits, requirements, and limitationsNew
presenter2/plugin.css: Stylesheet that reuses classic presenter styling via importImplementation Details
PresentationRequestAPI for display selection andPresentationConnectionfor bidirectional messagingapplyingRemote) to prevent echoing remote updates back to the other viewsessionStorageto enable silent reconnection after reloadhttps://claude.ai/code/session_01PLrW3z9316nvWakLoqYaY5