Skip to content

feat(web): export all project cards as CSV (PE-3) - #524

Open
prakruthi2411 wants to merge 2 commits into
Paca-AI:masterfrom
prakruthi2411:feature/export-project-cards-csv
Open

prakruthi2411 wants to merge 2 commits into
Paca-AI:masterfrom
prakruthi2411:feature/export-project-cards-csv

Conversation

@prakruthi2411

@prakruthi2411 prakruthi2411 commented Sep 28, 2026 •

Copy link
Copy Markdown

Summary

Implements PE-3 — Export All PACA Cards by Project.

  • Add an Export CSV button on Timeline / Product Backlog / sprint board headers
  • Download all project cards as CSV (status, type, sprint, assignees, priority, story points, tags, dates, custom fields)
  • Resolve IDs to display names before export
  • Includes i18n + unit tests for CSV helpers

Test plan

  • Open a project board → Export CSV downloads a .csv with all cards
  • CSV includes resolved status/type/assignee names and custom fields
  • bun --bun run test src/lib/export-tasks-csv.test.ts

Add an Export CSV action on interaction boards so teams can download all
project cards for reporting, and default board columns to 15 cards with
scroll-to-load-more batches of 15.

Co-authored-by: Cursor <cursoragent@cursor.com>

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — minor suggestions inline.

Reviewed changes

  • CSV export utility (apps/web/src/lib/export-tasks-csv.ts) — new fetchAllTasksForExport (cursor-paginates the full project task list), tasksToCsv (UTF-8 BOM + RFC-4180 quoting, ID→name resolution, custom fields), downloadCsv, and buildExportFilename.
  • Export button wired up (interaction-layout.tsx) — handleExportCsv assembles project lookups and adds a header button with spinner and inline error alert.
  • Board pagination defaults (view-utils.ts) — Board initial/per-page defaults move 20 → 15 and "15" is added to PAGE_SIZE_OPTIONS.
  • i18n — layout.shell.export* and exportColumns keys added across all 9 locales.
  • Tests — export-tasks-csv.test.ts covers BOM/headers/resolved names, escaping, parent resolution, and filename sanitization.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏


function escapeCsvCell(value: string): string {
if (/[",\r\n]/.test(value)) {
return `"${value.replaceAll('"', '""')}"`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User-controlled cells (task titles, tags, custom-field text) are emitted verbatim, so a title like =HYPERLINK(...) or =cmd|... will be evaluated as a formula when the CSV is opened in Excel/Sheets (CSV injection, CWE-1236). Consider neutralizing cells whose first non-space char is one of = + - @ (e.g. prefix with a single quote or tab) inside this escaper.

Technical details
# CSV formula injection

## Affected sites
- apps/web/src/lib/export-tasks-csv.ts:76-81 — `escapeCsvCell` only quotes when the value contains `"`, `,`, `\r`, or `\n`; leading `=`, `+`, `-`, `@` pass through unchanged.

## Required outcome
- Exported cells cannot be interpreted as spreadsheet formulas, while preserving the visible value for legitimate data.

## Suggested approach (optional)
- In `escapeCsvCell`, after determining the string, prepend a guard (commonly `'`) when `/^[\t\r ]*[=+\-@]/` matches, then run the normal quoting logic.

): string {
const safe = (projectSlug || "project")
.trim()
.replace(/[^\w.-]+/g, "-")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\w is ASCII-only, so a project named e.g. プロジェクト or Café collapses to an empty slug and the download falls back to the literal project-tasks-....csv. Using a Unicode-aware class (e.g. /[^\p{L}\p{N}._-]+/gu) would preserve those names.

Technical details
# Non-ASCII project names lose the filename slug

## Affected sites
- apps/web/src/lib/export-tasks-csv.ts:212-217 — `replace(/[^\w.-]+/g, "-")` strips every non-ASCII letter.

## Required outcome
- Filenames remain meaningful for non-ASCII project names while still stripping path/quote-unsafe characters.

## Open questions for the human (optional)
- Some filesystems/servers may prefer ASCII-only filenames; if so, this is acceptable as-is, but the current fallback is `project` rather than a transliteration.

/** Task list API caps page_size at 200. */
const PAGE_SIZE = 200;
/** Safety cap so a runaway cursor loop cannot hang the browser. */
const MAX_PAGES = 100;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MAX_PAGES guard silently drops cards once a project exceeds 20,000 tasks — the CSV will look complete while missing data. Consider surfacing a warning (or including the truncation in exportFailed/the alert) when the loop hits the cap.

Technical details
# Silent truncation at the pagination cap

## Affected sites
- apps/web/src/lib/export-tasks-csv.ts:20-21, 60-73 — loop stops after `MAX_PAGES` iterations with no signal that `next_cursor` was still non-null.

## Required outcome
- Callers can distinguish a complete export from a capped one; the user is told when the file is partial.

## Suggested approach (optional)
- Return `{ tasks, truncated: boolean }` (or throw) when the loop exits on the cap, and have `handleExportCsv` surface a message instead of writing a partial file silently.

PE-3 only covers exporting project cards as CSV; revert the unrelated
board pagination default change so this PR stays scoped.

Co-authored-by: Cursor <cursoragent@cursor.com>
@prakruthi2411 prakruthi2411 changed the title feat(web): export project cards as CSV and board page size 15 feat(web): export all project cards as CSV (PE-3) Sep 28, 2026
@pikann

pikann commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

Hi @prakruthi2411 ,

Exporting CSV is a great feature idea!

However, we shouldn't loop API calls from the web app to fetch the full task data. Instead, it would be better to create an API endpoint that handles export requests by publishing a message to a Valkey stream. A background job (acting as a Valkey stream consumer in the API service) can then fetch all the task data, generate the CSV file, and upload it to storage (Rustfs or S3, depending on the setup) for the user to download

Also, this feature should be placed under Project Settings and protected by a separate permission (e.g., project.export).

Thank you so much for your effort and for proposing this feature! If you'd like to continue working on this PR, feel free to update it following this approach. If you're busy, just let me know and I can take over

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.

2 participants