Summary
Downloading a builtin template (blank-dark / blank-light) does nothing on the
/templates page of the Web UI. Nothing is saved and no error is shown.
Downloading a user-uploaded template works fine.
Affected
- Builtin template download only (cloud mode)
- User template download is unaffected
- Deck PPTX download is unaffected
Steps to reproduce
- Open the
/templates page in the Web UI
- Click the download button on a builtin template (blank-dark or blank-light)
- Nothing happens — no file is saved, no error appears
Cause
The download path depends on per-bucket CORS configuration, which is not
satisfied for the builtin bucket.
download_template in api/index.py returns a presigned URL from two
different buckets:
- User templates → PPTX bucket (has a CORS configuration)
- Builtin templates → resource bucket (has no CORS configuration)
Meanwhile downloadTemplate in web-ui/src/services/deckService.ts retrieves the
presigned URL from the browser via fetch() → blob(), which requires CORS.
So only the builtin path is blocked by the browser. The files do exist in S3, so
this is not a 404.
Secondary problem: failures are completely silent
downloadTemplate swallows every failure with an early return:
if (!res.ok) return
// ...
if (!fileRes.ok) return
This is why the bug was only observable as "nothing happens". It is a UX problem
independent of the root cause.
Proposed fix
This repository already contains an implementation that does not need CORS.
Deck PPTX download uses a direct href (navigation):
// web-ui/src/components/deck/WorkspaceView.tsx, SlideCarousel.tsx
<a href={deck.pptxUrl} download>PPTX</a>
A direct href is a navigation, so CORS does not apply. CORS is only required for
the fetch() + blob() XHR path, and downloadTemplate is the only place in
this codebase that uses that style.
So rather than adding CORS to the resource bucket, the better fix is to
remove the CORS dependency altogether:
- Add
ResponseContentDisposition=attachment; filename="{name}.pptx" to the
presigned URL on the API side
- Drop
fetch() + blob() in the frontend and use the same direct-href
approach as deck download
- Show an error (toast) when fetching the presigned URL fails
Benefits:
- No infrastructure change (no CORS configuration to add)
- User templates and builtin templates go through the same path
- Consistent with the existing deck download implementation (today there are two
competing styles)
- No blob held in memory
- The
download attribute is ignored for cross-origin URLs, so
Content-Disposition is a more reliable way to control the filename
Adding CORS to the resource bucket would also be a one-line fix, but it keeps a
CORS dependency that should not exist in the first place. (For the record: the
buckets have Block Public Access enabled and require presigned URLs, so adding
CORS would not widen authorization by itself.)
Related files
api/index.py — download_template (GET /templates/<name>)
web-ui/src/services/deckService.ts — downloadTemplate
web-ui/src/app/(authenticated)/templates/page.tsx — handleDownload
web-ui/src/components/deck/WorkspaceView.tsx / SlideCarousel.tsx — the
existing pattern to follow
infra/lib/data-stack.ts — bucket definitions (where the CORS difference lives)
Summary
Downloading a builtin template (blank-dark / blank-light) does nothing on the
/templatespage of the Web UI. Nothing is saved and no error is shown.Downloading a user-uploaded template works fine.
Affected
Steps to reproduce
/templatespage in the Web UICause
The download path depends on per-bucket CORS configuration, which is not
satisfied for the builtin bucket.
download_templateinapi/index.pyreturns a presigned URL from twodifferent buckets:
Meanwhile
downloadTemplateinweb-ui/src/services/deckService.tsretrieves thepresigned URL from the browser via
fetch()→blob(), which requires CORS.So only the builtin path is blocked by the browser. The files do exist in S3, so
this is not a 404.
Secondary problem: failures are completely silent
downloadTemplateswallows every failure with an early return:This is why the bug was only observable as "nothing happens". It is a UX problem
independent of the root cause.
Proposed fix
This repository already contains an implementation that does not need CORS.
Deck PPTX download uses a direct href (navigation):
A direct href is a navigation, so CORS does not apply. CORS is only required for
the
fetch()+blob()XHR path, anddownloadTemplateis the only place inthis codebase that uses that style.
So rather than adding CORS to the resource bucket, the better fix is to
remove the CORS dependency altogether:
ResponseContentDisposition=attachment; filename="{name}.pptx"to thepresigned URL on the API side
fetch()+blob()in the frontend and use the same direct-hrefapproach as deck download
Benefits:
competing styles)
downloadattribute is ignored for cross-origin URLs, soContent-Dispositionis a more reliable way to control the filenameAdding CORS to the resource bucket would also be a one-line fix, but it keeps a
CORS dependency that should not exist in the first place. (For the record: the
buckets have Block Public Access enabled and require presigned URLs, so adding
CORS would not widen authorization by itself.)
Related files
api/index.py—download_template(GET /templates/<name>)web-ui/src/services/deckService.ts—downloadTemplateweb-ui/src/app/(authenticated)/templates/page.tsx—handleDownloadweb-ui/src/components/deck/WorkspaceView.tsx/SlideCarousel.tsx— theexisting pattern to follow
infra/lib/data-stack.ts— bucket definitions (where the CORS difference lives)