Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .agents/skills/logseq-journal-ios-sim-testing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,19 @@ fprintf(stderr, "[EVT-DBG] extension_event node=%d name=%s payload=%s\n", node,
Console via `xcrun simctl launch --console-pty <udid> com.logseq.journal > log 2>&1`
(stderr is the only diag channel). `git checkout` the file to revert.

## lui-migration notes (branch devin/lui-replacement, verified iOS 26.5)

- **Crash vs stall triage**: `invalidBatch("...")` + `JournalRuntime.swift:175 Fatal error` in stderr → app exits to home screen (a real crash — wire-schema violations are fatal, e.g. nested `list_item` without `expanded`, `sheet` with `min-width`, `toolbar` without `label`). Distinct from the "Downloading graph" parked-fiber stall (process alive at 0% CPU, screen stuck). On silent failures grep stderr for `Invalid_argument("property is unsupported by node kind` or `invalidBatch`.
- **Row right-alignment pitfall**: `Lui_elements.row` default `main=start` appends a trailing `Spacer` inside the HStack (`LUIRowLayoutPolicy.showsTrailingSpacer`), so a row used as a container is full-width with content packed at the LEADING edge — outer `spacer` siblings or `main=end` on a wrapper row cannot push the inner row's content right. To right-align chrome (e.g. the account capsule) put `~main:`end_` on the controls row itself. Also observed: `align`'s `alignment` prop on `overlay` children appeared not to land (capsule stayed top-left under `align `top_trailing` + content-sized column) — prefer `row ~main:`end_` inside the overlay.
- **Attach affordances (paperclip, composer doc/photo) reach OCaml but the picker may not present**: the press dispatches fine (add a temporary `fprintf(stderr, "[EVT-DBG] %s node=%lld\n", name, node)` in `dispatch_long` in `app/journal_lui_bridge.c` to confirm). If the press lands but no picker shows, check `LUIFilePickerView` — the journal mounts `file_picker` as a LEAF (no children), and a `Group { ForEach([]) }` produces NO rendered view, so `.fileImporter`/`.photosPicker`/`.sheet` and `onAppear` never install. The node needs a real placeholder view when `visibleChildren` is empty (e.g. `Color.clear.frame(width: 1, height: 1)` inside the Group) — verified that this makes the importer present.
- **Icon-only `Lui_elements.button`s need `~label`** — e.g. the composer pending-chip remove button (`xmark.circle.fill`, no `~text`/`~label`) caused `invalidBatch("button requires an accessible name")` → fatalError when the picked response rendered the pending strip. Grep journal code for `button ~icon` call sites lacking `~label` before exercising paths that mount them.
- **Media visibility appears unwired post-migration**: `Journal_media_runtime.root_visible`/`asset_visible` are only reachable via `"root"`/`"asset"` media-session actions, and nothing in journal OCaml emits them (the old native extension, kind_id 2105, self-reported visibility). Result: `media_views` never populate for browsed blocks → `file_image` thumbnails, external `link` rows, and "Open attachment" buttons never render; only the post-import optimistic `file-image` emit occurs, into the owning block's row label — which may sit in a backgrounded (non-visible) page subtree. Check whether `Journal_media_view.view`'s column should get `~on_appear` wiring to emit the `root` action.
- **Seed file-picker fixtures into "On My iPhone"** by copying files into `data/Containers/Shared/AppGroup/<uuid>/File Provider Storage/` where the group's `.com.apple.mobile_container_manager.metadata.plist` `MCMMetadataIdentifier` = `group.com.apple.FileProvider.LocalStorage` (the UUID differs per sim). The document picker's Browse tab → On My iPhone shows them.
- **Watch disk space**: a 100%-full disk silently kills `--console-pty` stderr capture and breaks staged-pick temp copies (pick lands, no chip, no error). Check `df -h /` if console goes quiet mid-run.
- **Row "..." context menu DOES work now** — opens Change status + "Delete block and descendants"; the delete removed the row and showed a "Block and descendants removed" Undo toast, and the deletion persisted across relaunch (supersedes the older note that context-menu actions were dead-wired — the lui path dispatches real actions).
- **iOS a11y bridge exposes ~6 nodes** for the LUI-rendered app (SwiftUI remote tree isn't introspectable) — locate controls by zooming screenshots + coordinate taps; sim mouse drags do not reliably trigger `swipeActions` (use the "..." context menu as the equivalent delete path).
- **Append "+" on a block detail page creates a top-level day entry**, not a nested child of the viewed block — detail bodies list a block's children, so leaf blocks render an empty body (not a bug).

## Devin Secrets Needed

- `LOGSEQ_JOURNAL_USERNAME`, `LOGSEQ_JOURNAL_PASSWORD` — Cognito sign-in
Expand Down
129 changes: 96 additions & 33 deletions app/application.ml
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ type state =
; asset_settings_open : bool
; media_views : Journal_media_runtime.view Media_views.t
; import_completion : (string * string option) option
; media_preview : string option
; pending_replace : string option
; replace_request : int
; capture_pick_request : int
Expand Down Expand Up @@ -312,6 +313,7 @@ let initial_state =
; asset_settings_open = false
; media_views = Media_views.empty
; import_completion = None
; media_preview = None
; pending_replace = None
; replace_request = 0
; capture_pick_request = 0
Expand Down Expand Up @@ -1620,19 +1622,16 @@ module Favorites_list = struct
let rows =
List.map2
(fun key label ->
let label =
if Keys.mem key block_keys
then
V.Navigation_link.create
~key:(Ui.Key.string ("favorite-open:" ^ key))
~activation_id:key
~enabled:actions_enabled
~on_activate:(bind_action on_open key)
~label
()
else label
in
V.Native_list.row ~key:(Ui.Key.string key) ~separator:Hidden label)
let is_block = Keys.mem key block_keys in
V.Native_list.row
~key:(Ui.Key.string key)
~separator:Hidden
?on_press:
(if is_block && actions_enabled
then Some (bind_action on_open key)
else None)
?icon:(if is_block then Some (Ui.journal_icon "chevron.right") else None)
label)
keys
children
in
Expand Down Expand Up @@ -1754,7 +1753,7 @@ let favorites_view
type composer_assets =
{ request : Journal_asset_import.request
; camera : bool
; completion : (string * string option) option
; pending : Journal_asset_import.staged list
; on_attach : Journal_asset_import.source -> unit
; on_event : string -> unit
}
Expand Down Expand Up @@ -1861,10 +1860,10 @@ let composer_content
Journal_asset_import.view
~key:(Ui.Key.string (scope ^ "-asset-import"))
~enabled:(enabled && not saving)
~completion:assets.completion
~completion:(Journal_asset_import.staged_completion assets.request assets.pending)
~replacement:None
~request:assets.request
~pending:(Journal_capture.pending_attachments capture)
~pending:assets.pending
~on_select:assets.on_event
(V.Body.static content)
;;
Expand Down Expand Up @@ -3031,6 +3030,8 @@ let response_tag = function
| 20 -> 21
| 22 -> 23
| 25 -> 26
| 28 -> 29
| 30 -> 31
| tag -> tag
;;

Expand Down Expand Up @@ -3843,13 +3844,34 @@ let start ~calendar_sampler ~client ~platform_code ~host_code : app_context =
| Error _ -> Effect.ignore
| Ok payload -> apply_authenticated_user payload)))
in
let seed_asset_settings =
platform_request Journal_platform.asset_recent_days_request ~f:(fun result ->
match Result.bind result Journal_platform.decode_asset_recent_days with
| Error _ -> Effect.ignore
| Ok days ->
(match Journal_asset_policy.settings ~recent_days:days with
| Error _ -> Effect.ignore
| Ok settings ->
Effect.of_thunk (fun () ->
(* A user edit that landed before the seed wins. *)
if Option.is_none !asset_settings
then (
asset_settings := Some settings;
let current = !state_ref in
if current.graph_state.phase = Graph_open
then
refresh_assets
~graph_generation:current.graph_state.generation
current.calendar))))
in
let calendar_startup =
Effect.bind (sample_calendar ()) ~f:(function
| Error error ->
set_state (fun state ->
{ state with graph_error = Some (Calendar_startup_failure error) })
| Ok calendar ->
Effect.bind (install_calendar set_state calendar) ~f:(fun () -> managed_startup))
Effect.bind (install_calendar set_state calendar) ~f:(fun () ->
Effect.many [ managed_startup; seed_asset_settings ]))
in
let calendar_tick_effect () : unit Effect.t =
Effect.bind
Expand Down Expand Up @@ -4396,6 +4418,9 @@ let start ~calendar_sampler ~client ~platform_code ~host_code : app_context =
(Journal_uploads.retry uploads operation))
| Some (Days settings) ->
Effect.of_thunk (fun () ->
emit_platform_request
(Journal_platform.set_asset_recent_days_request
(Journal_asset_policy.recent_days settings));
(* The extension re-emits its preference on every remount; only a
real change may refresh (each refresh republishes the model and
would loop under the full-remount view). *)
Expand Down Expand Up @@ -4497,7 +4522,8 @@ let start ~calendar_sampler ~client ~platform_code ~host_code : app_context =
{ state with modal = No_modal })
| Ui.Event.Payload.Text action ->
if String.starts_with ~prefix:"media:" action
then
then (
let preview_request = ref None in
Effect.bind
(Effect.of_thunk (fun () ->
sync_media snapshot;
Expand Down Expand Up @@ -4529,10 +4555,20 @@ let start ~calendar_sampler ~client ~platform_code ~host_code : app_context =
~asset:(text "asset")
| "reuse-next" -> Journal_media_runtime.reuse_next media_runtime ~root
| "reuse-cancel" -> Journal_media_runtime.end_reuse media_runtime ~root
| "preview" -> preview_request := Some (text "asset")
| _ -> ()
with
| _ -> ()))
~f:(fun () -> flush_media set_state)
~f:(fun () ->
Effect.many
[ flush_media set_state
; (match !preview_request with
| Some path ->
update (fun state -> { state with media_preview = Some path })
| None -> Effect.ignore)
]))
else if String.equal action "media-preview-dismissed"
then update (fun state -> { state with media_preview = None })
else if String.equal action "open-asset-import"
then
update (fun state ->
Expand All @@ -4553,7 +4589,9 @@ let start ~calendar_sampler ~client ~platform_code ~host_code : app_context =
else if String.starts_with ~prefix:"import-asset:" action
then (
let import_payload = String.sub action 13 (String.length action - 13) in
if Journal_asset_import.is_dismissal import_payload
if Journal_asset_import.is_error_dismissal import_payload
then update (fun state -> { state with import_completion = None })
else if Journal_asset_import.is_dismissal import_payload
then update (fun state -> { state with pending_replace = None })
else (
match Journal_routes.detail snapshot.routes with
Expand All @@ -4565,7 +4603,16 @@ let start ~calendar_sampler ~client ~platform_code ~host_code : app_context =
in
let source =
Result.bind target (fun target ->
Journal_asset_import.decode ~target import_payload)
Journal_asset_import.decode
~target
~replace_reference:
(match snapshot.pending_replace with
| Some reference ->
(match Logseq_db_types.Graph_types.Uuid.of_string reference with
| Ok reference -> Some reference
| Error _ -> None)
| None -> None)
import_payload)
in
(match source with
| Error _ -> update (fun state -> { state with pending_replace = None })
Expand Down Expand Up @@ -5230,13 +5277,13 @@ let start ~calendar_sampler ~client ~platform_code ~host_code : app_context =
let sync_error =
Option.map (fun notice -> sync_failure_message notice.failure) state.sync_error
in
let capture_assets ~camera =
let capture_assets ~camera ~capture =
{ request =
Journal_asset_import.staged_request
~id:state.capture_pick_request
~source:state.capture_pick_source
; camera
; completion = state.import_completion
; pending = Journal_capture.pending_attachments capture
; on_attach =
(fun source ->
Ui.Event.Handler.Private.invoke
Expand Down Expand Up @@ -5297,7 +5344,7 @@ let start ~calendar_sampler ~client ~platform_code ~host_code : app_context =
(match Journal_capture.phase capture with
| Failed message -> Some message
| Editing | Saving -> None))
~assets:(Some (capture_assets ~camera:true)))
~assets:(Some (capture_assets ~camera:true ~capture)))
])
state.direct_capture
| _ -> None
Expand Down Expand Up @@ -5411,7 +5458,10 @@ let start ~calendar_sampler ~client ~platform_code ~host_code : app_context =
| Failed message -> Some message
| Editing | Saving -> None))
~assets:
(Some (capture_assets ~camera:(state.environment.platform = "ios"))))
(Some
(capture_assets
~camera:(state.environment.platform = "ios")
~capture)))
state.direct_capture)
| Append_sheet ->
Option.bind (Journal_routes.detail state.routes) (fun detail ->
Expand Down Expand Up @@ -5528,12 +5578,32 @@ let start ~calendar_sampler ~client ~platform_code ~host_code : app_context =
(Journal_uploads.sync state.uploads (upload_context state)))
~offline:state.asset_offline
~presented:state.asset_settings_open
~days:
(Journal_asset_policy.recent_days
(Option.value ~default:Journal_asset_policy.default_settings !asset_settings))
~on_event:(fun value ->
Ui.Event.Handler.Private.invoke
dispatch
(Ui.Event.Payload.Text ("asset-settings:" ^ value)))
body
in
let body =
match state.media_preview with
| None -> body
| Some path ->
Ui.element
(Lui_elements.column
~grow:1.0
[ Ui.mount body
; Lui_elements.file_preview
~path
~on_dismiss:(fun _ ->
Ui.Event.Handler.Private.invoke
dispatch
(Ui.Event.Payload.Text "media-preview-dismissed"))
[]
])
in
V.Body.theme ~data:(application_theme ()) (V.Body.static body)
in
let view _context model_signal _send =
Expand Down Expand Up @@ -5576,14 +5646,7 @@ let start ~calendar_sampler ~client ~platform_code ~host_code : app_context =
true)
}
in
let app =
Lui_app.create_with_extensions
backend
Journal_lui_native.registry
initial_state
update
view
in
let app = Lui_app.create backend initial_state update view in
app_cell := Some app;
let context = { app; pump; client; send_action; apply_platform; running } in
current_app := Some context;
Expand Down
1 change: 0 additions & 1 deletion app/dune
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
journal_bridge
journal_environment
journal_ids
journal_lui_native
journal_pump
Comment on lines 8 to 9

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid the prohibited app/dune modification

This commit changes app/dune by removing journal_lui_native, but the repository instructions explicitly prohibit modifying any dune file unless that modification was requested. Restore the module/build arrangement or obtain explicit authorization and rescope the change accordingly.

AGENTS.md reference: AGENTS.md:L7-L7

Useful? React with 👍 / 👎.

journal_view
journal_uploads
Expand Down
Loading