From b84da940160f29b3aa47d3ff6990ec1367ba0475 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20=C5=A0vara?= Date: Tue, 6 Jan 2026 15:46:47 +0100 Subject: [PATCH 001/128] Add Hotwire Native Bridge wiring. --- app/javascript/application.js | 3 ++- app/javascript/bridge/initializers/index.js | 1 + app/javascript/initializers/index.js | 1 + config/importmap.rb | 4 ++++ 4 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 app/javascript/bridge/initializers/index.js diff --git a/app/javascript/application.js b/app/javascript/application.js index bdafb95b09..8f805eae13 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -1,8 +1,9 @@ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails import "@hotwired/turbo-rails" +import "@hotwired/hotwire-native-bridge" import "initializers" +import "bridge/initializers" import "controllers" import "lexxy" import "@rails/actiontext" - diff --git a/app/javascript/bridge/initializers/index.js b/app/javascript/bridge/initializers/index.js new file mode 100644 index 0000000000..84b27bb0df --- /dev/null +++ b/app/javascript/bridge/initializers/index.js @@ -0,0 +1 @@ +// Bridge initializers live here. diff --git a/app/javascript/initializers/index.js b/app/javascript/initializers/index.js index 10fb369755..d128addc96 100644 --- a/app/javascript/initializers/index.js +++ b/app/javascript/initializers/index.js @@ -1 +1,2 @@ import "initializers/current" +import "bridge/initializers" diff --git a/config/importmap.rb b/config/importmap.rb index 3431633f98..27dbd7015d 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -4,11 +4,15 @@ pin "@hotwired/turbo-rails", to: "turbo.min.js" pin "@hotwired/stimulus", to: "stimulus.min.js" pin "@hotwired/stimulus-loading", to: "stimulus-loading.js" +pin "@hotwired/hotwire-native-bridge", to: "@hotwired--hotwire-native-bridge.js" pin "@rails/request.js", to: "@rails--request.js" # @0.0.13 pin_all_from "app/javascript/controllers", under: "controllers" pin_all_from "app/javascript/helpers", under: "helpers" pin_all_from "app/javascript/initializers", under: "initializers" +pin_all_from "app/javascript/bridge/initializers", under: "bridge/initializers" +pin_all_from "app/javascript/bridge/helpers", under: "bridge/helpers" +pin_all_from "app/javascript/bridge/controllers/bridge", under: "controllers/bridge", to: "bridge/controllers/bridge" pin "marked" # @15.0.11 pin "lexxy" pin "@rails/activestorage", to: "activestorage.esm.js" From 177afe1a4158a663b7fd2342e4ee818ba084eaf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20=C5=A0vara?= Date: Tue, 6 Jan 2026 15:54:10 +0100 Subject: [PATCH 002/128] Add bridge page controller and helpers. --- app/javascript/application.js | 1 - .../controllers/bridge/page_controller.js | 131 ++++++++++++++++++ app/javascript/bridge/helpers/viewport.js | 19 +++ .../bridge/initializers/bridge_element.js | 13 ++ app/javascript/bridge/initializers/index.js | 2 +- 5 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 app/javascript/bridge/controllers/bridge/page_controller.js create mode 100644 app/javascript/bridge/helpers/viewport.js create mode 100644 app/javascript/bridge/initializers/bridge_element.js diff --git a/app/javascript/application.js b/app/javascript/application.js index 8f805eae13..c43a45eaa6 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -2,7 +2,6 @@ import "@hotwired/turbo-rails" import "@hotwired/hotwire-native-bridge" import "initializers" -import "bridge/initializers" import "controllers" import "lexxy" diff --git a/app/javascript/bridge/controllers/bridge/page_controller.js b/app/javascript/bridge/controllers/bridge/page_controller.js new file mode 100644 index 0000000000..d9fb53b1b7 --- /dev/null +++ b/app/javascript/bridge/controllers/bridge/page_controller.js @@ -0,0 +1,131 @@ +import { BridgeComponent } from "@hotwired/hotwire-native-bridge" +import { BridgeElement } from "@hotwired/hotwire-native-bridge" +import { viewport } from "bridge/helpers/viewport" +import { nextFrame } from "helpers/timing_helpers" + +export default class extends BridgeComponent { + static component = "page" + static targets = [ "header" ] + static values = { title: String } + + async connect() { + super.connect() + this.notifyBridgeOfPageChange() + await nextFrame() + this.startObserver() + window.addEventListener("resize", this.windowResized) + window.addEventListener("turbo:submit-start", this.submitStart) + window.addEventListener("turbo:submit-end", this.submitEnd) + } + + disconnect() { + super.disconnect() + this.stopObserver() + window.removeEventListener("resize", this.windowResized) + window.removeEventListener("turbo:submit-start", this.submitStart) + window.removeEventListener("turbo:submit-end", this.submitEnd) + } + + receive(message) { + switch (message.event) { + case "change": + this.updateHeaderVisibility(message.data) + break + case "set-text-size": + this.setTextSize(message.data) + break + } + } + + setTextSize(data) { + document.documentElement.dataset.textSize = data.textSize + } + + updateHeaderVisibility(data) { + if (!this.hasHeaderTarget) return + + const headerElement = new BridgeElement(this.headerTarget) + + if (data.displayOnPlatform) { + headerElement?.showOnPlatform() + } else { + headerElement?.hideOnPlatform() + } + } + + // Bridge + + notifyBridgeOfPageChange() { + let headerElement = null + const data = { + title: this.title, + url: window.location.href + } + + if (this.hasHeaderTarget) { + // Assume header visible by default until we get IntersectionObserver update + headerElement = new BridgeElement(this.headerTarget) + data.elementVisible = true + data.displayOnPlatform = headerElement.isDisplayedOnPlatform() + } + + this.send("change", data, message => this.receive(message)) + } + + notifyBridgeOfVisibilityChange(visible) { + this.send("visibility", { title: this.title, elementVisible: visible }) + } + + notifyBridgeOfSubmitStart() { + this.send("submitStart") + } + + notifyBridgeOfSubmitEnd() { + this.send("submitEnd") + } + + // Intersection Observer + + startObserver() { + if (!this.hasHeaderTarget) return + + this.observer = new IntersectionObserver(([ entry ]) => + this.notifyBridgeOfVisibilityChange(entry.isIntersecting), + { rootMargin: `-${this.topOffset}px 0px 0px 0px` } + ) + + this.observer.observe(this.headerTarget) + this.previousTopOffset = this.topOffset + } + + stopObserver() { + this.observer?.disconnect() + } + + updateObserverIfNeeded() { + if (this.topOffset === this.previousTopOffset) return + + this.stopObserver() + this.startObserver() + } + + windowResized = () => { + this.updateObserverIfNeeded() + } + + submitStart = () => { + this.notifyBridgeOfSubmitStart() + } + + submitEnd = () => { + this.notifyBridgeOfSubmitEnd() + } + + get title() { + return this.titleValue ? this.titleValue : document.title + } + + get topOffset() { + return viewport.top + } +} diff --git a/app/javascript/bridge/helpers/viewport.js b/app/javascript/bridge/helpers/viewport.js new file mode 100644 index 0000000000..bf85312044 --- /dev/null +++ b/app/javascript/bridge/helpers/viewport.js @@ -0,0 +1,19 @@ +let top = 0 + +export const viewport = { + get top() { + return top + }, + get height() { + return visualViewport.height + } +} + +function update() { + requestAnimationFrame(() => { + top = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--safe-area-inset-top")) + }) +} + +visualViewport.addEventListener("resize", update) +update() diff --git a/app/javascript/bridge/initializers/bridge_element.js b/app/javascript/bridge/initializers/bridge_element.js new file mode 100644 index 0000000000..7e96bae1c1 --- /dev/null +++ b/app/javascript/bridge/initializers/bridge_element.js @@ -0,0 +1,13 @@ +import { BridgeElement } from "@hotwired/hotwire-native-bridge" + +BridgeElement.prototype.isDisplayedOnPlatform = function() { + return !this.hasClass("hide-on-native") +} + +BridgeElement.prototype.showOnPlatform = function() { + this.element.classList.remove("hide-on-native") +} + +BridgeElement.prototype.hideOnPlatform = function() { + this.element.classList.add("hide-on-native") +} diff --git a/app/javascript/bridge/initializers/index.js b/app/javascript/bridge/initializers/index.js index 84b27bb0df..8e17fab0c0 100644 --- a/app/javascript/bridge/initializers/index.js +++ b/app/javascript/bridge/initializers/index.js @@ -1 +1 @@ -// Bridge initializers live here. +import "bridge/initializers/bridge_element" From ecf0921af9400865acc767fe4cec637e0cb51b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20=C5=A0vara?= Date: Tue, 6 Jan 2026 16:03:28 +0100 Subject: [PATCH 003/128] Add bridge page targets. --- app/views/account/settings/show.html.erb | 2 +- app/views/boards/columns/closeds/show.html.erb | 2 +- app/views/boards/columns/not_nows/show.html.erb | 2 +- app/views/boards/columns/show.html.erb | 2 +- app/views/boards/columns/streams/show.html.erb | 2 +- app/views/boards/edit.html.erb | 2 +- app/views/boards/show.html.erb | 2 +- app/views/cards/index.html.erb | 2 +- app/views/events/day_timeline/columns/show.html.erb | 2 +- app/views/events/index.html.erb | 2 +- app/views/layouts/application.html.erb | 2 +- app/views/my/access_tokens/index.html.erb | 2 +- app/views/my/access_tokens/new.html.erb | 2 +- app/views/my/access_tokens/show.html.erb | 2 +- app/views/my/pins/index.html.erb | 2 +- app/views/notifications/index.html.erb | 2 +- app/views/notifications/settings/show.html.erb | 2 +- app/views/public/boards/columns/closeds/show.html.erb | 2 +- app/views/public/boards/columns/not_nows/show.html.erb | 2 +- app/views/public/boards/columns/show.html.erb | 2 +- app/views/public/boards/columns/streams/show.html.erb | 2 +- app/views/public/boards/show.html.erb | 2 +- app/views/searches/show.html.erb | 2 +- app/views/webhooks/index.html.erb | 2 +- app/views/webhooks/new.html.erb | 2 +- 25 files changed, 25 insertions(+), 25 deletions(-) diff --git a/app/views/account/settings/show.html.erb b/app/views/account/settings/show.html.erb index fc5e799b38..799045c172 100644 --- a/app/views/account/settings/show.html.erb +++ b/app/views/account/settings/show.html.erb @@ -1,7 +1,7 @@ <% @page_title = "Account Settings" %> <% content_for :header do %> -

+

<%= @page_title %> <% unless Current.user.admin? %>
Only admins can change these settings
diff --git a/app/views/boards/columns/closeds/show.html.erb b/app/views/boards/columns/closeds/show.html.erb index 8a9d1f56cb..cfa56d721f 100644 --- a/app/views/boards/columns/closeds/show.html.erb +++ b/app/views/boards/columns/closeds/show.html.erb @@ -5,7 +5,7 @@ <%= link_back_to_board(@board) %> -

+

<%= @page_title %>

<% end %> diff --git a/app/views/boards/columns/not_nows/show.html.erb b/app/views/boards/columns/not_nows/show.html.erb index 88ea338685..3fb62213c5 100644 --- a/app/views/boards/columns/not_nows/show.html.erb +++ b/app/views/boards/columns/not_nows/show.html.erb @@ -5,7 +5,7 @@ <%= link_back_to_board(@board) %> -

+

<%= @page_title %>

<% end %> diff --git a/app/views/boards/columns/show.html.erb b/app/views/boards/columns/show.html.erb index 4fef4eec70..aa730b1798 100644 --- a/app/views/boards/columns/show.html.erb +++ b/app/views/boards/columns/show.html.erb @@ -5,7 +5,7 @@ <%= link_back_to_board(@column.board) %> -

+

<%= @page_title %>

<% end %> diff --git a/app/views/boards/columns/streams/show.html.erb b/app/views/boards/columns/streams/show.html.erb index 9f0477b390..c98bd28b16 100644 --- a/app/views/boards/columns/streams/show.html.erb +++ b/app/views/boards/columns/streams/show.html.erb @@ -5,7 +5,7 @@ <%= link_back_to_board(@board) %> -

+

<%= @page_title %>

<% end %> diff --git a/app/views/boards/edit.html.erb b/app/views/boards/edit.html.erb index 36beee3c86..92b74ebbb1 100644 --- a/app/views/boards/edit.html.erb +++ b/app/views/boards/edit.html.erb @@ -5,7 +5,7 @@ <%= link_back_to_board(@board) %> -

+

<%= @page_title %>
<% unless Current.user.can_administer_board?(@board) %>
Only admins can change these settings
diff --git a/app/views/boards/show.html.erb b/app/views/boards/show.html.erb index 9f0c2ebeef..c2a9320d08 100644 --- a/app/views/boards/show.html.erb +++ b/app/views/boards/show.html.erb @@ -8,7 +8,7 @@ <%= link_to_webhooks(@board) if Current.user.admin? %> -

+

<%= @board.name %>

diff --git a/app/views/cards/index.html.erb b/app/views/cards/index.html.erb index c11876e165..e59a91eb46 100644 --- a/app/views/cards/index.html.erb +++ b/app/views/cards/index.html.erb @@ -4,7 +4,7 @@ <%= render "cards/broadcasts", filter: @filter %> <% content_for :header do %> -

+

<%= @user_filtering.selected_boards_label %>

diff --git a/app/views/events/day_timeline/columns/show.html.erb b/app/views/events/day_timeline/columns/show.html.erb index dada2ebc4f..ae85d6acef 100644 --- a/app/views/events/day_timeline/columns/show.html.erb +++ b/app/views/events/day_timeline/columns/show.html.erb @@ -5,7 +5,7 @@ <%= back_link_to "Activity", root_path, "keydown.left@document->hotkey#click keydown.esc@document->hotkey#click" %> -

+

<%= @column.title %>

<% end %> diff --git a/app/views/events/index.html.erb b/app/views/events/index.html.erb index 1279e4dac1..c9b9d0e8c1 100644 --- a/app/views/events/index.html.erb +++ b/app/views/events/index.html.erb @@ -6,7 +6,7 @@ <% content_for :header do %> <%= render "events/index/add_card_button", user_filtering: @user_filtering %> -

+

<% if @user_filtering.boards.many? %> Activity <%= @user_filtering.filter.boards.any? ? "in" : "across" %> <% else %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 7211e718d9..3b7761bc0a 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -2,7 +2,7 @@ <%= render "layouts/shared/head" %> - + >