# CLAUDE.md

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

## Overview

`Web::Microformats2` is a Perl distribution that parses [Microformats2](http://microformats.org/wiki/microformats2-parsing) metadata out of HTML (or re-inflates it from MF2 JSON), exposing it as a queryable in-memory object graph that can be re-serialized to MF2 JSON.

## Commands

The distribution is managed with [Minilla](https://metacpan.org/pod/Minilla) and built with `Module::Build::Tiny`.

```bash
# Install dependencies (from cpanfile)
cpanm --installdeps .

# Run the full test suite
prove -l t/

# Run a single test file (verbose)
prove -lv t/parse.t

# Release tasks (regenerates META.json, README.md, Build.PL from lib/)
minil test
minil release
```

Note: `README.md`, `Build.PL`, and `META.json` are **generated by Minilla** — do not hand-edit them. Documentation lives in the POD inside the `.pm` files; `README.md` is built from `lib/Web/Microformats2.pm`.

## Testing model

The test suite is driven by the **official Microformats2 baseline test cases**, vendored under `t/microformats-v2/` (organized by vocabulary: `h-card`, `h-entry`, `rel`, etc.). Each case is a pair of files sharing a basename: `NAME.html` (input) and `NAME.json` (expected MF2 JSON output).

- `t/parse.t` recurses that directory, parses each `.html`, and compares `$parser->parse($html)->as_json` against the corresponding `.json` with `is_deeply`.
- `t/document.t` covers the Document object API (round-tripping through JSON, querying items).

To add coverage for a parsing bug or new feature, add an `.html`/`.json` pair under the appropriate `t/microformats-v2/` subdirectory rather than writing a bespoke test.

## Development workflow: TDD

Adhere to test-driven development for **all** changes to this distribution. Write the test first, watch it fail, then write the code that makes it pass.

- **Bugs and parsing/spec changes:** add the failing `.html`/`.json` baseline pair (or extend an existing bespoke test) *before* touching the code, confirm it's red, then fix.
- **New behavior:** express the desired behavior as a failing test first; let that test drive the implementation.
- **Refactors with no behavior change** (e.g. dependency swaps, deduplication): the relevant tests must already exist and stay green throughout — that green bar is the proof the refactor is safe. If the affected behavior isn't yet covered, add the characterizing test first.
- Run `prove -l t/` and confirm a clean pass before committing. Don't commit code whose tests you haven't seen go from red to green (or stay green, for refactors).

## Architecture

Three Moo classes under `lib/Web/Microformats2/`:

- **`Parser`** — the engine. `parse()` builds an `HTML::TreeBuilder::XPath` tree, then `analyze_element()` walks it recursively. The core loop teases MF2 class attributes off each element and dispatches by prefix: `h-*` creates a new **Item** (nested under the `current_item` to form the parent/child tree); `p-` / `u-` / `e-` / `dt-` attach typed **properties** to the current item. It also accumulates document-level `rel` / `rel-urls`. The prefix-to-handling logic and the value-parsing rules (plain text, URL resolution against `url_context`, datetime normalization via `DateTime::Format::ISO8601`, e-content HTML) are where most spec complexity lives.

- **`Document`** — the parse result. Holds `top_level_items`, a flat `items` list, and `rels`/`rel_urls`. `as_json` emits spec-shaped MF2 JSON (via `convert_blessed`); `new_from_json` reconstructs a Document from serialized MF2.

- **`Item`** — one `h-*` microformat (a "thingy": an entry, a card, an event…). Properties are stored split by prefix in `properties`/`p_properties`/`u_properties`/`e_properties`/`dt_properties` hashes, with a `parent` link and child items. `get_property` / `all_types` are the main query accessors.

The dependency boundary worth knowing: parsing relies on `HTML::TreeBuilder::XPath` for the DOM and `URI` for resolving relative URLs against the page's base (`url_context`, set from a `<base href>` or the `url_context` arg to `parse()`).
