diff --git a/app/javascript/application.js b/app/javascript/application.js
index a9c3d0391..b32063f80 100644
--- a/app/javascript/application.js
+++ b/app/javascript/application.js
@@ -9,6 +9,8 @@ import ToggleVisible from './elements/toggle_visible'
import ToggleCookies from './elements/toggle_cookies'
import DisableHidden from './elements/disable_hidden'
import TurboModal from './elements/turbo_modal'
+import NativeAction from './elements/native_action'
+import NativeHeader from './elements/native_header'
import FileDropzone from './elements/file_dropzone'
import MenuActive from './elements/menu_active'
import ClipboardCopy from './elements/clipboard_copy'
@@ -106,6 +108,8 @@ const safeRegisterElement = (name, element, options = {}) => !window.customEleme
safeRegisterElement('toggle-visible', ToggleVisible)
safeRegisterElement('disable-hidden', DisableHidden)
safeRegisterElement('turbo-modal', TurboModal)
+safeRegisterElement('native-action', NativeAction)
+safeRegisterElement('native-header', NativeHeader)
safeRegisterElement('file-dropzone', FileDropzone)
safeRegisterElement('menu-active', MenuActive)
safeRegisterElement('clipboard-copy', ClipboardCopy)
diff --git a/app/javascript/application.scss b/app/javascript/application.scss
index 1050df66c..d444c5e2a 100644
--- a/app/javascript/application.scss
+++ b/app/javascript/application.scss
@@ -166,3 +166,8 @@ button[disabled] .enabled, button.btn-disabled .enabled {
markdown-editor [contenteditable] p {
margin-bottom: 18px;
}
+
+native-action,
+native-header {
+ display: contents;
+}
diff --git a/app/javascript/elements/native_action.js b/app/javascript/elements/native_action.js
new file mode 100644
index 000000000..27119b812
--- /dev/null
+++ b/app/javascript/elements/native_action.js
@@ -0,0 +1,31 @@
+let counter = 0
+
+export default class extends HTMLElement {
+ connectedCallback () {
+ const bridge = window.webkit?.messageHandlers?.native
+
+ if (!bridge) return
+
+ const target = this.querySelector('a, button, [role="button"]')
+
+ if (!target) return
+
+ this.nativeId ||= `native-action-${++counter}`
+ target.dataset.nativeId = this.nativeId
+
+ bridge.postMessage({
+ type: 'action',
+ op: 'add',
+ id: this.nativeId,
+ title: this.dataset.label || target.textContent.trim(),
+ icon: this.dataset.icon,
+ placement: this.dataset.placement || 'menu'
+ })
+ }
+
+ disconnectedCallback () {
+ if (this.nativeId) {
+ window.webkit?.messageHandlers?.native?.postMessage({ type: 'action', op: 'remove', id: this.nativeId })
+ }
+ }
+}
diff --git a/app/javascript/elements/native_header.js b/app/javascript/elements/native_header.js
new file mode 100644
index 000000000..cfdbb0771
--- /dev/null
+++ b/app/javascript/elements/native_header.js
@@ -0,0 +1,14 @@
+export default class extends HTMLElement {
+ connectedCallback () {
+ window.webkit?.messageHandlers?.native?.postMessage({
+ type: 'header',
+ op: 'add',
+ title: this.dataset.label || this.querySelector('h1')?.textContent?.trim() || '',
+ search: 'search' in this.dataset
+ })
+ }
+
+ disconnectedCallback () {
+ window.webkit?.messageHandlers?.native?.postMessage({ type: 'header', op: 'remove' })
+ }
+}
diff --git a/app/views/accounts/show.html.erb b/app/views/accounts/show.html.erb
index 46787131e..aa77491d3 100644
--- a/app/views/accounts/show.html.erb
+++ b/app/views/accounts/show.html.erb
@@ -1,3 +1,4 @@
+<% content_for(:native_title, t('account')) if turbo_native_app? %>
<%= render 'shared/settings_nav' %>
diff --git a/app/views/api_settings/index.html.erb b/app/views/api_settings/index.html.erb
index 710fe7f85..e009646b7 100644
--- a/app/views/api_settings/index.html.erb
+++ b/app/views/api_settings/index.html.erb
@@ -1,3 +1,4 @@
+<% content_for(:native_title, 'API') if turbo_native_app? %>
<%= render 'shared/settings_nav' %>
diff --git a/app/views/email_smtp_settings/index.html.erb b/app/views/email_smtp_settings/index.html.erb
index cd750acf7..102ac75bd 100644
--- a/app/views/email_smtp_settings/index.html.erb
+++ b/app/views/email_smtp_settings/index.html.erb
@@ -1,3 +1,4 @@
+<% content_for(:native_title, t('email')) if turbo_native_app? %>
<%= render 'shared/settings_nav' %>
diff --git a/app/views/esign_settings/show.html.erb b/app/views/esign_settings/show.html.erb
index 2692aa375..fa0eab259 100644
--- a/app/views/esign_settings/show.html.erb
+++ b/app/views/esign_settings/show.html.erb
@@ -1,3 +1,4 @@
+<% content_for(:native_title, t('e_signature')) if turbo_native_app? %>
<%= render 'shared/settings_nav' %>
diff --git a/app/views/layouts/_head_tags.html.erb b/app/views/layouts/_head_tags.html.erb
index cedcab1b5..c9ed919c6 100644
--- a/app/views/layouts/_head_tags.html.erb
+++ b/app/views/layouts/_head_tags.html.erb
@@ -1,4 +1,4 @@
- <%= content_for(:html_title) || (turbo_native_app? ? '' : (signed_in? ? 'DocuSeal' : 'DocuSeal | Open Source Document Signing')) %>
+ <%= content_for(:native_title) || content_for(:html_title) || (signed_in? ? 'DocuSeal' : 'DocuSeal | Open Source Document Signing') %>
<%= render 'shared/meta' %>
diff --git a/app/views/mcp_settings/index.html.erb b/app/views/mcp_settings/index.html.erb
index b0afcc3a7..9191a6e29 100644
--- a/app/views/mcp_settings/index.html.erb
+++ b/app/views/mcp_settings/index.html.erb
@@ -1,3 +1,4 @@
+<% content_for(:native_title, 'MCP') if turbo_native_app? %>
<%= render 'shared/settings_nav' %>
diff --git a/app/views/notifications_settings/index.html.erb b/app/views/notifications_settings/index.html.erb
index 262178d57..a911c9494 100644
--- a/app/views/notifications_settings/index.html.erb
+++ b/app/views/notifications_settings/index.html.erb
@@ -1,3 +1,4 @@
+<% content_for(:native_title, t('notifications')) if turbo_native_app? %>
<%= render 'shared/settings_nav' %>
diff --git a/app/views/personalization_settings/show.html.erb b/app/views/personalization_settings/show.html.erb
index 20ea3c788..82651ce01 100644
--- a/app/views/personalization_settings/show.html.erb
+++ b/app/views/personalization_settings/show.html.erb
@@ -1,3 +1,4 @@
+<% content_for(:native_title, t('personalization')) if turbo_native_app? %>
<%= render 'shared/settings_nav' %>
diff --git a/app/views/profile/index.html.erb b/app/views/profile/index.html.erb
index 6444c53f9..e3b393bc5 100644
--- a/app/views/profile/index.html.erb
+++ b/app/views/profile/index.html.erb
@@ -1,3 +1,4 @@
+<% content_for(:native_title, t('profile')) if turbo_native_app? %>
<%= render 'shared/settings_nav' %>
diff --git a/app/views/settings/index.html.erb b/app/views/settings/index.html.erb
index 1232a47cb..9e1187fcb 100644
--- a/app/views/settings/index.html.erb
+++ b/app/views/settings/index.html.erb
@@ -1,3 +1,4 @@
+<% content_for(:native_title, t('settings')) if turbo_native_app? %>
<% unless turbo_native_app? %>
diff --git a/app/views/sms_settings/index.html.erb b/app/views/sms_settings/index.html.erb
index 2c2d2e902..b865f4e16 100644
--- a/app/views/sms_settings/index.html.erb
+++ b/app/views/sms_settings/index.html.erb
@@ -1,3 +1,4 @@
+<% content_for(:native_title, 'SMS') if turbo_native_app? %>
<%= render 'shared/settings_nav' %>
diff --git a/app/views/sso_settings/index.html.erb b/app/views/sso_settings/index.html.erb
index 9ac6bdf24..fb13a57fd 100644
--- a/app/views/sso_settings/index.html.erb
+++ b/app/views/sso_settings/index.html.erb
@@ -1,3 +1,4 @@
+<% content_for(:native_title, 'SSO') if turbo_native_app? %>
<%= render 'shared/settings_nav' %>
diff --git a/app/views/storage_settings/index.html.erb b/app/views/storage_settings/index.html.erb
index e4a5bd958..b459f1f7f 100644
--- a/app/views/storage_settings/index.html.erb
+++ b/app/views/storage_settings/index.html.erb
@@ -1,3 +1,4 @@
+<% content_for(:native_title, t('storage')) if turbo_native_app? %>
<%= render 'shared/settings_nav' %>
diff --git a/app/views/submissions/show.html.erb b/app/views/submissions/show.html.erb
index 6a863cc29..5ab68571a 100644
--- a/app/views/submissions/show.html.erb
+++ b/app/views/submissions/show.html.erb
@@ -1,3 +1,4 @@
+<% content_for(:native_title, @submission.name || @submission.template&.name) if turbo_native_app? %>
<% if local_assigns[:is_preview] %>
<%= render 'submissions/preview_tags' %>
<% end %>
diff --git a/app/views/submissions_archived/index.html.erb b/app/views/submissions_archived/index.html.erb
index 0118a3cb3..4b478f56a 100644
--- a/app/views/submissions_archived/index.html.erb
+++ b/app/views/submissions_archived/index.html.erb
@@ -1,3 +1,4 @@
+<% content_for(:native_title, t('archived')) if turbo_native_app? %>
<% filter_params = params.permit(Submissions::Filter::ALLOWED_PARAMS).compact_blank %>
<% unless turbo_native_app? %>
@@ -8,9 +9,7 @@
<% end %>
- <% if turbo_native_app? %>
- <% content_for(:html_title, t('archived')) %>
- <% else %>
+ <% unless turbo_native_app? %>