diff --git a/app/assets/stylesheets/components/_feed.scss b/app/assets/stylesheets/components/_feed.scss index 4d1daab9d..9e8245b7d 100644 --- a/app/assets/stylesheets/components/_feed.scss +++ b/app/assets/stylesheets/components/_feed.scss @@ -299,6 +299,19 @@ background: var(--color-brand-salmon); } + // Reassurance banner shown when a just-finished timelapse is being carried + // into this devlog (Lookout recording → "Add it to a devlog"). + &__timelapse-note { + margin: 0 0 var(--space-xs); + padding: var(--space-xs); + border: 1px solid var(--color-brand-mint); + border-radius: var(--border-radius); + background: var(--color-space-accent-soft); + color: var(--color-space-text); + font-size: calc(var(--font-size-s) * 1.1); + line-height: 1.4; + } + &__form { display: flex; flex-direction: column; @@ -841,6 +854,23 @@ font-size: calc(var(--font-size-s) * 1.2); } + &__timelapse { + display: inline-flex; + align-items: center; + gap: var(--space-xxs); + margin: var(--space-xxs) 0 0; + font-size: calc(var(--font-size-s) * 1.1); + color: var(--color-brand-mint); + } + + &__timelapse-dot { + flex: 0 0 auto; + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--color-brand-mint); + } + &__body { padding-left: var(--feed-content-offset); font-size: calc(var(--font-size-s) * 1.2); diff --git a/app/components/posts/card_component.html.erb b/app/components/posts/card_component.html.erb index 8f6458ad3..09883b887 100644 --- a/app/components/posts/card_component.html.erb +++ b/app/components/posts/card_component.html.erb @@ -57,6 +57,12 @@

<%= helpers.format_seconds(display_postable.duration_seconds) %> logged

<% end %> + <% if devlog? && current_user && Flipper.enabled?(:timelapse_devlog, current_user) && display_postable.lookout_sessions.exists? %> +

+ Timelapse attached +

+ <% end %> + <% if quote_repost? && body.present? %>
<%= helpers.md(body, allow_images: false) %> diff --git a/app/components/posts/composer_component.html.erb b/app/components/posts/composer_component.html.erb index 8c5665d42..4b131b651 100644 --- a/app/components/posts/composer_component.html.erb +++ b/app/components/posts/composer_component.html.erb @@ -37,6 +37,14 @@ action: class_names("submit->composer#guardSubmit", "paste->composer#paste": show_attachments?) } }, class: "feed-composer__form", local: true do |f| %> + <% if pending_lookout_session.present? %> + <%# Carried over from a just-finished Lookout recording: link that + 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<% 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 %>
<%= 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..9a8eb92c4 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.linkable + .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..f6a90d454 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,27 @@ 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 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; + + const params = new URLSearchParams(window.location.search); + if (!params.has("just_recorded")) return; + + if (!this.element.open) { + this.element.showModal(); + } + + params.delete("just_recorded"); + 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/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. 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 "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