Repository navigation
Highlights
- If you redacted with Search in 1.2.0: on a page whose text carries a ligature, an emoji or a letter written with a separate accent mark, a box placed from a Search match could sit beside part of the match, and verification could still pass it. In 1.3.0 a box covers the whole match, and verification fails when part of a match is left uncovered. Open any such redacted copy and look at the edges of each box, or redact the original again with this version.
- Photos and images: JPEG, PNG and HEIC images open from Photos (up to 20 at a time), from Files or by a drop. Each becomes one page of a new PDF, redrawn from its pixels, so the photo's metadata, such as its location, is not carried into it.
- Password-protected PDFs open once you enter the password. The import checks run on the unlocked document, and the redacted copy is not password-protected; the app says so at import and on the Share card.
- Stricter import: a document the app cannot read in full, or one carrying JavaScript or a launch action, is refused rather than opened unscanned, and a file over 50 MB is refused by its size before it is read.
- Hidden text: Searchable Redaction recognizes more ways a page can hide text, in a hidden layer or in near-white, nearly transparent or covered ink, and exports such a page as an image only.
- Marking: Mark Whole Page covers the page on screen with one region, and a double-tap returns to the fit zoom. A region drawn by hand is checked line by line, as the page text reads it.
- Verification results are a grouped list: the headline shows how many checks passed, names the outcome and says what to do next, and each problem row links to its page.
- Removed: code that no shipped build reached, including the face, barcode and signature detectors, the freeform and polygon drawing tools, and the search sheet's disabled diagnostics.
A password-protected PDF opens with its password, a photo or an image becomes one page of a new PDF you can redact, and a document the app cannot read in full, or whose pages it cannot render, is refused at import rather than opened unscanned. In the editor, Mark Whole Page covers the page on screen with one region and a double-tap returns to the fit zoom. Verification re-runs each applied search and scan on the original page and the OCR check reports a term's start or end read beside a box, so a box placed off its text no longer passes every check; the results list groups each check by its outcome and counts only passing checks as passed. The app opts into Apple's hardened-process protections in their reporting mode, the engine's public surface narrows to the document-runner API, and the surfaces no shipped build reached — the search sheet's disabled diagnostics, the in-editor importer, the paused-detection resume path, and the face, barcode, and signature detectors — are removed.
Added
- Search: when the text could not be fitted to the printed word, a box is drawn wider than the word; the results sheet names the pages holding such a box, and the page in view says so above the page, before and after the boxes are applied, asking to keep such a box wide: shrinking it back to the word can leave part of the word readable.
- Editor: the iPhone overflow menu offers Mark Whole Page for the page in view, on one-page documents too — or Unmark Whole Page, checked, on a marked page — through the page bar's own toggle.
- At the accessibility text sizes the page bar's counter shows its position above its region count, each on one line, instead of wrapping a letter at a time.
- Password sheet: when a refused password holds an accented letter, a line beneath the refusal says such a password may not open the document in Resecta even when it is correct (Known Issues KI-12).
- Settings: a "Rate Resecta on the App Store" row under Support opens the App Store's review page for the app.
- Acknowledgements: the third-party data and typeface notices the app ships (the repo-root NOTICE) are bundled and readable in-app, from the first-launch gate beside the EULA and Privacy Policy and from Settings.
- Photos and images (JPEG, PNG and HEIC) can be opened from the system Photos picker (up to 20 at a time, in the order chosen), from Files, or by a drop onto an open document; each image becomes one page of a new PDF, redrawn from its decoded pixels at 300 pixels per inch, so the image's embedded metadata is not carried into the document.
- Page bar: Mark Whole Page covers the page on screen with one region, through the same path a drawn region takes, and no search term is captured from it; on a marked page the control reads Unmark Whole Page and removes it, each way one undo step, and it keeps its place when the count changes; it is hidden on a one-page document, where the … menu keeps it.
- Double-tap an empty part of the page to return to the fit zoom.
- Import: a password-protected PDF can be opened by entering its password; the import checks run on the unlocked document, and the redacted copy is not password-protected. The password is held in memory while the document is open and released when the session closes; the failed-state card beneath the password sheet reads as locked rather than damaged. A document that was encrypted — one opened with a password, or one carrying only an owner password — shows a notice at import and a caption on the Share card saying the redacted copy carries neither the password nor the original's usage restrictions.
- With Differentiate Without Color on, every region of a multi-region selection also carries a dashed outline.
Changed
- Tapping an empty part of the page clears the region selection; with Add to Selection on, a tap on empty page changes nothing.
- Tapping one region of a selected group without moving it selects only that region; dragging it still moves the group.
- Holding a region opens its menu; to move a region, select it, then drag it.
- The long-press menu on a region of a selected group acts on the whole selection — Delete N Regions and Deselect All — and leaves the selection as it was.
- Undo and Redo name region steps with the controls' word, Region (Add Region, Duplicate Region, Delete Regions, Move Region, Resize Region), and Redo names the action it repeats.
- A search apply's undo step is named by the regions it creates, not counting results already covered.
- Duplicate selects the copy.
- A one-page document shows the page bar with its region count; the page arrows are hidden there. The counter reads "0 regions" on a page with none, drawn and spoken.
- A document dropped over an open one that holds drawn regions or selected matches asks before replacing it; Cancel keeps the open document. With the Search sheet up, such a drop is refused until the sheet is dismissed.
- With Add to Selection on, a capsule above the page bar reads "Adding to selection · N selected" with a Done button that turns the mode off.
- At the largest accessibility text sizes the drawing caption no longer covers the bottom of the page: the capsules' text stops growing at Accessibility 1.
- Editor toolbar on iPhone: one Undo button — tap to undo, touch and hold for Undo and Redo with the step's name — and Redact as a teal scissors button beside it; Settings moves into the … menu, which lists the selection actions first and Home last, and the separate More menu is gone. At the largest text sizes Redact moves into the … menu and Undo stays in the bar. The document's name is no longer drawn clipped in the bar.
- The "match nearby" notice says Mark, matching the Marked N notice.
- With VoiceOver on, the Rectangle tool is disabled with a hint pointing to Search and Scan.
- Deleting several selected regions happens at once, as one undo step, and a "Deleted N regions." notice offers Undo; a delete from the … menu or ⌘⌫ ends Add to Selection.
- A run of arrow-key nudges is one undo step.
- Deleting selected regions from a hardware keyboard takes Command-Delete.
- The Discard selections confirmation is an alert with a Cancel button.
- Verification: a page read both from its text layer and by OCR (a text layer over a page image) is counted under each route, and when every page was read both ways the Search Re-check and Detection Sweep details say "Text was read from the output's text layer and by OCR from the rendered pages."
- A light haptic on adding or resizing a region.
- Share: when annotations in the original document — a black box, a stamp, an unapplied redaction mark — cover text that no redaction box covers, the share confirmation names those pages before the file is shared, whatever the verification result. Annotations are not part of the page image, so that text is visible in the exported file. On a scanned page, where the printed text is in the page image, any such annotation that no redaction box covers is named; on a rotated page an annotation is compared with the boxes as the page is shown.
- The App Store rating prompt asks once per app version, after the first share of a document whose checks passed, instead of after the third export.
- Home: the app's mark replaces the document glyph; the three entries — Open a Document, Import Photos and Try the Sample — are cards of one size, centred on the screen, without link lines. VoiceOver reads each entry's title and description with a hint, the masthead as a heading and the trust words as one line; under Reduce Motion a press shades the card instead of shrinking it, and under Reduce Transparency the cards are opaque. The first-launch agreement fades into the home screen and keeps I Agree pinned while its text scrolls beneath; the app-switcher placeholder shows the mark on the home screen's grey; the verification progress screen's trust words read as one line.
- Search sheet: the recent-search recall chips, the per-term section headers of the results list and the saved-search names are marked privacy-sensitive, matching the result rows and the saved-query previews.
- Import: a file over the 50 MB cap is refused by its size on disk before any byte is read, from the Files picker and from a drop alike; a dropped file arrives as a file and is read inside its transfer rather than as 50 MB of memory.
- Verification results: the screen is a grouped list. The headline shows how many checks passed in a ring, names the outcome in the list's words and says what to do next. Share is the main button; when a check failed or needs review, or Share cannot share, Keep Editing leads and Share is outlined or greyed with the reason under it, and the undo note sits under the actions on every run. Findings, checks that could not verify, notes and checks with nothing to check follow in their own sections with their counts; each problem row shows its status and a link per affected page that opens the preview at that page, five at most until the row is opened. The run's facts, the page modes and the items left unredacted share one section; the passing checks fold into one row, or list as Checks when every check passed. Page Modes names the pages of each mode, in ranges, instead of a button per page.
- Verification results: the headline is titled in the list's words (Checks Passed, Completed with Warnings, Some Checks Incomplete, Needs Review, Checks Failed, Verification Skipped) and says, on every verdict, which control to use next; a skipped verification names Run Verification, and on a Searchable run the full Redact that includes every check. The check titles, the one-line descriptions, a second line on what each check requires and what it does not see, the reasons a check had nothing to check, the needs-review and search re-check captions, the group headers with their counts (Findings, Could not verify, Notes, Nothing to check), the share-disabled reasons, the timing lines and the VoiceOver labels follow one vocabulary; the results headline's spoken label also states the counts, and Share says when sharing will ask you to confirm.
- Share: the confirmation names each check and its outcome (failed, needs review, could not verify, skipped) instead of quoting the check's message, lists skipped checks, and has its own title for incomplete checks and for unverified output.
- Documentation: the privacy policy describes the detection the app performs (the seventeen text categories a scan reads from the page text layer or from recognized text, with the bundled gazetteers, Bloom filters and context scorer; no face or barcode sentence), says the app makes no network requests of its own and names the two system services that do, drops the detection-priors clause no shipped path stores or reads, and states the failed-state screen, the delete-all controls, the per-session workspace and its sweep, and the in-place metadata patches as they work; the EULA's Searchable Redaction fallback names hidden text. Both effective dates move to 2026-10-15.
- Documentation: the threat model says the share confirmation lists a line for each check that reported or could not verify, and no longer describes OCR word hints the shipped flow never builds.
- Documentation: the security policy gains a recipe for verifying a signed release tag:
.github/allowed_signerslists the release-tag signing key undersecurity@resecta.app, andgit tag -vchecks a tag against it oncegpg.ssh.allowedSignersFilepoints at the file; the threat model's signed-tag row points at the recipe. - Import: a page the app could not render — a side under 10 points, a page-scale entry (
/UserUnit) other than the default, or a rotation PDFKit and Core Graphics read apart — is refused at import with the error the render would raise, instead of opening and failing at Redact. - Verification: a hand-drawn region captures the words the Searchable filter would touch, line by line, and checks each line as the page text reads it ("123-45-6789", "j.doe@example.com", "Account Number 1234" whole); a region over one word keeps that word; a single word of a longer run is checked on its own only when it carries a digit, is a capitalized word of two or three letters (a short surname such as Lee or Ng), or has four characters or more — and is not one of about 140 common English words (articles, prepositions, pronouns, auxiliaries, the nouns of forms and statements; a word that is also a common name, such as Will or Page, is not listed). A region covering the whole page adds no term and is counted in the Binary String Search's note with the regions on pages that have no text layer.
- Search sheet: the undo action for an applied search reads "Mark N Instances", the verb the toast uses, with no document text.
- Import: a document whose structure the app cannot read in full is refused rather than opened unscanned. The reader the active-content and hidden-layer walks inspect must open, be unlocked, read a catalog and agree with PDFKit on the page count; a walk that cannot read a page or follow an action chain to its bound refuses instead of answering clean; an
/OCPropertiesentry without its default configuration reads as a hidden layer. An owner-password-only document still opens. - Documentation: the licence notices name ParaNames' data licence (CC BY 4.0) with its attribution and the changes made, state the OpenStreetMap and OpenAddresses rows as provenance acknowledgements for inputs no build reads, and attribute the Inter typeface embedded in the bundled sample document.
- The Search sheet's OCR source row appears only when a page reads via OCR or an OCR filter is set.
- The Custom Terms screen states that a literal always-flag term matches inside words and in any case.
- The "Marked N" notice is shorter so its "Not redacted until you tap Redact" clause fits at the default text size.
- A saved search's suggested name no longer carries the query text.
- While a match walk is parked the page bar steps aside: the walk's arrows page, a page with no hits is reached by scrolling or by raising the sheet, and the bar is back once the sheet rises or the walk ends.
- Moving to another page keeps only the selected regions on the page in view; a delete or a nudge never reaches a region you cannot see.
- One wording per concept across the Search sheet and Custom Terms; the Search options' warnings and the result-cap banner use the warning color token; the saved-searches sheet has a translucent material background.
- One-line regions can be drawn at the fit zoom: a box needs 20 on-screen points along its long side and 8 across, and a box drawn under that size is no longer enlarged by an alignment guide.
- Alignment guides engage closer to an edge and only for regions beside the one being moved, so a box follows the finger across lines of applied regions.
- Verification: a check that had nothing to check is listed under Nothing to check instead of passing or adding a note — the Search Re-check and the Detection Sweep when no search or scan was applied, the two term searches when no redacted term was captured, and Character Count, Monospace Fonts and Character Sequence when every page was rasterized. Such a check neither passes the document nor lowers its result.
- Verification: the Detection Sweep reports Needs review when an item remains on the output in a category your applied scan covered and you did not leave unselected, such as an SSN in an image after an SSN scan. Before, it read as a note. An item only in a category no applied scan covered stays a note.
- The first launch after updating asks for agreement to the End-User License Agreement and Privacy Policy again, as both have changed since 1.2.0.
- The privacy policy describes photo and image input, password-protected PDFs and the fixed export file name, and lists the values the app stores.
Removed
- The search sheet's disabled audit-export, scan-coverage, doctype-diagnostic and category-strip surfaces, the navigation-scope controls, the custom-terms template picker and the two debug launch arguments that revealed them: each sat behind a flag that was false in every shipped build, so no shipped screen changes. The match rationale's classifier explanation and the coverage counts the verification screen reads stay.
- The workspace's in-editor file importer, which nothing could present (the drop target stays), and the second sample packet bundled with no screen that opened it.
- The paused-detection resume path.
- The unreachable face, barcode and signature detection code.
- The detection review that nothing staged any more: its list in the Scan interface, its walk and counter, the cross-page grouping, the banner's Review button, and the long-press menu over recognized words that opened a pre-selected search.
- Settings: the "Reset Detection History" row and the on-device accept and reject counts it cleared; only the removed review recorded them, and no scan read them. Counts an earlier version stored are deleted the first time the updated app opens.
- The Pencil circle-to-select on the search results; the app runs on iPhone, where no Pencil touch arrives.
- The unused snap-to-text setting and the text-row snapping code only it reached.
- The freeform and polygon drawing tools, which the editor no longer offered, and the code behind them.
- The engine's polygon regions: the polygon fill and its readback, the polygon character filter and spatial check, and the polygon word capture. A region every path now fills, filters and verifies is a rectangle; a region saved by an earlier version with polygon vertices is read as its bounding rectangle, which covers at least the polygon.
Security
- The app opts into Apple's hardened-process protections in their reporting mode.
- The pre-commit hook and the pull-request gate scan the whole shipping source, on every run, for the call-shaped networking and hand-off symbols (a bare
Link(, the Network framework's listener and browser, the socket and stream constructors,openURL,UIApplication.shared.open(, animport Networkline) against the sites pinned inScripts/audit-lint.shwith their reasons, one pin per site (the file, the symbol and an identifier of the call), so a second use in a file that already has a pinned site fails too; two sites are pinned, the Settings view'smailto:feedback link and its App Store rating link. - The shipped-asset fence (
Scripts/verify-shipped-asset-hashes.sh) verifies the gazetteer manifest's Ed25519 signature with a verifier outside the engine (OpenSSL 3, else CryptoKit throughswift; no verifier fails the fence), beside the blob and digest pins it already held. SECURITY.mdsays how a security fix is announced: the changelog'sSecurityheading of the release that carries it, the App Store availability date, a GitHub security advisory with a CVE through GitHub's CNA where a report warrants one, and that a fix reaches users only as an update.THREAT-MODEL.md's build-and-distribution boundary says that the app repository holds no Actions secret, so no workflow can sign or upload a build, and that the detection-data signing key never enters a repository.THREAT-MODEL.md§7 re-dated at28b9a777(2026-10-20), with rows for the release-tag ruleset (v*tags cannot be updated, deleted or force-pushed once pushed), immutable releases in the app repository, and the mail policy on the reporting domain (DMARCp=reject, SPF-all); the branch-rules row states that the admin role may bypass the required check on a pull request, and names the rulesets API as the check a reader can run without a login.- The Searchable-mode ink test treats a glyph box whose pixels all lie within a fixed tolerance of one color (twelve of 255 levels) as showing no ink, so near-white or faint, nearly transparent and nearly covered text exports as an image only; light but inked text, such as a pale footer or a watermark, keeps its text layer.
- The Searchable-mode ink test also reads the middle of each letter and digit without the glyph box's one-pixel margin, so text under a box drawn into a received page that stops at the text's edges, at its selection or at its ink, exports as an image only; where the content just above and below a glyph is textured, as on a scanned page or a noisy scanned bar, the test allows 24 levels instead of twelve.
- A redaction box made from a search or Scan match grows, before it is filled, over ink that crosses its edge and touches the text inside it, such as an accent above a capital or an italic overhang, by at most a quarter of the box's height; a box you draw is filled as drawn.
- Import refuses a PDF whose catalog, non-widget annotation or form field additional actions, or a rendition action, carry JavaScript or a launch action; a PDF with XFA form data or embedded files imports with a notice that form data and attachments are not carried into the redacted copy.
- Searchable redaction treats optional content as hidden when the default configuration turns layers off through its base state (a group its
/ONlist does not name counts, not the list's length), when the configuration carries a usage-application entry, when a page or form names a membership dictionary (content that can be hidden while every group is on), when a page inherits the resources that name it, or when a form object, a Type 3 font, a tiling pattern or a soft mask reachable from the page carries or names it; an optional-content entry that is not of its declared shape (a/Propertiesthat is no dictionary, a form with no/Subtype, an/OFFthat is no array) is read as hidden, and a glyph outside the visible page counts as showing no ink. The walk reads a resource dictionary shared by many forms once and stops on cancel. - Document canvas: with a hardware keyboard, Select All then Copy (⌘A, ⌘C) no longer places the document's text on the system pasteboard. The canvas view refuses the copy, cut, paste, select and select-all actions and builds its menu without the edit, look-up, learn and share groups; in 1.2.0 that keyboard path copied the whole document's text with no selection shown.
Fixed
- The redacted copy keeps each page's own size; a page larger than the first, turned from it, or rotated could be cut off.
- Search: a text, regex or multi-term search with no results on a document whose pages were read by text recognition no longer says the term is not in the document. It says the search covered the text Resecta could read, how many pages were read by text recognition, and that a misread word won't match, and suggests checking those pages by eye. After such a run the OCR controls' caption reads "No results from text recognition" instead of "Awaiting OCR results". A document whose pages all have a text layer keeps the earlier message (Known Issues KI-14).
- Import and Share: a form field drawn as an opaque cover — an empty black field placed over text — counts as an annotation, so the import notice names it; the share confirmation names the pages where such a field, a highlight painted opaque or dark over the text, or a watermark covers text that no redaction box covers. The export does not draw annotations, so that text is visible in the exported file.
- VoiceOver: when verification passes with checks that had nothing to check or with a note, the announcement at the end of the run speaks the results header's title and counts ("Checks Passed. 9 of 12 checks passed. 3 had nothing to check.") instead of "All checks completed without issues."
- Results: a Needs Review from the Detection Sweep alone reads "Keep Editing, then open Scan to review the pages below." instead of pointing to text search for terms it does not list.
- Search: a result counts as already covered, and Apply skips it, only when one box holds the whole of it. A box that stops a letter short of a name no longer leaves that letter printed; the result is applied, the Redact gate waits for it, and the draw nudge offers it. A search whose every result was already covered is still re-checked when you redact.
- Search: a typed page that also carries a pasted photo or ID (an image of at least 250,000 pixels, such as 741 × 469 at a word processor's 220 ppi) now has the image read for text as well; before, only an image covering about half the page was read (Known Issues KI-13).
- Search: a page whose text layer holds text Search cannot read (an unresolved or broken encoding, or characters with no position on the page) is now read by text recognition, or reported as not analysed when text recognition is off; before, Search reported nothing found on it.
- Search: on a scanned page, a phrase that runs from the end of one recognised line into the next is now found, with a box on each line it touches, and the Search Re-check reads such a phrase left on the redacted copy as remaining. Before, each recognised line was searched on its own, so the phrase was not found on scanned pages and the check read the page as clear.
- A marquee selection is no longer an undo step: Undo after a marquee undoes the last edit to the regions.
- With Add to Selection on, a region joins or leaves the selection when the finger lifts; holding it for its menu, pinching or scrolling from it no longer changes the selection.
- Undoing a delete selects the deleted regions again if they were selected.
- The paused, scan-summary and annotation notices share one place at the top of the editor and never stack; the paused notice shows first.
- The Processing was paused notice stays until it is dismissed, Restart is tapped or another run starts, so it is still there after time away from the app.
- While a match walk is parked, the walk's arrows and J / K step from the page shown.
- Duplicate of a region at the page's edge no longer lands exactly on its source: with no room toward the edge, the copy steps the other way.
- With Paranoid Mode on, Switch Mode in the … menu shows Secure Rasterization and is unavailable, with the reason, instead of offering Searchable Redaction the run would not use.
- Undoing a search apply leaves none of its regions selected.
- A tap or an arrow-key nudge that leaves a region where it was no longer records an undo step or discards the redacted output.
- Duplicating a region or undoing a delete no longer offers the nearby-match notice.
- Two search results over the same text in one apply make one region.
- A Scan region on a text page keeps the detector's confidence instead of reading as high confidence.
- An apply whose results are all already covered no longer records an empty undo step.
- The run discloses the unchecked results of every Scan applied from, not only the last apply's; undoing an apply restores the earlier disclosure.
- Settings opens while the Search sheet is parked; the sheet steps aside and returns when Settings closes.
- Select All on Page on a page without regions no longer empties the selection.
- VoiceOver announces region deletes.
- A new Search or Scan session opens at the medium height, whatever height the last session ended at.
- Add to Selection turns off on Esc and when the page has no regions; the overflow row shows its on/off state.
- Undo and Redo show the page they change, and VoiceOver names the step with its page.
- The region hover popover no longer opens while the Search sheet is up.
- A Search or Scan that stops at the 1,000-result cap now shows the cap banner and the unscanned page count on the run that reached it.
- A match skipped as already covered no longer reads "Applied" after the covering region is deleted or undone.
- The applied-search record stamps the options the run used, not the options changed afterwards.
- The in-sheet notice no longer covers the "M of N selected" footer.
- Re-running a search shows how many unapplied matches the re-run cleared.
- Custom-term matches get their own Scan chip and are excluded while another category filter is active.
- A re-Scan shows which results an existing region already covers.
- Dismiss with selected matches still names the unapplied matches it clears.
- Esc closes the Search sheet at the medium and large heights.
- Dragging a group of regions past the page edge keeps their spacing.
- A thin selected region keeps a central zone that moves it, and with Add to Selection on a tap on a neighbouring region selects it instead of resizing.
- Resizing a thin region from a side handle no longer grows its height; dragging a handle past the opposite edge flips the box instead of mirroring it.
- A resize interrupted by the system restores the region's previous size.
- Long-pressing a region lifts only that region into the menu preview.
- A box too small to keep shows a notice saying so, not only a VoiceOver announcement.
- A handle on the page edge keeps its full touch target.
- Editor: a different document replacing the open one turns the draw tool and Add to Selection off, so neither carries into the new document.
- Editor: Delete from a region's long-press menu or its VoiceOver action is its own Undo step, so redoing an earlier apply no longer brings the deleted region back, and the other selected regions stay selected.
- A document dropped over an open one starts without the previous document's matched text and region notes, as a document opened from the home screen does.
- The purge Re-run tapped after Keep Editing is judged like Redact: with selected matches not applied it is refused with the same hint and the selections stay, instead of the search being dropped.
- Search sheet: closing an untouched sheet with a swipe or a tap above it names the unapplied matches it clears, as its Dismiss button does.
- Editor: after a region is drawn, a nearby match that another region already covers is no longer offered; the offer and Apply read one coverage rule.
- Editor: the nearby-match offer's Add button adds the match after the search sheet has closed, instead of doing nothing.
- Verification results: a match applied on its own, from the compact strip or the nearby-match offer, is no longer counted among the results left unredacted; an undone apply counts again.
- Share: the shared copy is named redacted.pdf, without the export time, and a second share in the same session no longer fails on the copy an earlier share left behind.
- Verification results: after a verification interrupted by Stop or by leaving the app, the results no longer say the regions changed after the PDF was made when the PDF carries every region, and Run Verification re-checks that PDF instead of redacting again.
- Share: when the share sheet does not appear, the editor returns to the results with a notice instead of staying on "Preparing for sharing…", and that card has a Cancel.
- Verification results: when a run applied a scan and no typed search, the Search Re-check row reads the check's own line saying what the coverage check compared, instead of the re-run explainer; and the fragment row counts its boxes by the terms it names, not by the pages.
- Search and Scan: a page that carries both a text layer and a page-sized image (a scan with a stamp or a header, or a scan under a recognised-text layer) is read on both: the text layer and the recognised text of the image. Before, such a page was read from its text layer only, so text inside the image was never searched. When text recognition is off, the page is listed as not fully analysed.
- Search: a phrase whose words are separated by more than one space, a tab, a line break or an invisible character is now found, and a match that wraps onto a second line gets a box on each line, counted once per line. Before, such a phrase was not found at all.
- Search: in Regex mode, the start and end anchors (
^and$) match at each line of the page. Before, they matched only the start and the end of the whole page. - Search: with every term required, the result cap counts the matches that pass the every-term test. Before, a page dense in one term alone could fill the cap and leave later pages unread.
- Search: in Regex mode on a scanned page, a match that runs across two recognised lines is boxed on every line it touches. Before, the box covered only the line where the match began.
- Scan: a name read after a role word or honorific ("Mr.", "Patient:", "Plaintiff") is boxed to its full width when the page text carries more than one space between the name's words. Before, the box was one character short per extra space, the name's last character stayed visible, and the coverage check, which re-runs the same reading, did not report the gap.
- Scan of a scanned or sparse page: a value on a line below an emoji or another character wider than one UTF-16 unit is boxed on its own line. Before, each such character ahead of the match moved its line lookup one unit later, which usually widened the box to the next line and, for a value at the end of a line below enough of them, placed the box on the next line alone.
- Verification: the OCR Check reports a redacted term that is still readable outside every box on a page that also carries a fill artifact, and reads the line of a word box that reaches past a box's edge for the term as well. Before, a page with both findings read as the fill artifact's informational note, a term printed just past a box could be read only through a word box sampled where it overlapped the fill, the note counted as a pass, and the document verified as passed with the term still readable on that page.
- Verification: the Binary String Search fails a file's structural data only for a redacted term inside one of the file's string objects. Before, a redacted word that is also part of the PDF format — a surname such as "Page", or a number such as "612" — matched the file's own syntax (
/Type /Page, a page size of 612 points) and failed a correct file. - Verification: the Search Re-check's coverage check reads a password-protected original with 128-bit encryption unlocked with the session's password; when the original pages cannot be read it reports "could not verify" instead of passing, and it names each original page it could not read (a page too large to scan for text, one it could not open, or a pattern that ran out of time or was not accepted) instead of passing that page. The OCR Check reports the start or the end of a redacted term read beside a box on a page that also carries a fill artifact, with the results row naming the term and the box as the remedy.
- Verification: the Search Re-check also re-runs each applied search and scan on the original page and fails when a redaction box leaves part of a match uncovered, and the OCR Check reports the start or the end of a redacted term read beside a box. Before, a box placed off its text by one or more characters passed every check while the uncovered characters stayed printed on the page: the rebuilt text layer dropped them, the text-reading checks read that layer, and the OCR Check read only the box. The re-run reads a scanned or header-only page the way the search did; a search box that covers none of its matches on a page with a match no box covers is reported as uncovered, not as a match left unselected; a box that covers its own match and only grazes a neighbouring match that was left unselected does not fail the check; and a match set vertically is sampled along its run. A run that applied a scan and no typed search reports what the coverage check compared instead of saying the re-check did not run, and says so when the re-run found none of the scan's matches on the original pages. The re-check's own re-run on the output reads a header-only page through OCR, as the search does.
- Search: a match's rect now covers the matched glyphs on every page, in Text mode and in Regex mode. Before, on a page whose text layer carries a ligature or another character that normalization expands, the rect sat as many characters to the right as the expansions before the match had added (and Regex mode still placed it there); and on a page with an emoji, a letter written with a separate accent mark or another character wider than one text unit before the match, the rect sat to the left of the match and short by those units.
- A document refused for its page count says so, instead of reporting a size of 0 MB. After a failed import, Choose Another File (Choose Other Photos after a Photos import) opens the picker the document came through instead of landing on the home screen. The return to the home screen no longer shows an empty page for a moment.
- In Regex mode the Case-sensitive option now governs the pattern; with it off, "smith" matches "Smith".
- A PDF whose document-information dictionary holds a key PDFKit cannot read no longer ends the app when it is searched or verified: the write PDFKit performs on the way to the search copy and to the verification reader runs behind an exception boundary, and such a document reads as "could not read" on the paths that already handle it.
- Redact: a page that finishes after Stop no longer brings the progress card back for a frame — Stop invalidates the run, and the run's later reports are dropped.
- Redact: the per-page retry's memory check estimates the 150-DPI render it performs, instead of a 96-DPI render the rasterizer never makes.
- Done and Home reset the session through the one Start Over path, so a re-imported document no longer shows the previous document's form-field notice.
- Home at the compact width: Cancel on the close dialog brings a presented search session back whole — its results and selections — instead of dropping it.
- The page sidebar renders each thumbnail on the main actor, behind its cache, instead of drawing a page of the open document from another thread while the canvas draws it.
- Toasts have no line cap at the accessibility text sizes (the standard sizes keep two lines), so a two-sentence toast's second sentence — the one that says what to do — is no longer cut off.
- VoiceOver's verification status reads the title the screen shows — Checks Passed, Completed with Warnings, Needs Review, Checks Failed, Verification Skipped — before its one sentence of advice.
- The page sidebar's thumbnail cache is cleared at Start Over and at every import, so a closed document's page images leave memory with it.
- The share confirmation for a failed or needs-review result also lists the same report's checks that could not verify part of the output or were skipped, as the documents say; its header reads "These checks reported issues or did not run in full:".
- The page sidebar's thumbnail cache tells a new document apart from a freed one at the same address, so a new session never shows the previous document's page images.
- The VoiceOver announcement at the end of a run speaks the verification status only, not the masthead sentence that can quote review terms.
- Redact and ⇧⌘R stay available while the search sheet holds selected matches that are not applied; a tap raises the sheet and says "Apply or clear the selected matches first" instead of tearing the session down and dropping them.
- Redact: a Stop that lands while the output is being finalized leaves no output file behind in the session folder.
- The app-switcher cover sits in its own window above every presented sheet, so the Search & Redact sheet and Settings → Custom Terms are covered too.
- The failed screen's copy for a refused page names its rotation beside the size and scale, and the advice says so.
- The password sheet says Resecta does not store the password, and that any offer to save it is the system's own.
- Settings: the Searchable Redaction description and the mode picker's hint state the limit the README states — text covered by a picture in the page can still enter the text layer.
- Redact clears the undo history once the redacted copy is built, as the results card says; Stop, a background pause or a failed run keep it, and an Undo after Keep Editing no longer unlinks the built output.
- Search sheet: the "unapplied matches cleared" toasts no longer count matches a region already covers.
- Search sheet: the custom-term time-limit toast names neither the term nor the page, and is shown once for a scan instead of once per page.
- Accessibility: the search sheet's header stacks Dismiss, the title and Apply on lines of their own at accessibility text sizes, so no button hyphenates and the title keeps its words.
- Accessibility: a toast's icon scales with its message and sits beside the first character instead of on it at accessibility text sizes.
- Accessibility: a search result row speaks its category (an address, a Social Security number), its page, its confidence and whether it is applied — never the matched text; the row's default action toggles its selection instead of parking the sheet.
- Accessibility: a staged search term's remove control is named "Remove" with the term and meets the 46-point target; the Custom Terms add row's Regex toggle and add button meet it too.
- Accessibility: the parked strip's Apply capsule is named "Applied" once the match is marked, with the match's position as its value, and every element of the strip offers a "Show full sheet" action.
- Accessibility: the page bar's counter is one spoken element, "Page k of N, n regions"; filter chips report when they are selected.
- Scan: fewer name proposals on words that resemble a listed surname by only one letter; the near match still raises the score of an otherwise supported name.
- Scan: a short account number beside its label is proposed at the balanced preset.
- Scan: the keywords listed under 'Why this classification?' no longer change between launches.
- Scan: on scanned pages, an address with one of the 19 listed ZIP codes that cross a state line is proposed with the state on either side of the line.
- Scan: a label line such as 'Reg # on file' is no longer proposed as a license plate.
- Scan: a phone number whose opening parenthesis was lost in text recognition is proposed.
- Scan: American Express numbers printed in spaced or hyphenated 4-6-5 groups are proposed.
- Scan: a Social Security number written after a three-digit number and a space is proposed.
- Custom terms and saved patterns: a regular expression holding a character whose uppercase form is two letters (such as ß or the fi ligature) no longer stops the app when the pattern is validated.
- Verification: the search re-check and the detection sweep list a page as not checked when a custom regular-expression term ran out of time on it, instead of counting the page as read.
- Verification: the detection sweep reports a custom term added after a scan was applied as a possible item instead of discarding it as the scan's.
- Verification: the sweep's count of items you left unredacted pairs each one with what it reads on the output by its text first and its position second, so scanned output no longer misattributes them.
- Verification: a typed pattern that ran out of time marks only its own search as not checked on that page; the detection sweep's row no longer carries another search's timeout.
- Verification: two applied scans that share a category count an item they both read once.
- Verification: the string-search check's note on manual regions names the mechanism — pages with no text layer to capture terms from — instead of calling the pages image-only.
- Custom terms: a literal term typed with a ligature or a compatibility character matches its plain spelling on pages whose text is plain ASCII.
- Verification: the Character Count and Font Verification checks report the per-page warnings their checks raise — a count excess as a note, a page whose fonts could not be inspected as not verified — where before the check passed.
- Searchable Redaction: a page whose text layout does not settle within the re-flow limit is written without a text layer instead of with a character placed inside a redacted area.
- Verification results: the count of passed checks no longer counts notes or checks with nothing to check as passed, and a check that could not verify part of the output no longer reads as completed.
- Verification results: a one-check report reads "0 of 1 check passed" and "1 check"; a page-count stop reads "Stopped at the Page Count check."; durations of a minute or more read in minutes and seconds.
- Share: when the redacted PDF was removed while the app was in the background, tapping Share shows the export failure; before, the attempt was refused as an invalid step and nothing was shown.
- Share: a report with a skipped check asks for confirmation before sharing even when another check reported a routine warning.
- Verification results: a search line starting 'Scan (' keeps its term formatting.
- The Source filter can be changed back from OCR on a document whose pages read as text, Reset to Defaults clears it, and the empty results state offers Show All.
- An always-flag or never-flag term of one character is refused with the reason.
- A query of spaces no longer runs via Search Anyway.
- Save current search is disabled while the session has no query or terms.
- The footer says how many selected matches a filter hides.
- The Scan category chips wrap instead of clipping at the right edge.
- Tapping an already-selected region, or one region of a selected group, without moving it no longer records a Move or Resize step, re-arms Redo or clears the sheet's applied markers.
- With Add to Selection on, a marquee adds to the selection and an empty marquee leaves it unchanged.
- VoiceOver announces region resizes.
- The paused banner's Restart no longer starts a run that Redact itself would refuse: with selected matches not yet applied, or no region left to redact, it shows Redact's notice and leaves the search, the regions and the undo history as they were. The purge Re-run and Run Verification's full run are refused the same way when no region is left.
- Cancel on the Importing card, or leaving the app, while a dropped document imports over an open one returns to that document instead of a blank editor.
- Keep Editing after verification was interrupted by leaving the app no longer shows the Processing was paused banner over a finished redaction.
- Home asks before closing when the search sheet holds selected matches that are not applied; Redact and the sheet's own Dismiss already guard them.
- Redact, Home and the sheet's Dismiss read the live selection — a selected match no region covers — instead of a tracker any selection gesture armed: Deselect All no longer disables Redact or makes Home ask with nothing to apply, and an undone apply gates Redact again until its matches are applied or cleared.
- Home → Cancel brings a parked search session back only over the same document, matched by a per-import token rather than the document object's address; a file dropped in behind the close dialog does not inherit the old session's results.
- The Run Verification card's full run holds the Redact gate too: with selected matches not applied it shows the hint instead of dropping the session.
- A refused drop on the results screen or an error screen names that screen's way back: Keep Editing, Return to Editor, Return to Results, Choose Another File or Re-open Document.
- A document dropped over an open one starts with an empty undo history, the redaction mode set in Settings and no notices from the previous document; a drop on the results or error screen is refused with a notice instead of doing nothing.
- A tap or a hold on stacked one-line regions picks the region under the finger instead of the one below it, and a whole-page region no longer takes touches meant for the regions inside it.
- The size label, the region outlines, the lift shadow and the handle rings keep one on-screen size at every zoom, and the lift shadow shows in dark appearance.
- A tap on a selected region no longer moves or reshapes it, and a resize keeps the edge where it was grabbed.
- A region moved against the page edge stays on the page when it aligns to a guide.
- When zoomed in, dragging a selected region moves it instead of scrolling the page; two fingers scroll.
- The zoom you set stays when the search sheet is raised, parked or dismissed, and when you move to another page.
- VoiceOver reaches each redaction region on the page, the whole-page region included.
- Search: the live highlight drawn while typing sits on the box Apply will place, on pages whose text the search normalizes and on a match that wraps onto a second line.
- At the Search sheet's medium height, an upward drag on the empty part of the Scan chip row raises the sheet again.
Internal
- Stubs the deleted detection run left behind: an always-zero apply count, a recognition level nothing read, a cached region count only tests read, and comments that named the run.
- CI: the simulator suite runs on every pull request to
mainagain, documentation-only ones included, because its honesty tests read the README, ENGINEERING, THREAT-MODEL, PRIVACY and EULA text. - CI: the pull-request gate also compiles the engine package's test bundle; the weekly simulator suite leaves out the 12-page searchable preflight and the two suites that call text recognition directly, which stall the hosted runner; the pre-push hook posts a
local-suitescommit status for each pushed commit it passed. - Detection: a date-collision rule that could never apply to a nine-digit number is removed.
- The import robustness suite and the output allow-list test cover image sources.
- The app's unit tests build their settings on a scratch defaults suite: the shared coordinator helpers take a fresh suite by default, and the source check that lists tests reading the shared defaults domain now fails on any such site.
- The engine package's test copy of the app's per-page mode choice is removed; the app's tests check the production choice through the coordinator, on a right-to-left page and on every page of the bundled sample.
- App tests: the six trapping requires in the mode-switch toast tests are throwing requires, and the hosting and transition smoke tests assert what they name.
- Context-scored pattern matches build their explanation through one factory; the signal order is unchanged.
- Decoded fields nothing reads are dropped: the per-filter manifest entries and the unapplied document-type temperature; the temperature file is read only for the startup diagnostics.
- Detection loaders: the context keyword sets switch over the typed scored families; the context keyword loader and the driver's-licence and passport pattern tables share one version check and one loading path; the state-code pattern is compiled once.
- The output re-check's page extraction runs off the main actor (
@concurrent), one reader on the output document at a time; the rasterizer's test seam keeps a page failing for a given number of attempts, so the per-page retry's second-failure branch is driven through the production wrapper. - The engine's password message names the missing password rather than unsupported encryption; the import checks read one parser's view of the document (the walks inspect the parsed document's own reader, unlocked with the same password, instead of a second parse of the bytes). The robustness mirror and the verification corpus runner take the same password; the runner gains two plain seed documents and three locked twins whose outputs must match their seeds byte for byte.
Scripts/release-flag-check.shcensuses the remaining debug-only launch argument, the saved-searches sim-drive hook, as its positive control.- The search sheet's row display contracts, copy catalogs, banners and Scan options sit in their own files (
SearchResultRow+Contracts.swift,SearchResultsCopy.swift,SearchResultsBanners.swift,SearchToolbarSection+Scan.swift), the copy catalogs under names that say what they hold; one detection-kind label table serves the canvas badge and its VoiceOver description; the two search-preparation guards share one failure path. - The pull-request gate counts the engine package's
publicandopendeclaration lines and fails a change whose count exceeds the cap the script carries, printing the count, the headroom under the cap and the release line caps on the largest source files (Scripts/engine-public-surface-check.sh); a change that adds to the count names the declarations in its description. - The growth ratchet tracks the two test trees as well as the source trees: a test file over 800 lines at the base of a change may not grow.
- The audit lint's shorthand check reads five more shapes on added Swift and string-catalog lines: a section sign with no standard or repository document named beside it, a Markdown file name the repository does not track, and, in comments, planning-process words, step tokens and the maintainer's first name.
- A report-only test lists every app test that builds its settings on the shared defaults domain instead of a scratch suite; with
RESECTA_PIN_SCRATCH_DEFAULTS=1it fails on them. CHANGELOG.mdmerges with git's union driver (.gitattributes), so two pull requests that each add lines under[Unreleased]no longer conflict on them.pr-closed.ymlleaves a merged pull request's runs to finish; only a pull request closed without a merge has itsciandsim-suiteruns cancelled, so the hosted simulator suite of a merged change reaches a verdict.- The batched runner (
Scripts/test-batched.sh) reads its stall allowance and its recovery shape from the environment: the hosted simulator suite widens the no-output watchdog to 900 seconds, and a recovery after a wedge shuts the simulator down and boots it again, waiting for it, instead of erasing it (the erase stays opt-in). Its suite enumerator reads@Testfunctions inside extensions of a suite type. A runtime exclusion list (TEST_BATCHED_EXCLUDE) names suites not run on a given runtime, lists them in the summary and counts them in the verdict; the hosted suite names the five suites whose verification pass, OCR sweep or OCR re-check hangs on the virtual machine, and the pre-push hook still runs every suite. sim-suite.ymlnames the account's five-macOS-job cap in its header, runs the editor's link-policy UI tests on the weekly and tag runs, and reports a failed weekly or tag run through an error annotation, a step summary and, when the repository variableNTFY_TOPICis set, a post to that ntfy topic.- The asset-tamper matrix measures the bundled assets with the name-model probe forced present, so a simulator without the OS name model measures every cell instead of raising the banner on each.
- Removed: the golden Bloom builder under
Scripts/gazetteer/(its fixture is committed; the shipped filters come from the data pipeline),engine-suite.yml(the on-demand host run of the engine package, which never reached a verdict; the simulator suite's engine job is the hosted run) and five permanently disabled wall-clock tests. Scripts/doc-metrics.shcounts the log interpolations markedprivacy: .publicand theprint,NSLoganddebugPrintcalls in the shipping source and holdsENGINEERING.mdto both numbers (61 and none); its--self-testproves the claims move. The pull-request gate's checkout no longer keeps the token, and its toolchain is selected by name (DEVELOPER_DIR) with the version printed in the summary. The ignore file refuses an age wrapper, a private key, a PEM file and a derived-data directory, with the one tracked public key named as the exception.- The audit exporter blanks the exemption, citation and note fields when sensitive content is excluded (the app passes none).
- The engine's unsupported-format message names the formats: PDF files and JPEG, PNG and HEIC images.
- Import lives in
Services/Import/: a loader protocol with its payload, limits and facts types; a router that reads the leading bytes, never the declared type, and refuses every image signature before any decode; the PDF stages as a pure function of the bytes; and a wrapper that applies the facts to the editor's state in one place and reports whether the import completed. The image decode and image-to-PDF render no door reached, and the Live Photo auxiliary-metadata stripper only they called, are deleted. - The run's page-data bridge is a pure function of values (
RunInputs.pageData); the rasterization steps sit inPipelineCoordinator+Rasterization.swift; the stores share one lenient row wrapper and one legacy-defaults move; the regex sentinel is entered through onePatternAdmissionpath; the sidebar's thumbnail cache keys on the document as well as the page. - The pre-push hook refuses a stale generated project, runs only the test schemes the pushed change touches (both for an engine change, none for documentation), and accepts the batched runner's passing verdict for the same tree in place of a re-run.
- The batched runner writes a verdict file beside its logs: the scheme, the counts, the commit and tree it ran on, the simulator and runtime, and the Xcode build.
- Release:
Scripts/release-identity.sh --archivewrites an identity record of the archived release build (the main executable's UUID and SHA-256, its linked libraries, its imported symbols counted against a list of networking interfaces, its entitlements, the security-relevant Info.plist keys, the privacy manifest and the SHA-256 of every other bundled file), and--checkcompares a published record with a fresh derivation from the archive.RELEASES/1.2.0-identity.mdis the 1.2.0 record, made from the submitted build's archive after submission. - A page whose hidden-text check cannot run — no bitmap to read, no page dictionary to inspect — takes the secure path and records that the check could not run, instead of reading as clean; the first-render bitmap is wiped when the render releases it, the image handed on being a copy. The robustness runner's import mirror and the verification corpus mirror follow the same reader rules.
- The reconstruction sweep test no longer races its siblings for the shared temporary directory.
- CI: CodeQL runs from a workflow in the repository (
.github/workflows/codeql.yml) instead of the default setup; the Swift job builds the Xcode project, so the app target and the engine package are both in the Swift database, and the Actions and Python analyses run without a build. - The engine's verification tests are organised by layer, and the security suites run through the production page pipeline.
- The polygon containment test the text-layer writer uses lives with the other polygon geometry; the writer no longer reaches into the verification code.
- The stale temporary-file sweep lives beside the per-session temporary directory it cleans up after.
- Resolution selection and the pre-render page checks live in one page memory policy file.
- The structural and metadata verification layers, and the dispatch of the text-layer checks, live in their own files.
- The text-layer position check is split into its three techniques over one shared scan state.
- The verification status ranking is an exhaustive switch over every status; the test-only tuning values are bound per task.
- Rendering a page from a PDFKit page and from its Core Graphics page runs one render body.
- The rectangle and polygon character filters share one sort, search and cancellation skeleton.
- Engine: the public surface is reduced to the document-runner API and what the app and its tests name; 103 more declarations become
internal(access level only). - A Mac-side recorder for attended runs on a physical device: per-step Instruments capture, crash-report and container-attribute facts, PDF checks and a filled record (
Scripts/device/). - Engine: unused detection model members, the learned-prior surface-form dictionary and the unused exact-match search option are removed.
- Engine: the search preview also reports its visible-page matches as ranges in the raw page text, through the map the full search places its boxes from.
Full changelog: v1.2.0...v1.3.0
Build identity record: https://github.com/Merlin1A/resecta/blob/main/RELEASES/1.3.0-identity.md