Skip to content

Release 0.5.0+11 - #548

Open
kipyin wants to merge 58 commits into
mainfrom
release/0.5.0-build-11
Open

Release 0.5.0+11#548
kipyin wants to merge 58 commits into
mainfrom
release/0.5.0-build-11

Conversation

@kipyin

@kipyin kipyin commented Apr 29, 2026

Copy link
Copy Markdown
Owner

No description provided.

kipyin and others added 30 commits April 16, 2026 22:05
…#493)

## What changed

`grace sentry start` accepts `--main-branch <name>` so you can point
sentry at a non-`main` base for that run without editing config. The
value is applied after TOML and `SENTRY_MAIN_BRANCH` are loaded, so the
flag overrides both for that process.

Documentation now describes the three ways to set the base branch:
`[sentry] main_branch` in `gracenotes-dev.toml`, `SENTRY_MAIN_BRANCH`,
and `--main-branch`. A commented example was added under `[sentry]` in
the repo TOML.

## Tests

`uv run --project Scripts/gracenotes-dev python -m unittest discover -s
Scripts/gracenotes-dev/tests` (196 tests, OK).
## Headline

Onboarding reset now truly starts the guided journal path from a clean
slate.

## User impact

If someone reset journal onboarding (for example from Settings or
support flows), leftover tutorial milestone flags could still make the
app treat the install as already onboarded. That could skip or confuse
the guided journal experience right after a reset. Clearing those keys
prevents the completion flag from being immediately inferred again from
old tutorial state.

## What changed

The journal onboarding reset path now removes all stored journal
tutorial keys up front, using the existing helper on
`JournalTutorialStorageKeys`, before clearing the onboarding-specific
`UserDefaults` entries and launch tracking. A short doc comment was
added on `resetAll` to explain that this avoids re-deriving “guided
journal complete” from leftover tutorial presence when the explicit
completion key is gone.

## Verification

On a Mac with Xcode and the project’s `grace` CLI installed, run `grace
ci` (or `grace test` with the usual simulator destination from
`gracenotes-dev.toml`) to confirm the app still builds and tests pass.

## Risk

Medium

## Touch class

`business-logic`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
…st (#497)

## Headline

Past statistics no longer sorts every journal before applying the
selected history window, which keeps large libraries responsive.

## User impact

Users with many entries outside the chosen Past range should see the
same charts and lists as before, with less wasted work when opening Past
statistics. Reliability improves indirectly by reducing unnecessary
sorting on the main thread during those updates.

## What changed

The history window helper used to sort the entire journal list, then
drop entries outside the range. That meant the cost of sorting grew with
total library size even when only a small slice was needed. It now
filters to the validated date range first and sorts only that subset,
using the same ordering rules as before.

## Verification

On a Mac with Xcode and the iOS Simulator, run `grace ci` (or `grace
test` with the project’s usual destination) to confirm the app still
builds and tests pass.

## Risk

Medium

## Touch class

`business-logic`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
## Headline

Resetting journal onboarding now leaves users in a true fresh state
instead of silently re-marking the guided journal as complete.

## User impact

When support or developers reset onboarding, people should see the full
guided journal flow again. Before this fix, clearing stored keys could
let the app infer “already completed” from reminders, first-run, or
other signals, so the journal could still behave like a finished
onboarding even right after a reset.

## What changed

The journal onboarding reset path clears the usual UserDefaults keys and
tutorial milestones, then explicitly stores guided journal completion as
false. That stops the resolver from treating a missing key as a signal
to run the migration heuristics again and immediately write completion
back to true. Documentation on the reset helper was updated to explain
why removing the key alone was not enough.

## Verification

On a Mac with Xcode, run `grace test` or `grace ci` from the repo root
(or the project’s usual CI profile) to lint, build, and exercise tests
against the configured simulator destinations.

## Risk

Medium

## Touch class

`business-logic`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
## Headline

Share card style picks and the busy overlay behave more predictably when
you customize and export a journal image.

## User impact

Style chip taps should refresh the live preview the same way other share
options do. After you tap Share, the screen should not stay frozen if
image creation fails or if future code paths are added, because the
in-progress lock is cleared in one place.

## What changed

The style preset buttons no longer mutate the share draft in place. They
copy the draft, change the selected style, and assign it back, matching
how redaction, section visibility, and toggles already update state so
SwiftUI reliably notices changes to the struct-backed draft.

Share now uses a single defer to clear the in-progress flag when
rendering finishes, instead of repeating that reset on the success and
error paths. That keeps the UI from staying disabled if a new early exit
slips in later and makes the intent obvious in one spot.

## Verification

On a Mac with Xcode and the project’s grace CLI, run `grace ci` (or
`grace test` with the repo’s default simulator destination). In the app,
open the journal share composer, tap different style chips and confirm
the preview updates, tap Share, and confirm controls re-enable after the
sheet flow; trigger a render failure path if you can and confirm the
composer is not stuck disabled.

## Risk

Medium

## Touch class

`ui-ux`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
…#503)

## Headline

Keeps the completion pill’s morph animation stable when progress math
goes bad.

## User impact

If bloom progress ever became NaN or infinite (for example from a bad
animation handoff or edge-case math), the outline and scale around the
completion pill could behave unpredictably or disappear. This change
treats those values as zero so the pill stays visible and predictable
instead of glitching.

## What changed

The completion pill already clamped morph bloom progress to the 0–1
range for normal values. That clamp did not protect against non-finite
floats, which can still flow through min/max and break opacity and
scale. The morph bloom progress path now checks finiteness first and
falls back to a safe value, then applies the same 0–1 clamp. The comment
was updated to describe that behavior in plain terms.

## Verification

On a Mac with Xcode and the project’s grace CLI installed, run `grace
ci` (or `grace test` with the usual simulator destination from
gracenotes-dev.toml) to confirm the app builds and tests pass. Manually
spot-check the journal header completion pill during any
morph/celebration transition if you want extra confidence in the visual
path.

## Risk

Medium

## Touch class

`ui-ux`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
## Headline

Journal import now deletes extra same-day journal rows so each day stays
a single source of truth.

## User impact

If the database ever had more than one journal row for the same calendar
day (for example from earlier bugs or edge cases), importing could
update one row while leaving stale duplicates behind. That could make
the timeline or day views confusing or show outdated content. After this
change, import removes those extras so each day matches the imported
backup cleanly.

## What changed

The import path already deduplicates entries in the backup file by
calendar day. On disk, `fetchEntry` returns one canonical journal per
day, but other rows for that day could still exist. When updating an
existing day, the service now fetches all journals whose `entryDate`
falls in that calendar day and deletes every row except the one being
updated.

The per-day deduplication helper was moved into an extension for
organization, and a small doc comment was wrapped for line length.
Behavior for merge conflict detection and file-side deduplication is
unchanged except for this cleanup of duplicate persisted rows during
apply.

## Verification

On a Mac with Xcode and the project’s `grace` CLI installed, run `grace
ci` (or `grace test` with the default simulator destination from
`gracenotes-dev.toml`) so lint and simulator build/tests cover this
path. Manually, import a backup on a store that has duplicate rows for
one day and confirm only one journal remains for that day after import.

## Risk

Medium

## Touch class

`business-logic`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.
Move default loading/reassurance strings to a nonisolated enum so init
defaults are valid under strict concurrency. Extend StartupCoordinator
async test waits to tolerate slow first ModelContainer on CI simulators.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
## Headline

Stops leftover CloudKit account-status work when Settings no longer
needs it.

## User impact

If someone opens Settings and leaves before iCloud status finishes
loading, the app no longer keeps that fetch running in the background.
That trims unnecessary work and avoids odd edge cases around a view
model that is already gone.

## What changed

The iCloud account status helper already cancels superseded refreshes
and ignores stale results. This adds the same cleanup when the
observable object is destroyed: any in-flight refresh task is cancelled
on teardown so work tied to a dismissed screen does not continue.

## Verification

On a Mac with Xcode and the repo’s grace CLI installed, run `grace ci`
from the repository root (or `grace test` with the project’s default
simulator destination) to confirm the app still builds and tests pass.

## Risk

Medium

## Touch class

`business-logic`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
## Headline

Keeps the guided tour on valid pages and measures path titles more
reliably.

## User impact

Users who skip the congratulations screen or swipe through the tour are
less likely to land on a missing page or see a blank pager state. The
growth-path strip should align dots with titles more consistently when
layout updates.

## What changed

The tour now clamps the selected page whenever it changes, not only on
first appearance, so the pager stays within the first and last visible
tabs when the congratulations page is omitted or SwiftUI tries to select
an invalid index. A small helper centralizes that range logic for
clarity and reuse. The preference key that records each step’s title
line height now merges child measurements with the maximum instead of
the last value, which better matches multi-line or updating title
layout.

## Verification

On a Mac with Xcode, run `grace ci` (or `grace test` with the project’s
default simulator destination). Manually open the App Tour with
congratulations both shown and skipped; swipe between pages and confirm
paging, indicators, and path-strip alignment look correct.

## Risk

Medium

## Touch class

`ui-ux`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
## Headline

Info card accent colors now follow the completion badge cases directly
instead of an extra mapping hop.

## User impact

Completion badge help text should look the same as before, with tint
colors still matching each growth stage. The change makes styling easier
to reason about and safer to maintain if completion levels or mappings
evolve.

## What changed

The info card tint helper used to branch on the mapped
`JournalCompletionLevel` (soil through bloom). It now switches on
`CompletionBadgeInfo` itself (empty through full), which matches how
titles and descriptions are already expressed. The color choices stay
aligned with the previous mapping: earliest stage muted, light check-in
accent, middle stages standard text, and full completion using the
full-stage text color.

## Verification

On a Mac with Xcode, run `grace ci` (or `grace test` per project docs)
to confirm the GraceNotes scheme builds and tests pass; spot-check the
journal completion badge info card in the simulator to confirm tint
still matches each stage.

## Risk

Medium

## Touch class

`ui-ux`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
…es (#509)

## Headline

Weekly review insight generation does less redundant work when scanning
your journal history.

## User impact

Faster, more efficient insight generation when you have many entries,
with the same weekly review behavior. You should see snappier review
screens and less unnecessary CPU work on device.

## What changed

Building the current- and previous-week entry lists used two full scans
of every journal entry—one filter for this week and another for last
week. That duplicated work for large libraries. The generator now walks
the entry list once, assigns each entry to this week, last week, or
neither, and passes those lists into the same analysis as before. Logic
for which entries belong to which period is unchanged; only how we
collect them is streamlined.

## Verification

On a Mac with the repo and Xcode, run `grace ci` (or `grace test` with
the project’s default simulator destination) to confirm the app builds
and tests pass after the change.

## Risk

Medium

## Touch class

`business-logic`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
## Headline

Journal share preview stubs now speak their on-screen message instead of
a separate generic label.

## User impact

People using VoiceOver hear the same italic preview line that sighted
users read, which is clearer than a second, generic announcement. It
also avoids overriding the real stub copy with a redundant accessibility
label.

## What changed

In the journal share card, preview stub rows are plain text lines with
italic styling. The view had an explicit accessibility label on those
rows, which could cause VoiceOver to announce a generic string instead
of (or in addition to) the visible stub message. That custom label was
removed so accessibility falls back to the line’s text content, matching
the visual copy and reducing confusing or duplicate speech.

## Verification

On a Mac with Xcode, run `grace ci` (or `grace test` with the project’s
usual simulator destination). For a manual check, open the share card
with VoiceOver enabled and move to a preview stub line; it should read
the full stub message as shown on screen.

## Risk

Medium

## Touch class

`ui-ux`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
## Headline

Scheduled folder backups now use the same timestamp rules as the rest of
journal export naming.

## User impact

Backup file names stay aligned with exports you trigger elsewhere in the
app, so files sort and read consistently in the chosen folder. One
shared implementation also means future timestamp tweaks cannot silently
diverge between flows.

## What changed

Scheduled backup filenames no longer configure their own DateFormatter
for the time stamp. The runner asks JournalDataExportService for the
stamp via exportFilenameTimestamp(for:) and keeps the existing
grace-notes-scheduled prefix, UUID suffix, and .json extension. Behavior
stays the same idea—sortable, unique names—while centralizing how the
stamp is produced.

## Verification

On macOS with Xcode and the iOS Simulator available, run grace ci from
the repository root (or grace test with your usual destination from
gracenotes-dev.toml) to confirm the GraceNotes scheme builds and
automated checks pass.

## Risk

Medium

## Touch class

`business-logic`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
)

## Headline

Growth-stage skyline glyphs now compute Dynamic Type scaling in one
place instead of two parallel switches.

## User impact

The Past skyline and calendar teaser growth icons should look the same
as before. The change mainly improves maintainability so future tweaks
to size or scaling are less likely to drift or get out of sync.

## What changed

The internal `Metrics` helper was replaced from an enum with duplicated
`switch` logic in `glyphSize` and `glyphChrome` to a lightweight struct
that holds both numbers together. Skyline sizing uses a single
`skyline(dynamicTypeSize:)` factory that applies the existing layout
scale once; the calendar day teaser keeps fixed dimensions as a static
preset. The public initializers and the view body are unchanged in
behavior.

## Verification

On a Mac with Xcode and the project’s `grace` CLI, run `grace ci` (or
`grace test` per your usual workflow) to confirm the app builds and
tests pass; spot-check the Review Past skyline and calendar day cells to
confirm growth glyphs still align and scale as expected at a few Dynamic
Type sizes.

## Risk

Medium

## Touch class

`ui-ux`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
#521)

## Headline

When one save crosses several first-time milestones, the app now shows
the strongest growth toast instead of the earliest rule in the list.

## User impact

Users who fill out their journal in one go can see a celebration that
matches their biggest step forward (Bloom over Leaf over 1/1/1) instead
of a weaker or misleading first toast. Behind the scenes, first-time
flags still record each milestone so nothing is silently skipped.

## What changed

The journal tutorial unlock evaluator already detects when someone
crosses 1/1/1, reaches Leaf (balanced), or reaches Bloom for the first
time. When more than one of those happens in a single update, only one
highlight drives the toast. The logic was reordered so Bloom is
preferred, then Leaf, then the 1/1/1 milestone. A short comment
documents that behavior and that the separate recording booleans still
mark each first-time achievement.

## Verification

On a Mac with the repo’s dev setup, run `grace test` (or `grace ci` for
lint plus simulator build) to confirm unit tests and the GraceNotes
scheme still pass.

## Risk

Medium

## Touch class

`business-logic`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
…status (#522)

## Headline

Stop flagging normal CloudKit or URL cancellations as iCloud status
failures in logs.

## User impact

When iCloud work is cancelled or interrupted in ways CloudKit reports as
operation cancelled (not only Swift task cancellation), Settings and
diagnostics see fewer scary “failed to fetch iCloud account status”
messages. That makes real sync or account problems easier to spot and
reduces noise during normal navigation or teardown.

## What changed

The iCloud account status fetch already had a helper that recognizes
several cancellation shapes (Swift cancellation, CloudKit operation
cancelled, URL cancelled, and nested underlying errors). The catch path
only skipped logging for plain Swift CancellationError, so other
cancellation forms were still logged as errors even though the code
already treated any failure the same for the user-facing bucket. The
catch path now uses the same cancellation detection before logging, so
benign cancellations are not reported as failures.

## Verification

On a Mac with Xcode and the repo’s grace CLI installed, run `grace ci`
(or `grace test` with the project’s default simulator destination) to
confirm the GraceNotes scheme still builds and tests pass.

## Risk

Medium

## Touch class

`business-logic`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
…523)

## Headline

Daily reminder rescheduling now relies on one shared, validated
clock-time reader.

## User impact

Reminder scheduling behavior stays the same for people who use daily
reminders, but the app no longer carries two different ways to interpret
the saved time from UserDefaults. That reduces the risk of future edits
accidentally changing behavior in one path but not the other.

## What changed

`DailyReminderNotificationSync` already used
`ReminderSettings.coercedTimeInterval(fromUserDefaults:)` when computing
the next reminder time. A private helper that duplicated the same
UserDefaults parsing and validation was left in the file but never
called, so it was removed. Reminder time coercion and non-finite value
handling remain centralized in `ReminderSettings`, which is the single
place reviewers should look for that logic.

## Verification

On a Mac with Xcode and the project’s `grace` CLI installed, run `grace
ci` (or `grace test` with the repo’s default simulator destination) to
confirm the GraceNotes scheme still builds and tests pass.

## Risk

Medium

## Touch class

`business-logic`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
## Headline

Separates SwiftUI row identity from UI-test identifiers so
recurring-theme rows update reliably when labels collide.

## User impact

When two recurring themes looked the same by label, list updates could
behave oddly and automation could target the wrong row. The card now
keys rows in a way that matches the theme value while still using a
composite key for accessibility identifiers when labels are not unique.

## What changed

The most recurring themes list no longer uses a custom string property
as the ForEach identity. Rows are keyed by the theme value itself, which
is appropriate when the model differentiates themes beyond the display
label. The old composite string used for stable differentiation was
moved into a small helper used only for accessibility and UI-test
identifiers, so test IDs stay stable where labels can collide without
tying that logic to list animation and updates.

## Verification

On a Mac with Xcode, run `grace ci` (or `grace test` with the project’s
default destination) to lint and build. Manually open Review insights
with overlapping or similarly labeled recurring themes and confirm rows
update; if you have UI tests that hit `MostRecurringThemeRow.*`
identifiers, re-run those to confirm they still resolve distinct rows.

## Risk

Medium

## Touch class

`ui-ux`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
kipyin and others added 18 commits April 17, 2026 01:36
## Headline

Weekly review rhythm stats compute the earliest entry day without
materializing a full day array.

## User impact

Users with large journals get snappier weekly review and past-statistics
aggregation with less transient memory use while scrolling or refreshing
review screens. Behavior stays the same: the code still finds the
minimum calendar day the same way, just without building every
intermediate date upfront.

## What changed

Computing the earliest calendar day across all entries used a non-lazy
map, which built a full array of normalized day values before taking the
minimum. That extra allocation scales with total entries and is
unnecessary for a min scan. The path now uses a lazy map so only one day
at a time is produced while the minimum is computed, cutting peak
allocation for the same logical result.

## Verification

On a Mac with Xcode, run `swiftlint lint` on the touched file or repo,
then `grace ci` (or `grace test`) from the project root to confirm the
GraceNotes scheme still builds and tests pass.

## Risk

Medium

## Touch class

`business-logic`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
…#531)

## Headline

Today tab journal appearance now migrates and normalizes even when the
saved preference is not a string.

## User impact

If the journal appearance key was ever stored as a non-string (or
otherwise read as nil by `string(forKey:)`), the app could skip cleanup
and leave `@AppStorage` out of sync with what you see. After this
change, those cases are treated as empty, resolved to the correct mode,
and rewritten to a canonical string so the Today tab stays predictable
and settings stay consistent.

## What changed

The migration helper for journal appearance no longer exits early
whenever `UserDefaults.string(forKey:)` is nil. It now checks whether
anything is stored under the key at all: real strings are used as
before; if a value exists but is not a string, it is treated as an empty
string so the existing normalization path can pick a valid mode and
persist the canonical raw value. If the key is absent entirely, the
function still returns without writing.

## Verification

On a Mac with Xcode and the project’s `grace` CLI, run `grace ci` (or
`grace test` per your usual profile) to confirm lint and simulator
builds pass. Optionally reset or seed `UserDefaults` with a non-string
for `journalTodayAppearanceMode`, launch the app, and confirm Today
appearance and migration behave as expected.

## Risk

Medium

## Touch class

`business-logic`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
## Headline

Canceling a JSON merge conflict now abandons the import cleanly instead
of leaving stale state behind.

## User impact

If you backed out of resolving merge conflicts, the app could still hold
onto the pending file and conflict list. That could make a later import
confusing or behave as if an old import was still in flight. Cancel now
clears that state so Settings reflects what you actually chose.

## What changed

The merge-conflict alert’s Cancel action used to do nothing beyond
dismissing the sheet. It now clears the pending import URL and empties
the list of conflicting days so abandoned imports do not linger in
memory.

This is a small state-management fix in Import & Export settings; no
change to how merge, keep-device, or keep-backup resolution works when
you confirm a choice.

## Verification

On a Mac with Xcode and the repo’s dev tooling, run `grace ci` (or
`grace test` with the project’s default simulator destination) after
reproducing merge conflicts during JSON import and tapping Cancel;
confirm a new import can start without odd carryover from the canceled
flow.

## Risk

Medium

## Touch class

`ui-ux`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
## Headline

Weekly review reflection days now follow the same meaningful-entry bar
everywhere.

## User impact

Reflection-day totals and the week’s day activity strip stay consistent
with how Grace Notes already scores a day as meaningful, so insights and
completion signals are not inflated by notes-only or reflections-only
stubs. Reviewers see fewer mismatches between “reflection days” and
other weekly stats.

## What changed

Reflection day counting and the per-day activity set previously treated
a day as active if an entry had meaningful content or only non-empty
trimmed reading notes or reflections. That second path could bump
reflection totals and disagree with `meaningfulEntryCount` and the rest
of the weekly aggregates.

The builder now counts those days only when `hasMeaningfulContent` is
true, matching the single definition used elsewhere, and removes the
helper that only looked at notes and reflections text.

## Verification

On a Mac with Xcode and the project’s `grace` CLI, run `grace ci` (or
`grace test` with your usual simulator destination). Optionally open
Weekly Review for a week where some days have only notes or reflections
and confirm reflection-day counts match expectations for meaningful
entries.

## Risk

Medium

## Touch class

`business-logic`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
…537)

## Headline

Past-tab review insights now refresh on a consistent day calendar, not
mixed timestamps.

## User impact

Entries saved at different times on the same day could be treated
inconsistently when deciding which journals affect Past insights and
trend weeks. That could cause insights to update too often, too little,
or feel out of sync with what you actually wrote. This change makes that
logic line up with how the rest of the window uses calendar days.

## What changed

The code that picks which journal entries belong in the current and
previous review weeks no longer uses raw date containment on the entry
timestamp. It compares the entry’s calendar day (start of day) to each
week range’s lower and upper bounds, the same way the past-statistics
history window already works. Snapshot ordering for the refresh key now
sorts entry IDs as UUIDs instead of stringifying them first, which keeps
ordering stable with less overhead.

## Verification

On a Mac with the repo’s Xcode setup, run `grace ci` (or at least `grace
test`) to confirm the GraceNotes scheme still builds and tests pass.

## Risk

Medium

## Touch class

`business-logic`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
## Headline

Growth-stage labels for your journal now derive from one consistent,
defensive count model.

## User impact

Chip-section completion (Soil through Bloom) drives progress cues and
history; inconsistent or edge-case counts could skew that story.
Clamping counts to zero and expressing bloom, leaf, and twig rules
through min/max across sections keeps the stage fair and predictable.

## What changed

Completion and bloom checks used repeated comparisons on raw integers,
and twig used easy-to-misread OR/AND logic. The change introduces a
small private helper that treats each section count as non-negative,
then derives the weakest and strongest section sizes from those values.
Bloom and leaf tiers now key off that shared minimum (with a named
threshold for the pre-bloom “balanced” bar), and the twig case uses max
versus min so “mixed progress” is expressed in plain language. Bloom
detection and “minimum across sections” reuse the same path so the app
does not drift between call sites.

## Verification

On a Mac with Xcode and the project’s default simulator setup, run
`grace ci` (or `grace test` with the usual GraceNotes destination) to
lint, build, and exercise automated tests.

## Risk

Medium

## Touch class

`business-logic`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.
…539)

## Headline

Past search line items now always reflect the entry’s stored text, and
long-field IDs hash UTF-8 with less overhead.

## User impact

Search results for line-based matches stay aligned with what is actually
saved in the journal, so highlights and snippets are less likely to
drift from the source. For whole-field matches (for example reading
notes or reflections), computing stable IDs should be a bit lighter on
memory when hashing large text.

## What changed

The initializer that builds a match from an `Entry` no longer takes a
separate `content` argument. It always fills the match’s text from the
entry’s full text, so callers cannot accidentally pass a different
string than what is stored. For fingerprinting whole-field matches,
SHA-256 now prefers hashing the string’s UTF-8 bytes in place when the
backing storage allows it, and only falls back to building a `Data`
buffer when it must. The public shape of `JournalSearchMatch` and the
meaning of its `id` for field matches are unchanged.

## Verification

On a Mac with Xcode and the project’s `grace` CLI installed, run `grace
ci` (or `grace test` with the usual simulator destination) to confirm
the app builds and tests pass.

## Risk

Medium

## Touch class

`business-logic`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
## Headline

Share images only honor redactions for lines that actually exist, and
toggles cannot store invalid indices.

## User impact

Stale or out-of-range redaction indices (for example after editing or
shortening journal text) could hide the wrong lines or leave privacy
expectations unclear. Negative indices could also pollute stored state.
The share card now treats redactions as valid only for the current line
count and ignores invalid toggles.

## What changed

The redaction toggle helper now ignores negative indices so the draft
never records impossible line numbers.

When building list and prose lines for the share preview, redaction is
applied using only indices that fall within the current number of lines.
Indices that are out of range are ignored, so the rendered card stays
consistent with what is on screen and avoids mis-applying redaction
across edits.

## Verification

From the repo root on macOS: run `swiftlint lint` (style). For a full
simulator build aligned with CI, run `grace ci` (or `grace test` with
your usual destination). Exercise the share composer: toggle redactions,
edit entries so line counts change, and confirm only intended lines are
hidden on the share preview.

## Risk

Medium

## Touch class

`business-logic`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
… thread (#504)

## Headline

Smoother, more reliable manual imports when merge conflicts appear or
files are large.

## User impact

Dismissing the merge-conflict alert with Cancel no longer leaves a
half-finished import hanging in memory, so you are less likely to see
odd follow-up behavior. Reading the chosen JSON off the main thread
keeps Settings responsive while a big backup file is loaded.

## What changed

The merge-conflict alert’s Cancel action now clears the pending import
URL and the list of conflicting days so abandoned flows do not retain
stale state.

Manual import now loads file data in a user-initiated background task
(with helpers made static so the work can run without capturing the
view), matching the existing pattern for the heavier import step and
reducing main-thread stalls when reading large files or resolving
security-scoped backup paths.

## Verification

On a Mac with Xcode and the project’s grace CLI installed, run
`swiftlint lint` from the repo root and `grace ci` (or `grace test` with
the usual simulator destination) to confirm the GraceNotes scheme still
builds and tests pass.

## Risk

Medium

## Touch class

`ui-ux`
---
*Automated by `grace sentry`.* Merge is normally unblocked by CI and
review resolution; if stuck, an allowlisted account may post
`/sentry-approve` as an emergency override.

---------

Co-authored-by: Kip <kipyin@users.noreply.github.com>
@kipyin

kipyin commented Apr 29, 2026

Copy link
Copy Markdown
Owner Author

@cursor resolve merge conflict

@cursor

cursor Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Here’s what was done:

Merge conflict

Merging origin/main into release/0.5.0-build-11 conflicted only in PersistenceController.swift, in resetUITestStoreIfRequested(storeURL:).

  • HEAD (release branch): removeUITestStoreFileIfPresent(at: storeURL) — uses the passed-in URL, retries removal once, logs via persistenceUITestLogger.
  • main: inlined removal using uiTestStoreURL again instead of storeURL, single try, logger.error.

Resolution: Kept the release branch’s removeUITestStoreFileIfPresent(at: storeURL) so reset targets the same URL that makeForUITesting() already resolved (and avoids re-resolving uiTestStoreURL), and keeps the retry + logging behavior from #504.

Git

  • Merge commit: 156c8a0Merge origin/main into release/0.5.0-build-11 with the message above.
  • Pushed: release/0.5.0-build-11origin.
  • SwiftLint on the resolved file: only the existing type_body_length warning.

The PR body wasn’t updated automatically (PR description isn’t agent-managed). Your GitHub conflict should clear now that the merged branch is pushed; if the UI still shows conflict, refresh or reopen the PR compare view.

Open in Web Open in Cursor 

cursoragent and others added 9 commits April 29, 2026 22:57
Resolve PersistenceController conflict: keep removeUITestStoreFileIfPresent(at:)
for UI test store reset (retry + explicit storeURL) alongside main updates.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
Co-authored-by: Kip <kipyin@users.noreply.github.com>
…ter import

Past journal sheets and backgrounding could dismiss before the 400ms
debounce completed, dropping unsaved edits. Settings import wrote on a
background ModelContext while Today kept stale in-memory state; a queued
autosave could overwrite imported rows for the current day.

- persistImmediately on JournalScreen disappear and scene background
- Import on the main ModelContext and post journalStoreDidChangeExternally
- Reload Today from store with autosave epoch to drop stale debounced saves
- Commit inline chip drafts before midnight day rollover

Co-authored-by: Kip <kipyin@users.noreply.github.com>
Midnight or resume calls refreshTodayIfStale, which persisted then loaded
today even when context.save() failed. That replaced in-memory edits with
a new day and could discard unsaved work. Gate loadEntry on a successful
persist and add a regression test.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
#468 fixed JournalItem decoding but the Settings import path still dropped
chip lines whose fullText was empty while entryLabel/chipLabel held the text.
mapItems now uses importableFullText so legacy backups restore those lines.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
…al rows

Added `.accessibilityHidden(true)` to decorative SF Symbols (like chevrons and clear marks) in `SettingsScreen`, `ImportExportSettingsScreen`, and `SequentialEntryRowView`. This removes redundant VoiceOver announcements since the text or button accessibility label already provides full context.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
Consolidates palette PRs #570 and #596 for App Tour settings parity,
Past toolbar done, and review trend badges.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
Replace the single welcome screen with paged onboarding copy and SVG
art, and drop the redundant search-match content argument now derived
from Entry.

Co-authored-by: Kip <kipyin@users.noreply.github.com>
@kipyin kipyin added the full-ci Apply to a PR to run the full test suite on the PR branch (see issue #96). label Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

full-ci Apply to a PR to run the full test suite on the PR branch (see issue #96).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants