From f79f9d5d0049113d350b153855b7ee81a9fafc53 Mon Sep 17 00:00:00 2001 From: rcmerci Date: Thu, 15 Oct 2026 10:56:02 +0800 Subject: [PATCH] Resolve UUID references page first and render expansions as plain text --- app/application.ml | 4 +- app/journal_asset_runtime.ml | 2 +- app/journal_graph_runtime.ml | 187 +++++--- app/journal_header.ml | 23 +- app/journal_header.mli | 4 +- app/journal_media_runtime.ml | 4 +- app/journal_model.ml | 17 +- app/journal_model.mli | 2 +- app/journal_row.ml | 9 +- app/journal_row.mli | 1 - app/journal_timeline.ml | 2 - .../JournalMldocTitleAcceptance.swift | 13 +- .../2026-09-01-logseq-overlay-db-package.md | 8 +- ...09-07-overlay-api-call-local-read-reuse.md | 12 +- ...6-10-08-targeted-canonical-confirmation.md | 2 +- ...-15-page-first-outline-block-references.md | 53 +++ .../feature/2026-10-14-mldoc-block-titles.md | 4 +- ...apse-overlay-database-workflow-wrappers.md | 2 +- ...-close-overlay-listener-read-loop-tests.md | 10 +- logseq_db_worker/contract/protocol.ml | 9 +- logseq_db_worker/contract/protocol.mli | 5 +- .../lib/effect_runner/effect_runner.ml | 12 +- .../lib/effect_runner/upload_database.ml | 4 +- .../fixtures/protocol/v2-command-catalog.json | 8 +- .../test/test_managed_sync_e2e.ml | 2 +- .../test/test_overlay_integration.ml | 4 +- logseq_db_worker/test/test_protocol.ml | 2 +- logseq_overlay_db/lib/database.ml | 2 +- logseq_overlay_db/spec/database.mli | 7 +- .../100000-blocks-v1.manifest.json | 4 +- logseq_overlay_db/test/test_asset_reads.ml | 16 +- .../test/test_overlay_changes.ml | 14 +- .../test/test_overlay_concurrency.ml | 2 +- .../test/test_overlay_mutations.ml | 28 +- logseq_overlay_db/test/test_overlay_reads.ml | 14 +- .../test/test_overlay_storage.ml | 12 +- logseq_overlay_db/test/test_overlay_sync.ml | 32 +- logseq_overlay_db/test/test_performance.ml | 4 +- logseq_overlay_db/test/test_support.ml | 2 +- logseq_overlay_db/tool/fixture_generator.ml | 2 +- logseq_overlay_db/tool/page_tree_probe.ml | 4 +- .../tool/performance_benchmark.ml | 10 +- logseq_overlay_db/tool/read_reuse_probe.ml | 32 +- logseq_overlay_db/tool/test_performance.sh | 8 +- swift/JournalChrome.swift | 17 - test/application_view_test.ml | 31 +- test/journal_asset_policy_test.ml | 18 +- test/journal_graph_runtime_locality_test.ml | 436 ++++++++++++++++-- test/journal_media_runtime_test.ml | 22 +- test/journal_model_test.ml | 27 +- test/journal_semantics_test.ml | 43 +- ..._db_worker_application_integration_test.ml | 4 +- test/logseq_overlay_db_boundary_test.ml | 2 +- test/macos_mutation_runtime_test.ml | 6 +- test/source_boundary_test.ml | 2 +- tool/rrbvec_bucket_profile.py | 2 +- tool/test_json_codec_tools.py | 2 +- 57 files changed, 832 insertions(+), 378 deletions(-) create mode 100644 docs/agent-guide/implemented/bugfix/2026-10-15-page-first-outline-block-references.md diff --git a/app/application.ml b/app/application.ml index 4956f1c0..491b081b 100644 --- a/app/application.ml +++ b/app/application.ml @@ -3064,12 +3064,12 @@ let detail_page V.column ~spacing:4. ~alignment:Leading [ V.of_lui (Lui_elements.text ~value:name ~style_class:"caption" ~foreground:"secondary" []) ; Journal_header.detail_text ~title:(render_title state value) - ~on_reference:(fun id -> local.send (Detail_open id)) ~id:key ~depth:2 value ] + ~id:key ~depth:2 value ] in let text = if property_value then field ~key:id (Option.value property_name ~default:"Property") source else Journal_header.detail_text ~title:(render_title state source) - ~on_reference:(fun id -> local.send (Detail_open id)) ~id ~depth source + ~id ~depth source in let fields = List.map (fun (name, value) -> field ~key:("detail-property:" ^ Journal_model.id block ^ ":" ^ name) name value) named_sources in diff --git a/app/journal_asset_runtime.ml b/app/journal_asset_runtime.ml index fd0ffdb3..d0ac3baf 100644 --- a/app/journal_asset_runtime.ml +++ b/app/journal_asset_runtime.ml @@ -205,7 +205,7 @@ let rec pump_dependencies t = in let command = match dependency with - | Block block -> Protocol.V2_get_block { block; revision = None } + | Block block -> Protocol.V2_get_outline_block { block; revision = None } | Page page -> V2_get_page { page; revision = None } in let request = Protocol.{ api_version = 2; request_id; command } in diff --git a/app/journal_graph_runtime.ml b/app/journal_graph_runtime.ml index 3fbb1326..1bee8e04 100644 --- a/app/journal_graph_runtime.ml +++ b/app/journal_graph_runtime.ml @@ -159,10 +159,18 @@ type target_summary = ; retries : int } +type reference_lookup = Page_lookup | Outline_lookup + +type reference_source = + | Source_text of string + | Source_missing + | Source_retryable + type operation = | Reference_read of { block_id : string ; depth : int + ; lookup : reference_lookup } | List_favorites of Journal_graph_request.favorites_request | Favorite_dependency of @@ -309,7 +317,7 @@ type t = ; mutable pages : (string * Projection.page) list ; mutable block_pages : (string * Projection.page) list ; tag_titles : (string, string list) Hashtbl.t - ; reference_sources : (string, int64 * string option) Hashtbl.t + ; reference_sources : (string, int64 * reference_source) Hashtbl.t ; reference_pages : (string, unit) Hashtbl.t ; reference_depths : (string, int) Hashtbl.t ; reference_pending : (string, unit) Hashtbl.t @@ -474,8 +482,8 @@ let request_uuid t = let uuid value = Graph.Uuid.of_string value -let make_request t operation command = - let source_epoch = t.next_request in +let make_request ?source_epoch t operation command = + let source_epoch = Option.value source_epoch ~default:t.next_request in let request_id = request_uuid t in Hashtbl.replace t.pending @@ -1095,14 +1103,14 @@ let submit_unowned t (request : Journal_graph_request.t) = ; after = Option.bind after (fun cursor -> cursor.Projection.protocol_cursor) }) - (Protocol.V2_get_block { block; revision = None }) + (Protocol.V2_get_outline_block { block; revision = None }) ]) | Find_block block_id -> (match parse_uuid "block UUID" block_id with | Error message -> reject message | Ok block -> requests - [ read t Find_block_result (Protocol.V2_get_block { block; revision = None }) ]) + [ read t Find_block_result (Protocol.V2_get_outline_block { block; revision = None }) ]) | Capture { calendar_generation; command } -> (match ( calendar_generation_is_current t calendar_generation @@ -1324,7 +1332,7 @@ let target_summary_read t summary cursor = [ read t (Target_summary summary) - (Protocol.V2_get_block_summary + (Protocol.V2_get_outline_block_summary { block; limit = Protocol.default_page_size; cursor }) ] ;; @@ -1459,7 +1467,7 @@ let continue_capture t (command : Projection.capture) page = [ read t (Capture_block { command; page }) - (Protocol.V2_get_block { block; revision = None }) + (Protocol.V2_get_outline_block { block; revision = None }) ]) ;; @@ -1659,7 +1667,7 @@ let favorite_dependency_read ?(refresh = false) t ~page ~seen uuid = (Favorite_dependency { epoch = t.favorite_epoch; uuid; seen; page }) (if page then Protocol.V2_get_page { page = uuid; revision = None } - else Protocol.V2_get_block { block = uuid; revision = None }))) + else Protocol.V2_get_outline_block { block = uuid; revision = None }))) ;; let recheck_dirty_favorite_dependency t ~uuid ~seen ~page = @@ -1808,27 +1816,28 @@ let remember_detail_block t (block : Graph.block) revision = t.detail_children ;; -let reference_read t block_id depth = +let reference_read ?(lookup = Page_lookup) ?source_epoch t block_id depth = if Hashtbl.mem t.reference_pending block_id then None else ( Hashtbl.replace t.reference_pending block_id (); + let uuid = Graph.Uuid.of_string block_id |> Result.get_ok in Some - (read - t - (Reference_read { block_id; depth }) - (let uuid = Graph.Uuid.of_string block_id |> Result.get_ok in - if Hashtbl.mem t.reference_pages block_id - then Protocol.V2_get_page { page = uuid; revision = None } - else Protocol.V2_get_block { block = uuid; revision = None }))) + (read ?source_epoch t (Reference_read { block_id; depth; lookup }) + (match lookup with + | Page_lookup -> Protocol.V2_get_page { page = uuid; revision = None } + | Outline_lookup -> + Protocol.V2_get_outline_block { block = uuid; revision = None }))) ;; let fence_reference t block_id = match Hashtbl.find_opt t.reference_sources block_id with - | Some (_, source) -> - Hashtbl.replace t.reference_sources block_id (t.next_request, source) + | Some _ -> + (* The displayed value is retained by the model until a completion, but the + invalidated runtime source must remain retryable if its read is abandoned. *) + Hashtbl.replace t.reference_sources block_id (t.next_request, Source_retryable) | None when Hashtbl.mem t.reference_depths block_id -> - Hashtbl.replace t.reference_sources block_id (t.next_request, None) + Hashtbl.replace t.reference_sources block_id (t.next_request, Source_retryable) | None -> () ;; @@ -1864,7 +1873,19 @@ let reference_source t (block : Graph.block) = |> Option.value ~default:block.title ;; +let reference_source_text = function + | Source_text source -> Some source + | Source_missing | Source_retryable -> None +;; + +let reference_needs_read t id = + match Hashtbl.find_opt t.reference_sources id with + | None | Some (_, Source_retryable) -> true + | Some (_, (Source_text _ | Source_missing)) -> false +;; + let observe_reference_sources + ?(missing_is_final = false) ?(extra_sources = Seq.empty) ?(known_sources = Seq.empty) t @@ -1888,8 +1909,13 @@ let observe_reference_sources Hashtbl.mem t.reference_depths id || Hashtbl.length t.reference_sources < maximum_reference_sources then ( - Hashtbl.replace t.reference_sources id (source_epoch, source); - if Option.map snd previous <> Some source && Hashtbl.mem t.reference_depths id + let state = match source with + | Some text -> Source_text text + | None -> if missing_is_final then Source_missing else Source_retryable in + Hashtbl.replace t.reference_sources id (source_epoch, state); + if (Option.map (fun (_, state) -> reference_source_text state) previous <> Some source + || (match previous with Some (_, Source_retryable) -> true | _ -> false)) + && Hashtbl.mem t.reference_depths id then updates := (id, source) :: !updates; Rrbvec.push_back installed value) else installed) @@ -1905,15 +1931,20 @@ let observe_reference_sources let previous = Hashtbl.find_opt t.reference_depths id in if match previous with - | Some old -> next_depth < old + | Some old -> + next_depth < old + || (next_depth = old + && reference_needs_read t id + && not (Hashtbl.mem t.reference_pending id)) | None -> Hashtbl.length t.reference_depths < maximum_reference_targets then ( Hashtbl.replace t.reference_depths id next_depth; match Hashtbl.find_opt t.reference_sources id with - | Some (_, source) -> - updates := (id, source) :: !updates; - Option.iter (discover next_depth) source - | None -> + | Some (_, Source_text source) -> + updates := (id, Some source) :: !updates; + discover next_depth source + | Some (_, Source_missing) -> updates := (id, None) :: !updates + | Some (_, Source_retryable) | None -> Option.iter (fun request -> reads := request :: !reads) (reference_read t id next_depth))) @@ -1938,7 +1969,43 @@ let complete_reference_read t ~source_epoch ~block_id ~depth source = then ( Hashtbl.remove t.reference_dirty block_id; requests (Option.to_list (reference_read t block_id depth))) - else observe_reference_sources t ~source_epoch depth [ block_id, source ] + else observe_reference_sources ~missing_is_final:true t ~source_epoch depth [ block_id, source ] +;; + +let reference_is_current t ~source_epoch block_id = + match Hashtbl.find_opt t.reference_sources block_id with + | Some (epoch, _) -> Int64.compare source_epoch epoch >= 0 + | None -> true +;; + +let fail_reference_read t ~source_epoch ~block_id ~depth = + Hashtbl.remove t.reference_pending block_id; + if Hashtbl.mem t.reference_dirty block_id + then ( + Hashtbl.remove t.reference_dirty block_id; + requests (Option.to_list (reference_read t block_id depth))) + else if reference_is_current t ~source_epoch block_id + then ( + (* A failed/cancelled read is retryable, not proof of target absence. *) + Hashtbl.replace t.reference_sources block_id (source_epoch, Source_retryable); + responses [ response (Reference_sources_changed [ block_id, None ]) ]) + else empty +;; + +let continue_reference_outline t ~source_epoch ~block_id ~depth = + Hashtbl.remove t.reference_pending block_id; + if Hashtbl.mem t.reference_dirty block_id + then ( + Hashtbl.remove t.reference_dirty block_id; + requests (Option.to_list (reference_read t block_id depth))) + else if reference_is_current t ~source_epoch block_id + then + (* Both stages belong to the original snapshot observation. A newer + fragment must fence the fallback even though its request id is newer. *) + requests + (Option.to_list + (reference_read ~lookup:Outline_lookup ~source_epoch t block_id depth)) + else empty ;; let hydration_for_changes t ~request_generation windows = @@ -1961,14 +2028,14 @@ let hydration_for_changes t ~request_generation windows = (read t (Changed_block { block_id; page }) - (Protocol.V2_get_block { block; revision = None })) + (Protocol.V2_get_outline_block { block; revision = None })) | None -> Option.map (fun page -> read t (Changed_detail_block { block_id; page }) - (Protocol.V2_get_block { block; revision = None })) + (Protocol.V2_get_outline_block { block; revision = None })) (Hashtbl.find_opt t.detail_pages block_id)) blocks in @@ -1986,7 +2053,7 @@ let hydration_for_changes t ~request_generation windows = @ List.filter_map (fun page -> let id = Graph.Uuid.to_string page in - if Hashtbl.mem t.reference_pages id + if Hashtbl.mem t.reference_depths id then if owns_detail_page page then ( @@ -2146,7 +2213,7 @@ let rehydrate_current_interests t ~request_generation ~generation = read t (Changed_detail_block { block_id; page = interest.page }) - (Protocol.V2_get_block { block = interest.root.block.uuid; revision = None })) + (Protocol.V2_get_outline_block { block = interest.root.block.uuid; revision = None })) |> List.of_seq in let detail_pages = Hashtbl.create 16 in @@ -2228,7 +2295,7 @@ let read_child t command page = [ read t (Child_read (command, page)) - (Protocol.V2_get_block + (Protocol.V2_get_outline_block { block = Graph.Uuid.of_string command.Projection.block_id |> Result.get_ok ; revision = None }) @@ -2240,7 +2307,7 @@ let read_child_parent t command page child = [ read t (Child_parent (command, page, child)) - (Protocol.V2_get_block + (Protocol.V2_get_outline_block { block = Graph.Uuid.of_string command.Projection.parent_block_id |> Result.get_ok ; revision = None @@ -2249,7 +2316,7 @@ let read_child_parent t command page child = ;; let operation_name = function - | Reference_read _ -> "readBlockReference" + | Reference_read _ -> "readUuidReference" | List_favorites _ -> "listFavorites" | Favorite_dependency _ -> "readFavoriteDependency" | Favorite_page_candidate _ -> "findFavoritePage" @@ -2396,13 +2463,8 @@ let failure_output ; failure = Worker_failure worker_failure }) ] - | Reference_read { block_id; depth } -> - Hashtbl.remove t.reference_pending block_id; - if Hashtbl.mem t.reference_dirty block_id - then ( - Hashtbl.remove t.reference_dirty block_id; - requests (Option.to_list (reference_read t block_id depth))) - else observe_reference_sources t ~source_epoch depth [ block_id, None ] + | Reference_read { block_id; depth; _ } -> + fail_reference_read t ~source_epoch ~block_id ~depth | Changed_block { block_id; _ } | Changed_detail_block { block_id; _ } -> let invalidation = if Hashtbl.mem t.reference_depths block_id @@ -2736,13 +2798,20 @@ let receive_response t (protocol_response : Protocol.response) = | _ -> failure_output t operation request_id "Unexpected journal response.") | V2_page_outcome lookup -> (match lookup, operation with - | (V2_present_page _ | V2_missing_page _), Reference_read { block_id; depth } -> - let source = - match lookup with - | V2_present_page { page; _ } when not page.recycled -> Some page.title - | V2_present_page _ | V2_missing_page _ -> None - in - complete_reference_read t ~source_epoch ~block_id ~depth source + | (V2_present_page _ | V2_missing_page _), + Reference_read { block_id; depth; lookup = Page_lookup } -> + (match lookup with + | V2_missing_page _ -> + continue_reference_outline t ~source_epoch ~block_id ~depth + | V2_present_page { page; _ } -> + if reference_is_current t ~source_epoch block_id + && not (Hashtbl.mem t.reference_dirty block_id) + then Hashtbl.replace t.reference_pages block_id (); + if page.recycled + then fail_reference_read t ~source_epoch ~block_id ~depth + else + complete_reference_read t ~source_epoch ~block_id ~depth + (Some page.title)) | _, Favorite_dependency { epoch; _ } when epoch <> t.favorite_epoch -> empty | _, Favorite_dependency { uuid; seen; page; _ } when Hashtbl.mem t.favorite_dirty (Graph.Uuid.to_string uuid) -> @@ -3175,7 +3244,8 @@ let receive_response t (protocol_response : Protocol.response) = in { refresh with responses = completion :: refresh.responses } | Error message, _ | _, Error message -> reject message)) - | (V2_present_block _ | V2_missing_block _), Reference_read { block_id; depth } -> + | (V2_present_block _ | V2_missing_block _), + Reference_read { block_id; depth; lookup = Outline_lookup } -> let source = match lookup with | V2_present_block { value; _ } -> Some (reference_source t value.block) @@ -3643,7 +3713,8 @@ let receive t (protocol_response : Protocol.response) = match outcome with | V2_block_outcome (V2_present_block { value; _ }) -> [ block value ] | V2_block_outcome (V2_missing_block { uuid; _ }) -> - [ Graph.Uuid.to_string uuid, None ] + let id = Graph.Uuid.to_string uuid in + if Hashtbl.mem t.reference_pages id then [] else [ id, None ] | V2_block_summary_outcome { lookup = V2_present_block { value; _ }; items; next_cursor = None; _ } when List.exists @@ -3739,10 +3810,10 @@ let receive t (protocol_response : Protocol.response) = discovery once when semantic metadata refines the lookup kind, fencing an older in-flight read. *) (match Hashtbl.find_opt t.reference_sources id with - | Some (_, None) -> - Hashtbl.remove t.reference_sources id; - Hashtbl.remove t.reference_depths id - | Some (_, Some _) | None -> ()); + | Some (epoch, (Source_missing | Source_retryable)) -> + Hashtbl.replace t.reference_sources id + (Int64.max epoch source_epoch, Source_retryable) + | Some (_, Source_text _) | None -> ()); if Hashtbl.mem t.reference_pending id then Hashtbl.replace t.reference_dirty id ()); if @@ -3808,9 +3879,11 @@ let fail_request t (request : Protocol.request) ~message = let abandon t (request : Protocol.request) = let key = Graph.Uuid.to_string request.request_id in (match Hashtbl.find_opt t.pending key with - | Some { operation = Reference_read { block_id; _ }; _ } -> + | Some { operation = Reference_read { block_id; _ }; source_epoch; _ } -> Hashtbl.remove t.reference_pending block_id; - Hashtbl.remove t.reference_dirty block_id + Hashtbl.remove t.reference_dirty block_id; + if reference_is_current t ~source_epoch block_id then + Hashtbl.replace t.reference_sources block_id (source_epoch, Source_retryable) | _ -> ()); Hashtbl.remove t.pending key; Hashtbl.remove t.hydration_active key @@ -3969,7 +4042,7 @@ module Copy = struct phase = Root ; version = Some (info.graph_uuid, info.generation, info.projection_revision) } - (Protocol.V2_get_block { block = job.root; revision = None }) + (Protocol.V2_get_outline_block { block = job.root; revision = None }) | Root, V2_block_outcome (V2_present_block { value; _ }) when Graph.Uuid.equal value.block.uuid job.root -> (match add_text job value.block.title with diff --git a/app/journal_header.ml b/app/journal_header.ml index 4be8f164..04cfbbe3 100644 --- a/app/journal_header.ml +++ b/app/journal_header.ml @@ -100,34 +100,23 @@ let bottom_controls ~key ~controls body = ;; (* The same wire representation drives bounded previews and unbounded Detail. *) -let title_text ?on_reference ~mode ?id ~level ?max_lines title = +let title_text ~mode ?id ~level ?max_lines title = let fields = ["mode", `String mode; "text", `String (Journal_model.Title.plain title); "runs", Journal_model.Title.to_yojson title; "level", `Int level] @ Option.to_list (Option.map (fun id -> "id", `String id) id) @ Option.to_list (Option.map (fun n -> "maxLines", `Int n) max_lines) in - let on_event (event : Journal_lui_native.event) = - match on_reference with - | None -> () - | Some open_reference -> - (try match Yojson.Safe.from_string event.payload with - | `Assoc [("reference", `String id)] - when Journal_validation.is_uuid id - && List.exists (fun (run : Journal_model.Title.run) -> run.reference = Some id) title -> - open_reference id - | _ -> () with _ -> ()) - in V.of_lui (Journal_lui_native.mount ?key:(Option.map (fun id -> "detail-label:" ^ id) id) - ~payload:(Yojson.Safe.to_string (`Assoc fields)) ~children:[] ~on_event + ~payload:(Yojson.Safe.to_string (`Assoc fields)) ~children:[] Journal_lui_native.chrome_identifier) ;; -let detail_text ?title ?on_reference ~id ~depth text = - title_text ?on_reference ~mode:"detail-text" ~id ~level:(min 2 depth) +let detail_text ?title ~id ~depth text = + title_text ~mode:"detail-text" ~id ~level:(min 2 depth) (match title with Some title -> title | None -> Journal_model.Title.render text) ;; -let bounded_text ?on_reference ~child title = - title_text ?on_reference ~mode:"bounded-text" ~level:(if child then 1 else 0) +let bounded_text ~child title = + title_text ~mode:"bounded-text" ~level:(if child then 1 else 0) ~max_lines:(if child then 2 else 4) title ;; diff --git a/app/journal_header.mli b/app/journal_header.mli index c7ac76fc..4eca3531 100644 --- a/app/journal_header.mli +++ b/app/journal_header.mli @@ -67,8 +67,8 @@ val feedback val date_header : ?relative_label:string -> title:string -> unit -> Journal_view.View.t -val detail_text : ?title:Journal_model.Title.t -> ?on_reference:(string -> unit) -> id:string -> depth:int -> string -> Journal_view.View.t -val bounded_text : ?on_reference:(string -> unit) -> child:bool -> Journal_model.Title.t -> Journal_view.View.t +val detail_text : ?title:Journal_model.Title.t -> id:string -> depth:int -> string -> Journal_view.View.t +val bounded_text : child:bool -> Journal_model.Title.t -> Journal_view.View.t val detail_row : depth:int -> Journal_view.View.t -> Journal_view.View.t val detail diff --git a/app/journal_media_runtime.ml b/app/journal_media_runtime.ml index 4440af1f..fdb9c455 100644 --- a/app/journal_media_runtime.ml +++ b/app/journal_media_runtime.ml @@ -212,7 +212,7 @@ let observe_request t (request : Protocol.request) = "Attachment dependency requests are full. Retry." in match request.command with - | Protocol.V2_get_block { block; _ } | V2_get_block_summary { block; _ } -> fail block + | Protocol.V2_get_outline_block { block; _ } | V2_get_outline_block_summary { block; _ } -> fail block | V2_get_children { parent; _ } -> fail parent | V2_get_page { page; _ } -> dependency_failure @@ -608,7 +608,7 @@ let request_fact t block = ; command = (if page then V2_get_page { page = uuid; revision = None } - else V2_get_block { block = uuid; revision = None }) + else V2_get_outline_block { block = uuid; revision = None }) })) ;; diff --git a/app/journal_model.ml b/app/journal_model.ml index 391fbffa..ac242fda 100644 --- a/app/journal_model.ml +++ b/app/journal_model.ml @@ -366,7 +366,6 @@ module Title = struct { text : string ; styles : string list ; url : string option - ; reference : string option } [@@deriving yojson] @@ -628,7 +627,7 @@ module Title = struct let render ?(lookup = fun _ -> None) source = let remaining = ref 256 and work = ref 512 in - let rec expand path depth styles url reference atoms = + let rec expand path depth styles url atoms = let output = ref [] and size = ref 0 and cyclic = ref false in @@ -645,7 +644,7 @@ module Title = struct if !work >= 0 then ( match atom with - | Text text -> emit [ { text; styles; url; reference } ] + | Text text -> emit [ { text; styles; url } ] | Span (style, content) -> let runs, cycle = expand @@ -653,19 +652,18 @@ module Title = struct depth (if style = "" then styles else style :: styles) url - reference content in cyclic := !cyclic || cycle; emit runs | External (target, content) -> let runs, cycle = - expand path depth styles (Some target) reference content + expand path depth styles (Some target) content in cyclic := !cyclic || cycle; emit runs | Reference (id, token, label) -> - let fallback () = emit [ { text = token; styles; url; reference } ] in + let fallback () = emit [ { text = token; styles; url } ] in if List.mem id path then ( cyclic := true; @@ -679,8 +677,7 @@ module Title = struct decr remaining; let content = Option.value label ~default:(parse target) in let runs, cycle = - expand (id :: path) (depth + 1) styles None - (match reference with Some _ -> reference | None -> Some id) content + expand (id :: path) (depth + 1) styles None content in if cycle then ( @@ -695,10 +692,10 @@ module Title = struct atoms; List.rev !output, !cyclic in - let runs, _ = expand [] 0 [] None None (parse source) in + let runs, _ = expand [] 0 [] None (parse source) in if List.fold_left (fun n run -> n + String.length run.text) 0 runs > 65536 || !work < 0 - then [ { text = source; styles = []; url = None; reference = None } ] + then [ { text = source; styles = []; url = None } ] else runs ;; diff --git a/app/journal_model.mli b/app/journal_model.mli index b2874eb0..ee00c080 100644 --- a/app/journal_model.mli +++ b/app/journal_model.mli @@ -87,7 +87,7 @@ val property_sources : ?localtime:(float -> Unix.tm) -> Logseq_db_types.Graph_ty (** mldoc owns syntax; native views consume these shared presentation runs. *) module Title : sig - type run = { text : string; styles : string list; url : string option; reference : string option } + type run = { text : string; styles : string list; url : string option } type t = run list val render : ?lookup:(string -> string option) -> string -> t val plain : t -> string diff --git a/app/journal_row.ml b/app/journal_row.ml index ec6e35d5..e064ee96 100644 --- a/app/journal_row.ml +++ b/app/journal_row.ml @@ -2,9 +2,9 @@ module Ui = Journal_view module V = Ui.View module L = Lui_elements -let body ?on_reference ?(child = false) title = +let body ?(child = false) title = if String.trim (Journal_model.Title.plain title) = "" then V.column [] - else Journal_header.bounded_text ?on_reference ~child title + else Journal_header.bounded_text ~child title let metadata block = let status = Journal_model.task_state block in @@ -47,7 +47,6 @@ let metadata block = let view ?(render_source = Fun.id) ?render_title - ?on_reference ~render_media ~show_timestamp (entry : Journal_graph_projection.timeline_entry) @@ -84,7 +83,7 @@ let view then [] else render (Journal_model.source block) in - let content = V.column ~spacing:8. ~alignment:Leading (body ?on_reference source :: metadata block) in + let content = V.column ~spacing:8. ~alignment:Leading (body source :: metadata block) in let labels = [ render_media ~title:(Journal_model.source block) ~root:id ~image_children content ] in @@ -96,7 +95,7 @@ let view ~title:summary.source ~root:summary.block_id ~image_children:[] - (body ?on_reference ~child:true (render summary.source))) + (body ~child:true (render summary.source))) text_children in let labels = diff --git a/app/journal_row.mli b/app/journal_row.mli index a4e33aed..c0c1ec1c 100644 --- a/app/journal_row.mli +++ b/app/journal_row.mli @@ -1,7 +1,6 @@ val view : ?render_source:(string -> string) -> ?render_title:(string -> Journal_model.Title.t) - -> ?on_reference:(string -> unit) -> render_media: (title:string -> root:string diff --git a/app/journal_timeline.ml b/app/journal_timeline.ml index a2d783d9..01274d73 100644 --- a/app/journal_timeline.ml +++ b/app/journal_timeline.ml @@ -233,8 +233,6 @@ let view let render entry show_timestamp = on_region "timeline-item-build"; Journal_row.view ~render_source ?render_title - ?on_reference:(if actions_enabled then Some (fun id -> - Ui.Event.Handler.Private.invoke on_open_block (Ui.Event.Payload.Text id)) else None) ~render_media ~show_timestamp entry in (match store with diff --git a/apple-tests/native-list/JournalMldocTitleAcceptance.swift b/apple-tests/native-list/JournalMldocTitleAcceptance.swift index 7417b554..73f7d997 100644 --- a/apple-tests/native-list/JournalMldocTitleAcceptance.swift +++ b/apple-tests/native-list/JournalMldocTitleAcceptance.swift @@ -77,14 +77,15 @@ final class JournalMldocTitleAcceptance: XCTestCase { screenshot("detail-cyclic-reference") } - func testLinksAndReferenceNavigation() { + func testURLsAndPlainReferenceText() { screenshot("links-before-taps") print("MLDOC_LINKS\n\(app.links.debugDescription)") - let reference = app.links.matching(NSPredicate(format: "label CONTAINS %@", "Referenced")).firstMatch - XCTAssertTrue(reference.waitForExistence(timeout: 5)) - reference.tap() - XCTAssertTrue(app.staticTexts.matching(identifier: "detail-block:82000000-0000-4000-a000-000000000001").firstMatch.waitForExistence(timeout: 10)) - screenshot("reference-target-detail") + XCTAssertEqual(app.links.matching(NSPredicate(format: "label CONTAINS %@", "Referenced")).count, 0) + let root = text("Mldoc fixture:") + root.coordinate(withNormalizedOffset: CGVector(dx: 0.03, dy: 0.08)).tap() + XCTAssertTrue(app.staticTexts.matching(identifier: rootID).firstMatch.waitForExistence(timeout: 10)) + XCTAssertEqual(app.links.matching(NSPredicate(format: "label CONTAINS %@", "Referenced")).count, 0) + screenshot("plain-reference-detail") app.navigationBars.buttons.firstMatch.tap() let labeled = app.links["标签链接"].firstMatch XCTAssertTrue(labeled.waitForExistence(timeout: 5)) diff --git a/docs/agent-guide/implemented/architecture/2026-09-01-logseq-overlay-db-package.md b/docs/agent-guide/implemented/architecture/2026-09-01-logseq-overlay-db-package.md index dc14255f..cfd4e063 100644 --- a/docs/agent-guide/implemented/architecture/2026-09-01-logseq-overlay-db-package.md +++ b/docs/agent-guide/implemented/architecture/2026-09-01-logseq-overlay-db-package.md @@ -472,7 +472,7 @@ All blocked queued intents remain durable for diagnostics and explicit user reco Move authoritative Datascript projection and logical effect composition into one `Overlay_read` implementation. -`get_blocks` queries authoritative blocks by UUID, applies only indexed effects for the requested UUIDs, applies tombstones, and preserves request order. +`get_outline_blocks` queries authoritative blocks by UUID, applies only indexed effects for the requested UUIDs, applies tombstones, and preserves request order. Every present or missing point result carries its block or page state revision so a later mutation can form a target-local precondition. @@ -1169,7 +1169,7 @@ val inspect_admission : t -> (Types.admission_inspection, Types.admission_inspection_error) result -val get_blocks +val get_outline_blocks : snapshot -> Graph.block_uuid list -> (Types.block_lookup list, Types.read_error) result @@ -1569,13 +1569,13 @@ Measure these release-profile budgets on the repository reference machine: | Operation | Required p95 | | --- | ---: | -| `get_blocks` for 64 UUIDs | Less than 20 ms. | +| `get_outline_blocks` for 64 UUIDs | Less than 20 ms. | | `get_structure` for 256 members | Less than 20 ms. | | `get_journals` for 512 pages | Less than 25 ms. | | Single-block logical change classification | Less than 10 ms. | | Pure replan and logical diff for 128 active records | Less than 100 ms. | | Reopen and index reconstruction for 1,024 active records | Less than 100 ms. | -| Maximum-admission `get_blocks` for 64 UUIDs | Less than 80 ms. | +| Maximum-admission `get_outline_blocks` for 64 UUIDs | Less than 80 ms. | | Maximum-admission `get_structure` for 256 members | Less than 80 ms. | | Maximum-admission `get_journals` for 512 pages | Less than 100 ms. | | Maximum-admission rebase and logical diff for 4,096 records | Less than 3.2 seconds. | diff --git a/docs/agent-guide/implemented/bugfix/2026-09-07-overlay-api-call-local-read-reuse.md b/docs/agent-guide/implemented/bugfix/2026-09-07-overlay-api-call-local-read-reuse.md index 6be9b655..bcefb9e8 100644 --- a/docs/agent-guide/implemented/bugfix/2026-09-07-overlay-api-call-local-read-reuse.md +++ b/docs/agent-guide/implemented/bugfix/2026-09-07-overlay-api-call-local-read-reuse.md @@ -13,7 +13,7 @@ The user selected five findings for this proposal: 2. Repeated block, subtree, and affected-page reads during deletion admission. 3. Duplicate sibling scans for insertion precondition validation and order assignment, including unnecessary full entity reads. -4. Metadata enumeration for an empty `Database.get_blocks` request. +4. Metadata enumeration for an empty `Database.get_outline_blocks` request. 5. Repeated metadata reads across one `Database.get_pages` batch. The 2026-09-07 audit used source commit @@ -24,9 +24,9 @@ children; deletion targeted the just-inserted ten-block overlay tree. | Operation | Consumed datom results | Relevant measured repetition | | --- | ---: | --- | -| `get_blocks []` | 173 | One unnecessary ident enumeration | -| `get_blocks`, one existing block | 222 | Includes the 173-ident catalog | -| `get_blocks`, 64 blocks | 600 | Existing batch-local hydration reuse | +| `get_outline_blocks []` | 173 | One unnecessary ident enumeration | +| `get_outline_blocks`, one existing block | 222 | Includes the 173-ident catalog | +| `get_outline_blocks`, 64 blocks | 600 | Existing batch-local hydration reuse | | `get_pages`, 64 pages | 1,408 | Repeated metadata lookups across pages | | `commit_local`, insert ten blocks | 3,266 | Two sibling scans: 2,562 entity results and 512 parent-index results | | `commit_local`, delete ten blocks | 7,920 | 33 ident enumerations, totaling 5,709 results | @@ -183,7 +183,7 @@ outside this scope. ### Empty block requests and batched page reads -For `get_blocks []`, preserve the existing `with_snapshot_read` lifecycle and +For `get_outline_blocks []`, preserve the existing `with_snapshot_read` lifecycle and error validation before returning `Ok []`. Do not allocate or enumerate a hydration cache. A released snapshot or closed database must not become valid merely because the request is empty. @@ -201,7 +201,7 @@ Likely production touch points are internal helpers in `logseq_overlay_db/lib/database.ml`: - `hydration_cache`, property-definition/summary helpers, and UUID resolution. -- `logical_block_at`, `logical_page_at`, `get_blocks`, and `get_pages`. +- `logical_block_at`, `logical_page_at`, `get_outline_blocks`, and `get_pages`. - `preconditions_match`, `revision_for_scope`, and structural child-fact helpers. - `insertion_orders`, `local_candidate_record`, `planned_effect`, and deletion artifact construction. diff --git a/docs/agent-guide/implemented/bugfix/2026-10-08-targeted-canonical-confirmation.md b/docs/agent-guide/implemented/bugfix/2026-10-08-targeted-canonical-confirmation.md index b6ceac02..fd023622 100644 --- a/docs/agent-guide/implemented/bugfix/2026-10-08-targeted-canonical-confirmation.md +++ b/docs/agent-guide/implemented/bugfix/2026-10-08-targeted-canonical-confirmation.md @@ -6,7 +6,7 @@ Confirming one changed block paginated a page tree until the requested root was ## Decision -`logseq_db_worker/contract/protocol.ml` and `.mli` expose `V2_get_block_summary`. Its runner reads the target block, page, and direct children from one captured Database snapshot through existing public APIs. `app/journal_graph_runtime.ml` uses this query after Capture, source edits, and status changes. It paginates only target children at `Protocol.default_page_size` (50); conflict reconciliation retains its existing page-tree path. +`logseq_db_worker/contract/protocol.ml` and `.mli` expose `V2_get_outline_block_summary`. Its runner reads the target block, page, and direct children from one captured Database snapshot through existing public APIs. `app/journal_graph_runtime.ml` uses this query after Capture, source edits, and status changes. It paginates only target children at `Protocol.default_page_size` (50); conflict reconciliation retains its existing page-tree path. ## Identity, pagination, and retained behavior diff --git a/docs/agent-guide/implemented/bugfix/2026-10-15-page-first-outline-block-references.md b/docs/agent-guide/implemented/bugfix/2026-10-15-page-first-outline-block-references.md new file mode 100644 index 00000000..2b1a748c --- /dev/null +++ b/docs/agent-guide/implemented/bugfix/2026-10-15-page-first-outline-block-references.md @@ -0,0 +1,53 @@ +# Page-first UUID references and outline-block lookup names + +## Problem + +PR76 renders UUID references to journal and ordinary pages literally because generic hydration calls a block projection requiring parent/page/order. A valid page therefore returns Missing_block and is negatively cached. Naming the API get_block obscures its narrower shape. + +## Proposal + +The user selected get_outline_block: rename V2_get_block, V2_get_block_summary and Database.get_blocks consistently across contracts, implementations, callers, tests, fixtures and direct documentation. No compatibility aliases. Resolve uncached UUID references through get_page first, falling back to get_outline_block only on explicit Missing_page. Only both missing outcomes create a negative target cache. Preserve reference epochs, typed page metadata, deduplication and the four-read hydration scheduler. Stage the two lookups privately; no additional public type or protocol operation is needed. The user additionally requires expanded UUID references to be plain text: remove their navigation metadata and handlers while preserving explicit URL links and native whole-row Detail navigation. + +## Decision + +Implement page-first staged hydration with explicit outline lookup names and retryable epoch-fenced cache state. UUID expansions are plain text; remove their dedicated navigation route and preserve ordinary URL and whole-row behavior. + +## Alternatives considered + +### Block-first fallback or unified entity lookup + +Not selected: the user explicitly requested page-first and no unified entity query. + +### Keep misleading summary name + +Not selected: its canonical target is also an outline block; its page result is the owning page. + +## Acceptance criteria + +- Page references resolve on cold and previously loaded sessions without a page-valued property. +- Ordinary outline blocks fall back only after explicit page absence, while both missing results retain literal tokens. +- Errors and cancellation never trigger fallback or a new negative cache; later discovery can retry. +- Duplicates, resets, invalidation during either stage, late completions and bounded bursts retain ownership and concurrency behavior. +- Actual DB worker and isolated Simulator validate synthetic targets, Timeline and Detail; screenshot evidence is separate from regression tests. +- Expanded and nested UUID references have no link/action metadata; tapping in Detail never navigates, while Timeline row navigation and explicit URL behavior remain intact. +- No Dune/lock/opam changes, real graph access, phone installation or publication. + +## Risks + +- An unknown outline block requires one additional indexed page lookup. The second stage must retain the original source epoch so a newer fragment cannot be overwritten. Expanded reference text retains its author formatting but gains no link style; explicit URL fragments keep native URL handling. + +## Questions + +No open questions. The user explicitly selected plain UUID text with no reference navigation; no page route is added. + +## Test ownership + +Journal_graph_runtime owns mutable reference caches, pending requests and hydration effects behind its public .mli. Application's pure reducer only consumes Reference_sources_changed, so injecting an incorrect None there does not reproduce query selection. The narrow regression boundary is the existing journal_graph_runtime_locality_test public submit/receive/fail/reset surface, without implementation copies or .mli bypass. Actual storage/worker and Simulator runs are diagnostic acceptance evidence, not duplicate committed regression suites. + +## Consequences + +Unknown outline targets cost an additional indexed page read; present pages stop after the first read. Internal text/missing/retryable cache states preserve source epochs through errors, abandonment, invalidation and later typed metadata. Only the dedicated page-missing then outline-missing chain produces a negative cache; unrelated projection misses remain retryable. Invalidated values remain visible until an accepted completion publishes their replacement or removal. + +The protocol and database source names change without aliases. Expanded UUID runs retain author formatting and explicit URLs, with no reference URL or action metadata. The four-read scheduler, target bounds and source-depth limit remain unchanged. + +Public runtime regressions were observed failing before implementation and pass after it, including independent review findings. Real worker acceptance uses databases created through normal storage transactions and covers cold/loaded/typed page metadata, both read stages, repeated discovery, terminal failure, abandonment and reset. External Simulator evidence is recorded in the task evidence directory; it is not injected into title caches or committed as generated data. diff --git a/docs/agent-guide/implemented/feature/2026-10-14-mldoc-block-titles.md b/docs/agent-guide/implemented/feature/2026-10-14-mldoc-block-titles.md index 7d44b968..f0da9e60 100644 --- a/docs/agent-guide/implemented/feature/2026-10-14-mldoc-block-titles.md +++ b/docs/agent-guide/implemented/feature/2026-10-14-mldoc-block-titles.md @@ -6,7 +6,7 @@ Timeline and Detail expose markup and raw URLs; string reference expansion misre ## Decision -Use official native mldoc AST in Journal_model.Title to emit styled runs with explicit URL/block targets. Timeline root/child and Detail share this adapter and graph-session reference cache. Swift maps runs to AttributedString and native Text for wrapping, truncation and clicks; it does not parse Markdown. Only UUID Page_ref nodes written as [[uuid]] are business references. Code, escapes and ordinary page names remain literal; missing/cyclic targets fall back to their tokens. Expanded nested labels retain their outer reference click target, while explicit URL fragments keep their URL. +Use official native mldoc AST in Journal_model.Title to emit styled runs with explicit URL targets. Timeline root/child and Detail share this adapter and graph-session reference cache. Swift maps runs to AttributedString and native Text for wrapping, truncation and clicks; it does not parse Markdown. Only UUID Page_ref nodes written as [[uuid]] are business references. Code, escapes and ordinary page names remain literal; missing/cyclic targets fall back to their tokens. Expanded nested labels are plain text, while explicit URL fragments keep their URL. The later page-first outline-block decision removes reference navigation at the user’s request. Admit at most 64 KiB source, 32 delimiter depth and 4096 open delimiters before parsing, then cap semantic conversion at 512 nodes. Keep a FIFO 512-entry/2 MiB source-byte syntax cache. Expansion is limited to 16 reference levels, 256 targets, 512 work nodes and 64 KiB output. Timeline keeps four parent lines and at most three smaller/fainter children of two lines; Detail retains status, properties, ten-level pagination, 100 ms loading and asset ownership. @@ -32,4 +32,4 @@ Three actual native XCTest cases pass on the isolated iPhone 13/iOS 26.1 synthet ## Test ownership -The nested-reference click defect belongs to pure Title.render, which has no reducer-event boundary. Its public output regression failed before the minimal fix and passed afterward, without injected DB/effect results. The external-build path defect belongs to the existing public compiler test harness; its direct execution and unchanged compiler assertions exercise the narrowest boundary. Native XCTest is the separately requested feature acceptance. +The original nested-reference click regression has been replaced by plain-text output coverage after the user removed reference navigation. Title.render has no reducer-event boundary; public output tests verify no reference metadata while preserving formatting and explicit URL fragments. The external-build path defect belongs to the existing public compiler test harness; its direct execution and unchanged compiler assertions exercise the narrowest boundary. Native XCTest is the separately requested feature acceptance. diff --git a/docs/agent-guide/implemented/simplification/2026-09-04-collapse-overlay-database-workflow-wrappers.md b/docs/agent-guide/implemented/simplification/2026-09-04-collapse-overlay-database-workflow-wrappers.md index 12b8c4a5..d8282cc0 100644 --- a/docs/agent-guide/implemented/simplification/2026-09-04-collapse-overlay-database-workflow-wrappers.md +++ b/docs/agent-guide/implemented/simplification/2026-09-04-collapse-overlay-database-workflow-wrappers.md @@ -325,7 +325,7 @@ the validated-result wrapper, not the external-crypto boundary. ### Merge all read APIs into one request/result ADT -Rejected. `get_blocks`, `get_pages`, `get_journals`, and `get_structure` have +Rejected. `get_outline_blocks`, `get_pages`, `get_journals`, and `get_structure` have different result shapes and caller intent. One union would replace clear functions with request/result coupling and exhaustive irrelevant cases without removing a domain concept. diff --git a/docs/agent-guide/implemented/testing/2026-09-04-close-overlay-listener-read-loop-tests.md b/docs/agent-guide/implemented/testing/2026-09-04-close-overlay-listener-read-loop-tests.md index 0d2a3e66..c5ed7ce8 100644 --- a/docs/agent-guide/implemented/testing/2026-09-04-close-overlay-listener-read-loop-tests.md +++ b/docs/agent-guide/implemented/testing/2026-09-04-close-overlay-listener-read-loop-tests.md @@ -59,7 +59,7 @@ Use a small test-local helper that: `after_revision` equals the transition result's projection revision. 5. Acquires `Database.current_snapshot` only after receiving the notification. 6. Proves that the acquired snapshot has the notified `after_revision`. -7. Reads the affected entities and structure through `Database.get_blocks` and +7. Reads the affected entities and structure through `Database.get_outline_blocks` and `Database.get_structure`. 8. Releases the snapshot and unlistens through cleanup paths even when an assertion fails. @@ -126,7 +126,7 @@ update in the same publication and is therefore required in addition to the two direct publication cases. Its assertions must stay at the stable public projection and sync-view boundary rather than inspect private planner details. -All three cases must use `Database.get_blocks` plus a `Children` structure read +All three cases must use `Database.get_outline_blocks` plus a `Children` structure read as their baseline successor pull. A case should add a complete `Page_tree` read only when its fixture introduces a multi-level subtree whose recursive nesting or ordering is not established by the direct children result. The proposed @@ -140,7 +140,7 @@ on 2026-09-04: - require the independent optimistic outbox case A and authoritative case B; - also require combined authoritative-rebase case C so one publication proves the merged authoritative and pending-outbox contract; and -- use `get_blocks` plus `Children` as the normal successor pull, adding +- use `get_outline_blocks` plus `Children` as the normal successor pull, adding `Page_tree` only when a case introduces a multi-level subtree. ## Alternatives considered @@ -194,7 +194,7 @@ to the merged logical change suite. Keeping the closed-loop cases in successor snapshot version. - Each case validates notification interests and the corresponding block and structure reads, rather than checking revision advancement alone. -- Each case uses `get_blocks` and a `Children` request for its baseline pull. +- Each case uses `get_outline_blocks` and a `Children` request for its baseline pull. `Page_tree` is required only for a fixture that introduces a multi-level subtree. - Outbox-driven coverage verifies the durable submission state with @@ -243,5 +243,5 @@ snapshot. ## Questions None. The user confirmed that cases A, B, and C are all required, that -`get_blocks` plus `Children` is the baseline pull, and that `Page_tree` is +`get_outline_blocks` plus `Children` is the baseline pull, and that `Page_tree` is required only for a multi-level subtree fixture on 2026-09-04. diff --git a/logseq_db_worker/contract/protocol.ml b/logseq_db_worker/contract/protocol.ml index a8e81428..9a12d730 100644 --- a/logseq_db_worker/contract/protocol.ml +++ b/logseq_db_worker/contract/protocol.ml @@ -45,11 +45,12 @@ and command = { page : page_uuid ; revision : string option } - | V2_get_block of + (* Outline content requires parent/page/order; page targets use V2_get_page. *) + | V2_get_outline_block of { block : block_uuid ; revision : string option } - | V2_get_block_summary of + | V2_get_outline_block_summary of { block : block_uuid ; limit : int ; cursor : Cursor.t option @@ -395,7 +396,7 @@ let validate_command = function List.length roots <= maximum_filter_values && valid_limit limit | V2_list_favorites { limit; _ } | V2_list_journals { limit; _ } - | V2_get_block_summary { limit; _ } + | V2_get_outline_block_summary { limit; _ } | V2_get_children { limit; _ } | V2_pull_changes { limit; _ } -> valid_limit limit | V2_get_page_tree { maximum_depth; limit; _ } -> @@ -405,7 +406,7 @@ let validate_command = function | V2_graph_info | V2_inspect_admission | V2_get_page _ - | V2_get_block _ + | V2_get_outline_block _ | V2_set_asset_reference _ | V2_delete_blocks _ | V2_set_task_status _ diff --git a/logseq_db_worker/contract/protocol.mli b/logseq_db_worker/contract/protocol.mli index 6d440ab7..c5caef69 100644 --- a/logseq_db_worker/contract/protocol.mli +++ b/logseq_db_worker/contract/protocol.mli @@ -45,14 +45,15 @@ and command = { page : page_uuid ; revision : string option } - | V2_get_block of + (* Outline content requires parent/page/order; page targets use V2_get_page. *) + | V2_get_outline_block of { block : block_uuid ; revision : string option } (* Canonical target, owning page and a bounded direct-child slice share one Database snapshot. Limit is 1..maximum_page_size; use default_page_size for normal metadata. The opaque child cursor is projection-version-bound. *) - | V2_get_block_summary of + | V2_get_outline_block_summary of { block : block_uuid ; limit : int ; cursor : Cursor.t option diff --git a/logseq_db_worker/lib/effect_runner/effect_runner.ml b/logseq_db_worker/lib/effect_runner/effect_runner.ml index d1786d44..26498b01 100644 --- a/logseq_db_worker/lib/effect_runner/effect_runner.ml +++ b/logseq_db_worker/lib/effect_runner/effect_runner.ml @@ -829,8 +829,8 @@ let local_mutation = function | V2_list_favorites _ | V2_list_journals _ | V2_get_page _ - | V2_get_block _ - | V2_get_block_summary _ + | V2_get_outline_block _ + | V2_get_outline_block_summary _ | V2_get_children _ | V2_get_page_tree _ | V2_pull_changes _ @@ -1078,8 +1078,8 @@ let read_snapshot database request command = (V2_missing_page { uuid; revision = Overlay.Page_state_revision.to_string revision })) | Ok _ | Error _ -> failure request Corrupt_storage "The page lookup failed.") - | V2_get_block { block; _ } -> - (match Database.get_blocks snapshot [ block ] with + | V2_get_outline_block { block; _ } -> + (match Database.get_outline_blocks snapshot [ block ] with | Ok [ Overlay.Present_block { value; revision } ] -> response request @@ -1095,7 +1095,7 @@ let read_snapshot database request command = (V2_missing_block { uuid; revision = Overlay.Block_state_revision.to_string revision })) | Ok _ | Error _ -> failure request Corrupt_storage "The block lookup failed.") - | V2_get_block_summary { block; limit; cursor } -> + | V2_get_outline_block_summary { block; limit; cursor } -> let version = Database.snapshot_version snapshot in let summary lookup page scope_revision items next_cursor = response @@ -1114,7 +1114,7 @@ let read_snapshot database request command = if limit < 1 || limit > Protocol.maximum_page_size then failure request Invalid_request "Invalid block summary limit." else ( - match Database.get_blocks snapshot [ block ] with + match Database.get_outline_blocks snapshot [ block ] with | Ok [ Overlay.Missing_block { uuid; revision } ] -> summary (V2_missing_block diff --git a/logseq_db_worker/lib/effect_runner/upload_database.ml b/logseq_db_worker/lib/effect_runner/upload_database.ml index 2e382420..5fc01e48 100644 --- a/logseq_db_worker/lib/effect_runner/upload_database.ml +++ b/logseq_db_worker/lib/effect_runner/upload_database.ml @@ -89,7 +89,7 @@ let apply_local db (i : I.t) = match pages with | [ T.Present_page { revision; _ } ] -> Ok ([], [ i.target, revision ]) | _ -> - let* blocks = read (D.get_blocks s [ i.target ]) in + let* blocks = read (D.get_outline_blocks s [ i.target ]) in (match blocks with | [ T.Present_block { revision; _ } ] -> Ok ([ i.target, revision ], []) | _ -> Error U.Invalid_content) @@ -119,7 +119,7 @@ let apply_metadata db (i : I.t) = | Local_present -> let* expected = snapshot db (fun s -> - let* blocks = read (D.get_blocks s [ i.asset ]) in + let* blocks = read (D.get_outline_blocks s [ i.asset ]) in match blocks with | [ T.Present_block { revision; _ } ] -> read (D.write_precondition ~blocks:[ i.asset, revision ] ~pages:[] ~scopes:[]) diff --git a/logseq_db_worker/test/fixtures/protocol/v2-command-catalog.json b/logseq_db_worker/test/fixtures/protocol/v2-command-catalog.json index f05dfe96..e8e927e5 100644 --- a/logseq_db_worker/test/fixtures/protocol/v2-command-catalog.json +++ b/logseq_db_worker/test/fixtures/protocol/v2-command-catalog.json @@ -59,7 +59,7 @@ "api_version": 2, "request_id": "00000000-0000-4000-8000-000000000004", "command": [ - "V2_get_block", + "V2_get_outline_block", { "block": "10000000-0000-4000-8000-000000000001", "revision": null @@ -302,7 +302,7 @@ "api_version": 2, "request_id": "00000000-0000-4000-8000-000000000019", "command": [ - "V2_get_block_summary", + "V2_get_outline_block_summary", { "block": "10000000-0000-4000-8000-000000000001", "limit": 50, @@ -339,13 +339,13 @@ "V2_graph_info", "V2_list_journals", "V2_get_page", - "V2_get_block", + "V2_get_outline_block", "V2_get_children", "V2_get_page_tree", "V2_inspect_admission", "V2_get_asset_descriptors", "V2_list_assets", - "V2_get_block_summary" + "V2_get_outline_block_summary" ], "mutation_operations": [ "V2_save_block", diff --git a/logseq_db_worker/test/test_managed_sync_e2e.ml b/logseq_db_worker/test/test_managed_sync_e2e.ml index 2adf35b1..a4804f04 100644 --- a/logseq_db_worker/test/test_managed_sync_e2e.ml +++ b/logseq_db_worker/test/test_managed_sync_e2e.ml @@ -421,7 +421,7 @@ let journal_parent context = ;; let read_block context block_uuid = - match graph_request context (V2_get_block { block = block_uuid; revision = None }) with + match graph_request context (V2_get_outline_block { block = block_uuid; revision = None }) with | V2_response { outcome = V2_block_outcome (V2_present_block { value; revision }); _ } -> Ok (value, revision) | V2_response { outcome = V2_block_outcome (V2_missing_block _); _ } -> Error () diff --git a/logseq_db_worker/test/test_overlay_integration.ml b/logseq_db_worker/test/test_overlay_integration.ml index 08176d39..26e5eac5 100644 --- a/logseq_db_worker/test/test_overlay_integration.ml +++ b/logseq_db_worker/test/test_overlay_integration.ml @@ -85,7 +85,7 @@ let () = ; T.case "v2 catalog contains only the current App surface" (fun () -> require_exact "read operations" - [ "V2_get_block" + [ "V2_get_outline_block" ; "V2_get_children" ; "V2_get_page" ; "V2_get_page_tree" @@ -95,7 +95,7 @@ let () = ; "V2_list_journals" ; "V2_get_asset_descriptors" ; "V2_list_assets" - ; "V2_get_block_summary" + ; "V2_get_outline_block_summary" ] (strings "read_operations" commands); require_exact diff --git a/logseq_db_worker/test/test_protocol.ml b/logseq_db_worker/test/test_protocol.ml index 083db60d..d2f0e077 100644 --- a/logseq_db_worker/test/test_protocol.ml +++ b/logseq_db_worker/test/test_protocol.ml @@ -486,7 +486,7 @@ let request_domain_constraints () = "invalid nested UUID" (native_request (native_variant - "V2_get_block" + "V2_get_outline_block" [ "block", `String "not-a-uuid"; "revision", `Null ])); let save title = native_request diff --git a/logseq_overlay_db/lib/database.ml b/logseq_overlay_db/lib/database.ml index b8aec436..36ba153a 100644 --- a/logseq_overlay_db/lib/database.ml +++ b/logseq_overlay_db/lib/database.ml @@ -2671,7 +2671,7 @@ let read_page context uuid = cached_logical_page context.read_snapshot (Lazy.force context.metadata) uuid ;; -let get_blocks snapshot uuids = +let get_outline_blocks snapshot uuids = with_snapshot_read snapshot (fun snapshot -> let context = lazy (read_context snapshot) in Ok diff --git a/logseq_overlay_db/spec/database.mli b/logseq_overlay_db/spec/database.mli index 62d66ebd..b68b400b 100644 --- a/logseq_overlay_db/spec/database.mli +++ b/logseq_overlay_db/spec/database.mli @@ -227,8 +227,9 @@ val inspect_admission [Types.Stale_read_cursor]. Malformed cursors or out-of-range offsets return [Types.Invalid_read_request]. Restart the query after a stale projection. *) -(** Looks up blocks by UUID in the captured projection. *) -val get_blocks +(** Looks up outline content blocks by UUID in the captured projection. These + records require parent, page and order; page records are read with [get_pages]. *) +val get_outline_blocks : snapshot -> Graph.block_uuid list -> (Types.block_lookup list, Types.read_error) result @@ -292,7 +293,7 @@ val get_structure | Ok snapshot -> Fun.protect ~finally:(fun () -> release_snapshot snapshot) - (fun () -> get_blocks snapshot uuids) + (fun () -> get_outline_blocks snapshot uuids) ]} *) (** {1 Crossing from a snapshot to live changes} diff --git a/logseq_overlay_db/test/fixtures/performance/100000-blocks-v1.manifest.json b/logseq_overlay_db/test/fixtures/performance/100000-blocks-v1.manifest.json index 770b94ad..522d123e 100644 --- a/logseq_overlay_db/test/fixtures/performance/100000-blocks-v1.manifest.json +++ b/logseq_overlay_db/test/fixtures/performance/100000-blocks-v1.manifest.json @@ -18,13 +18,13 @@ "maximum_bytes": 8388608 }, "p95_milliseconds": { - "get_blocks_64": 20, + "get_outline_blocks_64": 20, "get_structure_200": 20, "get_journals_200": 25, "single_block_change_classification": 10, "replan_and_diff_128": 100, "reopen_1024": 100, - "maximum_get_blocks_64": 80, + "maximum_get_outline_blocks_64": 80, "maximum_get_structure_200": 80, "maximum_get_journals_200": 100, "maximum_rebase_and_diff_4096": 3200, diff --git a/logseq_overlay_db/test/test_asset_reads.ml b/logseq_overlay_db/test/test_asset_reads.ml index d4917984..a0aaa4ad 100644 --- a/logseq_overlay_db/test/test_asset_reads.ml +++ b/logseq_overlay_db/test/test_asset_reads.ml @@ -239,7 +239,7 @@ let deletion database = seed database; with_snapshot database (fun before -> let revision = - match D.get_blocks before [ id 1 ] |> get with + match D.get_outline_blocks before [ id 1 ] |> get with | [ Types.Present_block b ] -> b.revision | _ -> Alcotest.fail "missing block" in @@ -347,7 +347,7 @@ let local_asset_import database = "duplicate import created another asset"); let expected = with_snapshot database (fun snapshot -> - match D.get_blocks snapshot [ id 90 ] |> get with + match D.get_outline_blocks snapshot [ id 90 ] |> get with | [ Types.Present_block b ] -> D.write_precondition ~blocks:[ id 90, b.revision ] ~pages:[] ~scopes:[] |> get | _ -> Alcotest.fail "imported asset block missing") @@ -406,7 +406,7 @@ let local_page_asset_import database = let expected = with_snapshot database (fun snapshot -> let revision = - match D.get_blocks snapshot [ text ] |> get with + match D.get_outline_blocks snapshot [ text ] |> get with | [ Types.Present_block block ] -> block.revision | _ -> Alcotest.fail "local capture text missing" in @@ -553,7 +553,7 @@ let reuse_asset_reference database = seed database; let expected block = with_snapshot database (fun snapshot -> - match D.get_blocks snapshot [ block ] |> get with + match D.get_outline_blocks snapshot [ block ] |> get with | [ Types.Present_block b ] -> D.write_precondition ~blocks:[ block, b.revision ] ~pages:[] ~scopes:[] |> get | _ -> Alcotest.fail "reference holder missing") @@ -628,7 +628,7 @@ let reference_pending_asset database = local_asset_import database; let expected = with_snapshot database (fun s -> - match D.get_blocks s [ id 1 ] |> get with + match D.get_outline_blocks s [ id 1 ] |> get with | [ Types.Present_block holder ] -> D.write_precondition ~blocks:[ id 1, holder.revision ] ~pages:[] ~scopes:[] |> get | _ -> Alcotest.fail "holder missing") @@ -656,7 +656,7 @@ let replace_asset_binary database = let expected = with_snapshot database (fun snapshot -> let revision = - match D.get_blocks snapshot [ parent ] |> get with + match D.get_outline_blocks snapshot [ parent ] |> get with | [ Types.Present_block holder ] -> holder.revision | _ -> Alcotest.fail "replacement holder missing" in @@ -950,7 +950,7 @@ let asset_target_classification_budget () = (function | Types.Missing_block _ -> true | _ -> false) - (D.get_blocks snapshot (List.init 8 (fun n -> id (100 + n))) |> get)) + (D.get_outline_blocks snapshot (List.init 8 (fun n -> id (100 + n))) |> get)) "unresolved reference stubs must not be projected blocks"); let subscriber, predecessor = D.listen database |> get in let revision = (D.snapshot_version predecessor).projection_revision in @@ -1130,7 +1130,7 @@ let negative_favorite_target_changes ?(link_changed = false) database = if List.length favorites.items <> 1 then ( let target = - match D.get_blocks snapshot [ id 96 ] |> get with + match D.get_outline_blocks snapshot [ id 96 ] |> get with | [ Types.Present_block { value; _ } ] -> Printf.sprintf "%s parent=%s page=%s" diff --git a/logseq_overlay_db/test/test_overlay_changes.ml b/logseq_overlay_db/test/test_overlay_changes.ml index 0394a617..3e676a41 100644 --- a/logseq_overlay_db/test/test_overlay_changes.ml +++ b/logseq_overlay_db/test/test_overlay_changes.ml @@ -345,7 +345,7 @@ let paused_subscription_overflow_coalesces_to_resync database = ~finally:(fun () -> Database.release_snapshot snapshot) (fun () -> match - Database.get_blocks snapshot [ T.authoritative_block_uuid ] + Database.get_outline_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior with | [ Present_block { revision; _ } ] -> revision @@ -468,7 +468,7 @@ let semantic_no_op_publishes_no_event database = let snapshot = Database.current_snapshot database |> T.require_ok ~behavior in let revision = match - Database.get_blocks snapshot [ T.missing_block_uuid ] |> T.require_ok ~behavior + Database.get_outline_blocks snapshot [ T.missing_block_uuid ] |> T.require_ok ~behavior with | [ Missing_block { revision; _ } ] -> revision | _ -> Alcotest.fail "missing no-op target unexpectedly exists" @@ -600,7 +600,7 @@ let local_outbox_commit_notifies_and_successor_snapshot_is_readable database = Fun.protect ~finally:(fun () -> Database.release_snapshot before) (fun () -> - match Database.get_blocks before [ T.block_uuid ] |> T.require_ok ~behavior with + match Database.get_outline_blocks before [ T.block_uuid ] |> T.require_ok ~behavior with | [ Missing_block { revision; _ } ] -> revision | _ -> Alcotest.fail "local listener target already exists") in @@ -614,7 +614,7 @@ let local_outbox_commit_notifies_and_successor_snapshot_is_readable database = T.require (commit.status = Applied) "local listener commit was not applied"; require_interest change ~block:T.block_uuid ~page:T.page_uuid; (match - Database.get_blocks successor [ T.block_uuid ] |> T.require_ok ~behavior + Database.get_outline_blocks successor [ T.block_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; revision } ] -> T.require @@ -672,7 +672,7 @@ let authoritative_update_notifies_and_successor_snapshot_is_readable database = "authoritative listener commit did not advance the checkpoint"; require_interest change ~block:T.missing_block_uuid ~page:T.page_uuid; (match - Database.get_blocks successor [ T.missing_block_uuid ] |> T.require_ok ~behavior + Database.get_outline_blocks successor [ T.missing_block_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; _ } ] -> T.require @@ -708,7 +708,7 @@ let authoritative_rebase_notifies_and_publishes_merged_pending_intent database = ~finally:(fun () -> Database.release_snapshot before) (fun () -> match - Database.get_blocks before [ T.authoritative_block_uuid ] + Database.get_outline_blocks before [ T.authoritative_block_uuid ] |> T.require_ok ~behavior with | [ Present_block { revision; _ } ] -> revision @@ -764,7 +764,7 @@ let authoritative_rebase_notifies_and_publishes_merged_pending_intent database = | Projection_resync_required _ -> Alcotest.fail "bounded listener rebase unexpectedly required resync"); (match - Database.get_blocks successor [ T.authoritative_block_uuid ] + Database.get_outline_blocks successor [ T.authoritative_block_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; _ } ] -> diff --git a/logseq_overlay_db/test/test_overlay_concurrency.ml b/logseq_overlay_db/test/test_overlay_concurrency.ml index bcb4f0a1..c9322025 100644 --- a/logseq_overlay_db/test/test_overlay_concurrency.ml +++ b/logseq_overlay_db/test/test_overlay_concurrency.ml @@ -166,7 +166,7 @@ let concurrent_commits_publish_in_revision_order database = Fun.protect ~finally:(fun () -> Database.release_snapshot snapshot) (fun () -> - match Database.get_blocks snapshot [ block ] |> T.require_ok ~behavior with + match Database.get_outline_blocks snapshot [ block ] |> T.require_ok ~behavior with | [ Present_block { revision; _ } ] -> revision | _ -> Alcotest.fail "concurrent commit fixture block is missing") in diff --git a/logseq_overlay_db/test/test_overlay_mutations.ml b/logseq_overlay_db/test/test_overlay_mutations.ml index d9c7a307..6cda2a0e 100644 --- a/logseq_overlay_db/test/test_overlay_mutations.ml +++ b/logseq_overlay_db/test/test_overlay_mutations.ml @@ -37,7 +37,7 @@ let journal_precondition database page behavior = let block_precondition database block behavior = current_snapshot database behavior (fun snapshot -> let block_revision = - match Database.get_blocks snapshot [ block ] |> T.require_ok ~behavior with + match Database.get_outline_blocks snapshot [ block ] |> T.require_ok ~behavior with | [ Present_block { revision; _ } ] | [ Missing_block { revision; _ } ] -> revision | _ -> Alcotest.fail "block precondition lookup returned the wrong cardinality" in @@ -48,7 +48,7 @@ let block_precondition database block behavior = let delete_precondition database block behavior = current_snapshot database behavior (fun snapshot -> let block_revision = - match Database.get_blocks snapshot [ block ] |> T.require_ok ~behavior with + match Database.get_outline_blocks snapshot [ block ] |> T.require_ok ~behavior with | [ Present_block { revision; _ } ] -> revision | _ -> Alcotest.fail "delete target block is missing" in @@ -82,7 +82,7 @@ let insert_precondition database parent behavior = let block_insert_precondition database parent behavior = current_snapshot database behavior (fun snapshot -> let parent_revision = - match Database.get_blocks snapshot [ parent ] |> T.require_ok ~behavior with + match Database.get_outline_blocks snapshot [ parent ] |> T.require_ok ~behavior with | [ Present_block { revision; _ } ] -> revision | _ -> Alcotest.fail "insert parent block is missing" in @@ -120,7 +120,7 @@ let insert_is_immediately_readable database = in T.require (commit.status = Applied) "pending insert did not report Applied"; current_snapshot database behavior (fun snapshot -> - match Database.get_blocks snapshot [ T.block_uuid ] |> T.require_ok ~behavior with + match Database.get_outline_blocks snapshot [ T.block_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; _ } ] -> T.require (value.block.title = "Inserted") "pending insert title is not visible" | _ -> Alcotest.fail "pending inserted block is not visible") @@ -172,7 +172,7 @@ let pending_insert_can_be_saved database = behavior |> require_new_commit behavior); current_snapshot database behavior (fun snapshot -> - match Database.get_blocks snapshot [ T.block_uuid ] |> T.require_ok ~behavior with + match Database.get_outline_blocks snapshot [ T.block_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; _ } ] -> T.require (value.block.title = "Edited pending") @@ -205,7 +205,7 @@ let pending_block_can_receive_an_inserted_child database = behavior |> require_new_commit behavior); current_snapshot database behavior (fun snapshot -> - match Database.get_blocks snapshot [ T.child_uuid ] |> T.require_ok ~behavior with + match Database.get_outline_blocks snapshot [ T.child_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; _ } ] -> T.require (Graph.Uuid.equal value.block.parent T.block_uuid) @@ -258,7 +258,7 @@ let pending_insert_can_be_deleted database = behavior |> require_new_commit behavior); current_snapshot database behavior (fun snapshot -> - match Database.get_blocks snapshot [ T.block_uuid ] |> T.require_ok ~behavior with + match Database.get_outline_blocks snapshot [ T.block_uuid ] |> T.require_ok ~behavior with | [ Missing_block _ ] -> () | _ -> Alcotest.fail "delete tombstone did not hide the pending block") ;; @@ -381,14 +381,14 @@ let delete_uses_root_revision_and_latest_local_subtree database = (commit database expected (T.delete_blocks ~ordinal:43 ()) behavior |> require_new_commit behavior); current_snapshot database behavior (fun snapshot -> - Database.get_blocks snapshot [ T.block_uuid; T.child_uuid; newer; grandchild ] + Database.get_outline_blocks snapshot [ T.block_uuid; T.child_uuid; newer; grandchild ] |> T.require_ok ~behavior |> List.iter (function | Missing_block _ -> () | Present_block _ -> Alcotest.fail "latest local descendant survived delete admission")); match - Database.get_blocks pinned [ T.block_uuid; newer ] |> T.require_ok ~behavior + Database.get_outline_blocks pinned [ T.block_uuid; newer ] |> T.require_ok ~behavior with | [ Present_block _; Missing_block _ ] -> () | _ -> Alcotest.fail "delete changed the earlier snapshot") @@ -532,7 +532,7 @@ let delete_rewrites_external_reference_source database = |> require_new_commit behavior); current_snapshot database behavior (fun snapshot -> match - Database.get_blocks snapshot [ T.authoritative_block_uuid; T.reference_source_uuid ] + Database.get_outline_blocks snapshot [ T.authoritative_block_uuid; T.reference_source_uuid ] |> T.require_ok ~behavior with | [ Missing_block _; Present_block { value = source; _ } ] -> @@ -557,7 +557,7 @@ let delete_default_property_value_replaces_holders database = |> require_new_commit behavior); current_snapshot database behavior (fun snapshot -> match - Database.get_blocks snapshot [ T.default_value_uuid; T.property_holder_uuid ] + Database.get_outline_blocks snapshot [ T.default_value_uuid; T.property_holder_uuid ] |> T.require_ok ~behavior with | [ Present_block _; Present_block { value = holder; _ } ] -> @@ -609,7 +609,7 @@ let deterministic_model_projection_matches_naive_oracle database = in current_snapshot database behavior (fun snapshot -> let actual = - Database.get_blocks + Database.get_outline_blocks snapshot (List.map (fun (block : Oracle.block) -> block.Oracle.uuid) expected_blocks) |> T.require_ok ~behavior @@ -695,7 +695,7 @@ let deterministic_model_projection_matches_naive_oracle database = let order_of database uuid = current_snapshot database "read assigned order" (fun snapshot -> match - Database.get_blocks snapshot [ uuid ] |> T.require_ok ~behavior:"read order" + Database.get_outline_blocks snapshot [ uuid ] |> T.require_ok ~behavior:"read order" with | [ Present_block { value; _ } ] -> value.block.order | _ -> Alcotest.fail "ordered block is missing") @@ -993,7 +993,7 @@ let orders_survive_reopen_and_outbox_drain () = let module Codec = Transit_native.Transit.Json in let records = current_snapshot database behavior (fun snapshot -> - Database.get_blocks snapshot (List.map order_uuid [ 600; 601; 602 ]) + Database.get_outline_blocks snapshot (List.map order_uuid [ 600; 601; 602 ]) |> T.require_ok ~behavior |> List.map (function | Present_block { value; _ } -> value.block diff --git a/logseq_overlay_db/test/test_overlay_reads.ml b/logseq_overlay_db/test/test_overlay_reads.ml index 9a771591..8b639257 100644 --- a/logseq_overlay_db/test/test_overlay_reads.ml +++ b/logseq_overlay_db/test/test_overlay_reads.ml @@ -123,7 +123,7 @@ let point_reads_preserve_property_summaries database = "page property summary was discarded" | _ -> Alcotest.fail "property fixture page is missing"); (match - Database.get_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior + Database.get_outline_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; _ } ] -> T.require @@ -135,7 +135,7 @@ let point_reads_preserve_property_summaries database = let authoritative_block_is_projected_by_uuid_and_structure _database snapshot = (match - Database.get_blocks snapshot [ T.authoritative_block_uuid ] + Database.get_outline_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior:"authoritative block UUID projection" with | [ Present_block { value; _ } ] -> @@ -585,7 +585,7 @@ let pinned_snapshot_keeps_optimistic_outbox_root database = (fun () -> let revision = match - Database.get_blocks snapshot [ T.authoritative_block_uuid ] + Database.get_outline_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior with | [ Present_block { revision; _ } ] -> revision @@ -615,7 +615,7 @@ let pinned_snapshot_keeps_optimistic_outbox_root database = let pinned = Database.current_snapshot database |> T.require_ok ~behavior in let block_title snapshot = match - Database.get_blocks snapshot [ T.authoritative_block_uuid ] + Database.get_outline_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; _ } ] -> value.block.title @@ -1011,7 +1011,7 @@ let page_tree_preserves_valid_windows_and_snapshots database = List.iter (fun (item : tree_member) -> match - Database.get_blocks snapshot [ item.block.block.uuid ] + Database.get_outline_blocks snapshot [ item.block.block.uuid ] |> T.require_ok ~behavior with | [ Present_block { value; revision } ] -> @@ -1049,7 +1049,7 @@ let page_tree_preserves_valid_windows_and_snapshots database = let first, cursor = read pinned 64 1 None in T.require (List.length first = 1 && Option.is_some cursor) "missing continuation"; let expected = - match Database.get_blocks pinned [ id 1 ] |> T.require_ok ~behavior with + match Database.get_outline_blocks pinned [ id 1 ] |> T.require_ok ~behavior with | [ Present_block { revision; _ } ] -> Database.write_precondition ~blocks:[ id 1, revision ] ~pages:[] ~scopes:[] |> T.require_ok ~behavior @@ -1241,7 +1241,7 @@ let favorites_resolve_order_filter_and_snapshot database = | _ -> Alcotest.fail "favorite target kinds were lost"); let expected = match - Database.get_blocks pinned [ T.authoritative_block_uuid ] + Database.get_outline_blocks pinned [ T.authoritative_block_uuid ] |> T.require_ok ~behavior with | [ Present_block { revision; _ } ] -> diff --git a/logseq_overlay_db/test/test_overlay_storage.ml b/logseq_overlay_db/test/test_overlay_storage.ml index 986ed5d4..a460f414 100644 --- a/logseq_overlay_db/test/test_overlay_storage.ml +++ b/logseq_overlay_db/test/test_overlay_storage.ml @@ -27,7 +27,7 @@ let block_precondition database block behavior = ~finally:(fun () -> Database.release_snapshot snapshot) (fun () -> let revision = - match Database.get_blocks snapshot [ block ] |> T.require_ok ~behavior with + match Database.get_outline_blocks snapshot [ block ] |> T.require_ok ~behavior with | [ Present_block { revision; _ } ] | [ Missing_block { revision; _ } ] -> revision | _ -> Alcotest.fail "block precondition returned the wrong cardinality" @@ -1186,7 +1186,7 @@ let pending_outbox_survives_reopen () = in let snapshot = Database.current_snapshot second |> T.require_ok ~behavior in (match - Database.get_blocks snapshot [ T.block_uuid ] |> T.require_ok ~behavior + Database.get_outline_blocks snapshot [ T.block_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; _ } ] -> T.require (value.block.title = "Inserted") "reopened pending title changed" @@ -1301,7 +1301,7 @@ let submitted_dependency_shadow_survives_reopen () = in let snapshot = Database.current_snapshot second |> T.require_ok ~behavior in (match - Database.get_blocks snapshot [ T.authoritative_block_uuid ] + Database.get_outline_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; _ } ] -> @@ -1370,7 +1370,7 @@ let no_change_receipt_survives_reopen () = let snapshot = Database.current_snapshot first |> T.require_ok ~behavior in let revision = match - Database.get_blocks snapshot [ T.missing_block_uuid ] + Database.get_outline_blocks snapshot [ T.missing_block_uuid ] |> T.require_ok ~behavior with | [ Missing_block { revision; _ } ] -> revision @@ -1494,7 +1494,7 @@ let authoritative_checkpoint_and_root_survive_reopen () = in let snapshot = Database.current_snapshot second |> T.require_ok ~behavior in (match - Database.get_blocks snapshot [ T.authoritative_block_uuid ] + Database.get_outline_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; _ } ] -> @@ -1542,7 +1542,7 @@ let outbox_persistence_failure_is_atomic () = before_version.projection_revision after_version.projection_revision) "failed outbox commit advanced projection revision"; - (match Database.get_blocks after [ T.block_uuid ] |> T.require_ok ~behavior with + (match Database.get_outline_blocks after [ T.block_uuid ] |> T.require_ok ~behavior with | [ Missing_block _ ] -> () | _ -> Alcotest.fail "failed outbox commit exposed the pending block"); Database.release_snapshot after; diff --git a/logseq_overlay_db/test/test_overlay_sync.ml b/logseq_overlay_db/test/test_overlay_sync.ml index f08a3ee6..25f28263 100644 --- a/logseq_overlay_db/test/test_overlay_sync.ml +++ b/logseq_overlay_db/test/test_overlay_sync.ml @@ -285,7 +285,7 @@ let block_precondition database block behavior = ~finally:(fun () -> Database.release_snapshot snapshot) (fun () -> let revision = - match Database.get_blocks snapshot [ block ] |> T.require_ok ~behavior with + match Database.get_outline_blocks snapshot [ block ] |> T.require_ok ~behavior with | [ Present_block { revision; _ } ] | [ Missing_block { revision; _ } ] -> revision | _ -> Alcotest.fail "block precondition returned the wrong cardinality" @@ -1076,7 +1076,7 @@ let definitive_rejection_rolls_back_once database = | No_logical_change | Logical_resync_required _ -> Alcotest.fail "single rejected insertion did not return an exact rollback"); let snapshot = Database.current_snapshot database |> T.require_ok ~behavior in - (match Database.get_blocks snapshot [ T.block_uuid ] |> T.require_ok ~behavior with + (match Database.get_outline_blocks snapshot [ T.block_uuid ] |> T.require_ok ~behavior with | [ Missing_block _ ] -> () | _ -> Alcotest.fail "rejected insertion remained visible"); Database.release_snapshot snapshot; @@ -1197,7 +1197,7 @@ let authoritative_batch_updates_the_logical_snapshot database = "visible authoritative update did not advance projection"; let after = Database.current_snapshot database |> T.require_ok ~behavior in (match - Database.get_blocks after [ T.authoritative_block_uuid ] |> T.require_ok ~behavior + Database.get_outline_blocks after [ T.authoritative_block_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; _ } ] -> T.require @@ -1212,7 +1212,7 @@ let authoritative_rebase_replans_queued_ordinary_mutation database = let snapshot = Database.current_snapshot database |> T.require_ok ~behavior in let revision = match - Database.get_blocks snapshot [ T.authoritative_block_uuid ] + Database.get_outline_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior with | [ Present_block { revision; _ } ] -> revision @@ -1256,7 +1256,7 @@ let authoritative_rebase_replans_queued_ordinary_mutation database = "queued ordinary mutation was not reported as replanned"; let snapshot = Database.current_snapshot database |> T.require_ok ~behavior in (match - Database.get_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior + Database.get_outline_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; _ } ] -> T.require @@ -1310,7 +1310,7 @@ let retained_effect_order_and_frozen_snapshot database = let current = Database.current_snapshot database |> T.require_ok ~behavior in let title snapshot = match - Database.get_blocks snapshot [ T.authoritative_block_uuid ] + Database.get_outline_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; _ } ] -> value.block.title @@ -1559,7 +1559,7 @@ let authoritative_crypto_is_correlated_and_applied database = | Ok _ -> Alcotest.fail "stale authoritative crypto was accepted"); let snapshot = Database.current_snapshot database |> T.require_ok ~behavior in (match - Database.get_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior + Database.get_outline_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; _ } ] -> T.require @@ -1744,7 +1744,7 @@ let pull_first_delete_conflict_proves_non_execution database = | _ -> Alcotest.fail "Pull-first conflict omitted its retained terminal receipt"); let snapshot = Database.current_snapshot database |> T.require_ok ~behavior in (match - Database.get_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior + Database.get_outline_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; _ } ] -> T.require @@ -2406,7 +2406,7 @@ let accepted_member_uses_intermediate_barrier_root database = | _ -> Alcotest.fail "intermediate barrier did not earn Applied receipt"); let snapshot = Database.current_snapshot database |> T.require_ok ~behavior in (match - Database.get_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior + Database.get_outline_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; _ } ] -> T.require @@ -2590,7 +2590,7 @@ let accepted_delete_rejects_missing_own_incorporation database = | Ok _ -> Alcotest.fail "accepted delete tolerated missing own incorporation"); let snapshot = Database.current_snapshot database |> T.require_ok ~behavior in (match - Database.get_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior + Database.get_outline_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior with | [ Missing_block _ ] -> () | _ -> Alcotest.fail "failed integrity preparation changed the logical delete"); @@ -2763,7 +2763,7 @@ let queued_delete_yields_to_remote_change_before_submission database = | _ -> Alcotest.fail "queued delete conflict omitted Remote_won receipt"); let snapshot = Database.current_snapshot database |> T.require_ok ~behavior in (match - Database.get_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior + Database.get_outline_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; _ } ] -> T.require @@ -2897,7 +2897,7 @@ let submitted_save_retains_required_block_shadow database = ~finally:(fun () -> Database.release_snapshot snapshot) (fun () -> (match - Database.get_blocks snapshot [ T.authoritative_block_uuid ] + Database.get_outline_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; _ } ] -> @@ -2934,7 +2934,7 @@ let submitted_save_retains_required_block_shadow database = items in (match - Database.get_blocks snapshot [ T.authoritative_block_uuid ] + Database.get_outline_blocks snapshot [ T.authoritative_block_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; revision } ] -> @@ -3094,7 +3094,7 @@ let partial_rejection_preserves_prefix_and_retries_independent_suffix database = | _ -> Alcotest.fail "independent suffix did not enter its retry group"); let snapshot = Database.current_snapshot database |> T.require_ok ~behavior in (match - Database.get_blocks snapshot [ first_uuid; second_uuid; third_uuid ] + Database.get_outline_blocks snapshot [ first_uuid; second_uuid; third_uuid ] |> T.require_ok ~behavior with | [ Present_block _; Missing_block _; Present_block _ ] -> () @@ -3202,7 +3202,7 @@ let partial_rejection_blocks_suffix_dependent_on_failed_insert database = (states = [ Blocked; Blocked ]) "failed member and dependent suffix did not become atomically Blocked"; let snapshot = Database.current_snapshot database |> T.require_ok ~behavior in - (match Database.get_blocks snapshot [ T.block_uuid ] |> T.require_ok ~behavior with + (match Database.get_outline_blocks snapshot [ T.block_uuid ] |> T.require_ok ~behavior with | [ Missing_block _ ] -> () | _ -> Alcotest.fail "dependent suffix remained in the logical projection"); Database.release_snapshot snapshot @@ -3693,7 +3693,7 @@ let stale_insert_collision_blocks_intent database = = [ Blocked ]) "replanning a conflicting insertion overwrote the authoritative UUID"; let snapshot = Database.current_snapshot database |> T.require_ok ~behavior in - (match Database.get_blocks snapshot [ T.block_uuid ] |> T.require_ok ~behavior with + (match Database.get_outline_blocks snapshot [ T.block_uuid ] |> T.require_ok ~behavior with | [ Present_block { value; _ } ] -> T.require (value.block.title = "Peer owns this UUID") diff --git a/logseq_overlay_db/test/test_performance.ml b/logseq_overlay_db/test/test_performance.ml index 7bcde0cc..460c806a 100644 --- a/logseq_overlay_db/test/test_performance.ml +++ b/logseq_overlay_db/test/test_performance.ml @@ -28,13 +28,13 @@ let manifest_has_every_numeric_gate () = let p95 = manifest |> member "p95_milliseconds" in List.iter (fun name -> ignore (require_positive_member p95 name)) - [ "get_blocks_64" + [ "get_outline_blocks_64" ; "get_structure_200" ; "get_journals_200" ; "single_block_change_classification" ; "replan_and_diff_128" ; "reopen_1024" - ; "maximum_get_blocks_64" + ; "maximum_get_outline_blocks_64" ; "maximum_get_structure_200" ; "maximum_get_journals_200" ; "maximum_rebase_and_diff_4096" diff --git a/logseq_overlay_db/test/test_support.ml b/logseq_overlay_db/test/test_support.ml index 5d6c44bb..68f92a89 100644 --- a/logseq_overlay_db/test/test_support.ml +++ b/logseq_overlay_db/test/test_support.ml @@ -461,7 +461,7 @@ let delete_precondition database ~block ~behavior = ~finally:(fun () -> Database.release_snapshot snapshot) (fun () -> let block_revision = - match Database.get_blocks snapshot [ block ] |> require_ok ~behavior with + match Database.get_outline_blocks snapshot [ block ] |> require_ok ~behavior with | [ Present_block { revision; _ } ] -> revision | _ -> Alcotest.fail "fixture delete target is missing" in diff --git a/logseq_overlay_db/tool/fixture_generator.ml b/logseq_overlay_db/tool/fixture_generator.ml index 5612592e..c317bbae 100644 --- a/logseq_overlay_db/tool/fixture_generator.ml +++ b/logseq_overlay_db/tool/fixture_generator.ml @@ -264,7 +264,7 @@ let seed_outbox ~database_path ~block_count ~count = Fun.protect ~finally:(fun () -> Database.release_snapshot snapshot) (fun () -> - match Database.get_blocks snapshot [ block ] |> Result.get_ok with + match Database.get_outline_blocks snapshot [ block ] |> Result.get_ok with | [ Types.Present_block { revision; _ } ] -> revision | _ -> failwith "performance outbox block is missing") in diff --git a/logseq_overlay_db/tool/page_tree_probe.ml b/logseq_overlay_db/tool/page_tree_probe.ml index 34e716ec..aabd2175 100644 --- a/logseq_overlay_db/tool/page_tree_probe.ml +++ b/logseq_overlay_db/tool/page_tree_probe.ml @@ -147,7 +147,7 @@ let measured name snapshot page depth limit cursor = require (calls "seek:Eavt:entity:*" = 0) "unrelated entity range scan"); List.iter (fun (item : T.tree_member) -> - match D.get_blocks snapshot [ item.block.block.uuid ] |> ok "point" with + match D.get_outline_blocks snapshot [ item.block.block.uuid ] |> ok "point" with | [ T.Present_block { value; revision } ] -> require (value = item.block && revision = item.revision) "point equivalence" | _ -> failwith "missing selected block") @@ -377,7 +377,7 @@ let audit_local support graph_id = snapshot (fun pinned -> let expected = let revision = - match D.get_blocks pinned [ root ] |> ok "root" with + match D.get_outline_blocks pinned [ root ] |> ok "root" with | [ T.Present_block { revision; _ } ] -> revision | _ -> failwith "root missing" in diff --git a/logseq_overlay_db/tool/performance_benchmark.ml b/logseq_overlay_db/tool/performance_benchmark.ml index 833b0d57..853df708 100644 --- a/logseq_overlay_db/tool/performance_benchmark.ml +++ b/logseq_overlay_db/tool/performance_benchmark.ml @@ -98,9 +98,9 @@ let measure_point_read database block_count = with_snapshot database (fun snapshot -> measure (fun () -> let results = - Database.get_blocks snapshot requested |> get_ok "measured get_blocks" + Database.get_outline_blocks snapshot requested |> get_ok "measured get_outline_blocks" in - if List.length results <> 64 then fail "measured get_blocks cardinality mismatch")) + if List.length results <> 64 then fail "measured get_outline_blocks cardinality mismatch")) ;; let performance_page = @@ -234,7 +234,7 @@ let measure_single_block_classification database cursor ordinal = let delete_precondition database block = with_snapshot database (fun snapshot -> let block_revision, parent = - match Database.get_blocks snapshot [ block ] |> get_ok "delete block revision" with + match Database.get_outline_blocks snapshot [ block ] |> get_ok "delete block revision" with | [ Types.Present_block { value; revision } ] -> revision, value.block.parent | _ -> fail "delete performance target is missing" in @@ -300,7 +300,7 @@ type sample = ; wall_time_samples : float list ; allocation_samples : float list ; rss_samples : int list - ; get_blocks_64 : measurement list + ; get_outline_blocks_64 : measurement list ; get_structure_200 : measurement list ; get_journals_200 : measurement list ; single_block_change_classification : measurement list @@ -383,7 +383,7 @@ let benchmark ~open_database ~database_path ~block_count required_sizes = ; wall_time_samples = values (fun value -> value.elapsed_ms) ; allocation_samples = values (fun value -> value.allocation_bytes) ; rss_samples = values (fun value -> value.rss_bytes) - ; get_blocks_64 = point_samples + ; get_outline_blocks_64 = point_samples ; get_structure_200 = structure_samples ; get_journals_200 = journal_samples ; single_block_change_classification = single_block_samples diff --git a/logseq_overlay_db/tool/read_reuse_probe.ml b/logseq_overlay_db/tool/read_reuse_probe.ml index d8406946..d194797e 100644 --- a/logseq_overlay_db/tool/read_reuse_probe.ml +++ b/logseq_overlay_db/tool/read_reuse_probe.ml @@ -114,7 +114,7 @@ let empty () = D.write_precondition ~blocks:[] ~pages:[] ~scopes:[] |> ok "empty let block_pre db b = snap db (fun s -> - match D.get_blocks s [ b ] |> ok "get revision" with + match D.get_outline_blocks s [ b ] |> ok "get revision" with | [ T.Present_block { revision; _ } ] -> D.write_precondition ~blocks:[ b, revision ] ~pages:[] ~scopes:[] |> ok "pre" | _ -> failwith "missing pre block") @@ -374,7 +374,7 @@ let shadow_insert db = metadata_gate "shadow insert"; gate "shadow insertion scans once" (calls "datoms:Avet:*:block/parent:value" = 1); snap db (fun s -> - match D.get_blocks s [ target ] |> ok "shadow visibility" with + match D.get_outline_blocks s [ target ] |> ok "shadow visibility" with | [ T.Present_block { value; _ } ] -> require (value.block.title = "Shadow title") "lost shadow" | _ -> failwith "authoritative miss hid dependency shadow") @@ -413,7 +413,7 @@ let deletion_comments db = (T.Delete_blocks { mutation_id = mid 30; root })); block_gate "comments deletion"; snap db (fun s -> - match D.get_blocks s [ root; area; child; source ] |> ok "comments footprint" with + match D.get_outline_blocks s [ root; area; child; source ] |> ok "comments footprint" with | [ T.Missing_block _ ; T.Missing_block _ ; T.Missing_block _ @@ -456,7 +456,7 @@ let structural_eligibility db = metadata_gate "structural eligibility"; gate "structural eligibility one scan" (calls "datoms:Avet:*:block/parent:value" = 1); snap db (fun s -> - match D.get_blocks s [ mid 603; mid 601 ] |> ok "eligibility orders" with + match D.get_outline_blocks s [ mid 603; mid 601 ] |> ok "eligibility orders" with | [ T.Present_block { value; _ }; T.Missing_block _ ] -> require (value.block.order = "b01") "structural eligibility tightened" | _ -> failwith "incomplete sibling hydration") @@ -464,19 +464,19 @@ let structural_eligibility db = let selected db = snap db (fun s -> - ignore (run "get_blocks/empty" (fun () -> D.get_blocks s [])); + ignore (run "get_outline_blocks/empty" (fun () -> D.get_outline_blocks s [])); gate "empty block datoms" (A.logical_counts () = []); List.iter (fun (name, ids) -> - let first = run name (fun () -> D.get_blocks s ids) in + let first = run name (fun () -> D.get_outline_blocks s ids) in block_gate name; let counts = A.logical_counts () in - let second = run (name ^ "-repeat") (fun () -> D.get_blocks s ids) in + let second = run (name ^ "-repeat") (fun () -> D.get_outline_blocks s ids) in require (first = second) "repeat block results"; gate "block contexts must not survive calls" (counts = A.logical_counts ())) - [ "get_blocks/1", [ block ] - ; "get_blocks/64", List.init 64 F.block_uuid - ; "get_blocks/duplicates", [ block; mid 9000; block; mid 9000 ] + [ "get_outline_blocks/1", [ block ] + ; "get_outline_blocks/64", List.init 64 F.block_uuid + ; "get_outline_blocks/duplicates", [ block; mid 9000; block; mid 9000 ] ]; get_pages db @@ -484,7 +484,7 @@ let selected db = (List.init 64 (fun n -> uuid (Printf.sprintf "10000000-0000-4000-8001-%012d" n)))); let old = D.current_snapshot db |> ok "old snapshot" in let root = mid 200 in - ignore (run "get_blocks/before-insert" (fun () -> D.get_blocks old [ root; root ])); + ignore (run "get_outline_blocks/before-insert" (fun () -> D.get_outline_blocks old [ root; root ])); let tree = T. { uuid = root @@ -505,10 +505,10 @@ let selected db = gate "insert one sibling enumeration" (calls "datoms:Avet:*:block/parent:value" = 1); gate "insert no full sibling payload scan" (calls "seek:Eavt:entity:*" = 0); snap db (fun s -> - match D.get_blocks s [ root ] |> ok "insert visibility" with + match D.get_outline_blocks s [ root ] |> ok "insert visibility" with | [ T.Present_block _ ] -> () | _ -> failwith "cached missing escaped"); - (match D.get_blocks old [ root ] |> ok "old snapshot" with + (match D.get_outline_blocks old [ root ] |> ok "old snapshot" with | [ T.Missing_block _ ] -> () | _ -> failwith "old snapshot changed"); let expected = block_pre db root in @@ -652,7 +652,7 @@ let selected db = (fun () -> D.get_pages s ids |> ok "concurrent right") in require (left = pages && right = pages) "concurrent results"; - match D.get_blocks s [ F.block_uuid 1 ] |> ok "rendered title" with + match D.get_outline_blocks s [ F.block_uuid 1 ] |> ok "rendered title" with | [ T.Present_block { value; _ } ] -> require (value.rendered_page_title = "Changed page title") @@ -666,12 +666,12 @@ let selected db = deletion_comments db; structural_eligibility db; D.release_snapshot old; - (match D.get_blocks old [] with + (match D.get_outline_blocks old [] with | Error T.Snapshot_released -> () | _ -> failwith "empty released snapshot"); let s = D.current_snapshot db |> ok "closing snapshot" in D.close db |> ok "close"; - (match D.get_blocks s [] with + (match D.get_outline_blocks s [] with | Error T.Database_closed | Error T.Snapshot_generation_invalidated -> () | _ -> failwith "empty closed database"); D.release_snapshot s diff --git a/logseq_overlay_db/tool/test_performance.sh b/logseq_overlay_db/tool/test_performance.sh index 281f6901..45cb969e 100755 --- a/logseq_overlay_db/tool/test_performance.sh +++ b/logseq_overlay_db/tool/test_performance.sh @@ -37,7 +37,7 @@ jq -e ' (.wall_time_samples | length) >= 5 and (.allocation_samples | length) >= 5 and (.rss_samples | length) >= 5 and - (.get_blocks_64 | length) >= 5 and + (.get_outline_blocks_64 | length) >= 5 and (.get_structure_200 | length) >= 5 and (.get_journals_200 | length) >= 5 and (.reopen | length) >= 5 and @@ -45,11 +45,11 @@ jq -e ' all(.allocation_samples[]; . > 0) and all(.rss_samples[]; . > 0)) and all(.samples[] | select(.active_outbox_records < 4096); - all(.get_blocks_64[].wall_time_ms; . < 20) and + all(.get_outline_blocks_64[].wall_time_ms; . < 20) and all(.get_structure_200[].wall_time_ms; . < 20) and all(.get_journals_200[].wall_time_ms; . < 25)) and all(.samples[] | select(.active_outbox_records == 4096); - all(.get_blocks_64[].wall_time_ms; . < 80) and + all(.get_outline_blocks_64[].wall_time_ms; . < 80) and all(.get_structure_200[].wall_time_ms; . < 80) and all(.get_journals_200[].wall_time_ms; . < 100)) and all(.samples[] | select(.active_outbox_records == 0); @@ -66,7 +66,7 @@ jq -e ' all(.replan_and_diff[].wall_time_ms; . < 3200) and all(.delete_conflict[].wall_time_ms; . < 3200) and all(.reopen[].wall_time_ms; . < 500)) and - all(.samples[] | .get_blocks_64[], .get_structure_200[], .get_journals_200[]; + all(.samples[] | .get_outline_blocks_64[], .get_structure_200[], .get_journals_200[]; .allocation_bytes < 33554432) and all(.samples[]; ([.rss_samples[]] | max) diff --git a/swift/JournalChrome.swift b/swift/JournalChrome.swift index 38a4a8f0..9b543bd8 100644 --- a/swift/JournalChrome.swift +++ b/swift/JournalChrome.swift @@ -55,7 +55,6 @@ struct JournalSheetDismissButton: SwiftUI.View { let text: String let styles: [String] let url: String? - let reference: String? } private static func attributed(_ runs: [TitleRun]?, fallback: String) -> AttributedString { @@ -73,22 +72,12 @@ struct JournalSheetDismissButton: SwiftUI.View { if run.styles.contains("highlight") { fragment.backgroundColor = .yellow.opacity(0.25) } if let target = run.url.flatMap(URL.init(string:)) { fragment.link = target - } else if let id = run.reference { - fragment.link = URL(string: "logseq-journal://block/" + id) } result.append(fragment) } return result } - private static func openTitleURL(_ url: URL, context: LUIAppleExtensionViewContext, runs: [TitleRun]?) -> OpenURLAction.Result { - guard url.scheme == "logseq-journal" else { return .systemAction } - let id = String(url.path.dropFirst()) - guard url.host == "block", runs?.contains(where: { $0.reference == id }) == true, - let payload = try? JSONEncoder().encode(["reference": id]) else { return .discarded } - return JournalExtensions.emit(context: context, payload: payload) ? .handled : .discarded - } - private struct DetailText: SwiftUI.View { let text: AttributedString let id: String @@ -289,9 +278,6 @@ struct JournalSheetDismissButton: SwiftUI.View { if let text = properties?.text, let maxLines = properties?.maxLines, maxLines > 0, context.childIDs.isEmpty { Text(JournalChrome.attributed(properties?.runs, fallback: text)) - .environment(\.openURL, OpenURLAction { url in - JournalChrome.openTitleURL(url, context: context, runs: properties?.runs) - }) .font(properties?.level == 1 ? .subheadline : .body) .foregroundStyle(properties?.level == 1 ? .secondary : .primary) .multilineTextAlignment(.leading) @@ -304,9 +290,6 @@ struct JournalSheetDismissButton: SwiftUI.View { if let properties, let text = properties.text, let id = properties.id, let level = properties.level, context.childIDs.isEmpty { DetailText(text: JournalChrome.attributed(properties.runs, fallback: text), id: id, level: level) - .environment(\.openURL, OpenURLAction { url in - JournalChrome.openTitleURL(url, context: context, runs: properties.runs) - }) } case .detailRow: if let level = properties?.level, context.childIDs.count == 1 { diff --git a/test/application_view_test.ml b/test/application_view_test.ml index fbe56306..84004bc7 100644 --- a/test/application_view_test.ml +++ b/test/application_view_test.ml @@ -802,7 +802,7 @@ let run_favorites_native_visibility | V2_ack_changes _ -> Atomic.set changes_acknowledged true; V2_changes_acknowledged { generation = "g"; through = "p2" } - | V2_get_block { block; _ } -> + | V2_get_outline_block { block; _ } -> V2_block_outcome (match List.find_opt @@ -831,7 +831,7 @@ let run_favorites_native_visibility | Some n -> V2_present_block { value = timeline_block n; revision = "root" } | None -> V2_missing_block { uuid = block; revision = "p" })) - | V2_get_block_summary { block; _ } when check_capture_imports -> + | V2_get_outline_block_summary { block; _ } when check_capture_imports -> let root = Atomic.get capture_root |> Option.get in let page = Atomic.get capture_page |> Option.get in Alcotest.(check string) @@ -3677,7 +3677,7 @@ let test_reference_outer_terminals cancel = | Import_asset _ -> Error "unused import" | Graph_request request -> (match request.command with - | P.V2_get_block { block = target; _ } + | P.V2_get_outline_block { block = target; _ } when not (Array.exists (G.Uuid.equal target) targets) -> completed request @@ -3685,7 +3685,8 @@ let test_reference_outer_terminals cancel = (if G.Uuid.equal target block.uuid then V2_present_block { value = record block; revision = "root" } else V2_missing_block { uuid = target; revision = "p" })) - | P.V2_get_block { block = target; _ } -> + | P.V2_get_page { page = target; _ } + when Array.exists (G.Uuid.equal target) targets -> let index = Array.to_list targets |> List.mapi (fun i id -> i, id) @@ -3709,14 +3710,9 @@ let test_reference_outer_terminals cancel = Atomic.incr late_returns; completed request - (V2_block_outcome - (V2_present_block - { value = - record - { block with - uuid = target - ; title = "Late cancelled target" - } + (V2_page_outcome + (V2_present_page + { page = { page with uuid = target; title = "Late cancelled target"; kind = Ordinary_page } ; revision = "old" }))) else ( @@ -3725,14 +3721,9 @@ let test_reference_outer_terminals cancel = else completed request - (V2_block_outcome - (V2_present_block - { value = - record - { block with - uuid = target - ; title = Printf.sprintf "Fresh target %d" index - } + (V2_page_outcome + (V2_present_page + { page = { page with uuid = target; title = Printf.sprintf "Fresh target %d" index; kind = Ordinary_page } ; revision = "fresh" })) | V2_graph_info -> diff --git a/test/journal_asset_policy_test.ml b/test/journal_asset_policy_test.ml index 81d4de32..ca553e91 100644 --- a/test/journal_asset_policy_test.ml +++ b/test/journal_asset_policy_test.ml @@ -546,7 +546,7 @@ let raw r n (block : G.block) = Wire. { api_version = 2 ; request_id = uuid n - ; command = V2_get_block { block = block.uuid; revision = None } + ; command = V2_get_outline_block { block = block.uuid; revision = None } } in Runtime.observe_request r request; @@ -658,7 +658,7 @@ let runtime_fixture } ; revision = "r" })) - | V2_get_block { block = target; _ } -> + | V2_get_outline_block { block = target; _ } -> respond r request.request_id @@ -707,7 +707,7 @@ let runtime_unconfigured_changes after_shutdown () = Wire. { api_version = 2 ; request_id = uuid (600 + n) - ; command = V2_get_block { block = uuid n; revision = None } + ; command = V2_get_outline_block { block = uuid n; revision = None } } in Runtime.observe_request r request; @@ -754,7 +754,7 @@ let runtime_unknown_unrelated () = check (!rounds <= 4) "unknown unrelated holder lookup must terminate"; match Queue.take sent with | Service.Graph_request - ({ command = Wire.V2_get_block { block = target; _ }; _ } as request) -> + ({ command = Wire.V2_get_outline_block { block = target; _ }; _ } as request) -> respond r request.request_id @@ -855,7 +855,7 @@ let runtime_late_fact () = Wire. { api_version = 2 ; request_id = uuid 601 - ; command = V2_get_block { block = uuid 2; revision = None } + ; command = V2_get_outline_block { block = uuid 2; revision = None } } in Runtime.observe_request r late_request; @@ -931,7 +931,7 @@ let runtime_real_capture_sibling () = incr rounds; check (!rounds <= 2) "Capture dependency point reads remain bounded"; match Queue.take sent with - | Service.Graph_request ({ command = V2_get_block _; _ } as request) -> + | Service.Graph_request ({ command = V2_get_outline_block _; _ } as request) -> respond r request.request_id (present (block 7 1 1 [])) | Service.Graph_request ({ command = V2_get_page { page; _ }; _ } as request) -> respond @@ -972,7 +972,7 @@ let runtime_registry_capacity () = Wire. { api_version = 2 ; request_id = uuid n - ; command = V2_get_block { block = uuid 99; revision = None } + ; command = V2_get_outline_block { block = uuid 99; revision = None } } done; check @@ -1065,7 +1065,7 @@ let runtime_hidden_holder_fanout () = while not (Queue.is_empty sent) do match Queue.take sent with | Service.Graph_request - ({ command = V2_get_block { block = target; _ }; _ } as request) -> + ({ command = V2_get_outline_block { block = target; _ }; _ } as request) -> respond r request.request_id @@ -1192,7 +1192,7 @@ let runtime_unknown_old_parent () = while not (Queue.is_empty sent) do match Queue.take sent with | Service.Graph_request - ({ command = V2_get_block { block = target; _ }; _ } as request) -> + ({ command = V2_get_outline_block { block = target; _ }; _ } as request) -> respond r request.request_id diff --git a/test/journal_graph_runtime_locality_test.ml b/test/journal_graph_runtime_locality_test.ml index b6aa9e10..da17ad47 100644 --- a/test/journal_graph_runtime_locality_test.ml +++ b/test/journal_graph_runtime_locality_test.ml @@ -318,7 +318,7 @@ let test_large_changes_are_bounded fail_one = | (request : Protocol.request) :: rest -> let id = match request.command with - | V2_get_block { block; _ } -> block + | V2_get_outline_block { block; _ } -> block | _ -> Alcotest.fail "unexpected background request" in let outcome = @@ -478,7 +478,7 @@ let test_resync_and_dependent_reads_share_hydration_bound () = ; items = [] ; next_cursor = None } ) - | V2_get_block { block = id; _ } -> + | V2_get_outline_block { block = id; _ } -> let value = List.find (fun (value : Protocol.v2_block_record) -> @@ -597,7 +597,7 @@ let test_one_block_change_hydrates_only_that_block () = let output = changed runtime (window ~blocks:[ block_uuid; unrelated_uuid ] ()) in require_ack output; match hydration_requests output with - | [ { Protocol.command = V2_get_block { block; _ }; _ } ] -> + | [ { Protocol.command = V2_get_outline_block { block; _ }; _ } ] -> Alcotest.(check string) "hydrated block" (Graph.Uuid.to_string block_uuid) @@ -1230,7 +1230,7 @@ let summary_response request page lookup children next_cursor revision = let check_target_summary (request : Protocol.request) cursor = match request.command with - | V2_get_block_summary { block; limit; cursor = actual_cursor } -> + | V2_get_outline_block_summary { block; limit; cursor = actual_cursor } -> Alcotest.(check bool) "target identity" true (Graph.Uuid.equal block block_uuid); Alcotest.(check int) "bounded target children" Protocol.default_page_size limit; Alcotest.(check bool) "target children cursor" true (actual_cursor = cursor) @@ -2373,12 +2373,23 @@ let reference_updates output = output.Runtime.responses ;; -let check_target_request output = - let request = only "shared reference point read" (hydration_requests output) in +let outline_after_page_miss runtime request = + match request.Protocol.command with + | V2_get_page {page;_} -> + Runtime.receive runtime (response request (Protocol.V2_page_outcome + (V2_missing_page {uuid=page;revision="page-absent"}))) + |> fun output -> only "outline lookup after page absence" output.requests + | V2_get_outline_block _ -> request + | _ -> Alcotest.fail "not a reference lookup" +;; + +let check_target_request runtime output = + let request = only "shared reference point read" (hydration_requests output) + |> outline_after_page_miss runtime in Alcotest.(check bool) "reads only target UUID" true - (request.Protocol.command = V2_get_block { block = target_uuid; revision = None }); + (request.Protocol.command = V2_get_outline_block { block = target_uuid; revision = None }); request ;; @@ -2386,7 +2397,7 @@ let test_reference_hydration_and_changes () = let runtime = Runtime.create () in let request = reference_feed runtime (reference ^ " " ^ reference ^ " [[page]]") - |> check_target_request + |> check_target_request runtime in let resolved = Runtime.receive runtime (target_response request "Referenced content") in Alcotest.(check (option string)) @@ -2399,7 +2410,7 @@ let test_reference_hydration_and_changes () = (List.length resolved.requests); let changed_request = changed runtime (window ~blocks:[ target_uuid; unrelated_uuid ] ()) - |> check_target_request + |> check_target_request runtime in let updated = Runtime.receive runtime (target_response changed_request "Edited content") @@ -2409,7 +2420,7 @@ let test_reference_hydration_and_changes () = (Some "Edited content") (List.assoc_opt target_id (reference_updates updated) |> Option.join); let deleted_request = - changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request + changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request runtime in let deleted = Runtime.receive @@ -2424,7 +2435,7 @@ let test_reference_hydration_and_changes () = true (List.assoc_opt target_id (reference_updates deleted) = Some None); let recreated_request = - changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request + changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request runtime in ignore (Runtime.receive runtime (target_response recreated_request "Recreated")); let resync = @@ -2442,7 +2453,7 @@ let test_reference_hydration_and_changes () = true (List.exists (fun (request : Protocol.request) -> - request.command = V2_get_block { block = target_uuid; revision = None }) + request.command = V2_get_page { page = target_uuid; revision = None }) resync.requests); Runtime.reset runtime; Alcotest.(check int) @@ -2454,7 +2465,7 @@ let test_reference_hydration_and_changes () = let test_reference_cycle_and_failure () = let runtime = Runtime.create () in - let request = reference_feed runtime reference |> check_target_request in + let request = reference_feed runtime reference |> check_target_request runtime in let output = Runtime.receive runtime @@ -2465,7 +2476,7 @@ let test_reference_cycle_and_failure () = 0 (List.length output.requests); let changed_request = - changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request + changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request runtime in let failed = Runtime.receive @@ -2479,14 +2490,14 @@ let test_reference_cycle_and_failure () = "failed target clears stale content" true (List.assoc_opt target_id (reference_updates failed) = Some None); - ignore (changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request) + ignore (changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request runtime) ;; let test_reference_invalidation_during_read () = List.iter (fun fail -> let runtime = Runtime.create () in - let request = reference_feed runtime reference |> check_target_request in + let request = reference_feed runtime reference |> check_target_request runtime in let changed = changed runtime (window ~blocks:[ target_uuid ] ()) in Alcotest.(check int) "pending target invalidation coalesces" @@ -2507,7 +2518,7 @@ let test_reference_invalidation_during_read () = "stale completion is not published" true (not (List.mem (target_id, Some "Stale") (reference_updates stale))); - let fresh = check_target_request stale in + let fresh = check_target_request runtime stale in let result = Runtime.receive runtime (target_response fresh "Fresh") in Alcotest.(check (option string)) "newest content wins" @@ -2535,7 +2546,7 @@ let test_abandoned_reference_can_refresh () = List.iter (fun fail -> let runtime = Runtime.create () in - let request = reference_feed runtime reference |> check_target_request in + let request = reference_feed runtime reference |> check_target_request runtime in if fail then ignore (Runtime.fail_request runtime request ~message:"Worker interrupted") else Runtime.abandon runtime request; @@ -2545,7 +2556,7 @@ let test_abandoned_reference_can_refresh () = 0 (List.length late.responses + List.length late.requests); let fresh = - changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request + changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request runtime in ignore (Runtime.receive runtime (target_response fresh "Recovered")); let duplicate = Runtime.fail_request runtime request ~message:"Late terminal" in @@ -2562,7 +2573,7 @@ let test_shorter_reference_path_discovers_nested_targets () = let link n = "[[" ^ id n ^ "]]" in let output = ref (reference_feed runtime (link 1)) in for n = 1 to Journal_model.maximum_reference_depth do - let request = only "chain point read" !output.requests in + let request = only "chain point read" !output.requests |> outline_after_page_miss runtime in output := Runtime.receive runtime @@ -2584,8 +2595,8 @@ let test_shorter_reference_path_discovers_nested_targets () = "reads next nested target" true (request.Protocol.command - = V2_get_block - { block = uuid (id (Journal_model.maximum_reference_depth + 1)) + = V2_get_page + { page = uuid (id (Journal_model.maximum_reference_depth + 1)) ; revision = None }) ;; @@ -2609,8 +2620,8 @@ let test_reference_burst_is_bounded () = runtime (response request - (Protocol.V2_block_outcome - (V2_missing_block { uuid = target_uuid; revision = "missing" }))) + (Protocol.V2_page_outcome + (V2_missing_page { uuid = (match request.command with V2_get_page {page;_}->page | _->assert false); revision = "missing" }))) in Alcotest.(check int) "completion drains one queued lookup" @@ -2672,7 +2683,7 @@ let test_stale_feed_cannot_replace_reference_source () = let current = begin_reference_tree runtime 9L in ignore (Runtime.receive runtime (reference_tree_response current fresh_title)); let changed_read = - changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request + changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request runtime in let stale = Runtime.receive runtime (reference_tree_response old "Stale") in Alcotest.(check bool) @@ -2683,7 +2694,7 @@ let test_stale_feed_cannot_replace_reference_source () = ignore fresh; let old = begin_reference_tree runtime 10L in let newer = - changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request + changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request runtime in ignore (Runtime.receive runtime (target_response newer fresh_title)); let stale = Runtime.receive runtime (reference_tree_response old "Rollback") in @@ -2700,7 +2711,7 @@ let test_shared_change_failure_invalidates_reference () = let initial = begin_reference_tree runtime 7L in ignore (Runtime.receive runtime (reference_tree_response initial "Old")); let changed_read = - changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request + changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request runtime in let failed = Runtime.receive @@ -2714,9 +2725,9 @@ let test_shared_change_failure_invalidates_reference () = "shared failure removes old source" true (List.mem (target_id, None) (reference_updates failed)); - let old = changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request in + let old = changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request runtime in let newer = - changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request + changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request runtime in ignore (Runtime.receive runtime (target_response newer "Fresh")); let failed = @@ -2731,7 +2742,7 @@ let test_shared_change_failure_invalidates_reference () = false (List.mem (target_id, None) (reference_updates failed)); let retry = - changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request + changed runtime (window ~blocks:[ target_uuid ] ()) |> check_target_request runtime in ignore (Runtime.receive runtime (target_response retry "Recovered")) ;; @@ -2925,7 +2936,7 @@ let copy_start root = "Copy reads selected root only" true (match snd (copy_read effects) with - | Protocol.V2_get_block { block; _ } -> Graph.Uuid.equal block root + | Protocol.V2_get_outline_block { block; _ } -> Graph.Uuid.equal block root | _ -> false); state, effects ;; @@ -3245,7 +3256,7 @@ let test_ordinary_detail_point_change child_changed = let output = changed runtime (window ~blocks:[ value.block.uuid ] ()) in let request = only "ordinary detail changed identity" (hydration_requests output) in (match request.command with - | V2_get_block { block; _ } -> + | V2_get_outline_block { block; _ } -> Alcotest.(check bool) "only changed identity" true @@ -3295,7 +3306,7 @@ let test_detail_children_uses_latest_root () = List.find (fun (request : Protocol.request) -> match request.command with - | V2_get_block _ -> true + | V2_get_outline_block _ -> true | _ -> false) (hydration_requests output) in @@ -3367,7 +3378,7 @@ let test_detail_resync_reads_latest_root () = (List.exists (fun (request : Protocol.request) -> match request.command with - | V2_get_block { block; _ } -> Graph.Uuid.equal block block_uuid + | V2_get_outline_block { block; _ } -> Graph.Uuid.equal block block_uuid | _ -> false) output.requests) ;; @@ -3444,7 +3455,7 @@ let seed_favorite_dependencies ] ; next_cursor = None } - | V2_get_block { block = id; _ } -> + | V2_get_outline_block { block = id; _ } -> visited := Graph.Uuid.to_string id :: !visited; let value = if Graph.Uuid.equal id block_uuid @@ -4611,9 +4622,10 @@ let test_detail_property_reference_reads_are_deduplicated () = let children=Protocol.V2_children_outcome {parent=block_uuid;revision_scope=V2_children_revision block_uuid;scope_revision="children-1";items=[];next_cursor=None} in let output=Runtime.receive runtime (response child children) in - let reference=only "one deduplicated closed property reference" output.requests in + let reference=only "one deduplicated closed property reference" output.requests + |> outline_after_page_miss runtime in (match reference.command with - |Protocol.V2_get_block {block;_}->Alcotest.(check string) "closed value UUID" + |Protocol.V2_get_outline_block {block;_}->Alcotest.(check string) "closed value UUID" (Graph.Uuid.to_string unrelated_uuid) (Graph.Uuid.to_string block) |_ ->Alcotest.fail "property value must use the existing bounded block read"); let closed={record with block={block with uuid=unrelated_uuid;title="Yes";properties=[]}} in @@ -4643,7 +4655,7 @@ let test_changed_journal_detail_discovers_property_reference () = {value={record with block={block with properties=[property]}}; revision="block-2"}))) in let reference = only "new property reference after sync" output.requests in (match reference.command with - | Protocol.V2_get_block {block;_} -> Alcotest.(check string) "new closed value" + | Protocol.V2_get_page {page=block;_} -> Alcotest.(check string) "new closed value" (Graph.Uuid.to_string unrelated_uuid) (Graph.Uuid.to_string block) | _ -> Alcotest.fail "sync did not use the owned reference reader") ;; @@ -4720,7 +4732,7 @@ let test_materialized_detail_leaf_discovers_property_reference () = in let read = only "displayed leaf property demand" output.requests in match read.command with - | Protocol.V2_get_block { block; _ } -> + | Protocol.V2_get_page { page=block; _ } -> Alcotest.(check string) "leaf closed value UUID" (Graph.Uuid.to_string unrelated_uuid) @@ -4820,7 +4832,7 @@ let test_detail_child_property_reference_without_child_load () = in let request = only "returned child property demand" output.requests in match request.command with - | Protocol.V2_get_block { block; _ } -> + | Protocol.V2_get_page { page=block; _ } -> Alcotest.(check bool) "child property target" true @@ -4965,8 +4977,9 @@ let test_page_property_promotes_prior_block_reference () = let root : Protocol.v2_tree_member = {value={record with block={block with title}};revision="root-1";depth=0;parent=page_uuid} in let first=Runtime.receive runtime (tree_page tree [root] None) - |> fun o -> only "initial generic reference" o.requests in - (match first.command with Protocol.V2_get_block _ -> () | _ -> Alcotest.fail "expected generic block lookup"); + |> fun o -> only "initial generic page reference" o.requests + |> outline_after_page_miss runtime in + (match first.command with Protocol.V2_get_outline_block _ -> () | _ -> Alcotest.fail "expected generic block lookup"); if not pending then ignore (Runtime.receive runtime (response first (Protocol.V2_block_outcome (V2_missing_block {uuid=unrelated_uuid;revision="missing-1"})))); let property : Graph.property_summary = @@ -5053,10 +5066,347 @@ let test_changed_tree_all_property_slice_continues () = output.responses) ;; +(* The runtime owns reference discovery and its private cache; exercise its + public requests/completions instead of injecting an application cache result. *) +let expect_reference_page output id = + let request = only "one deduplicated page-first request" (hydration_requests output) in + Alcotest.(check bool) "page lookup precedes outline lookup" true + (request.Protocol.command = V2_get_page { page = id; revision = None }); + request +;; + +let missing_reference_page runtime request id = + Runtime.receive runtime + (response request (Protocol.V2_page_outcome + (V2_missing_page { uuid = id; revision = "page-absent" }))) +;; + +let expect_reference_outline output id = + let request = only "one outline fallback after explicit page miss" (hydration_requests output) in + Alcotest.(check bool) "outline target" true + (request.Protocol.command = V2_get_outline_block { block = id; revision = None }); + request +;; + +let reference_page_response ?(recycled = false) request kind title = + response request (Protocol.V2_page_outcome + (V2_present_page { page = { page with uuid = target_uuid; kind; title; recycled } + ; revision = "resolved-page" })) +;; + +let test_reference_page_first_resolves_pages () = + List.iter (fun kind -> + let runtime = Runtime.create () in + let request = reference_feed runtime (reference ^ " " ^ reference) + |> fun output -> expect_reference_page output target_uuid in + let resolved = Runtime.receive runtime + (reference_page_response request kind "2026-09-01") in + Alcotest.(check (option string)) "actual page title is published" + (Some "2026-09-01") (List.assoc_opt target_id (reference_updates resolved) |> Option.join); + Alcotest.(check int) "present page never queries outline" 0 (List.length resolved.requests); + let cached = reference_feed runtime reference in + Alcotest.(check int) "positive page source is reused" 0 (List.length cached.requests); + let refresh = changed runtime (window ~pages:[target_uuid] ()) + |> fun output -> expect_reference_page output target_uuid in + let updated = Runtime.receive runtime (reference_page_response refresh kind "Edited page") in + Alcotest.(check (option string)) "page change invalidates typed cache" + (Some "Edited page") (List.assoc_opt target_id (reference_updates updated) |> Option.join)) + [Graph.Journal_page {journal_day=20260901}; Ordinary_page] +;; + +let test_reference_page_miss_then_outline () = + let runtime = Runtime.create () in + let page_read = reference_feed runtime reference + |> fun output -> expect_reference_page output target_uuid in + let fallback = missing_reference_page runtime page_read target_uuid in + Alcotest.(check bool) "page miss is not a negative target result" false + (List.mem (target_id,None) (reference_updates fallback)); + let outline = expect_reference_outline fallback target_uuid in + Alcotest.(check int) "duplicate source joins pending fallback" 0 + (List.length (reference_feed runtime (reference ^ " " ^ reference)).requests); + let resolved = Runtime.receive runtime (target_response outline "Outline body") in + Alcotest.(check (option string)) "outline source resolves after page absence" + (Some "Outline body") (List.assoc_opt target_id (reference_updates resolved) |> Option.join); + Alcotest.(check int) "positive outline source is reused" 0 + (List.length (reference_feed runtime reference).requests) +;; + +let test_reference_negative_cache_requires_both_misses () = + let runtime = Runtime.create () in + let page_read = reference_feed runtime reference + |> fun output -> expect_reference_page output target_uuid in + let fallback = missing_reference_page runtime page_read target_uuid in + let outline = expect_reference_outline fallback target_uuid in + let missing = Runtime.receive runtime (response outline (Protocol.V2_block_outcome + (V2_missing_block {uuid=target_uuid;revision="outline-absent"}))) in + Alcotest.(check bool) "both misses publish unavailable source" true + (List.mem (target_id,None) (reference_updates missing)); + Alcotest.(check int) "both misses are cached" 0 + (List.length (reference_feed runtime reference).requests) +;; + +let test_reference_errors_and_cancellation_retry_page_first () = + List.iter (fun outline_stage -> List.iter (fun terminal -> + let runtime=Runtime.create () in + let first=reference_feed runtime reference + |> fun output->expect_reference_page output target_uuid in + let current=if outline_stage then + missing_reference_page runtime first target_uuid |> fun output->expect_reference_outline output target_uuid + else first in + let failed = match terminal with + | 0 -> Runtime.receive runtime (response current + (Protocol.V2_failed {code="StorageBusy";message="transient"})) + | 1 -> Runtime.fail_request runtime current ~message:"Worker cancelled" + | _ -> Runtime.abandon runtime current; {Runtime.requests=[];responses=[]} in + Alcotest.(check int) "failure/cancel is not page absence" 0 (List.length failed.requests); + let retry=reference_feed runtime reference + |> fun output->expect_reference_page output target_uuid in + let late=Runtime.receive runtime (reference_page_response first Ordinary_page "Late") in + Alcotest.(check int) "old terminal cannot complete the retry" 0 + (List.length late.requests+List.length late.responses); + let fresh=Runtime.receive runtime (reference_page_response retry Ordinary_page "Recovered") in + Alcotest.(check (option string)) "retry is not poisoned by negative cache" + (Some "Recovered") (List.assoc_opt target_id (reference_updates fresh) |> Option.join)) [0;1;2]) [false;true] +;; + +let test_reference_reset_discards_both_stages () = + List.iter (fun outline_stage -> + let runtime=Runtime.create () in + let first=reference_feed runtime reference |> fun output->expect_reference_page output target_uuid in + let old=if outline_stage then missing_reference_page runtime first target_uuid + |> fun output->expect_reference_outline output target_uuid else first in + Runtime.reset runtime; + let fresh=reference_feed runtime reference |> fun output->expect_reference_page output target_uuid in + let late=Runtime.receive runtime + (if outline_stage then target_response old "Old graph" + else reference_page_response old Ordinary_page "Old graph") in + Alcotest.(check int) "reset rejects old graph completion" 0 + (List.length late.requests+List.length late.responses); + let result=Runtime.receive runtime (reference_page_response fresh Ordinary_page "New graph") in + Alcotest.(check (option string)) "new graph owns source" + (Some "New graph") (List.assoc_opt target_id (reference_updates result) |> Option.join)) [false;true] +;; + +let test_reference_invalidation_restarts_page_phase () = + List.iter (fun outline_stage -> + let runtime=Runtime.create () in + let first=reference_feed runtime reference |> fun output->expect_reference_page output target_uuid in + let current=if outline_stage then missing_reference_page runtime first target_uuid + |> fun output->expect_reference_outline output target_uuid else first in + let invalidated=changed runtime (window ~pages:[target_uuid] ()) in + Alcotest.(check int) "in-flight invalidations coalesce" 0 + (List.length (hydration_requests invalidated)); + let stale=Runtime.receive runtime + (if outline_stage then target_response current "Stale" + else response current (Protocol.V2_page_outcome + (V2_missing_page {uuid=target_uuid;revision="old-page-miss"}))) in + Alcotest.(check bool) "stale phase never publishes" false + (List.mem (target_id,Some "Stale") (reference_updates stale)); + let refresh=expect_reference_page stale target_uuid in + let result=Runtime.receive runtime (reference_page_response refresh Ordinary_page "Fresh") in + Alcotest.(check (option string)) "invalidation restarts from page" + (Some "Fresh") (List.assoc_opt target_id (reference_updates result) |> Option.join)) [false;true] +;; + +let test_reference_fallback_keeps_original_epoch () = + let runtime=Runtime.create () in + let old=reference_feed runtime reference |> fun output->expect_reference_page output target_uuid in + let newer=begin_reference_tree runtime 20L in + let newest = Runtime.receive runtime (reference_tree_response newer "Newer fragment") in + Alcotest.(check (option string)) "newer fragment publishes source" + (Some "Newer fragment") (List.assoc_opt target_id (reference_updates newest) |> Option.join); + let fallback=missing_reference_page runtime old target_uuid in + let completed=match hydration_requests fallback with + | [] -> fallback + | [_] -> Runtime.receive runtime (target_response + (expect_reference_outline fallback target_uuid) "Older fallback") + | _ -> Alcotest.fail "duplicate fallback" in + Alcotest.(check bool) "old two-stage lookup cannot replace a newer fragment" false + (List.mem (target_id,Some "Older fallback") (reference_updates completed)); + let cached=reference_feed runtime reference in + Alcotest.(check int) "newer cached source remains available without a retry" + 0 (List.length cached.requests); + Alcotest.(check bool) "older missing result cannot clear a newer source" false + (List.mem (target_id,None) (reference_updates completed)) +;; + +let test_reference_two_stage_burst_stays_bounded () = + let runtime=Runtime.create () in + let source=String.concat " " (List.init 30 (fun n-> + Printf.sprintf "[[a4000000-0000-4000-9000-%012d]]" n)) in + let pending=Queue.create () in + List.iter (fun request->Queue.push request pending) (reference_feed runtime source).requests; + Alcotest.(check int) "four initial page reads" 4 (Queue.length pending); + let pages=ref 0 and outlines=ref 0 in + while not (Queue.is_empty pending) do + let request : Protocol.request = Queue.pop pending in + let outcome=match request.command with + | V2_get_page {page;_}->incr pages;Protocol.V2_page_outcome + (V2_missing_page {uuid=page;revision="page-missing"}) + | V2_get_outline_block {block;_}->incr outlines;Protocol.V2_block_outcome + (V2_missing_block {uuid=block;revision="outline-missing"}) + | _->Alcotest.fail "unexpected hydration request" in + List.iter (fun request->Queue.push request pending) + (Runtime.receive runtime (response request outcome)).requests; + Alcotest.(check bool) "both phases share four-read admission" true (Queue.length pending<=4) + done; + Alcotest.(check int) "one page read per UUID" 30 !pages; + Alcotest.(check int) "one fallback per explicit miss" 30 !outlines; + Alcotest.(check int) "negative results remain deduplicated" 0 + (List.length (reference_feed runtime source).requests) +;; + +let test_recycled_reference_page_never_falls_back () = + let runtime=Runtime.create () in + let first=reference_feed runtime reference |> fun output->expect_reference_page output target_uuid in + let hidden=Runtime.receive runtime (reference_page_response ~recycled:true first Ordinary_page "Hidden") in + Alcotest.(check int) "existing recycled page is not a missing page" 0 (List.length hidden.requests); + Alcotest.(check bool) "recycled title is never shown" false + (List.mem (target_id,Some "Hidden") (reference_updates hidden)); + ignore (reference_feed runtime reference |> fun output->expect_reference_page output target_uuid) +;; + +let test_reference_failed_read_retains_epoch_fence () = + List.iter (fun abandon -> + let runtime=Runtime.create () in + let first=reference_feed runtime reference |> fun output->expect_reference_page output target_uuid in + let old_fragment=begin_reference_tree runtime 11L in + ignore (changed runtime (window ~pages:[target_uuid] ())); + let retry=missing_reference_page runtime first target_uuid + |> fun output->expect_reference_page output target_uuid in + if abandon then Runtime.abandon runtime retry + else ignore (Runtime.fail_request runtime retry ~message:"transient failure"); + let late=Runtime.receive runtime (reference_tree_response old_fragment "Rollback") in + Alcotest.(check bool) "failure/cancel preserves its epoch fence" false + (List.mem (target_id,Some "Rollback") (reference_updates late)); + let retry_output = + if hydration_requests late <> [] then late + else reference_feed runtime reference + in + ignore (expect_reference_page retry_output target_uuid); + Alcotest.(check int) "pending retry remains deduplicated" 0 + (List.length (reference_feed runtime reference).requests)) [false;true] +;; + +let test_reference_abandon_preserves_newer_source () = + let runtime=Runtime.create () in + let old=reference_feed runtime reference |> fun output->expect_reference_page output target_uuid in + let older_fragment=begin_reference_tree runtime 12L in + let current_fragment=begin_reference_tree runtime 13L in + let fresh=Runtime.receive runtime (reference_tree_response current_fragment "Current") in + Alcotest.(check bool) "fresh fragment published" true + (List.mem (target_id,Some "Current") (reference_updates fresh)); + Runtime.abandon runtime old; + let rollback=Runtime.receive runtime (reference_tree_response older_fragment "Rollback") in + Alcotest.(check bool) "abandon cannot delete a newer source fence" false + (List.mem (target_id,Some "Rollback") (reference_updates rollback)); + Alcotest.(check int) "newer source remains cached" 0 + (List.length (reference_feed runtime reference).requests) +;; + +let test_reference_shared_failure_can_retry_discovery () = + let runtime=Runtime.create () in + set_calendar runtime; + let initial=begin_reference_tree runtime 14L in + ignore (Runtime.receive runtime (reference_tree_response initial "Loaded")); + let update=changed runtime (window ~blocks:[target_uuid] ()) + |> check_target_request runtime in + ignore (Runtime.fail_request runtime update ~message:"shared point-read failed"); + ignore (reference_feed runtime reference |> fun output->expect_reference_page output target_uuid) +;; + +let test_reference_page_survives_unrelated_outline_miss () = + let runtime=Runtime.create () in + let first=reference_feed runtime reference |> fun output->expect_reference_page output target_uuid in + ignore (Runtime.receive runtime (reference_page_response first Ordinary_page "Actual page")); + let wrong_projection=Runtime.submit runtime (Journal_graph_request.Find_block target_id) + |> fun output->only "independent outline request" output.requests in + let missing=Runtime.receive runtime (response wrong_projection (Protocol.V2_block_outcome + (V2_missing_block {uuid=target_uuid;revision="not-an-outline"}))) in + Alcotest.(check bool) "outline miss does not invalidate an existing page" false + (List.mem (target_id,None) (reference_updates missing)); + Alcotest.(check int) "existing page stays positively cached" 0 + (List.length (reference_feed runtime reference).requests) +;; + +let test_reference_shared_page_miss_is_not_negative_cache () = + let runtime=Runtime.create () in + let first=reference_feed runtime reference |> fun output->expect_reference_page output target_uuid in + ignore (Runtime.receive runtime (reference_page_response first Ordinary_page "Actual page")); + let root=Runtime.submit runtime (Journal_graph_request.Load_detail + {block_id=Graph.Uuid.to_string block_uuid;after=None;limit=128;request_generation=22L}) + |> fun output->only "unretained owning page root" output.requests in + let owning=Runtime.receive runtime (response root (Protocol.V2_block_outcome + (V2_present_block {value={record with block={block with page=target_uuid;parent=target_uuid}};revision="owning"}))) + |> fun output->only "owning page metadata read" output.requests in + ignore (missing_reference_page runtime owning target_uuid); + ignore (reference_feed runtime reference |> fun output->expect_reference_page output target_uuid) +;; + +let test_typed_metadata_preserves_newer_reference_fence () = + let runtime=Runtime.create () in + let first=reference_feed runtime reference |> fun output->expect_reference_page output target_uuid in + let property : Graph.property_summary = + {ident="user.property/page";uuid=block_uuid;title="Actual page" + ;schema={property_type=Page;cardinality=One;hidden=false;public=false} + ;values=[Page_value target_uuid];values_truncated=false} in + let root=Runtime.submit runtime (Journal_graph_request.Load_detail + {block_id=Graph.Uuid.to_string block_uuid;after=None;limit=128;request_generation=31L}) + |> fun output->only "old typed Detail root" output.requests in + let children=Runtime.receive runtime (response root (Protocol.V2_block_outcome + (V2_present_block {value={record with block={block with properties=[property]}};revision="typed"}))) + |> fun output->only "old typed Detail children" output.requests in + let middle=begin_reference_tree runtime 32L in + ignore (changed runtime (window ~pages:[target_uuid] ())); + let fresh=missing_reference_page runtime first target_uuid + |> fun output->expect_reference_page output target_uuid in + ignore (Runtime.fail_request runtime fresh ~message:"newer failure fence"); + ignore (Runtime.receive runtime (response children (Protocol.V2_children_outcome + {parent=block_uuid;revision_scope=V2_children_revision block_uuid;scope_revision="typed";items=[];next_cursor=None}))); + let late=Runtime.receive runtime (reference_tree_response middle "Rollback") in + Alcotest.(check bool) "old typed metadata cannot lower a newer fence" false + (List.mem (target_id,Some "Rollback") (reference_updates late)) +;; + +let test_dirty_abandon_keeps_reference_retryable () = + List.iter (fun positive -> + let runtime=Runtime.create () in + let first=reference_feed runtime reference |> fun output->expect_reference_page output target_uuid in + if positive then ignore (Runtime.receive runtime (reference_page_response first Ordinary_page "Before")) + else ( + let outline=missing_reference_page runtime first target_uuid + |> fun output->expect_reference_outline output target_uuid in + ignore (Runtime.receive runtime (response outline (Protocol.V2_block_outcome + (V2_missing_block {uuid=target_uuid;revision="missing"}))))); + let pending=changed runtime (window ~pages:[target_uuid] ()) + |> fun output->expect_reference_page output target_uuid in + ignore (changed runtime (window ~pages:[target_uuid] ())); + Runtime.abandon runtime pending; + ignore (reference_feed runtime reference |> fun output->expect_reference_page output target_uuid)) [false;true] +;; + let () = Alcotest.run "journal graph runtime locality" - [ ( "property references", [Alcotest.test_case "Detail closed property deduplicates" `Quick test_detail_property_reference_reads_are_deduplicated + [ ( "review reference ownership", [ + Alcotest.test_case "typed metadata retains newer fence" `Quick test_typed_metadata_preserves_newer_reference_fence; + Alcotest.test_case "dirty abandon remains retryable" `Quick test_dirty_abandon_keeps_reference_retryable; + Alcotest.test_case "failed lookup retains epoch fence" `Quick test_reference_failed_read_retains_epoch_fence; + Alcotest.test_case "abandon retains newer source" `Quick test_reference_abandon_preserves_newer_source; + Alcotest.test_case "shared failure can retry discovery" `Quick test_reference_shared_failure_can_retry_discovery; + Alcotest.test_case "outline miss cannot hide a page" `Quick test_reference_page_survives_unrelated_outline_miss; + Alcotest.test_case "shared page miss remains retryable" `Quick test_reference_shared_page_miss_is_not_negative_cache]) + ; ( "page-first UUID references", [ + Alcotest.test_case "journal and ordinary page sources" `Quick test_reference_page_first_resolves_pages; + Alcotest.test_case "explicit page miss then outline" `Quick test_reference_page_miss_then_outline; + Alcotest.test_case "only both misses cache absence" `Quick test_reference_negative_cache_requires_both_misses; + Alcotest.test_case "errors and cancellation retry" `Quick test_reference_errors_and_cancellation_retry_page_first; + Alcotest.test_case "reset fences both stages" `Quick test_reference_reset_discards_both_stages; + Alcotest.test_case "invalidation restarts page lookup" `Quick test_reference_invalidation_restarts_page_phase; + Alcotest.test_case "fallback inherits original epoch" `Quick test_reference_fallback_keeps_original_epoch; + Alcotest.test_case "two-stage bursts remain bounded" `Quick test_reference_two_stage_burst_stays_bounded; + Alcotest.test_case "recycled page is not missing" `Quick test_recycled_reference_page_never_falls_back]) + ; ( "property references", [Alcotest.test_case "Detail closed property deduplicates" `Quick test_detail_property_reference_reads_are_deduplicated ;Alcotest.test_case "changed journal Detail discovers property" `Quick test_changed_journal_detail_discovers_property_reference ;Alcotest.test_case "materialized Detail leaf discovers property" `Quick test_materialized_detail_leaf_discovers_property_reference ;Alcotest.test_case "returned child property resolves without own load" `Quick test_detail_child_property_reference_without_child_load diff --git a/test/journal_media_runtime_test.ml b/test/journal_media_runtime_test.ml index 2166a0e5..0ce3e4f2 100644 --- a/test/journal_media_runtime_test.ml +++ b/test/journal_media_runtime_test.ml @@ -448,7 +448,7 @@ let observe runtime (block : G.block) = runtime { api_version = 2 ; request_id = uuid 999 - ; command = P.V2_get_block { block = block.G.uuid; revision = None } + ; command = P.V2_get_outline_block { block = block.G.uuid; revision = None } }; R.observe_response runtime (block_response block) ;; @@ -489,7 +489,7 @@ let reply_block runtime sent (block : G.block) = let ticket, request = Queue.take sent in match ticket, request with | ( Some ticket - , S.Graph_request { request_id; command = P.V2_get_block { block = uuid; _ }; _ } ) + , S.Graph_request { request_id; command = P.V2_get_outline_block { block = uuid; _ }; _ } ) when uuid = block.G.uuid -> let outcome = match block_response block with @@ -499,7 +499,7 @@ let reply_block runtime sent (block : G.block) = runtime ticket (S.Graph_response (P.V2_response { api_version = 2; request_id; outcome })) - | _ -> failwith "holder/liveness must use its owned bounded get_block" + | _ -> failwith "holder/liveness must use its owned bounded get_outline_block" ;; let window ?(structures = []) blocks : P.v2_change_window = @@ -527,7 +527,7 @@ let membership_change runtime n target = let reply_missing runtime sent n = let ticket, request = Queue.take sent in match ticket, request with - | Some ticket, S.Graph_request { request_id; command = P.V2_get_block { block; _ }; _ } + | Some ticket, S.Graph_request { request_id; command = P.V2_get_outline_block { block; _ }; _ } when block = uuid n -> R.receive runtime @@ -771,7 +771,7 @@ let () = r { P.api_version = 2 ; request_id = uuid 998 - ; command = P.V2_get_block { block = uuid 1; revision = None } + ; command = P.V2_get_outline_block { block = uuid 1; revision = None } }; for n = 10000 to 14100 do change r [ n ] @@ -806,7 +806,7 @@ let () = change r [ 80 ]; let ticket, request = Queue.take q in (match ticket, request with - | Some ticket, S.Graph_request { command = P.V2_get_block { block; _ }; _ } + | Some ticket, S.Graph_request { command = P.V2_get_outline_block { block; _ }; _ } when block = uuid 60 -> R.reject r ticket | _ -> failwith "missing ancestor must have one owned fact read"); let root = G.Uuid.to_string (uuid 1) in @@ -828,7 +828,7 @@ let () = let request id = { P.api_version = 2 ; request_id = uuid id - ; command = P.V2_get_block { block = uuid 1; revision = None } + ; command = P.V2_get_outline_block { block = uuid 1; revision = None } } in let answer id refs = @@ -859,7 +859,7 @@ let () = r { P.api_version = 2 ; request_id = uuid 998 - ; command = P.V2_get_block { block = uuid 1; revision = None } + ; command = P.V2_get_outline_block { block = uuid 1; revision = None } }; change r [ 80 ]; let answer = @@ -942,7 +942,7 @@ let () = change r [ 60 ]; reply_missing r q 60; check (list_reads q = 1) "ancestor tombstone refreshes its dependent only" ) - ; ( "unknown ancestor is filled through bounded get_block" + ; ( "unknown ancestor is filled through bounded get_outline_block" , fun () -> let r, q, _, _ = fixture () in observe r (raw_block 1 60); @@ -952,7 +952,7 @@ let () = let ticket, request = Queue.take q in match ticket, request with | ( Some ticket - , S.Graph_request { request_id; command = P.V2_get_block { block; _ }; _ } ) + , S.Graph_request { request_id; command = P.V2_get_outline_block { block; _ }; _ } ) when block = uuid 60 -> let value = match block_response (raw_block 60 61) with @@ -963,7 +963,7 @@ let () = ticket (S.Graph_response (P.V2_response { api_version = 2; request_id; outcome = value })) - | _ -> failwith "unknown ancestor requires a public bounded get_block" + | _ -> failwith "unknown ancestor requires a public bounded get_outline_block" in if Queue.is_empty q then change r [ 61 ]; answer (); diff --git a/test/journal_model_test.ml b/test/journal_model_test.ml index a86ef393..63974bd7 100644 --- a/test/journal_model_test.ml +++ b/test/journal_model_test.ml @@ -316,10 +316,10 @@ let test_rich_title_boundaries () = ; "before [[outer [[inner]]]] after", "before [[outer [[inner]]]] after" ; "SCHEDULED: <2026-10-14 Wed> " ^ link id, "SCHEDULED: <2026-10-14 Wed> Target 中文" ]; require - (List.for_all (fun (run : Journal_model.Title.run) -> run.reference = Some parent_id) (render (link parent_id))) - "nested expansion changed the outer reference click target"; + (List.for_all (fun (run : Journal_model.Title.run) -> run.url = None) (render (link parent_id))) + "nested expansion gained a link target"; let runs = render ("**" ^ link id ^ "**") in - require (List.exists (fun (run : Journal_model.Title.run) -> run.text = "Target 中文" && run.reference = Some id && List.mem "bold" run.styles) runs) "reference lost styles/target"; + require (List.exists (fun (run : Journal_model.Title.run) -> run.text = "Target 中文" && run.url = None && List.mem "bold" run.styles) runs) "reference lost text styles"; let runs = render "[**标签**](HTTP://example.org/path)" in require (List.exists (fun (run : Journal_model.Title.run) -> run.url = Some "http://example.org/path" && run.text = "标签" && List.mem "bold" run.styles) runs) "URL lost nested style or authority"; let repeated = String.concat " " (List.init 600 (fun _ -> link id)) in @@ -355,7 +355,28 @@ let test_ast_reference_discovery () = "AST reference discovery: %s" name) cases ;; +let test_resolved_references_are_plain_text () = + let token target = "[[" ^ target ^ "]]" in + let lookup target = + if target=id then Some ("**Date** " ^ token parent_id ^ " https://example.org/in-reference") + else if target=parent_id then Some "nested" else None in + let runs=Journal_model.Title.render ~lookup (token id ^ " https://example.org/ordinary") in + let json=Journal_model.Title.to_yojson runs in + (match json with + | `List values -> List.iter (function + | `Assoc fields -> require (not (List.mem_assoc "reference" fields)) + "resolved UUID still carries navigation metadata" + | _ -> require false "run JSON shape") values + | _ -> require false "runs JSON shape"); + require (List.filter_map (fun (run:Journal_model.Title.run)->run.url) runs + = ["https://example.org/in-reference";"https://example.org/ordinary"]) + "ordinary URLs lost their existing link behavior"; + require (List.exists (fun (run:Journal_model.Title.run)->run.text="Date" && List.mem "bold" run.styles) runs) + "plain reference lost content formatting" +;; + let () = + test_resolved_references_are_plain_text (); test_rich_title_boundaries (); test_ast_reference_discovery (); test_reference_text (); diff --git a/test/journal_semantics_test.ml b/test/journal_semantics_test.ml index 6f7fd411..9b11e4b5 100644 --- a/test/journal_semantics_test.ml +++ b/test/journal_semantics_test.ml @@ -1065,29 +1065,26 @@ let test_status_and_tags_mount () = ;; (* Title layout is owned by the mounted view, outside the pure reducers. *) -let test_rich_title_reference_events () = - let received = ref [] in - let target = "20000000-0000-4000-a000-000000000099" in - let title = Journal_model.Title.render ~lookup:(fun _ -> Some "Target") ("[[" ^ target ^ "]]") in - with_mounted (Journal_header.detail_text ~title ~on_reference:(fun id -> received := id :: !received) ~id:"rich-event" ~depth:0 "") (fun app ops -> - let node = List.find_map (function Lui_protocol.CreateExtension (node, name, _) when name = Journal_lui_native.chrome_identifier -> Some node | _ -> None) (ops ()) |> Option.get in - let send payload = - let values = Lui_protocol.String_map.empty - |> Lui_protocol.String_map.add "id" (Lui_protocol.IntValue 1) - |> Lui_protocol.String_map.add "payload" (Lui_protocol.StringValue payload) in - ignore (Lui_app.dispatch_event app (Lui_protocol.ExtensionEvent (node, Journal_lui_native.chrome_identifier, "event", values))); - ignore (Lui_app.flush app) in - send (Yojson.Basic.to_string (`Assoc ["reference", `String target])); - send (Yojson.Basic.to_string (`Assoc ["reference", `String block_id])); - send "broken"; - require (!received = [target]) "reference event did not bind to rendered target"); - with_mounted (Journal_header.detail_text ~on_reference:(fun id -> received := id :: !received) ~id:"missing-event" ~depth:0 ("[[" ^ target ^ "]]")) (fun app ops -> - let node = List.find_map (function Lui_protocol.CreateExtension (node, name, _) when name = Journal_lui_native.chrome_identifier -> Some node | _ -> None) (ops ()) |> Option.get in - let values = Lui_protocol.String_map.empty |> Lui_protocol.String_map.add "id" (Lui_protocol.IntValue 1) - |> Lui_protocol.String_map.add "payload" (Lui_protocol.StringValue (Yojson.Basic.to_string (`Assoc ["reference", `String target]))) in - ignore (Lui_app.dispatch_event app (Lui_protocol.ExtensionEvent (node, Journal_lui_native.chrome_identifier, "event", values))); +let test_rich_title_references_are_not_links () = + let target="20000000-0000-4000-a000-000000000099" in + let title=Journal_model.Title.render ~lookup:(fun _->Some "Target") ("[["^target^"]]") in + with_mounted (Journal_header.detail_text ~title ~id:"rich-plain" ~depth:0 "") (fun app ops -> + let payload=List.find_map (function + | Lui_protocol.SetExtensionProp (_, "payload", StringValue value)->Some (Yojson.Basic.from_string value) + | _->None) (ops ()) |> Option.get in + let runs=Yojson.Basic.Util.member "runs" payload |> Yojson.Basic.Util.to_list in + List.iter (fun run -> + require (not (List.mem_assoc "reference" (Yojson.Basic.Util.to_assoc run))) + "plain UUID label retained reference action metadata") runs; + let node=List.find_map (function Lui_protocol.CreateExtension (node,name,_) when name=Journal_lui_native.chrome_identifier -> Some node | _->None) (ops ()) |> Option.get in + let properties=Lui_protocol.String_map.empty + |> Lui_protocol.String_map.add "id" (Lui_protocol.IntValue 1) + |> Lui_protocol.String_map.add "payload" (Lui_protocol.StringValue + (Yojson.Basic.to_string (`Assoc ["reference",`String target]))) in + let before=List.length (ops ()) in + ignore (Lui_app.dispatch_event app (Lui_protocol.ExtensionEvent (node,Journal_lui_native.chrome_identifier,"event",properties))); ignore (Lui_app.flush app); - require (!received = [target]) "missing reference became clickable") + require (List.length (ops ())=before) "removed UUID event changed the mounted view") ;; let test_mldoc_titles_share_runs () = @@ -2826,7 +2823,7 @@ let tests = ; "top image asset body", test_top_image_asset_has_no_filename_body ; "scoped theme wire properties", test_scoped_theme_wire_properties ; "loading indicator and message", test_loading_keeps_indicator_and_message - ; "rich title reference events", test_rich_title_reference_events + ; "rich title references are plain text", test_rich_title_references_are_not_links ; "shared mldoc title runs", test_mldoc_titles_share_runs ; "UUID reference body and summary", test_reference_body_and_summary ; "status and tags mount", test_status_and_tags_mount diff --git a/test/logseq_db_worker_application_integration_test.ml b/test/logseq_db_worker_application_integration_test.ml index c4d60882..b944757f 100644 --- a/test/logseq_db_worker_application_integration_test.ml +++ b/test/logseq_db_worker_application_integration_test.ml @@ -604,7 +604,7 @@ let test_capture_task_reads_inserted_block_revision_before_status () = ignore (seed_visible_block runtime); let block_request = capture_task_block_request runtime in (match block_request.command with - | V2_get_block { block; revision = None } -> + | V2_get_outline_block { block; revision = None } -> Alcotest.(check string) "captured block read target" capture_task.block_id @@ -918,7 +918,7 @@ let test_point_read_tag_metadata () = List.find (fun (request : Protocol.request) -> match request.command with - | V2_get_block _ -> true + | V2_get_outline_block _ -> true | _ -> false) hydrated.requests in diff --git a/test/logseq_overlay_db_boundary_test.ml b/test/logseq_overlay_db_boundary_test.ml index 6d6ac4c3..67296dc7 100644 --- a/test/logseq_overlay_db_boundary_test.ml +++ b/test/logseq_overlay_db_boundary_test.ml @@ -132,7 +132,7 @@ let test_canonical_modules root = ; "type snapshot" ; "val current_snapshot" ; "val release_snapshot" - ; "val get_blocks" + ; "val get_outline_blocks" ; "val get_pages" ; "val get_journals" ; "val get_structure" diff --git a/test/macos_mutation_runtime_test.ml b/test/macos_mutation_runtime_test.ml index a345644a..c2f96698 100644 --- a/test/macos_mutation_runtime_test.ml +++ b/test/macos_mutation_runtime_test.ml @@ -194,7 +194,7 @@ let outcome worker ordinal value = ;; let block_revision worker block = - match outcome worker 801 (P.V2_get_block { block; revision = None }) with + match outcome worker 801 (P.V2_get_outline_block { block; revision = None }) with | V2_block_outcome (V2_present_block { revision; _ }) -> revision | _ -> failwith "worker block missing" ;; @@ -817,7 +817,7 @@ let test_encoded_utf8_budget_boundary () = let title = String.concat "" (List.init 300 (fun _ -> "日\"\n")) in let prepare = prepare_tagged_fixture ~count:1 ~title in let request = - command 1110 (P.V2_get_block { block = Fixture.mutation_uuid 10000; revision = None }) + command 1110 (P.V2_get_outline_block { block = Fixture.mutation_uuid 10000; revision = None }) in let size = ref 0 in with_worker ~prepare (fun worker -> @@ -891,7 +891,7 @@ let test_canonical_target_summary_query () = |> ignore) in let query ?(limit = P.default_page_size) ordinal cursor = - command ordinal (P.V2_get_block_summary { block = target; limit; cursor }) + command ordinal (P.V2_get_outline_block_summary { block = target; limit; cursor }) |> P.request_to_yojson |> P.request_of_yojson |> Result.fold ~ok:Fun.id ~error:(fun error -> diff --git a/test/source_boundary_test.ml b/test/source_boundary_test.ml index c9d823a3..145cdd44 100644 --- a/test/source_boundary_test.ml +++ b/test/source_boundary_test.ml @@ -1036,7 +1036,7 @@ let test_deployed_managed_sync_e2e_boundary root = ; "Service.Graph_request" ; "V2_list_journals" ; "V2_insert_blocks" - ; "V2_get_block" + ; "V2_get_outline_block" ; "V2_delete_blocks" ; "swift/JournalE2EECrypto.swift" ; "LOGSEQ_JOURNAL_E2EE_TEST_FILE_KEYCHAIN" diff --git a/tool/rrbvec_bucket_profile.py b/tool/rrbvec_bucket_profile.py index 557392ab..67c2688f 100644 --- a/tool/rrbvec_bucket_profile.py +++ b/tool/rrbvec_bucket_profile.py @@ -34,7 +34,7 @@ def instrument(source): for start, end in [('let logical_page_at', 'let logical_block_at'), - ('let logical_block_at', 'let get_blocks')]: + ('let logical_block_at', 'let get_outline_blocks')]: a, b = source.index(start), source.index(end) region = source[a:b] old = '(fun current (record : outbox_record) ->' diff --git a/tool/test_json_codec_tools.py b/tool/test_json_codec_tools.py index 672c00c6..9e6a5170 100644 --- a/tool/test_json_codec_tools.py +++ b/tool/test_json_codec_tools.py @@ -57,7 +57,7 @@ def test_small_benchmark_reopens_generated_pending_outbox(self): self.assertEqual(sample['active_outbox_records'], 1) self.assertGreater(sample['active_outbox_bytes'], 0) self.assertEqual(len(sample['reopen']), 5) - self.assertEqual(len(sample['get_blocks_64']), 5) + self.assertEqual(len(sample['get_outline_blocks_64']), 5) if __name__ == '__main__':