@@ -695,6 +695,20 @@ Most application code should prefer `Cmd.task`.
695695Cmd<Msg>::copy_to_clipboard("Saved URL")
696696```
697697
698+ ` Cmd.read_clipboard(handler:) ` reads text from the browser clipboard and
699+ dispatches the handler result.
700+
701+ ``` voyd
702+ Cmd<Msg>::read_clipboard(
703+ (value: String) -> Msg => Msg::ClipboardRead { value: value }
704+ )
705+ ```
706+
707+ Clipboard reads can fail because of browser permissions or unavailable APIs.
708+ Those failures are reported through the runtime command error path. Use
709+ ` Cmd.task ` when the response-producing work should be owned by Voyd task code
710+ instead of the browser runtime host.
711+
698712` Cmd.focus(target) ` focuses a DOM element found by ` data-vx-ref ` .
699713
700714``` voyd
@@ -708,8 +722,15 @@ Cmd<Msg>::focus(editor)
708722Cmd<Msg>::scroll_into_view(editor)
709723```
710724
725+ ` Cmd.select_text(target) ` selects text in an input-like DOM element found by
726+ ` data-vx-ref ` .
727+
728+ ``` voyd
729+ Cmd<Msg>::select_text(editor)
730+ ```
731+
711732` Cmd.set_document_title(value:) ` , ` Cmd.push_url(value:) ` ,
712- ` Cmd.replace_url(value:) ` , ` Cmd.navigate_back() ` , and
733+ ` Cmd.replace_url(value:) ` , ` Cmd.set_hash(value:) ` , ` Cmd. navigate_back()` , and
713734` Cmd.navigate_forward() ` cover common document and history effects.
714735
715736``` voyd
@@ -719,6 +740,32 @@ Cmd::batch([
719740])
720741```
721742
743+ ` Cmd.scroll_window_to(x:, y:) ` and ` Cmd.scroll_window_by(x:, y:) ` control the
744+ browser window scroll position.
745+
746+ ``` voyd
747+ Cmd<Msg>::scroll_window_to(x: 0.0, y: 240.0)
748+ Cmd<Msg>::scroll_window_by(x: 0.0, y: 80.0)
749+ ```
750+
751+ ` Cmd.local_storage_set(key:, value:) ` , ` Cmd.local_storage_remove(key:) ` ,
752+ ` Cmd.local_storage_clear() ` , ` Cmd.session_storage_set(key:, value:) ` ,
753+ ` Cmd.session_storage_remove(key:) ` , and ` Cmd.session_storage_clear() ` cover
754+ string storage writes and removals.
755+
756+ ``` voyd
757+ Cmd<Msg>::local_storage_set(key: "draft", value: model.draft)
758+ Cmd<Msg>::session_storage_remove("wizard-step")
759+ ```
760+
761+ ` Cmd.open_url(value:) ` opens a URL in a new browser context. Use the labeled
762+ form to choose the browser target.
763+
764+ ``` voyd
765+ Cmd<Msg>::open_url("https://voyd.dev")
766+ Cmd<Msg>::open_url(url: "/help", target: "_self")
767+ ```
768+
722769` Cmd.runtime(kind:) ` creates a host command envelope for custom browser or
723770application capabilities.
724771
@@ -959,11 +1006,57 @@ document_on_visibility_change(
9591006 handler: (visibility: DocumentVisibility) -> Msg =>
9601007 Msg::VisibilityChanged { hidden: visibility.hidden }
9611008)
1009+
1010+ location_on_change(
1011+ key: "location",
1012+ handler: (location: Location) -> Msg =>
1013+ Msg::LocationChanged { href: location.href }
1014+ )
1015+
1016+ window_on_focus(key: "focus", value: Msg::WindowFocused {})
1017+ window_on_blur(key: "blur", value: Msg::WindowBlurred {})
9621018```
9631019
9641020Each helper also has a fixed-message form with ` value: ` when the app only needs
9651021to know that the event happened.
9661022
1023+ For rendering loops and browser preferences, use animation frame and media query
1024+ subscriptions:
1025+
1026+ ``` voyd
1027+ animation_frame(
1028+ key: "render-loop",
1029+ handler: (frame: AnimationFrame) -> Msg =>
1030+ Msg::Frame { timestamp: frame.timestamp }
1031+ )
1032+
1033+ media_query(
1034+ query: "(prefers-reduced-motion: reduce)",
1035+ handler: (query: MediaQuery) -> Msg =>
1036+ Msg::ReducedMotionChanged { enabled: query.matches }
1037+ )
1038+ ```
1039+
1040+ For cross-tab and cross-context coordination, use storage and broadcast channel
1041+ subscriptions:
1042+
1043+ ``` voyd
1044+ storage_on_change(
1045+ key: "storage",
1046+ handler: (change: StorageChange) -> Msg =>
1047+ Msg::StorageChanged { key: change.key }
1048+ )
1049+
1050+ broadcast_channel<String, Msg>(
1051+ name: "updates",
1052+ handler: (value: String) -> Msg =>
1053+ Msg::Broadcast { value: value }
1054+ )
1055+ ```
1056+
1057+ ` StorageChange.key ` , ` old_value ` , and ` new_value ` are optional because browser
1058+ storage events use ` null ` for clears, missing old values, and removals.
1059+
9671060### Runtime Subscriptions
9681061
9691062` Sub.runtime_payload(kind:, key:, handler:) ` creates a host subscription
@@ -1312,10 +1405,14 @@ reports asynchronous listener failures with `phase: "subscriptions"`.
13121405The built-in browser runtime host already handles:
13131406
13141407- Commands: ` delay ` , ` task ` , ` copy_to_clipboard ` , ` focus ` ,
1315- ` scroll_into_view ` , ` set_document_title ` , ` push_url ` , ` replace_url ` ,
1316- ` navigate_back ` , ` navigate_forward ` .
1408+ ` read_clipboard ` , ` scroll_into_view ` , ` select_text ` , ` set_document_title ` ,
1409+ ` push_url ` , ` replace_url ` , ` set_hash ` , ` navigate_back ` ,
1410+ ` navigate_forward ` , ` open_url ` , ` scroll_window_to ` , ` scroll_window_by ` ,
1411+ ` local_storage_set ` , ` local_storage_remove ` , ` local_storage_clear ` ,
1412+ ` session_storage_set ` , ` session_storage_remove ` , ` session_storage_clear ` .
13171413- Subscriptions: ` interval ` , ` keyboard ` , ` online_status ` , ` window_resize ` ,
1318- ` visibility_change ` .
1414+ ` visibility_change ` , ` location_change ` , ` window_focus ` , ` window_blur ` ,
1415+ ` animation_frame ` , ` media_query ` , ` storage ` , ` broadcast_channel ` .
13191416
13201417Lower-level renderer APIs are available for integration work:
13211418
0 commit comments