mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
A tab named in Arabic showed replacement boxes: no interface font had the glyphs, and egui joins the letters of a right-to-left word but places the runs in logical order. - Fonts: the interface takes `Arab` faces from the craft-fonts build input (also on wasm), and on desktop adds one font already installed on the machine as the last fallback of every family. It is read at runtime, never embedded; PDFCRAFT_SYSTEM_FONTS=0 turns it off and `cargo xtask screenshots` sets that. AGENTS.md §1.4 records the rule. - Order: bidi::visual puts single-line text in display order with a left-to-right base, used for the painted tab label and hover text, the recent-files name and path, and the Properties File and Location rows. Accessibility names keep the logical text. misc.rtl-ui stays planned: multi-line text, text input and a mirrored layout are not done. Co-authored-by: Claude Opus 5.5 <[email protected]>
10015 lines
289 KiB
TOML
10015 lines
289 KiB
TOML
# PdfCraft 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", "crates/ui-egui/tests/ui.rs::files_and_quit_from_the_operating_system"]
|
||
notes = "File ▸ Open, the command line (Linux and Windows file associations), and on macOS Finder, Open With and the Dock (Apple events; the bundle declares PDF as a document type)"
|
||
|
||
[[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 = "shipped"
|
||
commands = []
|
||
tools = ["doc_info"]
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::damaged_files_say_they_were_repaired"]
|
||
notes = "a notice says the file was repaired; Document Properties ▸ Advanced lists the repairs; doc_info reports them"
|
||
|
||
[[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 = "shipped"
|
||
commands = []
|
||
tools = ["doc_open"]
|
||
evidence = ["crates/cos/src/document.rs::hybrid_reference_files_read_the_xref_stream_too"]
|
||
notes = "/XRefStm entries supplement the classic table and replace its free entries for objects in object streams; each section is merged as a unit so older revisions only fill gaps"
|
||
|
||
[[feature]]
|
||
id = "core.revision-chain"
|
||
title = "Follow /Prev revision chains with loop protection"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M1"
|
||
status = "shipped"
|
||
commands = ["file.properties"]
|
||
tools = ["doc_revisions"]
|
||
evidence = ["crates/cos/src/document.rs::looping_revision_chains_stop", "crates/cos/src/document.rs::revision_ends_split_incremental_updates"]
|
||
notes = "/Prev chains followed newest first with a visited set (loops stop and are logged); revisions listed and opened (doc_revisions, doc_open_revision)"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["file.properties"]
|
||
tools = ["doc_revisions"]
|
||
evidence = ["crates/cos/src/document.rs::revision_ends_split_incremental_updates", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
notes = "revision ends from the xref chain (linearized first-page sections fold into their revision); listed in Document Properties ▸ Advanced and by doc_revisions with the signatures covering each"
|
||
|
||
[[feature]]
|
||
id = "core.view-revision"
|
||
title = "View an earlier revision as a read-only snapshot"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["file.properties"]
|
||
tools = ["doc_open_revision"]
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::an_earlier_revision_opens_from_document_properties", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
notes = "opens the revision's bytes as a new unsaved document named '<name> (revision n)'"
|
||
|
||
[[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", "crates/ui-egui/tests/editing.rs::save_prompt_stays_inside_the_screen_for_a_long_filename"]
|
||
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 = "shipped"
|
||
commands = ["file.close_all"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::close_all_asks_only_for_changed_documents"]
|
||
notes = "clean documents close; each changed one asks"
|
||
|
||
[[feature]]
|
||
id = "core.revert"
|
||
title = "Revert to the last saved version"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["file.revert"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::revert_after_confirming", "crates/engine/src/tests.rs::revert_goes_back_to_the_saved_version"]
|
||
notes = "with confirmation; clears undo, as Acrobat"
|
||
|
||
[[feature]]
|
||
id = "core.signed-save-policy"
|
||
title = "Force incremental saves for signed documents"
|
||
area = "A"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["file.save"]
|
||
tools = ["doc_save"]
|
||
evidence = ["crates/engine/src/tests.rs::signing_saving_trusting_and_commenting_afterwards"]
|
||
notes = "saves of signed documents are always incremental: full rewrites are refused, and edits that would need one (applying redactions, changing security, sanitizing) are refused before they change anything"
|
||
|
||
[[feature]]
|
||
id = "core.drag-drop-open"
|
||
title = "Open files by drag and drop"
|
||
area = "A"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/ui.rs::dropping_a_pdf_on_the_window_opens_it"]
|
||
notes = "desktop: the dropped file opens in a tab (by path, or its bytes); web: read asynchronously"
|
||
|
||
[[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 = "shipped"
|
||
commands = []
|
||
tools = ["doc_save"]
|
||
evidence = ["crates/engine/src/tests.rs::signing_saving_trusting_and_commenting_afterwards", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
notes = "full rewrites of signed documents are refused; saves stay incremental"
|
||
|
||
[[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", "crates/render/src/raster.rs::highlights_without_an_appearance_are_drawn"]
|
||
notes = "a Highlight without /AP is drawn from /QuadPoints, /C and /CA; other comment types without /AP are not drawn yet"
|
||
|
||
[[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", "crates/render/src/raster.rs::watchdog_skips_a_stuck_page_and_keeps_rendering"]
|
||
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 = "shipped"
|
||
commands = ["view.layout.continuous"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/view.rs::continuous_layout_stacks_pages_vertically", "crates/ui-egui/tests/view.rs::page_display_commands_switch_layouts", "crates/ui-egui/tests/view.rs::default_page_display_is_used_for_new_documents", "crates/ui-egui/src/canvas.rs::fit_page_and_height_hold_still_while_mixed_page_sizes_scroll_past"]
|
||
notes = "the default for PDFs that don't ask for a layout (Preferences ▸ Default page display); pages stack with no snapping. Fit page and Fit height fit the largest page, so the zoom holds still across mixed page sizes"
|
||
|
||
[[feature]]
|
||
id = "view.layout-single-page"
|
||
title = "Single page view"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.layout.single"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/view.rs::single_page_layout_shows_one_page_at_a_time", "crates/ui-egui/tests/view.rs::single_page_wheel_pans_instead_when_zoomed_in", "crates/ui-egui/tests/view.rs::wheel_turns_one_page_per_notch_and_per_trackpad_swipe", "crates/ui-egui/tests/view.rs::rail_page_display_button_switches_layouts", "crates/ui-egui/tests/view.rs::view_menu_page_display_uses_the_same_commands"]
|
||
notes = "when the whole page fits, each wheel notch turns a page and each trackpad swipe turns one, momentum included; zoomed in, the wheel pans and never turns the page. Switched from the View menu, the rail's Page display button or the palette"
|
||
|
||
[[feature]]
|
||
id = "view.layout-two-up"
|
||
title = "Two-page view"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.layout.two_up"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/view.rs::two_up_layout_places_pages_side_by_side", "crates/ui-egui/tests/viewer.rs::two_page_view_steps_a_spread_at_a_time"]
|
||
notes = "next/previous page step a spread at a time"
|
||
|
||
[[feature]]
|
||
id = "view.layout-two-up-scrolling"
|
||
title = "Two-page scrolling"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::layouts_fit_height_labels_and_system_theme"]
|
||
notes = "the two-page view scrolls continuously"
|
||
|
||
[[feature]]
|
||
id = "view.cover-page"
|
||
title = "Show cover page in two-page view"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.layout.cover"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::layouts_fit_height_labels_and_system_theme", "crates/ui-egui/tests/view.rs::page_display_commands_switch_layouts", "crates/ui-egui/tests/viewer.rs::page_down_steps_every_spread_when_several_fit_on_screen"]
|
||
notes = "View ▸ Page display, the rail's Page display button or the palette; disabled outside two-page view; next / previous page step one spread at a time even when several spreads fit on screen"
|
||
|
||
[[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 = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/control.rs::middle_click_autoscroll_latches_has_a_dead_zone_and_scrolls_both_directions", "crates/ui-egui/tests/control.rs::organize_pages_supports_autoscroll_without_selecting_or_reordering_pages", "crates/ui-egui/tests/control.rs::moving_before_middle_button_release_keeps_scrolling_without_starting_page_tools", "crates/ui-egui/tests/control.rs::farther_from_the_click_scrolls_faster_in_the_viewer_and_page_grid", "crates/ui-egui/tests/control.rs::autoscroll_uses_the_initial_click_position_when_input_arrives_in_one_frame", "crates/ui-egui/tests/control.rs::autoscroll_uses_elapsed_frame_time_and_preserves_fractional_motion_in_both_views"]
|
||
notes = "Linux-only middle-button vertical autoscroll in the document and Organize Pages; other targets retain their existing middle-button input behavior. Click to toggle, release keeps scrolling on, and a Chromium-style nonlinear curve increases speed with vertical screen distance from the initial click. A 15-point dead zone pauses immediately. Continuous redraws use elapsed frame time and preserve fractional movement, tested at 30/60/120/144 fps. Cancels on another middle click, Escape, other pointer/wheel input, focus loss, tab changes and dialogs. Driven headlessly through the opt-in UI control channel (ui.click/ui.move/ui.drag). Keyboard-started timed scrolling is not implemented."
|
||
|
||
[[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 = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/view.rs::actual_size_fit_width_and_fit_page"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.zoom-fit-page"
|
||
title = "Fit page"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.fit_one_page"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/view.rs::actual_size_fit_width_and_fit_page", "crates/ui-egui/tests/view.rs::rail_page_display_menu_offers_acrobats_view_choices", "crates/ui-egui/tests/view.rs::fit_page_holds_zoom_across_mixed_page_sizes", "crates/ui-egui/tests/view.rs::single_page_fit_page_fits_the_shown_page"]
|
||
notes = "View ▸ Zoom to page level (⌘0) and the rail's Page display menu, which also offers Fit one full page (single-page view at Fit page). Scrolling views fit their largest page so mixed sizes never jump the zoom; single-page view fits the shown page"
|
||
|
||
[[feature]]
|
||
id = "view.zoom-fit-width"
|
||
title = "Fit width"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.fit_width_scrolling"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/view.rs::actual_size_fit_width_and_fit_page", "crates/ui-egui/tests/view.rs::rail_page_display_menu_offers_acrobats_view_choices"]
|
||
notes = "View ▸ Fit to width (⌘2) and the rail's Page display menu's Fit to width scrolling (continuous scrolling at Fit width)"
|
||
|
||
[[feature]]
|
||
id = "view.zoom-fit-height"
|
||
title = "Fit height"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::layouts_fit_height_labels_and_system_theme"]
|
||
notes = "View ▸ Fit to height"
|
||
|
||
[[feature]]
|
||
id = "view.zoom-fit-visible"
|
||
title = "Fit visible content"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.fit_visible"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::fit_visible_zooms_to_the_content_width", "crates/ui-egui/src/zoom_snap.rs::ink_bounds_finds_the_marked_area"]
|
||
notes = "View ▸ Zoom ▸ Fit visible (⌘3): the current page's inked area (found by a quick low-resolution render) spans the window width; respects view rotation; drivable via the control channel's command"
|
||
|
||
[[feature]]
|
||
id = "view.marquee-zoom"
|
||
title = "Marquee zoom tool"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.marquee_zoom"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::marquee_zoom_and_snapshot"]
|
||
notes = "drag to fill the window with an area; click to zoom in"
|
||
|
||
[[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 = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/view.rs::the_hand_tool_pans_by_dragging", "crates/ui-egui/tests/viewer.rs::v_h_and_space_pick_the_quick_tools"]
|
||
notes = "drag to pan; Grab/Grabbing cursors; H picks it, V goes back to Select, Space held pans temporarily"
|
||
|
||
[[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::full_screen_toggles_by_shortcut_and_command_and_escape_leaves"]
|
||
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", "crates/ui-egui/tests/viewer.rs::two_page_view_steps_a_spread_at_a_time", "crates/ui-egui/tests/viewer.rs::arrow_and_page_keys_move_through_a_scrolling_document"]
|
||
notes = "in two-page view, next and previous page move a whole spread (with or without a cover page); → / ← turn pages in every layout, ↓ / ↑ scroll a line and Page Down / Page Up a screen (the page in single-page view), as in Acrobat"
|
||
|
||
[[feature]]
|
||
id = "view.goto-page-label"
|
||
title = "Go to a page by its label (logical page numbers)"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::layouts_fit_height_labels_and_system_theme"]
|
||
notes = "the page box takes labels (iv, A-1) before numbers"
|
||
|
||
[[feature]]
|
||
id = "view.view-history"
|
||
title = "Previous view / next view"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::view_history_select_all_and_find_options"]
|
||
notes = "⌘[ / ⌘] and View ▸ Page navigation"
|
||
|
||
[[feature]]
|
||
id = "view.follow-links"
|
||
title = "Follow links and destinations"
|
||
area = "B"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/view.rs::clicking_a_link_goes_to_its_destination"]
|
||
notes = "GoTo and URI links; other action types (JavaScript, Launch, named actions) arrive with M6"
|
||
|
||
[[feature]]
|
||
id = "view.annotation-hover"
|
||
title = "Show comment text on hover"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/comments.rs::hovering_a_comment_shows_its_author_and_text"]
|
||
notes = "a popup with the author and the comment text, as Acrobat shows"
|
||
|
||
[[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", "crates/ui-egui/tests/viewer.rs::each_document_keeps_its_own_scroll_position"]
|
||
notes = "each tab keeps its own scroll position (#189)"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["edit.find"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::view_history_select_all_and_find_options"]
|
||
notes = "whole words only, case-sensitive"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["edit.advanced_search"]
|
||
tools = ["text_find"]
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::advanced_search_lists_results_with_context"]
|
||
notes = "Search panel (⇧⌘F): every match grouped by page with its context, whole words / case options, click to go; shares the find bar's search (closing the panel moves it to the bar)"
|
||
|
||
[[feature]]
|
||
id = "view.advanced-search"
|
||
title = "Advanced search window"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "partial"
|
||
commands = ["edit.advanced_search"]
|
||
tools = ["text_find"]
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::advanced_search_lists_results_with_context"]
|
||
notes = "in the current document, as a side panel; searching other open documents, folders and indexes is still to come"
|
||
|
||
[[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 = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::view_history_select_all_and_find_options"]
|
||
notes = "⌘A on the current page"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["edit.snapshot"]
|
||
tools = ["page_render"]
|
||
evidence = ["crates/ui-egui/tests/viewer.rs::marquee_zoom_and_snapshot"]
|
||
notes = "copies the area at the current zoom to the clipboard as an image"
|
||
|
||
[[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", "view.theme.light", "view.theme.dark"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/theme.rs::toolbar_and_view_menu_share_three_state_preferences", "crates/ui-egui/src/theme.rs::text_is_readable_on_every_surface"]
|
||
notes = "Toolbar and Display theme share three choices and a preference setter; manual choices exit system mode. Every text colour meets WCAG AA (4.5:1) on every surface in both themes; glyphs are pixel-aligned for sharper text at 100–150 % scaling."
|
||
|
||
[[feature]]
|
||
id = "view.theme-system"
|
||
title = "Follow the system theme"
|
||
area = "B"
|
||
tier = "P1"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = ["view.theme.system"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/theme.rs::toolbar_and_view_menu_share_three_state_preferences", "crates/ui-egui/tests/theme.rs::saved_preferences_restore_with_legacy_light_dark_settings", "crates/ui-egui/tests/control.rs::theme_commands_and_options_report_preference_and_effective_colours"]
|
||
notes = "Toolbar, Display theme and --theme system share a persisted System preference that follows OS changes without losing the selected mode; ui.state reports preference and resolved colours separately."
|
||
|
||
[[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::the_about_dialog_shows_the_running_version"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "view.check-for-updates"
|
||
title = "Help ▸ Check for updates"
|
||
area = "B"
|
||
tier = "P2"
|
||
milestone = "M3"
|
||
status = "partial"
|
||
commands = ["help.check_updates"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/updates.rs::a_newer_release_is_offered_for_download", "crates/ui-egui/tests/updates.rs::nothing_is_asked_until_the_user_checks", "apps/pdfcraft/src/updates.rs::answers_are_read_and_only_our_release_pages_are_offered"]
|
||
notes = "asks GitHub Releases on request and offers the release page. By the owner's decision there is no check at start and no in-place install (Acrobat has both)"
|
||
|
||
[[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 = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/perf.rs::scrolling_a_500_page_document_stays_within_the_frame_budget", "crates/ui-egui/tests/perf.rs::panels_with_hundreds_of_items_stay_within_the_frame_budget"]
|
||
notes = "UI work per frame ~6 ms scrolling 500 pages (comments panel with 500 cards ~6 ms, thumbnails ~2 ms); rendering is off-thread and tiled"
|
||
|
||
# ==================================================================================================
|
||
# C. Content editing
|
||
# ==================================================================================================
|
||
|
||
[[feature]]
|
||
id = "edit.text-edit"
|
||
title = "Edit existing text in place"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "partial"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["text_lines", "text_paragraphs", "text_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::paragraphs_are_found_and_rewrapped", "crates/edit/src/tests.rs::text_lines_are_found_and_replaced_in_place", "crates/ui-egui/tests/editing.rs::editing_existing_text_in_place", "crates/automation/tests/automation.rs::editing_existing_text_through_tools"]
|
||
notes = "click a paragraph box and retype it in place (font reused or Helvetica substituted, colour and spacing kept, rewrapped); drag a box to move it or its right-edge handle to rewrap it (edit.text-move-resize); per-character formatting changes are next"
|
||
|
||
[[feature]]
|
||
id = "edit.text-reflow"
|
||
title = "Reflow edited text inside its paragraph box"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["text_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::paragraphs_are_found_and_rewrapped", "crates/edit/src/tests.rs::text_lines_are_found_and_replaced_in_place", "crates/ui-egui/tests/editing.rs::editing_existing_text_in_place", "crates/automation/tests/automation.rs::editing_existing_text_through_tools"]
|
||
notes = "edited paragraphs are rewrapped to the box width with the font's own advance widths and the original line spacing, starting at the original text matrix; text state and fill colour restored after; a single line grows to the right margin"
|
||
|
||
[[feature]]
|
||
id = "edit.text-move-resize"
|
||
title = "Move and resize text boxes"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["text_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::paragraphs_move_and_rewrap_to_a_new_width", "crates/ui-egui/tests/editing.rs::dragging_a_paragraph_moves_it_and_its_edge_rewraps_it", "crates/automation/tests/automation.rs::editing_existing_text_through_tools"]
|
||
notes = "existing paragraphs: drag the box to move it, drag the handle on its right edge to rewrap it to a new width (upright pages); the move is applied in page space through the paragraph's CTM, in its own q … Q; a click still opens it for typing (#40)"
|
||
|
||
[[feature]]
|
||
id = "edit.paragraph-detection"
|
||
title = "Detect paragraphs and text blocks for editing"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["text_paragraphs"]
|
||
evidence = ["crates/edit/src/tests.rs::paragraphs_are_found_and_rewrapped", "crates/edit/src/tests.rs::text_lines_are_found_and_replaced_in_place", "crates/ui-egui/tests/editing.rs::editing_existing_text_in_place", "crates/automation/tests/automation.rs::editing_existing_text_through_tools"]
|
||
notes = "lines grouped into paragraphs by stream, font, size, left alignment and regular baseline spacing; hyphenated line ends are joined"
|
||
|
||
[[feature]]
|
||
id = "edit.font-reuse-substitution"
|
||
title = "Reuse embedded fonts or substitute with a warning"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["text_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::missing_glyphs_substitute_helvetica_and_impossible_text_is_refused", "crates/fonts/src/pdf.rs::subset_fonts_only_encode_the_glyphs_they_have", "crates/fonts/src/pdf.rs::composite_fonts_use_to_unicode"]
|
||
notes = "the line's font is reused when every character has a code and a glyph (ToUnicode, encodings, Differences; subset glyph checks); otherwise Helvetica (WinAnsi) is used and reported; text no font can show is refused"
|
||
|
||
[[feature]]
|
||
id = "edit.text-format-font"
|
||
title = "Change font, size and colour"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text", "edit.text"]
|
||
tools = ["text_edit", "content_update"]
|
||
evidence = ["crates/edit/src/tests.rs::paragraphs_take_new_formatting", "crates/ui-egui/tests/editing.rs::the_format_panel_restyles_the_paragraph_being_edited", "crates/automation/tests/automation.rs::editing_existing_text_through_tools"]
|
||
notes = "existing paragraphs and added text: font family (Helvetica, Times, Courier) with bold and italic, size, colour; existing paragraphs are rewrapped"
|
||
|
||
[[feature]]
|
||
id = "edit.text-format-style"
|
||
title = "Bold, italic, underline"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["text_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::justify_underline_and_spacing", "crates/edit/src/tests.rs::paragraphs_take_new_formatting", "crates/ui-egui/tests/editing.rs::the_format_panel_restyles_the_paragraph_being_edited"]
|
||
notes = "bold and italic through the standard families; underline drawn under each line in the text colour"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["edit.edit_text", "edit.text"]
|
||
tools = ["text_edit", "content_update"]
|
||
evidence = ["crates/edit/src/tests.rs::justify_underline_and_spacing", "crates/edit/src/tests.rs::paragraphs_take_new_formatting", "crates/ui-egui/tests/editing.rs::the_format_panel_restyles_the_paragraph_being_edited"]
|
||
notes = "left, centre, right and justify (word spacing per line, last line left) for existing paragraphs and added text"
|
||
|
||
[[feature]]
|
||
id = "edit.line-paragraph-spacing"
|
||
title = "Line and paragraph spacing"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "partial"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["text_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::justify_underline_and_spacing", "crates/edit/src/tests.rs::paragraphs_take_new_formatting", "crates/ui-egui/tests/editing.rs::the_format_panel_restyles_the_paragraph_being_edited"]
|
||
notes = "line spacing (1, 1.15, 1.5, 2 × size or any multiple); space between paragraphs is not adjustable yet"
|
||
|
||
[[feature]]
|
||
id = "edit.char-spacing-scale"
|
||
title = "Character spacing and horizontal scaling"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["text_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::justify_underline_and_spacing", "crates/edit/src/tests.rs::paragraphs_take_new_formatting", "crates/ui-egui/tests/editing.rs::the_format_panel_restyles_the_paragraph_being_edited"]
|
||
notes = "character spacing in points and horizontal scaling in percent for a paragraph (Tc, Tz), rewrapped"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["edit.text"]
|
||
tools = ["page_add_text", "content_update"]
|
||
evidence = ["crates/ui-egui/tests/content.rs::typing_moving_styling_and_deleting_added_content", "crates/ui-egui/tests/content.rs::clicking_elsewhere_keeps_the_text_and_starts_a_new_box", "crates/automation/tests/automation.rs::adding_content_through_tools", "crates/edit/src/tests.rs::added_text_and_images_are_page_content_that_stays_editable", "crates/engine/src/tests.rs::added_text_and_images_render_and_stay_editable"]
|
||
notes = "page content, editable later (retype, move, restyle); standard fonts, Windows-1252 text. Each click on the page keeps the box being typed and starts a new one; Done and Discard beside the box"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["edit.image"]
|
||
tools = ["page_add_image"]
|
||
evidence = ["crates/ui-egui/tests/content.rs::typing_moving_styling_and_deleting_added_content", "crates/automation/tests/automation.rs::adding_content_through_tools", "crates/edit/src/tests.rs::added_text_and_images_are_page_content_that_stays_editable", "crates/engine/src/tests.rs::added_text_and_images_render_and_stay_editable"]
|
||
notes = "PNG, JPEG, TIFF, GIF, BMP; centred at natural size or in a rect"
|
||
|
||
[[feature]]
|
||
id = "edit.image-replace"
|
||
title = "Replace an image (keep position and size)"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text", "edit.image"]
|
||
tools = ["image_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::page_images_move_turn_replace_and_delete", "crates/ui-egui/tests/editing.rs::editing_existing_images_on_the_page", "crates/automation/tests/automation.rs::editing_page_images_through_tools"]
|
||
notes = "existing page images (right-click ▸ Replace Image) and added images: the new picture is drawn in the same place"
|
||
|
||
[[feature]]
|
||
id = "edit.image-crop"
|
||
title = "Crop an image"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "partial"
|
||
commands = ["edit.image"]
|
||
tools = ["content_update"]
|
||
evidence = ["crates/engine/src/tests.rs::added_images_rotate_flip_and_crop_as_drawn", "crates/automation/tests/automation.rs::adding_content_through_tools"]
|
||
notes = "for added images: trim each side, clipped"
|
||
|
||
[[feature]]
|
||
id = "edit.image-rotate-flip"
|
||
title = "Rotate and flip images"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["image_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::page_images_move_turn_replace_and_delete", "crates/ui-egui/tests/editing.rs::editing_existing_images_on_the_page", "crates/automation/tests/automation.rs::editing_page_images_through_tools"]
|
||
notes = "existing page images: rotate either way about the centre, flip horizontally or vertically (a q/cm/Q wrapper); added images too"
|
||
|
||
[[feature]]
|
||
id = "edit.image-move-resize"
|
||
title = "Move and resize images"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["image_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::page_images_move_turn_replace_and_delete", "crates/ui-egui/tests/editing.rs::editing_existing_images_on_the_page", "crates/automation/tests/automation.rs::editing_page_images_through_tools"]
|
||
notes = "existing page images: select, drag to move, drag a corner to resize keeping proportions; Delete removes; added images too"
|
||
|
||
[[feature]]
|
||
id = "edit.image-extract"
|
||
title = "Save an image to a file"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["image_save"]
|
||
evidence = ["crates/edit/src/tests.rs::page_images_move_turn_replace_and_delete", "crates/ui-egui/tests/editing.rs::editing_existing_images_on_the_page", "crates/automation/tests/automation.rs::editing_page_images_through_tools", "crates/create/src/tests.rs::images_export_as_jpeg_unchanged_and_others_as_png"]
|
||
notes = "right-click an image ▸ Save Image As: JPEG unchanged, others PNG"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["edit.link"]
|
||
tools = ["link_add"]
|
||
evidence = ["crates/ui-egui/tests/pdf_links.rs::drawing_editing_and_deleting_links", "crates/automation/tests/automation.rs::links_through_tools", "crates/annot/src/tests.rs::links_are_added_edited_listed_and_removed"]
|
||
notes = "drag a rectangle; go to a page or open a web page"
|
||
|
||
[[feature]]
|
||
id = "edit.link-properties"
|
||
title = "Link appearance and properties"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.link"]
|
||
tools = ["link_edit"]
|
||
evidence = ["crates/ui-egui/tests/pdf_links.rs::drawing_editing_and_deleting_links", "crates/automation/tests/automation.rs::links_through_tools", "crates/annot/src/tests.rs::links_are_added_edited_listed_and_removed"]
|
||
notes = "visible/invisible rectangle, highlight, thickness, solid/dashed/underline, colour; action"
|
||
|
||
[[feature]]
|
||
id = "edit.links-from-urls"
|
||
title = "Create links from URLs in the text"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.links_from_urls"]
|
||
tools = ["links_from_urls"]
|
||
evidence = ["crates/engine/src/tests.rs::links_from_urls_and_link_edits", "crates/automation/tests/automation.rs::links_through_tools", "crates/annot/src/tests.rs::urls_are_found_in_text"]
|
||
notes = "http(s):// and www. addresses; already-linked text skipped"
|
||
|
||
[[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 = "partial"
|
||
commands = ["edit.link"]
|
||
tools = ["link_add"]
|
||
evidence = ["crates/annot/src/tests.rs::links_are_added_edited_listed_and_removed", "crates/ui-egui/tests/pdf_links.rs::drawing_editing_and_deleting_links"]
|
||
notes = "go to a page (Fit view); named destinations and zoom pending"
|
||
|
||
[[feature]]
|
||
id = "edit.action-uri"
|
||
title = "Open-a-web-link action"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.link"]
|
||
tools = ["link_add"]
|
||
evidence = ["crates/annot/src/tests.rs::links_are_added_edited_listed_and_removed", "crates/automation/tests/automation.rs::links_through_tools", "crates/ui-egui/tests/document_links.rs::a_link_asks_first_and_opens_when_allowed", "crates/ui-egui/tests/document_links.rs::a_button_uri_action_asks_first"]
|
||
notes = "Opening a document's link or button URI asks first; only http, https and mailto addresses are offered (#90, #91)."
|
||
|
||
[[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 = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/buttons.rs::hide_actions_hide_and_show_fields", "crates/forms/src/tests.rs::push_buttons_read_hide_actions"]
|
||
notes = "A push button's Hide action (/T names, field or widget references, arrays; /H) hides or shows the fields and their kids; hidden widgets take no clicks, tab stops or highlights. A widget reference acts on its whole field, and only form fields are hidden, not other annotations."
|
||
|
||
[[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 = "shipped"
|
||
commands = ["edit.header_footer"]
|
||
tools = ["doc_header_footer"]
|
||
evidence = ["crates/ui-egui/tests/marks.rs::header_and_footer_dialog_inserts_tokens_and_applies_to_a_subset", "crates/edit/src/tests.rs::header_and_footer_are_drawn_in_display_space_and_wrap_the_original_content", "crates/engine/src/tests.rs::headers_footers_watermarks_and_backgrounds_show_update_and_remove"]
|
||
notes = "Helvetica only (no font choice yet); drawn in display space on rotated pages; artifact-tagged"
|
||
|
||
[[feature]]
|
||
id = "edit.header-footer-update-remove"
|
||
title = "Update or remove header and footer"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.header_footer.update", "edit.header_footer.remove"]
|
||
tools = ["doc_header_footer", "doc_remove_marks"]
|
||
evidence = ["crates/edit/src/tests.rs::replace_and_remove_restore_the_original_content", "crates/ui-egui/tests/marks.rs::header_and_footer_dialog_inserts_tokens_and_applies_to_a_subset"]
|
||
notes = "recognises PdfCraft marks; Acrobat-made headers are not detected yet"
|
||
|
||
[[feature]]
|
||
id = "edit.header-footer-tokens"
|
||
title = "Page number and date tokens in header and footer"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.header_footer"]
|
||
tools = ["doc_header_footer"]
|
||
evidence = ["crates/edit/src/tests.rs::tokens_expand", "crates/engine/src/tests.rs::headers_footers_watermarks_and_backgrounds_show_update_and_remove"]
|
||
notes = "<<1>>, <<n>>, <<1 of n>>, <<Page 1 of n>>, dates, Bates"
|
||
|
||
[[feature]]
|
||
id = "edit.header-footer-ranges"
|
||
title = "Header and footer page ranges (all, range, odd, even)"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.header_footer"]
|
||
tools = ["doc_header_footer"]
|
||
evidence = ["crates/ui-egui/src/marks_ui.rs::page_ranges_with_subsets", "crates/ui-egui/tests/marks.rs::header_and_footer_dialog_inserts_tokens_and_applies_to_a_subset"]
|
||
notes = "all / from–to, all / even / odd"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["edit.watermark"]
|
||
tools = ["doc_watermark"]
|
||
evidence = ["crates/edit/src/tests.rs::watermarks_rotate_fade_and_can_go_behind", "crates/ui-egui/tests/marks.rs::watermark_and_background_dialogs", "crates/automation/tests/automation.rs::headers_watermarks_and_backgrounds_through_tools"]
|
||
notes = "Helvetica; fit-to-page or a size"
|
||
|
||
[[feature]]
|
||
id = "edit.watermark-add-file"
|
||
title = "Add a watermark from an image or PDF page"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.watermark"]
|
||
tools = ["doc_watermark"]
|
||
evidence = ["crates/engine/src/tests.rs::backgrounds_and_watermarks_from_files"]
|
||
notes = "an image or a page of a PDF, scaled relative to the page, rotated, with opacity, behind or on top"
|
||
|
||
[[feature]]
|
||
id = "edit.watermark-update-remove"
|
||
title = "Update or remove watermarks"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.watermark.update", "edit.watermark.remove"]
|
||
tools = ["doc_watermark", "doc_remove_marks"]
|
||
evidence = ["crates/edit/src/tests.rs::replace_and_remove_restore_the_original_content", "crates/engine/src/tests.rs::headers_footers_watermarks_and_backgrounds_show_update_and_remove"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "edit.watermark-appearance"
|
||
title = "Watermark rotation, opacity, scale, position, print/screen visibility"
|
||
area = "C"
|
||
tier = "P1"
|
||
milestone = "M7"
|
||
status = "partial"
|
||
commands = ["edit.watermark"]
|
||
tools = ["doc_watermark"]
|
||
evidence = ["crates/edit/src/tests.rs::watermarks_rotate_fade_and_can_go_behind"]
|
||
notes = "rotation, opacity, behind/on top; no position offsets, scale or print/screen-only variants in the UI yet"
|
||
|
||
[[feature]]
|
||
id = "edit.background-add"
|
||
title = "Add a page background (colour or file)"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.background"]
|
||
tools = ["doc_background"]
|
||
evidence = ["crates/ui-egui/tests/marks.rs::watermark_and_background_dialogs", "crates/engine/src/tests.rs::headers_footers_watermarks_and_backgrounds_show_update_and_remove", "crates/engine/src/tests.rs::backgrounds_and_watermarks_from_files", "crates/ui-egui/tests/marks.rs::backgrounds_and_watermarks_from_a_file", "crates/organize/src/tests.rs::a_page_becomes_a_form_xobject_upright"]
|
||
notes = "colour, or a picture from a file (an image or a page of a PDF) scaled relative to the page"
|
||
|
||
[[feature]]
|
||
id = "edit.background-update-remove"
|
||
title = "Update or remove backgrounds"
|
||
area = "C"
|
||
tier = "P0"
|
||
milestone = "M7"
|
||
status = "shipped"
|
||
commands = ["edit.background.update", "edit.background.remove"]
|
||
tools = ["doc_background", "doc_remove_marks"]
|
||
evidence = ["crates/edit/src/tests.rs::replace_and_remove_restore_the_original_content", "crates/engine/src/tests.rs::headers_footers_watermarks_and_backgrounds_show_update_and_remove"]
|
||
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 = "partial"
|
||
commands = ["edit.bates", "edit.header_footer"]
|
||
tools = ["doc_header_footer"]
|
||
evidence = ["crates/edit/src/tests.rs::tokens_expand"]
|
||
notes = "Bates ▸ Add opens Header & Footer with a Bates number; no multi-file batch yet"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["edit.edit_text"]
|
||
tools = ["text_edit"]
|
||
evidence = ["crates/edit/src/tests.rs::paragraphs_take_new_formatting", "crates/ui-egui/tests/editing.rs::the_format_panel_restyles_the_paragraph_being_edited", "crates/automation/tests/automation.rs::editing_existing_text_through_tools"]
|
||
notes = "Edit a PDF ▸ Format text shows the paragraph being edited (family, bold/italic guessed from its font, size) and applies changes at once as one undo step each, keeping the paragraph open"
|
||
|
||
[[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", "crates/ui-egui/tests/editing.rs::organize_select_all_shortcut_preserves_current_page_and_sets_range_anchor", "crates/ui-egui/tests/control.rs::select_all_key_selects_every_page_in_organize"]
|
||
notes = "Ctrl+A / ⌘A selects every page in the Organize grid, preserving the current page; text inputs and dialogs keep their own keyboard input."
|
||
|
||
[[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 = "shipped"
|
||
commands = ["page.rotate_dialog"]
|
||
tools = ["page_rotate"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::extract_options_and_rotate_pages_dialog", "crates/automation/tests/automation.rs::organizing_with_filters_bookmark_splits_and_extract_options", "crates/engine/src/tests.rs::split_by_size_and_bookmarks_and_page_filters"]
|
||
notes = "Rotate Pages: all/selection/range; even/odd; landscape/portrait"
|
||
|
||
[[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 = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/editing.rs::dragging_thumbnails_reorders_pages"]
|
||
notes = "drag thumbnails (or the selection) in the page grid; an insertion bar shows where they go; move earlier/later buttons too"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["page.extract"]
|
||
tools = ["page_extract"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::extract_options_and_rotate_pages_dialog", "crates/automation/tests/automation.rs::organizing_with_filters_bookmark_splits_and_extract_options"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.extract-and-delete"
|
||
title = "Delete pages after extracting"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.extract"]
|
||
tools = ["page_extract"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::extract_options_and_rotate_pages_dialog", "crates/automation/tests/automation.rs::organizing_with_filters_bookmark_splits_and_extract_options"]
|
||
notes = "undoable"
|
||
|
||
[[feature]]
|
||
id = "organize.replace-pages"
|
||
title = "Replace pages with pages from another file"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.replace"]
|
||
tools = ["page_replace"]
|
||
evidence = ["crates/organize/src/tests.rs::replace_pages_swaps_content_but_keeps_annotations", "crates/ui-egui/tests/editing.rs::replace_pages_dialog_swaps_page_content", "crates/automation/tests/automation.rs::replacing_pages_through_tools"]
|
||
notes = "content, resources and boxes replaced; links, comments, fields and bookmarks kept"
|
||
|
||
[[feature]]
|
||
id = "organize.duplicate-pages"
|
||
title = "Duplicate pages"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.duplicate"]
|
||
tools = ["page_duplicate"]
|
||
evidence = ["crates/ui-egui/tests/pageboxes.rs::duplicate_pages_copies_the_current_page", "crates/organize/src/tests.rs::duplicate_pages_inserts_copies_after_the_last"]
|
||
notes = "copies share fonts and images with the originals"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["page.copy", "page.cut", "page.paste"]
|
||
tools = ["page_insert_file"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::copying_cutting_and_pasting_pages"]
|
||
notes = "⌘C/⌘X/⌘V and the thumbnail menu in Organize Pages; paste works across documents"
|
||
|
||
[[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 = "partial"
|
||
commands = ["page.organize"]
|
||
tools = ["page_rotate"]
|
||
evidence = ["crates/engine/src/tests.rs::split_by_size_and_bookmarks_and_page_filters"]
|
||
notes = "Organize ▸ Select: all, odd, even, landscape, portrait; ranges via the page tools"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["page.split"]
|
||
tools = ["doc_split"]
|
||
evidence = ["crates/automation/tests/automation.rs::organizing_with_filters_bookmark_splits_and_extract_options", "crates/engine/src/tests.rs::split_by_size_and_bookmarks_and_page_filters"]
|
||
notes = "greedy by single-page size (an over-estimate: parts stay under the limit)"
|
||
|
||
[[feature]]
|
||
id = "organize.split-by-bookmarks"
|
||
title = "Split by top-level bookmarks"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.split"]
|
||
tools = ["doc_split"]
|
||
evidence = ["crates/automation/tests/automation.rs::organizing_with_filters_bookmark_splits_and_extract_options", "crates/engine/src/tests.rs::split_by_size_and_bookmarks_and_page_filters"]
|
||
notes = "files named after the bookmarks"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["page.combine"]
|
||
tools = ["doc_combine"]
|
||
evidence = ["crates/ui-egui/tests/editing.rs::combine_files_takes_chosen_pages_in_the_order_listed", "crates/automation/tests/automation.rs::combine_extract_and_split"]
|
||
notes = "Combine files list: order (move up/down), remove, add files, a page range per file (such as 1-3, 6; in the order given); bookmarks to left-out pages lose their destination"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["page.number"]
|
||
tools = ["page_number"]
|
||
evidence = ["crates/organize/src/tests.rs::number_pages_like_acrobat", "crates/engine/src/tests.rs::number_pages_shows_in_the_viewer_and_undoes", "crates/ui-egui/tests/bookmarks.rs::number_pages_dialog_labels_the_selected_pages", "crates/automation/tests/automation.rs::numbering_pages_through_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.page-boxes"
|
||
title = "Set page boxes (crop, trim, bleed, art)"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.boxes"]
|
||
tools = ["page_set_box"]
|
||
evidence = ["crates/ui-egui/tests/pageboxes.rs::set_page_boxes_dialog_applies_margins_to_the_chosen_pages", "crates/organize/src/tests.rs::page_boxes_default_inherit_and_set"]
|
||
notes = "crop/trim/bleed/art/media, margins in pt/in/mm, page ranges, preview; no Remove white margins yet"
|
||
|
||
[[feature]]
|
||
id = "organize.crop-tool"
|
||
title = "Crop pages by dragging a rectangle"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["page.crop"]
|
||
tools = ["page_set_box"]
|
||
evidence = ["crates/ui-egui/tests/pageboxes.rs::the_crop_tool_crops_to_the_dragged_rectangle_then_returns_to_select"]
|
||
notes = "drag to crop; double-click opens Set Page Boxes"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["bookmark.add"]
|
||
tools = ["bookmark_add"]
|
||
evidence = ["crates/ui-egui/tests/bookmarks.rs::new_rename_reorder_indent_delete_and_undo", "crates/organize/src/tests.rs::bookmarks_add_rename_move_delete", "crates/engine/src/tests.rs::bookmark_edits_show_in_the_viewer_undo_and_save", "crates/automation/tests/automation.rs::bookmarks_through_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.bookmark-rename"
|
||
title = "Rename bookmarks"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["bookmark_rename"]
|
||
evidence = ["crates/ui-egui/tests/bookmarks.rs::new_rename_reorder_indent_delete_and_undo", "crates/ui-egui/tests/bookmarks.rs::escape_cancels_a_rename", "crates/organize/src/tests.rs::bookmarks_add_rename_move_delete", "crates/organize/src/tests.rs::bookmark_edits_keep_unknown_keys_and_touch_few_objects"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.bookmark-delete"
|
||
title = "Delete bookmarks"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["bookmark_delete"]
|
||
evidence = ["crates/ui-egui/tests/bookmarks.rs::new_rename_reorder_indent_delete_and_undo", "crates/organize/src/tests.rs::bookmarks_add_rename_move_delete", "crates/engine/src/tests.rs::bookmark_edits_show_in_the_viewer_undo_and_save"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.bookmark-move"
|
||
title = "Reorder and nest bookmarks"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["bookmark_move"]
|
||
evidence = ["crates/ui-egui/tests/bookmarks.rs::new_rename_reorder_indent_delete_and_undo", "crates/organize/src/tests.rs::bookmarks_add_rename_move_delete", "crates/engine/src/tests.rs::bookmark_edits_show_in_the_viewer_undo_and_save", "crates/automation/tests/automation.rs::bookmarks_through_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.bookmark-set-destination"
|
||
title = "Set a bookmark's destination"
|
||
area = "D"
|
||
tier = "P0"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["bookmark_set_page"]
|
||
evidence = ["crates/organize/src/tests.rs::bookmarks_add_rename_move_delete", "crates/engine/src/tests.rs::bookmark_edits_show_in_the_viewer_undo_and_save", "crates/automation/tests/automation.rs::bookmarks_through_tools"]
|
||
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 = "shipped"
|
||
commands = ["file.properties"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/bookmarks.rs::expanding_and_collapsing_all_bookmarks"]
|
||
notes = "Bookmarks panel options: expand all, expand top-level, collapse all"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["file.properties"]
|
||
tools = ["doc_info"]
|
||
evidence = ["crates/ui-egui/tests/view.rs::fonts_tab_lists_fonts_and_embedding"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "organize.initial-view"
|
||
title = "Edit initial view (layout, magnification, panels, window options)"
|
||
area = "D"
|
||
tier = "P1"
|
||
milestone = "M4"
|
||
status = "shipped"
|
||
commands = ["file.properties"]
|
||
tools = ["doc_initial_view"]
|
||
evidence = ["crates/organize/src/tests.rs::initial_view_round_trips_and_keeps_scripts", "crates/ui-egui/tests/viewer.rs::initial_view_is_edited_and_honoured_on_open", "crates/automation/tests/automation.rs::initial_view_through_tools"]
|
||
notes = "navigation panel, layout, magnification, page, window and interface options; honoured on open; script open actions kept"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["file.properties"]
|
||
tools = ["doc_initial_view"]
|
||
evidence = ["crates/organize/src/tests.rs::initial_view_round_trips_and_keeps_scripts", "crates/automation/tests/automation.rs::initial_view_through_tools"]
|
||
notes = "Advanced ▸ Reading Options: binding and language"
|
||
|
||
[[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 = ["comment_list"]
|
||
evidence = ["crates/ui-egui/tests/ui.rs::opening_a_pdf_shows_comments_and_bookmarks", "crates/ui-egui/tests/comments.rs::the_panel_posts_comments_and_replies"]
|
||
notes = "cards grouped by page with replies, status badges, selection, reply box and an Add a comment box"
|
||
|
||
[[feature]]
|
||
id = "comment.appearance-generation"
|
||
title = "Generate correct appearances for every markup type"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "partial"
|
||
commands = []
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::every_tool_creates_a_drawable_comment", "crates/engine/src/tests.rs::highlight_multiplies_over_text", "crates/annot/src/tests.rs::polygons_clouds_connected_lines_callouts_and_carets_are_drawn"]
|
||
notes = "appearances for every type PdfCraft creates (highlight multiplies; clouds, callouts, stamps, polygons, carets); rich text not yet"
|
||
|
||
[[feature]]
|
||
id = "comment.sticky-note"
|
||
title = "Sticky note comments"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.note"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::sticky_note_composer_posts_and_returns_to_select", "crates/annot/src/tests.rs::every_tool_creates_a_drawable_comment"]
|
||
notes = "our own Comment/Note icon drawings, with a pop-up"
|
||
|
||
[[feature]]
|
||
id = "comment.highlight"
|
||
title = "Highlight text"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.highlight"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::dragging_over_text_with_the_highlighter_highlights_it", "crates/engine/src/tests.rs::highlight_multiplies_over_text"]
|
||
notes = "drag over text, or pick the tool with text selected; agents can highlight by text search"
|
||
|
||
[[feature]]
|
||
id = "comment.underline"
|
||
title = "Underline text"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.underline"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::every_tool_creates_a_drawable_comment", "crates/ui-egui/tests/comments.rs::a_selection_made_first_is_marked_when_a_tool_is_picked"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.strikeout"
|
||
title = "Strikethrough text"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.strikeout"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::a_selection_made_first_is_marked_when_a_tool_is_picked", "crates/annot/src/tests.rs::every_tool_creates_a_drawable_comment"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.squiggly"
|
||
title = "Squiggly underline"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.squiggly"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::every_tool_creates_a_drawable_comment", "crates/ui-egui/tests/comments.rs::the_squiggly_tool_marks_selected_text"]
|
||
notes = "Highlight ▸ Squiggly underline (with Underline and Strikethrough), the agent tool and the engine"
|
||
|
||
[[feature]]
|
||
id = "comment.replace-text"
|
||
title = "Replace-text proposal (strikeout + caret)"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.replace"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::replace_text_groups_a_strikeout_and_a_caret", "crates/ui-egui/tests/comments.rs::replacing_text_strikes_it_and_adds_a_caret", "crates/automation/tests/automation.rs::drawing_comments_through_tools"]
|
||
notes = "strikeout (/IT StrikeOutTextEdit) and a grouped caret (/RT /Group) with the replacement"
|
||
|
||
[[feature]]
|
||
id = "comment.insert-text"
|
||
title = "Insert-text proposal (caret)"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "partial"
|
||
commands = ["comment.caret"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::polygons_clouds_connected_lines_callouts_and_carets_are_drawn", "crates/ui-egui/tests/comments.rs::polygons_clouds_connected_lines_callouts_and_inserted_text", "crates/automation/tests/automation.rs::drawing_comments_through_tools"]
|
||
notes = "caret at a click; not yet snapped to the text insertion point"
|
||
|
||
[[feature]]
|
||
id = "comment.typewriter"
|
||
title = "Add text comment (typewriter)"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["sign.fill.text"]
|
||
tools = ["fill_sign_add"]
|
||
evidence = ["crates/annot/src/tests.rs::fill_and_sign_items_are_drawn", "crates/ui-egui/tests/fill_sign.rs::text_marks_and_date"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.text-box"
|
||
title = "Text box comment with rich text"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "partial"
|
||
commands = ["comment.freetext"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::sticky_note_composer_posts_and_returns_to_select", "crates/annot/src/tests.rs::text_box_text_and_colour_changes_are_drawn"]
|
||
notes = "Helvetica text with colour, size and alignment; /RC (XHTML) and /DS are written and kept in step with edits; per-run formatting (bold/italic spans) is not yet"
|
||
|
||
[[feature]]
|
||
id = "comment.callout"
|
||
title = "Callout comment"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.callout"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::polygons_clouds_connected_lines_callouts_and_carets_are_drawn", "crates/annot/src/tests.rs::editing_a_callout_refits_the_box_and_keeps_the_leader", "crates/ui-egui/tests/comments.rs::polygons_clouds_connected_lines_callouts_and_inserted_text", "crates/automation/tests/automation.rs::drawing_comments_through_tools"]
|
||
notes = "drag from the target to the box; resizing re-attaches the leader, and so does editing the text"
|
||
|
||
[[feature]]
|
||
id = "comment.pencil"
|
||
title = "Freehand drawing (ink)"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.ink"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::shapes_and_ink_are_drawn_by_dragging"]
|
||
notes = "one ink annotation per stroke"
|
||
|
||
[[feature]]
|
||
id = "comment.eraser"
|
||
title = "Eraser for ink strokes"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.eraser"]
|
||
tools = []
|
||
evidence = ["crates/annot/src/tests.rs::the_eraser_cuts_strokes_and_removes_empty_drawings", "crates/ui-egui/tests/comments.rs::erasing_part_of_a_drawing"]
|
||
notes = "cuts strokes where the eraser passes; an emptied drawing is deleted; one undo step"
|
||
|
||
[[feature]]
|
||
id = "comment.line"
|
||
title = "Line"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.line"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::every_tool_creates_a_drawable_comment"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.arrow"
|
||
title = "Arrow"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.arrow"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::shapes_and_ink_are_drawn_by_dragging"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.rectangle"
|
||
title = "Rectangle"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.square"]
|
||
tools = ["comment_add", "comment_edit"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::shapes_and_ink_are_drawn_by_dragging", "crates/annot/src/tests.rs::restyle_and_resize_redraw_the_appearance"]
|
||
notes = "resize with the 8 handles"
|
||
|
||
[[feature]]
|
||
id = "comment.oval"
|
||
title = "Oval"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.circle"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::every_tool_creates_a_drawable_comment"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.polygon"
|
||
title = "Polygon"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.polygon"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::polygons_clouds_connected_lines_callouts_and_carets_are_drawn", "crates/ui-egui/tests/comments.rs::polygons_clouds_connected_lines_callouts_and_inserted_text", "crates/automation/tests/automation.rs::drawing_comments_through_tools"]
|
||
notes = "click the corners; Enter, double-click or the first point finishes"
|
||
|
||
[[feature]]
|
||
id = "comment.polyline"
|
||
title = "Connected lines (polyline)"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.polyline"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::polygons_clouds_connected_lines_callouts_and_carets_are_drawn", "crates/ui-egui/tests/comments.rs::polygons_clouds_connected_lines_callouts_and_inserted_text", "crates/automation/tests/automation.rs::drawing_comments_through_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.cloud"
|
||
title = "Cloud shape"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.cloud"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::polygons_clouds_connected_lines_callouts_and_carets_are_drawn", "crates/annot/src/tests.rs::cloud_paths_bulge_outwards_either_way_round", "crates/ui-egui/tests/comments.rs::polygons_clouds_connected_lines_callouts_and_inserted_text", "crates/automation/tests/automation.rs::drawing_comments_through_tools"]
|
||
notes = "polygon with a cloudy border (/BE /S /C, /IT /PolygonCloud)"
|
||
|
||
[[feature]]
|
||
id = "comment.area-highlight"
|
||
title = "Area highlight"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.highlight"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::highlighting_an_area_off_the_text"]
|
||
notes = "drag the highlighter off text to highlight a box"
|
||
|
||
[[feature]]
|
||
id = "comment.attach-file-comment"
|
||
title = "Attach a file as a comment"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.attach"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::files_attach_as_comments", "crates/ui-egui/tests/comments.rs::attaching_a_file_as_a_comment", "crates/automation/tests/automation.rs::drawing_comments_through_tools"]
|
||
notes = "push pin, paperclip, graph or tag icon; the file is embedded and listed in the Attachments panel"
|
||
|
||
[[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 = "partial"
|
||
commands = []
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::every_tool_creates_a_drawable_comment"]
|
||
notes = "None, OpenArrow and ClosedArrow are drawn; other endings are kept but not redrawn"
|
||
|
||
[[feature]]
|
||
id = "comment.properties-dialog"
|
||
title = "Comment properties (appearance, author, subject)"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.list"]
|
||
tools = ["comment_edit"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::comment_properties_change_appearance_and_author", "crates/annot/src/tests.rs::author_subject_and_note_icon_change"]
|
||
notes = "Appearance (colour, opacity, thickness, note icon), General (author, subject), Review History"
|
||
|
||
[[feature]]
|
||
id = "comment.properties-bar"
|
||
title = "Comment properties bar"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.note"]
|
||
tools = ["comment_edit"]
|
||
evidence = ["crates/annot/src/tests.rs::restyle_and_resize_redraw_the_appearance", "crates/ui-egui/tests/comments.rs::the_opacity_set_for_a_tool_goes_into_its_new_comments"]
|
||
notes = "quick-bar colour swatches, opacity (10–100 %) and line thickness per tool; colour from the context menu"
|
||
|
||
[[feature]]
|
||
id = "comment.lock-comment"
|
||
title = "Lock comments"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["comment_lock"]
|
||
evidence = ["crates/engine/src/tests.rs::comments_lock_take_checkmarks_hide_and_summarize", "crates/ui-egui/tests/comments.rs::comments_take_checkmarks_lock_hide_and_summarize", "crates/automation/tests/automation.rs::comment_checkmarks_locks_hiding_and_summaries_through_tools"]
|
||
notes = "Comment Properties ▸ Locked; locked comments refuse move, resize, restyle and delete; text stays editable"
|
||
|
||
[[feature]]
|
||
id = "comment.default-properties"
|
||
title = "Make current properties the tool default"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.list"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/comments.rs::make_current_properties_default"]
|
||
notes = "colour, opacity and line width become the tool's defaults (canvas and Comments panel menus)"
|
||
|
||
[[feature]]
|
||
id = "comment.author-identity"
|
||
title = "Author name from identity preferences"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::dragging_over_text_with_the_highlighter_highlights_it", "crates/ui-egui/tests/identity.rs::the_identity_name_signs_new_comments_and_is_remembered"]
|
||
notes = "Preferences ▸ Identity: the name on new comments (default the login name, as Acrobat), remembered across restarts"
|
||
|
||
[[feature]]
|
||
id = "comment.reply"
|
||
title = "Reply to comments"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.list"]
|
||
tools = ["comment_reply"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::the_panel_posts_comments_and_replies", "crates/annot/src/tests.rs::replies_and_status_thread_onto_the_parent"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "comment.set-status"
|
||
title = "Set review status (accepted, rejected, cancelled, completed)"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.list"]
|
||
tools = ["comment_set_status"]
|
||
evidence = ["crates/annot/src/tests.rs::replies_and_status_thread_onto_the_parent", "crates/engine/src/tests.rs::comments_are_added_drawn_threaded_and_undone"]
|
||
notes = "status replies (/State, /StateModel Review), shown as a badge"
|
||
|
||
[[feature]]
|
||
id = "comment.checkmark"
|
||
title = "Mark comments with a checkmark"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["comment_mark"]
|
||
evidence = ["crates/engine/src/tests.rs::comments_lock_take_checkmarks_hide_and_summarize", "crates/ui-egui/tests/comments.rs::comments_take_checkmarks_lock_hide_and_summarize", "crates/automation/tests/automation.rs::comment_checkmarks_locks_hiding_and_summaries_through_tools"]
|
||
notes = "hidden /StateModel /Marked reply; card toggle and context menu"
|
||
|
||
[[feature]]
|
||
id = "comment.delete-comment"
|
||
title = "Delete comments"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.list"]
|
||
tools = ["comment_delete"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::comments_are_selected_moved_and_deleted_with_the_select_tool", "crates/annot/src/tests.rs::delete_takes_popup_and_reply_chain"]
|
||
notes = "deletes the pop-up and the reply thread"
|
||
|
||
[[feature]]
|
||
id = "comment.edit-comment-text"
|
||
title = "Edit comment text"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.list"]
|
||
tools = ["comment_edit"]
|
||
evidence = ["crates/annot/src/tests.rs::text_box_text_and_colour_changes_are_drawn", "crates/annot/src/tests.rs::editing_a_text_box_refits_its_rectangle", "crates/automation/tests/automation.rs::comments_through_tools"]
|
||
notes = "double-click a comment or card, or Edit in the menus; a text box's rectangle follows the new text (wrap width and top edge stay, the height fits the wrapped lines)"
|
||
|
||
[[feature]]
|
||
id = "comment.copy-comment-text"
|
||
title = "Copy comment text"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/comments.rs::comments_take_checkmarks_lock_hide_and_summarize"]
|
||
notes = "card and canvas context menus"
|
||
|
||
[[feature]]
|
||
id = "comment.filter-comments"
|
||
title = "Filter comments (type, author, status, colour, checkmark)"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.list"]
|
||
tools = ["comment_list"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::the_panel_filters_and_sorts", "crates/ui-egui/tests/comments.rs::the_panel_filters_by_colour_and_checkmark"]
|
||
notes = "type, author, status, colour and checkmark"
|
||
|
||
[[feature]]
|
||
id = "comment.sort-comments"
|
||
title = "Sort comments (page, author, date, type, colour)"
|
||
area = "E"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.list"]
|
||
tools = ["comment_list"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::the_panel_filters_and_sorts"]
|
||
notes = "page, author, date, type, colour, with group headers"
|
||
|
||
[[feature]]
|
||
id = "comment.search-comments"
|
||
title = "Search comments"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "partial"
|
||
commands = ["comment.list"]
|
||
tools = ["comment_list"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::the_panel_posts_comments_and_replies"]
|
||
notes = "search by author, text and type in the panel; not yet in the Find bar"
|
||
|
||
[[feature]]
|
||
id = "comment.show-hide-comments"
|
||
title = "Show or hide all comments and pop-ups"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "partial"
|
||
commands = ["comment.hide_all"]
|
||
tools = ["comments_hide"]
|
||
evidence = ["crates/engine/src/tests.rs::comments_lock_take_checkmarks_hide_and_summarize", "crates/ui-egui/tests/comments.rs::comments_take_checkmarks_lock_hide_and_summarize", "crates/automation/tests/automation.rs::comment_checkmarks_locks_hiding_and_summaries_through_tools"]
|
||
notes = "hide/show all comments (fields and links still draw); per-type hiding not yet"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["comment.list"]
|
||
tools = ["comment_list"]
|
||
evidence = ["crates/ui-egui/tests/comments.rs::comment_properties_change_appearance_and_author"]
|
||
notes = "status changes listed in Comment Properties"
|
||
|
||
[[feature]]
|
||
id = "comment.stamps-standard"
|
||
title = "Standard business stamps"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.stamp"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::stamps_draw_their_label_and_by_line", "crates/ui-egui/tests/stamps.rs::choosing_and_placing_stamps", "crates/automation/tests/automation.rs::stamps_through_tools"]
|
||
notes = "12 standard business stamps in PdfCraft's own design; ISO 32000 stamp names where defined"
|
||
|
||
[[feature]]
|
||
id = "comment.stamps-sign-here"
|
||
title = "Sign-here stamps"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.stamp"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::stamps_draw_their_label_and_by_line", "crates/ui-egui/tests/stamps.rs::choosing_and_placing_stamps", "crates/automation/tests/automation.rs::stamps_through_tools"]
|
||
notes = "Accepted, Initial Here, Rejected, Sign Here, Witness as pointed tags"
|
||
|
||
[[feature]]
|
||
id = "comment.stamps-dynamic"
|
||
title = "Dynamic stamps (name and date)"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["comment.stamp"]
|
||
tools = ["comment_add"]
|
||
evidence = ["crates/annot/src/tests.rs::stamps_draw_their_label_and_by_line", "crates/ui-egui/tests/stamps.rs::choosing_and_placing_stamps", "crates/automation/tests/automation.rs::stamps_through_tools"]
|
||
notes = "Approved, Confidential, Received, Reviewed, Revised with a By author at time, date line (local time)"
|
||
|
||
[[feature]]
|
||
id = "comment.stamps-custom"
|
||
title = "Custom stamps from images or PDF pages"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.stamp"]
|
||
tools = ["stamp_custom"]
|
||
evidence = ["crates/ui-egui/tests/stamps.rs::creating_placing_and_keeping_custom_stamps", "crates/automation/tests/automation.rs::stamps_through_tools"]
|
||
notes = "Create custom stamp from a PDF page or an image (category and name), kept in the app's settings; placed at natural size (at most 200 pt) keeping its shape; resizable (appearance redrawn from /PCPicture); delete from the palette"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["comment.import"]
|
||
tools = ["doc_import_data"]
|
||
evidence = ["crates/xfdf/src/tests.rs::xfdf_round_trips_comments_and_values", "crates/xfdf/src/tests.rs::fdf_round_trips_values_and_comments", "crates/automation/tests/automation.rs::comments_and_form_data_travel_as_xfdf_fdf_and_text"]
|
||
notes = "replies, pop-ups, ink, quads, line ends; same /NM replaces"
|
||
|
||
[[feature]]
|
||
id = "comment.export-fdf-xfdf"
|
||
title = "Export all comments to FDF/XFDF"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.export"]
|
||
tools = ["doc_export_data"]
|
||
evidence = ["crates/xfdf/src/tests.rs::xfdf_round_trips_comments_and_values", "crates/xfdf/src/tests.rs::fdf_round_trips_values_and_comments", "crates/automation/tests/automation.rs::comments_and_form_data_travel_as_xfdf_fdf_and_text"]
|
||
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 = "partial"
|
||
commands = ["comment.summarize"]
|
||
tools = ["comments_summarize"]
|
||
evidence = ["crates/engine/src/tests.rs::comments_lock_take_checkmarks_hide_and_summarize", "crates/ui-egui/tests/comments.rs::comments_take_checkmarks_lock_hide_and_summarize", "crates/automation/tests/automation.rs::comment_checkmarks_locks_hiding_and_summaries_through_tools"]
|
||
notes = "Comments only layout, sorted by page, author, date or type; the layouts with the document and connector lines not yet"
|
||
|
||
[[feature]]
|
||
id = "comment.flatten"
|
||
title = "Flatten comments into page content"
|
||
area = "E"
|
||
tier = "P1"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["comment.flatten"]
|
||
tools = ["doc_flatten"]
|
||
evidence = ["crates/edit/src/tests.rs::flattening_draws_appearances_into_the_page_and_removes_the_comments", "crates/engine/src/tests.rs::flattening_keeps_the_look_and_drops_the_objects", "crates/automation/tests/automation.rs::flattening_through_tools"]
|
||
notes = "appearances become page content; pop-ups and replies removed"
|
||
|
||
[[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 = "partial"
|
||
commands = ["measure.distance"]
|
||
tools = ["measure_distance"]
|
||
evidence = ["crates/automation/tests/automation.rs::measurements_calibrate_draw_save_reopen_and_export", "crates/measure/src/tests.rs::calibrated_lengths_concave_areas_and_axis_scales", "crates/ui-egui/tests/control.rs::measurement_tools_draw_live_calibrate_save_and_export"]
|
||
notes = "Two-point distance annotations with saved numeric captions and rectilinear scales. Partial: only single-unit decimal formats; compound (ft-in) and fractional formats are not supported (listed as unsupported, data kept), no leader lines or caption placement options, geospatial scales unsupported, and interop with Acrobat-authored measurements (reading theirs, Acrobat reading ours) is untested."
|
||
|
||
[[feature]]
|
||
id = "comment.measure-perimeter"
|
||
title = "Perimeter measuring tool"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["measure.perimeter"]
|
||
tools = ["measure_perimeter"]
|
||
evidence = ["crates/automation/tests/automation.rs::measurements_calibrate_draw_save_reopen_and_export", "crates/measure/src/tests.rs::calibrated_lengths_concave_areas_and_axis_scales", "crates/ui-egui/tests/control.rs::measurement_tools_draw_live_calibrate_save_and_export"]
|
||
notes = "Connected-line lengths with click vertices and double-click/Enter finish. Repeat the first point to include a closing edge. Saved standard PolyLineDimension annotations."
|
||
|
||
[[feature]]
|
||
id = "comment.measure-area"
|
||
title = "Area measuring tool"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["measure.area"]
|
||
tools = ["measure_area"]
|
||
evidence = ["crates/automation/tests/automation.rs::measurements_calibrate_draw_save_reopen_and_export", "crates/measure/src/tests.rs::calibrated_lengths_concave_areas_and_axis_scales", "crates/ui-egui/tests/control.rs::measurement_tools_draw_live_calibrate_save_and_export"]
|
||
notes = "Simple polygon areas, including concave boundaries and either winding. Self-intersecting boundaries are refused. Saved PolygonDimension annotations; no 3D surface measurement."
|
||
|
||
[[feature]]
|
||
id = "comment.measure-scale"
|
||
title = "Scale ratio and calibration per viewport"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "partial"
|
||
commands = ["measure.scale"]
|
||
tools = ["measure_scale"]
|
||
evidence = ["crates/measure/src/tests.rs::viewport_precedence_first_point_user_unit_and_roundtrip", "crates/measure/src/tests.rs::coordinates_roundtrip_for_crop_rotation_and_user_unit", "crates/automation/tests/automation.rs::measurements_calibrate_draw_save_reopen_and_export", "crates/ui-egui/tests/control.rs::measurement_tools_draw_live_calibrate_save_and_export"]
|
||
notes = "Persistent rectangular viewports with last-containing-viewport precedence, numeric scale and two-point calibration. First vertex selects the scale; prior annotations retain theirs. Partial: single-unit decimal rectilinear scales only; compound (ft-in), fractional and differing distance units can't be authored or imported, no viewport editing/removal UI, and interop with Acrobat's scale ratio dialog and its saved viewports is untested."
|
||
|
||
[[feature]]
|
||
id = "comment.measure-snap"
|
||
title = "Snap to paths, endpoints, midpoints and intersections"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["measure.snap"]
|
||
tools = ["measure_snap"]
|
||
evidence = ["crates/automation/tests/automation.rs::measurement_snap_tool_covers_all_targets", "crates/measure/src/tests.rs::nested_forms_transforms_curves_and_cycles_are_bounded", "crates/measure/src/tests.rs::snap_paths_endpoints_midpoints_and_intersections"]
|
||
notes = "Paths, endpoints, midpoints and intersections of painted vector geometry, including transformed Form XObjects. Cubics flattened to 0.2 user units. Bounded extraction and dense-intersection limits are reported. Raster/text outlines, clipping and optional-content visibility are not interpreted."
|
||
|
||
[[feature]]
|
||
id = "comment.measure-info-panel"
|
||
title = "Measurement info panel"
|
||
area = "E"
|
||
tier = "P2"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["measure.info"]
|
||
tools = ["measure_info", "measure_list"]
|
||
evidence = ["crates/automation/tests/automation.rs::measurements_calibrate_draw_save_reopen_and_export", "crates/ui-egui/tests/control.rs::measurement_tools_draw_live_calibrate_save_and_export"]
|
||
notes = "Live value, units, X/Y deltas, angle and selected scale while drawing; saved measurements can be selected from the panel. Rectilinear 2D readings only."
|
||
|
||
[[feature]]
|
||
id = "comment.measure-export"
|
||
title = "Export measurements as CSV"
|
||
area = "E"
|
||
tier = "P3"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["measure.export"]
|
||
tools = ["measure_export"]
|
||
evidence = ["crates/automation/tests/automation.rs::measurements_calibrate_draw_save_reopen_and_export", "crates/measure/src/tests.rs::measurement_csv_quotes_newlines_and_blocks_formula_cells", "crates/ui-egui/tests/control.rs::measurement_tools_draw_live_calibrate_save_and_export"]
|
||
notes = "Atomic CSV export of page, annotation index, type, numeric value, unit, label, author and scale. Quotes embedded delimiters and prefixes spreadsheet formula triggers."
|
||
|
||
[[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 = "shipped"
|
||
commands = ["form.fields"]
|
||
tools = ["doc_info"]
|
||
evidence = ["crates/ui-egui/tests/view.rs::field_list_jumps_to_a_fields_page"]
|
||
notes = "read-only list with jump-to-field; filling and editing are separate features (M6)"
|
||
|
||
[[feature]]
|
||
id = "form.highlight-fields"
|
||
title = "Highlight existing fields"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M3"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/view.rs::highlight_fields_tints_the_field_area"]
|
||
notes = ""
|
||
|
||
[[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 = "shipped"
|
||
commands = ["form.fields"]
|
||
tools = ["form_fill", "form_fields"]
|
||
evidence = ["crates/ui-egui/tests/forms.rs::typing_into_a_text_field_and_tabbing_on", "crates/forms/src/tests.rs::text_fields_get_new_appearances", "crates/engine/src/tests.rs::filling_a_form_shows_saves_and_undoes"]
|
||
notes = "in-place editor; Enter or clicking away commits, Escape cancels; no format/keystroke JavaScript yet (M6.4)"
|
||
|
||
[[feature]]
|
||
id = "form.fill-checkbox-radio"
|
||
title = "Toggle checkboxes and radio buttons (radios in unison)"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.fields"]
|
||
tools = ["form_fill"]
|
||
evidence = ["crates/ui-egui/tests/forms.rs::check_boxes_radios_and_choices", "crates/forms/src/tests.rs::check_boxes_and_radios_switch_states"]
|
||
notes = "NoToggleToOff honoured; widgets without appearances get our own"
|
||
|
||
[[feature]]
|
||
id = "form.fill-choice"
|
||
title = "Choose from combo boxes and list boxes"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.fields"]
|
||
tools = ["form_fill"]
|
||
evidence = ["crates/ui-egui/tests/forms.rs::check_boxes_radios_and_choices", "crates/forms/src/tests.rs::choices_accept_exports_or_display_text"]
|
||
notes = "combo and list boxes, multi-select lists; editable combo boxes accept free text"
|
||
|
||
[[feature]]
|
||
id = "form.push-buttons"
|
||
title = "Push buttons run their actions"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "partial"
|
||
commands = ["form.fields"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/buttons.rs::buttons_reset_navigate_and_print", "crates/forms/src/af.rs::common_button_scripts_are_recognised", "crates/ui-egui/tests/javascript.rs::button_scripts_run_and_alert"]
|
||
notes = "Reset form, Named (print, page navigation), URI, Go to, and any JavaScript (run in the sandboxed engine); submit explained, not sent"
|
||
|
||
[[feature]]
|
||
id = "form.field-navigation"
|
||
title = "Tab between fields"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.fields"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/forms.rs::tabbing_into_a_filled_field_selects_it_so_typing_replaces"]
|
||
notes = "Tab / Shift+Tab move between text and choice fields in field order; entering with Tab selects the value"
|
||
|
||
[[feature]]
|
||
id = "form.appearance-regeneration"
|
||
title = "Regenerate field appearances (auto-size, comb, multiline, password)"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "partial"
|
||
commands = ["form.fields"]
|
||
tools = ["form_fill"]
|
||
evidence = ["crates/forms/src/tests.rs::text_fields_get_new_appearances", "crates/forms/src/tests.rs::choices_accept_exports_or_display_text"]
|
||
notes = "auto-size, comb, multiline, password, quadding, /MK colours; approximate Helvetica metrics, no /MK rotation yet"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/forms/src/af.rs::numbers_format_like_acrobat", "crates/forms/src/tests.rs::formats_validation_and_calculations_run_like_acrobat", "crates/automation/tests/automation.rs::form_formats_and_calculations_through_tools"]
|
||
notes = "AFNumber_Format/Keystroke run natively: decimals, 5 separator styles, currency before/after, negative styles (red shown as minus/parentheses)"
|
||
|
||
[[feature]]
|
||
id = "form.af-percent"
|
||
title = "Percentage formatting"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/forms/src/af.rs::numbers_format_like_acrobat"]
|
||
notes = "AFPercent_Format/Keystroke"
|
||
|
||
[[feature]]
|
||
id = "form.af-date-time"
|
||
title = "Date and time formatting and parsing"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/forms/src/af.rs::dates_times_and_masks", "crates/forms/src/tests.rs::formats_validation_and_calculations_run_like_acrobat"]
|
||
notes = "AFDate_FormatEx/KeystrokeEx, AFDate_Format presets, AFTime_Format(Ex): lenient parsing, month and day names, impossible dates refused"
|
||
|
||
[[feature]]
|
||
id = "form.af-special"
|
||
title = "Special formats (zip, phone, SSN, arbitrary mask)"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/forms/src/af.rs::dates_times_and_masks"]
|
||
notes = "zip, zip+4, phone, SSN, arbitrary masks (9 A O X)"
|
||
|
||
[[feature]]
|
||
id = "form.af-range-validate"
|
||
title = "Range validation"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/forms/src/af.rs::validation_and_calculation", "crates/forms/src/tests.rs::formats_validation_and_calculations_run_like_acrobat", "crates/automation/tests/automation.rs::form_formats_and_calculations_through_tools"]
|
||
notes = "AFRange_Validate with Acrobat's messages"
|
||
|
||
[[feature]]
|
||
id = "form.af-simple-calculate"
|
||
title = "Sum, product, average, min, max calculations"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/forms/src/af.rs::validation_and_calculation", "crates/forms/src/tests.rs::formats_validation_and_calculations_run_like_acrobat"]
|
||
notes = "SUM, PRD, AVG, MIN, MAX over fields and groups"
|
||
|
||
[[feature]]
|
||
id = "form.simplified-notation"
|
||
title = "Simplified field notation calculations"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/forms/src/af.rs::validation_and_calculation", "crates/forms/src/tests.rs::formats_validation_and_calculations_run_like_acrobat", "crates/automation/tests/automation.rs::form_formats_and_calculations_through_tools"]
|
||
notes = "BVCALC expressions with + - * / and parentheses, escaped names"
|
||
|
||
[[feature]]
|
||
id = "form.custom-scripts"
|
||
title = "Custom format, keystroke, validate and calculate scripts"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.prepare"]
|
||
tools = ["form_set_script"]
|
||
evidence = ["crates/engine/src/tests.rs::form_javascript_validates_calculates_and_formats", "crates/automation/tests/automation.rs::javascript_through_tools"]
|
||
notes = "Custom keystroke (commit), validate, calculate and format scripts run in Acrobat's event order; AF calls still run natively."
|
||
|
||
[[feature]]
|
||
id = "form.calculation-order"
|
||
title = "Calculation order"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/forms/src/tests.rs::formats_validation_and_calculations_run_like_acrobat"]
|
||
notes = "/CO order, then other calculated fields; recalculated after every fill and Clear form"
|
||
|
||
[[feature]]
|
||
id = "form.event-order"
|
||
title = "Keystroke, validate, calculate, format event order"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.prepare"]
|
||
tools = ["form_fill"]
|
||
evidence = ["crates/engine/src/tests.rs::form_javascript_validates_calculates_and_formats"]
|
||
notes = "Keystroke (commit) \u2192 Validate \u2192 value \u2192 Calculate (in /CO order) \u2192 Format, for AF calls and custom scripts."
|
||
|
||
[[feature]]
|
||
id = "form.date-picker"
|
||
title = "Date picker for date fields"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["form_fill"]
|
||
evidence = ["crates/ui-egui/tests/forms.rs::date_fields_offer_a_calendar"]
|
||
notes = "a month calendar under date fields; the pick is written in the field's own date format"
|
||
|
||
[[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 = "shipped"
|
||
commands = []
|
||
tools = ["form_set_props"]
|
||
evidence = ["crates/ui-egui/tests/view.rs::required_fields_get_a_red_border_when_highlighting"]
|
||
notes = "required fields get a red border when fields are highlighted"
|
||
|
||
[[feature]]
|
||
id = "form.reset-form"
|
||
title = "Clear or reset a form"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.clear"]
|
||
tools = ["form_reset"]
|
||
evidence = ["crates/forms/src/tests.rs::reset_restores_defaults", "crates/ui-egui/tests/forms.rs::check_boxes_radios_and_choices"]
|
||
notes = "back to /DV; undoable"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["form.import_data"]
|
||
tools = ["doc_import_data"]
|
||
evidence = ["crates/xfdf/src/tests.rs::xfdf_round_trips_comments_and_values", "crates/xfdf/src/tests.rs::fdf_round_trips_values_and_comments", "crates/automation/tests/automation.rs::comments_and_form_data_travel_as_xfdf_fdf_and_text"]
|
||
notes = "XFDF, FDF; values go through formats and validation; read-only fields take data"
|
||
|
||
[[feature]]
|
||
id = "form.export-data"
|
||
title = "Export form data (FDF, XFDF)"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.export_data"]
|
||
tools = ["doc_export_data"]
|
||
evidence = ["crates/xfdf/src/tests.rs::xfdf_round_trips_comments_and_values", "crates/automation/tests/automation.rs::comments_and_form_data_travel_as_xfdf_fdf_and_text"]
|
||
notes = "XFDF, FDF"
|
||
|
||
[[feature]]
|
||
id = "form.data-xml-csv-txt"
|
||
title = "Form data as XML, CSV or tab-delimited text"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.export_data", "form.import_data"]
|
||
tools = ["doc_export_data", "doc_import_data"]
|
||
evidence = ["crates/xfdf/src/tests.rs::form_data_as_xml_csv_and_text", "crates/automation/tests/automation.rs::comments_and_form_data_travel_as_xfdf_fdf_and_text"]
|
||
notes = "Acrobat's XML (xfdf:original), CSV, tab-delimited"
|
||
|
||
[[feature]]
|
||
id = "form.merge-to-spreadsheet"
|
||
title = "Merge form data files into a spreadsheet"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.merge_data"]
|
||
tools = ["form_merge_data"]
|
||
evidence = ["crates/xfdf/src/tests.rs::data_files_merge_into_a_spreadsheet", "crates/automation/tests/automation.rs::merging_form_data_into_a_spreadsheet", "crates/ui-egui/tests/javascript.rs::merge_data_files_into_a_spreadsheet"]
|
||
notes = "FDF, XFDF or filled-in PDF forms \u2192 CSV, a column per field and a row per file."
|
||
|
||
[[feature]]
|
||
id = "form.flatten-fields"
|
||
title = "Flatten form fields"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.flatten"]
|
||
tools = ["doc_flatten"]
|
||
evidence = ["crates/edit/src/tests.rs::flattening_draws_appearances_into_the_page_and_removes_the_comments", "crates/engine/src/tests.rs::flattening_keeps_the_look_and_drops_the_objects", "crates/automation/tests/automation.rs::flattening_through_tools"]
|
||
notes = "widgets drawn into content and pruned from the field tree"
|
||
|
||
[[feature]]
|
||
id = "form.prepare-form-mode"
|
||
title = "Prepare a form mode"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.prepare"]
|
||
tools = ["form_add_field", "form_set_props", "form_delete_field"]
|
||
evidence = ["crates/ui-egui/tests/prepare.rs::placing_moving_editing_and_deleting_a_field", "crates/ui-egui/tests/prepare.rs::deleting_several_selected_fields_and_detecting_fields_from_the_panel", "crates/engine/src/tests.rs::preparing_a_form_adds_renames_and_deletes_fields"]
|
||
notes = "fields shown with blue fill and name tags; select, move, resize by corners, Delete (every selected field, one undo step); field tools return to Select after one field. Not yet: editing shared properties of several fields at once"
|
||
|
||
[[feature]]
|
||
id = "form.add-text-field"
|
||
title = "Add text fields"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.add.text"]
|
||
tools = ["form_add_field"]
|
||
evidence = ["crates/forms/src/tests.rs::every_field_type_can_be_added_named_and_drawn", "crates/ui-egui/tests/prepare.rs::placing_moving_editing_and_deleting_a_field", "crates/automation/tests/automation.rs::preparing_a_form_through_tools"]
|
||
notes = "click for the default size or drag; named Text1, Text2…"
|
||
|
||
[[feature]]
|
||
id = "form.add-checkbox"
|
||
title = "Add checkboxes"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.add.checkbox"]
|
||
tools = ["form_add_field"]
|
||
evidence = ["crates/forms/src/tests.rs::every_field_type_can_be_added_named_and_drawn", "crates/ui-egui/tests/prepare.rs::placing_moving_editing_and_deleting_a_field"]
|
||
notes = "Check Box1…, Yes/Off appearance states"
|
||
|
||
[[feature]]
|
||
id = "form.add-radio"
|
||
title = "Add radio buttons"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.add.radio"]
|
||
tools = ["form_add_field"]
|
||
evidence = ["crates/forms/src/tests.rs::every_field_type_can_be_added_named_and_drawn", "crates/automation/tests/automation.rs::preparing_a_form_through_tools"]
|
||
notes = "new group Group1… or join the selected group with the next ChoiceN"
|
||
|
||
[[feature]]
|
||
id = "form.check-box-style"
|
||
title = "Check box and radio button styles"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fields"]
|
||
evidence = ["crates/forms/src/tests.rs::check_box_styles_change_the_mark", "crates/ui-egui/tests/prepare.rs::options_tab_sets_the_check_box_style", "crates/automation/tests/automation.rs::preparing_a_form_through_tools"]
|
||
notes = "Field Properties ▸ Options: check, circle, cross, diamond, square, star (/MK /CA, Acrobat's codes), drawn as paths; defaults check (check boxes) and circle (radio buttons)"
|
||
|
||
[[feature]]
|
||
id = "form.add-dropdown"
|
||
title = "Add drop-down lists"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.add.combo"]
|
||
tools = ["form_add_field"]
|
||
evidence = ["crates/forms/src/tests.rs::every_field_type_can_be_added_named_and_drawn", "crates/automation/tests/automation.rs::preparing_a_form_through_tools"]
|
||
notes = "Dropdown1…; items in Field Properties ▸ Options"
|
||
|
||
[[feature]]
|
||
id = "form.add-listbox"
|
||
title = "Add list boxes"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.add.list"]
|
||
tools = ["form_add_field"]
|
||
evidence = ["crates/forms/src/tests.rs::every_field_type_can_be_added_named_and_drawn"]
|
||
notes = "List Box1…"
|
||
|
||
[[feature]]
|
||
id = "form.add-button"
|
||
title = "Add push buttons"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.add.button"]
|
||
tools = ["form_add_field"]
|
||
evidence = ["crates/forms/src/tests.rs::every_field_type_can_be_added_named_and_drawn"]
|
||
notes = "Button1…; actions arrive with the JavaScript engine"
|
||
|
||
[[feature]]
|
||
id = "form.add-image-field"
|
||
title = "Add image fields"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.add.image"]
|
||
tools = ["form_add_field", "form_set_image"]
|
||
evidence = ["crates/forms/src/tests.rs::image_fields_ask_for_a_picture_and_show_it", "crates/ui-egui/tests/prepare.rs::image_fields_take_a_picture_when_clicked", "crates/automation/tests/automation.rs::preparing_a_form_through_tools"]
|
||
notes = "icon-only push button with event.target.buttonImportIcon(), recognised natively; clicking asks for a PNG/JPEG/TIFF/GIF/BMP that becomes /MK /I, scaled proportionally and centred"
|
||
|
||
[[feature]]
|
||
id = "form.add-date-field"
|
||
title = "Add date fields"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.add.date"]
|
||
tools = ["form_add_field"]
|
||
evidence = ["crates/forms/src/tests.rs::every_field_type_can_be_added_named_and_drawn"]
|
||
notes = "text field with AFDate_FormatEx(\"mm/dd/yyyy\") format/keystroke actions"
|
||
|
||
[[feature]]
|
||
id = "form.add-signature-field"
|
||
title = "Add digital signature fields"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.add.signature"]
|
||
tools = ["form_add_field"]
|
||
evidence = ["crates/forms/src/tests.rs::every_field_type_can_be_added_named_and_drawn"]
|
||
notes = "unsigned /Sig field; signing is M9"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props"]
|
||
evidence = ["crates/forms/src/tests.rs::properties_rename_and_delete", "crates/ui-egui/tests/prepare.rs::placing_moving_editing_and_deleting_a_field", "crates/automation/tests/automation.rs::preparing_a_form_through_tools"]
|
||
notes = "name, tooltip, read only, required (visibility/orientation pending)"
|
||
|
||
[[feature]]
|
||
id = "form.props-appearance"
|
||
title = "Field properties: appearance (border, fill, font)"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props"]
|
||
evidence = ["crates/forms/src/tests.rs::appearance_properties_restyle_every_widget", "crates/ui-egui/tests/prepare.rs::appearance_tab_restyles_the_field", "crates/automation/tests/automation.rs::tab_order_and_field_appearance_through_tools"]
|
||
notes = "border/fill colour, thin/medium/thick, solid/dashed/beveled/inset/underline, text colour, Helvetica/Times/Courier, font size"
|
||
|
||
[[feature]]
|
||
id = "form.props-position"
|
||
title = "Field properties: position and size"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props"]
|
||
evidence = ["crates/forms/src/tests.rs::properties_rename_and_delete", "crates/ui-egui/tests/prepare.rs::placing_moving_editing_and_deleting_a_field"]
|
||
notes = "left/bottom/width/height in points; drag to move, corners to resize"
|
||
|
||
[[feature]]
|
||
id = "form.props-options"
|
||
title = "Field properties: options per field type"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props"]
|
||
evidence = ["crates/forms/src/tests.rs::properties_rename_and_delete", "crates/automation/tests/automation.rs::preparing_a_form_through_tools", "crates/forms/src/tests.rs::options_tab_flags_alignment_and_defaults", "crates/ui-egui/tests/prepare.rs::options_tab_sets_flags_alignment_and_defaults"]
|
||
notes = "text: alignment, default, multi-line, scroll, rich text flag, limit, password, file selection, spell check, comb; choices: items, default, sort, custom text, multiple selection, commit immediately; check boxes and radios: checked by default, radios in unison"
|
||
|
||
[[feature]]
|
||
id = "form.props-actions"
|
||
title = "Field properties: actions per trigger"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.prepare"]
|
||
tools = ["form_actions", "form_set_actions"]
|
||
evidence = ["crates/forms/src/tests.rs::field_actions_round_trip_on_every_trigger", "crates/automation/tests/automation.rs::field_actions_through_tools", "crates/ui-egui/tests/prepare.rs::actions_tab_adds_and_removes_actions"]
|
||
notes = "Six triggers; Run a JavaScript, Open a web link, Reset a form, Execute a menu item, Go to a page view, Show/hide a field, Submit a form. Other action types are kept."
|
||
|
||
[[feature]]
|
||
id = "form.props-format"
|
||
title = "Field properties: format"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/ui-egui/tests/prepare.rs::format_validate_and_calculate_tabs", "crates/automation/tests/automation.rs::form_formats_and_calculations_through_tools"]
|
||
notes = "None/Number/Percentage/Date/Time/Special with a live example; writes Acrobat's AF scripts"
|
||
|
||
[[feature]]
|
||
id = "form.props-validate"
|
||
title = "Field properties: validate"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/ui-egui/tests/prepare.rs::format_validate_and_calculate_tabs", "crates/automation/tests/automation.rs::form_formats_and_calculations_through_tools"]
|
||
notes = "range from/to"
|
||
|
||
[[feature]]
|
||
id = "form.props-calculate"
|
||
title = "Field properties: calculate"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.field.properties"]
|
||
tools = ["form_set_props", "form_fill"]
|
||
evidence = ["crates/ui-egui/tests/prepare.rs::format_validate_and_calculate_tabs", "crates/automation/tests/automation.rs::form_formats_and_calculations_through_tools"]
|
||
notes = "sum/product/average/min/max of picked fields, simplified field notation; custom script pending"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["form.prepare"]
|
||
tools = ["form_set_props"]
|
||
evidence = ["crates/ui-egui/tests/prepare.rs::aligning_distributing_and_sizing_several_fields"]
|
||
notes = "Shift/⌘-click to select several; right-click: Align (left, right, top, bottom, centres), Distribute, Set Fields to Same Size; the right-clicked field is the anchor"
|
||
|
||
[[feature]]
|
||
id = "form.duplicate-fields"
|
||
title = "Duplicate fields across pages"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.prepare"]
|
||
tools = []
|
||
evidence = ["crates/forms/src/tests.rs::duplicating_a_field_across_pages_shares_its_value", "crates/ui-egui/tests/prepare.rs::duplicating_a_field_onto_every_page"]
|
||
notes = "right-click a field ▸ Duplicate: all pages or a range; one field, shared value"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["form.prepare"]
|
||
tools = ["form_set_props"]
|
||
evidence = ["crates/forms/src/tests.rs::locked_fields_only_take_unlocking", "crates/ui-egui/tests/prepare.rs::preview_fills_the_form_and_locked_fields_keep_their_properties"]
|
||
notes = "rename in Field Properties ▸ General; Locked sets the widgets' annotation Locked flag (/F bit 8) and greys out the properties until unlocked; values can still be filled in"
|
||
|
||
[[feature]]
|
||
id = "form.tab-order"
|
||
title = "Tab order (row, column, structure, manual)"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.tab_order.row", "form.tab_order.column", "form.tab_order.structure"]
|
||
tools = ["form_tab_order"]
|
||
evidence = ["crates/forms/src/tests.rs::tab_order_follows_the_page_setting", "crates/automation/tests/automation.rs::tab_order_and_field_appearance_through_tools", "crates/forms/src/tests.rs::ordering_tabs_manually", "crates/ui-egui/tests/prepare.rs::the_fields_panel_orders_tabs_manually"]
|
||
notes = "rows, columns, structure/annotation order (page /Tabs), and manual order from the Fields panel while preparing a form; honoured by Tab"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["form.prepare"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/prepare.rs::preview_fills_the_form_and_locked_fields_keep_their_properties"]
|
||
notes = "Preview / Edit fields toggle in the Prepare a form panel"
|
||
|
||
[[feature]]
|
||
id = "form.auto-detect-fields"
|
||
title = "Automatic form field detection"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "partial"
|
||
commands = ["form.detect", "form.prepare"]
|
||
tools = ["form_detect_fields"]
|
||
evidence = ["crates/forms/src/tests.rs::detection_finds_blanks_rules_boxes_and_names_them", "crates/engine/src/tests.rs::detecting_fields_on_a_printed_form", "crates/automation/tests/automation.rs::detecting_form_fields_through_tools", "crates/ui-egui/tests/javascript.rs::prepare_a_form_detects_fields_on_a_paper_form", "crates/ui-egui/tests/prepare.rs::deleting_several_selected_fields_and_detecting_fields_from_the_panel"]
|
||
notes = "Underscore runs, rules, empty boxes and small squares (check boxes); runs when Prepare a form starts on a document without fields, and from Prepare a form ▸ Detect form fields at any time (existing fields are skipped). Not yet: comb boxes, tables of cells, scanned forms (needs OCR first), radio groups."
|
||
|
||
[[feature]]
|
||
id = "form.auto-name-fields"
|
||
title = "Name new fields from nearby labels"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["form.detect"]
|
||
tools = ["form_detect_fields"]
|
||
evidence = ["crates/forms/src/tests.rs::detection_finds_blanks_rules_boxes_and_names_them", "crates/engine/src/tests.rs::detecting_fields_on_a_printed_form", "crates/automation/tests/automation.rs::detecting_form_fields_through_tools"]
|
||
notes = "Names come from the label to the left (or above; right of check boxes), unique like Acrobat's (Name2, Text1)."
|
||
|
||
[[feature]]
|
||
id = "form.document-javascripts"
|
||
title = "Document-level JavaScripts"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["tools.document_js"]
|
||
tools = ["js_document_scripts", "js_set_document_script"]
|
||
evidence = ["crates/ui-egui/tests/javascript.rs::document_scripts_and_preferences", "crates/automation/tests/automation.rs::javascript_through_tools", "crates/engine/src/tests.rs::form_javascript_validates_calculates_and_formats"]
|
||
notes = "Names /JavaScript tree: list, add, edit, delete; functions are available to field scripts."
|
||
|
||
[[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 = "partial"
|
||
commands = ["tools.js_console"]
|
||
tools = ["js_run"]
|
||
evidence = ["crates/js/src/tests.rs::calculate_scripts_read_fields_and_set_the_value", "crates/engine/src/tests.rs::form_javascript_validates_calculates_and_formats", "crates/automation/tests/automation.rs::javascript_through_tools"]
|
||
notes = "boa (pure Rust) with the forms object model: event, getField/Field (value, readonly, display, colours, items, check boxes), app, util.printf/printd/printx, console, display, color; no annotations, layers or dialogs (app.response returns null) yet."
|
||
|
||
[[feature]]
|
||
id = "form.js-console"
|
||
title = "JavaScript console"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M6"
|
||
status = "shipped"
|
||
commands = ["tools.js_console"]
|
||
tools = ["js_run"]
|
||
evidence = ["crates/ui-egui/tests/javascript.rs::console_runs_scripts_against_the_document", "crates/automation/tests/automation.rs::javascript_through_tools"]
|
||
notes = "\u2318J: run JavaScript against the document, with console output, results and errors."
|
||
|
||
[[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 = "shipped"
|
||
commands = ["app.preferences"]
|
||
tools = ["js_enabled"]
|
||
evidence = ["crates/js/src/tests.rs::the_sandbox_has_no_host_access", "crates/js/src/tests.rs::errors_and_runaway_scripts_are_contained", "crates/ui-egui/tests/javascript.rs::document_scripts_and_preferences", "crates/ui-egui/tests/document_links.rs::a_script_asks_first_and_only_once", "crates/ui-egui/tests/document_links.rs::a_script_cannot_open_other_kinds_of_address"]
|
||
notes = "Preferences \u25b8 JavaScript \u25b8 Enable Acrobat JavaScript (remembered); no host access; loop and recursion limits; app.launchURL asks before opening and only offers http, https and mailto addresses (#90, #91)."
|
||
|
||
[[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 = "partial"
|
||
commands = ["file.open"]
|
||
tools = ["doc_open", "form_fill"]
|
||
evidence = ["crates/engine/src/tests.rs::static_xfa_forms_take_their_values_from_the_datasets_and_write_them_back", "crates/ui-egui/tests/xfa.rs::a_static_xfa_form_is_filled_from_its_data_and_says_so", "crates/xfa/src/tests.rs::static_forms_read_their_values_from_the_datasets", "crates/xfa/src/tests.rs::writing_values_keeps_the_data_no_field_binds_to", "crates/xfa/src/tests.rs::utf16_packets_are_read_and_written_back_as_utf16", "crates/engine/src/tests.rs::xfa_data_no_field_binds_to_survives_an_edit_and_save"]
|
||
notes = "the AcroForm fields are shown and filled as in any form; on open their values come from the XFA datasets (what another viewer saved) by the fields' Designer names (recorded as a document warning), and every edit merges the values back into the datasets, keeping data no field binds to, so Acrobat shows the same values; values that can't be written are warnings. Missing: no template semantics (no scripts, calculations or hidden subforms), explicit bind refs are not followed (#60)"
|
||
|
||
[[feature]]
|
||
id = "form.xfa-dynamic"
|
||
title = "View and fill dynamic XFA forms"
|
||
area = "F"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "partial"
|
||
commands = ["file.open"]
|
||
tools = ["doc_open", "doc_info", "form_fields", "form_fill", "js_run"]
|
||
evidence = ["crates/xfa/src/tests.rs::layout_places_captions_fields_and_sizes_containers_to_their_content", "crates/xfa/src/tests.rs::tables_break_across_pages_and_repeat_their_header_row", "crates/xfa/src/tests.rs::rendering_into_the_pdf_replaces_the_placeholder_page_and_adds_fields", "crates/engine/src/tests.rs::dynamic_xfa_forms_are_laid_out_on_open_filled_and_saved_incrementally", "crates/automation/tests/automation.rs::dynamic_xfa_forms_open_render_fill_and_save_through_tools", "crates/ui-egui/tests/xfa.rs::a_dynamic_xfa_form_is_laid_out_and_filled", "crates/xfa/src/tests.rs::nested_width_less_subforms_lay_out_in_polynomial_time", "crates/js/src/tests.rs::instance_managers_add_remove_and_count", "crates/xfa/src/tests.rs::the_form_tree_carries_values_presence_instances_and_the_events", "crates/engine/src/tests.rs::xfa_scripts_initialize_calculate_validate_toggle_and_add_rows", "crates/automation/tests/automation.rs::xfa_scripts_run_for_buttons_and_field_changes_through_tools", "crates/ui-egui/tests/xfa.rs::xfa_buttons_run_their_scripts_in_the_app", "crates/engine/src/tests.rs::hostile_xfa_open_scripts_are_merged_capped_kept_from_printing_and_noted", "crates/engine/src/tests.rs::runaway_xfa_calculations_at_open_end_quickly_with_a_report", "crates/xfa/src/tests.rs::the_live_form_of_nested_repeating_subforms_stops_at_its_node_budget", "crates/xfa/src/tests.rs::data_ops_of_one_event_are_one_datasets_rewrite"]
|
||
notes = "laid out from the template on open by the xfa crate (positioned, tb, lr-tb, table/row, page masters, breaks, repeated header rows, captions, rich text, JPEG images) into real pages and AcroForm fields (text, date with AFDate formats, check boxes, radio groups, buttons with reset/print/save/URL), filled from the datasets packet (including rows another viewer added), saved as an incremental revision with the values merged into the datasets so Adobe's viewers show them; doc_info reports xfa_layout. XFA JavaScript runs in the js crate's XFA object model (xfa.form, SOM resolution, rawValue, presence, access, instance managers, xfa.host, xfa.layout, xfa.event): initialize and calculate on open, exit, validate and calculate after a field changes, click for buttons (through js_run and the app), with rows added and removed, subforms shown and hidden, and the form laid out again, all as one undo step and kept in the file. Hostile scripts are bounded per event (merged and capped effects, messages and console lines, lower loop limits and a time budget on open and calculate, one datasets rewrite per event, print/save/links/focus from clicks only, a runaway script abandoned and the form's scripts turned off) and what open-time scripts changed is noted. Missing: loops inside nested calls are only stopped by abandoning their thread, and script memory is not capped; FormCalc (reported, not run); the change event per keystroke; xfa.template, connections and data descriptions; a hidden instance of a repeating subform hides them all; choice lists are text fields; signature, image and barcode fields are blank; PNG images; keep rules; metrics are approximate and Acrobat fidelity is eyeballed on real forms, not measured (#60)"
|
||
|
||
[[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 = "partial"
|
||
commands = ["file.open", "file.save"]
|
||
tools = ["doc_open", "form_fill", "doc_save"]
|
||
evidence = ["crates/xfa/src/tests.rs::the_datasets_packet_is_written_from_the_fields_and_read_back", "crates/xfa/src/tests.rs::data_fills_the_fields_and_adds_rows", "crates/engine/src/tests.rs::dynamic_xfa_forms_are_laid_out_on_open_filled_and_saved_incrementally"]
|
||
notes = "the datasets packet inside the PDF: read on open (values, dates in ISO form, check and radio states, extra rows for repeating subforms) and rewritten after every form edit from the fields' SOM paths (/PCSom). Missing: importing or exporting a standalone XML/XDP data file, explicit bind refs, data descriptions"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["sign.fill.text"]
|
||
tools = ["fill_sign_add"]
|
||
evidence = ["crates/ui-egui/tests/fill_sign.rs::text_marks_and_date", "crates/annot/src/tests.rs::fill_and_sign_items_are_drawn", "crates/automation/tests/automation.rs::fill_and_sign_through_tools"]
|
||
notes = "typewriter FreeText, 10 pt Helvetica"
|
||
|
||
[[feature]]
|
||
id = "form.fill-sign-marks"
|
||
title = "Fill & Sign: check, cross, dot, line marks"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["sign.fill.check", "sign.fill.cross", "sign.fill.dot", "sign.fill.line"]
|
||
tools = ["fill_sign_add"]
|
||
evidence = ["crates/ui-egui/tests/fill_sign.rs::text_marks_and_date", "crates/annot/src/tests.rs::fill_and_sign_items_are_drawn"]
|
||
notes = "PdfCraft-drawn stamps"
|
||
|
||
[[feature]]
|
||
id = "form.fill-sign-date"
|
||
title = "Fill & Sign: today's date"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "shipped"
|
||
commands = ["sign.fill.date"]
|
||
tools = ["fill_sign_add"]
|
||
evidence = ["crates/ui-egui/tests/fill_sign.rs::text_marks_and_date", "crates/automation/tests/automation.rs::fill_and_sign_through_tools"]
|
||
notes = "local date, m/d/yyyy"
|
||
|
||
[[feature]]
|
||
id = "form.fill-sign-signature"
|
||
title = "Fill & Sign: typed, drawn or image signature and initials"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "partial"
|
||
commands = ["sign.fill.signature", "sign.fill.initials", "sign.fill.signature.change", "sign.fill.initials.change", "sign.fill.signature.remove", "sign.fill.initials.remove"]
|
||
tools = ["fill_sign_add"]
|
||
evidence = ["crates/ui-egui/tests/fill_sign.rs::typed_signatures_and_initials", "crates/ui-egui/tests/fill_sign.rs::signing_draws_a_signature_once_and_places_it", "crates/ui-egui/tests/fill_sign.rs::changing_saved_signatures_and_initials_preserves_placed_marks", "crates/ui-egui/tests/fill_sign.rs::changing_drawn_signatures_can_be_cancelled_or_switched_to_type", "crates/ui-egui/tests/fill_sign.rs::saved_signature_cards_remove_and_add_without_changing_the_document", "crates/ui-egui/tests/fill_sign.rs::long_typed_names_fit_the_placed_signature_and_keep_every_outline", "crates/ui-egui/src/fill_sign.rs::long_signature_previews_have_clear_margins", "crates/ui-egui/tests/control.rs::saved_signature_can_be_changed_through_the_control_channel", "crates/fonts/src/script.rs::a_name_becomes_outlines", "crates/fonts/src/script.rs::long_names_keep_every_glyph_with_bounded_outline_work", "crates/automation/tests/automation.rs::fill_and_sign_through_tools"]
|
||
notes = "signature and initials, typed (Dancing Script outlines as filled paths, no font embedded) or drawn, saved in settings; preview cards with add/change/remove controls in the panel and picker, also through ui.command; changes leave placed annotations untouched; long names fit within 150 pt with overhangs included in preview/PDF bounds; image signatures not yet"
|
||
|
||
[[feature]]
|
||
id = "form.fill-sign-flatten"
|
||
title = "Fill & Sign: flatten on save"
|
||
area = "F"
|
||
tier = "P0"
|
||
milestone = "M5"
|
||
status = "partial"
|
||
commands = ["comment.flatten"]
|
||
tools = ["doc_flatten"]
|
||
evidence = ["crates/edit/src/tests.rs::flattening_draws_appearances_into_the_page_and_removes_the_comments", "crates/engine/src/tests.rs::flattening_keeps_the_look_and_drops_the_objects", "crates/automation/tests/automation.rs::flattening_through_tools"]
|
||
notes = "Flatten comments flattens Fill & Sign items; no flatten-on-save option yet"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["protect.password"]
|
||
tools = ["doc_protect"]
|
||
evidence = ["crates/ui-egui/tests/protect.rs::protecting_for_viewing_requires_matching_passwords_and_saves_encrypted", "crates/engine/src/tests.rs::protect_with_an_open_password_then_save_reopen_and_undo", "crates/automation/tests/automation.rs::protecting_through_tools"]
|
||
notes = "Protect Using Password (Viewing); applied by the next save"
|
||
|
||
[[feature]]
|
||
id = "protect.permissions-password"
|
||
title = "Restrict editing and printing with a permissions password"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["protect.password"]
|
||
tools = ["doc_protect"]
|
||
evidence = ["crates/ui-egui/tests/protect.rs::editing_restrictions_via_advanced_options", "crates/engine/src/tests.rs::permissions_password_restricts_others_but_not_this_session", "crates/automation/tests/automation.rs::protecting_through_tools"]
|
||
notes = "Protect Using Password (Editing); the author keeps full rights after saving"
|
||
|
||
[[feature]]
|
||
id = "protect.permissions-matrix"
|
||
title = "Choose printing, changes, copying and accessibility permissions"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["protect.password"]
|
||
tools = ["doc_protect"]
|
||
evidence = ["crates/ui-egui/tests/protect.rs::editing_restrictions_via_advanced_options", "crates/engine/src/tests.rs::permissions_password_restricts_others_but_not_this_session"]
|
||
notes = "Advanced Options: printing (none/low/high), changes (5 levels), copying, screen readers"
|
||
|
||
[[feature]]
|
||
id = "protect.encryption-level"
|
||
title = "Choose encryption compatibility (RC4 40/128, AES-128, AES-256)"
|
||
area = "G"
|
||
tier = "P1"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["protect.password"]
|
||
tools = ["doc_protect"]
|
||
evidence = ["crates/engine/src/tests.rs::protection_is_validated_undoable_and_never_logged"]
|
||
notes = "AES-256 (default), AES-128, RC4-128, RC4-40"
|
||
|
||
[[feature]]
|
||
id = "protect.encrypt-except-metadata"
|
||
title = "Encrypt everything except metadata"
|
||
area = "G"
|
||
tier = "P1"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["protect.password"]
|
||
tools = ["doc_protect"]
|
||
evidence = ["crates/engine/src/tests.rs::protection_is_validated_undoable_and_never_logged"]
|
||
notes = "Advanced Options; disabled for RC4 levels as in Acrobat"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["protect.password"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/protect.rs::protecting_for_viewing_requires_matching_passwords_and_saves_encrypted"]
|
||
notes = "Weak / Medium / Strong under the password field"
|
||
|
||
[[feature]]
|
||
id = "protect.remove-security"
|
||
title = "Remove security"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["protect.remove"]
|
||
tools = ["doc_unprotect"]
|
||
evidence = ["crates/engine/src/tests.rs::protect_with_an_open_password_then_save_reopen_and_undo", "crates/automation/tests/automation.rs::protecting_through_tools"]
|
||
notes = "needs the permissions password (or none set)"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["redact.mark"]
|
||
tools = ["redact_mark"]
|
||
evidence = ["crates/ui-egui/tests/redact.rs::marking_applying_and_clearing", "crates/automation/tests/automation.rs::redacting_through_tools", "crates/redact/src/tests.rs::glyphs_under_a_mark_go_and_the_rest_stays_put"]
|
||
notes = "drag across text (or select first) with the Redact tool"
|
||
|
||
[[feature]]
|
||
id = "protect.redact-area"
|
||
title = "Mark areas and images for redaction"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.mark"]
|
||
tools = ["redact_mark"]
|
||
evidence = ["crates/ui-egui/tests/redact.rs::marking_applying_and_clearing", "crates/redact/src/tests.rs::images_are_removed_or_have_their_pixels_cleared"]
|
||
notes = "drag a box anywhere else"
|
||
|
||
[[feature]]
|
||
id = "protect.redact-pages"
|
||
title = "Mark whole pages for redaction"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.pages"]
|
||
tools = ["redact_mark"]
|
||
evidence = ["crates/ui-egui/tests/redact.rs::marking_applying_and_clearing"]
|
||
notes = "Mark Page Range: current page or a range"
|
||
|
||
[[feature]]
|
||
id = "protect.search-and-redact"
|
||
title = "Find text and redact"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.search"]
|
||
tools = ["redact_mark"]
|
||
evidence = ["crates/automation/tests/automation.rs::redacting_through_tools"]
|
||
notes = "single word or phrase, every match on every page"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["redact.search"]
|
||
tools = ["redact_mark"]
|
||
evidence = ["crates/redact/src/patterns.rs::phone_numbers", "crates/redact/src/patterns.rs::credit_cards_need_a_valid_checksum", "crates/automation/tests/automation.rs::redacting_through_tools"]
|
||
notes = "phone, e-mail, credit card (Luhn), SSN, dates (English US forms)"
|
||
|
||
[[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 = "partial"
|
||
commands = ["redact.properties"]
|
||
tools = ["redact_mark"]
|
||
evidence = ["crates/ui-egui/tests/redact.rs::marking_applying_and_clearing", "crates/redact/src/tests.rs::overlay_text_takes_its_font_size_colour_alignment_and_repeats"]
|
||
notes = "fill colour or none; overlay text with font, size or auto-size, colour, alignment and repeat; redaction codes pending"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["redact.apply"]
|
||
tools = ["redact_apply"]
|
||
evidence = ["crates/ui-egui/tests/redact.rs::marking_applying_and_clearing", "crates/engine/src/tests.rs::redaction_marks_apply_for_good_and_undo", "crates/automation/tests/automation.rs::redacting_through_tools"]
|
||
notes = "confirmation; one undo step; next save is a full rewrite"
|
||
|
||
[[feature]]
|
||
id = "protect.redact-glyph-removal"
|
||
title = "Remove redacted text glyphs from content streams"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.apply"]
|
||
tools = ["redact_apply"]
|
||
evidence = ["crates/redact/src/tests.rs::glyphs_under_a_mark_go_and_the_rest_stays_put", "crates/redact/src/tests.rs::kerning_spacing_scaling_and_line_operators_are_honoured", "crates/redact/src/tests.rs::rotated_text_and_composite_fonts", "crates/redact/src/tests.rs::shared_form_xobjects_are_copied_not_changed"]
|
||
notes = "TJ displacement keeps the rest in place; simple, composite (CMaps), Type 3 fonts"
|
||
|
||
[[feature]]
|
||
id = "protect.redact-image-pixels"
|
||
title = "Remove redacted image pixels"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.apply"]
|
||
tools = ["redact_apply"]
|
||
evidence = ["crates/redact/src/tests.rs::images_are_removed_or_have_their_pixels_cleared"]
|
||
notes = "covered pixels cleared in a copy; DCT/JPX/JBIG2/CCITT images removed whole"
|
||
|
||
[[feature]]
|
||
id = "protect.redact-vectors"
|
||
title = "Remove or cut redacted vector paths"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.apply"]
|
||
tools = ["redact_apply"]
|
||
evidence = ["crates/redact/src/tests.rs::vectors_are_removed_or_clipped_and_inline_images_go"]
|
||
notes = "covered paths removed, others clipped; shadings clipped"
|
||
|
||
[[feature]]
|
||
id = "protect.redact-annots-fields"
|
||
title = "Remove annotations and fields under redactions"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.apply"]
|
||
tools = ["redact_apply"]
|
||
evidence = ["crates/redact/src/tests.rs::comments_links_and_fields_under_a_mark_go", "crates/ui-egui/tests/redact.rs::marking_applying_and_clearing"]
|
||
notes = "with pop-ups; fields lose all widgets"
|
||
|
||
[[feature]]
|
||
id = "protect.redact-struct-tree"
|
||
title = "Remove redacted content from tags and alternate text"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.apply"]
|
||
tools = ["redact_apply"]
|
||
evidence = ["crates/redact/src/tests.rs::tags_lose_what_redaction_removed"]
|
||
notes = "marked content changed by redaction is found by comparing each MCID before and after; its elements lose Alt, ActualText and E, and references to now-empty marked content are dropped; untouched tags stay"
|
||
|
||
[[feature]]
|
||
id = "protect.redaction-verification"
|
||
title = "Verify no residue remains after redaction"
|
||
area = "G"
|
||
tier = "P0"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.apply"]
|
||
tools = ["redact_apply"]
|
||
evidence = ["crates/redact/src/tests.rs::unreadable_content_fails_closed", "crates/engine/src/tests.rs::redaction_marks_apply_for_good_and_undo"]
|
||
notes = "re-reads redacted pages; residue or unreadable content fails the whole operation"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["protect.remove_hidden"]
|
||
tools = ["doc_hidden_info", "doc_remove_hidden"]
|
||
evidence = ["crates/redact/src/tests.rs::hidden_information_is_counted_and_removed", "crates/ui-egui/tests/redact.rs::removing_hidden_information_and_sanitizing", "crates/automation/tests/automation.rs::removing_hidden_information_through_tools"]
|
||
notes = "metadata, attachments, comments, form fields (flattened), hidden text, hidden layers, bookmarks, links/actions/scripts, private data; overlapping objects and cropped content pending"
|
||
|
||
[[feature]]
|
||
id = "protect.sanitize"
|
||
title = "Sanitize document (remove all hidden information)"
|
||
area = "G"
|
||
tier = "P1"
|
||
milestone = "M8"
|
||
status = "shipped"
|
||
commands = ["redact.sanitize"]
|
||
tools = ["doc_remove_hidden"]
|
||
evidence = ["crates/redact/src/tests.rs::hidden_information_is_counted_and_removed", "crates/ui-egui/tests/redact.rs::removing_hidden_information_and_sanitizing", "crates/automation/tests/automation.rs::removing_hidden_information_through_tools"]
|
||
notes = "every category, then a full rewrite on save"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["sign.digital"]
|
||
tools = ["sign_document"]
|
||
evidence = ["crates/sign/tests/crypto.rs::opens_every_openssl_flavour_of_pkcs12", "crates/sign/tests/pdf.rs::signing_then_validating_with_and_without_trust", "crates/ui-egui/tests/signing.rs::certifying_without_a_visible_signature", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
notes = "PBES2 (AES, 3DES) and legacy SHA-1 3DES/RC2 files; RSA, P-256, P-384"
|
||
|
||
[[feature]]
|
||
id = "sign.macos-keychain"
|
||
title = "Sign with macOS Keychain identities"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.digital"]
|
||
tools = ["sign_keychain_ids", "sign_document"]
|
||
evidence = ["crates/sign/tests/pdf.rs::signing_with_keychain_identities", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
notes = "Security framework identities (RSA PKCS #1 v1.5, ECDSA P-256/P-384) listed with the digital IDs in Sign with a Digital ID; the key never leaves the Keychain (ExternalKey); keychain:<fingerprint|name> for agents. The test uses a throwaway keychain file (run with --ignored)"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["sign.digital"]
|
||
tools = ["sign_id_create"]
|
||
evidence = ["crates/sign/tests/crypto.rs::new_digital_ids_are_self_signed_and_survive_a_p12_round_trip", "crates/ui-egui/tests/signing.rs::drawing_a_signature_creating_an_id_signing_and_trusting", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
notes = "RSA 2048/3072/4096 or P-256, saved as a password-protected .p12"
|
||
|
||
[[feature]]
|
||
id = "sign.export-certificate"
|
||
title = "Export a public certificate"
|
||
area = "H"
|
||
tier = "P2"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.panel"]
|
||
tools = []
|
||
evidence = ["crates/sign/tests/crypto.rs::certificates_load_from_pem_and_der_and_export_as_pem"]
|
||
notes = "Signatures panel ▸ Export certificate (.cer, PEM)"
|
||
|
||
[[feature]]
|
||
id = "sign.visible-signature"
|
||
title = "Visible signature by drawing a rectangle"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.digital"]
|
||
tools = ["sign_document"]
|
||
evidence = ["crates/ui-egui/tests/signing.rs::drawing_a_signature_creating_an_id_signing_and_trusting", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
notes = "drag a rectangle; Acrobat's name-and-details appearance"
|
||
|
||
[[feature]]
|
||
id = "sign.sign-existing-field"
|
||
title = "Sign an existing signature field"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.digital"]
|
||
tools = ["sign_document"]
|
||
evidence = ["crates/sign/tests/pdf.rs::signing_an_existing_field_and_counter_signing", "crates/ui-egui/tests/signing.rs::clicking_an_empty_signature_field_signs_it"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.invisible-signature"
|
||
title = "Invisible signature"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.certify_invisible"]
|
||
tools = ["sign_document"]
|
||
evidence = ["crates/sign/tests/pdf.rs::invisible_signatures_and_refusals", "crates/ui-egui/tests/signing.rs::certifying_without_a_visible_signature"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.signature-appearance"
|
||
title = "Configure signature appearance (text, graphic, name)"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "partial"
|
||
commands = ["sign.digital"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/signing.rs::drawing_a_signature_creating_an_id_signing_and_trusting"]
|
||
notes = "text items (name, date, reason, location, DN, labels); no graphics or saved appearances yet"
|
||
|
||
[[feature]]
|
||
id = "sign.reason-location"
|
||
title = "Reason, location and contact info"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.digital"]
|
||
tools = ["sign_document"]
|
||
evidence = ["crates/sign/tests/pdf.rs::signing_then_validating_with_and_without_trust", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
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 = "shipped"
|
||
commands = ["sign.validate"]
|
||
tools = ["sign_list"]
|
||
evidence = ["crates/sign/tests/pdf.rs::validates_a_signature_made_by_openssl"]
|
||
notes = "validated; new signatures use CAdES"
|
||
|
||
[[feature]]
|
||
id = "sign.cades-pades-bb"
|
||
title = "CAdES / PAdES B-B signatures"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.digital"]
|
||
tools = ["sign_document"]
|
||
evidence = ["crates/sign/tests/crypto.rs::signatures_round_trip_for_every_key_type", "crates/sign/tests/pdf.rs::signing_then_validating_with_and_without_trust"]
|
||
notes = "ETSI.CAdES.detached with signing-certificate-v2; pdfsig reports them valid"
|
||
|
||
[[feature]]
|
||
id = "sign.hash-algorithms"
|
||
title = "SHA-256/384/512 hashes; no SHA-1 for new signatures"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.digital"]
|
||
tools = ["sign_document"]
|
||
evidence = ["crates/sign/tests/crypto.rs::signatures_round_trip_for_every_key_type"]
|
||
notes = "SHA-256, SHA-384 for P-384; SHA-1 refused for new signatures, validated in old ones"
|
||
|
||
[[feature]]
|
||
id = "sign.certify"
|
||
title = "Certify a document (DocMDP)"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.certify", "sign.certify_invisible"]
|
||
tools = ["sign_document"]
|
||
evidence = ["crates/sign/tests/pdf.rs::later_changes_are_classified_under_the_signature_permissions", "crates/ui-egui/tests/signing.rs::certifying_without_a_visible_signature"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.certify-permissions"
|
||
title = "Allowed changes after certifying"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.certify"]
|
||
tools = ["sign_document"]
|
||
evidence = ["crates/sign/tests/pdf.rs::later_changes_are_classified_under_the_signature_permissions"]
|
||
notes = "DocMDP P=1/2/3 enforced when validating"
|
||
|
||
[[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 = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/sign/tests/pdf.rs::a_document_timestamp_covers_the_file_and_validates"]
|
||
notes = "timestamp_document appends a standalone /ETSI.RFC3161 signature dictionary over the whole file; validation discovers and verifies these (imprint, token signature, TSA cert validity) and reports the trusted time. Missing: automation tool and timestamp-server configuration (transport is caller-injected)."
|
||
|
||
[[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 = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/sign/tests/pdf.rs::signing_with_a_timestamp_embeds_a_verified_rfc3161_token"]
|
||
notes = "Sign with an embedded RFC 3161 token (sign_with_timestamp): imprint and TSA signature verified at signing and validation time, trusted time reported. Missing: timestamp-server configuration and a fetcher (transport is caller-injected), the standalone document timestamp, automation tool."
|
||
|
||
[[feature]]
|
||
id = "sign.ltv"
|
||
title = "Long-term validation (DSS/VRI)"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/sign/tests/pdf.rs::embedding_ltv_evidence_adds_a_dss_and_keeps_signatures_valid", "crates/sign/tests/pdf.rs::an_embedded_verified_revocation_invalidates_the_signature"]
|
||
notes = "dss::embed merges caller-supplied evidence into the catalog /DSS with /VRI entries per signature, deduplicating blobs, as a permitted incremental change; revocation.rs verifies the embedded CRLs and OCSP responses (signature, responder identity, CertID, window) and a verified revocation invalidates the signature. Missing: fetching evidence, automation tool."
|
||
|
||
[[feature]]
|
||
id = "sign.pades-blta"
|
||
title = "PAdES B-LT and B-LTA"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/sign/tests/pdf.rs::sign_then_ltv_then_timestamp_makes_a_b_lta_file"]
|
||
notes = "The B-LT/B-LTA chain works with caller-supplied evidence: sign, embed DSS (B-LT), then append a document timestamp over the result (B-LTA); earlier signatures stay valid with the store classified as a permitted change. Missing: OCSP/CRL verification of the evidence, re-timestamping automation."
|
||
|
||
[[feature]]
|
||
id = "sign.ocsp-crl"
|
||
title = "Revocation checking (OCSP and CRL)"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/sign/tests/crypto.rs::crls_verify_and_report_revocation", "crates/sign/tests/crypto.rs::ocsp_responses_verify_and_match_the_certificate", "crates/sign/tests/pdf.rs::an_embedded_verified_revocation_invalidates_the_signature"]
|
||
notes = "RFC 5280 CRLs and RFC 6960 OCSP responses are parsed and verified (issuer/responder signature, delegated responder's OCSP-signing EKU, CertID hash match, validity window) and checked against the DSS during validation. Missing: network fetching of fresh evidence and the OS trust store."
|
||
|
||
[[feature]]
|
||
id = "sign.validate-all"
|
||
title = "Validate all signatures"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.validate"]
|
||
tools = ["sign_list"]
|
||
evidence = ["crates/sign/tests/pdf.rs::signing_then_validating_with_and_without_trust", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.signatures-panel"
|
||
title = "Signatures panel with per-revision tree"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "partial"
|
||
commands = ["sign.panel"]
|
||
tools = ["sign_list"]
|
||
evidence = ["crates/ui-egui/tests/signing.rs::drawing_a_signature_creating_an_id_signing_and_trusting"]
|
||
notes = "per-signature tree with details and actions; no per-revision page outline yet"
|
||
|
||
[[feature]]
|
||
id = "sign.status-icons"
|
||
title = "Signature status (valid, unknown, invalid)"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.panel"]
|
||
tools = ["sign_list"]
|
||
evidence = ["crates/ui-egui/tests/signing.rs::drawing_a_signature_creating_an_id_signing_and_trusting"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "sign.validation-banner"
|
||
title = "Signed/certified document message bar"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.panel"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/signing.rs::drawing_a_signature_creating_an_id_signing_and_trusting", "crates/ui-egui/tests/signing.rs::certifying_without_a_visible_signature"]
|
||
notes = "signed/valid, problems, invalid and certified bars"
|
||
|
||
[[feature]]
|
||
id = "sign.signature-properties"
|
||
title = "Signature properties (validity summary, signer, time)"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "partial"
|
||
commands = ["sign.panel"]
|
||
tools = ["sign_list"]
|
||
evidence = ["crates/ui-egui/tests/signing.rs::drawing_a_signature_creating_an_id_signing_and_trusting"]
|
||
notes = "validity summary, signer, issuer, time, algorithm in the panel; no separate properties dialog"
|
||
|
||
[[feature]]
|
||
id = "sign.certificate-viewer"
|
||
title = "Certificate viewer"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.panel"]
|
||
tools = ["sign_list"]
|
||
evidence = ["crates/ui-egui/tests/signing.rs::drawing_a_signature_creating_an_id_signing_and_trusting"]
|
||
notes = "Signatures panel ▸ Show certificate: the chain (root on top) and Summary, Details (serial, key, constraints, usage, SHA-1/SHA-256 digests) and Trust tabs; add to trusted certificates; export"
|
||
|
||
[[feature]]
|
||
id = "sign.trust-store"
|
||
title = "Trusted certificates store"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.panel"]
|
||
tools = ["sign_trust"]
|
||
evidence = ["crates/sign/tests/pdf.rs::signing_then_validating_with_and_without_trust", "crates/ui-egui/tests/signing.rs::drawing_a_signature_creating_an_id_signing_and_trusting", "crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
notes = "trusted certificates persist with the app's settings"
|
||
|
||
[[feature]]
|
||
id = "sign.trust-import"
|
||
title = "Import trusted certificates and set trust"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "partial"
|
||
commands = []
|
||
tools = ["sign_trust"]
|
||
evidence = ["crates/automation/tests/automation.rs::digital_ids_signing_and_validation_through_tools"]
|
||
notes = "import .cer/.pem/.der/.p12 through the tool; per-certificate trust settings not yet"
|
||
|
||
[[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 = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/sign/tests/pdf.rs::signing_with_a_timestamp_embeds_a_verified_rfc3161_token", "crates/sign/tests/pdf.rs::an_untrusted_timestamp_does_not_set_the_validation_time"]
|
||
notes = "Validation prefers the verified RFC 3161 timestamp time over /M when checking certificate validity and revocation, but only when the TSA chains to the trust store; an untrusted token is reported as an unverified timestamp and its time is not used. Missing: an explicit verification-time option (verify at a chosen date / now) in the validation API."
|
||
|
||
[[feature]]
|
||
id = "sign.changes-after-signing"
|
||
title = "List changes made after signing (MDP-aware diff)"
|
||
area = "H"
|
||
tier = "P0"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.panel"]
|
||
tools = ["sign_list"]
|
||
evidence = ["crates/sign/tests/pdf.rs::later_changes_are_classified_under_the_signature_permissions", "crates/sign/tests/pdf.rs::signing_an_existing_field_and_counter_signing", "crates/engine/src/tests.rs::signing_saving_trusting_and_commenting_afterwards"]
|
||
notes = "revision diff classified as signing, form fill, comments, metadata, page content or structure"
|
||
|
||
[[feature]]
|
||
id = "sign.view-signed-version"
|
||
title = "View the signed version"
|
||
area = "H"
|
||
tier = "P1"
|
||
milestone = "M9"
|
||
status = "shipped"
|
||
commands = ["sign.panel"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/signing.rs::drawing_a_signature_creating_an_id_signing_and_trusting"]
|
||
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 = "shipped"
|
||
commands = []
|
||
tools = ["sign_list"]
|
||
evidence = ["crates/engine/src/tests.rs::signing_saving_trusting_and_commenting_afterwards"]
|
||
notes = "validated when opened and after every edit"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["ocr.recognize"]
|
||
tools = ["ocr_recognize", "ocr_status"]
|
||
evidence = ["crates/engine/src/tests.rs::recognize_text_makes_a_scanned_page_searchable", "crates/automation/tests/automation.rs::ocr_tools_make_a_scan_searchable", "crates/ui-egui/tests/ocr.rs::recognize_text_dialog_adds_searchable_text"]
|
||
notes = "ocrs engine (MIT/Apache) with CC-BY-SA models fetched by `cargo xtask models`; runs on a worker thread with progress; one undoable step; pages with text are skipped."
|
||
|
||
[[feature]]
|
||
id = "ocr.recognize-range"
|
||
title = "Recognize text in the current page or a range"
|
||
area = "I"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["ocr.recognize"]
|
||
tools = ["ocr_recognize"]
|
||
evidence = ["crates/automation/tests/automation.rs::ocr_tools_make_a_scan_searchable", "crates/ui-egui/tests/ocr.rs::recognize_text_dialog_adds_searchable_text"]
|
||
notes = "All pages, current page or a range."
|
||
|
||
[[feature]]
|
||
id = "ocr.recognize-multiple"
|
||
title = "Recognize text in multiple files"
|
||
area = "I"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["ocr.recognize_batch"]
|
||
tools = ["ocr_recognize_files"]
|
||
evidence = ["crates/automation/tests/automation.rs::ocr_recognize_files_writes_searchable_copies", "crates/ui-egui/tests/ocr.rs::recognize_text_in_multiple_files_writes_searchable_copies"]
|
||
notes = "Pick PDFs, then an output folder; copies are saved incrementally under the same names, in the background with progress."
|
||
|
||
[[feature]]
|
||
id = "ocr.ocr-languages"
|
||
title = "OCR document language selection"
|
||
area = "I"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "partial"
|
||
commands = ["ocr.recognize"]
|
||
tools = ["ocr_recognize"]
|
||
evidence = ["crates/ui-egui/tests/ocr.rs::recognize_text_dialog_adds_searchable_text"]
|
||
notes = "Language choice exists, but the models read only the Latin alphabet without accents (English)."
|
||
|
||
[[feature]]
|
||
id = "ocr.searchable-image"
|
||
title = "Searchable image output (invisible text layer)"
|
||
area = "I"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "partial"
|
||
commands = ["ocr.recognize"]
|
||
tools = ["ocr_recognize"]
|
||
evidence = ["crates/engine/src/tests.rs::recognize_text_makes_a_scanned_page_searchable"]
|
||
notes = "Invisible text (Tr 3) fitted to each word box; the image is not deskewed or downsampled (exact mode only)."
|
||
|
||
[[feature]]
|
||
id = "ocr.searchable-image-exact"
|
||
title = "Searchable image (exact): leave the image untouched"
|
||
area = "I"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["ocr.recognize"]
|
||
tools = ["ocr_recognize"]
|
||
evidence = ["crates/engine/src/tests.rs::recognize_text_makes_a_scanned_page_searchable"]
|
||
notes = "The image is left untouched."
|
||
|
||
[[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 = "shipped"
|
||
commands = ["ocr.recognize"]
|
||
tools = ["ocr_recognize"]
|
||
evidence = ["crates/engine/src/tests.rs::recognize_text_makes_a_scanned_page_searchable", "crates/automation/tests/automation.rs::ocr_tools_make_a_scan_searchable"]
|
||
notes = "Pages that already have text are skipped and reported (skip_text_pages false reads them anyway)."
|
||
|
||
[[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 = "shipped"
|
||
commands = ["create.images"]
|
||
tools = ["doc_create"]
|
||
evidence = ["crates/create/src/tests.rs::jpeg_2000_images_are_embedded_as_is", "crates/create/src/tests.rs::images_export_as_jpeg_unchanged_and_others_as_png"]
|
||
notes = "PNG, JPEG, TIFF (multi-page), GIF (first frame), BMP, and JPEG 2000 (JP2 or codestream, embedded as JPXDecode with its capture resolution)"
|
||
|
||
[[feature]]
|
||
id = "create.from-multipage-tiff"
|
||
title = "Multi-page TIFF to multi-page PDF"
|
||
area = "J"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["create.images", "create.file"]
|
||
tools = ["doc_create"]
|
||
evidence = ["crates/create/src/tests.rs::bmp_gif_and_multi_page_tiff_images"]
|
||
notes = "every page; 1-bit scans stay 1-bit; resolution from the TIFF"
|
||
|
||
[[feature]]
|
||
id = "create.from-text"
|
||
title = "Create PDF from plain text"
|
||
area = "J"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["create.file"]
|
||
tools = ["doc_create"]
|
||
evidence = ["crates/create/src/tests.rs::text_is_wrapped_and_paginated", "crates/automation/tests/automation.rs::creating_and_reducing_through_tools"]
|
||
notes = "Helvetica 11 pt on US Letter; form feeds start pages"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["create.clipboard"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/create.rs::clipboard_images_and_text_become_new_pdfs"]
|
||
notes = "an image (one page at its size) or text; headless tools read files instead"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["create.blank"]
|
||
tools = ["doc_create"]
|
||
evidence = ["crates/ui-egui/tests/create.rs::opening_images_and_text_converts_them_to_new_pdfs", "crates/create/src/tests.rs::blank_documents", "crates/automation/tests/automation.rs::creating_and_reducing_through_tools"]
|
||
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 = "partial"
|
||
commands = ["create.images"]
|
||
tools = ["doc_create"]
|
||
evidence = ["crates/create/src/tests.rs::image_resolution_override_changes_size_without_resampling", "crates/automation/tests/automation.rs::creating_images_with_dpi_through_tools", "crates/ui-egui/tests/create.rs::image_import_dialog_chooses_dpi_and_cancels"]
|
||
notes = "Page sizing: embedded resolution (72 dpi fallback), fixed 72 dpi, or custom 1–1200 dpi; pixels are not resampled. Compression options are still absent. Windows image context menu opens the same chooser; other OS context-menu integration not yet."
|
||
|
||
[[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 = "partial"
|
||
commands = ["export.docx"]
|
||
tools = ["doc_export_office"]
|
||
evidence = ["crates/export/src/lib.rs::docx_is_a_word_package", "crates/export/src/lib.rs::grid_text_becomes_a_real_table_in_every_format", "crates/export/src/lib.rs::word_opens_text_with_control_characters_and_long_pages", "crates/engine/src/tests.rs::exporting_office_files_keeps_images", "crates/automation/tests/automation.rs::exporting_to_word_html_and_rtf", "crates/ui-egui/tests/compare.rs::export_to_word_html_and_rtf"]
|
||
notes = "Flowing Word document: paragraphs, Heading 1/2 from larger text, bold/italic, images inline, page breaks, and detected tables with spans/empty cells; checked with macOS textutil and opened in Microsoft Word (Windows). Characters XML forbids are dropped; the page size stays within Word's 22 in. Not yet: columns, fonts and colours, exact layout mode."
|
||
|
||
[[feature]]
|
||
id = "create.export-rtf"
|
||
title = "Export to RTF"
|
||
area = "J"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["export.rtf"]
|
||
tools = ["doc_export_office"]
|
||
evidence = ["crates/export/src/lib.rs::rtf_escapes_and_sizes", "crates/export/src/lib.rs::word_opens_text_with_control_characters_and_long_pages", "crates/automation/tests/automation.rs::exporting_to_word_html_and_rtf", "crates/ui-egui/tests/compare.rs::export_to_word_html_and_rtf"]
|
||
notes = "Paragraphs with sizes, bold, italic, Unicode; page breaks. Images are left out."
|
||
|
||
[[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 = "shipped"
|
||
commands = ["export.image"]
|
||
tools = ["doc_export_images", "page_render"]
|
||
evidence = ["crates/ui-egui/tests/export.rs::export_dialogs_write_images_and_text", "crates/engine/src/tests.rs::export_images_and_text_follow_the_working_file", "crates/automation/tests/automation.rs::exporting_images_and_text_through_tools"]
|
||
notes = "PNG at 72–600 dpi, page ranges; includes unsaved edits; background thread with progress"
|
||
|
||
[[feature]]
|
||
id = "create.export-jpeg-tiff"
|
||
title = "Export pages as JPEG or TIFF"
|
||
area = "J"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["export.image"]
|
||
tools = ["doc_export_images"]
|
||
evidence = ["crates/engine/src/tests.rs::pages_export_as_png_jpeg_and_tiff", "crates/automation/tests/automation.rs::exporting_images_and_text_through_tools"]
|
||
notes = "JPEG with quality, TIFF; flattened onto white"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["export.all_images"]
|
||
tools = ["doc_export_all_images"]
|
||
evidence = ["crates/create/src/tests.rs::images_export_as_jpeg_unchanged_and_others_as_png", "crates/create/src/tests.rs::indexed_and_one_bit_images_decode", "crates/automation/tests/automation.rs::exporting_all_images_through_tools", "crates/ui-egui/tests/export.rs::export_dialogs_write_images_and_text"]
|
||
notes = "each image once (page resources and nested forms); JPEG unchanged, others PNG (gray/RGB/CMYK/ICC/Indexed, 1–16 bpc, stencil masks, soft mask as alpha); JPX/JBIG2/CCITT/separations reported as skipped; minimum size filter"
|
||
|
||
[[feature]]
|
||
id = "create.export-html"
|
||
title = "Export to HTML"
|
||
area = "J"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "partial"
|
||
commands = ["export.html"]
|
||
tools = ["doc_export_office"]
|
||
evidence = ["crates/export/src/lib.rs::html_has_headings_paragraphs_and_images_in_order", "crates/export/src/lib.rs::grid_text_becomes_a_real_table_in_every_format", "crates/automation/tests/automation.rs::exporting_to_word_html_and_rtf", "crates/ui-egui/tests/compare.rs::export_to_word_html_and_rtf"]
|
||
notes = "Single HTML file with headings, paragraphs, inline images, and detected tables with spans/empty cells. Not yet: multi-page folder output, CSS from fonts."
|
||
|
||
[[feature]]
|
||
id = "create.export-text"
|
||
title = "Export to plain text"
|
||
area = "J"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["export.text"]
|
||
tools = ["doc_export_text", "text_extract"]
|
||
evidence = ["crates/ui-egui/tests/export.rs::export_dialogs_write_images_and_text", "crates/automation/tests/automation.rs::exporting_images_and_text_through_tools"]
|
||
notes = "plain reading-order text; accessible text (tags) comes with M12"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["optimize.reduce"]
|
||
tools = ["doc_reduce"]
|
||
evidence = ["crates/ui-egui/tests/create.rs::reduce_file_size_writes_a_compact_copy", "crates/engine/src/tests.rs::create_and_reduce", "crates/automation/tests/automation.rs::creating_and_reducing_through_tools", "crates/automation/tests/automation.rs::optimizing_through_tools"]
|
||
notes = "images above 225 ppi to 150 ppi JPEG medium, thumbnails dropped, resources merged, object streams"
|
||
|
||
[[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 = "partial"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/ui-egui/tests/create.rs::the_pdf_optimizer_dialog_saves_an_optimized_copy", "crates/automation/tests/automation.rs::optimizing_through_tools"]
|
||
notes = "Images, Discard Objects, Discard User Data, Clean Up; no Fonts or Transparency panels, no space audit"
|
||
|
||
[[feature]]
|
||
id = "optimize.downsample-images"
|
||
title = "Downsample colour, grey and mono images"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "partial"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/optimize/tests/optimize.rs::images_are_measured_where_drawn_and_downsampled", "crates/optimize/tests/optimize.rs::settings_choose_what_happens", "crates/automation/tests/automation.rs::optimizing_through_tools"]
|
||
notes = "colour and grayscale, bicubic, effective resolution measured where drawn; monochrome not yet"
|
||
|
||
[[feature]]
|
||
id = "optimize.recompress-images"
|
||
title = "Recompress images (JPEG, JPEG 2000, Flate)"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "partial"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/optimize/tests/optimize.rs::images_are_measured_where_drawn_and_downsampled", "crates/optimize/tests/optimize.rs::settings_choose_what_happens"]
|
||
notes = "JPEG (quality steps) and ZIP; no JPEG 2000"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/optimize/tests/optimize.rs::images_are_measured_where_drawn_and_downsampled"]
|
||
notes = "always on"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/redact/src/tests.rs::hidden_information_is_counted_and_removed", "crates/automation/tests/automation.rs::optimizing_through_tools"]
|
||
notes = "with links and actions (Remove Hidden Information)"
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-thumbnails"
|
||
title = "Discard embedded thumbnails"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/optimize/tests/optimize.rs::images_are_measured_where_drawn_and_downsampled"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-tags"
|
||
title = "Discard document tags"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/optimize/tests/optimize.rs::discards_and_clean_up", "crates/ui-egui/tests/create.rs::the_pdf_optimizer_dialog_saves_an_optimized_copy"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-bookmarks"
|
||
title = "Discard bookmarks"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/redact/src/tests.rs::hidden_information_is_counted_and_removed", "crates/automation/tests/automation.rs::optimizing_through_tools"]
|
||
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 = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/optimize/tests/optimize.rs::discards_and_clean_up"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-alternate-images"
|
||
title = "Discard alternate images"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/optimize/tests/optimize.rs::images_are_measured_where_drawn_and_downsampled"]
|
||
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 = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/redact/src/tests.rs::hidden_information_is_counted_and_removed", "crates/automation/tests/automation.rs::optimizing_through_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-comments"
|
||
title = "Discard comments, forms and multimedia"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/redact/src/tests.rs::hidden_information_is_counted_and_removed", "crates/automation/tests/automation.rs::optimizing_through_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-metadata"
|
||
title = "Discard document information and metadata"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/automation/tests/automation.rs::optimizing_through_tools", "crates/ui-egui/tests/create.rs::the_pdf_optimizer_dialog_saves_an_optimized_copy"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-attachments"
|
||
title = "Discard file attachments"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/redact/src/tests.rs::hidden_information_is_counted_and_removed", "crates/automation/tests/automation.rs::optimizing_through_tools"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "optimize.discard-private-data"
|
||
title = "Discard private application data (PieceInfo)"
|
||
area = "K"
|
||
tier = "P2"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/redact/src/tests.rs::hidden_information_is_counted_and_removed", "crates/automation/tests/automation.rs::optimizing_through_tools"]
|
||
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 = "partial"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/redact/src/tests.rs::hidden_information_is_counted_and_removed", "crates/automation/tests/automation.rs::optimizing_through_tools"]
|
||
notes = "hidden layer content removed; visible layers not flattened"
|
||
|
||
[[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 = "partial"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/optimize/tests/optimize.rs::discards_and_clean_up"]
|
||
notes = "unencoded streams; LZW not yet re-encoded"
|
||
|
||
[[feature]]
|
||
id = "optimize.remove-invalid-links"
|
||
title = "Remove invalid links, bookmarks and unreferenced destinations"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_optimize"]
|
||
evidence = ["crates/optimize/tests/optimize.rs::invalid_links_and_unreferenced_destinations_go"]
|
||
notes = "Clean Up: links to missing pages or names are removed; bookmarks keep their place (and children) but lose a broken destination; named destinations nothing refers to (links, bookmarks, actions anywhere) are removed from the name tree and the old /Dests; counts reported"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["optimize.advanced"]
|
||
tools = ["doc_audit_space"]
|
||
evidence = ["crates/optimize/tests/optimize.rs::space_audit_shares_the_file_out_by_kind", "crates/automation/tests/automation.rs::auditing_space_through_tools", "crates/ui-egui/tests/viewer.rs::optimizer_audits_space_usage"]
|
||
notes = "PDF Optimizer ▸ Audit space usage: 16 categories (images, content streams, fonts, X Object forms, structure, bookmarks, thumbnails, comments, forms, named destinations, colour spaces, shadings, patterns, graphics states, embedded files, overhead) with bytes and percentages"
|
||
|
||
[[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 = "partial"
|
||
commands = ["standards.pdfa"]
|
||
tools = ["pdfa_verify"]
|
||
evidence = ["crates/preflight/src/tests.rs::verify_reports_what_breaks_pdfa", "crates/preflight/src/tests.rs::convert_fixes_all_but_fonts", "crates/automation/tests/automation.rs::pdfa_through_tools", "crates/ui-egui/tests/compare.rs::pdfa_dialog_verifies_and_converts"]
|
||
notes = "PDF/A-2b/3b structural rules: identification, encryption, output intent, fonts embedded, annotations, actions, images, embedded files. Not yet: content-stream operator checks, transparency details, PDF/A-1 and -4, a veraPDF comparison."
|
||
|
||
[[feature]]
|
||
id = "optimize.pdfa-convert"
|
||
title = "Save as PDF/A (1b, 2b, 3b, 4)"
|
||
area = "K"
|
||
tier = "P1"
|
||
milestone = "M11"
|
||
status = "partial"
|
||
commands = ["standards.pdfa"]
|
||
tools = ["pdfa_convert"]
|
||
evidence = ["crates/preflight/src/tests.rs::convert_fixes_all_but_fonts", "crates/automation/tests/automation.rs::pdfa_through_tools", "crates/ui-egui/tests/compare.rs::pdfa_dialog_verifies_and_converts"]
|
||
notes = "Fixes XMP, output intent (generated sRGB ICC profile), forbidden actions, annotation flags, interpolation, encryption, AFRelationship. Not yet: embedding fonts, converting device CMYK, flattening transparency for PDF/A-1."
|
||
|
||
[[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 = "partial"
|
||
commands = ["standards.pdfa"]
|
||
tools = ["pdfa_convert", "pdfa_verify"]
|
||
evidence = ["crates/preflight/src/tests.rs::convert_fixes_all_but_fonts", "crates/automation/tests/automation.rs::pdfa_through_tools", "crates/ui-egui/tests/compare.rs::pdfa_dialog_verifies_and_converts"]
|
||
notes = "sRGB output intent added and listed; no choice of other conditions yet."
|
||
|
||
[[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 = "partial"
|
||
commands = ["standards.pdfa"]
|
||
tools = ["pdfa_verify"]
|
||
evidence = ["crates/automation/tests/automation.rs::pdfa_through_tools", "crates/ui-egui/tests/compare.rs::pdfa_dialog_verifies_and_converts"]
|
||
notes = "Declared PDF/A, PDF/UA and output intents shown in the PDF/A dialog and returned by pdfa_verify."
|
||
|
||
[[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 = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print", "printers"]
|
||
evidence = ["crates/ui-egui/tests/print.rs::print_dialog_lays_out_sheets_and_saves_a_pdf", "crates/engine/src/tests.rs::printing_lays_out_sheets_that_render"]
|
||
notes = "⌘P; settings and live sheet preview"
|
||
|
||
[[feature]]
|
||
id = "print.printer-selection"
|
||
title = "Choose printer and properties"
|
||
area = "L"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["printers", "doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::spooler_arguments_and_printer_list"]
|
||
notes = "CUPS printers via lpstat; Save as PDF; Windows spooler pending"
|
||
|
||
[[feature]]
|
||
id = "print.copies-collate"
|
||
title = "Copies and collation"
|
||
area = "L"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::spooler_arguments_and_printer_list"]
|
||
notes = "lp -n, collate option"
|
||
|
||
[[feature]]
|
||
id = "print.page-range"
|
||
title = "Print all, current page or a range (labels allowed)"
|
||
area = "L"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::page_selection", "crates/automation/tests/automation.rs::printing_through_tools"]
|
||
notes = "all, current, ranges with labels and open ends"
|
||
|
||
[[feature]]
|
||
id = "print.odd-even-reverse"
|
||
title = "Odd/even pages and reverse order"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::page_selection"]
|
||
notes = ""
|
||
|
||
[[feature]]
|
||
id = "print.scaling"
|
||
title = "Fit, actual size, shrink oversized, custom scale"
|
||
area = "L"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::size_modes"]
|
||
notes = "fit, actual size, shrink oversized, custom %"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::spooler_arguments_and_printer_list"]
|
||
notes = "long/short edge via the spooler"
|
||
|
||
[[feature]]
|
||
id = "print.n-up"
|
||
title = "Multiple pages per sheet"
|
||
area = "L"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::multiple_pages_per_sheet", "crates/automation/tests/automation.rs::printing_through_tools", "crates/print/src/tests.rs::cut_stack_keeps_piles_in_order_and_blanks_in_place", "crates/print/src/tests.rs::cutting_and_stacking_recovers_every_selected_page", "crates/automation/tests/automation.rs::cut_stack_printing_through_tools", "crates/ui-egui/tests/print.rs::cut_stack_dialog_previews_saves_and_refuses_duplex"]
|
||
notes = "2/4/6/9/16, four sequential orders, borders, auto-rotate; also single-sided cut-and-stack order with aligned gutter cut marks, blank padding and range/reverse support (an extension, not an Acrobat parity claim). Duplex cut-and-stack is refused."
|
||
|
||
[[feature]]
|
||
id = "print.booklet"
|
||
title = "Booklet printing"
|
||
area = "L"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::booklets_pair_pages_for_folding"]
|
||
notes = "both/front/back, left/right binding"
|
||
|
||
[[feature]]
|
||
id = "print.poster"
|
||
title = "Poster / tiled printing with overlap and cut marks"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::posters_tile_with_overlap", "crates/automation/tests/automation.rs::printing_through_tools"]
|
||
notes = "tile scale, overlap, cut marks (labels pending)"
|
||
|
||
[[feature]]
|
||
id = "print.orientation"
|
||
title = "Auto portrait/landscape"
|
||
area = "L"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::size_modes"]
|
||
notes = "auto portrait/landscape"
|
||
|
||
[[feature]]
|
||
id = "print.comments-and-forms"
|
||
title = "Print document, markups, stamps or form fields only"
|
||
area = "L"
|
||
tier = "P0"
|
||
milestone = "M10"
|
||
status = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::imposed_pdf_has_the_sheets_and_honours_comments_and_forms"]
|
||
notes = "Print flag honoured"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/print.rs::print_dialog_lays_out_sheets_and_saves_a_pdf"]
|
||
notes = "live sheet preview with sheet navigation"
|
||
|
||
[[feature]]
|
||
id = "print.grayscale"
|
||
title = "Print in grayscale"
|
||
area = "L"
|
||
tier = "P1"
|
||
milestone = "M10"
|
||
status = "partial"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/print/src/tests.rs::spooler_arguments_and_printer_list"]
|
||
notes = "asks the printer for monochrome; the PDF itself stays in colour"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["print.dialog"]
|
||
tools = ["doc_print"]
|
||
evidence = ["crates/ui-egui/tests/print.rs::print_dialog_lays_out_sheets_and_saves_a_pdf", "crates/automation/tests/automation.rs::printing_through_tools", "crates/engine/src/tests.rs::printing_lays_out_sheets_that_render"]
|
||
notes = "Save as PDF from the dialog"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::an_untagged_document_fails_the_document_rules", "crates/a11y/src/tests.rs::a_well_tagged_document_passes", "crates/ui-egui/tests/accessibility.rs::checking_fixing_skipping_and_reporting", "crates/automation/tests/automation.rs::accessibility_check_report_and_fixes_through_tools"]
|
||
notes = "full check: options dialog (report, page range, 32 rules by category, 31 on by default), results panel by category with Passed / Failed / Needs manual check / Skipped and findings that go to their page"
|
||
|
||
[[feature]]
|
||
id = "a11y.checker-report"
|
||
title = "Accessibility report"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.report"]
|
||
tools = ["accessibility_report"]
|
||
evidence = ["crates/a11y/src/tests.rs::rules_have_ids_and_the_report_lists_them", "crates/ui-egui/tests/accessibility.rs::checking_fixing_skipping_and_reporting", "crates/automation/tests/automation.rs::accessibility_check_report_and_fixes_through_tools"]
|
||
notes = "HTML report: summary counts and a table per category with descriptions and findings"
|
||
|
||
[[feature]]
|
||
id = "a11y.checker-fix"
|
||
title = "Fix, skip, explain and re-check rules"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_fix"]
|
||
evidence = ["crates/ui-egui/tests/accessibility.rs::checking_fixing_skipping_and_reporting", "crates/automation/tests/automation.rs::accessibility_check_report_and_fixes_through_tools"]
|
||
notes = "right-click a rule: Fix (language via Document Properties, title, tab order), Skip Rule (kept on Check Again), Explain, Check Again, Show Report, Options"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-permission-flag"
|
||
title = "Rule: accessibility permission flag"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::an_untagged_document_fails_the_document_rules"]
|
||
notes = "rule permission-flag: the file's /P bit 10 whichever password opened it"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-image-only"
|
||
title = "Rule: image-only PDF"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::scans_identity_fonts_and_long_documents"]
|
||
notes = "rule image-only: images and no text on the checked pages"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-tagged-pdf"
|
||
title = "Rule: tagged PDF"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::an_untagged_document_fails_the_document_rules"]
|
||
notes = "rule tagged-pdf: StructTreeRoot and MarkInfo Marked true"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-logical-order"
|
||
title = "Rule: logical reading order (manual)"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::a_well_tagged_document_passes"]
|
||
notes = "rule logical-reading-order: needs manual check"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-primary-language"
|
||
title = "Rule: primary language"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/automation/tests/automation.rs::accessibility_check_report_and_fixes_through_tools"]
|
||
notes = "rule primary-language: catalog /Lang; fix via Document Properties or accessibility_fix"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-title"
|
||
title = "Rule: title shown in the window"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/automation/tests/automation.rs::accessibility_check_report_and_fixes_through_tools"]
|
||
notes = "rule title: Info Title and DisplayDocTitle; Fix sets both"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-bookmarks"
|
||
title = "Rule: bookmarks in long documents"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::scans_identity_fonts_and_long_documents"]
|
||
notes = "rule bookmarks: more than 20 pages need bookmarks"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-color-contrast"
|
||
title = "Rule: colour contrast (manual)"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/automation/tests/automation.rs::accessibility_check_report_and_fixes_through_tools"]
|
||
notes = "rule color-contrast: needs manual check; off by default as in Acrobat"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-tagged-content"
|
||
title = "Rule: all content tagged or artifact"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::page_content_and_annotation_problems_are_found"]
|
||
notes = "rule tagged-content: painting outside MCID or Artifact marked content, through form XObjects; counted per page"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-tagged-annotations"
|
||
title = "Rule: tagged annotations"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::page_content_and_annotation_problems_are_found"]
|
||
notes = "rule tagged-annotations: annotations other than popups, widgets and multimedia need /StructParent"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-tab-order"
|
||
title = "Rule: tab order follows structure"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::page_content_and_annotation_problems_are_found"]
|
||
notes = "rule tab-order: pages with annotations need /Tabs /S; Fix sets it on every page"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-character-encoding"
|
||
title = "Rule: reliable character encoding"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::scans_identity_fonts_and_long_documents"]
|
||
notes = "rule character-encoding: ToUnicode, a standard encoding, or a predefined CJK collection; each font once"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-tagged-multimedia"
|
||
title = "Rule: tagged multimedia"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::a_well_tagged_document_passes"]
|
||
notes = "rule tagged-multimedia: Screen, Movie, Sound, RichMedia and 3D annotations need /StructParent"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-screen-flicker"
|
||
title = "Rule: screen flicker (manual)"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::a_well_tagged_document_passes"]
|
||
notes = "rule screen-flicker: needs manual check"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-scripts"
|
||
title = "Rule: scripts (manual)"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/automation/tests/automation.rs::accessibility_check_report_and_fixes_through_tools"]
|
||
notes = "rule scripts: needs manual check"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-timed-responses"
|
||
title = "Rule: timed responses (manual)"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::a_well_tagged_document_passes"]
|
||
notes = "rule timed-responses: needs manual check"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-navigation-links"
|
||
title = "Rule: navigation links (manual)"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::a_well_tagged_document_passes"]
|
||
notes = "rule navigation-links: needs manual check"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-tagged-fields"
|
||
title = "Rule: tagged form fields"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::a_well_tagged_document_passes"]
|
||
notes = "rule tagged-form-fields: widgets need /StructParent"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-field-descriptions"
|
||
title = "Rule: field descriptions"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::page_content_and_annotation_problems_are_found"]
|
||
notes = "rule field-descriptions: fields need a tooltip (/TU)"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-figure-alt"
|
||
title = "Rule: figures have alternate text"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule figures-alt-text: Figure elements need Alt or ActualText (or an ancestor's)"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-nested-alt"
|
||
title = "Rule: no nested alternate text"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule nested-alt-text: Alt inside an element with Alt"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-alt-associated"
|
||
title = "Rule: alternate text associated with content"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule alt-text-associated: Alt on an element with no content beneath it"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-alt-hides-annot"
|
||
title = "Rule: alternate text does not hide annotations"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule alt-text-hides-annotation: Alt on an element containing an OBJR"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-other-alt"
|
||
title = "Rule: other elements have alternate text"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule other-elements-alt-text: Formula elements need Alt"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-table-rows"
|
||
title = "Rule: table rows"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule table-rows: TR parent is Table, THead, TBody or TFoot"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-table-cells"
|
||
title = "Rule: TH and TD in rows"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule table-cells: TH/TD parent is TR"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-table-headers"
|
||
title = "Rule: tables have headers"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule table-headers: at least one TH"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-table-regularity"
|
||
title = "Rule: table regularity"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::row_and_column_spans_keep_a_table_regular"]
|
||
notes = "rule table-regularity: equal row widths counting ColSpan and RowSpan"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-table-summary"
|
||
title = "Rule: table summary"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule table-summary: Summary table attribute"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-list-items"
|
||
title = "Rule: list items"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule list-items: LI parent is L"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-lbl-lbody"
|
||
title = "Rule: Lbl and LBody"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule lbl-lbody: Lbl/LBody parent is LI (TOCI and notes allowed)"
|
||
|
||
[[feature]]
|
||
id = "a11y.rule-heading-nesting"
|
||
title = "Rule: heading nesting"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["a11y.check"]
|
||
tools = ["accessibility_check"]
|
||
evidence = ["crates/a11y/src/tests.rs::structure_problems_are_found"]
|
||
notes = "rule heading-nesting: H1–H6 never skip a level going down; the first is H1"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["a11y.alt_text"]
|
||
tools = ["accessibility_figures", "accessibility_set_alt"]
|
||
evidence = ["crates/a11y/src/tests.rs::figures_are_listed_with_their_alt_text_and_place", "crates/a11y/src/tests.rs::setting_alt_text_and_marking_decorative", "crates/ui-egui/tests/accessibility.rs::setting_alternate_text_figure_by_figure", "crates/automation/tests/automation.rs::accessibility_check_report_and_fixes_through_tools"]
|
||
notes = "Set Alternate Text dialog: each tagged figure (role map honoured) with a preview of where it is drawn, its alternate text, Decorative figure (content becomes /Artifact, element leaves the tags and parent tree), Previous/Next, Save & Close as one undo step"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["file.properties"]
|
||
tools = ["doc_initial_view"]
|
||
evidence = ["crates/organize/src/tests.rs::initial_view_round_trips_and_keeps_scripts", "crates/automation/tests/automation.rs::initial_view_through_tools", "crates/ui-egui/tests/viewer.rs::initial_view_is_edited_and_honoured_on_open"]
|
||
notes = "Document Properties ▸ Initial View ▸ Language (catalog /Lang)"
|
||
|
||
[[feature]]
|
||
id = "a11y.set-title-display"
|
||
title = "Show the document title in the window"
|
||
area = "M"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["file.properties"]
|
||
tools = ["doc_initial_view"]
|
||
evidence = ["crates/automation/tests/automation.rs::initial_view_through_tools", "crates/ui-egui/tests/viewer.rs::tab_and_window_show_the_document_title_when_asked"]
|
||
notes = "Initial View ▸ Show: Document Title (/DisplayDocTitle); the tab and window then show the title"
|
||
|
||
[[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 = "shipped"
|
||
commands = ["doc.compare"]
|
||
tools = ["doc_compare", "doc_compare_mark"]
|
||
evidence = ["crates/compare/src/lib.rs::insertions_deletions_and_replacements", "crates/engine/src/tests.rs::comparing_two_versions_of_a_document", "crates/automation/tests/automation.rs::comparing_documents_through_tools", "crates/ui-egui/tests/compare.rs::compare_two_versions"]
|
||
notes = "Word-level Myers diff with patience anchors for long files; replaced/inserted/deleted shaded on the new file, listed in the Compare panel, or added as comments."
|
||
|
||
[[feature]]
|
||
id = "misc.compare-visual"
|
||
title = "Compare files: visual differences"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M12"
|
||
status = "shipped"
|
||
commands = ["doc.compare"]
|
||
tools = ["doc_compare"]
|
||
evidence = ["crates/compare/src/lib.rs::finds_changed_regions", "crates/engine/src/tests.rs::comparing_two_versions_of_a_document", "crates/automation/tests/automation.rs::comparing_documents_through_tools"]
|
||
notes = "Pages rendered at 72 dpi and compared page by page; differing regions shaded orange where no text change explains them."
|
||
|
||
[[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 = "shipped"
|
||
commands = ["doc.compare"]
|
||
tools = ["doc_compare_report"]
|
||
evidence = ["crates/engine/src/tests.rs::comparing_two_versions_of_a_document", "crates/automation/tests/automation.rs::comparing_documents_through_tools", "crates/ui-egui/tests/compare.rs::compare_two_versions"]
|
||
notes = "Summary report as a new PDF (counts and every change with pages)."
|
||
|
||
[[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 = "shipped"
|
||
commands = ["actions.wizard"]
|
||
tools = ["action_list", "action_run"]
|
||
evidence = ["crates/engine/src/tests.rs::actions_run_their_steps_on_files", "crates/automation/tests/automation.rs::actions_through_tools", "crates/ui-egui/tests/actions.rs::run_and_create_actions"]
|
||
notes = "Pick an action and files; results go to a chosen folder, in the background with progress."
|
||
|
||
[[feature]]
|
||
id = "misc.action-wizard-create"
|
||
title = "Create and edit actions"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M13"
|
||
status = "shipped"
|
||
commands = ["actions.wizard"]
|
||
tools = ["action_run"]
|
||
evidence = ["crates/automation/tests/automation.rs::actions_through_tools", "crates/ui-egui/tests/actions.rs::run_and_create_actions"]
|
||
notes = "New/Edit/Delete actions with 12 step kinds (OCR, reduce size, remove hidden information, sanitize, flatten, watermark, header, footer, title, detect fields, JavaScript); remembered between runs."
|
||
|
||
[[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 = "shipped"
|
||
commands = ["actions.distribution"]
|
||
tools = ["action_run"]
|
||
evidence = ["crates/engine/src/tests.rs::actions_run_their_steps_on_files", "crates/automation/tests/automation.rs::actions_through_tools", "crates/ui-egui/tests/actions.rs::run_and_create_actions"]
|
||
notes = "Remove hidden information, flatten comments and fields."
|
||
|
||
[[feature]]
|
||
id = "misc.action-optimize-scans"
|
||
title = "Built-in action: optimize scanned documents"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M13"
|
||
status = "shipped"
|
||
commands = ["actions.optimize_scans"]
|
||
tools = ["action_run"]
|
||
evidence = ["crates/engine/src/tests.rs::actions_run_their_steps_on_files", "crates/automation/tests/automation.rs::actions_through_tools"]
|
||
notes = "Recognize text, then reduce file size."
|
||
|
||
[[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 = "partial"
|
||
commands = ["actions.wizard", "ocr.recognize_batch"]
|
||
tools = ["action_run", "ocr_recognize_files"]
|
||
evidence = ["crates/automation/tests/automation.rs::actions_through_tools", "crates/ui-egui/tests/actions.rs::run_and_create_actions"]
|
||
notes = "Actions and OCR run over chosen files; whole folders (with subfolders) not yet."
|
||
|
||
[[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", "apps/pdfcraft-cli/tests/stdout.rs::output_commands_exit_cleanly_when_the_reader_has_closed"]
|
||
notes = "CLI stdout exits cleanly if a pipe reader closes early; other output errors and command failures remain errors."
|
||
|
||
[[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", "crates/automation/tests/automation.rs::root_refusals_do_not_reveal_what_exists_outside", "apps/pdfcraft-cli/tests/run_root.rs::script_image_output_is_confined_to_the_root", "crates/automation/tests/automation.rs::file_names_from_documents_stay_in_the_output_folder"]
|
||
notes = "One refusal for every path outside the root, existing or not (#136). `run --script` images (`out`) are confined too (#137). Check-then-write, not openat-style: a process racing the tool on the same disk could swap a folder for a link between the check and the write."
|
||
|
||
[[feature]]
|
||
id = "misc.mcp-resources"
|
||
title = "MCP resources for page images and text"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M13"
|
||
status = "shipped"
|
||
commands = []
|
||
tools = ["doc_info", "text_extract", "page_render"]
|
||
evidence = ["crates/automation/tests/automation.rs::mcp_resources_expose_open_documents", "crates/automation/src/mcp.rs::resource_uris_parse"]
|
||
notes = "resources/list, resources/templates/list and resources/read: pdfcraft://doc/{doc}/info (JSON), /text, /page/{page}/text, /page/{page}/image?dpi= (PNG)"
|
||
|
||
[[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 = "partial"
|
||
commands = ["app.preferences"]
|
||
tools = ["js_enabled"]
|
||
evidence = ["crates/ui-egui/tests/javascript.rs::document_scripts_and_preferences", "crates/ui-egui/tests/ui.rs::preferences_selects_default_workspace_for_next_open", "crates/ui-egui/tests/control.rs::control_default_workspace_and_session_override"]
|
||
notes = "Preferences dialog with Interface language, Documents and view (persistent default workspace with session override), Identity and JavaScript; other categories still live in their tools, and it isn't generated from a schema yet."
|
||
|
||
[[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 = "partial"
|
||
commands = []
|
||
tools = []
|
||
evidence = ["crates/ui-egui/tests/view.rs::default_page_display_is_used_for_new_documents", "crates/ui-egui/tests/view.rs::default_zoom_is_used_for_new_documents", "crates/ui-egui/tests/view.rs::preferences_set_the_default_zoom"]
|
||
notes = "Preferences ▸ Documents and view: Default page display and Default zoom (Fit to width, Zoom to page level or a percentage) for every new tab; a PDF that asks for a layout or zoom gets it. Missing: Acrobat's resolution, rendering and page-content options"
|
||
|
||
[[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 = "partial"
|
||
commands = ["app.preferences"]
|
||
tools = []
|
||
evidence = ["crates/ui-egui/src/i18n/mod.rs::windows_display_language_resolves_chinese_scripts", "crates/ui-egui/src/i18n/mod.rs::windows_display_language_query_returns_a_locale_tag", "crates/ui-egui/src/i18n/mod.rs::language_persists_and_invalid_input_keeps_current_language", "crates/ui-egui/src/i18n/mod.rs::bundled_catalogs_are_consistent", "crates/ui-egui/src/i18n/mod.rs::japanese_covers_commands_and_catalogue", "crates/ui-egui/src/i18n/mod.rs::simplified_chinese_covers_commands_and_catalogue", "crates/ui-egui/src/i18n/mod.rs::simplified_chinese_covers_ui_literals", "crates/ui-egui/src/i18n/mod.rs::simplified_chinese_history_and_diagnostics_preserve_user_values", "crates/ui-egui/tests/control.rs::simplified_chinese_signature_prompts_and_errors_keep_document_state", "crates/ui-egui/tests/control.rs::language_switch_preserves_document_and_command_ids", "crates/ui-egui/tests/control.rs::japanese_controls_and_search_keep_command_ids", "crates/ui-egui/tests/control.rs::japanese_dialogs_errors_and_custom_action_names"]
|
||
notes = "Preferences switches Auto (system language)/English/Japanese/Simplified Chinese/Traditional Chinese/Czech/Brazilian Portuguese and persists; ui.set language auto|en|ja|zh-hans|zh-hant|cs|pt-br and ui.state expose it. TSV catalogs per language (same system as PhotoCraft); every dialog, panel and notice goes through tl!. Japanese, Traditional Chinese and Simplified Chinese cover commands, dialogs, panels, shortcuts and history labels; Czech and Brazilian Portuguese the menus. Engine/OS error details stay untranslated. Auto detects the Windows display language after locale environment overrides, with Chinese script/region mapping and English fallback. Missing: the other catalogs filled in, Chinese font delivery, a full localization audit."
|
||
|
||
[[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 = "shipped"
|
||
commands = ["app.preferences"]
|
||
tools = ["js_enabled"]
|
||
evidence = ["crates/ui-egui/tests/javascript.rs::document_scripts_and_preferences"]
|
||
notes = "Enable Acrobat JavaScript, remembered between runs."
|
||
|
||
[[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 = "Only a first step exists: Arabic and Hebrew file names and paths in the tab strip, the recent-files list and Properties have glyphs (a craft-fonts Arab face, or an installed font on desktop) and are shown in display order (crates/ui-egui/src/bidi.rs). Missing: multi-line text (bookmarks, comments, search results), text input, a mirrored layout and a right-to-left interface language."
|
||
|
||
[[feature]]
|
||
id = "misc.installers"
|
||
title = "Signed installers for macOS, Windows and Linux"
|
||
area = "N"
|
||
tier = "P1"
|
||
milestone = "M14"
|
||
status = "planned"
|
||
commands = []
|
||
tools = []
|
||
evidence = []
|
||
notes = "Windows MSI creates a Start menu shortcut and an optional (default on) desktop shortcut and confirms full-UI completion with Finish (#143); packaging/windows/test-msi.ps1 checks the compiled MSI and windows-arm64.yml checks installation/removal. Signed installers across platforms remain planned."
|
||
|
||
[[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 = ""
|