Release v0.3

This commit is contained in:
Akshay Kolli
2026-06-24 17:51:26 -07:00
parent 3d112c677a
commit 085d7a16dc
33 changed files with 2828 additions and 428 deletions

44
docs/APP_STORE.md Normal file
View File

@@ -0,0 +1,44 @@
# Mac App Store Release
Bundle ID: `net.akkolli.ihatepdfs`
Current App Store build values:
- `CFBundleShortVersionString`: `0.3.0`
- `CFBundleVersion`: `4`
- Privacy policy URL: `https://www.akkolli.net/ihatepdfs/privacy`
## Required Apple Developer Items
- An explicit macOS App ID for `net.akkolli.ihatepdfs`.
- An App Store provisioning profile for that App ID.
- An application signing certificate installed in Keychain, usually named `Apple Distribution: ...` or `3rd Party Mac Developer Application: ...`.
- An installer signing certificate installed in Keychain, usually named `3rd Party Mac Developer Installer: ...`. Apple may label this certificate type as Mac Installer Distribution in the developer portal or Xcode.
The app only needs these sandbox entitlements right now:
- `com.apple.security.app-sandbox`
- `com.apple.security.files.user-selected.read-write`
Do not add network, Apple Events, Downloads-folder, or bookmark entitlements unless the app gains a feature that requires them.
## Build The Upload Package
Download the App Store provisioning profile from Apple Developer, then run:
```sh
APP_SIGNING_IDENTITY="3rd Party Mac Developer Application: Your Name (TEAMID)" \
INSTALLER_SIGNING_IDENTITY="3rd Party Mac Developer Installer: Your Name (TEAMID)" \
PROVISIONING_PROFILE="$HOME/Downloads/IHatePDFs_AppStore.provisionprofile" \
scripts/make-app-store-pkg.sh
```
The package is written to `dist/IHatePDFs-v0.3-macos-appstore.pkg`.
The script derives the App Store application identifier and team identifier from the provisioning profile before signing. It also clears download quarantine metadata from the bundle before packaging, because App Store Connect rejects packages that contain quarantine extended attributes.
## Upload
Upload the `.pkg` with Transporter. You can also set `VALIDATE_WITH_ALTOOL=1` when running `scripts/make-app-store-pkg.sh` if you want the script to perform an `altool` validation after packaging. After App Store Connect processes the build, select it in the app version, finish metadata, answer App Privacy, fill review notes, and submit for review.
Keep `CFBundleShortVersionString` as `0.3.0` and `CFBundleVersion` as `4` for this upload. Increment `BUILD_NUMBER` in `scripts/release-version.sh` before uploading another build for the same version.

70
docs/ENGINEERING.md Normal file
View File

@@ -0,0 +1,70 @@
# Engineering Principles
I Hate PDFs is intentionally a small native macOS app. Future work should preserve that constraint unless there is a documented, user-visible reason to do otherwise.
## Native First
- Build features with Swift, SwiftUI, AppKit, PDFKit, and other system frameworks that ship with macOS.
- Do not replace the app with Electron, Chromium, a web runtime, a bundled JavaScript app shell, or a cross-platform UI toolkit.
- Do not bundle a PDF renderer, OCR engine, database, scripting runtime, or large framework when a macOS system API can satisfy the requirement.
- Prefer native macOS controls and document behaviors over custom reimplementations when they meet the product need.
## Small By Default
Every change should aim for the smallest final app that still delivers the required fluidity, reliability, and functionality.
- Keep third-party dependencies at or near zero. Any new package must justify its shipped size, runtime cost, maintenance cost, and why system APIs are insufficient.
- Keep assets minimal. Avoid large raster images, fonts, sample PDFs, videos, model files, or generated resources in the app bundle.
- Keep build outputs out of source and releases unless they are intentional release artifacts.
- Prefer dynamic links to Apple system frameworks over vendored libraries.
- Avoid storing duplicate PDF data, rendered page caches, or annotation indexes unless profiling shows they are required for fluid interaction.
- Favor targeted updates over whole-document rescans for common interactions such as editing, replying, filtering, hovering, and sidebar refreshes.
## Size Budget
The release DMG should stay as small as practical. Treat size growth as a product regression, not just a packaging detail.
Before merging release-impacting work, compare:
```sh
scripts/build-app.sh
scripts/make-dmg.sh
du -sh "dist/I Hate PDFs.app" \
"dist/I Hate PDFs.app/Contents/MacOS/IHatePDFs" \
"dist/I Hate PDFs.app/Contents/Resources/AppIcon.icns" \
dist/IHatePDFs-v*-macos.dmg
```
If a change materially increases the app bundle or DMG size, document why in the PR or commit notes. A useful rule of thumb: any dependency addition, bundled asset addition, or release-size increase above roughly 10% needs explicit justification.
## Performance Budget
Small size should not come at the expense of reader fluidity.
- Opening, scrolling, zooming, searching, annotating, saving, and sidebar navigation should remain responsive on long PDFs.
- Optimize around measured user workflows instead of speculative micro-optimizations.
- Keep expensive work page-scoped or lazy when possible.
- Use `swift test` plus the PDF verification scripts after behavior changes:
```sh
swift test
swift scripts/verify-sample-pdf.swift
swift scripts/verify-pdf-annotations.swift
```
## Release Packaging
Release builds should use the existing lightweight packaging path:
```sh
scripts/build-app.sh
scripts/make-dmg.sh
```
`scripts/build-app.sh` strips release binaries by default to reduce shipped size. Use `STRIP_RELEASE=0 scripts/build-app.sh` only when a symbol-rich release build is needed for debugging.
Universal `arm64` + `x86_64` builds are the default for public releases. Single-architecture builds are acceptable for local testing:
```sh
ARCHS="" scripts/build-app.sh
```

View File

@@ -15,20 +15,50 @@ Use at least:
## App Workflow
1. Open the PDF in I Hate PDFs.
2. Select text and add a highlight.
3. Add a comment to the highlight.
4. Add an underline with a comment.
5. Select text, right-click, and add a comment from the context menu.
6. Add free text directly on the page.
7. Open the comments sidebar and verify count, grouping, search, filters, edit, delete, reply, and click-to-navigate.
8. Quit and reopen the same PDF at the same approximate window width and verify the app restores that PDF's sidebar state; then open a different PDF and verify it starts in focused single-pane reading unless that document has its own saved state.
9. Add at least one reply and verify the comments sidebar presents the thread like a clean review/chat stream, with a visible connector line from the parent comment to the reply.
10. Hover a comment row and verify the corresponding PDF text is highlighted; click both the parent comment text and the reply text in the sidebar and verify the PDF view navigates to and selects the corresponding annotation.
11. Verify highlights, comment markers, hidden page-level replies, and selected sidebar rows use muted native-feeling colors in light mode and do not visually overpower the document.
12. Switch the app to dark mode and verify the reading background, comments sidebar, editor popover, connector lines, selected rows, text fields, and annotation markers remain legible and restrained.
13. Save As an annotated copy.
14. Reopen the annotated copy in I Hate PDFs and verify the annotations and comments remain.
15. Save over a disposable original and verify the overwrite warning appears.
2. Close the PDF, then drag a `.pdf` file onto the empty no-document window and verify it opens.
3. Open Settings from File > Settings... and with Command-, then verify highlight color, comment color, and opacity changes can be edited and reset.
4. Select text and add a highlight; verify no comment popover opens.
5. Select text and add a comment; verify the comment color matches the Settings value.
6. In the comment box, press Shift-Return and verify it inserts a new line, then press Return and verify the comment is saved.
7. Add an underline with a comment.
8. Select text, right-click, and add a comment from the context menu.
9. Add free text directly on the page.
10. Open the comments sidebar and verify count, grouping, search, filters, edit, delete, reply, and click-to-navigate.
11. Quit and reopen the same PDF at the same approximate window width and verify the app restores that PDF's sidebar state; then open a different PDF and verify it starts in focused single-pane reading unless that document has its own saved state.
12. Add at least one reply and verify the comments sidebar presents the thread like a clean review/chat stream, with a visible connector line from the parent comment to the reply.
13. Hover a comment row and verify the corresponding PDF text is highlighted; click both the parent comment text and the reply text in the sidebar and verify the PDF view navigates to and selects the corresponding annotation.
14. Click on commented text and underlined text and verify the comment popover opens; then click the line below or nearby whitespace and verify no popover opens.
15. Verify highlights, comment markers, hidden page-level replies, and selected sidebar rows use muted native-feeling colors in light mode and do not visually overpower the document.
16. Switch the app to dark mode and verify the reading background, comments sidebar, editor popover, connector lines, selected rows, text fields, and annotation markers remain legible and restrained.
17. Save As an annotated copy.
18. Reopen the annotated copy in I Hate PDFs and verify the annotations and comments remain.
19. Save over a disposable original and verify the overwrite warning appears.
20. Add an annotation and verify the window shows the native macOS unsaved/edited document indicator until the PDF is saved.
21. Search for a word, close the search toolbar, and verify the match highlights disappear; repeat after opening a different PDF to confirm stale search highlights do not carry over.
22. Type an invalid page number and an out-of-range page number in the page field, and verify the app restores the current page number with a clear status message; also verify previous/next page controls disable at the first and last pages.
23. Apply comment filters or search text that hide every comment, verify the empty state offers Clear Filters, and verify page counts include visible replies.
24. Collapse a page group in the comments sidebar, search for a comment on that page, and verify the matching results are shown while the filter is active.
25. Start typing a sidebar reply, click Reply on a different comment, and verify the original draft remains until you send or cancel it.
26. Click one comment row, then click Reply on a different comment or reply, and verify the sidebar selection and PDF highlight move to the reply target.
27. Click one comment row, then click Edit or the review-status chip on a different row, and verify the sidebar selection and PDF highlight move to the edited or reviewed row.
28. Set a comments-sidebar filter and collapse a page group, then open another PDF and verify the comments sidebar starts unfiltered with page groups expanded.
29. In Settings, choose very low-opacity highlight and comment colors, add each annotation type, and verify saved annotations remain visibly readable.
30. Start typing a sidebar reply without sending it, then close or replace the PDF and verify the app asks before discarding the draft and the window shows the edited indicator while the draft exists.
31. Start typing a sidebar reply without sending it, choose Share, and verify the app warns that the draft will not be included unless it is sent first.
32. Start typing a sidebar reply, delete the comment thread it belongs to, and verify the app asks before discarding the reply draft.
33. Add replies to a comment, delete the parent comment from both the sidebar and the popover path in separate runs, and verify the whole thread is removed each time.
34. Hover a comment row until the matching PDF annotation highlights, then hide the comments sidebar or apply a filter that removes the row and verify the hover highlight clears.
35. Hover and click a sidebar reply, and verify the PDF scrolls to and highlights the visible parent annotation rather than jumping to a hidden reply marker.
36. Search for a word with matches, edit the search field without pressing Return, and verify old PDF match highlights clear and previous/next search buttons disable until the new query is submitted.
37. Start typing a sidebar reply without sending it, choose Save As, and verify the app warns that the draft will not be included unless it is sent first.
38. Create a new selected-text comment or free text, leave its popover empty, choose Save before closing the popover, and verify the temporary empty annotation is discarded instead of saved.
39. Start typing a sidebar reply with no other unsaved annotation changes and verify the status bar shows a reply draft instead of presenting the PDF as clean.
40. Search for a word with matches, step through results, and verify the status bar reports the current match position; then search for text that is not present and verify PDF match highlights clear.
41. Open comments search and verify the field is focused immediately; enter a search, hide the search controls, and verify the search icon still indicates an active hidden filter.
42. Select a comment row, apply a comments-sidebar filter or search that hides that row, and verify the PDF selection highlight clears instead of lingering on the page.
43. Create a new selected-text comment or free text, leave its popover empty, choose Share, and verify the temporary empty annotation is discarded before any Save and Share output is written.
44. Select a comment or annotation row, hide the only sidebar that shows that row, and verify the PDF selection highlight clears; repeat while the left Annotations sidebar is visible and verify the selection stays visible there.
45. Select a comment row, collapse its page group in the comments sidebar, and verify the PDF selection highlight clears; then search/filter comments and verify matching page groups expand while filtering.
## External Readers
@@ -39,7 +69,9 @@ swift scripts/verify-sample-pdf.swift
swift scripts/verify-pdf-annotations.swift
```
These checks generate an annotated PDF, reopen it with PDFKit, and inspect the raw PDF annotation dictionaries for standard `/Highlight`, `/Underline`, `/Text`, `/FreeText`, `/Popup`, `/Contents`, `/QuadPoints`, `/IRT`, `/RT`, and `/Parent` entries.
These checks generate an annotated PDF, reopen it with PDFKit, and inspect the raw PDF annotation dictionaries for standard `/Highlight`, `/Underline`, `/Text`, `/FreeText`, `/Contents`, `/QuadPoints`, `/IRT`, `/RT`, and `/Parent` entries.
For Preview interoperability, exported markup comments should keep the comment text on the parent annotation's standard `/Contents` key and should not depend on PDFKit-generated `/Popup` links for highlights or underlines.
Open the saved annotated copy in: