Skip to content

Add sprint summary PDF report (standalone) - #96

Merged
VeryComplexAndLongName merged 2 commits into
mainfrom
feat/sprint-report-pdf
Aug 27, 2026
Merged

Add sprint summary PDF report (standalone)#96
VeryComplexAndLongName merged 2 commits into
mainfrom
feat/sprint-report-pdf

Conversation

@VeryComplexAndLongName

Copy link
Copy Markdown
Owner

Summary

  • For a user-picked date range and set of changes, generates a downloadable PDF sprint report: per change, git-derived authorship, dates, task completion, a plain-text "Why" excerpt; plus aggregate statistics.
  • New git primitive getChangeAuthorship — no prior code in this repo extracted author name/email, only timestamps. Primary author = most recent commit touching the change's directory (most representative given this repo's own squash-merge convention); contributors lists every distinct author.
  • New pdfkit dependency (pure JS, no browser/native deps) — confirmed via grep across all 6 package.json files that no PDF library existed anywhere before this. npm audit --omit=dev --audit-level=high (this repo's actual CI gate) reports zero vulnerabilities with it installed.
  • First non-JSON response this server's REST layer has ever sent (application/pdf + content-disposition: attachment), mirroring static.ts's existing raw-Buffer pattern rather than inventing a JSON-base64 convention.
  • New "Sprint report" mode in the standalone Timeline tab, reusing the same date-range/multi-select UI as "Compare changes."
  • Date-range semantics (explicit decision, in design.md): the range filters which tasks count toward the sprint stats, not which changes appear — a change the user explicitly selected never silently disappears from its own report.
  • VS Code command is a separate, follow-up change (add-sprint-report-vscode-command).
  • Versioned via a changeset: @openspec-ui/core 0.28.0 -> 0.29.0, @openspec-ui/server 1.9.0 -> 1.10.0, @openspec-ui/webui 1.14.0 -> 1.15.0 (all minor).

Verification note

Manually generated a real PDF against this repository's own actual data: five of this session's real archived changes, confirmed correct authorship, dates, task counts, and aggregate statistics (file reports "PDF document, version 1.3, 2 page(s)").

Test plan

  • getChangeAuthorship tests against a real temp git repo fixture (primary author, contributors, empty-authorship cases)
  • buildSprintReport tests: aggregation, date-range task filtering, a change kept despite starting before the range, author ranking, empty report
  • renderSprintReportPdf tests: real PDF magic bytes/trailer, empty report
  • REST handler tests: correct content-type/content-disposition, 400 on a missing date range
  • sprint-report-client.test.ts
  • npm run typecheck, npm run lint (including lint:english), npm run test workspace-wide
  • npm audit --omit=dev --audit-level=high
  • Real-data manual PDF generation (see above)
  • openspec change validate --strict add-sprint-report-pdf

🤖 Generated with Claude Code

VeryComplexAndLongName and others added 2 commits August 27, 2026 03:05
For a user-picked date range and set of changes, generates a
downloadable PDF: per change, git-derived authorship (primary author
= most recent commit touching the change's directory -- the most
representative single answer given this repo's own squash-merge
convention), dates, task completion, a plain-text "Why" excerpt; plus
aggregate statistics (total changes, tasks completed within the
range, a per-author breakdown).

New git primitive (getChangeAuthorship) -- no prior code in this repo
extracted author identity, only timestamps. New pdfkit dependency
(pure JS, no browser/native deps, npm audit --omit=dev confirmed
clean). First non-JSON response this server's REST layer has ever
sent, mirroring static.ts's existing raw-Buffer pattern.

Manually verified against real data: generated an actual PDF for five
of this session's own real archived changes and confirmed correct
authorship, dates, and statistics.

VS Code command comes next, as its own change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CI caught this on PR #96 (Extension integration and package job):
"Activating extension ... failed: Invalid URL". Repro'd locally --
pdfkit's package.json "exports" resolves a bare `import` to its ESM
build (js/pdfkit.node.mjs), which uses real `import.meta.url` syntax.
esbuild cannot preserve that when bundling the extension host to a
single CJS file (`format: "cjs", platform: "node"` in
build-options.mjs) and substitutes an empty object, so pdfkit's
top-level `new URL("./data/...", undefined)` throws at module load --
which crashed the whole extension's activation, not just sprint-report
code, since core's index.ts barrel pulls pdfkit in for every consumer.

Fix: require("pdfkit") via a createRequire, forcing resolution through
the package's `require` condition (js/pdfkit.js) instead -- a genuine
CommonJS build that reads `__filename` rather than `import.meta.url`,
which esbuild *can* shim correctly for a node/cjs bundle. `__filename`
is unavailable in this file's own plain-ESM runtime (server/core,
unbundled), hence the `typeof __filename !== "undefined"` guard --
`typeof` never throws on an undeclared identifier.

Verified: rebuilt dist/extension.js and loaded it in plain Node with a
stubbed `vscode` module -- activation now succeeds. All previously
passing core/server/webui/extension suites still pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@VeryComplexAndLongName
VeryComplexAndLongName merged commit 4e492c0 into main Aug 27, 2026
6 checks passed
@VeryComplexAndLongName
VeryComplexAndLongName deleted the feat/sprint-report-pdf branch August 27, 2026 03:22
VeryComplexAndLongName added a commit that referenced this pull request Aug 27, 2026
Second half of the sprint-report feature (add-sprint-report-pdf, PR
#96, shipped the standalone-only half). New Command Palette command
"OpenSpec UI: Generate Sprint Report (PDF)": reuses
pickChangesForTimeline unchanged, adds two validated YYYY-MM-DD date
prompts (VS Code has no native date picker, and this needs a real
user-specified range, not an auto-derived one), calls
buildSprintReport/renderSprintReportPdf from @openspec-ui/core
directly (per ADR-0001 -- no server/REST round-trip), then
showSaveDialog -> workspace.fs.writeFile -> a confirmation message
with an "Open" action.

This is also the first code path that actually executes
renderSprintReportPdf from inside the bundled extension -- which
re-exposed the pdfkit/esbuild bundling hazard PR #96's CI caught
(esbuild's CJS bundle can't shim pdfkit's ESM build's
import.meta.url). The createRequire-based workaround from that PR
fixed activation but silently stopped esbuild from inlining pdfkit at
all (a local variable named `require`, however constructed, isn't
recognized as a bundleable require once esbuild renames it during
bundling -- confirmed by inspecting the built dist/extension.js, which
dropped from 2.8MB to 635KB and left a real runtime
`require("pdfkit")` call). Since `vsce package --no-dependencies`
ships no node_modules, that would have failed at runtime with "Cannot
find module 'pdfkit'" the first time this command actually ran.

Reverted sprint-report-pdf.ts to a plain `import PDFDocument from
"pdfkit"` (correct as-is for every plain-Node/ESM consumer -- server,
core's own tests) and instead fixed the extension-only bundling
problem in the extension's own esbuild config: an `alias` mapping
"pdfkit" to its real CommonJS build file at bundle time
(build-options.mjs), which genuinely inlines it and reads __filename
instead of import.meta.url.

Verified: rebuilt dist/extension.js and loaded it in plain Node with a
stubbed vscode module -- activation succeeds, and the bundle contains
pdfkit's real CommonJS build inline (grep for ICC_PROFILE_PATH's
pathToFileURL(__filename) form, not import_meta2.url).

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
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