| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
feat: stage explicit reminders with date precision and creation evidence (#24485) * feat(notes): action-item owner, due and context on every surface Meeting-note action items now read the same on web, macOS and mobile: the item, then owner and due date on one line, then the context on its own line. An unknown owner or due date is left out instead of saying "Unknown". - web share page: drop "Due Unknown" and the unknown-owner avatar; the context moves to its own line. The agent Markdown export keeps its explicit Unknown markers. - macOS: ActionItem now decodes and caches owner_name, due_at and context; the row shows them, and Add to Tasks keeps the due date. - mobile: the summary shows the note's tasks again (read-only, after the sections, as on the Mac) with the same metadata. The reader's own items say "You". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * changelog(app): meeting-note tasks fragment Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(web): share HTTP test pins the omit-unknown action-item rule Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(desktop): avoid force unwraps in action-item metadata fixture Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(notes): action-item owner, due and context on every surface Meeting-note action items now read the same on web, macOS and mobile: the item, then owner and due date on one line, then the context on its own line. An unknown owner or due date is left out instead of saying "Unknown". - web share page: drop "Due Unknown" and the unknown-owner avatar; the context moves to its own line. The agent Markdown export keeps its explicit Unknown markers. - macOS: ActionItem now decodes and caches owner_name, due_at and context; the row shows them, and Add to Tasks keeps the due date. - mobile: the summary shows the note's tasks again (read-only, after the sections, as on the Mac) with the same metadata. The reader's own items say "You". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * changelog(app): meeting-note tasks fragment Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(web): share HTTP test pins the omit-unknown action-item rule Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(desktop): avoid force unwraps in action-item metadata fixture Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(notes): add action item due certainty and mobile actions * feat(desktop): reuse linked tasks when toggling action items * fix: keep action-item metadata across cache encoders; pin due/owner coverage Structured.toJson() wrote description-only strings, so the Flutter conversation cache dropped owner/due/context on every restart; it now emits the full action-item objects while the legacy String branch still decodes old caches. Structured.encode(to:) on macOS rebuilt items with dueAt: nil and no ownerName/context; it now maps all three fields, and a new Structured-level round-trip test pins it. The widget test now covers a due-dated row (keeping the no-Unknown assertions), the share HTTP test asserts the owner inside .sn-owner, and the meta separator rides at the owner's tail so a wrapped due date never starts a row with a stray dot. * test(app): update action task provider mock * changelog(notes): mention action-item cache metadata * docs(api): expose shared action-item due certainty * fix(api): regenerate clients for action item due certainty * fix(notes): completed creates, confirmed toggles, unique item identity Address review feedback on the action-item task flows: - TasksStore.createTask now inserts completed creates into completedTasks and passes 'completed' to the backend create, so a checked-off summary item no longer resurfaces as an open task after refresh. - TasksStore.toggleTask reports whether the mutation took effect; ConversationActionItemsSection only patches the conversation summary after a confirmed task mutation (or a completed create), so the two surfaces can no longer disagree. - Action-item session identity binds segments + description + due date on both macOS and mobile: segment IDs are evidence references, not unique identities, and one segment can carry several commitments. * fix(notes): persist promoted action-item task links across sessions The promoted task id and its idempotency key lived only in the page's in-memory session, so navigating away or restarting left the summary item unlinked and a later Add or check-off minted a duplicate task. - ConversationActionItemTaskSession now takes the conversation id and derives the create idempotency key from it plus the item's content identity, so a retry from any session lands on the same task (the backend returns the task an equal key already created). - The identity -> task id map is persisted per conversation (SharedPreferences, bounded to the 50 most recent conversations) and restored before the first mutation, so a reopened note addresses the existing task instead of creating another. - New tests cover cross-session restore and deterministic keys. * test(desktop): pin metadata retention on link and handle-shaped owner names * fix(app): drop a dead null check flagged by the analyzer ratchet segment.id is non-nullable, so 'segment.id != null' is always true and the unnecessary_null_comparison lint (baseline 0) failed the Mobile App Checks analyzer ratchet at 0ebf66b0. * fix(desktop): pass recurrenceRule before completed to match createActionItem The origin/main merge moved recurrenceRule ahead of completed in APIClient.createActionItem's parameter list; this call site kept the pre-merge order, so the desktop Swift compile failed with 'argument recurrenceRule must precede argument completed'. * fix(desktop): isolate ActionItemRowMetadataTests on the main actor ConversationSummaryTaskPromoter.linkedConversation, merged from origin/main (#19815), is a @MainActor static method; calling it from a nonisolated synchronous test failed the desktop Swift compile. Mark the test class @MainActor like ConversationSummaryTaskPromoterTests. * fix(desktop): assert toggleTask's effect result in owner boundary tests This PR made TasksStore.toggleTask return whether the completion mutation took effect; the three Task-wrapped call sites in TasksStoreOwnerBoundaryTests still discarded the result, which the Swift compiler rejects as an unused Bool expression. Assert the outcome at each site: false when the owner lease was revoked mid-flight, true when the toggle completed under the captured lease. * fix(notes): address action-item review round — row identity, rollbacks, canonical promotion Trusted-bot review round on the action-item metadata work: - Mobile: task-session identity now includes the row index, so identical extracted twins keep separate pending state, idempotency keys, task links and widget keys; a conversation PATCH failure compensates the task it just created (delete) or toggled (restore); the row is re-resolved against the provider's current items before patching so a reprocessed note cannot redirect the write onto another row. - macOS: completion-created tasks go through ConversationSummaryTaskPromoter (durable target_task_id link, backend-addressable id — no local-only ids), the conversation PATCH failure rolls the task back, and completion overrides key by row identity instead of the description. - Apple Reminders: a completed create propagates isCompleted/completionDate to the new EKReminder instead of appearing open in Reminders. - Web: the Markdown export marks tentative due dates, matching the page. - Backend: a linked row (target_task_id) mirrors exactly its task instead of every standalone task sharing the description; unlinked rows keep the legacy description mirror. - Tests: hermetic mirror regression suite; twins/rollback/reprocess mobile tests; tentative-export test; manifest registers the two new writer files; the owner-boundary assertion now matches toggleTask's stale-lease bail. * fix(notes): include action items in the Summary tab's search The section's rows now take part in the tab's search: the page counts matches in each row's description, verbatim owner name and context (the localized "You" label, email-shaped names and deleted rows are not searchable), passes the section its offset into the tab's results, and the rows highlight their matches and scroll to the tab's current result exactly like the note's own paragraphs. * test(backend): give the events-bounds action-item fake a target_task_id set_action_item_status now reads the row's target_task_id to scope the standalone-task mirror; the fake predating that field made the route's mirror raise inside its except and skip the mirror entirely, so the two reminder tests failed with mark_action_item_completed called 0 times. * Keep notes-model meeting identities provisional and persist inferred bindings * fix(notes): corroborate provisional identities and fence speaker bindings Require exact textual or attendee corroboration for names and emails; only screen/background or attendee corroborated names can become provisional people. Preserve owner speech, existing non-notes matches, and manual receipts, and remove rejected identifiers from the saved participant list. Correct the identity-write and late-refresh types without adding teaching or another admission scanner. Validation: backend/test.sh selected identity, rich-context, screen-evidence, admission, people and speaker suites: 308 passed. Hook-configured targeted Pyright: 0 errors. Line-Count-Exception: backend/utils/conversations/process_conversation.py | 3571 -> 3577 | Expose the existing notes generator as a public accessor for late refresh; this round adds only three lines at that seam. Line-Count-Exception: backend/utils/llm/conversation_processing.py | 1961 -> 1969 | Retain the notes evidence gate while removing the name-validation waiver; this round reduces the file from 1971 lines. * fix(notes): retain late-evidence timing without identity context Record the existing note-save timestamp and zero-frame count when a completed note has no identity context. This preserves the already approved one-time late evidence refresh after gating transcript-only identity work, without creating people, assigning speakers, or attaching _notes_identity on the flag-off path. Validation: backend/test.sh identity file: 57 passed, including a synthetic transaction test that the context-free note admits only one later marker. * chore(api): export provisional participant fields The notes identity model adds alias, speaker_bindings, and a screen source. The app-client OpenAPI document has to match or pre-push rejects the branch. * fix(api): keep participant source on the released enum Released app clients only decode roster or transcript. Screen and background evidence still creates the person; the wire value folds to transcript. * chore(app): mark provisional identity wire regen as internal The generated conversation wire gained alias and speaker binding fields. Released clients still decode participant source as roster or transcript. * test(web): polyfill localStorage for Node 25+ Node 25 omits the experimental localStorage global. The web app check calls clear() in setup, so the gate failed before any identity assertion. * test(notes): expect released participant source after screen fold Screen evidence still keeps the name and email. The stored source is transcript because released clients cannot decode a new enum value. * fix(notes): keep notes validation free of the notes-identity persistence graph meeting_notes_validation imported identifier_in_text from database.notes_identity, whose person_aliases dependency pulls Firestore module-level imports into every isolated-environment test that loads the validation module. That broke three stubbed-environment suites at collection/setup on CI shards (timezone, action-item date validation, conversation archive filter contract). identifier_in_text is a pure regex used only by this validation module, so keep a local copy; notes_identity retains its own for persistence-side use. The archive filter contract stub already faked FieldFilter for the same reason — teach it transactional, which person_aliases now needs via notes_identity. The TTS rate-limit and shared-action-items failures on these shards are main's known red suite, repaired separately in #20748. * feat(speakers): exclude media speakers with Not a Person * chore: add not-a-person speaker label changelog * feat(speakers): complete exclusion controller regression fixture * fix(speakers): reapply not-a-person exclusions across conversation refreshes * fix(speakers): record ignored voices for live not-a-person, forward the exclusion seam, and document the wire map - Bulk assign-bulk not_a_person now records the same ignored-voice marker (fenced by assignment generation) that the reject route and tag-prompt answers write, so live exclusions appear under Settings > Ignored Voices and can be restored from there. - listen_pusher_pipeline.mdx documents the speaker_assigned assign_type field and the bulk exclusion path per the backend wire-contract rule. - CaptureDependencies/CaptureProvider/composeCaptureProvider forward the excludeSpeaker seam so the production composition stays hermetically testable (C1 contract); composition tests pin the forwarding and the routed exclusion behavior. * fix(speakers): import voice_profiles the way the router's hermetic stubs resolve `import database.voice_profiles` fails in unit shards that stub the database package with an AutoMockModule (no submodule), while `from database import voice_profiles` resolves through the stub — the form every other router uses. * fix(speakers): make live not-a-person exclusions restorable and retire markers on positive assignment Address review threads on _assign_manual_speaker: - remove_ignored_voice now also drops generation-matched segment-scoped exclusion decisions, so restoring an ignored voice actually clears the manual decision that kept the speaker prompt-suppressed (thread 1). - A later person/user assignment clears the ignored-voice marker it supersedes via new clear_ignored_voices, so the voice leaves Settings > Ignored Voices and rejoins matching once reassigned (thread 2). - Router test drives the bulk path end-to-end; unit tests cover the segment-scoped restore and the superseding positive assignment. * test(speakers): keep marker retirement on the hermetic store in app-level fixtures Positive assignments now retire ignored-voice markers through voice_profiles.clear_ignored_voices; the match-scores and taught-persistence fixtures drove those routes without stubbing that module's Firestore client, so hermetic shards blocked on the GCP metadata server. Patch the client seam the same way test_speaker_rejections already does. * test(speakers): stub the voice-profiles client in owner-teaching route tests Speaker-scoped is_user assignments retire ignored-voice markers even when the assignment transaction is stubbed; keep that Firestore write hermetic. * fix(speakers): fence ignored-voice markers by generation and reconcile newer assignments Address review threads on the live not-a-person exclusion: - record_ignored_voice now checks, inside the marker transaction, whether a newer whole-voice positive decision already won the receipt; stale-generation markers are not recorded, so a reassign during an in-flight exclusion cannot re-ignore the voice (segment_only range entries do not unseat it). - Positive reassignment only retires markers for speakers whose rejection no longer wins the receipt, so a range-only reassignment keeps the remaining whole-voice exclusion restorable from Settings > Ignored Voices. - Marker record/clear failures after the assignment transaction committed no longer 500 the endpoint: the decision is durable, the response stays authoritative, and the failure is logged for reconciliation. - The capture controller retires a local exclusion when the incremental socket path delivers a persisted manual positive label for that speaker (the receipt projects exclusions as unlabeled, never manual); automatic labels still cannot unseat a fresh exclusion. * fix(speakers): fence marker retirement by generation, share it across writers, and surface degraded persistence Address review threads on the not-a-person exclusion loop: - clear_ignored_voices now takes assignment_generation and retains markers recorded by newer decisions, so a delayed clear from an older positive request can no longer remove a newer exclusion's marker (the voice would silently leave Settings > Ignored Voices). - Marker record/retire bookkeeping moves to utils/ignored_voice_markers.py shared by the live sheet router and tag-prompt card answers; a card Me/person answer now retires a prior live exclusion marker instead of leaving the positively assigned voice listed as ignored. - Both post-commit marker persistence handlers fail open with shared fallback telemetry (record_fallback, component=speaker_assignments) instead of log-only, per the backend fallback contract. - The conversations import in voice_profiles moves to module scope (database -> database edge verified cycle-safe). - The app's same-conversation refresh retires a local exclusion when the snapshot carries a newer manual positive label, mirroring the socket path. Verification: 384 backend tests across 12 affected suites; black clean; import-cycle smoke OK; 46 Dart tests across 4 suites; no desktop changelog entry required. * fix(speakers): keep lazy conversations imports, fence re-recorded markers, and gate range-exclusion markers - Restore function-local database.conversations imports in voice_profiles: the module-level import broke test collection via the utils.speaker_identification import order (Backend unit shard 1/6). - record_ignored_voice now retains an existing marker whose assignment generation is newer than the write being recorded, so a delayed older exclusion cannot strand a newer exclusion's restore linkage (Codex P2). - record_exclusion_markers records a whole-voice marker only when the committed receipt has a winning whole-voice rejection at this write's generation; range (segment_only) exclusions no longer record markers that suppress segments outside the range (Codex P2). - Live capture keeps a WS manual-positive conflict excluded and reconciles via one coalesced authoritative in-progress snapshot instead of inferring ordering from label provenance, which the socket cannot order (Codex P2). - Tests: delayed-older-exclusion regression, range-exclusion no-marker, import-order probe, and reconcile-semantics rewrite of the WS retire test. * fix(speakers): fence exclusion retirement by request epoch and bind reconcile to capture session Review follow-up for the live 'Not a person' exclusion (Codex round): - Exclusions now record a monotonic decision epoch at acknowledgement and REST refreshes record the epoch at request start, so a same-conversation refresh whose request predates the exclusion acknowledgement can never retire it: only a refresh that started after the ack — and therefore saw the committed exclusion server-side — may retire it via a persisted manual positive. - The 300 ms exclusion reconcile is now bound to the capture lifetime and the active capture session: a stop/replace/dispose during the delay or the load cancels the install instead of mutating a later session. - ListenReceiver._handle_speaker_assigned clears the speaker-wide inference map (speaker_to_person, voice/segment identity status) for winning not_a_person rejections, so newly arriving segments of the rejected speaker are not relabeled from the stale map. Regression tests: stale-request fence, session-cancelled reconcile, and socket rejection clearing the speaker map. * fix(speakers): fence reconcile by per-recording token, queue mid-refresh conflicts, retire full matcher state and superseded markers Review follow-up round 2 (Codex): - The exclusion reconcile is now fenced by the CaptureSessionOwner token (per-recording identity) instead of activeCaptureSessionId, which the controller docs define as unchanged across sequential phone-mic stop/next-start; a recording roll mid-delay or mid-load cancels the install. A conflict arriving while a reconcile is in flight now queues one follow-up refresh instead of being discarded. - ListenReceiver._handle_speaker_assigned delegates the rejected voice's cleanup to the new SpeakerMatcher.retire_excluded_voice seam so the full matcher state retires (mapping origin, candidates, suggestions), letting a restored ignored voice match automatically again. - retire_exclusion_markers now distinguishes rejection kinds: only a still-winning not_a_person keeps its ignored-voice marker, so a newer not_me / not_person / unnamed card answer supersedes an earlier exclusion and clears the stale marker; the tag-prompt whole-voice branch calls the shared retirement for every answer. Regression tests: per-recording fence across a phone-mic restart, mid-refresh conflict queueing, full matcher-state retirement, and kind-distinguishing marker retirement. * test: warm-up phase instrumentation for perf harness * perf(app): offload cold recording scans from UI isolate * perf: mount Tasks tab on demand instead of 350ms prewarm * perf: decode app catalog once; stop blocking hydration on Intercom * perf: quiet dependency wiring notifications and shortcut publication during startup * fix: fence stale task loads and preserve optimistic creates across the demand load Address review races in the demand-mounted Tasks workstream: - fetchActionItems: guard the typed projection, loading-flag retirement and final notify by session generation, so a fetch that started before sign-out cannot project error/locked state onto the next account or hide its shimmer. - createActionItem/_applyFetchedActionItems/_fetchHomeWidgetPreview: preserve optimistic creates across wholesale list replacement. Temp rows and rows that settled while a fetch was in flight survive exactly the replacements of fetches that predate them; later fetches stay authoritative. Temp rows are excluded from the Siri/Spotlight mirror while their final server identity is unknown. - UsageProvider.fetchSubscription: single-flight onto the real in-flight request so the unlimited deep link's _subscriptionReady await cannot pass while the fetch onboarding started is still unresolved (fail-open paywall flash). clearUserData detaches the in-flight future for the next account. Verified: dart analyze clean; new demand-load race tests (3), subscription single-flight tests (3), plus action_items/usage/siri suites all pass. * fix: address round-2 review — dedupe preserved creates, pagination offset, bounded waits - createActionItem: when a concurrent list response already observed the committed server row, replacing the temp row could leave a duplicate; the confirmed row now replaces both the temp row and any earlier copy. - loadMoreActionItems: offset counts server-backed rows only; preserved optimistic/shadowed creates stay out of the offset until a server page has contained them, so pagination never skips a server task. - Home widget freshness: admit the preview refresh after a bounded deadline even while repeating animations keep the scheduler busy; the timer no longer spins 4x/second indefinitely. - Quick actions: a failed shortcut publication now schedules a bounded delayed retry (still clearing the dedupe cache for natural republish), canceled by any successful publish or reset. - Conversation detail projection: replace full ServerConversation/App toJson encoding per notification with a cheap fingerprint of rendered fields (identity covers final heavy lists; structured title/overview and mutable catalog fields are included explicitly). Verified: dart analyze clean; demand-load race suite (now covering dedupe and pagination offset), quiet detail wiring, quick actions, and related action-items/usage/conversation-detail suites: 57/57 pass. * fix: address round-3 review — retire server-observed creates, runnable shortcut retry, screen-vs-preview readiness, app metadata fingerprint - _withPendingCreates retired _preservedCreateIds only when a create also needed preserving; the common no-fetch-in-flight settle path never retired the id, so a confirmed create stayed excluded from the pagination offset forever and loadMore could stall on repeated already-visible pages. - The scheduled shortcut retry guarded against the dedupe cache the failure path had just cleared, so it never ran. Failed projections are now tracked separately; a successful retry restores the dedupe cache. - The Tasks first-load shimmer consulted hasLoaded, which the Home widget preview also satisfies; a completed zero-task preview could flash the empty state on first visit. The screen now tracks its own readiness. - The detail change fingerprint omitted app description/image, which the attribution row renders; catalog-only metadata edits were suppressed. * fix: ignore superseded shortcut publication failures instead of retrying them A late failure from an older setShortcutItems call unconditionally re-armed its obsolete projection for retry even after a newer projection had already published, so the 5s retry could overwrite the current iOS shortcuts with stale connect/mute actions. Guard the failure path with the same ownership check the retry path already uses: if this call no longer owns _publishedShortcuts, the failure is superseded and is ignored. * fix: keep task pagination and staged deletes correct across demand-load races - loadMoreActionItems retires preserved creates observed in the page's server response, mirroring _withPendingCreates: a create ordered beyond the initial page no longer stays excluded from the offset forever. - A Home preview that loses the navigation race prunes its fetch ordinal from shadowed creates in its finally block; previously an aborted preview never released the shadow, so a server-confirmed row kept lagging the pagination offset. - A staged delete of a temp row transfers to the confirmed server row when the create settles instead of reinserting it; undo/commit resolve the temp-id alias, and a failed create dissolves the staged delete. Each path has a regression test in the demand-load races suite. * fix: generation-fence stale load-more cleanup and skip preserving fetched creates - loadMoreActionItems only resets _isFetching and notifies for its owning session generation, so a sign-out-mid-flight load-more cannot clear the successor account's fetch state or admit overlapping pagination. - _settlePendingCreate no longer marks a create as offset-excluded when a concurrent fetch already placed its confirmed row in the visible list: a server page has contained it, so it is server-backed immediately instead of undercounting the pagination offset until some later page happens to retire it. Both paths have regression tests in the demand-load races suite. * fix: release fetch shadow ordinals on aborted refreshes and re-arm Undo preservation Codex review round 4 on the demand-load races: - fetchActionItems aborted (typed outage, sign-out discard, or throw) without running its _withPendingCreates replacement, but its finally only removed the ordinal from _wholesaleFetchesInFlight. The shadow kept excluding the server-confirmed row from _serverBackedRowCount forever, so pagination repeatedly re-requested overlapping pages. Prune the ordinal via _dropFetchShadowOrdinal exactly like the Home preview abort path. - _settlePendingCreate skipped registering fetch threats when the settled row was hidden by a staged delete. Undo restores the row, but the older in-flight response's replacement then dropped it from the list until the next refresh. Register the threats regardless of visibility and enforce deletion intent at replacement time instead: _withPendingCreates now skips preserving rows whose ids sit in _pendingDeletionIds, so committed/outright deletes are never resurrected while Undo re-arms preservation. Regression tests: aborted refresh releases its shadow; Undo before the fetch returns keeps the confirmed row; a committed staged delete stays deleted across the replacement. * fix: hide transferred staged-delete rows, clear stale wholesale ordinals, fold speaker-identity revision - Thread 1 (action_items): a concurrent fetch that observed the confirmed server row mid-toast admitted it into the visible lists because the tombstone still held only the temp id; the transfer branch now removes the observed row everywhere a fresh stageDeleteActionItem would, so the task cannot reappear during the Undo window or outlive a successful commit. - Thread 2 (action_items): clearUserData now clears _wholesaleFetchesInFlight so a stuck signed-out generation's ordinals cannot shadow a successor account's confirmed creates out of the pagination offset. - Thread 3 (conversation detail): applySpeakerPromptIdentities mutates transcript segments in place with no container-identity change; the conversation provider now bumps a speakerIdentityRevision and the detail fingerprint folds it in so owner answers re-render Speaker N rows. Regression tests for all three paths. * fix: preserve settled edits across wholesale list replacements A settled complete/description/due-date mutation could be silently reverted when a wholesale list fetch that started earlier (the Home preview or the first Tasks demand load) applied its stale replacement: the response predates the mutation and restores the pre-edit server row, so a completed task showed incomplete until another refresh. Mirror the fetch-shadowed-create machinery for edits: record the server-confirmed row with the set of wholesale fetches still in flight when the mutation settles, and re-apply it over exactly those replacements. Fetches that started after the settle see the edit server-side and stay authoritative; aborted fetches and sign-out release their threat like create shadows. * fix: close round-4 optimistic-UI races in the Tasks provider - updateActionItemState now splices the settled server row back into the visible list, so a wholesale fetch that applies mid-mutation cannot leave the pre-completion row on screen (Codex PRRT NnpK). - Drag-reorder and indent changes are tracked as uncommitted layout and re-applied over wholesale replacements until their debounced batch write settles, with the same in-flight-ordinal threat semantics as edit shadows; failures retire so a later refresh restores server truth (Codex PRRT NnpR). - Bulk delete captures optimistic temp rows instead of sending temp ids to the bulk endpoint: the row hides behind a tombstone and the deletion completes with a follow-up single delete of the confirmed server id once the create settles; failed creates dissolve their capture and failed bulk requests roll back only the real rows (Codex PRRT Nnpu). - ensureHomeWidgetPreviewLoaded refreshes through refreshActionItems once the Tasks screen has loaded, restoring resume-time widget freshness behind the shared 30s cooldown (Codex PRRT sEMXt). * fix(speakers): fence markers against newer rejection kinds, clear rejected segment mappings, retire markers on not_me, harden reconcile fetch and wire Settings restoration - voice_profiles: the stale-generation fence now treats any newer whole-voice decision as superseding — not only positives — so a delayed not_a_person write cannot recreate a marker after a newer not_me/not_person answer. - receiver: a winning whole-voice rejection now also drops per-segment person mappings for every segment of the rejected speaker; a segment absent from the socket payload kept its stale mapping otherwise, and process_speaker_assigned_segments prefers segment_assignments. - conversations router: every non-exclusion assignment reconciles ignored-voice markers (assign_type=is_user&value=false commits a whole-voice not_me that previously skipped retirement via the elif person/user guard). - capture controller: the reconcile refresh uses getConversationsResult and skips installation when the fetch failed, instead of converting a transport failure into a destructive null install mid-capture. - capture/settings: restoring a voice from Settings > Ignored Voices now forwards to the live capture (onIgnoredVoiceRestored) — restoration writes no positive label, so no snapshot could ever satisfy the manual-positive retirement condition and the local exclusion would outlive the restore. * fix: keep non-layout mutation echoes from flattening task hierarchy The settled-row splice and fetch-shadow introduced in round 4 could replace the visible row with a mutation echo that omits sort_order and indent_level (the wire schema defaults both to 0), flattening an indented task on complete/description/due-date settle — exactly what the upstream gesture contract (hub #4) forbids. Non-layout mutation echoes now merge the visible row's layout before splicing or shadow registration, so layout truth keeps flowing only from list fetches and layout mutations. - Partial bulk-delete rollback now filters captured temp rows like the null-response path: the bulk request never carried their ids, so a partial response cannot report them deleted and must not resurrect them (Codex PRRT sTqiZ). - Layout batch settlement is revision-aware: each debounced batch snapshots the per-id revisions it submitted, so an older batch response can no longer retire a newer edit the user staged while the request was in flight; the newer edit keeps protecting the visible rows until its own batch settles (Codex PRRT sTqip). - Regression tests for all three races in the demand-load race suite. * fix(speakers): preserve whole-voice exclusions across range-only labels A manual positive snapshot only retires the local exclusion when it carries whole-voice authority: the receipt's speaker entry projects onto every segment of the speaker. A range-only decision (segment_only / time_range) deliberately keeps the whole-voice not_a_person rejection authoritative outside the range — its in-range manual label must not drop the speaker-wide local exclusion while the server still considers the voice excluded. Retire per speaker only when the entire cluster in the snapshot carries the manual positive (and the refresh started after the exclusion's acknowledgement); otherwise keep blanking labels and suppressing suggestions. Update the reconcile tests to model whole-voice snapshots and add a range-only regression. * fix: close round-5 Tasks provider races (completion splice, atomic layout batch, shimmer join) - updateActionItemState now splices the confirmed server row when a wholesale fetch already applied mid-mutation, so a completion that settles after an intervening list response still lands (PRRT sNnpK). - Sort and indent share one debounce and one batch request: a drag that changes both commits atomically, and the shared layout revision can no longer be settled by whichever single-field flush finished first (PRRT sTqip, sZ2hW). - The Tasks screen joining a non-screen first load upgrades the presentation to the shimmer instead of flashing the false empty state (PRRT sZ2hf). - Regression tests for the atomic combined batch and the older-batch settle guard in action_items_demand_load_races_test.dart. * fix(speakers): reconcile exclusions against receipt authority via excluded_speakers A selected-segment superseder (assign-bulk with explicit segment ids) retires the whole-voice not_a_person exclusion in the manual receipt, but only its segments project a manual label — a partial projection identical to a range-only (segment_only) label, which deliberately keeps the exclusion. The controller's whole-cluster-positive heuristic therefore re-blanked valid assignments and kept suppressing suggestions after the server had already retired the exclusion. Project the receipt's still-winning whole-voice not_a_person voices as 'excluded_speakers' on the Conversation payload (shared pure walker in models/speaker_exclusion.py; the receipt itself never serializes), and reconcile against it (epoch-fenced) with the projection heuristic kept only as the fallback for older servers. Strip the projection from the integration redactor, pin the field classification, regenerate the app-client spec and first-party clients, and document the wire map. * fix: fence joined shimmer cleanup and superseded completion responses - Generation-fence the joined-load shimmer cleanup so a stale completion from the signed-out account cannot clear the successor account's shimmer (Codex PRRT_kwDOLkKqys6sf5P1). - Track a per-item completion mutation revision: only the latest done/ undone attempt may settle its row, register its fetch shadow, revert the visible state on failure, or project to Siri/Apple Reminders, so out-of-order PATCH responses cannot resurrect superseded completion state (Codex PRRT_kwDOLkKqys6sf5P9). - Regression tests in the demand-load race suite (round 6). * fix: keep Home widget freshness unfiltered while the Tasks screen filters Codex review (round 7): after the Tasks screen loaded, resume-time widget freshness delegated to refreshActionItems(), whose fetch carries the screen's _startDate/_endDate and replaces _actionItems with only matching rows. HomeWidgetsPublisher derives Up Next and the open-in-all count from that list, so the iOS widget dropped tasks outside the screen filter until the filter cleared. - ensureHomeWidgetPreviewLoaded: with an active date-range filter, refresh a widget-only unfiltered snapshot instead (single-flight, shared 30s cooldown, generation-fenced, never replaces _actionItems); without a filter the authoritative screen refresh is already unfiltered and shared. - incompleteItems/todayPreviewTasks read the snapshot only while a filter is active, so the screen keeps its filtered list. - setDateRangeFilter/clearDateRangeFilter kick/invalidate the snapshot at the source-flip transition; clearUserData resets snapshot state. - Regression tests: round-7 group in action_items_demand_load_races_test. * fix(speakers): authoritative empty exclusion projection, shared reject-route markers, stacked-generation restore, reconcile release window - excluded_speakers now projects [] whenever a manual receipt exists: a selected-segment superseder or cross-client restoration must read as an authoritative empty set, never null (null = old server heuristic that can never retire the exclusion). Doc updated to pin the contract. - The reject endpoint records/retires ignored-voice markers through the shared helpers (record_exclusion_markers / retire_exclusion_markers): not_me / not_person answers now retire a superseded exclusion marker, and a post-commit marker failure fails open with shared telemetry like every other writer. - Settings restore drops every not_a_person decision no newer than the marker's generation (stacked whole-voice + selected-segment exclusions); explicit not_me/not_person answers and genuinely newer decisions survive. - The exclusion reconcile completion frees the in-flight slot and schedules any queued follow-up synchronously before its first await, closing the release-window race that could strand a conflict's queued refresh. Verified: 139 backend tests (6 focused suites) and 14 capture tests pass; black and dart format clean. * fix(app): fix the trial paywall and Pocket onboarding arms in code Both experiment flags already serve one arm at 100%. Reading them is what blocks the release that stops clients loading flags from PostHog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(flags): serve client flags only from Omi Disable PostHog flag preloads, explicit and implicit requests while preserving event capture and mobile replay. Keep missing-key defaults and account-bound snapshots; prepare the pending chat and replay store migration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(flags): distinguish fixed client arms from legacy experiments Preserve the Coordinator-owned #24370 fixed trial and Pocket arms and make their exposure-only role explicit for new builds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(flags): keep unused SDK survey initialization disabled The app has no PosthogObserver survey surface. Disable SDK surveys in Dart and the first native setup so the transport bootstrap cannot install native survey UI in place of Flutter delegation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(flags): verify both SDK flag endpoint path forms Normalize trailing slashes in the request-path recorder; pinned SDK capture uses /batch and flag requests use /flags. Exercise both forms of flags and decide locally. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(flags): retain the registered local-capability kill key Use the established killSwitchFlag constant convention so the registry checker tracks this Omi-only read after the SDK bypass is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(flags): update the screen-task flow for Omi-only authority Replace the retired PostHog fallback expectation and add a hermetic SDK request-path step covering the new transport while verifying analytics batch delivery. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(flags): preserve iOS SDK attribution at the first setup PostHog snapshots its SDK context during setup. Seed Flutter attribution before native initialization and pin the declared version to pubspec.lock with a regression test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(app): block unsafe pendant firmware updates * fix(app): keep firmware battery readings current * fix(app): require 30% battery for pendant firmware updates * feat: stage explicit reminders with date precision and creation evidence * harden: retain each reminder source revision across retries * harden: align reminder types across generated clients * harden: clear speech-only date basis after manual edits * harden: preserve task wording and explicit null date compatibility * harden: validate reminder due through the shared storage boundary * chore: record internal reminder wire compatibility update * harden: narrow the validated reminder due payload * harden: keep unresolved reminder date gestures all day * harden: preserve reminder provenance and validate additive contracts * test: keep reminder policies importable in processing fixtures * chore: avoid competing feature flag registry insertions * test: make backend CI fixtures deterministic Keep the synthetic slow drain row first and release every wait during cleanup. Give independent jobs separate owners and preserve void transaction results. Settle recovery discovery pages and assert exact probe counts, following 9e166a6407eb from PR #24503. Stub unrelated episode I/O in owner-recognition witnesses. No production code, timing budgets, markers, or durable-outcome assertions change. * test: serialize ownerless record drain fixtures Paging and privacy fixtures share the missing-owner key, so drain them with one worker instead of racing the per-owner admission guard. Parallel admission tests retain their explicit worker counts. Keep assertions, CI selection and timing budgets unchanged. * test: reconcile drain fixtures already fixed upstream Let upstream #24503 supply the shared drain fixture setup so GitHub can form the PR test revision. Keep the serial paging default and bounded cleanup. All test functions and assertions remain unchanged. * ci(mobile): re-time Dart Analyze & Tests at 40 minutes The analyzer ratchet plus the full flutter test suite now needs 27-30 minutes of wall time on ubuntu-latest (the suite alone is ~26); the 30-minute job timeout cancelled a run whose every step had already succeeded — the suite printed "All tests passed" and the cancellation landed during cache post-steps (PR #24370, 30:07 elapsed). Recent green runs of the same job sit at 26:54-29:21, so the ceiling, not the suite, was the flake. The same suite under far timezones (dart-tests-kiritimati) already runs with 60 minutes. * ci: raise Dart Analyze & Tests ceiling above the measured cold path Main's healthy runs measured 27-29.6 min (run 41736391155: 29:36 for 8207 tests) against the 30-min cap; run 41693050609 was cancelled at the cap with all 7842 executed tests passing and ~360 unexecuted, failing Mobile Release Eligibility downstream. Same cold-path ceiling problem test_mobile_ci_infra_guards.py documents for ios-compile-check; 40 keeps ~10 min headroom and the CI budget counts p50, not the ceiling. * fix(flags): require the public chat switch and unblock the native test build Read the public mobile-chat-apps switch before the private all-channels key so the allowlist alone can never turn the surface on, and update the gate and provider key-sequence assertions to the new read order. Drop the two redundant URLProtocol @unchecked Sendable conformances that the pinned toolchain rejects as already unavailable, and satisfy the blocker capture expectation only when the decoded batch carries the requested event. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci(mobile): reword the Dart re-time comment off the kiritimati job id test_mobile_kiritimati_dart_suite_is_a_parallel_second_pass asserts the analyze-and-test job body never mentions dart-tests-kiritimati (a blunt substring guard against a needs: edge lengthening the critical path), and the timeout rationale comment tripped it; name the far-timezone second pass by role instead. * Restore main's safe_mode.c blob (24434's delta was clang-format dialect only) * Regenerate app-client openapi, wire schemas and TS/Swift clients over the folded tree * Fold fixes: port 21380 exclusion reconcile into snapshot path, quiet first-time external-actions binding, drop tests for superseded 21364 provider machinery * Fix updateExternalActions ordering so mutated adapters propagate * Fold adaptations: fixture stubs for main's newer module graph, provisional-identity test imports, drop old-pipeline notes tests, adopt no-placeholder share-note meta * Drop next-autogenerated web/frontend/AGENTS.md; the agents-md-lean ratchet has no budget for it * Restore the folded mobile-chat-apps-all-channels registry row, drop the retired rich-context env gate, and re-render the flag registry doc * Allow-list updateExternalActions proxy notify: quiet-ness is the mutable projection comparison, not adapter identity * Place the proxy-hook-notify allow comment on the line directly above the declaration * Drop unused datetime import and orphaned transcript-name helper from the 20766 union * Drop regenerated web/frontend/AGENTS.md again * Gate fixes from the latest main re-merge: move notes-identity attach into the extracted finisher via inputs, restore main's screen-frame evidence tests, extend mirror fixture stubs for the newer router import graph * Mirror the reminder sync from the in-list row (main's no-refetch contract) with the survivor's status/deleted fields * Keep main's post-write reminder-sync refetch; adapt the folded mirror test's fetch assertion * Adapt the folded mirror test's fetch-count assertions to main's reminder-sync refetch * Pass the folded row identity into taskState after the main re-merge brought the identity-based overrides * CI fixes: analyzer dead code from the saveString union, citation-contract participant fields, people-generation re-audit for the notes-identity writer * CI fixes for the folded survivor: participant alias/bindings in the citation contract, people-generation re-audit for the notes-identity writer, lifecycle payload-site declarations (outbox note writer, evidence refresh, diarization scorecards), linear share-email shape check, analyzer dead code from the saveString union * Keep main's summary recovery affordance beside the folded action-items section; un-orphan its widget * Restore the fold-lost contracts: screen_evidence as a valid decision trigger, the detail page's resume re-read lifecycle hook, DeviceProvider setProviders notify-on-change, and main's adapter-identity updateExternalActions (drop the projection layer that suppressed real installs and read strict mocks); remove the widget-snapshot fetch path whose extra page fetches broke the task ownership counts * Give DeviceProvider a silent reference-binding path: setProviders keeps main's notify-on-change for real installs and capture swaps, bindProviders wires action targets by reference without device-state notifications (quick actions setup) * Reconcile the flag budget count field to its 244 declared names (merge took the count from one side and the names from the other) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: David Zhang <9387252+Git-on-my-level@users.noreply.github.com> | 1 天前 |