Skip to content

Sync upstream v9.0.0 + coach long-term memory, chat redesign & floating button - #2

Merged
DX23876 merged 10 commits into
mainfrom
sync-9.0.0-coach
Jul 15, 2026
Merged

DX23876 merged 10 commits into
mainfrom
sync-9.0.0-coach

Conversation

@DX23876

@DX23876 DX23876 commented Jul 15, 2026

Copy link
Copy Markdown
Owner

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; the
fork's free-Apple-ID signing config (com.marvindoku.noopai, no App Group, widget/watch excluded)
survived intact.

2. Coach: long-term memory

  • MemoryFact gains a category (goal/injury/preference/physiology/schedule) and importance
    (pinned/normal), with back-compatible decoding so existing facts survive.
  • Pinned facts ride the system prompt; facts relevant to the current question are folded into that
    turn's context instead of dumping all 40 into every request.
  • Near-duplicate detection supersedes rephrasings rather than stacking them; update_fact /
    forget_fact let the model correct itself instead of only appending contradictions.
  • Cross-conversation recall: conversations gain a summary, a search_past_conversations tool does
    deterministic on-device keyword search, and a digest of recent summaries rides the context (so it
    works on providers without tool-calling).
  • MemoryMaintainer distils 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

  • Full-screen messenger chat (docked composer, time separators, copy/regenerate, stop mid-stream);
    settings and conversation history split into their own screens.
  • Charts are larger, tappable to enlarge, and persist across relaunch.
  • Two entry points, user-selectable: a Today card and a draggable floating button that can be
    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.md carries the technical depth.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation
  • CI / tooling

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.swift and docs. (The Packages/ and android/ diffs in this branch come
from the upstream v9.0.0 merge, not from this work.)

Since this is app-target Swift, no default CI covers it (swift-packages.yml builds only
Packages/**; app-build.yml is disabled), so both app targets were built locally after every
phase:

xcodebuild -scheme NOOPiOS -destination 'platform=iOS Simulator,name=iPhone 17 Pro' build  → BUILD SUCCEEDED
xcodebuild -scheme Strand  -destination 'platform=macOS'                             build  → BUILD SUCCEEDED

macOS is built deliberately: TodayView, LiquidTodayView and CoachView are shared, so they must
keep 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 ⌘R on a real iPhone before it should be trusted.

Checklist

  • Swift package tests pass for any package I touched — n/a, no Packages/ changes in this work
  • Android unit tests pass if I touched android/ — n/a, no android/ changes in this work
  • No new build warnings introduced (verified: the iOS build emits no warnings for any changed file)
  • UI changes use only StrandDesign tokens — no hardcoded colors, fonts, or spacing (StrandPalette / StrandFont / NoopCard; the only literals are .white icon fill and .black.opacity() shadows on the accent circle, matching the existing hero-card convention)
  • No hardcoded hex frame bytes — n/a, no protocol changes
  • Follows the conventions in docs/CONTRIBUTING.md
  • I did not commit generated output (Strand.xcodeproj/) or any secrets/keystores (checked before push; *.xcstrings build 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.

Marvin Doku and others added 10 commits July 15, 2026 11:07
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>
@DX23876
DX23876 merged commit 0dcd108 into main Jul 15, 2026
3 of 4 checks passed
@DX23876
DX23876 deleted the sync-9.0.0-coach branch July 15, 2026 18:42
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
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.

2 participants