# Tasks: clean paste, HTML to Markdown (roadmap 29) One task = one agent (`specs/agents/opencode-runbook.md`). Spec: `spec.md`. Rules: `AGENTS.md`, `docs/CODE-MAP.md`. **First tool call rule**: after at most three reads, write the first OWNS file in your SECOND tool call (compiling stub + ONE failing test), then make it pass. **Standing VERIFY**: `export PATH="$HOME/.cargo/bin:$PATH"`; `cargo test -p `; `cargo clippy -p --all-targets -- -D warnings`; `rustfmt --edition 2024 `; UI tasks add `cargo xtask i18n`; last task of a phase adds `cargo xtask layers` and `cargo xtask tree`. **Standing DO NOT**: edit files outside OWNS, git state, `cargo fmt --all`; add `allow`; `unwrap/expect/panic/[i]/[a..b]` on input-derived data; add a dependency (the crate has none). ## Owner/lead step 0 Answer Q1 to Q3. Collect three real fixtures into `crates/htmlmd/tests/fixtures/` (Word, Google Docs, a browser article): the owner or lead pastes `text/html` clipboards (macOS: `osascript -e 'the clipboard as «class HTML»'`); task 3 cannot finish without them. ## Phase 1 - pure crate - [ ] 1. Crate `notescraft-htmlmd`: tokenizer, entities, limits (brief `briefs/16-htmlmd-tokenizer.md`; includes crate registration). OWNS: `crates/htmlmd/Cargo.toml`, `crates/htmlmd/src/lib.rs`, `crates/htmlmd/src/tokens.rs`, `crates/htmlmd/src/entities.rs`, `crates/htmlmd/tests/tokens.rs`; registration: root `Cargo.toml` (one `[workspace.dependencies]` line), `xtask/src/layers.rs` (`("htmlmd", Class::Layer(0))`). FILES TO OPEN / GOAL / TESTS / VERIFY / DO NOT: see the brief. - [ ] 2. Blocks and inlines to GFM. OWNS: `crates/htmlmd/src/convert.rs`, `crates/htmlmd/src/inline.rs`, `crates/htmlmd/src/list.rs`, `crates/htmlmd/src/table.rs`, `crates/htmlmd/src/lib.rs` (`to_markdown`, `Options`, `Converted` exports), `crates/htmlmd/tests/convert.rs`. FILES TO OPEN: `spec.md` D1, `crates/htmlmd/src/tokens.rs`, `crates/markdown/src/lib.rs` (how GFM is parsed here, for the round-trip test: use `notescraft_markdown` only as a dev-dependency? NO: layers forbid same-layer deps. Use `pulldown-cmark` already approved as a DEV dependency in `crates/htmlmd/Cargo.toml` and say so in the report). GOAL: D1 and the escaping rules; iterative stack, depth cap; tables with a header row and pipes escaped; code blocks fenced with a fence longer than any backtick run inside. TESTS: each construct; nested lists 4 deep; ordered list start; `
` in cell; escapes at line start; depth 65 -> TooLarge; round trip of 6 sample Markdown docs through pulldown-cmark HTML. VERIFY: standing `-p notescraft-htmlmd`. DO NOT: add runtime deps; handle Word quirks yet. - [ ] 3. Word and Google Docs quirks, safety, fixtures. OWNS: `crates/htmlmd/src/quirks.rs`, `crates/htmlmd/src/convert.rs` (hook lines), `crates/htmlmd/tests/fixtures/*.html` + `*.md`, `crates/htmlmd/tests/fixtures.rs`, `crates/htmlmd/tests/hostile.rs`. FILES TO OPEN: `spec.md` D2 and D4, the fixtures. GOAL: D2 heuristics (inline style weight/italic/strike, MsoList levels, Docs wrapper, conditional comments, `o:p`); URL scheme filter. TESTS: acceptance 1, 2, 3 and 5 (LCG-seeded tag soup). VERIFY: standing. DO NOT: read files outside `tests/fixtures`. ## Phase 2 - engine and UI - [ ] 4. Command `edit.html_to_markdown` and setting. OWNS: `crates/engine/Cargo.toml` (one dep line), `crates/engine/src/clipboard_md/{mod,commands}.rs` (module name must not clash: check `git grep -n "mod clipboard" crates/engine/src`), one `mod` line in `crates/engine/src/lib.rs`, one `register_*` call in `commands.rs::register_core`, `crates/engine/src/prefs/paste_config.rs`, `crates/engine/src/prefs/catalog/` entry, `crates/engine/src/ai/assistant_gate.rs`, `crates/engine/tests/core_commands.rs`, `crates/engine/tests/html_to_markdown.rs`. FILES TO OPEN: `crates/engine/src/viewers/` (small command group to copy), `crates/engine/src/prefs/files_config.rs`, `crates/engine/tests/files_config_viewers.rs`. GOAL: command + `editor.paste_html` setting (`convert|plain`, invalid rejected). TESTS: command converts a fixture; TooLarge maps to a typed error; setting default and validation. VERIFY: standing + `--test core_commands`. DO NOT: touch the UI. - [ ] 5. Read HTML from the clipboard and insert. OWNS: `crates/ui-egui/src/shell/attachment_drop/clipboard.rs` (`Html` variant + read), `crates/ui-egui/src/shell/attachment_drop/mod.rs` (`Request::Text`, routing; Shift+V = plain), `crates/ui-egui/src/shell/app/editor/attachments.rs` (handle `Request::Text`), `crates/ui-egui/src/text/paste.rs` + `[ui.paste]` keys at END, `crates/ui-egui/tests/paste_html.rs`. FILES TO OPEN: `spec.md` D3, those files, `crates/ui-egui/src/testsupport.rs` (fake `ClipboardSource`). GOAL: acceptance 3 and 4; verify `arboard::Get::html()` compiles (Q1: stop and report if it needs a feature). TESTS: fake clipboard with HTML -> one undo step with converted text; Shift -> plain; setting `plain`; TooLarge -> plain + toast; image on clipboard still follows the old path. VERIFY: standing + `cargo xtask i18n`. DO NOT: break image/file paste; do vault IO while painting. - [ ] 6. Docs and status. OWNS: `ROADMAP.md`, `docs/ARCHITECTURE.md` (new crate), `docs/CODE-MAP.md`, `specs/plugins-parity/roadmap.md` (mark 29). VERIFY: `cargo xtask ci`.