Repository navigation
fix(cli): protect batch artifacts and skipped inputs during preflight - #254
Conversation
… restore behavior
…xt off the keystroke path
…ocks The grid emulator now fills erased, inserted and scrolled-in cells with the current SGR background (bce), so TUI color bars and theme backgrounds reach the right margin. The routing scan no longer stops at a CSI interrupted by a control byte. Line mode keeps a line after a carriage return until something is drawn or erased there, so script's \r\r\n output survives. Cursor up/down moves between emitted lines, so log-update style redraws keep only the final frame. Erases keep SGR and OSC escapes, and backspace removes a glyph instead of escape bytes. TerminalScreen.plainText() had no production caller and moves to the tests; the routing table in ARCHITECTURE.md now documents the repeated ED 2 trigger.
Decoded images now take their point size from the file's DPI, so a 144-DPI Retina screenshot lays out at half its pixel size and exports 1:1 at @2x instead of being upsampled. Image reads go through BoundedFileReader (non-blocking open, descriptor type and stability checks) instead of a separate FileHandle loop. The pixel budget is a single ImageDecodePolicy function that the import tests now exercise directly; the unused decodedByteCost helper and the duplicate validateImageDimensions are removed.
… / => assignments
Scanner accepted a 0x prefix and leading whitespace that the length check counted, so 0x1E1E1E decoded as a nearly transparent color. RGBAColor and HexColor now use the same ASCII-only parser.
Recipe import decodes the style strictly, so a string font size or an unknown background kind fails with its JSON path instead of silently falling back to a default. Presets and preferences keep the tolerant decoder.
…xt and LineSplitter.plainLines
- Read --stdin in bounded chunks and report oversized stdin or files as inputTooLarge instead of "could not read". - Refuse an --out image (not only sidecars) that would overwrite the source, a background image, or a watermark logo. - Batch discovery runs before the output folder is created and skips the output folder, manifest, and skipped report, so re-runs do not render earlier artifacts; render failures keep their typed reason. - --edit rejects theme and output options the handoff cannot carry, carries --terminal-width as a columns hint, and fails on empty sources. - --copy with --out writes the file first and copies that raster; clipboard-only copies reject a non-PNG --format and report png. - Locate fonts and the enclosing app through the dyld executable path, not argv[0]. - Usage errors exit 2 and print usage; runtime errors exit 1 with only their message. A second positional is an unexpected argument. - --alt-text requires a Markdown or HTML sidecar; help text for --profile and terminal-capture matches the parser.
…cribed - Network quiet now counts in-flight fetch/XHR from document start and waits for an idle window with no new resources, instead of returning about 0.4 s after load. - The sign-in window hosts popups in their own windows, answers HTTP Basic/Digest/NTLM challenges with a credential sheet, and shows the page host and whether it is fully secure. - Production URL capture uses the tested URLRenderer.configured mapping. - HTML mode hides capture options that HTML rendering ignores. - The capture caption no longer claims nothing is sent to a server. - Image-backed social cards export with the same opaque matte as snapshots, in raster and PDF. - Raster-backed PDFs (Web Snapshot, comparison boards) use point-sized pages. - Export all sizes encodes off the main actor, honors the export format, names files from the real capture size, and never overwrites. - Toolbar tooltips and labels are localized; "%lld slides" has an English plural. - The unreachable disabled-disclosure branch is removed.
Menu bar - Return focus to the previous app after a panel action or the history consent alert, so the next paste lands there. - Relaunch to Apply passes the old process id, so the single-instance guard no longer quits both instances; terminated processes are ignored. - Refresh the panel's hotkey chip on every open and name what the hotkey runs. - Announce capture HUD results to VoiceOver (failures with high priority). - File > Copy Image closes the editor under "Close the editor after copying". - Services renders with the Export scale, color profile, and destination size. - Close the panel before running a capture so a modal alert keeps Escape. - Anchor the helper's panel on the icon; label the in-process status item. - Show the newest captures in the panel; say when history is off; use the pinned-capture warning for Clear All. - Localize the Recents title, diagnostics panel, Services and automation errors; report a failed diagnostics export. - View > Theme shows a checkmark and custom themes; Check for Updates is validated against Sparkle and also offered in Settings > About. - Remove the unreachable Web Snapshot recovery action, the unused editor shortcut name, and the production-only test wrapper; compile memory-journey hooks in Debug only. Settings - Route captions, tooltips, panels, and import errors through the String Catalog, including domain summaries and messages. - Report why a license activation failed with a specific message. - Show the real launch-at-login status after a change, with an approval link. - Add corner radius, shadow depth, and window title to the Style pane with value readouts and the persisted slider ranges; share one background control; fix the Brand Kit, Library, Help, and Reset copy. - Disable URL-only Input controls on builds without URL capture; confirm before clearing web data. - Offer the sudo fallback only for permission failures; pick the shell from the chosen startup file. - Keep a seat release running when the user leaves the About pane, with progress; show restore and pending-purchase results on the App Store build. - Make the free-placement handle adjustable from the keyboard and VoiceOver; announce disclosure state; label the Style sections. - Follow the system accent for focus rings and selected pills; drop stale and unused catalog entries; use consistent Spanish terms. Architecture - Add WindowPlacement for every window controller and fit the Comparison Board window to small displays. - Add one save-panel writer with a single atomic write policy. - Share one image-share route and inject feedback into ShareManager. - Add one image-copy helper that always reads clipboard privacy app-wide. - Remove `.shared` default arguments; tests build isolated settings. - Resolve the editor's window from viewDidMoveToWindow instead of polling. - Route activation through AppActivation using cooperative activation. - Use the batch-export folder picker for the documentation export.
…026-10 # Conflicts: # Vitrine/State/WindowState.swift # docs/ARCHITECTURE.md
…026-10 # Conflicts: # Vitrine/App/EditorWindowController.swift # Vitrine/Resources/Localizable.xcstrings
…tings Auto-copy, save-to-file, close-after-copy, and clipboard concealment move out of ExportSettings into a new OutputBehavior store. The app-wide AppSettings creates the only instance; editor sessions reference it instead of seeding their own, so a window can no longer read a stale default. Make Default and EditorPreferencesSnapshot keep carrying only the per-capture output (scale, format, color profile, rich clipboard, text sidecar). All four image-copy paths (toolbar, File > Copy Image, menu-bar recents, recents gallery presets) go through RenderedImageCopy, which takes the config, the per-capture output, and the app-wide behavior. Persisted keys and values are unchanged, and a regression test covers an editor session copying with the app-wide conceal and close-after-copy preferences.
johnny4young
left a comment
There was a problem hiding this comment.
Review: protect batch artifacts and skipped inputs during preflight
This is stacked on #251, and I reviewed only the delta against fix/app-review-2026-10.
What it does: before any directory is created, the batch now checks that --manifest and --skipped-report don't collide with each other, with any planned image or sidecar, or with the background or watermark resources. It uses canonical, case-folded keys. --no-overwrite now also protects existing reports. Every discovered input, including skipped ones, is protected from sidecars, and the input set is canonicalized once per batch instead of once per output. An existing file inside the input tree may only be replaced if it is a recognized prior report of the same kind (≤ 5 MiB, strict keys and types).
Verdict: ready to merge after #251, then retarget to main and re-run CI.
What I checked
- Ordering:
createDirectorymoved after both guards, so a rejected run leaves nothing behind, as the tests assert with!fileExists(output). - Report recognition (
matchesReportStructure):- Manifest entries use synthesized
CodablewithencodeIfPresent, soplannedentries omitwidth/height, exactly what the validator expects. formatcomes fromExportFormat, which has onlypng/pdf/heic/avif.languageisLanguage.rawValue.- Empty arrays are accepted, so
[]reruns work.
- Manifest entries use synthesized
- Size limit:
maximumExistingReportBytesreusesFileInputLoader.maximumByteCount(5 MiB), which matches the README. - Symlinks: a symlink alias to a resource is caught through
canonicalPath(resolvingSymlinksInPath). - CI was green on
2d3132e(all 5 required jobs).
Nits (not blocking)
- 🟡
plannedOutputs = Array(outputURLs.values)iterates a dictionary, so when several outputs conflict, the path in the error message can differ between runs. Sorting by path would make CI logs reproducible. - 🟡
guardOutputsDoNotOverwriteInputs(inCLIOutputWriter) still comparescanonicalPathwithout case folding, whileguardReportTargetsfolds case. On the default case-insensitive APFS,render notes.MD --out notes.png --markdown-sidecarstill slips past the input guard. That's pre-existing, but both guards could sharefilesystemKey. - 🟡 Report conflicts throw
incompatibleOptions, which with #251 means exit code 2 and the full usage text, for what is really a filesystem conflict. A dedicatedCLIErrorcase (exit 1, no usage dump) would read better in CI logs. - 🟡 A legitimate manifest larger than 5 MiB stored inside the input tree can't be re-used. That's documented, but a very large recursive batch could hit it.
Generated by Claude Code
An output or sidecar that would replace an input or image resource is found against the filesystem, not in the command line, so it now raises CLIError.outputConflict (exit 1, message only) instead of incompatibleOptions (exit 2 with the usage text). The claim check folds case like the default macOS volume, so NOTES.md cannot replace notes.md; on a case-sensitive volume this fails closed.
Subtitle KVO observations for sign-in popups lived in one controller-wide array that was only cleared when the main sign-in window closed, so every closed popup left its observations (and the closures retaining its web view) alive. Each popup now owns its observations, invalidated when that popup closes.
Apply the case-folded claim key and runtime outputConflict to the batch form of guardOutputsDoNotOverwriteInputs.
Plan outputs in source-key order instead of dictionary order, so the first reported conflict is the same on every run, and report a batch report path that collides with a resource or output as a runtime outputConflict (exit 1) like the other filesystem conflicts.
…ecipe themes - Compute the DPI-aware point size from the decoded, budget-bounded bitmap so a downsampled foreground image is never laid out larger than its pixels. - Reuse an earlier re-keyed copy when a recipe theme collides with a different local theme, so reapplying the recipe no longer adds a new copy each time. - Split asciinema casts only on CR, LF, and CRLF so events carrying a raw U+2028, U+2029, or NEL are not cut into invalid JSON fragments.
…h-report-preflight
…light # Conflicts: # CHANGELOG.md # Tests/CLI/CLIBatchArtifactTests.swift # Vitrine/CLI/CLIBatchRenderer.swift # Vitrine/CLI/CLIOutputWriter.swift
…sed inputs - Raise outputConflict (exit 1, no usage text) when a report would replace an unrecognized input file, matching every other filesystem conflict. - Compare report paths with every discovered input using the case-folded claim key, so a differently cased report path cannot overwrite a source. - Refuse a report path that is the output folder, a folder the run creates, or a path below a planned image or sidecar before anything is rendered. - Skip the report preflight when no report was requested, reuse the shared claim key, and recognize manifest formats from ExportFormat.
Review follow-up to #251
This is stacked on
fix/app-review-2026-10so the focused safety delta can be reviewed independently of the existing full-app review. Merge order, if approved: #251 first, then retarget this PR to main. No merge or release was performed.Findings
--manifestor--skipped-reportcould name an image or sidecar produced by the same run. JSON was written after rendering and silently replaced that artifact, while the summary still reported success.--no-overwritechecked images and sidecars but not reports.--manifest/--skipped-reportcould be overwritten. Existing destinations inside the input tree now require bounded recognition as the matching prior report structure.Changes
--no-overwrite, including dry runs that still emit reportsReview follow-ups (2026-10-06)
plannedentries omitwidth/height,formatis limited topng/pdf/heic/avif, andlanguageis a raw value.cca7677merges fix: full app review — editor, menu bar, settings, domain, web/CLI, docs and screenshots #251's head, which carries the runtimeoutputConflicterror and case-folded claim keys. The conflict inCLIOutputWriteris resolved by applying both to the batch (array) form ofguardOutputsDoNotOverwriteInputs. The CHANGELOG keeps both entries.0c05fdcfix(cli): report batch output conflicts deterministically:outputConflict(exit 1, message only), like the other filesystem conflicts.swiftc -parseandswift format lint --stricton the changed files (Swift 6.0.3, Linux) and the repository hygiene script pass. AppKit targets cannot be compiled on Linux; native build and tests rely on macOS CI for this head.Earlier verification (head
2d3132e)npm testpassed: Astro check/build and 58/58 Node tests.Scope
No dependency, entitlement, rendering-style or test-lane changes. The existing rerun behavior for separately named manifest/report files is retained.
Merge order
#251 first, then retarget this PR to
mainand re-run CI.