Skip to content

proposal: add entrypoint-based custom export api - #114

Open
maxwellpeterson wants to merge 5 commits into
mainfrom
mpeterson/gadget-export-entrypoint-api
Open

proposal: add entrypoint-based custom export api#114
maxwellpeterson wants to merge 5 commits into
mainfrom
mpeterson/gadget-export-entrypoint-api

Conversation

@maxwellpeterson

Copy link
Copy Markdown
Member

This PR proposes a new file export API for Gadgets. Currently, Gadgets can only be exported to PDF by rendering their UI in a remote browser and printing the page:

image

We want Gadgets to be able to register their own custom export formats. For example, a document editor Gadget could support Markdown, HTML, and PDF exports, and a data analysis Gadget could support CSV and XLSX exports:

image

The API interface described in this PR allows Gadgets to customize their own export behavior, and has several desirable attributes:

  • All Gadgets can be exported as HTML or PDF by default without implementing the export handler interface
  • Gadgets can support custom file formats as well as multiple HTML or PDF variants (e.g. with different content) by implementing the export handler interface
  • Gadgets can disable or reimplement the default HTML and PDF export formats for additional customization if needed
  • The export handler interface implemented by the Gadget is only callable by the workshop backend and does not change the public RPC interface of the Gadget

This PR exists to illustrate the proposed API interface. It's meant for discussion, and not intended to be merged as-is.

@ask-bonk ask-bonk 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.

This is a clean, well-documented API proposal, and the entrypoint-based approach is the right shape: it keeps the export handler off the gadget's public RPC surface (only the workshop backend can call it) and reuses the existing named-WorkerEntrypoint mechanism the hooks path already uses (getGadgetHookEntrypointstub.getEntrypoint(name) in overseer.ts). The getExportFormats(gadget) / export(gadget, id) signatures taking a Fetcher<Gadget> match the facet-passing pattern that exportPdf/connectToGadget already use. Since this is explicitly for discussion, here's design-level feedback rather than a merge review.

The naming collision with "output formats" is the biggest concern

The kernel already has a first-class concept called output format (BlueprintOutput, OutputFormatOffer, listOutputFormats(), the AdminFormat/Formats admin panel, packages/workshop-frontend/src/components/format/). That's "what a blueprint produces" (Document / Slides / Spreadsheet). This PR introduces GadgetExportFormat / GadgetClientExportFormat / getExportFormats() meaning something entirely different (file export targets: HTML/PDF/CSV/...).

Two things called "format" that are unrelated will be a persistent source of confusion for reviewers and future readers of the kernel. Worth considering GadgetExportTarget / ExportOption / getExportOptions(), or otherwise disambiguating in the type names — not just the doc comments.

How does GadgetClientExportApi attach to the RPC surface?

GadgetClient (api.ts:2724) already owns exportPdf(chatId?), and the "use" role doc (api.ts:2824) specifically enumerates getUiBundle(), connectToGadget(), and exportPdf() as the use-role surface. It's unclear how the new GadgetClientExportApi relates:

  • Are getExportFormats/export intended to be added to GadgetClient (replacing/superseding exportPdf)? If so, the proposal should say so, and address the migration of exportPdf (deprecate? keep as sugar for export("pdf")?).
  • Or is GadgetClientExportApi a separate capability returned by some accessor? Then, per the RPC conventions here, it should extend RpcTarget (every other client-facing RPC interface in this file does — WorkpieceClient, PublicApi, Overseer, etc.), and the proposal should show where it's minted.

As written it's a bare interface with no RpcTarget and no attachment point, so a reader can't tell how the client actually reaches it. Given exportPdf already lives on GadgetClient with the same chatId? parameter, folding these two methods into GadgetClient and retiring exportPdf seems like the most natural fit and avoids a parallel capability.

globalThis.gadgetExportFormatId deserves scrutiny

Browser-mode variants signal the selected id via globalThis.gadgetExportFormatId in the remotely-rendered UI. A few questions for the design:

  • The current renderer (browser-export.ts) serves a locked-down CSP document and injects the gadget's jsCode. Where/when is gadgetExportFormatId set relative to the gadget's own bootstrap, and is it guaranteed to be readable before the gadget renders? A global that races the app's first paint is fragile.
  • globalThis on an untyped magic name is awkward to consume from a typed gadget. Consider whether this belongs in the same injected surface the gadget already gets, rather than a free-floating global — and if it stays, ship an ambient declaration so gadget authors get types.
  • Naming: everything else here is "export format"; the global is gadgetExportFormatId. Keep them consistent with whatever the type rename lands on.

Smaller points

  • getExportFormats uniqueness across modes is a contract the backend must enforce. The doc says ids "must be unique across both modes" and that a gadget "should return" the default browser formats unless opting out. Since a gadget authors this, the backend needs to (a) reject/de-dupe colliding ids and (b) decide what happens when the entrypoint is present but returns [] or omits the method — worth stating the backend's validation and fallback rules in the proposal, since that's kernel behavior reviewers will care about.
  • Default-format opt-out semantics. "return these ... unless it specifically does not want to support browser-mode HTML or PDF" — so the defaults are injected only when ExportHandler/getExportFormats is absent, and a present handler is fully authoritative (can drop PDF)? Please make that explicit; it's the crux of the "disable/reimplement defaults" feature bullet.
  • HTML browser-mode renderer doesn't exist yet. browser-export.ts only implements renderGadgetPdf; contentType: "text/html" implies a new capture path. Fine for a proposal, but flag it as required follow-up work (and note the CSP/settle guarantees must hold for HTML capture too).
  • export() returns ReadableStream<Uint8Array> but carries no filename. GadgetClientExportFormat has label + fileExtension; the frontend's makeExportFilename(gadgetTitle, ext) presumably builds the name. Confirm the custom-mode handler never needs to influence the filename beyond the extension (e.g. a report named by date).
  • Stream limits / abuse. Custom-mode export() returns a gadget-authored stream. The PDF path enforces MAX_EXPORT_DURATION_MS / MAX_EXPORT_BYTES. The same bounding must wrap custom streams (gadget code is untrusted); worth noting in the proposal.
  • Doc-comment consistency. Per the repo's kernel bar, every exported member of the shared public API needs a doc comment. GADGET_EXPORT_ENTRYPOINT has one; good. The two block comments inside the GadgetExportFormat union use // inline comments rather than /** */ on the members — minor, but the reviewers here read every line of shared API changes.
  • export.ts imports DurableObject only for the Gadget default type param. Fine, just confirming it's intentional that GadgetExportEntrypoint<Gadget extends DurableObject = DurableObject> defaults to the base DurableObject (the gadget's real DO class isn't known in shared code).

Overall: the capability model (backend-only caller, entrypoint not on the public RPC interface, defaults-when-absent) is sound and matches how hooks already work. The main things to resolve before this becomes an implementation PR are the "format" naming collision with the existing output-format concept and how GadgetClientExportApi attaches to the client RPC surface vs. the existing exportPdf.

@ask-bonk

ask-bonk Bot commented Aug 10, 2026

Copy link
Copy Markdown

Posted a design review on PR #114. Summary of what I flagged:

Positives

  • The entrypoint-based approach is the right shape — keeps the export handler off the gadget's public RPC surface and reuses the existing named-WorkerEntrypoint mechanism (like getGadgetHookEntrypoint). The Fetcher<Gadget> signatures match the existing facet-passing pattern.
  • Confirmed the PR is purely additive/unwired — nothing imports it yet.

Main design concerns

  1. "format" naming collision — the kernel already has a first-class "output format" concept (BlueprintOutput, listOutputFormats(), the Formats admin panel). Two unrelated things called "format" will confuse reviewers of the kernel.
  2. GadgetClientExportApi attachment is undefinedGadgetClient already owns exportPdf(chatId?) and the "use" role enumerates it. The proposal doesn't say whether the new methods fold into GadgetClient (superseding exportPdf) or are a separate capability (which would need to extend RpcTarget like every other client-facing interface).
  3. globalThis.gadgetExportFormatId — races the gadget's bootstrap, is untyped, and is inconsistent with the injected-surface pattern.

Smaller points: backend must enforce id-uniqueness/validation and define the defaults opt-out semantics; the HTML browser-mode renderer doesn't exist yet (browser-export.ts is PDF-only); custom-mode streams need the same MAX_EXPORT_BYTES/duration bounding since gadget code is untrusted; filename handling; and doc-comment style on the union members per the kernel bar.

No code changes were made — this PR is explicitly a proposal for discussion, so I reviewed it as a design.

github run

@maxwellpeterson maxwellpeterson changed the title add entrypoint-based custom export api proposal: add entrypoint-based custom export api Aug 10, 2026
@maxwellpeterson
maxwellpeterson marked this pull request as ready for review August 10, 2026 16:35

@ask-bonk ask-bonk 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.

Follow-up on the current revision. Since the last review the doc comments tightened up several of the earlier concerns — getExportFormats is now stated as mandatory (Workshop rejects a missing method rather than applying defaults), GadgetClientExportMethods explicitly folds into GadgetClient and supersedes exportPdf, and the 30s / 100MB bounds are now called out for custom streams. Good. A few things remain worth resolving before this becomes an implementation PR.

The "format" naming collision is still the biggest issue

The kernel already owns a first-class output format concept — BlueprintOutput / OutputFormatOffer / Overseer.listOutputFormats() (api.ts:420, api.ts:1063, api.ts:1076), the AdminFormat/Formats admin panel, and workshop-frontend/src/components/format/. That's "what a blueprint produces" (Document / Slides / Spreadsheet). This PR reuses the same word for "file export targets" (HTML/PDF/CSV). getExportFormats() on the client will sit a few hundred lines from listOutputFormats() on the same Overseer/client surface, and both return ...Format[]. That's a real hazard for the kernel reviewers who read every line here.

Recommend disambiguating in the type names, not just prose: GadgetExportTarget / GadgetClientExportTarget / getExportTargets() (or ExportOption / getExportOptions()), and rename the magic global to match whatever lands.

GadgetClientExportMethods folds into GadgetClient — verify the two consumers of exportPdf

The doc now commits to replacing GadgetClient.exportPdf() (api.ts:2745). When this is implemented, two spots must move together:

  • The "use" role doc at api.ts:2824-2825 enumerates exportPdf() as part of the use-role surface — it needs to name getExportFormats()/export() instead.
  • overseer.ts has two exportPdf implementations (overseer.ts:9054 and overseer.ts:9301). Both are the backend caller that renderGadgetPdf backs.

Since export.ts is a standalone module, it can't structurally enforce that GadgetClient actually adopts GadgetClientExportMethods. Consider having GadgetClient extends GadgetClientExportMethods directly (in api.ts) so the intent is checked by the compiler rather than living only in a comment in a sibling file.

globalThis.gadgetExportFormatId vs. the actual render path

Grounding this against browser-export.ts: renderGadgetPdf responds to the navigation request with makeExportHtml(clientCode) under a locked-down CSP (browser-export.ts:293-300), opens the RPC session, then waitForDomSettled (browser-export.ts:308-312). For the global to be reliable it has to be set before the gadget's bundle executes — i.e. injected into makeExportHtml ahead of clientCode, not set post-load — otherwise it races first paint. Please state that in the design, and ship an ambient declare global so gadget authors reading it get a type instead of consuming an untyped magic name.

Browser-mode is PDF-only today

Confirming the follow-up scope: browser-export.ts only implements renderGadgetPdf (PDF via page.createPDFStream, browser-export.ts:318). contentType: "text/html" needs a new capture path that reuses the same deadline / MAX_EXPORT_BYTES / releaseWhenSettled machinery (browser-export.ts:249, 324) and the same CSP/settle guarantees. Worth flagging as required work in the proposal.

Smaller points

  • Custom-mode stream bounding is documented but must be wired. The 30s/100MB text on export() matches MAX_EXPORT_DURATION_MS/MAX_EXPORT_BYTES, but those constants are currently private to browser-export.ts (browser-export.ts:14,16). Since a gadget-authored custom stream is untrusted, the same limitStream + deadline wrapping must apply on the custom path — probably by lifting those limits/helpers to a shared spot.
  • Doc-comment style on the union members. The three-way union in GadgetExportFormat still has leading // section comments ("Gadget-facing export API:", "Export API exposed to the Workshop UI:"). The exported members are documented with /** */, which meets the bar; just confirming the inline // dividers are intentional stylistic separators and not meant to document exported members.
  • getExportFormats() returning [] or an all-custom list. The doc covers "method absent → defaults" and "method present → authoritative (may drop PDF)". Please also pin down: (a) is an empty array legal (gadget exposes no export options, UI hides the button)? and (b) backend id de-dup/validation of the returned list, since a gadget authors it.
  • Fetcher<Gadget extends DurableObject = DurableObject> default to the base DurableObject is fine for shared code (the real DO class isn't known here) — matches the getGadgetHookEntrypoint pattern in overseer.ts. No change needed, just confirming.

Net: the capability shape (backend-only caller, entrypoint off the public RPC surface, defaults-when-absent) is right and the recent doc tightening resolved most of the earlier ambiguity. The naming collision and the mechanical exportPdfexport/getExportFormats migration (both overseer.ts sites + the "use" role doc) are the two things I'd want nailed down before implementation.

@ask-bonk

ask-bonk Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review posted to PR #114.

Since this is an explicitly design-only proposal (150 additions, purely additive/unwired), I reviewed it as a design rather than making code changes. My follow-up focused on what the current revision (SHA 447dbb0) still leaves open, avoiding re-flagging the points the doc comments already tightened since the prior review.

What's now resolved in the current revision (so I didn't re-raise): getExportFormats is documented as mandatory (Workshop rejects a missing method vs. applying defaults), GadgetClientExportMethods explicitly folds into GadgetClient and supersedes exportPdf, and the 30s/100MB limits are documented for custom streams.

What I flagged as still open, grounded in the current code:

  1. "format" naming collision — the kernel already owns BlueprintOutput/OutputFormatOffer/listOutputFormats() (api.ts:420, 1063, 1076) meaning "what a blueprint produces." Reusing "format" for file export targets on the same client surface is a real hazard. Recommended renaming the types (e.g. GadgetExportTarget/getExportTargets()).

  2. exportPdf → new-methods migration is mechanical and must move together — the "use" role doc (api.ts:2824-2825) enumerates exportPdf(), and there are two exportPdf impls in overseer.ts (9054, 9301). Suggested GadgetClient extends GadgetClientExportMethods so the compiler enforces adoption instead of a cross-file comment.

  3. globalThis.gadgetExportFormatId races bootstrap — grounded against browser-export.ts:293-312, it must be injected into makeExportHtml before clientCode runs, plus ship an ambient declare global.

  4. Browser-mode is PDF-only today (renderGadgetPdf at browser-export.ts:318) — text/html needs a new capture path reusing the same deadline/MAX_EXPORT_BYTES/releaseWhenSettled machinery.

  5. Smaller: the untrusted custom stream must actually be wrapped in limitStream+deadline (constants currently private to browser-export.ts:14,16); empty-array/de-dup semantics for getExportFormats(); and confirmation the inline // dividers are intentional.

github run

@ask-bonk

ask-bonk Bot commented Aug 10, 2026

Copy link
Copy Markdown

UnknownError: ProviderInitError

github run

@ask-bonk

ask-bonk Bot commented Aug 10, 2026

Copy link
Copy Markdown

@maxwellpeterson Bonk workflow failed. Check the logs for details.

View workflow run · To retry, trigger Bonk again.

@ask-bonk

ask-bonk Bot commented Aug 10, 2026

Copy link
Copy Markdown

Follow-up on the current revision (SHA 371ff37). The two new commits since my last review (050976b "switch to export target terminology", 371ff37 "fix confusing target wording") resolve the largest concern — the "format" naming collision is gone: the types are now GadgetExportTarget / GadgetClientExportTarget / getExportTargets() and the magic global was renamed to gadgetExportTargetId, so nothing on the client surface now collides with the kernel's existing BlueprintOutput / OutputFormatOffer / listOutputFormats() (api.ts:420, 1063, 1076). That was the #1 thing I wanted nailed down before implementation, and it's done. Two doc-level items were also tightened: an empty list from getExportTargets() is now stated to disable exports (export.ts:90-91), and the global is now documented as injected before the Gadget UI module evaluates (export.ts:26-28, 45-46), which is exactly the ordering guarantee I asked for against the render path.

A few things remain open before this becomes an implementation PR.

One stale name left in prose

The rename is complete in the type/method signatures, but the getExportTargets doc comment still refers to the old method name twice in prose:

Every ExportHandler entrypoint must implement this method. The Workshop rejects a missing getExportFormats method instead of applying defaults.

(export.ts:89-90) — should read getExportTargets. Minor, but this is shared API and the kernel reviewers read every line here.

GadgetClientExportMethods folds into GadgetClient — still a cross-file comment, and the migration is mechanical

export.ts:136-138 still documents that these methods "will be added directly to GadgetClient ... replace the existing GadgetClient.exportPdf()", but GadgetClient (api.ts:2724) still declares exportPdf(chatId?) (api.ts:2745) and does not extend GadgetClientExportMethods. That's correct for a proposal (it's unwired), but for the implementation PR I'd still recommend GadgetClient extends GadgetClientExportMethods in api.ts so the compiler enforces adoption rather than a comment in a sibling module. When it lands, three spots must move together:

  • GadgetClient.exportPdf() at api.ts:2745,
  • the "use" role doc at api.ts:2825 which enumerates exportPdf() as part of the use-role surface,
  • the two backend implementations at overseer.ts:9054 and overseer.ts:9301.

gadgetExportTargetId — the doc now states the ordering; still needs an ambient declare global

The "injected before the Gadget UI module evaluates" wording (export.ts:26-28, 45-46) addresses the bootstrap-race concern against browser-export.ts — the global has to be set inside makeExportHtml (browser-export.ts:133) ahead of clientCode, before waitForDomSettled (browser-export.ts:203). What's still missing is a typed surface: there's no declare global for gadgetExportTargetId in export.ts, so a typed gadget still consumes an untyped magic name. Ship the ambient declaration alongside the implementation.

Follow-up work still not present (fine for a proposal, flagging as required)

  • Browser-mode is still PDF-only. browser-export.ts implements only renderGadgetPdf (browser-export.ts:243) via page.createPDFStream (browser-export.ts:318). contentType: "text/html" needs a new capture path reusing the same deadline / MAX_EXPORT_BYTES / releaseWhenSettled machinery (browser-export.ts:249, 324).
  • Custom-mode stream bounding must be wired, and the limits shared. The 30s/100MB text on export() matches MAX_EXPORT_DURATION_MS / MAX_EXPORT_BYTES, but those are still private module constants in browser-export.ts (browser-export.ts:14, 16). limitStream is already exported (browser-export.ts:155); since a gadget-authored custom stream is untrusted, the same limitStream + deadline wrapping must apply on the custom path, which likely means lifting the two limit constants to a shared spot.
  • Backend id de-dup/validation of the gadget-returned target list (the ids "must be unique" contract is authored by untrusted gadget code).

Confirmed / no change needed

  • The inline // section dividers ("Gadget-facing export API:" / "Export API exposed to the Workshop UI:") are stylistic separators; the exported members are all /** */-documented, which meets the bar.
  • Fetcher<Gadget extends DurableObject = DurableObject> defaulting to base DurableObject matches the getGadgetHookEntrypoint pattern — fine for shared code.

Net: the naming collision is resolved and the ordering guarantee is documented — the two biggest items from prior rounds are closed. Remaining before implementation: the stale getExportFormats prose reference, the declare global, and the mechanical exportPdfgetExportTargets/export migration wired with GadgetClient extends GadgetClientExportMethods so it's compiler-checked.

@ask-bonk

ask-bonk Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review posted to PR #114.

Since this is an explicitly design-only proposal (purely additive/unwired, 2 files), I reviewed the current revision (SHA 371ff37) as a design rather than making code changes. The two commits since my last review (050976b, 371ff37) landed the "export target" rename, so I focused on what's newly resolved vs. what's still open, grounding every point against the current code.

What's now resolved (so I didn't re-raise):

  • The "format" naming collision — the top concern across both prior reviews — is gone. Types are now GadgetExportTarget/GadgetClientExportTarget/getExportTargets() and the global is gadgetExportTargetId, so nothing collides with the kernel's existing BlueprintOutput/OutputFormatOffer/listOutputFormats().
  • Empty-list-disables-exports semantics are now documented.
  • The global is now documented as injected before the Gadget UI module evaluates, which is the bootstrap-ordering guarantee I asked for against browser-export.ts.

What I flagged as still open:

  1. One stale getExportFormats prose reference left in the getExportTargets doc comment (export.ts:89-90) — the rename missed it.
  2. GadgetClientExportMethodsGadgetClient fold is still a cross-file comment; recommended GadgetClient extends GadgetClientExportMethods so the compiler enforces the mechanical exportPdf migration (api.ts:2745, api.ts:2825 use-role doc, overseer.ts:9054/9301).
  3. gadgetExportTargetId still needs an ambient declare global for a typed surface.
  4. Required follow-up work: browser-mode is still PDF-only; custom-stream bounding must be wired with the limit constants lifted to a shared spot; backend id de-dup/validation.

github run

@ask-bonk

ask-bonk Bot commented Aug 10, 2026

Copy link
Copy Markdown

Follow-up on the current revision (SHA 83beace). One new commit since my last review — 83beace "fix missing format => target rename" — and it lands exactly the one item I flagged: the stale getExportFormats prose reference in the getExportTargets doc comment is now corrected to getExportTargets (export.ts:90). Verified against the file: no getExportFormats occurrences remain.

Re-grounding the rest against the current code, nothing else changed, so the standing items are unchanged:

Still open (all fine for a proposal; flagging as the implementation-PR checklist)

  1. gadgetExportTargetId still has no ambient declare global. The ordering guarantee is documented — "injected before the Gadget UI module evaluates" (export.ts:27-28, 45-46) — which addresses the bootstrap race against the render path (the global must be set inside makeExportHtml ahead of clientCode, browser-export.ts:243/318). But there's still no typed surface: the only references to gadgetExportTargetId in export.ts are in prose (export.ts:27, 45). A typed gadget still consumes an untyped magic name. Ship the declare global alongside the implementation.

  2. GadgetClientExportMethodsGadgetClient fold is still a cross-file comment. export.ts:135-139 documents that these methods replace GadgetClient.exportPdf(), but GadgetClient (api.ts:2724) still declares exportPdf(chatId?) (api.ts:2745) and does not extend GadgetClientExportMethods. Correct for an unwired proposal. For the implementation PR, GadgetClient extends GadgetClientExportMethods in api.ts makes the compiler enforce adoption. When it lands, three spots move together:

    • GadgetClient.exportPdf() at api.ts:2745,
    • the "use" role doc at api.ts:2825 which enumerates exportPdf(),
    • the two backend impls at overseer.ts:9054 and overseer.ts:9301.
  3. Browser-mode is still PDF-only. browser-export.ts implements only renderGadgetPdf (browser-export.ts:243) via page.createPDFStream (browser-export.ts:318). contentType: "text/html" needs a new capture path reusing the same deadline / MAX_EXPORT_BYTES / releaseWhenSettled machinery.

  4. Custom-mode stream bounding must be wired, and the limits shared. The 30s/100MB text on export() (export.ts:105) matches MAX_EXPORT_DURATION_MS / MAX_EXPORT_BYTES, but those are still private module constants (browser-export.ts:14, 16). limitStream is exported (browser-export.ts:155); since a gadget-authored custom stream is untrusted, the same limitStream + deadline wrapping must apply on the custom path — likely lifting the two limit constants to a shared spot.

  5. Backend id de-dup/validation of the gadget-returned target list — the "unique, non-empty" contract (export.ts:25, 30, 60) is authored by untrusted gadget code, so the backend must enforce it.

Confirmed / no change needed

  • The naming collision is resolved and stays resolved: types are GadgetExportTarget / GadgetClientExportTarget / getExportTargets() and the global is gadgetExportTargetId, none colliding with the kernel's BlueprintOutput (api.ts:1063) / OutputFormatOffer (api.ts:1076) / listOutputFormats() (api.ts:420).
  • Empty-list-disables-exports is documented (export.ts:91).
  • The inline // section dividers (export.ts:3, 112) are stylistic separators; the exported members are all /** */-documented, which meets the bar.
  • Fetcher<Gadget extends DurableObject = DurableObject> (export.ts:82) defaulting to base DurableObject matches the getGadgetHookEntrypoint pattern.

Net: the two biggest items from prior rounds (the "format" naming collision and the stale prose) are both closed. What remains is the implementation-PR checklist above — the declare global, the compiler-enforced exportPdfgetExportTargets/export migration, and the two follow-up render/stream paths — none of which is expected in an explicitly design-only proposal.

@ask-bonk

ask-bonk Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review posted to PR #114.

Since the last review, one new commit (83beace "fix missing format => target rename") landed. I verified it against the current code and reviewed the current revision as a design (still explicitly proposal-only, purely additive/unwired).

Newly resolved (verified):

  • The stale getExportFormats prose reference I flagged last round is fixed — 83beace corrected it to getExportTargets (export.ts:90), and no getExportFormats occurrences remain in the file.

Confirmed still resolved: the "format" naming collision (types are GadgetExportTarget/GadgetClientExportTarget/getExportTargets(), no clash with BlueprintOutput/OutputFormatOffer/listOutputFormats()), empty-list semantics, and the injection-ordering doc.

Still open (implementation-PR checklist, fine for a proposal):

  1. gadgetExportTargetId still needs an ambient declare global — only prose references exist (export.ts:27, 45).
  2. GadgetClientExportMethods fold is still a cross-file comment; GadgetClient (api.ts:2724) still declares exportPdf (api.ts:2745). Recommended GadgetClient extends GadgetClientExportMethods so the migration (api.ts:2745, api.ts:2825, overseer.ts:9054/9301) is compiler-enforced.
  3. Browser-mode is still PDF-only (renderGadgetPdf, browser-export.ts:243/318); text/html needs a new capture path.
  4. Custom-stream bounding must be wired with the limit constants (browser-export.ts:14,16) lifted to a shared spot.
  5. Backend id de-dup/validation of the untrusted target list.

The two biggest items across all rounds (naming collision + stale prose) are now closed.

github run

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