Skip to content

release: CheatSheet 1.2 (Spanish, analytics, iOS 26 baseline, new screenshots) - #6

Merged
weskcode merged 34 commits into
mainfrom
release/1.2
Oct 1, 2026
Merged

weskcode merged 34 commits into
mainfrom
release/1.2

Conversation

@weskcode

@weskcode weskcode commented Sep 5, 2026 •

Copy link
Copy Markdown
Owner

Merges the release/1.2 line into main. main was 28 commits behind it.

What it contains

  • iOS 26 and macOS 26 baseline (deployment floor 26.0).
  • Full Spanish localization of the app and widget.
  • Headings that end in #, such as # C#, keep the hash in the editor and the widget.
  • Anonymous TelemetryDeck analytics (2.14.2, pinned) and a pin-based review prompt.
  • Sidebar collapse, Trash, and onboarding fixes.
  • On compact iPhone layouts, the editor header controls stay on screen.
  • CI moved from GitHub Actions to Xcode Cloud.
  • New App Store screenshot set (10 iPhone, 10 Mac), release kit, and press kit.
  • Contribution docs now describe a single-branch flow.

Version

project.yml at merge says 1.0 (16) from the relabel commit, while the release docs say 1.2. This PR does not settle that.

Verification

Run locally on 2026-10-01 against the Xcode 27 beta SDK:

  • 69 unit tests on macOS (arm64 and x86_64) and 69 on iOS
  • 11 iPhone UI tests on iPhone 17 Pro, iOS 27.0
  • Universal Release build for macOS (arm64 and x86_64)

Not run: the iPad UI suite and an Xcode 26 archive. The iOS TestFlight archive on 6e34368 failed at export because the widget App ID could not be signed with its App Group.

weskcode and others added 30 commits September 2, 2026 02:08
Raise the deployment floor to iOS 26.0 / macOS 26.0, pin the test runtime
to iOS 26.5 or newer, and make both contracts enforceable rather than
assumed.

Deployment floor:
- project.yml declares iOS 26.0 and macOS 26.0, and drops the duplicate
  MACOSX_DEPLOYMENT_TARGET from settings.base so options.deploymentTarget
  is the single source of truth for the macOS floor.
- Every availability gate is now unconditionally true, so the Liquid Glass
  paths in LiquidGlassGroup.swift and ViewModifiers.swift call the API
  directly and the macOS/iOS arms collapse into one. No #available or
  @available checks remain in the codebase.
- 26.0 rather than a point release: nothing in the app needs a 26.x point
  API, so a higher floor would delete the same code and support fewer
  devices.

SDK contract:
- Scripts/verify-build-sdk.sh asserts the active toolchain's SDK major
  matches the branch contract (26), so a machine carrying an Xcode beta
  cannot silently produce a beta-SDK submission build. CI hard-fails;
  Scripts/verify-macos.sh warns, since developing on a beta is fine.

Test runtime:
- Scripts/resolve-ios-simulator.sh takes a minimum runtime (26.5 by
  default, CHEATSHEET_MIN_IOS_RUNTIME to override) and fails with a
  per-runtime breakdown rather than falling back below the floor. A green
  suite that ran on an unsupported runtime certifies nothing.
- It matches device family on deviceTypeIdentifier instead of the display
  name; renamed simulators were being skipped entirely. Ranking now
  prefers stock-named devices so the pick is reproducible.

Enforcement and CI:
- Scripts/verify-project-config.sh asserts the deployment floors, so a
  regression fails CI instead of shipping.
- CI moves to the macos-26 runner: a macOS 26 app cannot run on a macOS 15
  host, and macos-26 also carries an SDK and simulator runtime satisfying
  both floors. Xcode selection prefers a versioned Xcode 26 bundle but
  defers to the SDK check as the real gate, since a bundle name cannot
  guarantee an SDK version.

Docs/os-support-policy.md records the policy, the branching model for iOS
27 adoption, and the measured iOS 27 readiness result.

Verified locally: 55 tests in 5 suites pass on both the iOS simulator and
macOS, iOS Release builds clean, 0 warnings. The iOS 26 SDK itself is not
installed on this machine, so CI remains the authority for the shipping
SDK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
simctl keeps listing the iOS 27.0 beta runtime as available after switching
back to the release Xcode, so "newest wins" sent shipping test runs onto the
beta OS. Under Xcode 26.6 the resolver picked an iOS 27.0 iPhone rather than
the 26.5 one.

Exclude runtimes whose major exceeds the active simulator SDK. The toolchain
now sets the ceiling, so the iOS 27 readiness branch needs no special case:
selecting Xcode 27 raises it automatically. Major-level rather than exact, so
a 26.6 runtime under a 26.5 SDK stays usable.

Verified under Xcode 26.6 (SDK 26.5): the resolver returns the iOS 26.5
iPhone 17 Pro (9C915472), and the diagnostic marks 27.0 runtimes as newer
than the active SDK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
parsedChecklistLine stripped heading markup with
trimmingCharacters(in: CharacterSet(charactersIn: "# ")), which trims BOTH
ends. Any heading ending in "#" silently lost it:

  "# C#"        rendered as "C"
  "# F#"        rendered as "F"
  "# C# vs F#"  rendered as "C# vs F"

DisplayLine lives in Shared/Sources, so this hit the editor and the widget on
both platforms -- and "C#"/"F#" headings are ordinary content in a developer
cheat sheet, which is what this app is for.

Strip only the leading hashes instead, then trim whitespace. Adds two
regression tests.

Verified: 57 tests in 5 suites pass on macOS (was 55). The fixed parser was
also compiled standalone against the four pre-existing heading/task/plain
cases to confirm nothing outside the bug changed behaviour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The workflow duplicated its setup four times, ran only on main, kept no
diagnostics, and never verified the shipped Mac binary was universal.

Structure:
- New composite action .github/actions/setup replaces the Xcode-select,
  SDK-assert, XcodeGen and generate steps duplicated across both jobs.
- A cheap `checks` job runs first. It asserts the deployment floor, App Group
  wiring and privacy-manifest reasons -- none of which any build catches, since
  every xcodebuild here passes CODE_SIGNING_ALLOWED=NO -- so a bad config fails
  in seconds instead of after twelve minutes of simulator work.
- iOS and macOS both gate on it and run in parallel.

Coverage:
- Universal macOS Release build with an explicit lipo assertion on both the
  arm64 and x86_64 slices. Nothing previously proved the shipped Mac app
  carried both, and macOS still ships to Intel. Only the x86_64 *test* run
  needs Rosetta, which the arm64 runner does not document, so that half stays
  in Scripts/verify-macos.sh for local use.
- iOS unit tests run once rather than on every matrix leg; they are pure logic
  and device-independent.
- The UI step drops -only-testing. The UI target globs its whole source
  directory, so naming classes there silently skipped any newly added class.

Operations:
- concurrency cancels superseded runs, so a new push stops burning the macOS
  runners the previous one held.
- permissions: contents: read.
- Push now also builds `develop`, which was previously merged unverified.
- Result bundles are written and uploaded on failure. UI tests attach
  screenshots with .keepAlways; those previously died with the runner and every
  failure had to be reproduced blind.

iPad is intentionally not a matrix leg yet: the UI suite here has no iPad
handling, so the leg would fail. The rationale and the one-word change to
enable it are recorded inline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Localize every user-visible string in CheatSheet and its WidgetKit
extension into neutral international Spanish (es), following Apple's
String Catalog approach:

- Add CheatSheetApp/Resources/Localizable.xcstrings and
  CheatSheetWidgets/Resources/Localizable.xcstrings with English and
  Spanish translations for every UI string, including onboarding, the
  editor, sidebar, trash, settings, the macOS menu bar extra, and the
  widget's empty and populated states.
- Fix several call sites that silently bypassed SwiftUI's automatic
  string-catalog lookup because a String value sat between the literal
  and Text()/Label() (color and font display names, the trash
  countdown, storage error descriptions, the widget empty-state title,
  onboarding step rows, the persistence status banner, and the
  default/fallback note title and starter notes), switching them to
  String(localized:) with stable keys or LocalizedStringKey so Spanish
  translations actually apply.
- Localize the first-run starter notes and default new-note title so a
  Spanish-speaking user's first launch isn't in English.
- Add Scripts/verify-localization.sh, wired into CI, to check both
  catalogs for missing translations and mismatched %@/%lld placeholders
  between languages.
- Add LocalizationTests (Swift Testing) covering the localized call
  sites' fallback behavior, and CheatSheetiOSSpanishLocaleUITests, a UI
  smoke suite that launches under -AppleLanguages (es-ES) to confirm
  the Spanish catalog actually renders in onboarding, the seeded
  starter note, and the empty Trash state.
- Document the localization setup in README.md and add
  Docs/app-store-localization-es.md with proposed Spanish App Store
  Connect metadata.

Existing English strings, accessibility identifiers, and default
values are unchanged, so the existing English-locale test suite is
unaffected; XcodeGen already picked up the new catalogs and added `es`
to the project's known regions automatically.
Adds -cheatsheet-seed-screenshot-demo, gated behind the existing
UI-testing in-memory store path, so App Store screenshot captures are
reproducible without manual tapping and never touch a real device's
notes. Demo content is 8 generic, accurate developer-reference notes
(git/docker/vim/HTTP facts), same spirit as the existing "Git Flow"
starter note.

Also adds AppStoreScreenshots/ planning docs (feature evidence, visual
DNA, message map) grounding the screenshot brief in actual app source
rather than invented UI.

Verified: 64/64 unit tests pass; manually launched the built app with
the new flag and visually confirmed all 8 notes render correctly with
distinct colors and the pin/widget flag on Git Rescue.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
16 evidence-verified concepts (iPhone 6, iPad 3+1 reused, Mac 2+1
reused) after generation, scoring, and one targeted retry of a lens
that failed to a transient rate limit. Two claims from the
unreviewed cross-platform-depth lens (the Cmd-N shortcut and the
5-note Recent Notes cap on Mac) were independently re-verified
against source rather than trusted as-is.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
compose_screenshot.py implements the approved Brand Gradient style:
full-bleed gradient canvas, eyebrow+headline caption in dedicated
space above the device (never overlapping real UI, per user
feedback on the first pass), real device bezel cropped at the
bottom edge, real SF system font. Verified output is exactly
1320x2868 with no alpha channel.

style-direction.md records the two-pass approval (backdrop choice,
then a caption-layout correction against real reference examples)
so this isn't re-litigated in later work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Found during a review pass: the --device parameter was accepted and
plumbed through to compose(), but nothing in the function body
branched on it — every device silently got the iPhone treatment,
including a Dynamic Island cutout that real iPads don't have.

- iPhone keeps the pill-shaped island; iPad gets a small circular
  camera dot instead, and a smaller corner radius (both verified
  against reference photos)
- 'mac' now fails fast (invalid --device choice) instead of quietly
  rendering an iPhone-style bezel — Mac has no frame design yet,
  see style-direction.md
- Re-verified: iPhone output unchanged, iPad output now correct,
  mac correctly rejected. ~2.5s per image, fine for 16+ concepts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The concept-matrix.csv has always had 15 rows (10 iphone + 3 ipad +
2 mac) - "16" was a stray miscount in the shortlist.md summary line
and style-direction.md's cross-reference, caught during a review
pass (verified by parsing the actual CSV with csv.DictReader).
Per-device breakdowns were already correct; only the total was wrong.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
testQASweep_iPad (added in an earlier session as an iPad-only interactive
QA walkthrough) was reused against iPhone's compact layout as-is and hit
two real cross-device gaps, both root-caused against the live accessibility
hierarchy rather than guessed at:

- The pin invariant check queries the sidebar list's "Pinned" image, which
  only stays on screen on iPad's NavigationSplitView. iPhone's compact
  NavigationStack pushes the editor over the list, so the list briefly
  isn't part of the accessible hierarchy at all. Added
  returnToListIfPushed(in:), a no-op on iPad/Mac, which pops back via the
  nav-bar back button first on iPhone.
- Clearing the search field's text doesn't end its active/focused session;
  .searchable() keeps the Cancel/Close button up and hides the rest of the
  toolbar (New Note, Show Trash, ...) until that session is dismissed.
  Added dismissSearchIfActive(in:) to tap Close after search.

Renamed testQASweep_iPad -> testQASweep now that it's genuinely
cross-device. Verified 0 failures on both a booted iPhone 17 Pro and iPad
Pro 11-inch (iOS 27 simulators), screenshots spot-checked visually.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two tests failed on iOS 26.5 having only ever run on iOS 27.0 (the simulator
resolver used to prefer the newest runtime). Both were environment-sensitive
rather than product failures -- verified by running them against pre-rebase
code on the same simulator, where they fail identically.

Spanish empty-Trash test: it asserts a translated *string*, but reached Trash
by driving the toolbar. That item lives in the secondary-action group, which
iOS collapses into a system "More" overflow at compact widths -- and a
collapsed item becomes a plain menu row that no longer resolves by
accessibility identifier, so the fallback could never succeed. Adds a
-cheatsheet-show-trash launch argument (gated on UI testing, alongside the
existing onboarding hooks) so the test lands in Trash directly, and deletes
the chrome-driving helper.

English suite: it asserts rendered English text in roughly ten places but
pinned no locale, passing only because the runner happens to be English. Pins
-AppleLanguages/-AppleLocale like the Spanish suite already does.

Search checkpoint: typed into the search field without waiting for first
responder, which on a loaded machine surfaces as "Neither element nor any
descendant has keyboard focus". Adds a focusForTyping helper that waits for
the keyboard, and re-focuses before the clearing pass, since the screenshot
between the two steps is long enough for .searchable to drop focus.

CI: drops the -only-testing allow-list. The UI target globs the whole
directory, so a new test class joined the target but not the list and would
silently never run. Fails closed now.

Verified: 66 tests in 6 suites pass on macOS. The UI changes need a simulator,
which this machine cannot currently boot (process limit), so CI verifies them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The app ships iPad (TARGETED_DEVICE_FAMILY "1,2") and the UI it presents there
is structurally different: a NavigationSplitView with a sidebar rather than a
compact NavigationStack with a navigationBarDrawer search field. The UI suite
branches on the idiom, but only the iPhone half had ever run -- CI resolved a
single iPhone simulator for every iOS step, so the iPad paths were dead code in
practice.

Adds ipad to the existing matrix. fail-fast is already false, so one device
failing still reports the other. Unit tests stay pinned to the iphone leg;
they are pure logic and device-independent.

No other change was needed: Scripts/resolve-ios-simulator.sh already takes a
device family, and the macos-26 image carries iPad models on the iOS 26.5
runtime the resolver selects.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adding the ipad leg to CI (previous commit) failed 3 of 12 UI tests. All
three were the suite's helpers assuming an iPhone-shaped NavigationStack;
NavigationSplitView needed different queries, not different behavior.

Root-caused each by dumping the real accessibility tree rather than guessing:

- revealSearchField: .searchable(placement: .sidebar) puts the field above
  the sidebar's first row, off-screen above the viewport. A full-screen
  swipeDown() (the iPhone path) hits the detail pane and scrolls nothing.
  Falls back to swiping the "Sidebar" collection view directly.

- returnToList: with two navigation bars on screen (sidebar + detail pane),
  navigationBars.buttons.firstMatch resolved to the DETAIL pane's first
  button -- "New Note" -- so this was silently creating a note instead of
  navigating. iPad has nothing to pop; the list is always visible. Guarded
  on a new isSplitViewLayout(in:) check.

- Two membership/deletion assertions: a note's title renders in both the
  sidebar row and the detail pane on iPad, so app.staticTexts[title] is
  ambiguous, and a stale detail pane can defeat a "no longer in the list"
  check. Added noteList(in:) to scope those queries to the actual list.

- Permanent-delete confirmation: both the detail pane and the confirmation
  dialog have a "Delete Now" button. Picking the last app-wide match assumed
  the dialog sorts after the pane, which does not hold on iPad, where the
  confirmation is a popover -- the pane's own button was tapped, the dialog
  was never confirmed, and the note silently survived. Added
  confirmPermanentDelete(in:), scoped to the sheet/alert container.

Verified: 12/12 pass on iPad, 12/12 on iPhone -- including on a fresh,
disposable simulator, to rule out interference from other sessions sharing
this machine's simulators. Zero syntax errors from a standalone parse of the
changed file.

Also removed four identical, untracked duplicate files (iCloud sync
artifacts: Localizable 2.xcstrings, ScreenshotDemoContent 2.swift,
LocalizationTests 2.swift, verify-localization 2.sh) that broke local
compilation with "invalid redeclaration" -- unrelated to this branch, but
blocked reproducing the CI failure locally.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Version
-------
1.1 (13) -> 1.2 (14). The released v1.1.0 shipped build 13 on 2026-06-10, so
resubmitting at that build number would be rejected as a duplicate. Minor
rather than patch: this line carries a user-visible bug fix (headings ending
in "#", e.g. "# C#", rendered with the hash eaten in both the editor and the
widget), full Spanish localization, and an iOS 26 / macOS 26 baseline.

CI
--
Excludes testQASweep from the blocking UI gate via -skip-testing, which fails
closed -- unlike -only-testing, a newly added test class still runs.

That test is a 164-line, 13-checkpoint, two-launch monolith whose own header
describes it as a manual verification pass; it exists to capture App Store
screenshots. As a gate it flaked at four different assertions across
consecutive runs (lines 328, 359, 439, 451) while passing on the identical
commit on another branch, including the run that turned develop red after the
iPad leg merged. Every checkpoint it asserts is already covered by the focused
tests beside it, so skipping it loses no coverage. The comment records how to
run it on demand for screenshot capture.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nine store-ready images (6 iPhone at 1320x2868, 3 iPad at 2064x2752) in
AppStoreScreenshots/final/, plus the capture path that reproduces them.

Preview line no longer repeats the title
----------------------------------------
Rows rendered their title twice -- "Git Rescue / Git Rescue" -- because
notePreviewLine returns the first non-empty parsed line and both shipped
starter notes open with "# <their own title>". Every first-run user saw it,
and it read as placeholder data in the hero screenshot.

notePreviewLine now takes an optional title and skips a leading heading that
exactly matches it; CheatSheetNote.previewLine passes it. Only an exact match
is skipped, so "# Before you push" under a note titled "Git Rescue" still
shows. The hero slide now reads "Git Rescue / git switch -c fix/name",
"Docker Cleanup / docker ps -a" -- every row demonstrating the product.
Fixed in the app rather than the demo content on purpose: fixing only the
seed would have made the listing show something users never see.

MarketingScreenshotTests
------------------------
A capture path separate from testQASweep. That sweep creates a throwaway
"QA Sweep Note" with placeholder body text, so from its third checkpoint on
every capture shows test data -- one shot was a half-keyboard view of
"- [x] Done task line# New Cheat Sheet". Apple treats placeholder content in
a listing as grounds for rejection.

The new tests only ever photograph seeded ScreenshotDemoContent, and archive
a real note so the Trash shot carries content instead of an empty state. They
passed 3/3 on both iPhone and iPad first run, where the sweep flaked. CI skips
them: they are a capture tool, not a gate.

Captions
--------
Apple began OCR-indexing caption text in June 2025, making captions a Tier 1
ranking signal that -- unlike title/subtitle -- should reinforce core
keywords. Each headline is 4-6 words, action-led where natural, owning one
keyword theme, white on the dark brand gradient for OCR contrast.

Verified: 69 tests in 6 suites pass; all nine images are exact upload-slot
sizes with no alpha channel; status bar pinned to 9:41 per Apple convention.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A full local suite run surfaced two iPhone failures that were not product
bugs:

  testTrashRestoreAndPermanentDeleteFlow -- Failed to tap "Select All"
    MenuItem: No matches found
  testEditAndSearchFlow -- failed to scroll to visible (by AX action)
    Button 'Return', kAXErrorCannotComplete

Both tap system chrome the moment they ask for it. The edit menu and the
keyboard appear on their own schedule, later on a loaded machine, so these
fail under load and pass on an idle one -- the same class of false signal
that made testQASweep useless as a gate.

The suite already contained the correct pattern: the QA sweep's own Select All
waits for the menu first. Two of the three call sites did not. Extracted
selectAllText(in:app:) with that wait and routed both through it, and made the
Return tap check isHittable rather than mere existence.

Verified on both device families under the conditions that produced the
failures: 11/11 pass on iPhone and 11/11 on iPad, having failed 2 on iPhone
immediately before the change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copy-paste-ready submission materials for a normal (non-Xcode-Cloud) release:

- RELEASE-1.2.md          master checklist + local archive/upload + Git Flow steps
- app-store-release-kit-1.2.md   every App Store Connect field, validated to
                                 Apple's character limits, plus review notes,
                                 category, age rating, and hosted URLs
- press-kit-1.2.md        fact sheet, journalist pitch, Product Hunt copy,
                          Apple editorial nomination, social launch posts
- testflight-1.2-what-to-test.md  the TestFlight tester message (full + short)

Privacy Policy and Marketing URLs are now live via GitHub Pages
(gh-pages branch): privacy.html mirrors PRIVACY.md, index.html is a themed
landing page. Both return HTTP 200.

Spanish App Store Connect metadata already lives in app-store-localization-es.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SpyLegacyRepository was declared @unchecked Sendable but mutated savedNotes
with no synchronization, while its sibling SpyNoteRepository (NoteStoreTests)
guards the identical state with an NSLock. No test exercises it concurrently
today, so no race manifests -- but the @unchecked Sendable claim should be
backed by real synchronization. Match the sibling's pattern.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rewrote every em dash out of the docs, README, CONTRIBUTING, and a few
source comments, replacing each with the punctuation that actually fits
the sentence rather than a mechanical swap. Also trimmed a handful of
AI-sounding passages: staged contrasts, a forced fragment triad, and
some meta-commentary that referenced the writing process instead of
the app.

Fixed two stale references left over from earlier in the release work:
the release checklist and release kit both still pointed at the interim
GitHub Pages privacy URL after the site moved to the branded
cheatsheet.apphq.online domain.

No factual content changed. Character counts on App Store Connect
fields were rechecked and corrected where a dash removal shifted the
count by a character or two.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lists weskcode@duck.com alongside the GitHub issues link in PRIVACY.md,
the press kit fact sheet, and the App Store release kit. Also adds a
note in the release kit that App Store Connect's App Review Contact
Information (a separate field from the public Support URL) should use
this same address.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GitHub Actions never produced a submittable archive for this project, it
only ran build and test checks. macOS runners bill Actions minutes at a
10x multiplier on every GitHub plan, and the account backing this repo is
now on the Free tier (2,000 minutes a month for a private repo), which
leaves almost no room for iOS work at that rate. Xcode Cloud comes with
its own Apple Developer Program build-hour allotment, does the same
build and test work, and can also sign and deliver straight to
TestFlight, something the old pipeline could never do.

Removed
-------
.github/workflows/ci.yml and its composite action. The whole workflow
ran on macos-26 runners for every job, including the cheap
toolchain-free checks, so there was no lint-only job to keep.

Added
-----
ci_scripts/ci_post_clone.sh regenerates the gitignored Xcode project
from project.yml on every Xcode Cloud clone, then runs the same three
fast checks the old GitHub Actions pipeline ran before its simulator
work: verify-build-sdk.sh, verify-project-config.sh, and
verify-localization.sh.

.xcode-version pins 26.6 for local tooling. Xcode Cloud's own Environment
tab, not this file, is what actually governs which Xcode a workflow
builds with; real-world reports say Xcode Cloud does not always honor a
committed version file, so the two are documented separately rather than
treated as one mechanism.

Updated
-------
CONTRIBUTING.md, README.md, Docs/os-support-policy.md, and
Docs/RELEASE-1.2.md no longer describe the GitHub Actions pipeline or
its Xcode-selection mechanics. Docs/RELEASE-1.2.md's signing section is
rewritten for Xcode Cloud's managed signing, which does not depend on a
certificate in a local keychain the way a manual archive and upload did.

Setting up the actual Xcode Cloud workflow in App Store Connect still
needs a person with the Apple Developer account; steps are in
Docs/RELEASE-1.2.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Follow-up to the previous commit, which removed GitHub Actions but was
missing its own replacement files due to a broken git add invocation.
This adds what should have shipped together with that removal.

ci_scripts/ci_post_clone.sh regenerates the gitignored Xcode project
from project.yml on every Xcode Cloud clone, then runs the same three
fast checks the old GitHub Actions pipeline ran before its simulator
work: verify-build-sdk.sh, verify-project-config.sh, and
verify-localization.sh.

.xcode-version pins 26.6 for local tooling. Xcode Cloud's own Environment
tab, not this file, is what actually governs which Xcode a workflow
builds with.

CONTRIBUTING.md, README.md, Docs/os-support-policy.md, and
Docs/RELEASE-1.2.md no longer describe the GitHub Actions pipeline or its
Xcode-selection mechanics. Docs/RELEASE-1.2.md's signing section now
describes Xcode Cloud's managed signing instead of a local archive and
manual Organizer upload.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- compose_screenshot.py gains a mac device treatment (thin near-square bezel, no camera cutout)
- three 2880x1800 shots from the seeded demo build: split view, instant search, 30-day trash
- release kit and final README updated; mac slot is no longer an open item
…t findings

Full UI/UX/accessibility audit and fix pass:

- Fix NavigationSplitView's sidebar silently collapsing after creating a
  note by pinning columnVisibility to .all; this also made the system
  "Show/Hide Sidebar" command actually functional for the first time.
- Rebuild onboarding around the real note preview instead of a generic
  icon-in-circle row template (owner-approved direction).
- Scope the Trash "Back to Notes" button to only render where it doesn't
  duplicate iOS's system back chevron; keep it on macOS/iPad where it's
  the only way back.
- Add a Settings entry point on iOS/iPadOS -- there was none before, so
  dismissing the widget hint there was permanent with no way to undo it.
- Block onboarding's swipe-to-dismiss so it can't skip marking itself
  complete, which was causing it to reappear on every launch.
- Unify save/load retry into one action; the retry banner (converted to
  an alert on iOS) now covers save failures too, not just load failures.
- Stop misreporting a widget-snapshot save failure as "your note didn't
  save" when the note itself saved fine.
- Make the widget's per-family line limit scale with the note text size
  setting and add a "+N more" indicator so long notes don't clip
  silently; the widget background now follows the pinned note's actual
  color, matching onboarding's own claim.
- Flush pending saves before macOS quits (NSApplicationDelegateAdaptor)
  so a ⌘Q right after typing can't drop the last debounced save.
- Add the missing Spanish translation for "Hide" and close the gap in
  verify-localization.sh that let it through -- it now scans every
  source file in both targets plus literal Text/Button/Label/Toggle
  strings, not just stable dotted keys.
- Soften the checklist "ghost circle" opacity on non-task lines, restyle
  Settings to standard Form Sections, align Trash/editor content padding
  so it doesn't jump on iPad, add accessibility-element grouping to the
  icon-only color swatch and pin buttons.
- Misc: fix the app tint color to match the real accent color instead of
  system blue, remove dead code (LiquidGlassGroup, unused after the
  onboarding rebuild), add justification comments to four @unchecked
  Sendable conformances, drop a redundant state reassignment.

Verified: macOS and iOS builds clean, 69/69 unit tests pass,
verify-localization.sh and verify-project-config.sh both pass. Three
blockers (sidebar collapse, missing accessibility labels, trash-restore
data loss) were checked live against the running app -- the first is
fixed and confirmed, the third didn't reproduce and no code path
supports it, and the second is a suspected macOS 26 beta SDK
accessibility-bridging issue that survived two different code-level
fixes and needs re-verification with VoiceOver on a non-beta OS.

Not touched, needs explicit sign-off per this repo's persistence and
architecture policies: a SwiftData migration plan for
PersistedCheatSheetNote, and moving Color/SwiftUI helpers out of the
Shared model layer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ompt

Sends aggregate, anonymous usage signals only (e.g. "a note was pinned"),
never note content. Signals are a no-op until AnalyticsService.appID is
replaced with a real TelemetryDeck App ID. Declares the new collected
data type in PrivacyInfo.xcprivacy and discloses it in PRIVACY.md,
README.md, and the 1.2 App Store release kit.

Also prompts for a StoreKit review once a user has pinned 5 distinct
notes, at most once per install.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same gap as develop: Xcode Cloud disables automatic SPM resolution and
needs a resolved manifest already in place before it will build.
release/1.2 pins the same TelemetryDeck 2.14.2 as develop, so the same
pin applies. Commit it under ci_scripts/ and have ci_post_clone.sh copy
it into place right after xcodegen generates the project.
On compact iPhone layouts the palette picker shared a row with the font,
size, and pin controls and could push the pin button off screen. Put the
palette on its own row, move the pin button into a shared property, and
have the style-and-pin UI test assert the title field and pin button sit
inside the app frame.
Add the checklist, text size, and per-note captures, hide widget hints,
and replace the silent `if exists` fallbacks with XCTFail so a missing
control fails the run instead of producing a short screenshot set.
render_final.py builds every iPhone and Mac screenshot from captions.tsv
using the centered-caption layout. Rewrite the final/ README to match the
ten-plus-ten set, and ignore the Python bytecode cache.
…mages

iPhone images are 1320x2868 and Mac images are 2880x1800, all RGB with no
alpha. The iPad images from the earlier set are unchanged.
Replace the Git Flow description in CONTRIBUTING.md and the OS support
policy with a main-only flow, keeping gh-pages as the Pages host. Update
the release checklist for the 20-image screenshot set, the header layout
fix, and the re-run test results, and drop the develop back-merge steps.
Point the screenshot capture command at an installed simulator.
Use colons, commas, and periods instead. No change to the facts stated.
@weskcode
weskcode merged commit df1045c into main Oct 1, 2026
1 check was pending
@weskcode
weskcode deleted the release/1.2 branch October 1, 2026 20:35
@weskcode weskcode changed the title release: 1.2 (build 14) release: CheatSheet 1.2 (Spanish, analytics, iOS 26 baseline, new screenshots) Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant