Align with TPEN messaging contract — opt into TPEN_HYDRATED_CONTEXT#16
Conversation
The parent now ships a lean TPEN_CONTEXT (project identity + URIs) on boot. Prompt templates need fully-hydrated project/page/canvas, so on receipt of TPEN_CONTEXT we send REQUEST_HYDRATED_CONTEXT upstream and acceptContext() now consumes TPEN_HYDRATED_CONTEXT instead. init() also requests hydration eagerly to handle the case where the parent posted TPEN_CONTEXT before the listener was wired up; duplicate requests are cheap and idempotent on the parent side.
The MessageHandler listener attaches in the PromptsApp constructor at DOMContentLoaded, before the parent's iframe load event fires — so the 'listener not yet wired up' scenario the eager call was guarding against can't actually happen. The TPEN_CONTEXT case in MessageHandler is the single source of truth for kicking off the hydration handshake; exactly one round trip per boot.
| // a `TPEN_CONTEXT` message that lands during `await initTemplates()` | ||
| // renders the workspace, and then this init() continuation silently | ||
| // overwrites it with the awaiting screen. | ||
| // a `TPEN_HYDRATED_CONTEXT` message that lands during |
There was a problem hiding this comment.
hm. This is kind of describing what happens when it launches, but the hydrated context isn't actually in the first init, so the comment maybe doesn't belong here. The whole block doesn't seem much like jsdoc and more like narration. The async story feels like thinking, not documenting.
There was a problem hiding this comment.
Agreed — trimmed the init() JSDoc to one line and tightened the inline 'paint sync' comment to just the load-bearing why in f49ba2a.
| * @param {string|null} lineId full line IRI or null. | ||
| */ | ||
| updateCurrentLine(lineId) { | ||
| this.ui.updateCurrentLine(lineId ? (trailingId(lineId) ?? '') : '') |
There was a problem hiding this comment.
There is a lot of support here for showing what line you are on, but I don't think it is ever used for anything...
updateCurrentLine(lineID) {
this.state.lineID = lineID ?? ''
this.state.line = lineID && this.state.page?.items
? (this.state.page.items.find(it => trailingId(it) === lineID) ?? null)
: null
if (this.#lineMetaValue) {
this.#lineMetaValue.textContent = this.state.lineID
? labelOf(this.state.line, this.state.lineID)
: '(none selected)'
}
}
There was a problem hiding this comment.
It's used by ui-manager.js — state.lineID and state.line drive the workspace's 'Line' meta row (line 377 / 703-712) so it stays in sync as the user navigates lines in the parent. Templates don't reference it directly today, but state.line is exposed for any future template that wants the active annotation. Happy to remove the machinery if you'd rather not carry that surface area.
| * `REQUEST_HYDRATED_CONTEXT` reply and the parent answers with | ||
| * `TPEN_HYDRATED_CONTEXT` carrying the hydrated objects. | ||
| * | ||
| * Line navigation arrives as `UPDATE_CURRENT_LINE` deltas. The token is |
There was a problem hiding this comment.
mentioned elsewhere, but why is current line relevant?
There was a problem hiding this comment.
Reworked the file docstring in f49ba2a — it no longer enumerates the protocol. The file just routes inbound parent messages into PromptsApp; relevance of any particular message lives at the call site.
| case 'TPEN_CONTEXT': | ||
| // Lean payload (project identity + URIs). Templates wait for | ||
| // TPEN_HYDRATED_CONTEXT, so we just request hydration here. | ||
| this.requestHydratedContext() |
There was a problem hiding this comment.
Thanks 👍 — note that this case has since rewritten in f49ba2a. The lean TPEN_CONTEXT arrival now triggers requestPopulatedContext() (which posts both REQUEST_POPULATED_PROJECT and REQUEST_POPULATED_PAGE), so the inline comment got slimmer too.
Replace REQUEST_HYDRATED_CONTEXT / TPEN_HYDRATED_CONTEXT (one envelope with a mismatched grab-bag shape) with two request/reply pairs whose names match their projection: REQUEST_POPULATED_PROJECT → TPEN_POPULATED_PROJECT, REQUEST_POPULATED_PAGE → TPEN_POPULATED_PAGE. MessageHandler accumulates both halves into a populated bag and only calls app.acceptContext once both have arrived, so the workspace renders once with the full bundle. Mirrors parent change in CenterForDigitalHumanities/TPEN-interfaces#564. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cubap flagged the init() JSDoc and the message-handler file-level docstring as narration rather than documentation. Trim both: - main.js: init() JSDoc reduced to one line; the inline "paint sync" comment kept (the WHY is non-obvious) but tightened. - message-handler.js: file-level docstring no longer enumerates the whole protocol. Describes what this file does (route inbound parent messages into PromptsApp) and keeps the non-obvious parentOrigin capture rule. Addresses cubap's review on PR #16. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the partial has* flag reset with a full accumulator reset, so stale project/page/canvas/currentLineId fields can't leak into a future bundle if the flush gate is ever loosened.
Summary
Align TPEN-Prompts with the canonical TPEN messaging contract. The lean
TPEN_CONTEXTboot payload doesn't carry the populated project/page/canvas objects this tool needs for prompt-template rendering, so on receipt ofTPEN_CONTEXTit now sendsREQUEST_POPULATED_PROJECTandREQUEST_POPULATED_PAGEupstream and renders once bothTPEN_POPULATED_PROJECTandTPEN_POPULATED_PAGEreplies have arrived. The full contract is defined in CenterForDigitalHumanities/TPEN-interfaces#564.Changes
message-handler.jsTPEN_POPULATED_PROJECTandTPEN_POPULATED_PAGEcases. The handler accumulates both halves into apopulatedbag and only callsapp.acceptContext({ project, page, canvas, currentLineId })once both have landed — so the workspace renders once, with the full bundle.TPEN_CONTEXTcase now triggersrequestPopulatedContext(), which posts bothREQUEST_POPULATED_PROJECTandREQUEST_POPULATED_PAGE.requestHydratedContext()/TPEN_HYDRATED_CONTEXTpath is removed.main.jsacceptContextJSDoc updated to describe the split-then-bundle boot flow.Coordinated cut
Hard cut, all PRs must merge together. Full contract and other PRs:
Test plan
Developer-validated locally on 2026-05-08 against CenterForDigitalHumanities/TPEN-interfaces#564 on
:4000, services on:3012:jekyll s), add TPEN-Prompts to a project's tools. Open/transcribe.TPEN_CONTEXT(parent → tool), then exactly oneREQUEST_POPULATED_PROJECTand oneREQUEST_POPULATED_PAGE(tool → parent), and oneTPEN_POPULATED_PROJECT+ oneTPEN_POPULATED_PAGEreply (parent → tool). No re-requests on the same boot.UPDATE_CURRENT_LINEarrives and the tool's current-line state updates.REQUEST_TPEN_ID_TOKEN, parent toast confirms, inboundTPEN_ID_TOKEN, prompts use the token.?projectID=...) still works.allow="clipboard-write"on the parent's<iframe>element), so the existingdocument.execCommand('copy')fallback engages and copy succeeds. The[Violation]console warning is benign — kept the fallback rather than expanding the contract scope.