agentleFS
cooklangcooklang/cookcli/CLAUDE.md

cookcli

CLAUDE.md1.4k starsChanged 5 days agoMITView on GitHub

CookCLI is a command-line interface for managing Cooklang recipes. It's written in Rust and includes a web server with server-side rendered HTML using Askama templates and Tailwind CSS. The project follows UNIX philosophy where each command does one thing well. build.rs stops the build until static/css/output.css and static/js/editor.bundle.js exist. They are gitignored, so a fresh clone or git worktree needs npm ci && npm run build-css && npm run build-js before cargo. In debug builds rust-embed reads static/ from the…

  • Installs packages

We split this CLAUDE.md into the rules it gives an agent

32 rules. About 0 are common, 15 are distinctive, and 17 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈15

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 17

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Every handler that changes something takes Extension<Viewer> and, once the change succeeded, calls server::activity::record — one stdout …

    line 184
  2. Very specific:

    Never put e.to_string() in a response body by hand, and never put the recipe directory's absolute path in a page or API answer.

    line 191
  3. Very specific:

    Every parser is built with Extensions::empty() (crates/core/src/parser.rs, crates/format/src/lib.rs): no @& references, no range values …

    line 239
  4. Very specific:

    Add every new message to every locale.

    line 284
  5. Distinctive:

    When a change alters output other pages share (URLs, templates, badges), run the whole Playwright project, not only the specs you touched — …

    line 331
  6. Very specific:

    Use cook seed to create test recipes

    line 364
  7. Distinctive:

    Test each command with various options

    line 365
  8. Distinctive:

    Check error handling with invalid inputs

    line 367
  9. Very specific:

    When Dependabot cannot fix an advisory because the fixed version raises other crates' minimums, cargo update -p <crate> --precise <version> …

    line 408
  10. Distinctive:

    Always run these checks before creating a PR:

    line 414
  11. Distinctive:

    All three must pass cleanly with no warnings or errors.

    line 421
  12. Very specific:

    Add variant to Command enum in src/args.rs

    line 426
  13. Very specific:

    Create module in src/ with XxxArgs struct and run() function

    line 427
  14. Very specific:

    Add case in main.rs match statement

    line 428
  15. Distinctive:

    Update help text and documentation

    line 429
  16. Very specific:

    Create new template in templates/ directory

    line 435
  17. Distinctive:

    Add route in the UI router

    line 438
  18. Very specific:

    Run make css or npm run build-css to compile

    line 442
  19. Very specific:

    Start CSS watch: npm run watch-css

    line 448
  20. Very specific:

    Use RUST_LOG=trace to see detailed parsing information including:

    line 456
  21. Very specific:

    Use beforeEach for setup

    line 693
  22. Distinctive:

    Prefer semantic selectors (roles, labels)

    line 697
  23. Distinctive:

    Use data attributes for test-specific targeting

    line 698
  24. Distinctive:

    Avoid brittle CSS selectors

    line 699
  25. Very specific:

    Use explicit waits (waitForLoadState)

    line 702
  26. Distinctive:

    Check visibility before interaction

    line 703
  27. Distinctive:

    Verify both positive and negative cases

    line 704
  28. Distinctive:

    Run tests in parallel when possible

    line 707
  29. Distinctive:

    Use page objects for reusability

    line 708
  30. Very specific:

    Server not starting: Ensure make dev_server works locally

    line 752
  31. Very specific:

    Browser installation: Run npx playwright install

    line 753
  32. Distinctive:

    Port conflicts: Check port 9080 is available

    line 754

≈ “Common” is an estimate: at least 1 in 5 of the 25,680 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
CLAUDE.md

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Repository Overview

CookCLI is a command-line interface for managing Cooklang recipes. It's written in Rust and includes a web server with server-side rendered HTML using Askama templates and Tailwind CSS. The project follows UNIX philosophy where each command does one thing well.

Build and Development Commands

Building

# Full release build (includes CSS compilation)
make release

# Development build
cargo build
make build  # Includes CSS compilation

# Build CSS only
make css
npm run build-css

# Build JS only
make js
npm run build-js

# Build specific workspace member
cargo build -p cookcli

# Run without building
cargo run -- [command] [args]

# Docker image for local testing (thin-LTO `docker-dev` profile, much faster
# rebuilds than `make docker-build`, which uses `release`)
make docker-build-dev

build.rs stops the build until static/css/output.css and static/js/editor.bundle.js exist. They are gitignored, so a fresh clone or git worktree needs npm ci && npm run build-css && npm run build-js before cargo.

In debug builds rust-embed reads static/ from the source tree at runtime (src/web/mod.rs), so a debug cook copied or mounted at a different path serves pages without CSS or JS.

Testing

# Run all tests
cargo test

# Run specific test
cargo test test_name

# Run tests with output
cargo test -- --nocapture

# Run with debug logging
RUST_LOG=debug cargo run -- [command]
RUST_LOG=trace cargo run -- [command]  # More verbose

Web UI Development

# Start development server with CSS built
make dev_server

# Start development with CSS watch mode
make css-watch  # In one terminal
cargo run -- server ./seed  # In another terminal

# Install dependencies (first time setup)
npm install

# Build CSS for production
npm run build-css

# Watch CSS changes during development
npm run watch-css

# Build JS for production
npm run build-js

# Watch JS changes during development
npm run watch-js

Linting and Formatting

cargo fmt         # Format code
cargo clippy      # Lint code

Architecture

Workspace Structure

This is a Cargo workspace with multiple crates:

  • cookcli (this crate) - The CLI application
  • crates/core (cookcli-core) - Command logic as a library
  • crates/format (cooklang-format) - Recipe output formatters, published for reuse
  • ../cooklang-rs - Core Cooklang parser and models
  • ../cooklang-find - Recipe discovery and search
  • ../cooklang-reports - Template-based report generation
  • ../cooklang-import - Recipe import from websites (external dependency)

Command Architecture

Each command is a module in src/ with a consistent pattern:

  1. XxxArgs struct in src/args.rs or module file - Clap argument definitions
  2. pub fn run(ctx: &Context, args: XxxArgs) -> Result<()> - Entry point
  3. Commands receive a Context with base_path, aisle, and pantry configuration

Context System

The Context struct (in src/main.rs) provides:

  • base_path: Current working directory for recipes
  • aisle(): Returns path to aisle.conf (local ./config/ or global ~/.config/cook/)
  • pantry(): Returns path to pantry.conf (same search pattern)

Configuration search order:

  1. ./config/[aisle.conf|pantry.conf] - Local to recipe directory
  2. The global config directory, resolved by cookcli_core::global_config_path:
    • $COOK_CONFIG_DIR when set to a non-empty value
    • otherwise ~/.config/cook/ (Linux), ~/Library/Application Support/cook/ (macOS), %APPDATA%\cook\config\ (Windows)

global_config_path also resolves session.json (src/login.rs, src/logout.rs, src/server/mod.rs) and sync.db (sync::sync_db_path), so COOK_CONFIG_DIR moves the whole of CookCLI's global state at once.

Command Modules

Core Commands

  • recipe: Parse and display recipes (supports multiple output formats)
  • shopping_list: Generate shopping lists with ingredient aggregation
  • server: Web server using Axum + Askama templates with Tailwind CSS
  • search: Full-text recipe search
  • import: Import from websites via cooklang-import
  • doctor: Validate recipes and check configurations
  • seed: Initialize with example recipes
  • report: Generate custom outputs using Jinja2 templates
  • update: Self-update the CookCLI binary to the latest version (can be disabled with --no-self-update feature)
  • completions: Print a shell completion script (bash, zsh, fish, powershell, elvish) generated from the clap definition

Utility Modules

  • util/: Shared utilities for parsing, conversion, and output formatting
    • Recipe parsing and scaling
    • Output format conversions (human, JSON, YAML, Markdown, Cooklang)
    • Shopping list generation with aisle/pantry support

Web Server

  • Backend: Axum web framework in src/server/
  • Frontend: Server-side rendered HTML using Askama templates
  • Templates: Located in templates/ directory
  • Styling: Tailwind CSS with custom components
  • API handlers in src/server/handlers/
  • Static files served from static/ directory
  • Shopping list stored as .shopping-list and .shopping-checked at the root of the recipe directory (ShoppingListStore); server/shopping_list_watcher.rs pushes outside edits to open pages
  • Opt-in sign-in in src/server/auth/: a users.toml in the config dir (or --users-file / COOK_USERS_FILE) turns it on. Each user has a role (reader < shopper < editor < admin; a bare hash is admin). auth::middleware::required_capability is the one place deciding which Capability a request needs — every non-GET route except an explicit allowlist, plus a few GETs (/edit, /new, /api/ws/lsp, /api/sync). A write route not listed in its WRITES table needs Administer, so list a new one there at the capability it deserves. A GET handler must never change anything. Guests get 401, signed-in users whose role falls short 403; on /api both are translated and marked X-Cook-Refused, which static/js/sign-in-notice.js turns into a notice (with a sign-in link for 401) for any page's write. Templates get a Viewer (src/web/viewer.rs) and hide controls with viewer.can_edit_lists() / can_edit_recipes() / can_admin()
  • --recipes-only / COOK_RECIPES_ONLY: whoever is not signed in (everyone without a users file) only reads recipes. auth::middleware::browses_recipes is an allowlist (RECIPE_READS) of what they may request; anything else, a new route included, asks them to sign in (404 without sign-in). Their Viewer can do nothing and viewer.recipes_only() hides the rest in templates
  • Leaving a page with something unsaved asks first (static/js/unsaved.js, loaded by base.html). A write sent with fetch counts on its own until it answers. Mark what holds typed data: data-unsaved on a form, inline editor or field (it counts while on screen and changed since first touched), data-dirty while a page holds changes it has not sent, or CookUnsaved.watch(fn). A page that moves on by itself after saving uses CookUnsaved.leave(url) / CookUnsaved.reload() instead of location
  • Every handler that changes something takes Extension<Viewer> and, once the change succeeded, calls server::activity::record — one stdout line saying who did what. Quote request text with activity::quoted / activity::file so it cannot forge a line.
  • Error text sent to a client goes through handlers::common::json_error or ui::error_page, which pass it to server::private_paths::hide: paths come out relative to the recipe directory, or as …/file for a config folder. Never put e.to_string() in a response body by hand, and never put the recipe directory's absolute path in a page or API answer.
  • Files from the recipe directory are served narrowly: /api/static gives only pictures and never a name starting with . (server/static_files.rs), and the recipe endpoints take only .cook / .menu paths (handlers::common::is_recipe_request). Serve new files through these rather than a fresh ServeDir.

Recipe Processing Pipeline

  1. Recipe discovery via cooklang-find (handles paths and search)
  2. Parsing via cooklang parser (lenient mode for validation)
  3. Scaling and conversion in util/ modules
  4. Output formatting based on requested format

Error Handling

  • Uses anyhow::Result throughout for error propagation
  • User-friendly error messages with context
  • Special handling for recipe validation (warnings vs errors)

Important Implementation Details

Recipe Scaling

  • Scaling notation: recipe.cook:2 (uses colon)
  • Handled in util::split_recipe_name_and_scaling_factor()
  • Applied during parsing before output

Shopping List Aggregation

  • Ingredients with same name are automatically combined
  • Unit conversion handled by cooklang crate
  • Aisle categorization via aisle.conf
  • Pantry filtering via pantry.conf (TOML format with quantities and dates)

Doctor Command

  • validate: Checks syntax, deprecated features, and recipe references
  • aisle: Finds ingredients not in aisle configuration
  • Exit codes: Normal (0) or strict mode (1) for CI/CD

Report Command

  • Uses cooklang-reports with Jinja2 templates
  • Config builder pattern for scale, datastore, aisle, pantry
  • Falls back to context configurations if not specified

File Formats

  • .cook files use Cooklang markup
  • Supports YAML frontmatter or >> metadata (deprecated)
  • Output formats: human, json, yaml, markdown, cooklang

Parser and Config File Limits

  • Every parser is built with Extensions::empty() (crates/core/src/parser.rs, crates/format/src/lib.rs): no @& references, no range values (2-3 stays text), no Unicode fractions. Don't turn extensions on in CookCLI; they need a spec change first (#219, #502).
  • -- and [- open a comment anywhere on a line, > notes included, and \ escapes one character. The editor's highlighting (static/js/src/cooklang-mode.js) has to follow the same rules.
  • cooklang's scale_to_target rounds a servings target to a whole number, so the shopping list scales servings references itself (scale_to_servings in crates/core/src/shopping_list/mod.rs).
  • Recipe reference paths keep their leading ./ (/recipe/./Shared/X): strip ./ and the extension before comparing two of them.
  • aisle.conf only has // comments; # or -- lines become ingredients, which cookcli_core::aisle::comment_lookalikes warns about. pantry.conf is TOML, edited with toml_edit in cookcli-core so comments survive; the server writes it through core too.

Frontend Architecture

Template System

The web UI uses server-side rendering with Askama templates:

  • Templates located in templates/ directory
  • Base template (base.html) provides common layout
  • Page-specific templates extend the base template
  • Template data structures defined in src/web/templates.rs
  • Templates are compiled into the binary: a template change needs a rebuild and a server restart
  • In page scripts, DOM replaceChildren / append write false and undefined as text, so never pass them cond && node; filter first, like el() / fill() in templates/aisles.html

Styling

  • Tailwind CSS for utility-first styling
  • Custom components defined in static/css/components.css
  • Compiled CSS output in static/css/output.css
  • Tailwind is configured in static/css/input.css (@source, @custom-variant, @theme); components live in static/css/components.css

Translations

  • UI strings are Fluent messages in locales/<lang>/*.ftl (aisles, common, pantry, preferences, recipes, shopping), one folder per locale: de-DE, en-US, es-ES, eu-ES, fr-FR, it-IT, ja-JP, nl-NL, sv-SE
  • A literal { in a message is written {"{"}
  • Templates call tr.t("key"), tr.tn("key", count) (plurals) and tr.tf("key", factor), defined on Tr in src/web/templates.rs; askama passes fields by reference, so helpers take &usize / &f64
  • Add every new message to every locale. tests/locales_test.rs fails when a locale lacks an en-US message id, or defines one twice — Fluent rejects a locale with a duplicate id, and every page in that language panics. Two branches adding the same key both pass on their own, so check again after a merge or rebase

Key Templates

  • base.html - Common layout with navigation and search
  • recipes.html - Recipe listing with directory navigation
  • recipe.html - Individual recipe display with scaling
  • menu.html - Menu and meal plan display
  • edit.html / new.html - Recipe and menu editor, new file form
  • shopping_list.html - Shopping list management
  • pantry.html / aisles.html - Pantry and aisle management
  • preferences.html - User preferences and settings

Frontend Features

  • Recipe Browsing: Directory-based navigation with breadcrumbs
  • Recipe Display: Ingredients, steps, metadata with colorful badges
  • Shopping List: Persistent storage in .shopping-list / .shopping-checked in the recipe directory
  • Recipe Scaling: Dynamic scaling with URL parameters
  • Search: Real-time recipe search with dropdown results
  • Responsive Design: Mobile-friendly layout with Tailwind

CSS Component Classes

Component classes live in static/css/components.css and resolve every colour through the tokens in static/css/input.css:

  • .card / .card-head - Bordered surface with hairline and 6px radius
  • .btn, .btn-primary, .btn-danger, .btn-sm - 40px controls (32px compact); one accent-filled primary per view
  • .icon-btn - 36px icon-only control
  • .nav-card, .nav-pill, .menu-item - Navigation card, pills, and small-screen menu rows
  • .recipe-card, .recipe-card-icon, .recipe-card-title - Index cards with a flat icon disc
  • .ingredient-badge, .cookware-badge, .timer-badge - Inline recipe entities (tint, dotted underline, chip)
  • .metadata-pill, .tag - Neutral bordered pills
  • .step-box, .step-number, .step-body, .step-refs - Boxed recipe steps
  • .ingredient-row, .row, .row-value, .row-note - List rows
  • .pantry-row, .pantry-editor, .pantry-expiry, .pantry-actions, .item-status-dot - Pantry item rows, their edit panel, expiry tags and stock state
  • .stepper, .select, .search-input - Form controls
  • .editor-toolbar, .editor-toolbar-group, .editor-toolbar-glyph - Cooklang insert toolbar above the editor (static/js/src/toolbar.js)

Testing Approach

Automated tests live in two places:

  • Rust integration tests in tests/*.rs (cargo test), with insta snapshots in tests/snapshots/
  • Playwright end-to-end specs in tests/e2e/ (npm test; see UI Testing below)

Things to know when running them:

  • When a change alters output other pages share (URLs, templates, badges), run the whole Playwright project, not only the specs you touched — other specs assert on that output too.
  • The pantry e2e specs add and edit items in ./seed, which can leave seed/config/pantry.conf modified; check git status after a run.
  • Two cookcli-core pantry tests check file permissions and cannot pass when cargo runs as root (e.g. in a root container). Their failure message says so; it is not a regression.
  • cargo test stops at the first failing test binary; add --no-fail-fast to see them all.
  • Build the expected value of a path in a test with the platform separator (Path::join, or .replace('/', std::path::MAIN_SEPARATOR_STR) as in tests/doctor_characterization_test.rs). CookCLI prints native paths, so a literal "config/pantry.conf" fails only on the Windows CI job. URLs and API paths the code normalizes to / are the exception.
  • docs/api.md is generated from the API docs: regenerate it with UPDATE_API_DOCS=1 cargo test --test api_docs_md_test.
  • The Docker image builds with --no-default-features --features server,lsp (no sync). Anything the server needs must not sit under #[cfg(feature = "sync")]; check with that build when touching AppState.
  • Two checkouts must not share a CARGO_TARGET_DIR: the artifacts have the same names, so one checkout's build replaces the cook the other's tests run.
  • Playwright is fully parallel locally but uses one worker on CI. The shopping-list and pantry specs share files in ./seed, so retry a local failure there with --workers=1 before blaming the change, and restore seed/config/aisle.conf as well as pantry.conf afterwards.
  • A spec that fakes ticks through the POST /api/shopping_list answer must also stub GET /api/shopping_list/checked: the page's event stream refetches the ticks when it opens.
  • The Docker image's docker-entrypoint.sh refuses a /recipes it cannot write; mount a writable copy to try it.

For manual testing:

  1. Use cook seed to create test recipes
  2. Test each command with various options
  3. Validate output formats
  4. Check error handling with invalid inputs

Isolating spawned processes from the real config

Any test that spawns cook — and every test that spawns cook server — must route its Command through common::with_isolated_config (tests/common/mod.rs), which sets COOK_CONFIG_DIR to a directory inside the test's TempDir.

This is not about tidiness. cook server loads session.json on boot and, if it finds one, starts syncing the directory it was given against the global sync.db — the database tracking the developer's own recipe folder. Without isolation, cargo test on a machine where someone has run cook login can upload fixture recipes to their real cook.md account, or index a temp directory into that database and propagate the deletions.

Setting HOME / XDG_CONFIG_HOME is not sufficient: directories resolves the Windows config directory through the Known Folder API and ignores both. COOK_CONFIG_DIR is the only lever that works everywhere. tests/config_dir_env_test.rs is the regression guard; the Playwright webServer sets the same variable in playwright.config.ts.

Release Process

Uses semantic commit messages for automated releases:

  • feat: - New features
  • fix: - Bug fixes
  • docs: - Documentation changes
  • chore: - Maintenance tasks

Packaging scripts live in packaging/: debian/ (.deb), fedora/ (.rpm) and source-tarball.sh, which builds the source tarball with the compiled front-end assets (shared by the release workflow and the RPM build).

Release workflow (.github/workflows/release.yaml) traps:

  • The Linux glibc binary is built in PyPA's manylinux_2_28 image (Cross.toml), and a release step fails when the binary needs a glibc above FLOOR=2.28. Building it natively on ubuntu-latest would raise that floor to the runner's glibc (#420). Bump the image's dated tag, never latest.
  • The Swatinem/rust-cache key includes matrix.target: a shared key once fed build scripts linked against a newer glibc to older cross images (#656).
  • When Dependabot cannot fix an advisory because the fixed version raises other crates' minimums, cargo update -p <crate> --precise <version> can (#640).

Before Creating a PR

Always run these checks before creating a PR:

cargo fmt         # Fix formatting
cargo clippy      # Check for lint warnings
cargo test        # Run tests

All three must pass cleanly with no warnings or errors.

Common Development Tasks

Adding a New Command

  1. Add variant to Command enum in src/args.rs
  2. Create module in src/ with XxxArgs struct and run() function
  3. Add case in main.rs match statement
  4. Update help text and documentation

Modifying Output Formats

Output formatting is centralized in src/util/ modules. Each format has its own module with consistent interface.

Working with Templates

  1. Create new template in templates/ directory
  2. Define data structure in src/web/templates.rs
  3. Implement handler in src/server/ui.rs
  4. Add route in the UI router

Modifying Styles

  1. Edit component classes in static/css/components.css
  2. Run make css or npm run build-css to compile
  3. For development, use npm run watch-css for auto-rebuild
  4. Custom colors and utilities can be added to static/css/input.css (@theme) and component classes to static/css/components.css

Frontend Development Workflow

  1. Install dependencies: npm install
  2. Start CSS watch: npm run watch-css
  3. Run server: cargo run -- server ./seed
  4. Make changes to templates or styles
  5. Rebuild and restart the server for template changes (Askama compiles them in); CSS and JS changes only need a browser refresh in a debug build, once watch-css / watch-js has rebuilt them

Debugging Recipe Parsing

Use RUST_LOG=trace to see detailed parsing information including:

  • File discovery paths
  • Configuration loading
  • Recipe reference resolution
  • Static file serving paths

UI Testing Documentation

This document describes the comprehensive UI testing setup for CookCLI using Playwright.

Overview

The testing suite provides end-to-end (E2E) testing for the CookCLI web interface, covering:

  • Navigation and routing
  • Recipe display and scaling
  • Shopping list functionality
  • Search capabilities
  • Pantry management
  • User preferences
  • Accessibility compliance
  • Performance metrics

Setup

Installation

# Install dependencies
npm install

# Install Playwright browsers
npx playwright install

Running Tests

# Run all tests
npm test

# Run tests with UI mode (interactive)
npm run test:ui

# Run tests in debug mode
npm run test:debug

# Run tests with browser visible
npm run test:headed

# Run specific browser tests
npm run test:chrome
npm run test:firefox
npm run test:webkit

# Run mobile tests
npm run test:mobile

# Show test report
npm run test:report

# Generate tests interactively
npm run test:codegen

Test Structure

Directory Layout

tests/
├── e2e/                    # End-to-end tests
│   ├── navigation.spec.ts     # Navigation and routing tests
│   ├── recipe-display.spec.ts # Recipe rendering tests
│   ├── recipe-scaling.spec.ts # Recipe scaling functionality
│   ├── search.spec.ts         # Search functionality
│   ├── shopping-list.spec.ts  # Shopping list management
│   ├── preferences.spec.ts    # User preferences
│   ├── pantry.spec.ts        # Pantry management
│   ├── accessibility.spec.ts  # WCAG compliance tests
│   └── performance.spec.ts    # Performance metrics
└── fixtures/              # Test utilities
    └── test-helpers.ts    # Reusable helper functions

Test Helpers

The test-helpers.ts file provides reusable utilities:

  • TestHelpers: Common navigation and interaction methods
  • RecipePage: Recipe-specific page object model
  • ShoppingListPage: Shopping list page object model

Test Coverage

Navigation Tests (navigation.spec.ts)

  • Home page display
  • Recipe navigation
  • Breadcrumb navigation
  • Directory browsing
  • Navigation menu consistency
  • Browser history handling

Recipe Display (recipe-display.spec.ts)

  • Recipe title and description
  • Ingredients list
  • Cooking steps
  • Ingredient/cookware/timer highlighting
  • Recipe metadata
  • Responsive layout

Recipe Scaling (recipe-scaling.spec.ts)

  • Scale input functionality
  • URL parameter scaling
  • Decimal scaling support
  • Scaling persistence
  • Shopping list integration

Search (search.spec.ts)

  • Search input functionality
  • Search results display
  • No results handling
  • Special character support
  • Case-insensitive search
  • Search persistence

Shopping List (shopping-list.spec.ts)

  • Empty state display
  • Adding ingredients
  • Item completion toggling
  • List clearing
  • Ingredient aggregation
  • Aisle organization
  • Session persistence

Preferences (preferences.spec.ts)

  • Preference display
  • Pantry configuration
  • Aisle configuration
  • File upload handling
  • Configuration validation
  • Settings persistence

Pantry Management (pantry.spec.ts)

  • Pantry navigation
  • Item display
  • Adding/editing/removing items
  • Shopping list filtering
  • Recipe integration
  • Import/export functionality

Accessibility (accessibility.spec.ts)

  • WCAG 2.0 AA compliance
  • Keyboard navigation
  • Screen reader support
  • ARIA labels
  • Color contrast
  • Focus indicators
  • Form labels

Performance (performance.spec.ts)

  • Page load times
  • Search performance
  • Scaling responsiveness
  • Memory usage
  • Asset caching
  • Cumulative Layout Shift (CLS)

Configuration

Playwright Configuration (playwright.config.ts)

Key settings:

  • Base URL: http://localhost:9080
  • Web Server: Automatically starts dev server
  • Browsers: Chrome, Firefox, Safari, Mobile
  • Parallel Execution: Enabled
  • Retries: 2 on CI, 0 locally
  • Artifacts: Screenshots, videos, traces on failure

CI/CD Integration

The UI Tests job of .github/workflows/test.yml:

  • Runs on push/PR to main branch, alongside the Rust test jobs
  • Chromium only, against the pre-built debug binary
  • Uploads the Playwright report, plus videos when a test fails

Changes under .github/ are checked by workflow-lint.yml (actionlint and zizmor). To run the same checks locally:

docker run --rm -v "$PWD:/repo" -w /repo rhysd/actionlint:1.7.12
docker run --rm -v "$PWD:/repo" -w /repo ghcr.io/zizmorcore/zizmor:1.30.1 --min-severity=low .github/workflows

Writing New Tests

Basic Test Structure

import { test, expect } from '@playwright/test';
import { TestHelpers } from '../fixtures/test-helpers';

test.describe('Feature Name', () => {
  let helpers: TestHelpers;

  test.beforeEach(async ({ page }) => {
    helpers = new TestHelpers(page);
    await helpers.navigateTo('/');
  });

  test('should do something', async ({ page }) => {
    // Test implementation
    await expect(page.locator('selector')).toBeVisible();
  });
});

Using Test Helpers

// Navigate to a page
await helpers.navigateTo('/recipes');

// Search for a recipe
await helpers.searchRecipe('pasta');

// Scale a recipe
await helpers.scaleRecipe(2);

// Add to shopping list
await helpers.addToShoppingList();

Best Practices

Test Isolation

  • Each test should be independent
  • Use beforeEach for setup
  • Clean up after tests when necessary

Selectors

  • Prefer semantic selectors (roles, labels)
  • Use data attributes for test-specific targeting
  • Avoid brittle CSS selectors

Assertions

  • Use explicit waits (waitForLoadState)
  • Check visibility before interaction
  • Verify both positive and negative cases

Performance

  • Run tests in parallel when possible
  • Use page objects for reusability
  • Minimize test data setup

Debugging

Visual Debugging

# Run with UI mode
npm run test:ui

# Run with browser visible
npm run test:headed

# Debug specific test
npm run test:debug

Trace Viewer

Traces are automatically captured on failure:

# View trace
npx playwright show-trace trace.zip

Screenshots and Videos

Available in test-results/ directory after failures.

Continuous Integration

Tests run automatically on:

  • Push to main branch
  • Pull requests
  • Manual workflow dispatch

Results available as GitHub Actions artifacts.

Troubleshooting

Common Issues

  1. Server not starting: Ensure make dev_server works locally
  2. Browser installation: Run npx playwright install
  3. Port conflicts: Check port 9080 is available
  4. Slow tests: Increase timeouts in config

Environment Variables

  • CI: Set in CI environment for different behavior
  • DEBUG: Enable Playwright debug output

Future Improvements

  • Visual regression testing
  • API mocking for edge cases
  • Load testing with multiple concurrent users
  • Internationalization testing
  • Cross-browser compatibility matrix

Download now (ZIP)

Trending

Steady: 0 new projects took it in the last 4 weeks, none the 4 weeks before.thin data

Week of Aug 30: 0 new projectsWeek of Sep 6: 0 new projectsWeek of Sep 13: 0 new projectsWeek of Sep 20: 0 new projectsWeek of Sep 27: 0 new projectsWeek of Oct 4: 0 new projectsWeek of Oct 11: 0 new projectsWeek of Oct 18: 0 new projects
Aug 2026Sep 2026Oct 2026
New projects taking a version of this file each week, over the 8 weeks our latest run read: only 0 in all, so this is a hint, not a trend.

Who's talking about cooklang/cookcli

The biggest 5 of 52 mentions linking to the repository that our crawl found. Each line is their own headline.

See all 52

Talked about on GitHub, Hacker News and Bluesky (52 times).

On GitHub · 50

45 more on GitHub

On Hacker News · 1

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.