Skip to content

Fix transient .quarto_ipynb files accumulating during preview#14287

Merged
cderv merged 12 commits intomainfrom
fix/issue-14281
Mar 31, 2026
Merged

Fix transient .quarto_ipynb files accumulating during preview#14287
cderv merged 12 commits intomainfrom
fix/issue-14281

Conversation

@cderv
Copy link
Copy Markdown
Collaborator

@cderv cderv commented Mar 31, 2026

When previewing a .qmd file with Jupyter execution (Python/Julia), transient .quarto_ipynb files accumulate in the source directory: test.quarto_ipynb, test.quarto_ipynb_1, test.quarto_ipynb_2, etc. Each re-render creates a new numbered variant that persists on disk even with keep-ipynb: false. Regression from v1.8.27 to v1.9.

Root Cause

Two interacting changes from #13804:

  1. A collision-avoidance loop in jupyter.ts target() creates numbered suffixes (_1, _2) when the target .quarto_ipynb already exists on disk.

  2. renderForPreview() calls fileInformationCache.delete(file) before each re-render to pick up file changes. This removes the cache entry (losing track of which .quarto_ipynb was previously created) but does not delete the old file from disk. On the next render, target() sees the old file via existsSync and increments the counter.

Additionally, cmd.ts creates a ProjectContext for format detection (shiny routing, serveable project check), then discards it when calling preview(). preview() creates a second independent context. The first context's .quarto_ipynb is orphaned on disk, causing a duplicate at startup. This double-context pattern pre-dates #13804 but was invisible because target() always reused the same filename.

Fix

  1. Add invalidateForFile() method to FileInformationCacheMap that deletes any transient notebook file from disk before removing the cache entry. renderForPreview() now calls this instead of bare delete(). This fix is backported to v1.9 in a separate PR since the regression was introduced there.

  2. Pass the ProjectContext from cmd.ts to preview() via a new pProject parameter (matching render()'s existing pContext pattern). This eliminates the redundant second context creation. This is a structural improvement not backported to v1.9.

Test Plan

  • Unit tests for invalidateForFile() (transient file deleted, non-transient preserved)
  • Existing smoke tests pass (inspect-cleanup, keep-ipynb)
  • Manual preview test: single file with Python cell, 3+ saves, no accumulation
  • Manual: plain qmd (no code) — no .quarto_ipynb created
  • Manual: knitr engine — no .quarto_ipynb created

Fixes #14281

cderv added 9 commits March 30, 2026 16:06
Preview re-renders delete the fileInformationCache entry to pick up
file changes, but this loses track of the transient .quarto_ipynb path.
The collision-avoidance loop in jupyter.ts target() then sees the old
file on disk and creates numbered variants (_1, _2, etc.) that are
never cleaned up until preview exits.

Add invalidateForFile() to FileInformationCacheMap that cleans up any
transient notebook file from disk before removing the cache entry.
Replace the bare delete() call in renderForPreview() with this method.

Fixes #14281
Document asMappedString() and createMockProjectContext() utilities
for constructing Quarto types in unit tests.
cmd.ts already creates a ProjectContext for format detection (shiny
routing, serveable project check). Previously this context was discarded
and preview() created a second one independently — a structural
redundancy that caused duplicate .quarto_ipynb files on startup when
both contexts called renderFormats()/target().

Add pProject parameter to preview() (matching render()'s pContext
pattern) and pass the cmd.ts context through. Derive nbContext from the
project instead of creating a new one.
Documents the preview command branching (5 paths), context lifecycle,
file mutation state machine, transient notebook lifecycle, and
invalidateForFile role. Based on investigation for #14281.
Manual test document for verifying that .quarto_ipynb files do not
accumulate during preview re-renders. Requires interactive preview
session, so placed in tests/docs/manual/ (not discovered by test runners).
Generic /quarto-preview-test command for testing quarto preview with
browser automation (/agent-browser preferred, Chrome DevTools MCP for
deep debugging). Cross-platform, no personal setup required.

Manual test matrix in tests/docs/manual/preview/README.md covers 16
scenarios across 3 priority tiers for the quarto_ipynb accumulation fix.
- Fix inconsistent verification rules in README (conditional by test type)
- Add excluded-file test case (T10b) for files not in project inputs
- Add PowerShell equivalents for background preview examples in skill
Use _excluded.qmd consistently in both setup and steps.
Test fixtures for T1-T10: single-file Python, project website, keep-ipynb,
plain qmd, and knitr. Project scaffolded via quarto create.

Skill updated with: venv activation requirement, Windows SIGINT limitation
for cleanup testing, simplified background process examples.
cleanupFileInformationCache() now delegates to invalidateForFile()
instead of duplicating the same transient-check logic. Add tests for
invalidateForFile() with no-target entries and missing keys.
@cderv
Copy link
Copy Markdown
Collaborator Author

cderv commented Mar 31, 2026

Note: #11597 reports the same symptom (stray .quarto_ipynb files) but in the quarto publish path rather than preview. This PR fixes the preview accumulation; the publish path likely has a separate cleanup gap.

cderv added 2 commits March 31, 2026 12:22
Add YAML frontmatter (main_commit, analyzed_date, key_files) per
llm-docs maintenance rules. Update cleanupFileInformationCache
description to reflect delegation to invalidateForFile.
Regression fix: transient .quarto_ipynb accumulation during preview.
Command improvement: eliminate duplicate context creation on startup.
@cderv cderv merged commit 5fc58cd into main Mar 31, 2026
92 of 93 checks passed
@cderv cderv deleted the fix/issue-14281 branch March 31, 2026 13:46
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.

Lots of quarto_ipynb files created

1 participant