Skip to content

fix: builtin template download does nothing (resource bucket has no CORS config + silent failures) #281

Description

@ShotaroKataoka

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

  1. Open the /templates page in the Web UI
  2. Click the download button on a builtin template (blank-dark or blank-light)
  3. 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:

  1. Add ResponseContentDisposition=attachment; filename="{name}.pptx" to the
    presigned URL on the API side
  2. Drop fetch() + blob() in the frontend and use the same direct-href
    approach as deck download
  3. 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.pydownload_template (GET /templates/<name>)
  • web-ui/src/services/deckService.tsdownloadTemplate
  • web-ui/src/app/(authenticated)/templates/page.tsxhandleDownload
  • 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions