You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Codex CLI Tool Review - CSV Viewer Web Application
This directory contains files from a Codex CLI tool review where the task was to create a CSV viewer web application using the Air web framework (https://github.com/feldroy/air).
Files
01.config.toml - The Codex CLI configuration used for this review
02.task.md - The original task given to the AI agent
03.plan.md - The execution plan prepared by the agent
04.showcsv.zip - The complete project generated by the agent
05.review.md - Review of the project generated using the Codex VSCode extension
About Air Framework
Air is a modern Python web framework built with FastAPI, Starlette, Pydantic, and HTMX, designed to breathe fresh air into Python web development.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
AI Agent Instructions — CSV Viewer & Converter (AIR)
Objective
Build a neat, simple web application using the AIR framework to:
Upload a CSV file (no persistent storage; session-scoped state only).
Display it in a nice, readable table with:
Sorting on all columns
Pagination with 10 / 25 / 100 / All rows
Header row if present
Subtle colour coding per column for easy visual distinction
Allow downloads of the loaded data as:
Markdown (.md) — CSV rendered as a Markdown table
HTML (.html) — full HTML page with an HTML table
PDF (.pdf)
Work in a local Git repo. No branches/remotes. Make small, clear commits.
Step 1 — Update .gitignore
Create/update a comprehensive Python .gitignore including common patterns for a Python project.
The tmp/ directory should be ignored - it is for ephemeral planning and testing files.
Step 2 — Research AIR
Using the documentation, GitHub repo, and the tools and MCP servers you have available:
Review the Air web framework, how it works, what conventions it uses.
Consider how to use this framework effectively for implementing the CSV viewer and converter features.
showcsv is a small web app for uploading a CSV file, viewing it as a sortable and paginated table, and exporting it to Markdown, HTML, or (optionally) PDF. It is built with the Air web framework (leveraging Starlette/FastAPI under the hood) and Uvicorn for development.
Primary entrypoint: app.py
Key capabilities:
Upload CSV and parse with Python’s csv.Sniffer and csv.reader.
View table with sortable headers and pagination; subtle per-column background colors.
Download as Markdown (/download.md), standalone HTML (/download.html), or PDF (/download.pdf via WeasyPrint optional dependency).
How It Works
Request flow and state
SessionMiddleware adds cookie-based sessions using a secret (defaults to dev-secret-showcsv).
Datasets are stored in a module-level in-memory DATA_STORE keyed by a random UUID. The session stores the dataset key. Only one dataset per session is retained; the previous one is deleted on upload.
Upload and parsing
Route: POST /upload reads the uploaded file (UploadFile) and hands it to parse_csv().
parse_csv() uses csv.Sniffer to detect dialect and header presence, then reads the entire file into memory and returns (header, data_rows) as List[str] and List[List[str]] respectively.
Rendering and interactions
Route: GET / renders the main page with an upload form. If a dataset is present, it renders a table component built by table_component() with sorting and pagination.
HTMX is included on the page. Table header links and pagination links make hx_get requests back to "/", using hx_select="#table-root" to swap just the table fragment from the full page response. There is also a dedicated GET /table route that renders only the table fragment (functionally redundant with the hx_select approach).
Column background colors are generated with col_color_css() using an HSL palette based on column index.
Sorting and pagination
sort_rows() accepts header, rows, sort_by (index or name), and order. It attempts numeric comparison first (via float()), falling back to lexicographic string comparison. Invalid indices or errors result in returning the original order.
paginate() slices rows based on page and per_page. A per_page of 0 is treated as “All”.
Exports
to_markdown() emits a pipe-delimited Markdown table with escaped pipes.
table_html_full() renders a full HTML page containing the table for download/printing. Note: it passes the Request class (not an instance) to table_component(), but the parameter is unused by that function, so behavior is unaffected.
GET /download.md, /download.html, and /download.pdf build file responses with Content-Disposition for download. PDF generation requires WeasyPrint ([project.optional-dependencies].pdf).
Project Structure
app.py: All app logic, routes, view composition, helpers.
pyproject.toml: Project metadata, runtime deps (air, uvicorn), optional PDF extra (weasyprint).
README.md: Setup and usage instructions. Mentions uv sync and uv run for local dev.
Running Locally (per README)
uv sync to create/resolve the virtual env and dependencies.
uv run uvicorn app:app --host 127.0.0.1 --port 8000 or uv run python -c "import app; app.run()".
Optional: install PDF support with uv sync --extra pdf (system libs may be needed for WeasyPrint).
Sensible export features (Markdown/HTML/PDF) with an optional PDF dependency to avoid heavy installs by default.
Reasonable CSV dialect/header detection with csv.Sniffer() and robust fallbacks.
Issues and Risks
Default secret key: Using a hardcoded default secret (dev-secret-showcsv) is fine for dev, but dangerous if deployed without override.
In-memory global store: DATA_STORE grows with active sessions and is not bounded or expired. A long-lived process could accumulate stale datasets.
Upload limits: No size/type validation is enforced; large CSVs are fully read into memory which can impact performance and memory usage.
Encoding robustness: Assumes UTF-8 with errors="replace". Non-UTF-8 encodings will silently mangle characters rather than detect/handle encoding.
XSS and escaping: Cell values are inserted into the DOM via air.Td(...). If the Air framework escapes by default, this is safe; if not, user-controlled content may cause XSS. This should be verified and, if needed, explicitly escaped/sanitized.
Duplicate approaches for partials: Table updates use hx_select on "/", and there is also a GET /table fragment route. Keeping both increases maintenance surface.
Sorting heuristic: Mixed-type columns (e.g., numbers with commas or currency symbols) will not sort numerically as intended. Casting to float may raise or yield surprising ordering.
Filenames in Content-Disposition: Filenames are not quoted or RFC 5987 encoded; spaces and non-ASCII may behave inconsistently in some browsers.
table_html_full() signature quirk: Passes Request (class) to table_component(); harmless today, but confusing and brittle if table_component() later needs request context.
Suggestions for Improvement
Security and robustness
Require SECRET_KEY in non-dev environments and document this in the README. Consider failing fast on startup if not provided and ENV != dev.
Add upload size/type limits (e.g., using server config or middleware) and return a friendly error if exceeded.
Validate page, per_page, and sort_by query params, clamping to safe ranges and handling bad input explicitly.
Quote/encode download filenames in Content-Disposition to handle spaces and non-ASCII safely.
Data handling and scalability
Consider streaming CSV parsing for very large files (e.g., csv incremental read) and only materialize the current page for view.
Implement dataset eviction/expiration for DATA_STORE (LRU, TTL) or move to a lightweight cache (e.g., Redis) if multi-process or long-lived.
Improve encoding handling: try chardet/charset-normalizer for detection or allow user to pick an encoding.
UX and consistency
Consolidate to a single partial rendering approach. Prefer GET /table for HTMX fragments and have header/pager links target that route, or keep hx_select on / but remove the /table route.
Add a small toolbar with dataset name, row count, and a “clear dataset” action to free memory for the current session.
Allow CSV delimiter selection override if sniffer guesses incorrectly.
Sorting and formatting
Improve numeric detection (handle thousands separators, currency symbols) and consider locale-aware sorting for text.
Add per-column type inference for better sorting and optional formatting.
Testing and CI
Add unit tests for:
parse_csv() variations: with/without header, quoted fields, different delimiters, malformed rows.
sort_rows() numeric vs. lexicographic behavior, invalid indices, stability.
paginate() boundaries and All mode (per_page=0).
to_markdown() pipe escaping and row alignment.
Add simple integration tests for upload and downloads (/download.md/.html).
Set up GitHub Actions CI to run tests and lint (e.g., ruff, mypy if types are expanded).
Code quality
Extract helpers into a small module (e.g., data.py for parse/sort/paginate, export.py for to_markdown/table_html_full) to keep app.py focused on routing and composition.
Make table_html_full() pass a real Request or drop the parameter from table_component() if it isn’t used.
Centralize query param handling (parse/validate/build URLs) to avoid manual string concatenation for links.
Quick Wins (low effort, high value)
Quote filenames in Content-Disposition headers.
Remove the unused request parameter from table_component() or pass a proper instance everywhere.
Replace dual partial strategies with one (/table route) and update HTMX links accordingly.
Add max upload size and basic file-type check.
Document SECRET_KEY override in README and warn on startup if using default outside dev.
app.py:52: Global DATA_STORE with session-keyed datasets.
app.py:69: Old dataset cleanup when setting a new one (per-session only).
app.py:86: Numeric-then-text sorting strategy.
app.py:159: HTMX link attributes on header cells using hx_select against "/".
app.py:290: table_html_full() passing Request class into table_component().
Overall
The project is a tidy, approachable example with a nice UI and practical exports. For production, address secret management, memory lifecycle, input limits, and escaping guarantees. For maintainability, consolidate partial rendering, extract helpers, and add tests/CI. These changes keep the simplicity while making the app safer and more reliable.