Repository navigation
Sync upstream v9.0.0 + coach long-term memory, chat redesign & floating button - #2
Merged
Merged
Conversation
Foundation for the Coach UI redesign. Replaces the single overwritten
transcript with a list of named, findable conversations, and makes the
plot_metric charts survive a relaunch.
- ChatMessage gains a `date` (back-compat decode: old transcripts
without it default to now) for time separators.
- CoachChartSnapshot: a Codable mirror of CoachChartArtifact (TrendPoint
in StrandDesign isn't Codable, so only the raw {date,value} pairs +
range + kind are stored) so charts persist. Kind gains a String raw
value for that.
- CoachConversation + CoachConversationStore (coach-conversations.json),
migrating a legacy coach-transcript.json into the first conversation
on first run. CoachTranscriptStore kept only as the legacy reader.
- AICoachEngine: conversations + activeConversationID; `messages` is now
a computed view over the active conversation (every existing append/
remove/subscript site keeps working), auto-titling from the first user
turn and re-sorting most-recent-first. New newConversation/switchTo/
renameConversation/deleteConversation; clearChat kept as an alias.
flushPendingCharts also snapshots charts into the active conversation;
charts are rebuilt from snapshots on load/switch.
Storage is device-local (not in the .noopbak whitelist), so no Kotlin
twin is required. UI rewire follows in later commits; the existing
CoachView still compiles against this (macOS build verified).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Redesigns the Coach screen around the user's complaints: settings were stacked above the chat, the transcript was a cramped nested scroll, and old conversations were lost. - CoachView is now a full-screen messenger: slim header (history • title • new-chat • gear), a transcript that fills the screen (no more 460pt nested scroll), suggestion chips, and a composer docked to the bottom via safeAreaInset (rises above the keyboard). Time separators from ChatMessage.date; per-message Copy and, on the last reply, Regenerate via context menu; the send button becomes Stop while a reply streams. Corner radii come from one local CoachRadius source instead of scattered literals. - CoachSettingsView (new): all six config cards + provider/key/model setup + Disconnect, moved behind the gear as a sheet. Bindings unchanged — relocated, not rewired. - CoachHistoryView (new): the saved conversations as findable threads — tap to switch, swipe/long-press to rename or delete, plus New chat. - AICoachEngine: startSend/stop (cancellable task, cancellation no longer surfaces as an error) and regenerate (drops back to the last user turn, purging its charts, and resends). macOS build verified (BUILD SUCCEEDED). iOS build + simulator run come with Phase 6. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- CoachChartBubble is taller (170→220) and tappable, opening a CoachChartDetail sheet with a full-size chart, so the plot_metric trends are actually readable (and, with persistence from the data- model commit, survive a relaunch). - A prominent "Ask your Coach" card on Today opens the redesigned chat directly via a full-screen cover (sheet on macOS), so Coach is no longer buried two levels deep under More. Added to both the default LiquidTodayView and the classic TodayView. The shared View.coachCover helper lives in CoachView.swift so both entries reuse it; the More route and the daily check-in deep link still work. iOS (NOOPiOS) and macOS (Strand) both build. End-to-end simulator run still to do. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase A of the long-term-memory upgrade. - MemoryFact gains category (goal/injury/preference/physiology/schedule/ other) and importance (pinned/normal), with back-compat decoding so existing facts survive the upgrade. - CoachMemory: near-duplicate detection on add (a rephrasing supersedes the old fact instead of stacking and wasting the 40-slot budget); update(id,text); firstMatch(query); relevantFacts(for:limit:) ranks by keyword overlap with the question + recency; pinnedBlock (always) split from relevantBlock(for:limit:). - Injection reworked: the system prompt now carries only pinned facts + goal; the facts relevant to the CURRENT question are folded into that turn's context in wireMessages, so a large memory no longer bloats every request. - Tools: remember_fact takes optional category/importance; new update_fact and forget_fact let the model self-heal its memory instead of only appending contradictions. macOS build verified. iOS build + tests come with the later phases. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… (ryanbr#487) The home screen left a loose band of empty sky below the 'N O O P' wordmark: the decorative wordmark and the pinned 'Arrange' affordance were each their own full-width LazyColumn item, so the scaffold's uniform 12dp rowSpacing stacked two near-empty rows (each with a section gap above AND below) between the compact header and the hero card. Fold both into one item under the header — the wordmark hangs 2dp beneath the title and shares its row with the Arrange button (wordmark centred, Arrange trailing) — removing those section gaps and the dead vertical space, while keeping the iOS-parity wordmark and the Arrange affordance fully functional. Reported by Dharaneshwar Sasidharan. Compiles (compileFullDebugKotlin); on-device look validated via the 9.0.1 staging build.
Phases B–D of the long-term-memory upgrade: the coach can now draw on past conversations, and a cheap model keeps that memory current. Cross-conversation recall (B): - CoachConversation gains summary + summarizedCount (optional, back- compat decode). - search_past_conversations tool: deterministic on-device keyword search over past chats (title + summary + messages), returned as titled, dated snippets — so the model pulls history on demand without every prompt carrying it. - buildFullContext injects a short digest of recent conversation summaries, giving continuity even on providers without tool-calling. Both ride the existing dataConsent channel. Cheap-model memory maintenance (C): - AIProvider.cheapModel per provider (Haiku / gpt-4o-mini / flash-lite). - Engine gains memoryModel (defaults to cheapModel) + autoSummarize. - MemoryMaintainer: on leaving a conversation (threshold + debounced, consent-gated) or on demand, one cheap call distils the chat into a one-line summary + durable facts (deduped into CoachMemory). Runs in a background Task; best-effort and silent. Key access stays in AICoach via internal cheapComplete/applySummary helpers. Settings + surfacing (D): - CoachSettingsView: memory-model field, auto-summarise toggle, "Summarise this chat now"; memory card shows category icon + pinned marker and allows editing a fact in place. iOS (NOOPiOS) and macOS (Strand) both build. Simulator run pending (needs a provider key; CoreSimulator still wedged from earlier). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a second way to reach the Coach and lets the user pick which they want. - CoachEntry.swift (shared): CoachEntryMode (card / button / both, @AppStorage-backed) + CoachFloatingButton — a draggable circular FAB whose position persists (normalised to the screen, so it survives rotation/relaunch) and whose tap opens the chat. iOS-only button; the enum is shared because the Today views read it on both platforms. - RootTabView overlays the floating button (when the mode includes it) over every tab and presents the chat via the shared coachCover helper. - LiquidTodayView + TodayView show the Coach card only when the mode includes the card. - CoachSettingsView gains a segmented "Coach entry" picker (iOS). Default is Both, so nothing is lost for existing users. iOS (NOOPiOS) and macOS (Strand) both build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The floating Coach button could only be free-dragged, so it was easy to nudge out of place and there was no way to place it deliberately. - CoachButtonCorner: four corners plus `.custom` (dragged). Corners are resolved against the safe area with chrome clearances — bottom pins sit above the floating tab bar, top pins below the status bar and Today header — so a pinned button never covers the menu. - CoachFloatingButton honours the pinned corner, animates when it changes, and drops to `.custom` when dragged. Locking removes the drag gesture entirely; a tap still opens the chat. - Coach settings gain a four-corner picker (icon buttons, so a dragged button can honestly show "no corner selected") and a "Lock position" toggle, shown only when the entry mode includes the button. - migrateIfNeeded keeps an already-dragged spot as `.custom` instead of letting the new bottom-trailing default yank the button away. iOS (NOOPiOS) and macOS (Strand) both build; macOS is untouched (the button and its settings block are iOS-only). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The README still described the coach as it looked several features ago (11 tools, no memory system, no chat UI, no floating button) and never mentioned the 9.0.0 sync. - README: rewritten to be inviting and readable rather than a spec dump. Leads with why anyone would want this, keeps the "this is a fork, not a rename" framing and the upstream deference, and is honest about what leaves the phone. Now accurate: 14 tools, the memory system, the chat UI, the two entry points, v9.0.0 merged with zero conflicts. - docs/COACH.md (new): the technical deep-dive the README now links to — architecture, the request path, every tool, the memory model and its ranking/dedup, cheap-model maintenance, providers, the privacy model, entry points, and how to build/verify it. Deeper detail lives in the subpage so the README can stay a tour. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8 of 12 tasks
DX23876
pushed a commit
that referenced
this pull request
Jul 22, 2026
Fork CI: staging-release APK, versioned artifact names, R8-optimised release
DX23876
pushed a commit
that referenced
this pull request
Jul 22, 2026
…m time (#34) When a strap's clock/alarm register is corrupted it silently rejects SET_ALARM_TIME — the readback keeps reporting an old time — and the firmware alarm never fires. Today that mismatch is detectable only in the debug export, and only as a one-shot. Instrumentation (#2 — observability only, no BLE command change): - FrameRouter: on each GET_ALARM_TIME readback, count CONSECUTIVE rejections (reported != last sent). A matching readback resets the streak, so a transient (first read stale, then correct) never trips it; only a persistent refusal climbs. - recordAlarmArm: persist the strap-clock skew AT ARM. Skew ~0 while the strap still rejects ⇒ a corrupted alarm register, not a clock problem — pins whether a re-clock could ever help. - Debug export Alarm block surfaces both (clock-at-arm on Last arm; "N in a row" on Strap reports). - disableStrapAlarm clears the streak (nothing armed to refuse once disarmed). User warning (#4): - SmartAlarmView shows a card, ONLY when the alarm is on and the strap has refused >=2 arms in a row, telling the user the strap isn't accepting the alarm and to reset it via the official WHOOP app / keep a phone Clock alarm. Driven by @AppStorage so it appears/clears live. Not a fix for a corrupted strap (that's firmware-side, #34) — it turns a silent, export-only failure into a persistent signal and actionable on-device guidance.
DX23876
added a commit
that referenced
this pull request
Jul 22, 2026
Sync upstream v9.0.0 + coach long-term memory, chat redesign & floating button
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this PR does
Brings the fork up to upstream v9.0.0 and lands the AI-coach work built on top of it.
1. Upstream v9.0.0 —
git merge upstream/main, zero conflicts. Version 9.0.0 / build 200; thefork's free-Apple-ID signing config (
com.marvindoku.noopai, no App Group, widget/watch excluded)survived intact.
2. Coach: long-term memory
MemoryFactgains a category (goal/injury/preference/physiology/schedule) and importance(pinned/normal), with back-compatible decoding so existing facts survive.
turn's context instead of dumping all 40 into every request.
update_fact/forget_factlet the model correct itself instead of only appending contradictions.summary, asearch_past_conversationstool doesdeterministic on-device keyword search, and a digest of recent summaries rides the context (so it
works on providers without tool-calling).
MemoryMaintainerdistils a chat into a summary + durable facts via a cheap model(
AIProvider.cheapModel: Haiku / gpt-4o-mini / Flash-Lite), debounced, consent-gated, best-effort.3. Coach: UI
settings and conversation history split into their own screens.
pinned to any of four chrome-clear corners (bottom pins clear the tab bar, top pins clear the
header) or locked in place.
4. Docs — README rewritten (it still described an 11-tool, memory-less coach); new
docs/COACH.mdcarries the technical depth.Type of change
How it was tested
Not a BLE, protocol or analytics change — nothing on the strap path is touched. All fork commits
are confined to
Strand/AI/,Strand/Screens/,Strand/Liquid/LiquidTodayView.swift,StrandiOS/App/RootTabView.swiftand docs. (ThePackages/andandroid/diffs in this branch comefrom the upstream v9.0.0 merge, not from this work.)
Since this is app-target Swift, no default CI covers it (
swift-packages.ymlbuilds onlyPackages/**;app-build.ymlis disabled), so both app targets were built locally after everyphase:
macOS is built deliberately:
TodayView,LiquidTodayViewandCoachVieware shared, so they mustkeep compiling there even though this fork ships iOS only. The floating button and its settings block
are
#if os(iOS); the macOS build proves that separation holds.Still outstanding, stated plainly: the end-to-end run on device (pin each corner, lock/unlock,
cheap-model summarisation with a real API key) has not been done yet — CoreSimulator wedged on
this machine mid-session. It needs one
⌘Ron a real iPhone before it should be trusted.Checklist
Packages/changes in this workandroid/— n/a, noandroid/changes in this workStrandDesigntokens — no hardcoded colors, fonts, or spacing (StrandPalette/StrandFont/NoopCard; the only literals are.whiteicon fill and.black.opacity()shadows on the accent circle, matching the existing hero-card convention)docs/CONTRIBUTING.mdStrand.xcodeproj/) or any secrets/keystores (checked before push;*.xcstringsbuild churn reverted after every build)Related issues
None — fork-local work. Not intended for upstream: an iPhone-only, cloud-key AI coach is exactly what
upstream's Swift/Kotlin byte-identical-parity rule should keep out of the core project.