<%= image_tag current_user.avatar, alt: "", class: "feed-composer__avatar" %>
diff --git a/app/components/posts/composer_component.rb b/app/components/posts/composer_component.rb
index 50d0dce81..acd3169df 100644
--- a/app/components/posts/composer_component.rb
+++ b/app/components/posts/composer_component.rb
@@ -7,13 +7,13 @@ class ComposerComponent < ViewComponent::Base
attr_reader :post, :current_user, :projects, :selected_project, :test_time_granted,
:url, :scope, :aria_label, :body_label, :placeholder, :submit_text,
:disable_with, :simple_mode, :show_project_chips, :show_attachments,
- :show_time_preview, :show_record, :quote_preview_post
+ :show_time_preview, :show_record, :quote_preview_post, :pending_lookout_session
def initialize(post:, current_user:, projects:, selected_project:, test_time_granted: false,
url: nil, scope: nil, aria_label: "Create a devlog", body_label: "What are you working on?",
placeholder: "What are you working on?", submit_text: "Post", disable_with: "Posting...",
simple_mode: false, show_project_chips: true, show_attachments: true, show_time_preview: true,
- show_record: false, quote_preview_post: nil)
+ show_record: false, quote_preview_post: nil, pending_lookout_session: nil)
@post = post
@current_user = current_user
@projects = projects
@@ -32,6 +32,7 @@ def initialize(post:, current_user:, projects:, selected_project:, test_time_gra
@show_time_preview = show_time_preview
@show_record = show_record
@quote_preview_post = quote_preview_post
+ @pending_lookout_session = pending_lookout_session
end
# When a post to quote is supplied, the composer shows it below the text box
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index ab631ad71..48c107613 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -49,6 +49,14 @@ def prepare_project_show_context
@composer_devlog = Post::Devlog.new
@composer_projects = current_user.projects.order(updated_at: :desc)
+ # Coming back from a just-finished Lookout recording: pre-link that
+ # session to the devlog the user is about to write, so their timelapse
+ # attaches to the post instead of feeling lost.
+ if Flipper.enabled?(:timelapse_devlog, current_user) && params[:lookout_session_id].present?
+ @pending_lookout_session = @project.lookout_sessions.attachable
+ .find_by(id: params[:lookout_session_id], user: current_user, devlog_id: nil)
+ end
+
@hackatime_linked = current_user.hackatime_identity.present?
if @hackatime_linked
diff --git a/app/javascript/controllers/modal_controller.js b/app/javascript/controllers/modal_controller.js
index ce1ddf032..f91defd49 100644
--- a/app/javascript/controllers/modal_controller.js
+++ b/app/javascript/controllers/modal_controller.js
@@ -30,6 +30,7 @@ export default class extends Controller {
this.openSettingsModalFromQueryParam();
this.openIdvModalFromQueryParam();
+ this.openComposerModalFromQueryParam();
}
disconnect() {
@@ -130,4 +131,26 @@ export default class extends Controller {
}`;
window.history.replaceState(window.history.state, "", nextUrl);
}
+
+ // Landing on the project page straight from a finished Lookout recording
+ // (?just_recorded=1): pop the devlog composer open so the timelapse can be
+ // attached, then strip the params so a refresh doesn't reopen it.
+ openComposerModalFromQueryParam() {
+ if (!this.element.id.startsWith("composer-modal-")) return;
+
+ const params = new URLSearchParams(window.location.search);
+ if (!params.has("just_recorded")) return;
+
+ if (!this.element.open) {
+ this.element.showModal();
+ }
+
+ params.delete("just_recorded");
+ params.delete("lookout_session_id");
+ const query = params.toString();
+ const nextUrl = `${window.location.pathname}${query ? `?${query}` : ""}${
+ window.location.hash
+ }`;
+ window.history.replaceState(window.history.state, "", nextUrl);
+ }
}
diff --git a/app/views/projects/lookout_sessions/record.html.erb b/app/views/projects/lookout_sessions/record.html.erb
index 13991bcab..5b0e021d8 100644
--- a/app/views/projects/lookout_sessions/record.html.erb
+++ b/app/views/projects/lookout_sessions/record.html.erb
@@ -165,8 +165,15 @@
<%# Shown after the time is sent — nudge to actually log it in a devlog. %>
- <%= link_to "Post a Devlog", project_path(@project), class: "lookout-rec__post-btn" %>
-
Your time only counts once you do a devlog.
+ <% if Flipper.enabled?(:timelapse_devlog, current_user) %>
+ <%= link_to "Add it to a devlog",
+ project_path(@project, lookout_session_id: @lookout_session.id, just_recorded: 1),
+ class: "lookout-rec__post-btn" %>
+
Saved! We've got your timelapse. Add it to a devlog so your time counts.
+ <% else %>
+ <%= link_to "Post a Devlog", project_path(@project), class: "lookout-rec__post-btn" %>
+
Your time only counts once you do a devlog.
+ <% end %>
<%= link_to "Back to project →", project_path(@project),
diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb
index e3c8bb0a8..1585225f3 100644
--- a/app/views/projects/show.html.erb
+++ b/app/views/projects/show.html.erb
@@ -905,7 +905,8 @@
current_user: current_user,
projects: @composer_projects,
selected_project: @project,
- test_time_granted: @test_time_granted
+ test_time_granted: @test_time_granted,
+ pending_lookout_session: @pending_lookout_session
) %>
<% end %>
diff --git a/config/initializers/flipper.rb b/config/initializers/flipper.rb
index 4bd1f6e66..6bdbdf01a 100644
--- a/config/initializers/flipper.rb
+++ b/config/initializers/flipper.rb
@@ -42,6 +42,7 @@
ship_event_payouts
lookout
payout_recommendations
+ timelapse_devlog
].each { |flag| Flipper.add(flag) }
end
rescue StandardError => e
From eb3dd94a47d8df7a978306f35bfaa98744dd05f8 Mon Sep 17 00:00:00 2001
From: Dhamari Trice-Hanson <39872667+dhamariT@users.noreply.github.com>
Date: Wed, 1 Jul 2026 15:50:50 -0400
Subject: [PATCH 2/2] =?UTF-8?q?Fix=20timelapse=E2=86=92devlog=20link=20gap?=
=?UTF-8?q?s=20flagged=20in=20review?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Broaden the pending-session lookup to a new :linkable scope
(stopped/compiling/complete) so a background sync flipping a
just-stopped session to compiling mid-flow no longer silently
drops the attach. Leaves :attachable (finished-video only) intact
for the hardware-review recordings list.
- Keep lookout_session_id in the URL after auto-opening the composer;
only strip just_recorded. A pre-submit refresh now still resolves
@pending_lookout_session instead of losing the link.
- Hide the duration in the reassurance banner when it hasn't synced
yet, so it no longer reads "Your timelapse (0s)".
---
app/components/posts/composer_component.html.erb | 2 +-
app/controllers/projects_controller.rb | 2 +-
app/javascript/controllers/modal_controller.js | 5 +++--
app/models/lookout_session.rb | 7 +++++++
4 files changed, 12 insertions(+), 4 deletions(-)
diff --git a/app/components/posts/composer_component.html.erb b/app/components/posts/composer_component.html.erb
index 0946433fa..4b131b651 100644
--- a/app/components/posts/composer_component.html.erb
+++ b/app/components/posts/composer_component.html.erb
@@ -42,7 +42,7 @@
session to this devlog, and reassure the user we kept their lapse. %>
<%= hidden_field_tag "post_devlog[lookout_session_ids][]", pending_lookout_session.id %>
- Your timelapse (<%= helpers.format_seconds(pending_lookout_session.duration_seconds) %>) is saved and will be attached to this devlog.
+ Your timelapse<% if pending_lookout_session.duration_seconds.to_i > 0 %> (<%= helpers.format_seconds(pending_lookout_session.duration_seconds) %>)<% end %> is saved and will be attached to this devlog.
<% end %>
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 48c107613..9a8eb92c4 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -53,7 +53,7 @@ def prepare_project_show_context
# session to the devlog the user is about to write, so their timelapse
# attaches to the post instead of feeling lost.
if Flipper.enabled?(:timelapse_devlog, current_user) && params[:lookout_session_id].present?
- @pending_lookout_session = @project.lookout_sessions.attachable
+ @pending_lookout_session = @project.lookout_sessions.linkable
.find_by(id: params[:lookout_session_id], user: current_user, devlog_id: nil)
end
diff --git a/app/javascript/controllers/modal_controller.js b/app/javascript/controllers/modal_controller.js
index f91defd49..f6a90d454 100644
--- a/app/javascript/controllers/modal_controller.js
+++ b/app/javascript/controllers/modal_controller.js
@@ -134,7 +134,9 @@ export default class extends Controller {
// Landing on the project page straight from a finished Lookout recording
// (?just_recorded=1): pop the devlog composer open so the timelapse can be
- // attached, then strip the params so a refresh doesn't reopen it.
+ // attached, then strip just_recorded so a refresh doesn't reopen it.
+ // Keep lookout_session_id: it's what the server reads to render the hidden
+ // attach field, so dropping it would lose the link on a pre-submit refresh.
openComposerModalFromQueryParam() {
if (!this.element.id.startsWith("composer-modal-")) return;
@@ -146,7 +148,6 @@ export default class extends Controller {
}
params.delete("just_recorded");
- params.delete("lookout_session_id");
const query = params.toString();
const nextUrl = `${window.location.pathname}${query ? `?${query}` : ""}${
window.location.hash
diff --git a/app/models/lookout_session.rb b/app/models/lookout_session.rb
index beb5725f7..e628e1cc2 100644
--- a/app/models/lookout_session.rb
+++ b/app/models/lookout_session.rb
@@ -45,6 +45,13 @@ class LookoutSession < ApplicationRecord
scope :for_project, ->(project) { where(project: project) }
scope :attachable, -> { where(status: %w[stopped complete]) }
+ # Sessions that can be linked to a devlog from the recorder → composer flow.
+ # Broader than `attachable` (which requires a finished video): a session still
+ # `compiling` will finalize shortly, and the submit-side attach accepts any
+ # status, so the composer must surface the attach field for it too. Otherwise
+ # a background sync flipping stopped → compiling mid-flow silently drops the
+ # link. Excludes `failed` and the still-recording states.
+ scope :linkable, -> { where(status: %w[stopped compiling complete]) }
# Sessions that might still advance — everything not yet in a terminal state.
# SyncPendingLookoutSessionsJob re-polls these so a recording can finalize even
# when the builder closed the recorder tab before Lookout finished compiling.