updateEntry's .map() always returned a new array even when no entry matched, causing unnecessary state changes. During note creation, addEntry uses startTransition (deferred) while markContentPending calls updateEntry synchronously — the entry doesn't exist yet, so the no-op .map() produced a new reference that cascaded into "Maximum update depth exceeded" (which surfaced as React error #185 in the production WKWebView build). The fix makes updateEntry bail out (return prev) when no entry was changed, preventing the spurious state update. Also removes the defensive try-catch from the previous fix attempt and cleans up an unnecessary setToastMessage dependency. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1.7 KiB
1.7 KiB
type, id, title, status, date
| type | id | title | status | date |
|---|---|---|---|---|
| ADR | 0017 | Canary release channel and local feature flags | active | 2026-03-25 |
Context
Shipping new features directly to all users is risky. A mechanism was needed to let early adopters test pre-release builds and to gate experimental features behind flags that can be toggled without a new release.
Keyboard-first design principle
Decision
Add a canary release channel alongside stable, with builds from the canary branch. Feature flags are localStorage-based (ff_<name>) with compile-time defaults, checked via useFeatureFlag(flag) hook. The update channel is configurable in Settings.
Options considered
- Option A (chosen): Canary branch + localStorage feature flags — simple, no server infrastructure, users opt in via Settings. Downside: no remote flag management, no gradual rollout percentages.
- Option B: Server-side feature flags (LaunchDarkly, Unleash) — gradual rollouts, A/B testing. Downside: external dependency, requires server infrastructure, adds latency.
- Option C: Single release channel with only feature flags — simpler CI. Downside: no way to test full pre-release builds.
Consequences
release.ymlbuilds stable frommain;release-canary.ymlbuilds canary fromcanarybranch.- Canary releases produce
latest-canary.jsonon GitHub Pages, marked as prerelease. useUpdater(channel)checks the appropriate update manifest.useFeatureFlag(flag)checks localStorage override, then compile-time default. Type-safe viaFeatureFlagNameunion.update_channelstored in Settings as"stable"or"canary".- Re-evaluation trigger: if user base grows enough to warrant server-side gradual rollouts.