Skip to content

Commit bfd603e

Browse files
authored
Merge pull request #115 from logseq/codex/timeline-pull-sync-20261024
Add Timeline pull-to-sync with shared sync activity
2 parents d37ee9c + f234dca commit bfd603e

15 files changed

Lines changed: 381 additions & 32 deletions

‎app/application.ml‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2501,10 +2501,10 @@ let composer_content
25012501
[ V.of_lui
25022502
(Lui_elements.button_group ~gap:0 ~height:44 ~background:"glass"
25032503
~corner_radius:999
2504-
([ action ~label:"文件" ~icon:"doc" Journal_asset_import.Files
2505-
; action ~label:"照片" ~icon:"photo" Journal_asset_import.Photos ]
2504+
([ action ~label:"Files" ~icon:"doc" Journal_asset_import.Files
2505+
; action ~label:"Photos" ~icon:"photo" Journal_asset_import.Photos ]
25062506
@ if assets.camera then
2507-
[ action ~label:"相机" ~icon:"camera" Journal_asset_import.Camera ]
2507+
[ action ~label:"Camera" ~icon:"camera" Journal_asset_import.Camera ]
25082508
else []))
25092509
|> V.with_test_id (Ui.Test_id.string (scope ^ "-attach"))
25102510
]
@@ -2718,6 +2718,7 @@ let timeline_page
27182718
~status_name
27192719
?current_day
27202720
?store:timeline_store
2721+
?on_sync:(if interaction_enabled then Some (bind_action on_account_action Begin_online_recovery) else None)
27212722
~on_region
27222723
~render_source
27232724
~render_title

‎app/journal_header.ml‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,11 @@ type presentation =
190190
; capture_enabled : bool
191191
}
192192

193+
let sync_active = function
194+
| Some (Graph_service.Connecting | Pulling | Submitting) -> true
195+
| Some (Offline | Current | Paused | Failed) | None -> false
196+
;;
197+
193198
let view_impl
194199
~presentation_signal
195200
~key
@@ -429,23 +434,21 @@ let view_impl
429434
~key
430435
~props_for:(fun presentation ->
431436
props ~mode:"page" ?title:(match context with Journals -> None | Favorites -> Some "Favorites")
432-
~connecting:(presentation.sync_phase = Some Graph_service.Connecting)
437+
~connecting:(sync_active presentation.sync_phase)
433438
~controls:(controls presentation) ()) (* Keep absent slots mounted so the native child indexes stay stable. *)
434439
~children:
435440
[ V.Body.Private.to_widget body
436441
; cluster
437442
; dynamic
438443
~equal:(fun left right ->
439-
left.sync_phase
440-
= Some Graph_service.Connecting
441-
= (right.sync_phase = Some Graph_service.Connecting))
444+
sync_active left.sync_phase = sync_active right.sync_phase)
442445
(fun presentation ->
443446
V.column
444-
[ (if presentation.sync_phase = Some Graph_service.Connecting
447+
[ (if sync_active presentation.sync_phase
445448
then
446449
V.progress ~style:Circular ()
447450
|> V.semantics
448-
~properties:(Ui.Semantics.create ~label:"Connecting" ())
451+
~properties:(Ui.Semantics.create ~label:"Syncing" ())
449452
|> test_id "journal-header-sync-progress"
450453
else V.empty ())
451454
])

‎app/journal_native_collection.ml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ let view
2020
~rows
2121
~scroll_target
2222
?scroll_target_signal
23+
?on_sync
2324
~on_scroll_completed
2425
~actions_enabled
2526
~on_visible_range
@@ -161,6 +162,7 @@ let view
161162
?scroll_request_signal
162163
~on_scroll_completed
163164
~on_visible_range
165+
?on_sync
164166
sections in
165167
(match scroll_target_signal with
166168
| None -> list ()

‎app/journal_native_collection.mli‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ val view
1414
-> rows:row list
1515
-> scroll_target:(int64 * string * string) option
1616
-> ?scroll_target_signal:(int64 * string * string) option Signal.signal
17+
-> ?on_sync:Ui.Event.Handler.t
1718
-> on_scroll_completed:Ui.Event.Handler.t
1819
-> actions_enabled:bool
1920
-> on_visible_range:Ui.Event.Handler.t

‎app/journal_timeline.ml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ let view
208208
?status_name
209209
?current_day
210210
?store
211+
?on_sync
211212
?(on_region = fun _ -> ())
212213
~render_source
213214
?render_title
@@ -366,6 +367,7 @@ let view
366367
~rows
367368
~scroll_target:target
368369
?scroll_target_signal
370+
?on_sync
369371
~on_scroll_completed
370372
~actions_enabled:(delete_enabled && actions_enabled)
371373
~on_visible_range

‎app/journal_timeline.mli‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ val view
1818
: ?status_name:(Journal_model.task_state -> string)
1919
-> ?current_day:int
2020
-> ?store:Store.t
21+
-> ?on_sync:Ui.Event.Handler.t
2122
-> ?on_region:(string -> unit)
2223
-> render_source:(string -> string)
2324
-> ?render_title:(string -> Journal_model.Title.t)

‎app/journal_view.ml‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1615,7 +1615,8 @@ module View = struct
16151615
[@@deriving yojson]
16161616
type props =
16171617
{ style : string; sections : section list
1618-
; track_visible_range : bool; track_scroll_completion : bool }
1618+
; track_visible_range : bool; track_scroll_completion : bool
1619+
; sync_enabled : bool }
16191620
[@@deriving yojson]
16201621
type row_event = { row : string; key : string } [@@deriving yojson]
16211622
type event =
@@ -1633,7 +1634,7 @@ module View = struct
16331634
;;
16341635

16351636
(* Content indexes retain the native child ownership order. *)
1636-
let build sections ~style ~track_visible ~track_scroll =
1637+
let build sections ~style ~track_visible ~track_scroll ~sync_enabled =
16371638
let contents = ref Rrbvec.empty in
16381639
let next_content_index = ref 0 in
16391640
let push element =
@@ -1660,7 +1661,7 @@ module View = struct
16601661
in
16611662
let wire : Json.props =
16621663
{ style = style_name style; sections = List.map section_wire sections
1663-
; track_visible_range = track_visible; track_scroll_completion = track_scroll }
1664+
; track_visible_range = track_visible; track_scroll_completion = track_scroll; sync_enabled }
16641665
in
16651666
Json.props_to_yojson wire |> Yojson.Safe.to_string, Rrbvec.to_list !contents
16661667
;;
@@ -1697,6 +1698,7 @@ module View = struct
16971698
?scroll_request_signal
16981699
?on_scroll_completed
16991700
?on_visible_range
1701+
?on_sync
17001702
sections
17011703
=
17021704
let payload, contents =
@@ -1705,6 +1707,7 @@ module View = struct
17051707
~style
17061708
~track_visible:(Option.is_some on_visible_range)
17071709
~track_scroll:(Option.is_some on_scroll_completed)
1710+
~sync_enabled:(Option.is_some on_sync)
17081711
in
17091712
(* Expansion state arrives as {"type":"expanded","key":..,"expanded":bool};
17101713
the owning row's handler receives Bool like the old disclosure callback. *)
@@ -1746,6 +1749,7 @@ module View = struct
17461749
| Error source -> report_event_error "decodeListEvent" source
17471750
| Ok wire ->
17481751
match wire.event_type with
1752+
| "sync" -> Option.iter (fun handler -> invoke handler Event.Payload.Unit) on_sync
17491753
| "visible_range" ->
17501754
(match wire.first, wire.last with
17511755
| Some first_index, Some last_exclusive

‎app/journal_view.mli‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,7 @@ module View : sig
705705
-> ?scroll_request_signal:scroll_request option Signal.signal
706706
-> ?on_scroll_completed:Event.handler
707707
-> ?on_visible_range:Event.handler
708+
-> ?on_sync:Event.handler
708709
-> section list
709710
-> t
710711
end

‎apple/Info.plist‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
<key>NSCameraUsageDescription</key>
3030
<string>Take a photo to attach to a journal entry.</string>
3131
<key>NSPhotoLibraryAddUsageDescription</key>
32-
<string>将你选择的图片保存到相册。Save the image you select to your photo library.</string>
32+
<string>Save the image you select to your photo library.</string>
3333
<key>UILaunchScreen</key>
3434
<dict/>
3535
<key>UISupportedInterfaceOrientations</key>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Timeline pull sync through the shared owner
2+
3+
## Problem
4+
5+
Timeline has no deliberate pull-at-top entry into the existing sync owner.
6+
Online recovery currently inspects local sync state without guaranteeing a fresh
7+
pull, and busy force intent is not retained. The existing header spinner only
8+
covers Connecting.
9+
10+
## Proposal
11+
12+
The user approved design v3 and implementation plus isolated A135 acceptance on
13+
2026-10-24. The later English-default requirement sets the top drag hints to
14+
Pull to sync, then Release to sync at the threshold;
15+
hide it at release/cancel. Reuse only the existing Options-left loading component.
16+
Extend the existing Online_recovery_requested path and private pull-owner facts:
17+
online forces a check, offline uses existing admission/connection recovery, busy
18+
intent coalesces until the shared owner can send. Existing response/authoritative
19+
commit and sync phases own completion. Do not add a refresh state machine or
20+
per-refresh public contract. No spec/ or Dune changes are needed or authorized.
21+
Use the native List's existing container geometry/phase observers for the small
22+
trigger and text overlay; retain native scrolling, delegate, rows and anchors.
23+
24+
## Decision
25+
26+
Implemented the optional Timeline callback through Application's existing
27+
Begin_online_recovery action and Online_recovery_requested event. The shared Core
28+
retains one private forced intent until its existing pull owner can send; the
29+
existing response and authoritative commit own completion. The existing header
30+
progress node now spans Connecting, Pulling and Submitting without replacement.
31+
No protected spec/ or Dune change, independent refresh state or top spinner.
32+
33+
The default native accessibility action is Sync. The English-default audit also
34+
replaces unconditional Capture attachment labels with Files, Photos and Camera,
35+
and the photo-add permission description with English. Existing locale-selected
36+
Chinese image-preview translations and graph/test contents remain intact.
37+
38+
The existing public Core contract first reproduced the missing force behavior,
39+
then passed with online, busy, commit, offline/scope, cancel/shutdown and submission
40+
barrier cases. Header identity and optional native callback checks pass in their
41+
existing targets. Full dune build @all and dune runtest --force pass.
42+
43+
A135 iPhone 13/iOS26.1 ran the actual production List, Timeline, header and Core
44+
in a Git-external synthetic IO fixture. Public XCTest touch drags verified short
45+
pull cancellation, armed release, both hints in actual recorded frames,
46+
online/no-change, offline/reconnect/apply, busy repeats, page exit/return, deep
47+
scroll non-trigger and stable first-row position. Actual system Reduce Motion
48+
was enabled for short/long physical drags and restored to its original off value.
49+
The complete flow and Reduce Motion tests both pass. CUA coordinate dragging was
50+
ineffective, so the explicitly authorized official XCTest path was used.
51+
52+
## Alternatives considered
53+
54+
### Default refreshable and a separate per-check contract
55+
56+
Rejected by the updated user design: a second top spinner and independent refresh
57+
state are unwanted. The original spec extension proposal remains external evidence
58+
only and is not applied. Shared sync phase is sufficient for the reused activity
59+
indicator when the actual shared owner retains force and owns completion.
60+
61+
## Acceptance criteria
62+
63+
- Below-threshold release/cancel creates no sync request; armed release creates
64+
one shared trigger and hides the hint immediately.
65+
- Online pulls, offline reconnect/open/pull, and busy/repeated coalescing use the
66+
existing owner and transport; no parallel connections or dropped forced intent.
67+
- Loading is one existing header component, derived from active shared sync phases.
68+
Queue admission, old Current and socket open never imply completion; response
69+
and required authoritative apply retain Pulling until the shared work settles.
70+
- Graph/lifecycle exit retires or fences intent; no refresh continuation or endless
71+
independent spinner is introduced. Existing errors/Diagnostics remain shared.
72+
- Stable List/row identity, anchors, return-top cancellation and Reduce Motion remain.
73+
- Write meaningful failing public Core checks first, then minimal implementation,
74+
passing checks and full applicable local validation in existing test targets.
75+
- Use A135 only with isolated synthetic fixture, no accounts/real graphs, no new
76+
device, 1446, reset/deletion or OS permission changes. Record actual implemented
77+
native UI, with accurate coverage limits. No push/PR/merge/TestFlight.
78+
79+
## Consequences
80+
81+
- Native List overscroll remains system-owned. Only semantic hint changes update
82+
view state; the overlay reserves no layout space and no per-row geometry exists.
83+
- Keep the unrelated error-icon branch separate; main remains d37ee9c as fetched.
84+
- Reuse prepared build inputs without changing another owner's switches or caches.
85+
- Simulator host-restamping has cross_abi_validated=false; this is execution
86+
evidence, not a validated cross-compiled iOS release artifact. Synthetic auth,
87+
transport and apply completions do not establish live service/account acceptance.
88+
- No performance measurement, live graph mutation or dedicated native blocking
89+
error scenario is claimed. Existing shared error/Diagnostics behavior remains.
90+
- Evidence, temporary XCTest, original videos, result bundles and build manifests
91+
are outside the repository in the task's implementation-evidence directory.
92+
The final actual-gesture recording supersedes the earlier partial AX recording;
93+
neither it nor the earlier design movie claims production network verification.
94+
95+
## Questions
96+
97+
None. The latest user approval answers the implementation/isolated A135 scope.
98+
The obsolete per-check protected-spec proposal is not part of this design.

0 commit comments

Comments
 (0)