diff --git a/AGENTS.md b/AGENTS.md index 672a21bb1a..f984c81b3a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -86,6 +86,7 @@ If you find an asset that breaks these rules, stop and remove it from the reposi - **The UI control channel is opt-in too.** It runs only when the app is launched with `--control `. It binds to loopback, uses a fresh random token per launch, and writes it to an owner-only file. Never enable it by default, widen the bind address, or log the token. - **The MCP server is opt-in.** Never make the app, the CLI or an installer start it, register it with an agent, or listen on a port by default. It runs only when a user (or an agent the user configured) launches `printcraft-cli mcp`. Any future network transport must be off by default, bound to loopback and authenticated with a token. - **Safe by default.** Tools must never discard unsaved work silently, must write atomically, must honour `--root`, and must return errors an agent can act on (say what was wrong and what is allowed). +- **Track parity honestly.** Every user-facing feature has an entry in `parity/acrobat-features.toml`. Mark it `shipped` only when it works end to end and the entry cites a passing test (`evidence`); `cargo xtask parity` (part of `xtask ci`) rejects claims whose commands, tools or tests don't exist. - **Use it to test.** Agents should drive the engine through `printcraft-cli run --script` or the MCP tools, and look at `page_render` output, to check their own changes. For UI changes, launch the app with `--control` and use `printcraft-cli ui` (`inspect`, `click`, `screenshot`) to check the real interface. ## 4. Everything else diff --git a/CLAUDE.md b/CLAUDE.md index 1c16d931ba..2af43a8f35 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,6 +26,8 @@ PrintCraft is a clean-room, open-source, Rust-native PDF application targeting A ## Running and looking at the app - `cargo run -p printcraft -- ` opens the desktop app. - `cargo run -p printcraft-cli -- run --script steps.json --root DIR` drives the engine headlessly through the automation tools (`printcraft-cli tools` lists them). Use it, together with `page_render`, to check engine changes. `printcraft-cli mcp` is the opt-in MCP server (AGENTS.md §3). +- `cargo xtask fuzz --time 300` mutation-fuzzes open/render/edit/save in child processes. Findings land in `fuzz-out/findings/` (git-ignored; never commit corpus-derived files). Turn every real finding into a small synthetic regression test before fixing it. +- `cargo xtask parity [--partial]` reports Acrobat-parity progress from `parity/acrobat-features.toml`. Update the entry when a feature ships. - `cargo xtask demo-pdf` builds `dist/demo/printcraft-showcase.pdf` (needs Chrome) for visual checks. - For UI work, **look at the result**. Either launch `printcraft --control FILE doc.pdf` and use `printcraft-cli ui --control FILE screenshot --out x.png` (plus `inspect`, `click`, `key`, `type`, `command`, `set`), or take a headless shot with `cargo run -p printcraft-ui-egui --example shot`. Compare against `plan/acrobat/02-ui-ux.md`. Control-channel tests use kittest (`crates/ui-egui/tests/control.rs`). - Parallel agents: use a separate `CARGO_TARGET_DIR` per agent and separate git worktrees. diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml new file mode 100644 index 0000000000..ce2e717665 --- /dev/null +++ b/parity/acrobat-features.toml @@ -0,0 +1,9978 @@ +# PrintCraft parity checklist vs Acrobat Pro (offline feature set). Generated from plan docs; maintained by hand. +# Validated and reported by `cargo xtask parity`. Schema: see the [schema] table. +# Status rules: `shipped` = usable today with at least one cited test; `partial` = part exists (notes say what is +# missing); `planned` = not started; `na` = Adobe-cloud-only (notes name the local or pluggable alternative). +# `commands` lists registry ids from crates/engine/src/commands.rs only; `tools` lists crates/automation/src/tools.rs names. +[schema] +version = 1 +statuses = ["planned", "partial", "shipped", "na"] +tiers = ["P0", "P1", "P2", "P3", "N/A"] +areas = { A = "core", B = "view", C = "edit", D = "organize", E = "comment", F = "form", G = "protect", H = "sign", I = "ocr", J = "create", K = "optimize", L = "print", M = "a11y", N = "misc" } + +# ================================================================================================== +# A. Core document model and fidelity +# ================================================================================================== + +[[feature]] +id = "core.open-pdf" +title = "Open PDF files" +area = "A" +tier = "P0" +milestone = "M1" +status = "shipped" +commands = ["file.open"] +tools = ["doc_open"] +evidence = ["crates/automation/tests/automation.rs::open_inspect_render_and_find", "crates/ui-egui/tests/ui.rs::opening_a_pdf_shows_comments_and_bookmarks"] +notes = "" + +[[feature]] +id = "core.repair-broken-files" +title = "Open damaged files by reconstructing the cross-reference table" +area = "A" +tier = "P0" +milestone = "M1" +status = "shipped" +commands = ["file.open"] +tools = ["doc_open"] +evidence = ["crates/cos/src/document.rs::damaged_xref_is_reconstructed", "crates/cos/src/parser.rs::indirect_stream_with_wrong_length_recovers", "crates/cos/src/document.rs::self_referential_length_does_not_overflow"] +notes = "" + +[[feature]] +id = "core.malformed-input-safety" +title = "Reject garbage or hostile input without crashing" +area = "A" +tier = "P0" +milestone = "M1" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/ui.rs::garbage_input_is_rejected_without_panicking", "crates/cos/src/parser.rs::garbage_never_panics", "crates/render/src/raster.rs::missing_page_and_garbage_input_fail_gracefully"] +notes = "fuzzed nightly (M1.10)" + +[[feature]] +id = "core.repair-log" +title = "Record repairs and show them to the user" +area = "A" +tier = "P0" +milestone = "M1" +status = "partial" +commands = [] +tools = ["doc_info"] +evidence = [] +notes = "doc_info reports repair notes; no repair log in the UI yet" + +[[feature]] +id = "core.xref-streams" +title = "Read cross-reference streams and compressed object streams" +area = "A" +tier = "P0" +milestone = "M1" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/cos/tests/writer.rs::incremental_saves_stack_on_an_xref_stream_file", "crates/cos/tests/writer.rs::page_tree_loops_in_object_streams_do_not_crash_readers"] +notes = "" + +[[feature]] +id = "core.hybrid-xref" +title = "Hybrid-reference files (/XRefStm) and quirky xref subsections" +area = "A" +tier = "P0" +milestone = "M1" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "M1.4" + +[[feature]] +id = "core.revision-chain" +title = "Follow /Prev revision chains with loop protection" +area = "A" +tier = "P0" +milestone = "M1" +status = "partial" +commands = [] +tools = [] +evidence = ["crates/organize/src/tests.rs::successive_incremental_saves_stack_revisions"] +notes = "reading works; revision list API is M1.4" + +[[feature]] +id = "core.stream-filters" +title = "Standard stream filters (Flate, LZW, ASCII85, ASCIIHex, RunLength, predictors)" +area = "A" +tier = "P0" +milestone = "M1" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/filters/src/lib.rs::chain_three_filters_with_predictor", "crates/filters/src/lzw.rs::spec_example", "crates/filters/src/predictor.rs::png_hand_vectors"] +notes = "" + +[[feature]] +id = "core.image-filters" +title = "Image codecs in the filters crate (DCT, JPX, JBIG2, CCITT)" +area = "A" +tier = "P0" +milestone = "M1" +status = "partial" +commands = [] +tools = [] +evidence = [] +notes = "decoded through the hayro bootstrap for display; own codecs are M1.1/M1.2" + +[[feature]] +id = "core.decompression-limits" +title = "Bounded decompression (filter bomb protection)" +area = "A" +tier = "P0" +milestone = "M1" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/filters/src/flate.rs::bomb_is_bounded", "crates/filters/src/lib.rs::limit_applies_to_intermediate_buffers"] +notes = "" + +[[feature]] +id = "core.open-rc4" +title = "Open RC4-encrypted documents (40/128-bit)" +area = "A" +tier = "P0" +milestone = "M1" +status = "shipped" +commands = ["file.open"] +tools = ["doc_open"] +evidence = ["crates/crypt/src/tests.rs::rc4_reference_vectors", "crates/cos/tests/encryption.rs::every_algorithm_round_trips_and_hides_the_content"] +notes = "" + +[[feature]] +id = "core.open-aes" +title = "Open AES-128 and AES-256 encrypted documents (R4-R6)" +area = "A" +tier = "P0" +milestone = "M1" +status = "shipped" +commands = ["file.open"] +tools = ["doc_open"] +evidence = ["crates/crypt/src/tests.rs::aes_cbc_nist_sp800_38a_vectors", "crates/cos/tests/encryption.rs::every_algorithm_round_trips_and_hides_the_content"] +notes = "" + +[[feature]] +id = "core.password-prompt" +title = "Ask for the user or owner password, with retry" +area = "A" +tier = "P0" +milestone = "M1" +status = "shipped" +commands = ["file.open"] +tools = ["doc_open"] +evidence = ["crates/ui-egui/tests/editing.rs::password_prompt_opens_and_security_tab_reports_the_details", "crates/crypt/src/tests.rs::empty_user_password_opens_without_asking"] +notes = "" + +[[feature]] +id = "core.unicode-passwords" +title = "Unicode passwords normalised with SASLprep" +area = "A" +tier = "P0" +milestone = "M1" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/crypt/src/tests.rs::r6_passwords_are_saslprepped"] +notes = "" + +[[feature]] +id = "core.owner-password-unlock" +title = "Owner password lifts document restrictions" +area = "A" +tier = "P0" +milestone = "M1" +status = "shipped" +commands = [] +tools = ["doc_open"] +evidence = ["crates/engine/src/tests.rs::restricted_documents_refuse_changes_unless_opened_by_the_owner", "crates/engine/src/tests.rs::owner_password_of_older_revisions_opens_the_viewer_too", "crates/crypt/src/tests.rs::owner_authentication_recovers_the_user_password"] +notes = "" + +[[feature]] +id = "core.permissions-enforced" +title = "Honour document permissions (print, modify, assemble, copy)" +area = "A" +tier = "P0" +milestone = "M1" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/editing.rs::restricted_documents_show_a_notice_and_block_page_changes", "crates/crypt/src/tests.rs::permission_bits_follow_table_22", "crates/engine/src/tests.rs::combining_protected_files_is_refused_clearly"] +notes = "" + +[[feature]] +id = "core.crypt-filters" +title = "Crypt filters (Identity, /StmF, /StrF)" +area = "A" +tier = "P0" +milestone = "M1" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/crypt/src/tests.rs::identity_crypt_filters_leave_data_alone"] +notes = "" + +[[feature]] +id = "core.attachment-only-encryption" +title = "Attachment-only encryption (/EFF) opens without a password" +area = "A" +tier = "P1" +milestone = "M1" +status = "partial" +commands = [] +tools = [] +evidence = [] +notes = "two corpus files still ask for a password (M1.6)" + +[[feature]] +id = "core.unencrypted-metadata" +title = "Leave XMP metadata readable when /EncryptMetadata is false" +area = "A" +tier = "P1" +milestone = "M1" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/cos/tests/encryption.rs::unencrypted_metadata_is_left_readable"] +notes = "" + +[[feature]] +id = "core.unsupported-handlers" +title = "Clear message for unsupported security handlers" +area = "A" +tier = "P1" +milestone = "M1" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/crypt/src/tests.rs::unsupported_handlers_are_reported"] +notes = "" + +[[feature]] +id = "core.open-public-key" +title = "Open certificate-encrypted (public-key handler) documents" +area = "A" +tier = "P1" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "core.incremental-save" +title = "Save as an incremental update (original bytes kept, atomic write)" +area = "A" +tier = "P0" +milestone = "M1" +status = "shipped" +commands = ["file.save"] +tools = ["doc_save"] +evidence = ["crates/ui-egui/tests/editing.rs::save_writes_an_incremental_update_and_clears_dirty", "crates/engine/src/tests.rs::save_is_incremental_stamps_mod_date_and_rebases", "crates/organize/src/tests.rs::successive_incremental_saves_stack_revisions"] +notes = "" + +[[feature]] +id = "core.save-as-full-rewrite" +title = "Save as a new file with a full rewrite and garbage collection" +area = "A" +tier = "P0" +milestone = "M1" +status = "shipped" +commands = ["file.save_as"] +tools = ["doc_save"] +evidence = ["crates/organize/src/tests.rs::full_save_drops_unreachable_objects", "crates/engine/src/tests.rs::full_save_is_readable_and_smaller_after_deletes"] +notes = "" + +[[feature]] +id = "core.object-stream-compression" +title = "Compress objects into object streams behind an xref stream on full save" +area = "A" +tier = "P0" +milestone = "M1" +status = "shipped" +commands = ["file.save_as"] +tools = ["doc_save"] +evidence = ["crates/cos/tests/writer.rs::full_save_packs_objects_into_object_streams", "crates/cos/tests/writer.rs::qpdf_accepts_object_stream_output", "crates/cos/tests/writer.rs::objects_referenced_by_the_encrypt_dictionary_stay_outside_object_streams"] +notes = "" + +[[feature]] +id = "core.encrypted-save" +title = "Save encrypted documents re-encrypted under the same keys" +area = "A" +tier = "P0" +milestone = "M1" +status = "shipped" +commands = ["file.save"] +tools = ["doc_save"] +evidence = ["crates/cos/tests/encryption.rs::incremental_edits_of_encrypted_documents_stay_encrypted", "crates/engine/src/tests.rs::encrypted_documents_open_edit_and_save_encrypted"] +notes = "" + +[[feature]] +id = "core.copy-on-write-fidelity" +title = "Preserve untouched objects byte-for-byte (copy-on-write edits)" +area = "A" +tier = "P0" +milestone = "M1" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/cos/src/document.rs::reads_classic_revision_and_edits_are_copy_on_write"] +notes = "" + +[[feature]] +id = "core.write-validity" +title = "Written files pass qpdf --check" +area = "A" +tier = "P0" +milestone = "M1" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/cos/tests/encryption.rs::qpdf_agrees", "crates/cos/tests/writer.rs::qpdf_accepts_object_stream_output"] +notes = "skipped when qpdf is absent" + +[[feature]] +id = "core.deterministic-save" +title = "Deterministic full-save mode" +area = "A" +tier = "P2" +milestone = "M1" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "M1.9 option" + +[[feature]] +id = "core.linearization" +title = "Linearized save (Fast Web View)" +area = "A" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "core.revision-list" +title = "List document revisions from the xref chain" +area = "A" +tier = "P1" +milestone = "M1" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "core.view-revision" +title = "View an earlier revision as a read-only snapshot" +area = "A" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "core.lazy-loading" +title = "Lazy loading of very large (GB) files within a memory budget" +area = "A" +tier = "P0" +milestone = "M1" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "ByteSource (M1.5); files are buffered whole today" + +[[feature]] +id = "core.pdf20-utf8-strings" +title = "PDF 2.0 UTF-8 text strings" +area = "A" +tier = "P1" +milestone = "M2" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "core.pdf20-associated-files" +title = "PDF 2.0 associated files (/AF)" +area = "A" +tier = "P1" +milestone = "M2" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "core.pdf20-dpart" +title = "PDF 2.0 document parts (DPart)" +area = "A" +tier = "P2" +milestone = "M2" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "core.pdf20-namespaces" +title = "PDF 2.0 structure namespaces" +area = "A" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "core.arlington-validation" +title = "Structural validation against the Arlington PDF model" +area = "A" +tier = "P1" +milestone = "M2" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "core.undo-redo" +title = "Undo and redo every document edit" +area = "A" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = ["edit.undo", "edit.redo"] +tools = ["edit_undo", "edit_redo"] +evidence = ["crates/engine/src/tests.rs::undo_and_redo_restore_exact_states", "crates/ui-egui/tests/editing.rs::organize_delete_then_undo_and_redo_with_keys"] +notes = "" + +[[feature]] +id = "core.undo-labels" +title = "Edit menu names the step to undo or redo" +area = "A" +tier = "P1" +milestone = "M4" +status = "shipped" +commands = ["edit.undo", "edit.redo"] +tools = [] +evidence = ["crates/ui-egui/tests/editing.rs::edit_menu_names_the_step_to_undo", "crates/engine/src/commands.rs::enablement_follows_the_document_state"] +notes = "" + +[[feature]] +id = "core.undo-after-save" +title = "Undo still works after saving" +area = "A" +tier = "P1" +milestone = "M4" +status = "shipped" +commands = ["edit.undo"] +tools = ["edit_undo"] +evidence = ["crates/engine/src/tests.rs::undo_still_works_after_save"] +notes = "" + +[[feature]] +id = "core.atomic-batch-edits" +title = "Multi-step edits are one undo step and all-or-nothing" +area = "A" +tier = "P1" +milestone = "M4" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/engine/src/tests.rs::batch_is_one_undo_step_and_all_or_nothing", "crates/engine/src/tests.rs::failed_edit_changes_nothing"] +notes = "" + +[[feature]] +id = "core.history-panel" +title = "History panel with named snapshots" +area = "A" +tier = "P2" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "core.autosave" +title = "Autosave changed documents periodically" +area = "A" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/engine/src/tests.rs::autosave_snapshots_only_changed_documents", "crates/ui-egui/tests/recovery.rs::saving_or_closing_clears_the_entry_and_nothing_is_written_for_clean_documents"] +notes = "" + +[[feature]] +id = "core.crash-recovery" +title = "Offer to recover unsaved work after a crash" +area = "A" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/recovery.rs::a_crashed_session_is_offered_and_recovered_unsaved", "crates/ui-egui/tests/recovery.rs::discarding_removes_the_autosaves", "crates/engine/src/tests.rs::recovered_documents_reopen_unsaved_at_their_original_path"] +notes = "" + +[[feature]] +id = "core.encrypted-autosave" +title = "Autosaves of encrypted documents stay encrypted" +area = "A" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/recovery.rs::encrypted_documents_are_autosaved_encrypted_and_recovered_with_the_password"] +notes = "" + +[[feature]] +id = "core.web-recovery" +title = "Crash recovery in the web app" +area = "A" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "core.unsaved-changes-prompt" +title = "Ask before closing or quitting with unsaved changes" +area = "A" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = ["file.close"] +tools = ["doc_close"] +evidence = ["crates/ui-egui/tests/editing.rs::closing_a_dirty_tab_asks_and_cancel_keeps_it", "crates/ui-egui/tests/editing.rs::closing_a_dirty_tab_can_save_first", "crates/ui-egui/tests/editing.rs::quitting_with_unsaved_changes_asks_for_each_document"] +notes = "" + +[[feature]] +id = "core.dirty-indicator" +title = "Unsaved-changes marker on document tabs" +area = "A" +tier = "P1" +milestone = "M4" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/editing.rs::organize_delete_then_undo_and_redo_with_keys", "crates/engine/src/tests.rs::edits_update_view_data_and_mark_dirty"] +notes = "" + +[[feature]] +id = "core.close-document" +title = "Close a document" +area = "A" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = ["file.close"] +tools = ["doc_close"] +evidence = ["crates/ui-egui/tests/editing.rs::clean_tabs_close_without_asking"] +notes = "" + +[[feature]] +id = "core.close-all" +title = "Close all documents" +area = "A" +tier = "P1" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "core.revert" +title = "Revert to the last saved version" +area = "A" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "core.signed-save-policy" +title = "Force incremental saves for signed documents" +area = "A" +tier = "P0" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "M4.3; needs signature detection" + +[[feature]] +id = "core.drag-drop-open" +title = "Open files by drag and drop" +area = "A" +tier = "P1" +milestone = "M3" +status = "partial" +commands = [] +tools = [] +evidence = [] +notes = "desktop drop is handled; no test yet" + +[[feature]] +id = "core.recent-files" +title = "Local recent-files list" +area = "A" +tier = "P1" +milestone = "M3" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/ui.rs::persistence_round_trips_and_tolerates_garbage"] +notes = "never reads other apps' lists" + +[[feature]] +id = "core.usage-rights-preserve" +title = "Preserve usage-rights (UR3) signatures from Reader-extended files" +area = "A" +tier = "P3" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "preserve only" + +# ================================================================================================== +# B. Viewing and navigation +# ================================================================================================== + +[[feature]] +id = "view.render-pages" +title = "Render pages (text, vectors, images)" +area = "B" +tier = "P0" +milestone = "M2" +status = "partial" +commands = [] +tools = ["page_render"] +evidence = ["crates/render/src/raster.rs::renders_a_blue_rectangle", "crates/automation/tests/automation.rs::open_inspect_render_and_find"] +notes = "hayro bootstrap renderer; own DisplayList devices in M2.6" + +[[feature]] +id = "view.render-shadings-patterns" +title = "Shadings, gradients and tiling patterns" +area = "B" +tier = "P0" +milestone = "M2" +status = "partial" +commands = [] +tools = [] +evidence = ["crates/render/src/raster.rs::self_referencing_tiling_pattern_terminates"] +notes = "via hayro bootstrap" + +[[feature]] +id = "view.render-transparency" +title = "Blend modes, soft masks and transparency groups" +area = "B" +tier = "P0" +milestone = "M2" +status = "partial" +commands = [] +tools = [] +evidence = ["crates/render/src/raster.rs::soft_mask_without_group_colour_space_is_applied"] +notes = "via hayro bootstrap" + +[[feature]] +id = "view.render-knockout" +title = "Knockout transparency groups" +area = "B" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.render-type3" +title = "Type 3 fonts" +area = "B" +tier = "P0" +milestone = "M2" +status = "partial" +commands = [] +tools = [] +evidence = [] +notes = "hayro bootstrap with vendored patches" + +[[feature]] +id = "view.render-cid-fonts" +title = "CID fonts and CMaps incl. CJK without ToUnicode" +area = "B" +tier = "P0" +milestone = "M2" +status = "partial" +commands = [] +tools = [] +evidence = ["crates/render/src/raster.rs::huge_cid_width_ranges_terminate"] +notes = "CID-to-Unicode tables are M2.4" + +[[feature]] +id = "view.render-image-codecs" +title = "JBIG2, JPEG 2000, CCITT and JPEG images" +area = "B" +tier = "P0" +milestone = "M2" +status = "partial" +commands = [] +tools = [] +evidence = [] +notes = "hayro bootstrap" + +[[feature]] +id = "view.render-annotation-appearances" +title = "Annotation and widget appearances incl. appearance states" +area = "B" +tier = "P0" +milestone = "M2" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/render/src/raster.rs::widget_appearance_states"] +notes = "" + +[[feature]] +id = "view.render-error-isolation" +title = "Per-page error isolation (a bad page never takes down the app)" +area = "B" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/render/src/raster.rs::missing_page_and_garbage_input_fail_gracefully", "crates/render/src/raster.rs::scale_is_clamped_for_huge_pages"] +notes = "" + +[[feature]] +id = "view.render-priority" +title = "Render visible pages first, then neighbours and thumbnails" +area = "B" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/render/src/raster.rs::inline_pool_renders_in_priority_order"] +notes = "" + +[[feature]] +id = "view.deep-zoom-tiles" +title = "Tiled rendering keeps deep zoom sharp" +area = "B" +tier = "P0" +milestone = "M3" +status = "partial" +commands = [] +tools = [] +evidence = ["crates/render/src/raster.rs::tiles_match_full_render", "crates/render/src/raster.rs::effective_scale_never_exceeds_the_caps"] +notes = "capped at 8192 px per side (M3.3)" + +[[feature]] +id = "view.layout-continuous" +title = "Single page continuous scrolling" +area = "B" +tier = "P0" +milestone = "M3" +status = "partial" +commands = [] +tools = [] +evidence = [] +notes = "works; no layout test yet" + +[[feature]] +id = "view.layout-single-page" +title = "Single page view" +area = "B" +tier = "P0" +milestone = "M3" +status = "partial" +commands = [] +tools = [] +evidence = [] +notes = "works; no layout test yet" + +[[feature]] +id = "view.layout-two-up" +title = "Two-page view" +area = "B" +tier = "P0" +milestone = "M3" +status = "partial" +commands = [] +tools = [] +evidence = [] +notes = "works; no layout test yet" + +[[feature]] +id = "view.layout-two-up-scrolling" +title = "Two-page scrolling" +area = "B" +tier = "P1" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.cover-page" +title = "Show cover page in two-page view" +area = "B" +tier = "P1" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.page-gaps" +title = "Show or hide gaps between pages" +area = "B" +tier = "P2" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.auto-scroll" +title = "Automatic scrolling" +area = "B" +tier = "P2" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.zoom-in-out" +title = "Zoom in and out around the cursor (keys, wheel, pinch)" +area = "B" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/ui.rs::zoom_keeps_the_point_under_the_cursor_still"] +notes = "" + +[[feature]] +id = "view.zoom-to-percent" +title = "Zoom to a chosen percentage" +area = "B" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/ui.rs::selection_works_in_every_view_rotation"] +notes = "" + +[[feature]] +id = "view.zoom-actual-size" +title = "Actual size" +area = "B" +tier = "P0" +milestone = "M3" +status = "partial" +commands = [] +tools = [] +evidence = [] +notes = "menu item exists; no test" + +[[feature]] +id = "view.zoom-fit-page" +title = "Fit page" +area = "B" +tier = "P0" +milestone = "M3" +status = "partial" +commands = [] +tools = [] +evidence = [] +notes = "fit page / fit width toggle; no test" + +[[feature]] +id = "view.zoom-fit-width" +title = "Fit width" +area = "B" +tier = "P0" +milestone = "M3" +status = "partial" +commands = [] +tools = [] +evidence = [] +notes = "fit page / fit width toggle; no test" + +[[feature]] +id = "view.zoom-fit-height" +title = "Fit height" +area = "B" +tier = "P1" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.zoom-fit-visible" +title = "Fit visible content" +area = "B" +tier = "P1" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.marquee-zoom" +title = "Marquee zoom tool" +area = "B" +tier = "P1" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.dynamic-zoom" +title = "Dynamic zoom (drag to zoom)" +area = "B" +tier = "P2" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.loupe" +title = "Loupe magnifier tool" +area = "B" +tier = "P2" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.pan-and-zoom" +title = "Pan and zoom overview window" +area = "B" +tier = "P2" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.hand-tool" +title = "Hand tool for panning" +area = "B" +tier = "P0" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.rotate-view" +title = "Rotate the view without changing the file" +area = "B" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/ui.rs::selection_works_in_every_view_rotation"] +notes = "" + +[[feature]] +id = "view.read-mode" +title = "Read mode (hide tool chrome)" +area = "B" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = ["view.read_mode"] +tools = [] +evidence = ["crates/ui-egui/tests/control.rs::inspect_and_click_by_label_and_id", "crates/ui-egui/tests/commands.rs::every_registered_command_is_implemented"] +notes = "" + +[[feature]] +id = "view.full-screen" +title = "Full screen mode" +area = "B" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = ["view.full_screen"] +tools = [] +evidence = ["crates/ui-egui/tests/commands.rs::every_registered_command_is_implemented"] +notes = "" + +[[feature]] +id = "view.full-screen-presentation" +title = "Full-screen presentation options (auto-advance, loop, click to advance)" +area = "B" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.reflow" +title = "Reflow text to the window width" +area = "B" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.page-navigation" +title = "Page navigation (next, previous, first, last, go to page)" +area = "B" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/control.rs::state_and_view_options", "crates/ui-egui/tests/ui.rs::theme_and_view_options_apply"] +notes = "" + +[[feature]] +id = "view.goto-page-label" +title = "Go to a page by its label (logical page numbers)" +area = "B" +tier = "P1" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.view-history" +title = "Previous view / next view" +area = "B" +tier = "P1" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.follow-links" +title = "Follow links and destinations" +area = "B" +tier = "P0" +milestone = "M3" +status = "partial" +commands = [] +tools = [] +evidence = [] +notes = "link clicks work in the viewer; no test; actions beyond GoTo/URI pending" + +[[feature]] +id = "view.annotation-hover" +title = "Show comment text on hover" +area = "B" +tier = "P1" +milestone = "M3" +status = "partial" +commands = [] +tools = [] +evidence = [] +notes = "no test yet" + +[[feature]] +id = "view.document-tabs" +title = "Tabbed documents" +area = "B" +tier = "P1" +milestone = "M3" +status = "shipped" +commands = [] +tools = ["doc_list"] +evidence = ["crates/ui-egui/tests/editing.rs::combining_files_opens_a_new_unsaved_tab", "crates/ui-egui/tests/control.rs::state_and_view_options"] +notes = "" + +[[feature]] +id = "view.multiple-windows" +title = "Multiple windows; drag a tab out into a window" +area = "B" +tier = "P1" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.split-window" +title = "Split window (two panes on one document)" +area = "B" +tier = "P2" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.spreadsheet-split" +title = "Spreadsheet split (four synchronised panes)" +area = "B" +tier = "P2" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.thumbnails-panel" +title = "Page thumbnails panel" +area = "B" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/ui.rs::theme_and_view_options_apply"] +notes = "" + +[[feature]] +id = "view.thumbnail-context-ops" +title = "Page operations from the thumbnails panel (drag reorder, context menu)" +area = "B" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.bookmarks-panel" +title = "Bookmarks panel with navigation" +area = "B" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = [] +tools = ["doc_info"] +evidence = ["crates/ui-egui/tests/ui.rs::opening_a_pdf_shows_comments_and_bookmarks", "crates/ui-egui/tests/control.rs::state_and_view_options"] +notes = "" + +[[feature]] +id = "view.highlight-current-bookmark" +title = "Highlight the current bookmark" +area = "B" +tier = "P2" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.attachments-panel" +title = "Attachments panel: list, open and save embedded files" +area = "B" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = [] +tools = ["doc_info"] +evidence = ["crates/render/src/inspect.rs::attachments_of_both_kinds_are_listed_and_extracted"] +notes = "" + +[[feature]] +id = "view.layers-panel" +title = "Layers panel: toggle optional content visibility" +area = "B" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = [] +tools = ["doc_info"] +evidence = ["crates/render/src/raster.rs::layer_override_hides_content", "crates/engine/src/tests.rs::layer_choices_survive_edits"] +notes = "" + +[[feature]] +id = "view.layers-reset" +title = "Reset layers to initial visibility; list for all or current pages" +area = "B" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.destinations-panel" +title = "Named destinations panel" +area = "B" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.articles-panel" +title = "Articles panel and article reading" +area = "B" +tier = "P3" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.model-tree" +title = "3D model tree panel" +area = "B" +tier = "P3" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.page-labels-display" +title = "Show page labels (i, ii, 1, 2...)" +area = "B" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = [] +tools = ["doc_info"] +evidence = ["crates/render/src/inspect.rs::roman_and_alpha_labels", "crates/automation/tests/automation.rs::open_inspect_render_and_find"] +notes = "" + +[[feature]] +id = "view.find" +title = "Find text across the document" +area = "B" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = ["edit.find"] +tools = ["text_find"] +evidence = ["crates/ui-egui/tests/ui.rs::find_counts_matches_across_pages", "crates/ui-egui/tests/ui.rs::find_reports_no_matches", "crates/automation/tests/automation.rs::open_inspect_render_and_find"] +notes = "" + +[[feature]] +id = "view.find-next-previous" +title = "Find next / previous match" +area = "B" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = ["edit.find"] +tools = [] +evidence = ["crates/ui-egui/tests/ui.rs::find_counts_matches_across_pages"] +notes = "" + +[[feature]] +id = "view.find-options" +title = "Find options: whole words, case-sensitive" +area = "B" +tier = "P1" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.find-in-comments-bookmarks" +title = "Find in comments and bookmarks" +area = "B" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.find-results-panel" +title = "Find results list" +area = "B" +tier = "P1" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.advanced-search" +title = "Advanced search window" +area = "B" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.search-folder" +title = "Search all PDFs in a folder" +area = "B" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.search-boolean" +title = "Boolean, proximity and stemming search" +area = "B" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.search-index" +title = "Build and search a full-text index across files" +area = "B" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.embedded-index" +title = "Embedded search index" +area = "B" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.text-select" +title = "Select text in reading order" +area = "B" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/ui.rs::drag_selects_text_and_copy_returns_it", "crates/ui-egui/tests/ui.rs::selection_works_in_every_view_rotation"] +notes = "" + +[[feature]] +id = "view.copy-text" +title = "Copy selected text" +area = "B" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/ui.rs::drag_selects_text_and_copy_returns_it"] +notes = "" + +[[feature]] +id = "view.select-all" +title = "Select all text on a page" +area = "B" +tier = "P1" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.copy-with-layout" +title = "Copy text keeping layout" +area = "B" +tier = "P2" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.snapshot" +title = "Snapshot tool (copy a region as an image)" +area = "B" +tier = "P1" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.rulers" +title = "Rulers" +area = "B" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.grid" +title = "Grid and snap to grid" +area = "B" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.guides" +title = "Guides" +area = "B" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.line-weights" +title = "Show line weights" +area = "B" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.cursor-coordinates" +title = "Cursor coordinates" +area = "B" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.theme-light-dark" +title = "Light and dark themes" +area = "B" +tier = "P1" +milestone = "M3" +status = "shipped" +commands = ["view.theme"] +tools = [] +evidence = ["crates/ui-egui/tests/ui.rs::theme_and_view_options_apply"] +notes = "" + +[[feature]] +id = "view.theme-system" +title = "Follow the system theme" +area = "B" +tier = "P1" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.home-view" +title = "Home view with recommended tools and recent files" +area = "B" +tier = "P1" +milestone = "M3" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/ui.rs::home_shows_welcome_and_tools"] +notes = "" + +[[feature]] +id = "view.all-tools" +title = "All tools catalogue with milestone badges" +area = "B" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/ui.rs::every_catalog_tool_is_listed_after_view_more", "crates/engine/src/commands.rs::every_ready_catalogue_item_is_a_registered_command"] +notes = "" + +[[feature]] +id = "view.command-palette" +title = "Command palette (⌘K) over every command" +area = "B" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = ["view.palette"] +tools = [] +evidence = ["crates/ui-egui/tests/commands.rs::the_palette_lists_commands_with_shortcuts_and_runs_them"] +notes = "" + +[[feature]] +id = "view.keyboard-shortcuts" +title = "Platform keyboard shortcuts for registered commands" +area = "B" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/commands.rs::shortcuts_run_registered_commands", "crates/engine/src/commands.rs::shortcut_labels_follow_platform_conventions"] +notes = "" + +[[feature]] +id = "view.shortcuts-dialog" +title = "Keyboard shortcuts reference dialog" +area = "B" +tier = "P1" +milestone = "M4" +status = "shipped" +commands = ["help.shortcuts"] +tools = [] +evidence = ["crates/ui-egui/tests/commands.rs::the_shortcuts_dialog_lists_the_real_bindings"] +notes = "" + +[[feature]] +id = "view.menus-from-registry" +title = "Menus generated from the command registry" +area = "B" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/commands.rs::the_pages_menu_comes_from_the_registry"] +notes = "" + +[[feature]] +id = "view.about-dialog" +title = "About dialog" +area = "B" +tier = "P2" +milestone = "M3" +status = "shipped" +commands = ["help.about"] +tools = [] +evidence = ["crates/ui-egui/tests/commands.rs::every_registered_command_is_implemented"] +notes = "" + +[[feature]] +id = "view.single-key-accelerators" +title = "Single-key tool accelerators" +area = "B" +tier = "P2" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.customize-quick-tools" +title = "Customise the quick-action toolbar and pinned tools" +area = "B" +tier = "P2" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.restriction-notice" +title = "Message bar for restricted documents" +area = "B" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/editing.rs::restricted_documents_show_a_notice_and_block_page_changes"] +notes = "" + +[[feature]] +id = "view.pdfa-view-mode" +title = "Read-only view for PDF/A files with an Enable editing bar" +area = "B" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.show-page-boxes" +title = "Show art, trim and bleed boxes" +area = "B" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "view.web-app" +title = "Run in the browser (WebAssembly)" +area = "B" +tier = "P0" +milestone = "M3" +status = "partial" +commands = [] +tools = [] +evidence = [] +notes = "web build works; IndexedDB/OPFS recent list and download flow are M3.10" + +[[feature]] +id = "view.large-document-performance" +title = "Smooth scrolling on 500-page documents" +area = "B" +tier = "P0" +milestone = "M3" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "60 fps acceptance test pending" + +# ================================================================================================== +# C. Content editing +# ================================================================================================== + +[[feature]] +id = "edit.text-edit" +title = "Edit existing text in place" +area = "C" +tier = "P0" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.text-reflow" +title = "Reflow edited text inside its paragraph box" +area = "C" +tier = "P0" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.paragraph-detection" +title = "Detect paragraphs and text blocks for editing" +area = "C" +tier = "P0" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.font-reuse-substitution" +title = "Reuse embedded fonts or substitute with a warning" +area = "C" +tier = "P0" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.text-format-font" +title = "Change font, size and colour" +area = "C" +tier = "P0" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.text-format-style" +title = "Bold, italic, underline" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.text-super-subscript" +title = "Superscript and subscript" +area = "C" +tier = "P2" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.paragraph-alignment" +title = "Paragraph alignment (left, centre, right, justify)" +area = "C" +tier = "P0" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.line-paragraph-spacing" +title = "Line and paragraph spacing" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.char-spacing-scale" +title = "Character spacing and horizontal scaling" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.lists" +title = "Bulleted and numbered lists" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.add-text" +title = "Add a new text box as page content" +area = "C" +tier = "P0" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.find-replace" +title = "Find and replace in edit mode" +area = "C" +tier = "P2" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.rtl-cjk-editing" +title = "Edit right-to-left and CJK text" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.rotated-text-editing" +title = "Edit rotated text" +area = "C" +tier = "P2" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.keep-tags-on-edit" +title = "Keep structure tags when editing" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.image-add" +title = "Add an image" +area = "C" +tier = "P0" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.image-replace" +title = "Replace an image (keep position and size)" +area = "C" +tier = "P0" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.image-crop" +title = "Crop an image" +area = "C" +tier = "P0" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.image-rotate-flip" +title = "Rotate and flip images" +area = "C" +tier = "P0" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.image-move-resize" +title = "Move and resize images" +area = "C" +tier = "P0" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.image-extract" +title = "Save an image to a file" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.image-external-editor" +title = "Edit an image in an external application" +area = "C" +tier = "P2" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.arrange-objects" +title = "Arrange objects (bring forward, send backward)" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.align-distribute-objects" +title = "Align and distribute objects" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.vector-edit" +title = "Select, move, scale, recolour and delete vector paths" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.edit-object-tool" +title = "Edit object tool for any page object" +area = "C" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.object-properties" +title = "Object properties (colour space, font, convert colour)" +area = "C" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.link-create" +title = "Create web and page links" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.link-properties" +title = "Link appearance and properties" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.links-from-urls" +title = "Create links from URLs in the text" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.remove-all-links" +title = "Remove all links" +area = "C" +tier = "P2" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.action-goto" +title = "Go-to-page-view action (with named destinations)" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.action-uri" +title = "Open-a-web-link action" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.action-launch" +title = "Open-a-file action" +area = "C" +tier = "P2" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.action-named" +title = "Execute-a-menu-item action (safe list)" +area = "C" +tier = "P2" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.action-set-ocg-state" +title = "Set-layer-visibility action" +area = "C" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.action-show-hide-field" +title = "Show/hide-a-field action" +area = "C" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.action-javascript" +title = "Run-a-JavaScript action" +area = "C" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.action-thread" +title = "Read-an-article action" +area = "C" +tier = "P3" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.action-goto-embedded" +title = "Go to a target in an embedded file (GoToE)" +area = "C" +tier = "P3" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.action-media" +title = "Play sound / media / rich-media actions" +area = "C" +tier = "P3" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.header-footer-add" +title = "Add header and footer" +area = "C" +tier = "P0" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.header-footer-update-remove" +title = "Update or remove header and footer" +area = "C" +tier = "P0" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.header-footer-tokens" +title = "Page number and date tokens in header and footer" +area = "C" +tier = "P0" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.header-footer-ranges" +title = "Header and footer page ranges (all, range, odd, even)" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.header-footer-shrink" +title = "Shrink document to avoid overwriting content" +area = "C" +tier = "P2" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.watermark-add-text" +title = "Add a text watermark" +area = "C" +tier = "P0" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.watermark-add-file" +title = "Add a watermark from an image or PDF page" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.watermark-update-remove" +title = "Update or remove watermarks" +area = "C" +tier = "P0" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.watermark-appearance" +title = "Watermark rotation, opacity, scale, position, print/screen visibility" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.background-add" +title = "Add a page background (colour or file)" +area = "C" +tier = "P0" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.background-update-remove" +title = "Update or remove backgrounds" +area = "C" +tier = "P0" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.decoration-profiles" +title = "Saved header/footer and watermark settings" +area = "C" +tier = "P2" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.bates-numbering" +title = "Bates numbering" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.bates-multi-file" +title = "Bates numbering across multiple files" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.bates-remove" +title = "Remove Bates numbering" +area = "C" +tier = "P1" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.spell-check" +title = "Spell check in comments, form fields and edited text" +area = "C" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.spell-dictionaries" +title = "Spelling dictionaries per language and a user dictionary" +area = "C" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.format-panel" +title = "Format panel for edited text" +area = "C" +tier = "P0" +milestone = "M7" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "edit.adobe-express-edits" +title = "Adobe Express image edits (remove background, generative fill)" +area = "C" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "cloud-only; images open in an external editor instead" + +# ================================================================================================== +# D. Organize pages and documents +# ================================================================================================== + +[[feature]] +id = "organize.organize-grid" +title = "Organize pages thumbnail grid" +area = "D" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = ["page.organize"] +tools = [] +evidence = ["crates/ui-egui/tests/editing.rs::shift_click_selects_a_range_and_moves_it"] +notes = "" + +[[feature]] +id = "organize.multi-select-pages" +title = "Select pages with click, ⌘-click and ⇧-click" +area = "D" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = ["page.organize"] +tools = [] +evidence = ["crates/ui-egui/tests/editing.rs::command_click_toggles_and_rotation_applies_to_selection", "crates/ui-egui/tests/editing.rs::shift_click_selects_a_range_and_moves_it"] +notes = "" + +[[feature]] +id = "organize.rotate-pages" +title = "Rotate pages" +area = "D" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = ["page.rotate", "page.rotate_ccw"] +tools = ["page_rotate"] +evidence = ["crates/organize/src/tests.rs::rotate_uses_inherited_rotation_and_round_trips", "crates/ui-egui/tests/editing.rs::command_click_toggles_and_rotation_applies_to_selection"] +notes = "" + +[[feature]] +id = "organize.rotate-filters" +title = "Rotate by range with even/odd and orientation filters" +area = "D" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.delete-pages" +title = "Delete pages" +area = "D" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = ["page.delete"] +tools = ["page_delete"] +evidence = ["crates/organize/src/tests.rs::delete_keeps_inherited_attributes_on_survivors", "crates/organize/src/tests.rs::cannot_delete_every_page_or_missing_pages", "crates/ui-egui/tests/editing.rs::delete_is_refused_when_it_would_remove_every_page"] +notes = "" + +[[feature]] +id = "organize.move-pages" +title = "Move pages earlier or later" +area = "D" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = [] +tools = ["page_move"] +evidence = ["crates/organize/src/tests.rs::move_pages_reorders", "crates/ui-egui/tests/editing.rs::shift_click_selects_a_range_and_moves_it"] +notes = "" + +[[feature]] +id = "organize.drag-reorder" +title = "Reorder pages by drag and drop" +area = "D" +tier = "P0" +milestone = "M4" +status = "partial" +commands = [] +tools = [] +evidence = [] +notes = "move-earlier/later buttons exist; drag in the grid is M4.7" + +[[feature]] +id = "organize.insert-blank" +title = "Insert blank pages" +area = "D" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = ["page.insert_blank"] +tools = ["page_insert_blank"] +evidence = ["crates/organize/src/tests.rs::inserts_blank_page", "crates/ui-egui/tests/editing.rs::insert_blank_page_after_selection", "crates/engine/src/tests.rs::insert_blank_between_pages_keeps_both_neighbours"] +notes = "" + +[[feature]] +id = "organize.insert-from-file" +title = "Insert pages from another file" +area = "D" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = ["page.insert"] +tools = ["page_insert_file"] +evidence = ["crates/organize/src/tests.rs::inserting_pages_from_another_file_is_an_incremental_edit", "crates/ui-egui/tests/editing.rs::inserting_a_file_goes_after_the_selection_and_undoes"] +notes = "" + +[[feature]] +id = "organize.insert-from-clipboard" +title = "Insert pages from the clipboard" +area = "D" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.insert-from-scanner" +title = "Insert pages from a scanner" +area = "D" +tier = "P2" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.insert-from-web" +title = "Insert pages from a web page" +area = "D" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.insert-by-drop" +title = "Insert by dropping files onto the page grid" +area = "D" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.extract-pages" +title = "Extract pages to a new document" +area = "D" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = ["page.extract"] +tools = ["page_extract"] +evidence = ["crates/ui-egui/tests/editing.rs::extract_button_copies_selected_pages_to_a_new_tab", "crates/automation/tests/automation.rs::combine_extract_and_split"] +notes = "" + +[[feature]] +id = "organize.extract-separate-files" +title = "Extract pages as separate files" +area = "D" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.extract-and-delete" +title = "Delete pages after extracting" +area = "D" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.replace-pages" +title = "Replace pages with pages from another file" +area = "D" +tier = "P0" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.duplicate-pages" +title = "Duplicate pages" +area = "D" +tier = "P0" +milestone = "M4" +status = "partial" +commands = [] +tools = [] +evidence = ["crates/organize/src/tests.rs::duplicate_pages_become_independent_copies"] +notes = "engine only; no command or UI yet" + +[[feature]] +id = "organize.reverse-pages" +title = "Reverse page order" +area = "D" +tier = "P2" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.cut-copy-paste-pages" +title = "Cut, copy and paste pages" +area = "D" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.drag-pages-between-documents" +title = "Drag pages between documents" +area = "D" +tier = "P2" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.page-range-selection" +title = "Select by range, odd/even, landscape/portrait" +area = "D" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.thumbnail-size" +title = "Thumbnail size slider" +area = "D" +tier = "P2" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.split-every-n" +title = "Split every N pages" +area = "D" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = ["page.split"] +tools = ["doc_split"] +evidence = ["crates/ui-egui/tests/editing.rs::split_dialog_writes_one_file_per_part", "crates/organize/src/tests.rs::split_ranges_cover_every_page_once", "crates/organize/src/tests.rs::split_produces_standalone_documents_with_metadata"] +notes = "" + +[[feature]] +id = "organize.split-before-pages" +title = "Split before chosen pages" +area = "D" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = ["page.split"] +tools = ["doc_split"] +evidence = ["crates/ui-egui/tests/editing.rs::split_before_selected_pages"] +notes = "" + +[[feature]] +id = "organize.split-by-size" +title = "Split by file size" +area = "D" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.split-by-bookmarks" +title = "Split by top-level bookmarks" +area = "D" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.split-naming" +title = "Name split parts after bookmarks; never overwrite" +area = "D" +tier = "P2" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.split-multiple-files" +title = "Split multiple files in one run" +area = "D" +tier = "P2" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.combine-files" +title = "Combine files into one PDF" +area = "D" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = ["page.combine"] +tools = ["doc_combine"] +evidence = ["crates/organize/src/tests.rs::combine_concatenates_with_a_bookmark_per_file", "crates/ui-egui/tests/editing.rs::combining_files_opens_a_new_unsaved_tab", "crates/automation/tests/automation.rs::combine_extract_and_split"] +notes = "" + +[[feature]] +id = "organize.combine-bookmarks" +title = "Combine adds a bookmark per file with its own bookmarks nested" +area = "D" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = ["page.combine"] +tools = ["doc_combine"] +evidence = ["crates/organize/src/tests.rs::combine_nests_source_bookmarks_and_keeps_attachments"] +notes = "" + +[[feature]] +id = "organize.combine-choose-pages" +title = "Choose which pages of each file to combine" +area = "D" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.combine-sort" +title = "Sort and reorder files before combining" +area = "D" +tier = "P2" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.combine-options" +title = "Combine options (file size, convert to PDF/A, include open files)" +area = "D" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.resource-dedupe" +title = "Store identical fonts and images once when combining or inserting" +area = "D" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = ["page.combine", "page.insert"] +tools = ["doc_combine"] +evidence = ["crates/organize/src/tests.rs::combine_and_insert_share_identical_resources", "crates/organize/src/dedupe.rs::merges_bottom_up_and_keeps_identity_objects"] +notes = "" + +[[feature]] +id = "organize.preserve-links-dests" +title = "Rewire links and named destinations to copied pages" +area = "D" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/organize/src/tests.rs::links_are_rewired_to_copied_pages_or_dropped", "crates/organize/src/tests.rs::named_destinations_are_resolved_to_the_copies"] +notes = "" + +[[feature]] +id = "organize.preserve-fields" +title = "Keep form fields interactive when copying pages" +area = "D" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/organize/src/tests.rs::form_fields_come_along_and_are_registered"] +notes = "" + +[[feature]] +id = "organize.preserve-layers" +title = "Keep layers and their default state when copying pages" +area = "D" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/organize/src/tests.rs::layers_are_registered_with_their_default_state"] +notes = "" + +[[feature]] +id = "organize.preserve-inherited" +title = "Copy inherited page attributes with pages" +area = "D" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/organize/src/tests.rs::inherited_attributes_travel_with_copied_pages"] +notes = "" + +[[feature]] +id = "organize.preserve-attachments" +title = "Keep attachments when combining" +area = "D" +tier = "P1" +milestone = "M4" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/organize/src/tests.rs::combine_nests_source_bookmarks_and_keeps_attachments"] +notes = "" + +[[feature]] +id = "organize.field-name-conflicts" +title = "Rename or merge colliding field names when combining" +area = "D" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.struct-tree-merge" +title = "Merge structure trees when combining" +area = "D" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.page-labels-edit" +title = "Edit page labels (style, prefix, start)" +area = "D" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.page-boxes" +title = "Set page boxes (crop, trim, bleed, art)" +area = "D" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.crop-tool" +title = "Crop pages by dragging a rectangle" +area = "D" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.remove-white-margins" +title = "Remove white margins" +area = "D" +tier = "P2" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.change-page-size" +title = "Change page size" +area = "D" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.page-transitions" +title = "Page transitions" +area = "D" +tier = "P2" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.page-tab-order" +title = "Page tab order property" +area = "D" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.page-actions" +title = "Page open/close actions" +area = "D" +tier = "P2" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.page-templates" +title = "Page templates" +area = "D" +tier = "P3" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.bookmark-add" +title = "Add a bookmark (⌘B)" +area = "D" +tier = "P0" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.bookmark-rename" +title = "Rename bookmarks" +area = "D" +tier = "P0" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.bookmark-delete" +title = "Delete bookmarks" +area = "D" +tier = "P0" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.bookmark-move" +title = "Reorder and nest bookmarks" +area = "D" +tier = "P0" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.bookmark-set-destination" +title = "Set a bookmark's destination" +area = "D" +tier = "P0" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.bookmark-appearance" +title = "Bookmark style and colour" +area = "D" +tier = "P2" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.bookmark-actions" +title = "Bookmark actions" +area = "D" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.bookmark-expand-collapse" +title = "Expand and collapse all bookmarks" +area = "D" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.bookmarks-from-structure" +title = "Generate bookmarks from headings or tags" +area = "D" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.attachments-add" +title = "Add file attachments" +area = "D" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.attachments-delete" +title = "Delete attachments" +area = "D" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.attachments-description" +title = "Edit attachment descriptions" +area = "D" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.associated-files" +title = "Associated files with relationships (PDF/A-3, PDF 2.0)" +area = "D" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.portfolio-view" +title = "View PDF Portfolios as a file list" +area = "D" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.portfolio-extract" +title = "Extract files from a portfolio" +area = "D" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.portfolio-create" +title = "Create PDF Portfolios" +area = "D" +tier = "P3" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.document-properties" +title = "Document properties dialog (description, fonts, advanced)" +area = "D" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = ["file.properties"] +tools = ["doc_info"] +evidence = ["crates/ui-egui/tests/editing.rs::document_properties_edit_is_one_undoable_step", "crates/ui-egui/tests/editing.rs::cancelling_properties_discards_the_draft"] +notes = "" + +[[feature]] +id = "organize.edit-metadata" +title = "Edit title, author, subject and keywords" +area = "D" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = ["file.properties"] +tools = ["doc_set_info"] +evidence = ["crates/organize/src/tests.rs::document_info_edits_round_trip_with_unicode", "crates/ui-egui/tests/editing.rs::document_properties_edit_is_one_undoable_step"] +notes = "" + +[[feature]] +id = "organize.fonts-list" +title = "List fonts with type and embedding status" +area = "D" +tier = "P1" +milestone = "M3" +status = "partial" +commands = ["file.properties"] +tools = ["doc_info"] +evidence = [] +notes = "Fonts tab works; no test yet" + +[[feature]] +id = "organize.initial-view" +title = "Edit initial view (layout, magnification, panels, window options)" +area = "D" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.custom-properties" +title = "Custom document properties" +area = "D" +tier = "P2" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.xmp-editor" +title = "Additional metadata (XMP) editor" +area = "D" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.document-language" +title = "Set document language and binding" +area = "D" +tier = "P1" +milestone = "M4" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.print-presets" +title = "Print dialog presets in document properties" +area = "D" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "organize.base-url" +title = "Base URL for relative links" +area = "D" +tier = "P3" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +# ================================================================================================== +# E. Comments and review +# ================================================================================================== + +[[feature]] +id = "comment.list-view" +title = "Comments panel with threaded replies (read-only)" +area = "E" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = ["comment.list"] +tools = ["doc_info"] +evidence = ["crates/ui-egui/tests/ui.rs::opening_a_pdf_shows_comments_and_bookmarks"] +notes = "read-only; creating and managing comments is M5" + +[[feature]] +id = "comment.appearance-generation" +title = "Generate correct appearances for every markup type" +area = "E" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.sticky-note" +title = "Sticky note comments" +area = "E" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.highlight" +title = "Highlight text" +area = "E" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.underline" +title = "Underline text" +area = "E" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.strikeout" +title = "Strikethrough text" +area = "E" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.squiggly" +title = "Squiggly underline" +area = "E" +tier = "P2" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.replace-text" +title = "Replace-text proposal (strikeout + caret)" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.insert-text" +title = "Insert-text proposal (caret)" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.typewriter" +title = "Add text comment (typewriter)" +area = "E" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.text-box" +title = "Text box comment with rich text" +area = "E" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.callout" +title = "Callout comment" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.pencil" +title = "Freehand drawing (ink)" +area = "E" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.eraser" +title = "Eraser for ink strokes" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.line" +title = "Line" +area = "E" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.arrow" +title = "Arrow" +area = "E" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.rectangle" +title = "Rectangle" +area = "E" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.oval" +title = "Oval" +area = "E" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.polygon" +title = "Polygon" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.polyline" +title = "Connected lines (polyline)" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.cloud" +title = "Cloud shape" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.area-highlight" +title = "Area highlight" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.attach-file-comment" +title = "Attach a file as a comment" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.record-audio" +title = "Sound comments" +area = "E" +tier = "P3" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.image-comment" +title = "Add an image as a comment" +area = "E" +tier = "P2" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.line-endings" +title = "All ten line-ending styles and dashed/cloudy borders" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.properties-dialog" +title = "Comment properties (appearance, author, subject)" +area = "E" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.properties-bar" +title = "Comment properties bar" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.lock-comment" +title = "Lock comments" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.default-properties" +title = "Make current properties the tool default" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.author-identity" +title = "Author name from identity preferences" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.reply" +title = "Reply to comments" +area = "E" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.set-status" +title = "Set review status (accepted, rejected, cancelled, completed)" +area = "E" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.checkmark" +title = "Mark comments with a checkmark" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.delete-comment" +title = "Delete comments" +area = "E" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.edit-comment-text" +title = "Edit comment text" +area = "E" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.copy-comment-text" +title = "Copy comment text" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.filter-comments" +title = "Filter comments (type, author, status, colour, checkmark)" +area = "E" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.sort-comments" +title = "Sort comments (page, author, date, type, colour)" +area = "E" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.search-comments" +title = "Search comments" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.show-hide-comments" +title = "Show or hide all comments and pop-ups" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.popups" +title = "Open and reposition comment pop-ups" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.connector-lines" +title = "Connector lines between markups and pop-ups" +area = "E" +tier = "P2" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.review-history" +title = "Review history of state changes" +area = "E" +tier = "P2" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.stamps-standard" +title = "Standard business stamps" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.stamps-sign-here" +title = "Sign-here stamps" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.stamps-dynamic" +title = "Dynamic stamps (name and date)" +area = "E" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.stamps-custom" +title = "Custom stamps from images or PDF pages" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.stamps-manage" +title = "Manage stamp categories and imports" +area = "E" +tier = "P2" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.stamps-rotate" +title = "Rotate stamps with a handle" +area = "E" +tier = "P2" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.import-fdf-xfdf" +title = "Import comments from FDF/XFDF" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.export-fdf-xfdf" +title = "Export all comments to FDF/XFDF" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.export-selected" +title = "Export selected comments" +area = "E" +tier = "P2" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.import-from-pdf" +title = "Import comments from another PDF" +area = "E" +tier = "P2" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.summarize" +title = "Summarize comments into a new PDF" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.flatten" +title = "Flatten comments into page content" +area = "E" +tier = "P1" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.migrate" +title = "Migrate comments to a revised document" +area = "E" +tier = "P3" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.measure-distance" +title = "Distance measuring tool" +area = "E" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.measure-perimeter" +title = "Perimeter measuring tool" +area = "E" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.measure-area" +title = "Area measuring tool" +area = "E" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.measure-scale" +title = "Scale ratio and calibration per viewport" +area = "E" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.measure-snap" +title = "Snap to paths, endpoints, midpoints and intersections" +area = "E" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.measure-info-panel" +title = "Measurement info panel" +area = "E" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.measure-export" +title = "Export measurements as CSV" +area = "E" +tier = "P3" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.geospatial" +title = "Geospatial location and measuring" +area = "E" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "comment.shared-review" +title = "Send for comments / shared review" +area = "E" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "Adobe cloud; exchange XFDF files or a pluggable review server instead" + +[[feature]] +id = "comment.mentions-live-review" +title = "@mentions, presence and live co-review" +area = "E" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "Adobe cloud; no local equivalent" + +[[feature]] +id = "comment.review-tracker" +title = "Review tracker" +area = "E" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "Adobe cloud / legacy" + +[[feature]] +id = "comment.enable-reader-commenting" +title = "Enable commenting and measuring in Reader (usage rights)" +area = "E" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "needs Adobe's private key; modern readers allow commenting anyway" + +# ================================================================================================== +# F. Forms and JavaScript +# ================================================================================================== + +[[feature]] +id = "form.field-list" +title = "Form fields panel listing every field and value" +area = "F" +tier = "P0" +milestone = "M3" +status = "partial" +commands = ["form.fields"] +tools = ["doc_info"] +evidence = [] +notes = "read-only list works; no panel test yet; editing in M6" + +[[feature]] +id = "form.highlight-fields" +title = "Highlight existing fields" +area = "F" +tier = "P0" +milestone = "M3" +status = "partial" +commands = [] +tools = [] +evidence = [] +notes = "toggle exists; no test" + +[[feature]] +id = "form.field-appearance-display" +title = "Display checkbox, radio, list and signature widgets as designed" +area = "F" +tier = "P0" +milestone = "M2" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/render/src/raster.rs::widget_appearance_states"] +notes = "" + +[[feature]] +id = "form.fill-text" +title = "Fill text fields" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.fill-checkbox-radio" +title = "Toggle checkboxes and radio buttons (radios in unison)" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.fill-choice" +title = "Choose from combo boxes and list boxes" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.push-buttons" +title = "Push buttons run their actions" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.field-navigation" +title = "Tab between fields" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.appearance-regeneration" +title = "Regenerate field appearances (auto-size, comb, multiline, password)" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.rich-text-fields" +title = "Rich-text field values" +area = "F" +tier = "P2" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.af-number" +title = "Number and currency formatting (AFNumber)" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.af-percent" +title = "Percentage formatting" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.af-date-time" +title = "Date and time formatting and parsing" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.af-special" +title = "Special formats (zip, phone, SSN, arbitrary mask)" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.af-range-validate" +title = "Range validation" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.af-simple-calculate" +title = "Sum, product, average, min, max calculations" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.simplified-notation" +title = "Simplified field notation calculations" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.custom-scripts" +title = "Custom format, keystroke, validate and calculate scripts" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.calculation-order" +title = "Calculation order" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.event-order" +title = "Keystroke, validate, calculate, format event order" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.date-picker" +title = "Date picker for date fields" +area = "F" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.auto-complete" +title = "Auto-complete from previous entries" +area = "F" +tier = "P2" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.required-fields" +title = "Required-field highlighting" +area = "F" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.reset-form" +title = "Clear or reset a form" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.submit-form" +title = "Submit form to a URL or email (with a prompt)" +area = "F" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.import-data" +title = "Import form data (FDF, XFDF)" +area = "F" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.export-data" +title = "Export form data (FDF, XFDF)" +area = "F" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.data-xml-csv-txt" +title = "Form data as XML, CSV or tab-delimited text" +area = "F" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.merge-to-spreadsheet" +title = "Merge form data files into a spreadsheet" +area = "F" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.flatten-fields" +title = "Flatten form fields" +area = "F" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.prepare-form-mode" +title = "Prepare a form mode" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.add-text-field" +title = "Add text fields" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.add-checkbox" +title = "Add checkboxes" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.add-radio" +title = "Add radio buttons" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.add-dropdown" +title = "Add drop-down lists" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.add-listbox" +title = "Add list boxes" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.add-button" +title = "Add push buttons" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.add-image-field" +title = "Add image fields" +area = "F" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.add-date-field" +title = "Add date fields" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.add-signature-field" +title = "Add digital signature fields" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.add-barcode-field" +title = "Barcode fields (PDF417, QR, Data Matrix)" +area = "F" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.props-general" +title = "Field properties: general (name, tooltip, visibility, read-only, required)" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.props-appearance" +title = "Field properties: appearance (border, fill, font)" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.props-position" +title = "Field properties: position and size" +area = "F" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.props-options" +title = "Field properties: options per field type" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.props-actions" +title = "Field properties: actions per trigger" +area = "F" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.props-format" +title = "Field properties: format" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.props-validate" +title = "Field properties: validate" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.props-calculate" +title = "Field properties: calculate" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.props-selection-change" +title = "Field properties: selection change" +area = "F" +tier = "P2" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.props-signed" +title = "Field properties: signed (lock fields when signed)" +area = "F" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.align-distribute-fields" +title = "Align, centre, distribute and match field sizes" +area = "F" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.duplicate-fields" +title = "Duplicate fields across pages" +area = "F" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.multiple-copies" +title = "Create multiple copies of a field in a grid" +area = "F" +tier = "P2" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.rename-lock-fields" +title = "Rename and lock fields" +area = "F" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.tab-order" +title = "Tab order (row, column, structure, manual)" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.show-tab-numbers" +title = "Show tab numbers" +area = "F" +tier = "P2" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.preview-form" +title = "Preview mode for testing a form" +area = "F" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.auto-detect-fields" +title = "Automatic form field detection" +area = "F" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.auto-name-fields" +title = "Name new fields from nearby labels" +area = "F" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.document-javascripts" +title = "Document-level JavaScripts" +area = "F" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.document-actions" +title = "Document actions (will close, will save, did save, will print, did print)" +area = "F" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.js-host" +title = "JavaScript engine with the Acrobat object model subset" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.js-console" +title = "JavaScript console" +area = "F" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.js-debugger" +title = "JavaScript debugger" +area = "F" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.edit-all-javascripts" +title = "Edit all JavaScripts in one editor" +area = "F" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.js-security" +title = "JavaScript enable switch and sandboxing" +area = "F" +tier = "P0" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.folder-level-js" +title = "Folder-level JavaScript" +area = "F" +tier = "P3" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.xfa-static" +title = "View and fill static XFA forms" +area = "F" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.xfa-dynamic" +title = "View and fill dynamic XFA forms" +area = "F" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.xfa-formcalc" +title = "FormCalc scripting" +area = "F" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.xfa-data" +title = "XFA XML data import and export" +area = "F" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.xfa-flatten" +title = "Flatten XFA forms" +area = "F" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.fill-sign-text" +title = "Fill & Sign: add text on flat forms" +area = "F" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.fill-sign-marks" +title = "Fill & Sign: check, cross, dot, line marks" +area = "F" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.fill-sign-date" +title = "Fill & Sign: today's date" +area = "F" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.fill-sign-signature" +title = "Fill & Sign: typed, drawn or image signature and initials" +area = "F" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.fill-sign-flatten" +title = "Fill & Sign: flatten on save" +area = "F" +tier = "P0" +milestone = "M5" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "form.autofill-profile" +title = "Autofill collection synced to an Adobe account" +area = "F" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "Adobe cloud; a local profile may come later" + +[[feature]] +id = "form.distribute-track" +title = "Distribute and track forms, collect responses" +area = "F" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "Adobe cloud; only submit-to-URL/email actions are supported" + +[[feature]] +id = "form.web-forms" +title = "Acrobat Sign web forms" +area = "F" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "Adobe cloud" + +# ================================================================================================== +# G. Protect, redact, sanitize +# ================================================================================================== + +[[feature]] +id = "protect.security-properties" +title = "Security properties (method, which password opened it, each permission)" +area = "G" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = ["protect.properties"] +tools = ["doc_info"] +evidence = ["crates/ui-egui/tests/editing.rs::password_prompt_opens_and_security_tab_reports_the_details"] +notes = "" + +[[feature]] +id = "protect.password-to-open" +title = "Protect with a password to open" +area = "G" +tier = "P0" +milestone = "M8" +status = "partial" +commands = [] +tools = [] +evidence = ["crates/cos/tests/encryption.rs::changing_and_removing_passwords"] +notes = "engine can set passwords; Protect UI and command are M8.1" + +[[feature]] +id = "protect.permissions-password" +title = "Restrict editing and printing with a permissions password" +area = "G" +tier = "P0" +milestone = "M8" +status = "partial" +commands = [] +tools = [] +evidence = ["crates/crypt/src/tests.rs::created_encryption_authenticates_owner_and_user"] +notes = "engine only; no UI" + +[[feature]] +id = "protect.permissions-matrix" +title = "Choose printing, changes, copying and accessibility permissions" +area = "G" +tier = "P0" +milestone = "M8" +status = "partial" +commands = [] +tools = [] +evidence = ["crates/crypt/src/tests.rs::permission_bits_follow_table_22"] +notes = "engine only; no UI" + +[[feature]] +id = "protect.encryption-level" +title = "Choose encryption compatibility (RC4 40/128, AES-128, AES-256)" +area = "G" +tier = "P1" +milestone = "M8" +status = "partial" +commands = [] +tools = [] +evidence = ["crates/cos/tests/encryption.rs::every_algorithm_round_trips_and_hides_the_content"] +notes = "engine only; AES-256 default in the planned UI" + +[[feature]] +id = "protect.encrypt-except-metadata" +title = "Encrypt everything except metadata" +area = "G" +tier = "P1" +milestone = "M8" +status = "partial" +commands = [] +tools = [] +evidence = ["crates/cos/tests/encryption.rs::unencrypted_metadata_is_left_readable"] +notes = "engine only" + +[[feature]] +id = "protect.encrypt-attachments-only" +title = "Encrypt only file attachments" +area = "G" +tier = "P2" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.password-strength" +title = "Password strength meter" +area = "G" +tier = "P2" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.remove-security" +title = "Remove security" +area = "G" +tier = "P0" +milestone = "M8" +status = "partial" +commands = [] +tools = [] +evidence = ["crates/cos/tests/encryption.rs::changing_and_removing_passwords"] +notes = "engine only" + +[[feature]] +id = "protect.certificate-encryption" +title = "Encrypt with certificates for recipients" +area = "G" +tier = "P1" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.recipient-permissions" +title = "Per-recipient permissions for certificate security" +area = "G" +tier = "P1" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.recipient-ldap" +title = "Find recipients in a directory (LDAP)" +area = "G" +tier = "P3" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.security-policies" +title = "Local security policies (saved presets)" +area = "G" +tier = "P2" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.rights-management" +title = "Server-based rights management (AEM/LiveCycle)" +area = "G" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "server product; local policy presets only" + +[[feature]] +id = "protect.mip-labels" +title = "Open Microsoft Purview-protected PDFs and apply labels" +area = "G" +tier = "P3" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "optional Windows plug-in" + +[[feature]] +id = "protect.redact-text" +title = "Mark text for redaction" +area = "G" +tier = "P0" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.redact-area" +title = "Mark areas and images for redaction" +area = "G" +tier = "P0" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.redact-pages" +title = "Mark whole pages for redaction" +area = "G" +tier = "P0" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.search-and-redact" +title = "Find text and redact" +area = "G" +tier = "P0" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.redact-word-lists" +title = "Redact lists of words or phrases" +area = "G" +tier = "P1" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.redact-patterns" +title = "Redact patterns (phone, email, card, SSN, dates)" +area = "G" +tier = "P0" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.redact-pattern-locales" +title = "Pattern sets per locale" +area = "G" +tier = "P1" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.redact-partial-words" +title = "Redact part of a match (e.g. all but the last four digits)" +area = "G" +tier = "P2" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.redact-folder" +title = "Search and redact across a folder" +area = "G" +tier = "P2" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.redaction-properties" +title = "Redaction fill colour and overlay text" +area = "G" +tier = "P0" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.redaction-codes" +title = "Redaction code sets (e.g. FOIA exemptions)" +area = "G" +tier = "P1" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.apply-redactions" +title = "Apply redactions (remove underlying content)" +area = "G" +tier = "P0" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.redact-glyph-removal" +title = "Remove redacted text glyphs from content streams" +area = "G" +tier = "P0" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.redact-image-pixels" +title = "Remove redacted image pixels" +area = "G" +tier = "P0" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.redact-vectors" +title = "Remove or cut redacted vector paths" +area = "G" +tier = "P0" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.redact-annots-fields" +title = "Remove annotations and fields under redactions" +area = "G" +tier = "P0" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.redact-struct-tree" +title = "Remove redacted content from tags and alternate text" +area = "G" +tier = "P0" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.redaction-verification" +title = "Verify no residue remains after redaction" +area = "G" +tier = "P0" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.redact-filename-suffix" +title = "Add a suffix to redacted file names" +area = "G" +tier = "P3" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.redact-ai-suggestions" +title = "Suggest sensitive content to redact via an AI provider" +area = "G" +tier = "P2" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "local or user-provided model; off by default" + +[[feature]] +id = "protect.remove-hidden-information" +title = "Remove hidden information (selectable categories)" +area = "G" +tier = "P1" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.sanitize" +title = "Sanitize document (remove all hidden information)" +area = "G" +tier = "P1" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.remove-hidden-on-close" +title = "Remove hidden information when closing or sending" +area = "G" +tier = "P3" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.attachment-trust" +title = "Block opening risky attachment types" +area = "G" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.url-access-control" +title = "Control internet access from PDFs" +area = "G" +tier = "P1" +milestone = "M6" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.protected-view" +title = "Open untrusted files in a restricted view" +area = "G" +tier = "P2" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "protect.security-envelope" +title = "Security envelopes" +area = "G" +tier = "P3" +milestone = "M8" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "legacy" + +# ================================================================================================== +# H. Digital signatures +# ================================================================================================== + +[[feature]] +id = "sign.pkcs12" +title = "Sign with a PKCS#12 (.p12/.pfx) digital ID" +area = "H" +tier = "P0" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.macos-keychain" +title = "Sign with macOS Keychain identities" +area = "H" +tier = "P0" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.windows-cert-store" +title = "Sign with the Windows certificate store" +area = "H" +tier = "P0" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.pkcs11" +title = "Sign with PKCS#11 tokens and smart cards" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.self-signed-id" +title = "Create a self-signed digital ID" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.export-certificate" +title = "Export a public certificate" +area = "H" +tier = "P2" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.visible-signature" +title = "Visible signature by drawing a rectangle" +area = "H" +tier = "P0" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.sign-existing-field" +title = "Sign an existing signature field" +area = "H" +tier = "P0" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.invisible-signature" +title = "Invisible signature" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.signature-appearance" +title = "Configure signature appearance (text, graphic, name)" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.reason-location" +title = "Reason, location and contact info" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.lock-after-signing" +title = "Lock document or fields after signing" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.pkcs7-detached" +title = "PKCS#7 detached signatures" +area = "H" +tier = "P0" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.cades-pades-bb" +title = "CAdES / PAdES B-B signatures" +area = "H" +tier = "P0" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.hash-algorithms" +title = "SHA-256/384/512 hashes; no SHA-1 for new signatures" +area = "H" +tier = "P0" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.certify" +title = "Certify a document (DocMDP)" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.certify-permissions" +title = "Allowed changes after certifying" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.field-mdp" +title = "Field locks (FieldMDP)" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.document-timestamp" +title = "Add a document timestamp (RFC 3161)" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.timestamp-servers" +title = "Configure timestamp servers" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.pades-bt" +title = "PAdES B-T (signature timestamp)" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.ltv" +title = "Long-term validation (DSS/VRI)" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.pades-blta" +title = "PAdES B-LT and B-LTA" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.ocsp-crl" +title = "Revocation checking (OCSP and CRL)" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.validate-all" +title = "Validate all signatures" +area = "H" +tier = "P0" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.signatures-panel" +title = "Signatures panel with per-revision tree" +area = "H" +tier = "P0" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.status-icons" +title = "Signature status (valid, unknown, invalid)" +area = "H" +tier = "P0" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.validation-banner" +title = "Signed/certified document message bar" +area = "H" +tier = "P0" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.signature-properties" +title = "Signature properties (validity summary, signer, time)" +area = "H" +tier = "P0" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.certificate-viewer" +title = "Certificate viewer" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.trust-store" +title = "Trusted certificates store" +area = "H" +tier = "P0" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.trust-import" +title = "Import trusted certificates and set trust" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.eutl" +title = "EU Trusted Lists" +area = "H" +tier = "P2" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.os-trust" +title = "Trust the operating system's root certificates" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.verification-time" +title = "Verification time (signing time, timestamp, now)" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.changes-after-signing" +title = "List changes made after signing (MDP-aware diff)" +area = "H" +tier = "P0" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.view-signed-version" +title = "View the signed version" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.compare-signed-version" +title = "Compare signed version with the current one" +area = "H" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.verify-on-open" +title = "Verify signatures when a document opens" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.clear-signature" +title = "Clear an unsaved signature; clear all signature fields" +area = "H" +tier = "P2" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.seed-values" +title = "Signature seed values" +area = "H" +tier = "P2" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.preview-mode" +title = "Preview document mode before signing" +area = "H" +tier = "P2" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.qualified-labels" +title = "Show qualified signature/seal labels" +area = "H" +tier = "P2" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.web-signing" +title = "Sign in the browser with a PKCS#12 file" +area = "H" +tier = "P1" +milestone = "M9" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "sign.aatl" +title = "Adobe Approved Trust List" +area = "H" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "Adobe-hosted list; use OS trust stores and EU Trusted Lists" + +[[feature]] +id = "sign.request-esignatures" +title = "Request e-signatures (Acrobat Sign)" +area = "H" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "Adobe cloud; pluggable provider hook only" + +[[feature]] +id = "sign.cloud-signatures" +title = "Cloud signature (CSC) digital IDs via Adobe" +area = "H" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "Adobe cloud; a pluggable remote-signing KeyProvider may follow" + +# ================================================================================================== +# I. Scan and OCR +# ================================================================================================== + +[[feature]] +id = "ocr.recognize-text" +title = "Recognize text (OCR) in this file" +area = "I" +tier = "P0" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.recognize-range" +title = "Recognize text in the current page or a range" +area = "I" +tier = "P0" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.recognize-multiple" +title = "Recognize text in multiple files" +area = "I" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.ocr-languages" +title = "OCR document language selection" +area = "I" +tier = "P0" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.searchable-image" +title = "Searchable image output (invisible text layer)" +area = "I" +tier = "P0" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.searchable-image-exact" +title = "Searchable image (exact): leave the image untouched" +area = "I" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.editable-text-output" +title = "Editable text and images output" +area = "I" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.ocr-downsample" +title = "Downsample images during OCR" +area = "I" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.skip-renderable-text" +title = "Skip pages that already have text" +area = "I" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.rotation-detection" +title = "Detect and fix page rotation" +area = "I" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.deskew" +title = "Deskew" +area = "I" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.despeckle" +title = "Despeckle" +area = "I" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.background-removal" +title = "Background removal" +area = "I" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.descreen" +title = "Descreen" +area = "I" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.remove-halo" +title = "Remove halos" +area = "I" +tier = "P3" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.edge-shadow-removal" +title = "Edge shadow removal" +area = "I" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.camera-perspective" +title = "Enhance camera images (edges, perspective, contrast)" +area = "I" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.camera-document-type" +title = "Camera image document types (whiteboard, card, form)" +area = "I" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.correct-recognized-text" +title = "Correct recognized text (low-confidence suspects)" +area = "I" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.adaptive-compression" +title = "Adaptive (MRC) compression of scans" +area = "I" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.scan-pdfa" +title = "Make scans PDF/A compliant" +area = "I" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.ocr-engines" +title = "Pluggable OCR engines (ocrs default; tesseract optional)" +area = "I" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.ocr-web" +title = "OCR in the browser" +area = "I" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.scanner-acquire" +title = "Scan from TWAIN/WIA/ICA/SANE scanners" +area = "I" +tier = "P2" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.scan-presets" +title = "Scan presets" +area = "I" +tier = "P2" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.scan-append" +title = "Append scans to an existing PDF or split into files" +area = "I" +tier = "P2" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "ocr.adobe-scan-sync" +title = "Adobe Scan mobile capture sync" +area = "I" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "Adobe cloud; import images from any source instead" + +# ================================================================================================== +# J. Create and export +# ================================================================================================== + +[[feature]] +id = "create.from-images" +title = "Create PDF from images (JPEG, PNG, TIFF, GIF, BMP, JPEG 2000)" +area = "J" +tier = "P0" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.from-multipage-tiff" +title = "Multi-page TIFF to multi-page PDF" +area = "J" +tier = "P0" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.from-text" +title = "Create PDF from plain text" +area = "J" +tier = "P0" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.from-markdown" +title = "Create PDF from Markdown" +area = "J" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.from-clipboard" +title = "Create PDF from the clipboard" +area = "J" +tier = "P0" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.from-screenshot" +title = "Create PDF from a screen capture" +area = "J" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.blank" +title = "Create a blank PDF" +area = "J" +tier = "P0" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.from-html" +title = "Create PDF from an HTML file" +area = "J" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.from-web-page" +title = "Create PDF from a web page URL" +area = "J" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.from-office" +title = "Create PDF from Office documents (LibreOffice sidecar)" +area = "J" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.create-multiple" +title = "Convert many files to PDF in one run" +area = "J" +tier = "P1" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.image-conversion-options" +title = "Image compression options when converting" +area = "J" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.pdf-printer" +title = "Print to PDF from other applications" +area = "J" +tier = "P3" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.distiller" +title = "PostScript/EPS to PDF (Distiller)" +area = "J" +tier = "P3" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "deferred: a PostScript interpreter is a large project" + +[[feature]] +id = "create.job-options" +title = "PDF settings presets (job options)" +area = "J" +tier = "P3" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.export-docx" +title = "Export to Word (.docx)" +area = "J" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.export-rtf" +title = "Export to RTF" +area = "J" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.export-xlsx" +title = "Export to Excel (.xlsx) with table detection" +area = "J" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.export-pptx" +title = "Export to PowerPoint (.pptx)" +area = "J" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.export-png" +title = "Export pages as PNG" +area = "J" +tier = "P0" +milestone = "M10" +status = "partial" +commands = [] +tools = ["page_render"] +evidence = ["crates/automation/tests/automation.rs::open_inspect_render_and_find"] +notes = "page_render tool and CLI render only; no Export UI" + +[[feature]] +id = "create.export-jpeg-tiff" +title = "Export pages as JPEG or TIFF" +area = "J" +tier = "P0" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.export-jpeg2000" +title = "Export pages as JPEG 2000" +area = "J" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.export-all-images" +title = "Export all embedded images" +area = "J" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.export-html" +title = "Export to HTML" +area = "J" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.export-text" +title = "Export to plain text" +area = "J" +tier = "P0" +milestone = "M10" +status = "partial" +commands = [] +tools = ["text_extract"] +evidence = ["crates/automation/tests/automation.rs::parallel_text_extraction_keeps_page_order_and_follows_edits", "crates/render/src/text.rs::two_columns_read_left_then_right_even_if_drawn_interleaved"] +notes = "text_extract tool and CLI text; no Export UI" + +[[feature]] +id = "create.export-accessible-text" +title = "Export accessible text (from tags)" +area = "J" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.export-xml" +title = "Export to XML" +area = "J" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.export-svg" +title = "Export to SVG" +area = "J" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.export-ps" +title = "Export to PostScript" +area = "J" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.export-eps" +title = "Export to EPS" +area = "J" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.export-ocr" +title = "Run OCR automatically when exporting scans" +area = "J" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.export-settings" +title = "Per-format export settings" +area = "J" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "create.text-extraction" +title = "Extract text in reading order (columns, RTL, CJK)" +area = "J" +tier = "P0" +milestone = "M2" +status = "shipped" +commands = [] +tools = ["text_extract"] +evidence = ["crates/render/src/text.rs::rtl_runs_come_out_in_logical_order_and_cjk_has_no_spaces", "crates/render/src/text.rs::lines_words_and_search", "crates/automation/tests/automation.rs::parallel_text_extraction_keeps_page_order_and_follows_edits"] +notes = "" + +[[feature]] +id = "create.translate" +title = "Translate documents" +area = "J" +tier = "P3" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "through an optional AiProvider; never Adobe's cloud" + +[[feature]] +id = "create.cloud-office-conversion" +title = "Cloud conversion from the macOS Office add-in" +area = "J" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "Adobe cloud; LibreOffice sidecar instead" + +# ================================================================================================== +# K. Optimize and standards +# ================================================================================================== + +[[feature]] +id = "optimize.reduce-file-size" +title = "Reduce file size (one click)" +area = "K" +tier = "P0" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.compatibility-level" +title = "Make compatible with a chosen PDF version" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.optimizer" +title = "Advanced optimization (PDF Optimizer)" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.downsample-images" +title = "Downsample colour, grey and mono images" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.recompress-images" +title = "Recompress images (JPEG, JPEG 2000, Flate)" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.mono-compression" +title = "Monochrome compression (JBIG2, CCITT G4)" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.only-if-smaller" +title = "Optimize images only if smaller" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.unembed-fonts" +title = "Unembed fonts" +area = "K" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.flatten-transparency" +title = "Flatten transparency" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.discard-javascript" +title = "Discard JavaScript actions" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.discard-thumbnails" +title = "Discard embedded thumbnails" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.discard-tags" +title = "Discard document tags" +area = "K" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.discard-bookmarks" +title = "Discard bookmarks" +area = "K" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.discard-search-index" +title = "Discard embedded search index" +area = "K" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.discard-print-settings" +title = "Discard embedded print settings" +area = "K" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.discard-alternate-images" +title = "Discard alternate images" +area = "K" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.discard-form-actions" +title = "Discard form submit, import and reset actions" +area = "K" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.flatten-form-fields" +title = "Flatten form fields while optimizing" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.discard-comments" +title = "Discard comments, forms and multimedia" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.discard-metadata" +title = "Discard document information and metadata" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.discard-attachments" +title = "Discard file attachments" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.discard-private-data" +title = "Discard private application data (PieceInfo)" +area = "K" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.discard-external-refs" +title = "Discard external cross-references" +area = "K" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.discard-hidden-layers" +title = "Discard hidden layers and flatten visible ones" +area = "K" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.merge-image-fragments" +title = "Detect and merge image fragments" +area = "K" +tier = "P3" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.lines-to-curves" +title = "Convert smooth lines to curves" +area = "K" +tier = "P3" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.compress-structure" +title = "Compress document structure (object streams)" +area = "K" +tier = "P1" +milestone = "M11" +status = "partial" +commands = ["file.save_as"] +tools = ["doc_save"] +evidence = ["crates/cos/tests/writer.rs::full_save_packs_objects_into_object_streams"] +notes = "on by default for full saves; no Optimizer option yet" + +[[feature]] +id = "optimize.flate-unencoded" +title = "Flate-encode unencoded streams; replace LZW with Flate" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.remove-invalid-links" +title = "Remove invalid links, bookmarks and unreferenced destinations" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.optimize-content" +title = "Optimize page content streams" +area = "K" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.audit-space" +title = "Audit space usage" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.optimizer-presets" +title = "Optimizer presets" +area = "K" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.batch-optimize" +title = "Optimize multiple files" +area = "K" +tier = "P2" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.preflight" +title = "Preflight analysis" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.preflight-fix" +title = "Preflight analyze and fix" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.preflight-profiles" +title = "Built-in preflight profiles" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.preflight-single-checks" +title = "Single checks" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.preflight-single-fixups" +title = "Single fixups" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.preflight-custom-profiles" +title = "Create and edit custom profiles" +area = "K" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.preflight-profile-exchange" +title = "Import, export and lock profiles" +area = "K" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.preflight-results" +title = "Preflight results tree with object details" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.preflight-snap-view" +title = "Snap view of a flagged object" +area = "K" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.preflight-reports" +title = "Preflight reports (PDF, XML, text)" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.preflight-droplets" +title = "Preflight droplets and batch preflight" +area = "K" +tier = "P2" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.preflight-audit-trail" +title = "Embed preflight audit trail" +area = "K" +tier = "P3" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.pdfa-validate" +title = "Verify PDF/A compliance (1-4)" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.pdfa-convert" +title = "Save as PDF/A (1b, 2b, 3b, 4)" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.pdfa-accessible-levels" +title = "PDF/A conformance levels a and u" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.pdfx-validate" +title = "Verify PDF/X compliance" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.pdfx-convert" +title = "Save as PDF/X (1a, 3, 4)" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.output-intents" +title = "Choose output intents and conditions" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.pdfe" +title = "PDF/E" +area = "K" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.pdfvt" +title = "PDF/VT" +area = "K" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.pdfua-validate" +title = "Verify PDF/UA-1 and PDF/UA-2" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.standards-panel" +title = "Standards panel showing declared conformance" +area = "K" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.remove-standards-id" +title = "Remove PDF/A or PDF/X identification" +area = "K" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.pdfx5-reference-xobjects" +title = "Show PDF/X-5 reference XObjects" +area = "K" +tier = "P3" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "optimize.einvoice-pdfa3" +title = "Embed e-invoice XML in PDF/A-3" +area = "K" +tier = "P3" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +# ================================================================================================== +# L. Print and print production +# ================================================================================================== + +[[feature]] +id = "print.print-dialog" +title = "Print dialog" +area = "L" +tier = "P0" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.printer-selection" +title = "Choose printer and properties" +area = "L" +tier = "P0" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.copies-collate" +title = "Copies and collation" +area = "L" +tier = "P0" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.page-range" +title = "Print all, current page or a range (labels allowed)" +area = "L" +tier = "P0" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.odd-even-reverse" +title = "Odd/even pages and reverse order" +area = "L" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.scaling" +title = "Fit, actual size, shrink oversized, custom scale" +area = "L" +tier = "P0" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.paper-source-by-size" +title = "Choose paper source by PDF page size" +area = "L" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.duplex" +title = "Print on both sides" +area = "L" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.n-up" +title = "Multiple pages per sheet" +area = "L" +tier = "P0" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.booklet" +title = "Booklet printing" +area = "L" +tier = "P0" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.poster" +title = "Poster / tiled printing with overlap and cut marks" +area = "L" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.orientation" +title = "Auto portrait/landscape" +area = "L" +tier = "P0" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.comments-and-forms" +title = "Print document, markups, stamps or form fields only" +area = "L" +tier = "P0" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.print-with-comment-summary" +title = "Print with a comments summary" +area = "L" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.print-preview" +title = "Print preview" +area = "L" +tier = "P0" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.grayscale" +title = "Print in grayscale" +area = "L" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.print-as-image" +title = "Print as image" +area = "L" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.page-setup" +title = "Page setup" +area = "L" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.print-ready-pdf" +title = "Produce a print-ready imposed PDF" +area = "L" +tier = "P1" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.print-pages-from-thumbnails" +title = "Print selected pages from thumbnails" +area = "L" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.postscript-output" +title = "PostScript Level 3 output" +area = "L" +tier = "P2" +milestone = "M10" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.output-preview" +title = "Output preview" +area = "L" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.separations" +title = "Separations preview with area coverage" +area = "L" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.total-area-coverage" +title = "Total area coverage warning" +area = "L" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.color-warnings" +title = "Overprint and rich-black warnings" +area = "L" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.object-inspector" +title = "Object inspector" +area = "L" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.simulate-paper-ink" +title = "Simulate paper colour and black ink" +area = "L" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.overprint-preview" +title = "Overprint preview" +area = "L" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.convert-colors" +title = "Convert colours" +area = "L" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.ink-manager" +title = "Ink manager" +area = "L" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.ink-aliases" +title = "Ink aliases and spot-to-process" +area = "L" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.printer-marks" +title = "Add printer marks (crop, bleed, registration, colour bars)" +area = "L" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.fix-hairlines" +title = "Fix hairlines" +area = "L" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.flattener-preview" +title = "Flattener preview" +area = "L" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.color-management" +title = "Colour management settings (working spaces, intents)" +area = "L" +tier = "P1" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.advanced-print-separations" +title = "Print separations and in-RIP separations" +area = "L" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.print-marks-bleeds" +title = "Marks and bleeds when printing" +area = "L" +tier = "P2" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.trap-presets" +title = "Trap presets (stored as data)" +area = "L" +tier = "P3" +milestone = "M11" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "print.jdf" +title = "JDF job definitions (read-only)" +area = "L" +tier = "P3" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +# ================================================================================================== +# M. Accessibility +# ================================================================================================== + +[[feature]] +id = "a11y.app-screen-reader" +title = "Screen-reader support for the app (AccessKit)" +area = "M" +tier = "P0" +milestone = "M3" +status = "partial" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/control.rs::inspect_and_click_by_label_and_id"] +notes = "AccessKit labels on all custom widgets; full audit in M14.3" + +[[feature]] +id = "a11y.keyboard-only" +title = "Full keyboard operation of the app" +area = "M" +tier = "P0" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.checker" +title = "Accessibility checker (full check)" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.checker-report" +title = "Accessibility report" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.checker-fix" +title = "Fix, skip, explain and re-check rules" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-permission-flag" +title = "Rule: accessibility permission flag" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-image-only" +title = "Rule: image-only PDF" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-tagged-pdf" +title = "Rule: tagged PDF" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-logical-order" +title = "Rule: logical reading order (manual)" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-primary-language" +title = "Rule: primary language" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-title" +title = "Rule: title shown in the window" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-bookmarks" +title = "Rule: bookmarks in long documents" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-color-contrast" +title = "Rule: colour contrast (manual)" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-tagged-content" +title = "Rule: all content tagged or artifact" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-tagged-annotations" +title = "Rule: tagged annotations" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-tab-order" +title = "Rule: tab order follows structure" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-character-encoding" +title = "Rule: reliable character encoding" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-tagged-multimedia" +title = "Rule: tagged multimedia" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-screen-flicker" +title = "Rule: screen flicker (manual)" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-scripts" +title = "Rule: scripts (manual)" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-timed-responses" +title = "Rule: timed responses (manual)" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-navigation-links" +title = "Rule: navigation links (manual)" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-tagged-fields" +title = "Rule: tagged form fields" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-field-descriptions" +title = "Rule: field descriptions" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-figure-alt" +title = "Rule: figures have alternate text" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-nested-alt" +title = "Rule: no nested alternate text" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-alt-associated" +title = "Rule: alternate text associated with content" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-alt-hides-annot" +title = "Rule: alternate text does not hide annotations" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-other-alt" +title = "Rule: other elements have alternate text" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-table-rows" +title = "Rule: table rows" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-table-cells" +title = "Rule: TH and TD in rows" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-table-headers" +title = "Rule: tables have headers" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-table-regularity" +title = "Rule: table regularity" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-table-summary" +title = "Rule: table summary" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-list-items" +title = "Rule: list items" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-lbl-lbody" +title = "Rule: Lbl and LBody" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.rule-heading-nesting" +title = "Rule: heading nesting" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.autotag" +title = "Automatically tag a document" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.autotag-fields" +title = "Autotag form fields" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.reading-order-tool" +title = "Reading order tool" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.tags-panel" +title = "Tags panel (new, delete, find, change to artifact)" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.tag-properties" +title = "Tag properties (type, title, actual text, alternate text, language)" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.role-map" +title = "Role map editor" +area = "M" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.class-map" +title = "Class map editor" +area = "M" +tier = "P3" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.order-panel" +title = "Order panel" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.content-panel" +title = "Content panel" +area = "M" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.create-tag-from-selection" +title = "Create a tag from a selection" +area = "M" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.artifact-marking" +title = "Mark content as artifact" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.find-untagged" +title = "Find unmarked content, comments and links" +area = "M" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.table-editor" +title = "Table editor (headers, scope, spans)" +area = "M" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.alt-text-workflow" +title = "Set alternate text figure by figure (with decorative option)" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.alt-text-suggestions" +title = "Suggest alternate text via an AI provider" +area = "M" +tier = "P2" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "optional provider; off by default" + +[[feature]] +id = "a11y.set-language" +title = "Set document language" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.set-title-display" +title = "Show the document title in the window" +area = "M" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.make-accessible-action" +title = "Make Accessible guided action" +area = "M" +tier = "P1" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.setup-assistant" +title = "Accessibility setup assistant" +area = "M" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.read-aloud" +title = "Read out loud (platform TTS)" +area = "M" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.read-aloud-options" +title = "Read-aloud voice, pitch and speed" +area = "M" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.replace-page-colors" +title = "Replace page colours (high contrast)" +area = "M" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.reflow-tagged" +title = "Reflow tagged content" +area = "M" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "a11y.keyboard-selection-cursor" +title = "Always show the keyboard selection cursor" +area = "M" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +# ================================================================================================== +# N. Compare, automation, AI, preferences, misc +# ================================================================================================== + +[[feature]] +id = "misc.compare-text" +title = "Compare files: text differences" +area = "N" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.compare-visual" +title = "Compare files: visual differences" +area = "N" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.compare-side-by-side" +title = "Side-by-side compare view with synchronised scrolling" +area = "N" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.compare-report" +title = "Compare summary report as PDF" +area = "N" +tier = "P1" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.compare-filters" +title = "Filter changes by type (text, images, formatting)" +area = "N" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.compare-page-ranges" +title = "Compare page ranges" +area = "N" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.compare-scanned" +title = "Compare scanned documents via OCR" +area = "N" +tier = "P2" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.action-wizard-run" +title = "Run guided actions on files" +area = "N" +tier = "P1" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.action-wizard-create" +title = "Create and edit actions" +area = "N" +tier = "P1" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.action-wizard-manage" +title = "Import, export, copy and delete actions" +area = "N" +tier = "P2" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.action-prepare-distribution" +title = "Built-in action: prepare for distribution" +area = "N" +tier = "P1" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.action-optimize-scans" +title = "Built-in action: optimize scanned documents" +area = "N" +tier = "P1" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.action-archive" +title = "Built-in action: archive documents" +area = "N" +tier = "P1" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.action-publish-web" +title = "Built-in action: prepare for web publishing" +area = "N" +tier = "P2" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.batch-processing" +title = "Batch processing over files and folders" +area = "N" +tier = "P1" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.action-prompt-steps" +title = "Prompt-user and instruction steps" +area = "N" +tier = "P2" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.action-javascript-step" +title = "Execute JavaScript step" +area = "N" +tier = "P2" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.custom-commands" +title = "Custom commands" +area = "N" +tier = "P1" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.custom-tools" +title = "Custom tool sets" +area = "N" +tier = "P2" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.command-registry" +title = "One command registry for menus, shortcuts, palette and automation" +area = "N" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = [] +tools = ["command_list"] +evidence = ["crates/engine/src/commands.rs::ids_and_shortcuts_are_unique", "crates/ui-egui/tests/commands.rs::every_registered_command_is_implemented"] +notes = "" + +[[feature]] +id = "misc.disabled-command-reasons" +title = "Disabled commands explain why" +area = "N" +tier = "P1" +milestone = "M4" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/commands.rs::disabled_commands_explain_themselves"] +notes = "" + +[[feature]] +id = "misc.cli-inspect" +title = "CLI: inspect, render and extract text" +area = "N" +tier = "P0" +milestone = "M2" +status = "shipped" +commands = [] +tools = ["doc_info", "page_render", "text_extract"] +evidence = ["crates/automation/tests/automation.rs::open_inspect_render_and_find"] +notes = "" + +[[feature]] +id = "misc.cli-edit" +title = "CLI: edit, combine, extract, split" +area = "N" +tier = "P0" +milestone = "M4" +status = "shipped" +commands = [] +tools = ["page_rotate", "page_delete", "doc_combine", "page_extract", "doc_split"] +evidence = ["crates/automation/tests/automation.rs::combine_extract_and_split", "crates/automation/tests/automation.rs::edit_undo_redo_and_save_round_trip"] +notes = "" + +[[feature]] +id = "misc.cli-run" +title = "CLI run: call any automation tool or a JSON script" +area = "N" +tier = "P0" +milestone = "M13" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/automation/tests/automation.rs::tool_table_is_well_formed", "crates/automation/tests/automation.rs::edit_undo_redo_and_save_round_trip"] +notes = "" + +[[feature]] +id = "misc.cli-completeness" +title = "CLI covers every shipped command" +area = "N" +tier = "P0" +milestone = "M13" +status = "partial" +commands = [] +tools = [] +evidence = ["crates/automation/tests/automation.rs::command_list_reports_enablement_and_tools"] +notes = "tools grow as features land" + +[[feature]] +id = "misc.automation-tools" +title = "Headless JSON-Schema tool table" +area = "N" +tier = "P0" +milestone = "M13" +status = "shipped" +commands = [] +tools = ["command_list"] +evidence = ["crates/automation/tests/automation.rs::tool_table_is_well_formed", "crates/automation/tests/automation.rs::errors_are_specific_and_safe"] +notes = "" + +[[feature]] +id = "misc.command-list-tool" +title = "List commands with enablement and their tools" +area = "N" +tier = "P1" +milestone = "M13" +status = "shipped" +commands = [] +tools = ["command_list"] +evidence = ["crates/automation/tests/automation.rs::command_list_reports_enablement_and_tools"] +notes = "" + +[[feature]] +id = "misc.mcp-server" +title = "Opt-in MCP server over stdio" +area = "N" +tier = "P0" +milestone = "M13" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/automation/tests/automation.rs::mcp_session_over_stdio", "crates/automation/tests/automation.rs::mcp_errors"] +notes = "" + +[[feature]] +id = "misc.automation-root-confinement" +title = "Confine automation file access to a root directory" +area = "N" +tier = "P0" +milestone = "M13" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/automation/tests/automation.rs::errors_are_specific_and_safe"] +notes = "" + +[[feature]] +id = "misc.mcp-resources" +title = "MCP resources for page images and text" +area = "N" +tier = "P1" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.ui-control-channel" +title = "UI control channel (inspect, click, type, keys, commands)" +area = "N" +tier = "P0" +milestone = "M3" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/control.rs::inspect_and_click_by_label_and_id", "crates/ui-egui/tests/control.rs::commands_keys_and_typing", "crates/ui-egui/tests/control.rs::loopback_transport_requires_the_token"] +notes = "" + +[[feature]] +id = "misc.ui-screenshots" +title = "Screenshots of the window or a region via the control channel" +area = "N" +tier = "P1" +milestone = "M3" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/control.rs::screenshots_of_window_and_region"] +notes = "" + +[[feature]] +id = "misc.ai-provider" +title = "AI provider interface (local model or own endpoint), off by default" +area = "N" +tier = "P2" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.ai-summary" +title = "Summarize a document" +area = "N" +tier = "P2" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.ai-ask" +title = "Ask questions with citations to pages" +area = "N" +tier = "P2" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.ai-conversational-edit" +title = "Natural-language editing through registry commands" +area = "N" +tier = "P3" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.ai-presentation" +title = "Generate a presentation from documents" +area = "N" +tier = "P3" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.ai-podcast" +title = "Generate an audio overview" +area = "N" +tier = "P3" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.ai-charts" +title = "Charts from document data" +area = "N" +tier = "P3" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.contract-analysis" +title = "Contract key terms and multi-contract compare" +area = "N" +tier = "P3" +milestone = "M13" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.adobe-ai-assistant" +title = "Adobe AI Assistant" +area = "N" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "Adobe cloud; replaced by the optional local/BYO AiProvider" + +[[feature]] +id = "misc.pdf-spaces" +title = "PDF Spaces" +area = "N" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "Adobe cloud; no local equivalent planned" + +[[feature]] +id = "misc.adobe-express" +title = "Adobe Express integration and generated images" +area = "N" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "Adobe cloud" + +[[feature]] +id = "misc.document-cloud-storage" +title = "Adobe Document Cloud storage" +area = "N" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "Adobe cloud; local files and native dialogs (pluggable storage later)" + +[[feature]] +id = "misc.share-links" +title = "Share links and invitations" +area = "N" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "Adobe cloud; pluggable share provider" + +[[feature]] +id = "misc.connected-storage" +title = "Connected third-party storage accounts in-app" +area = "N" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "Adobe cloud; use OS file dialogs and synced folders" + +[[feature]] +id = "misc.cross-device-sync" +title = "Sync signatures, stamps and preferences across devices" +area = "N" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "Adobe account service" + +[[feature]] +id = "misc.adobe-account" +title = "Adobe account sign-in" +area = "N" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "not applicable; no account needed" + +[[feature]] +id = "misc.rich-media-preserve" +title = "Preserve 3D, video and sound content" +area = "N" +tier = "P3" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.rich-media-poster" +title = "Show posters for rich media" +area = "N" +tier = "P3" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.rich-media-playback" +title = "Play video and sound via the platform" +area = "N" +tier = "P3" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.3d-view" +title = "View 3D (U3D/PRC) content" +area = "N" +tier = "P3" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.legacy-multimedia" +title = "Legacy multimedia (screen annotations, renditions)" +area = "N" +tier = "P3" +milestone = "M12" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.flash-swf" +title = "Flash/SWF content" +area = "N" +tier = "N/A" +milestone = "none" +status = "na" +commands = [] +tools = [] +evidence = [] +notes = "Flash is end-of-life; content is preserved but not played" + +[[feature]] +id = "misc.preferences-dialog" +title = "Preferences dialog generated from a schema" +area = "N" +tier = "P1" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-general" +title = "Preferences: general" +area = "N" +tier = "P1" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-page-display" +title = "Preferences: page display" +area = "N" +tier = "P1" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-documents" +title = "Preferences: documents (autosave interval, recent count)" +area = "N" +tier = "P1" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-commenting" +title = "Preferences: commenting" +area = "N" +tier = "P1" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-forms" +title = "Preferences: forms" +area = "N" +tier = "P1" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-identity" +title = "Preferences: identity" +area = "N" +tier = "P1" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-full-screen" +title = "Preferences: full screen" +area = "N" +tier = "P2" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-search" +title = "Preferences: search" +area = "N" +tier = "P2" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-signatures" +title = "Preferences: signatures" +area = "N" +tier = "P1" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-security" +title = "Preferences: security and trust" +area = "N" +tier = "P1" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-spelling" +title = "Preferences: spelling" +area = "N" +tier = "P2" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-language" +title = "Preferences: language" +area = "N" +tier = "P1" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-reading" +title = "Preferences: reading" +area = "N" +tier = "P2" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-accessibility" +title = "Preferences: accessibility" +area = "N" +tier = "P1" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-color-management" +title = "Preferences: colour management" +area = "N" +tier = "P1" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-javascript" +title = "Preferences: JavaScript" +area = "N" +tier = "P1" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-measuring" +title = "Preferences: measuring" +area = "N" +tier = "P2" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-units-guides" +title = "Preferences: units and guides" +area = "N" +tier = "P2" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-content-editing" +title = "Preferences: content editing" +area = "N" +tier = "P2" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-convert" +title = "Preferences: convert to and from PDF" +area = "N" +tier = "P2" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.prefs-catalog" +title = "Preferences: catalog / index" +area = "N" +tier = "P3" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.admin-preferences" +title = "Admin-deployable preference defaults" +area = "N" +tier = "P2" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.localization" +title = "Localization (Fluent catalogs)" +area = "N" +tier = "P1" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.rtl-ui" +title = "Right-to-left user interface" +area = "N" +tier = "P2" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.installers" +title = "Signed installers for macOS, Windows and Linux" +area = "N" +tier = "P1" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.web-deploy" +title = "Hosted web app deployment" +area = "N" +tier = "P1" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.performance-budgets" +title = "Meet performance budgets on Tier-1 platforms" +area = "N" +tier = "P0" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.send-email" +title = "Send the file by email (system mail client)" +area = "N" +tier = "P3" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" + +[[feature]] +id = "misc.user-docs" +title = "User documentation and keyboard reference" +area = "N" +tier = "P1" +milestone = "M14" +status = "planned" +commands = [] +tools = [] +evidence = [] +notes = "" diff --git a/xtask/src/fuzz.rs b/xtask/src/fuzz.rs index 0bc2f14770..d3cf23a3c1 100644 --- a/xtask/src/fuzz.rs +++ b/xtask/src/fuzz.rs @@ -193,11 +193,21 @@ fn synthetic_seeds() -> Vec> { fn seeds() -> Vec<(String, Vec)> { let mut out: Vec<(String, Vec)> = synthetic_seeds().into_iter().enumerate().map(|(i, b)| (format!("synthetic-{i}"), b)).collect(); + // Seeds the corpus sweep already knows to hang only rediscover that hang. + let known_hangs: std::collections::HashSet = std::fs::read_to_string(root().join("xtask/baselines/pdfjs.json")) + .ok() + .and_then(|t| serde_json::from_str::>(&t).ok()) + .map(|m| m.into_iter().filter(|(_, v)| v == "timeout").map(|(k, _)| k).collect()) + .unwrap_or_default(); let dir = root().join("corpus/pdfjs/test/pdfs"); if let Ok(rd) = std::fs::read_dir(&dir) { let mut files: Vec = rd.flatten().map(|e| e.path()).filter(|p| p.extension().is_some_and(|e| e == "pdf")).collect(); files.sort(); for f in files { + let name = f.file_name().unwrap_or_default().to_string_lossy().into_owned(); + if known_hangs.contains(&name) { + continue; + } if let Ok(b) = std::fs::read(&f) && b.len() <= 64 * 1024 { diff --git a/xtask/src/gates.rs b/xtask/src/gates.rs index 133718e236..2b0f64f3ab 100644 --- a/xtask/src/gates.rs +++ b/xtask/src/gates.rs @@ -93,6 +93,7 @@ pub fn ci(_: &[String]) -> anyhow::Result<()> { ("wasm", Box::new(|| wasm(&[]))), ("assets", Box::new(|| crate::assets::run(&[]))), ("deny", Box::new(|| deny(&[]))), + ("parity", Box::new(|| crate::parity::run(&[]))), ]; for (i, (name, f)) in steps.iter().enumerate() { eprintln!("\n=== ci: {name} ==="); diff --git a/xtask/src/main.rs b/xtask/src/main.rs index cad041270e..221729f93a 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -7,6 +7,7 @@ mod demo_pdf; mod fuzz; mod gates; mod layers; +mod parity; mod screenshots; type Command = fn(&[String]) -> anyhow::Result<()>; @@ -16,11 +17,12 @@ const COMMANDS: &[(&str, &str, Command)] = &[ ("layers", "Enforce the crate dependency layering (plan/architecture.md §3)", gates::layers), ("wasm", "cargo check --target wasm32-unknown-unknown for every crate below L8", gates::wasm), ("deny", "Dependency licences, bans, sources and advisories (deny.toml; needs cargo-deny)", gates::deny), - ("ci", "fmt --check, clippy -D warnings, test, layers, wasm, assets, deny (stops at first failure)", gates::ci), + ("ci", "fmt --check, clippy -D warnings, test, layers, wasm, assets, deny, parity (stops at first failure)", gates::ci), ("assets", "Enforce the asset policy (AGENTS.md §1) against ATTRIBUTION.toml; --write regenerates ATTRIBUTION.md", assets::run), ("corpus", "Fetch test corpora into corpus/ (git-ignored): pdf.js test PDFs", gates::corpus), ("check", "Robustness sweep over corpus/ with printcraft-cli; fails on crashes or regressions vs xtask/baselines", gates::check), ("fuzz", "Mutation fuzzing of open/render/edit/save in child processes; findings in fuzz-out/ (--time 300)", fuzz::run), + ("parity", "Validate parity/acrobat-features.toml against the registry, tools and tests; report progress (--json, --partial)", parity::run), ("text-oracle", "Compare text extraction with pdftotext over corpus/ (word F1; target median ≥ 0.97)", gates::text_oracle), ("screenshots", "Regenerate the README screenshots in docs/images/ and their ATTRIBUTION entries", screenshots::run), ("demo-pdf", "Build dist/demo/printcraft-showcase.pdf (needs Google Chrome or Chromium)", demo_pdf::run), diff --git a/xtask/src/parity.rs b/xtask/src/parity.rs new file mode 100644 index 0000000000..0e906d285c --- /dev/null +++ b/xtask/src/parity.rs @@ -0,0 +1,283 @@ +//! `cargo xtask parity`: validate `parity/acrobat-features.toml` and report progress (M0.8). +//! +//! Checks: +//! - schema: unique ids (`area.kebab-name`), known area letters, tiers, milestones and statuses; +//! - for `shipped` and `partial` features, every listed command exists in the engine registry and +//! every tool exists in the automation table (so the claim is reachable from the UI and +//! headlessly); +//! - `shipped` features cite at least one test, and every cited `path::fn` exists. +//! +//! The report gives the shipped share per tier and per area (partial counts half in the +//! weighted figure). `--json` prints the numbers for STATUS.md; `--partial` lists partial +//! features with their notes. Fails on any validation error. + +use std::collections::{BTreeMap, HashSet}; +use std::path::Path; + +use anyhow::{Context, Result, bail}; +use serde::Deserialize; + +use crate::gates::root; + +const AREAS: &[(&str, &str)] = &[ + ("A", "Core model and fidelity"), + ("B", "Viewing and navigation"), + ("C", "Content editing"), + ("D", "Organize pages"), + ("E", "Comments and review"), + ("F", "Forms and JavaScript"), + ("G", "Protect, redact, sanitize"), + ("H", "Digital signatures"), + ("I", "Scan and OCR"), + ("J", "Create and export"), + ("K", "Optimize and standards"), + ("L", "Print and print production"), + ("M", "Accessibility"), + ("N", "Compare, automation, AI, misc"), +]; +const TIERS: &[&str] = &["P0", "P1", "P2", "P3", "N/A"]; +const STATUSES: &[&str] = &["planned", "partial", "shipped", "na"]; + +#[derive(Debug, Deserialize)] +struct File { + schema: Schema, + #[serde(rename = "feature")] + features: Vec, +} + +#[derive(Debug, Deserialize)] +struct Schema { + version: u32, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Feature { + pub id: String, + pub title: String, + pub area: String, + pub tier: String, + pub milestone: String, + pub status: String, + #[serde(default)] + pub commands: Vec, + #[serde(default)] + pub tools: Vec, + #[serde(default)] + pub evidence: Vec, + #[serde(default)] + pub notes: String, +} + +/// Quoted ids that follow `prefix(` in a source file: `c("page.rotate", …)` → `page.rotate`. +fn quoted_after(src: &str, prefixes: &[&str]) -> HashSet { + let mut out = HashSet::new(); + for p in prefixes { + let pat = format!("{p}("); + let mut from = 0; + while let Some(i) = src[from..].find(&pat).map(|i| i + from) { + from = i + pat.len(); + // Whole identifiers only: `t(` must not match inside `ct(`. + let boundary = src[..i].chars().next_back().is_none_or(|c| !(c.is_alphanumeric() || c == '_')); + // The id may start on the next line (`t(\n "doc_save",`). + let rest = src[from..].trim_start(); + if boundary + && let Some(body) = rest.strip_prefix('"') + && let Some(end) = body.find('"') + { + out.insert(body[..end].to_string()); + } + } + } + out +} + +fn valid_id(id: &str) -> bool { + let Some((area, name)) = id.split_once('.') else { return false }; + !area.is_empty() + && area.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit()) + && !name.is_empty() + && name.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-' || c == '.') +} + +fn valid_milestone(m: &str) -> bool { + m == "none" || m.strip_prefix('M').and_then(|n| n.parse::().ok()).is_some_and(|n| n <= 14) +} + +/// Validate features against the registry, tool table and source tree; returns the errors. +pub fn validate(features: &[Feature], commands: &HashSet, tools: &HashSet, repo: &Path) -> Vec { + let mut errors = Vec::new(); + let mut ids = HashSet::new(); + for f in features { + let e = |m: String| format!("{}: {m}", f.id); + if !ids.insert(f.id.as_str()) { + errors.push(e("duplicate id".into())); + } + if !valid_id(&f.id) { + errors.push(e("id must look like `area.kebab-name`".into())); + } + if f.title.trim().is_empty() { + errors.push(e("empty title".into())); + } + if !AREAS.iter().any(|(a, _)| *a == f.area) { + errors.push(e(format!("unknown area {:?} (A–N)", f.area))); + } + if !TIERS.contains(&f.tier.as_str()) { + errors.push(e(format!("unknown tier {:?}", f.tier))); + } + if !STATUSES.contains(&f.status.as_str()) { + errors.push(e(format!("unknown status {:?}", f.status))); + } + if !valid_milestone(&f.milestone) { + errors.push(e(format!("milestone {:?} must be M0–M14 or none", f.milestone))); + } + if matches!(f.status.as_str(), "shipped" | "partial") { + for c in f.commands.iter().filter(|c| !commands.contains(*c)) { + errors.push(e(format!("command {c:?} is not in the engine registry"))); + } + for t in f.tools.iter().filter(|t| !tools.contains(*t)) { + errors.push(e(format!("tool {t:?} is not in the automation table"))); + } + } + if f.status == "shipped" && f.evidence.is_empty() { + errors.push(e("shipped features must cite at least one test in `evidence`".into())); + } + for ev in &f.evidence { + let Some((path, func)) = ev.split_once("::") else { + errors.push(e(format!("evidence {ev:?} must be `path.rs::test_fn`"))); + continue; + }; + match std::fs::read_to_string(repo.join(path)) { + Ok(src) if src.contains(&format!("fn {func}(")) => {} + Ok(_) => errors.push(e(format!("evidence: no `fn {func}` in {path}"))), + Err(_) => errors.push(e(format!("evidence: {path} does not exist"))), + } + } + } + errors +} + +pub fn run(args: &[String]) -> Result<()> { + let repo = root(); + let path = repo.join("parity/acrobat-features.toml"); + let text = std::fs::read_to_string(&path).with_context(|| format!("reading {}", path.display()))?; + let file: File = toml::from_str(&text).with_context(|| format!("parsing {}", path.display()))?; + if file.schema.version != 1 { + bail!("unsupported parity schema version {}", file.schema.version); + } + let commands = quoted_after(&std::fs::read_to_string(repo.join("crates/engine/src/commands.rs"))?, &["c", "ct"]); + let tools = quoted_after(&std::fs::read_to_string(repo.join("crates/automation/src/tools.rs"))?, &["t"]); + let errors = validate(&file.features, &commands, &tools, &repo); + + // Progress: per tier and per area. N/A tier and `na` status are excluded from percentages. + let counted: Vec<&Feature> = file.features.iter().filter(|f| f.status != "na" && f.tier != "N/A").collect(); + let score = |fs: &[&Feature]| { + let shipped = fs.iter().filter(|f| f.status == "shipped").count(); + let partial = fs.iter().filter(|f| f.status == "partial").count(); + let pct = |x: f64| if fs.is_empty() { 0.0 } else { 100.0 * x / fs.len() as f64 }; + (fs.len(), shipped, partial, pct(shipped as f64), pct(shipped as f64 + partial as f64 / 2.0)) + }; + let mut by_tier = BTreeMap::new(); + for t in TIERS.iter().filter(|t| **t != "N/A") { + let fs: Vec<&Feature> = counted.iter().copied().filter(|f| f.tier == *t).collect(); + by_tier.insert(*t, score(&fs)); + } + let mut by_area = Vec::new(); + for (a, name) in AREAS { + let fs: Vec<&Feature> = counted.iter().copied().filter(|f| f.area == *a).collect(); + by_area.push((*a, *name, score(&fs))); + } + let total = score(&counted); + let without_headless: Vec<&str> = file.features.iter().filter(|f| f.status == "shipped" && f.tools.is_empty()).map(|f| f.id.as_str()).collect(); + + if args.iter().any(|a| a == "--json") { + let json = serde_json::json!({ + "features": file.features.len(), + "total": { "counted": total.0, "shipped": total.1, "partial": total.2, "shipped_pct": total.3, "weighted_pct": total.4 }, + "tiers": by_tier.iter().map(|(t, s)| (t.to_string(), serde_json::json!({ "counted": s.0, "shipped": s.1, "partial": s.2, "shipped_pct": s.3 }))).collect::>(), + "errors": errors, + }); + println!("{}", serde_json::to_string_pretty(&json)?); + } else { + println!("parity: {} features ({} counted; {} na)", file.features.len(), counted.len(), file.features.len() - counted.len()); + println!("\n tier features shipped partial shipped% weighted%"); + for (t, s) in &by_tier { + println!(" {t:<4} {:>8} {:>7} {:>7} {:>7.1}% {:>8.1}%", s.0, s.1, s.2, s.3, s.4); + } + println!(" all {:>8} {:>7} {:>7} {:>7.1}% {:>8.1}%", total.0, total.1, total.2, total.3, total.4); + println!("\n area features shipped partial weighted%"); + for (a, name, s) in &by_area { + println!(" {a} {name:<33} {:>8} {:>7} {:>7} {:>8.1}%", s.0, s.1, s.2, s.4); + } + if args.iter().any(|a| a == "--partial") { + println!("\npartial features and what is missing:"); + for f in file.features.iter().filter(|f| f.status == "partial") { + println!(" {:<40} {}", f.id, f.notes); + } + } + if !without_headless.is_empty() { + println!( + "\nnote: {} shipped feature(s) have no automation tool yet (AGENTS.md §3): {}", + without_headless.len(), + without_headless.join(", ") + ); + } + } + if !errors.is_empty() { + for e in &errors { + eprintln!("parity error: {e}"); + } + bail!("{} parity error(s)", errors.len()); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn f(id: &str, status: &str) -> Feature { + Feature { + id: id.into(), + title: "T".into(), + area: "D".into(), + tier: "P0".into(), + milestone: "M4".into(), + status: status.into(), + commands: vec![], + tools: vec![], + evidence: vec![], + notes: String::new(), + } + } + + #[test] + fn registry_ids_are_extracted_from_source() { + let src = + "c(\"file.open\", \"Open…\", FILE, None), ct(\"edit.undo\", \"Undo\"), t(\"doc_open\", \"Open\"), t(\n \"doc_save\",), format(x)"; + assert_eq!(quoted_after(src, &["c", "ct"]), HashSet::from(["file.open".to_string(), "edit.undo".to_string()])); + assert_eq!(quoted_after(src, &["t"]), HashSet::from(["doc_open".to_string(), "doc_save".to_string()])); + } + + #[test] + fn validation_catches_false_claims() { + let repo = root(); + let cmds = HashSet::from(["page.rotate".to_string()]); + let tools = HashSet::from(["page_rotate".to_string()]); + let mut ok = f("organize.rotate", "shipped"); + ok.commands = vec!["page.rotate".into()]; + ok.tools = vec!["page_rotate".into()]; + ok.evidence = vec!["xtask/src/parity.rs::validation_catches_false_claims".into()]; + assert!(validate(&[ok], &cmds, &tools, &repo).is_empty()); + + let mut bad = f("organize.Rotate", "shipped"); + bad.commands = vec!["page.spin".into()]; + bad.evidence = vec!["xtask/src/parity.rs::no_such_test".into(), "nowhere.rs::x".into(), "nopath".into()]; + let errs = validate(&[bad, f("organize.x", "planned"), f("organize.x", "bogus")], &cmds, &tools, &repo); + let all = errs.join("\n"); + for needle in ["kebab", "registry", "no `fn no_such_test`", "does not exist", "path.rs::test_fn", "duplicate id", "unknown status"] { + assert!(all.contains(needle), "missing {needle:?} in:\n{all}"); + } + assert!(validate(&[f("organize.y", "shipped")], &cmds, &tools, &repo)[0].contains("at least one test")); + } +}