Skip to content

Instantly share code, notes, and snippets.

@intellectronica
Last active August 30, 2025 11:45
Show Gist options
  • Select an option

  • Save intellectronica/9f26383605ad18e8d8a77e2d8df2c5e0 to your computer and use it in GitHub Desktop.

Select an option

Save intellectronica/9f26383605ad18e8d8a77e2d8df2c5e0 to your computer and use it in GitHub Desktop.

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.

approval_policy = "never"
sandbox_mode = "danger-full-access"
file_opener = "vscode"
profile = "gpt5"
[profiles.gpt5]
model = "gpt-5"
model_reasoning_effort = "medium"
model_reasoning_summary = "detailed"
[mcp_servers.playwright]
command = "npx"
args = ["-y", "@playwright/mcp@latest"]
[mcp_servers.markitdown]
command = "uvx"
args = ["markitdown-mcp"]
[mcp_servers.tavily]
command = "npx"
args = ["-y", "tavily-mcp"]
env = { TAVILY_API_KEY = "tvly-XxXxXxXxXxXxXxXxXxX" }
[mcp_servers.context7]
command = "npx"
args = ["-y", "@upstash/context7-mcp"]
[mcp_servers.deep_wiki]
command = "npx"
args = ["-y", "mcp-remote@latest", "https://mcp.deepwiki.com/mcp"]
[projects."/Users/eleanor/showcsv"]
trust_level = "trusted"
[mcp_servers.gitmcp_feldroy_air]
command = "npx"
args = ["-y", "mcp-remote@latest", "https://gitmcp.io/feldroy/air"]

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.

Links


Step 3 — Understand & Record the Plan, then STOP

Create tmp/plan.md with a concise plan for implementing the CSV viewer and converter features using the Air web framework.

HARD STOP: After creating tmp/plan.md, stop and inform me that the plan is ready for review. Do not implement further until explicitly approved.


After Approval (for later)

  • Implement features per tmp/plan.md.
  • Make orderly commits.
  • Run the local server and test the app thoroughly using Playwright, confirming that it works as expected and looks great.
  • Only when everything works and looks great, report done and provide concise run & test instructions for me.

CSV Viewer & Converter — Implementation Plan (Air)

Goals

  • Upload CSV (session-only), render sortable/paginated table, color-coded columns.
  • Allow downloads of current dataset as Markdown, HTML, and PDF.

Stack & Conventions

  • air.Air() app with air.layouts.picocss for clean UI.
  • SessionMiddleware (Starlette) for session-scoped state; store parsed rows + header.
  • Prefer Air Tags for views; use HTMX-friendly endpoints for partial updates.

Routes

  • GET / — Upload form; if data in session, show table + controls.
  • POST /upload — Accept UploadFile, parse CSV, detect header, persist to session, redirect to /.
  • GET /table — Return table fragment (HTMX) honoring ?page&per_page&sort_by&order.
  • GET /download.md — Markdown table of current dataset (attachment).
  • GET /download.html — Full HTML page (attachment) with table.
  • GET /download.pdf — HTML→PDF of current dataset (attachment).

Core Components

  • CSV parsing: use Python csv with Sniffer to detect dialect; normalize to List[Dict[str, str]] with header: List[str].
  • Session schema: { header: List[str], rows: List[List[str]] } (keep original order; header optional).
  • Sorting: in-memory sort by column index or name; toggle asc/desc via query params.
  • Pagination: server-side slice; per-page options 10,25,100,All.
  • Column colors: generate stable palette by column index; lightweight CSS classes.
  • Converters:
    • Markdown: build pipe-delimited table, include header separator.
    • HTML: reuse Air Tag table render; wrap in minimal layout for standalone file.
    • PDF: render same HTML and convert using weasyprint (pure-Python). Fallback message if lib missing.

Views (Air Tags)

  • Upload form: file input + submit; show notes about size limits.
  • Table: <table> with clickable <th> to sort (links preserving pagination), <tfoot> with per-page selector.
  • Partial rendering: /table returns just table + controls for HTMX swaps.

Middleware & Config

  • Add SessionMiddleware(secret_key=...).
  • Reasonable upload size guard; reject empty/invalid CSV with friendly alerts.

UX Details

  • Use picocss layout; responsive container; sticky header.
  • Subtle column background colors; hover highlight row; monospace for numeric alignment optional.
  • Preserve filename in downloads when possible (e.g., ?name=...).

Validation & Testing

  • Manual flows after implementation: upload, sort, paginate, and each download route.
  • Use Playwright to confirm behavior and visuals (snapshots).

Next Steps

  1. Scaffold app skeleton and middleware.
  2. Implement upload and CSV parsing with session storage.
  3. Build table render with sorting/pagination (+ /table HTMX endpoint).
  4. Add download routes for MD/HTML/PDF.
  5. Polish styles and column coloring.
  6. Playwright run-through and fixes.

Project Review: showcsv

Overview

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).

Strengths

  • Clear, minimal code that is easy to follow.
  • Nice UX touches: sticky header, per-column colors, HTMX partial updates, pagination controls.
  • 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.

Notable Code References

  • app.py:18: Session secret default (dev-secret-showcsv).
  • 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment