Repository: mbta/screens
Pull request #2495: UI for the "all elevators working" state
Author: login: jacmrob
Created: 2025-03-14T17:07:51Z | Merged: 2025-03-21T20:49:25Z

**Asana task**: [No elevator alerts](https://app.asana.com/1/15492006741476/project/1185117109217422/task/1209256677260370?focus=true)

Description
When the elevator alerts array is empty, display the "all elevators working screen." If there is an upcoming outage at the elevator, display that in a smaller bar on top.  One issue -- I wasn't sure how to remove the footer from these screens, or if I should. The mock-ups don't have a footer.


 Screenshots added below: 

<img width="414" alt="Screenshot 2025-03-14 at 12 58 25 PM" src="https://github.com/user-attachments/assets/76fbe7fa-405b-4e21-b8d4-cef85f116e4f" />

<img width="412" alt="Screenshot 2025-03-14 at 12 57 49 PM" src="https://github.com/user-attachments/assets/26f85e4b-5f65-4b3d-a450-9c12c21964e3" />


- [ ] Tests added?



Diff:
diff --git a/assets/css/v2/elevator/closures.scss b/assets/css/v2/elevator/closures.scss
index fe989544e..f519fb283 100644
--- a/assets/css/v2/elevator/closures.scss
+++ b/assets/css/v2/elevator/closures.scss
@@ -11,6 +11,7 @@
   .in-station-summary {
     position: relative;
     display: flex;
+    align-items: center;
     justify-content: space-between;
     padding: 24px 48px;
 
@@ -29,6 +30,35 @@
     }
   }
 
+  .divider {
+    width: 936px;
+    height: 4px;
+    background-color: #73737364;
+    border-radius: 2px;
+  }
+
+  .no-closures {
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    gap: 80px;
+    padding: 148px 72px;
+    color: colors.$cool-black-15;
+
+    &__header {
+      font-size: 90px;
+      font-weight: 700;
+      line-height: 106px;
+    }
+
+    &__text {
+      margin-bottom: 30px;
+      font-size: 70px;
+      font-weight: 400;
+      line-height: 92px;
+    }
+  }
+
   .closures-info {
     height: 100%;
     background-color: colors.$warm-neutral-90;
@@ -40,6 +70,7 @@
       font-size: 80px;
       font-weight: 400;
       line-height: 88px;
+      color: colors.$cool-black-30;
 
       & > * {
         margin-bottom: 40px;
@@ -54,6 +85,7 @@
         font-size: var(--size);
         font-weight: 700;
         line-height: 1;
+        color: colors.$cool-black-15;
       }
 
       &__postfix {
diff --git a/assets/css/v2/elevator/normal.scss b/assets/css/v2/elevator/normal.scss
index cdf209a7c..8e5eb06a1 100644
--- a/assets/css/v2/elevator/normal.scss
+++ b/assets/css/v2/elevator/normal.scss
@@ -1,6 +1,9 @@
 .screen-normal {
   position: relative;
+  display: flex;
+  flex-direction: column;
   width: 1080px;
+  height: 1920px;
   margin-right: auto;
   margin-left: auto;
 }
@@ -11,12 +14,13 @@
 }
 
 .screen-normal__body {
+  flex: 1;
   width: 1080px;
-  height: 1632px;
 }
 
 .screen-normal__footer {
   position: relative;
+  flex: 0;
   width: 1080px;
-  height: 184px;
+  max-height: 184px;
 }
diff --git a/assets/src/components/v2/elevator/closures.tsx b/assets/src/components/v2/elevator/closures.tsx
index 4d70af723..53058fb0f 100644
--- a/assets/src/components/v2/elevator/closures.tsx
+++ b/assets/src/components/v2/elevator/closures.tsx
@@ -9,9 +9,10 @@ import PagingIndicators from "Components/v2/elevator/paging_indicators";
 import { type StationWithClosures } from "Components/v2/elevator/types";
 import useIntervalPaging from "Hooks/v2/use_interval_paging";
 import CalendarIcon from "Images/svgr_bundled/calendar.svg";
-import CalendarAlertIcon from "Images/svgr_bundled/calendar-alert.svg";
 import NormalServiceIcon from "Images/svgr_bundled/normal-service.svg";
+import CalenderAlertIcon from "Images/svgr_bundled/calendar-alert.svg";
 import AccessibilityAlert from "Images/svgr_bundled/accessibility-alert.svg";
+import Logo from "Images/svgr_bundled/logo.svg";
 import { hasOverflowX } from "Util/utils";
 import { CLOSURES_PAGING_INTERVAL_MS } from "./constants";
 
@@ -141,7 +142,7 @@ const UpcomingClosure = ({
 
   return (
     <div className="upcoming-closure">
-      <CalendarAlertIcon width={224} />
+      <CalenderAlertIcon height={249} width={249} />
       <div className="upcoming-closure__title" ref={titleRef}>
         {title}:
       </div>
@@ -161,6 +162,38 @@ const UpcomingClosure = ({
   );
 };
 
+const NoCurrentClosures = ({ closure }: { closure?: UpcomingClosureInfo }) => {
+  return (
+    <>
+      {closure ? (
+        <div className="closures-info">
+          <div className="in-station-summary">
+            <div>All MBTA elevators are working.</div>
+            <div>
+              <NormalServiceIcon width={72} height={72} fill="#145A06" />
+            </div>
+          </div>
+          <UpcomingClosure closure={closure} />
+        </div>
+      ) : (
+        <div className="no-closures">
+          <NormalServiceIcon height={150} width={150} fill="#145A06" />
+          <div className="no-closures__header">
+            All MBTA elevators are working.
+          </div>
+          <div className="divider" />
+          <div className="no-closures__text">
+            For info on elevator outages and alternate paths:{" "}
+            <b>mbta.com/elevators</b> or call the elevator hotline:{" "}
+            <b>617-222-2828</b>
+          </div>
+          <Logo height={124} width={124} />
+        </div>
+      )}
+    </>
+  );
+};
+
 const sortStations = (
   stations: StationWithClosures[],
   thisStationId: string,
@@ -180,9 +213,11 @@ const sortStations = (
     }
   });
 
+type ClosuresStatus = "no_closures";
+
 interface Props extends WrappedComponentProps {
   id: string;
-  stations_with_closures: StationWithClosures[];
+  stations_with_closures: StationWithClosures[] | ClosuresStatus;
   station_id: string;
   upcoming_closure?: UpcomingClosureInfo;
 }
@@ -193,9 +228,11 @@ const Closures = ({
   upcoming_closure: upcomingClosure,
   updateVisibleData,
 }: Props) => {
-  const numClosuresInStation = stations
-    .filter((s) => s.id === stationId)
-    .flatMap((s) => s.closures).length;
+  const numClosuresInStation =
+    typeof stations === "string"
+      ? 0
+      : stations.filter((s) => s.id === stationId).flatMap((s) => s.closures)
+          .length;
 
   const listRef = useRef<HTMLDivElement>(null);
 
@@ -253,41 +290,52 @@ const Closures = ({
 
   return (
     <div className="elevator-closures">
-      <InStationSummary
-        numClosures={numClosuresInStation}
-        upcomingClosure={isUpcomingClosurePage ? undefined : upcomingClosure}
-      />
-
-      <div className="closures-info">
-        {isUpcomingClosurePage ? (
-          <UpcomingClosure closure={upcomingClosure} />
-        ) : (
-          <div className="header-container">
-            <div className="header">
-              <div className="header__title">MBTA Elevator Closures</div>
-              <div>
-                <AccessibilityAlert height={128} width={155} />
+      {stations === "no_closures" ? (
+        <NoCurrentClosures closure={upcomingClosure} />
+      ) : (
+        <>
+          <InStationSummary
+            numClosures={numClosuresInStation}
+            upcomingClosure={
+              isUpcomingClosurePage ? undefined : upcomingClosure
+            }
+          />
+
+          <div className="closures-info">
+            {isUpcomingClosurePage ? (
+              <UpcomingClosure closure={upcomingClosure} />
+            ) : (
+              <div className="header-container">
+                <div className="header">
+                  <div className="header__title">MBTA Elevator Closures</div>
+                  <div>
+                    <AccessibilityAlert height={128} width={155} />
+                  </div>
+                </div>
               </div>
+            )}
+
+            <div className="closure-list-container">
+              {
+                <div
+                  className="closure-list"
+                  ref={listRef}
+                  style={listOffsetStyle}
+                >
+                  {sortStations(stations, stationId).map((station, index) => (
+                    <ClosureRow
+                      isCurrentStation={station.id == stationId}
+                      isFirstRowOnPage={firstRowIndices.includes(index)}
+                      key={station.id}
+                      station={station}
+                    />
+                  ))}
+                </div>
+              }
             </div>
           </div>
-        )}
-
-        <div className="closure-list-container">
-          {
-            <div className="closure-list" ref={listRef} style={listOffsetStyle}>
-              {sortStations(stations, stationId).map((station, index) => (
-                <ClosureRow
-                  isCurrentStation={station.id == stationId}
-                  isFirstRowOnPage={firstRowIndices.includes(index)}
-                  key={station.id}
-                  station={station}
-                />
-              ))}
-            </div>
-          }
-        </div>
-      </div>
-
+        </>
+      )}
       {numPages > 1 && (
         <div className="paging-info-container">
           {!isUpcomingClosurePage && numRowsOffPage > 0 && (
diff --git a/lib/screens/v2/candidate_generator/elevator/closures.ex b/lib/screens/v2/candidate_generator/elevator/closures.ex
index 290bcefe6..c96aa48aa 100644
--- a/lib/screens/v2/candidate_generator/elevator/closures.ex
+++ b/lib/screens/v2/candidate_generator/elevator/closures.ex
@@ -65,14 +65,35 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do
     active_closures = Enum.flat_map(active, &elevator_closure/1)
     at_this_elevator? = fn %Closure{id: id} -> id == elevator_id end
 
+    {:ok, %Stop{id: stop_id}} = @facility.fetch_stop_for_facility(elevator_id)
+
+    relevant_closures =
+      Enum.filter(active_closures, &relevant_closure?(&1, stop_id, active_closures))
+
     case Enum.find(active_closures, at_this_elevator?) do
       nil ->
         upcoming_closures =
           upcoming |> Enum.flat_map(&elevator_closure/1) |> Enum.filter(at_this_elevator?)
 
+        footer =
+          if Enum.empty?(relevant_closures) and Enum.empty?(upcoming_closures),
+            do: [],
+            else: [%Footer{screen: config}]
+
         [
-          elevator_closures(active_closures, upcoming_closures, app_params, now)
-          | header_footer_instances(config, now)
+          elevator_closures(
+            relevant_closures,
+            active_closures,
+            upcoming_closures,
+            app_params,
+            now,
+            stop_id
+          ),
+          header_instances(
+            config,
+            now
+          )
+          | footer
         ]
 
       _closure ->
@@ -86,7 +107,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do
   defp header_footer_instances(
          %Screen{app_params: %ElevatorConfig{elevator_id: elevator_id}} = config,
          now,
-         variant \\ nil
+         variant
        ) do
     [
       %NormalHeader{text: "Elevator #{elevator_id}", screen: config, time: now, variant: variant},
@@ -94,6 +115,14 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do
     ]
   end
 
+  defp header_instances(
+         %Screen{app_params: %ElevatorConfig{elevator_id: elevator_id}} = config,
+         now,
+         variant \\ nil
+       ) do
+    %NormalHeader{text: "Elevator #{elevator_id}", screen: config, time: now, variant: variant}
+  end
+
   defp elevator_closure(%Alert{
          id: id,
          active_period: active_periods,
@@ -132,12 +161,30 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do
   defp elevator_closure(_alert), do: []
 
   defp elevator_closures(
+         [],
+         _active_closures,
+         upcoming_closures,
+         app_params,
+         now,
+         stop_id
+       ) do
+    %ElevatorClosures{
+      app_params: app_params,
+      now: now,
+      station_id: stop_id,
+      stations_with_closures: :no_closures,
+      upcoming_closure: build_upcoming_closure(upcoming_closures)
+    }
+  end
+
+  defp elevator_closures(
+         relevant_closures,
          active_closures,
          upcoming_closures,
          %ElevatorConfig{elevator_id: elevator_id} = app_params,
-         now
+         now,
+         stop_id
        ) do
-    {:ok, %Stop{id: stop_id}} = @facility.fetch_stop_for_facility(elevator_id)
     {:ok, station_names} = @stop.fetch_parent_station_name_map()
     station_route_pills = fetch_station_route_pills(active_closures, stop_id)
 
@@ -147,8 +194,8 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do
       station_id: stop_id,
       stations_with_closures:
         build_stations_with_closures(
+          relevant_closures,
           active_closures,
-          stop_id,
           station_names,
           station_route_pills,
           elevator_id
@@ -174,14 +221,13 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do
   end
 
   defp build_stations_with_closures(
-         closures,
-         home_station_id,
+         relevant_closures,
+         active_closures,
          station_names,
          station_route_pills,
          elevator_id
        ) do
-    closures
-    |> Enum.filter(&relevant_closure?(&1, home_station_id, closures))
+    relevant_closures
     |> log_station_closures(elevator_id)
     |> Enum.group_by(& &1.station_id)
     |> Enum.map(fn {station_id, station_closures} ->
@@ -197,7 +243,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do
             station_closures,
             fn %Closure{id: id, name: name} -> %WidgetClosure{id: id, name: name} end
           ),
-        summary: active_summary(station_closures, closures)
+        summary: active_summary(station_closures, active_closures)
       }
     end)
   end
diff --git a/lib/screens/v2/widget_instance/elevator_closures.ex b/lib/screens/v2/widget_instance/elevator_closures.ex
index cabcb4321..74086161b 100644
--- a/lib/screens/v2/widget_instance/elevator_closures.ex
+++ b/lib/screens/v2/widget_instance/elevator_closures.ex
@@ -119,7 +119,7 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosures do
   @type t :: %__MODULE__{
           app_params: Elevator.t(),
           now: DateTime.t(),
-          stations_with_closures: list(Station.t()),
+          stations_with_closures: list(Station.t()) | :no_closures,
           station_id: String.t(),
           upcoming_closure: Upcoming.t() | nil
         }
diff --git a/test/screens/v2/candidate_generator/elevator/closures_test.exs b/test/screens/v2/candidate_generator/elevator/closures_test.exs
index ad29db607..b8a8318f2 100644
--- a/test/screens/v2/candidate_generator/elevator/closures_test.exs
+++ b/test/screens/v2/candidate_generator/elevator/closures_test.exs
@@ -72,8 +72,41 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do
 
   describe "header and footer" do
     test "have no variant when current elevator is not closed", %{now: now} do
+      expect(@stop, :fetch_parent_station_name_map, fn ->
+        {:ok, %{"place-haecl" => "Haymarket"}}
+      end)
+
+      expect(@alert, :fetch_elevator_alerts_with_facilities, fn ->
+        alerts = [
+          build_alert(
+            informed_entities: [
+              %{stop: "place-haecl", facility: %{name: "Test 2", id: "facility-test-2"}}
+            ]
+          )
+        ]
+
+        {:ok, alerts}
+      end)
+
+      elevator_closures = %ElevatorClosures{
+        app_params: @screen.app_params,
+        now: now,
+        station_id: "place-test",
+        stations_with_closures: [
+          %ElevatorClosures.Station{
+            id: "place-haecl",
+            name: "Haymarket",
+            route_icons: [%{type: :text, text: "OL", color: :orange}],
+            closures: [
+              %Closure{id: "facility-test-2", name: "Test 2"}
+            ],
+            summary: "Visit mbta.com/elevators for more info"
+          }
+        ]
+      }
+
       assert [
-               _elevator_closures,
+               elevator_closures,
                %NormalHeader{screen: @screen, text: "Elevator 111", time: ^now, variant: nil},
                %Footer{screen: @screen, variant: nil}
              ] = Generator.elevator_status_instances(@screen, now)
@@ -93,6 +126,15 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do
       assert [_current_closed, %NormalHeader{variant: :closed}, %Footer{variant: :closed}] =
                Generator.elevator_status_instances(@screen, now)
     end
+
+    test "have footer not displayed and header with no variant when all elevators are working", %{
+      now: now
+    } do
+      assert [
+               _elevator_closures,
+               %NormalHeader{screen: @screen, text: "Elevator 111", time: ^now, variant: nil}
+             ] = Generator.elevator_status_instances(@screen, now)
+    end
   end
 
   describe "alternate path widget" do
@@ -213,12 +255,6 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do
     end
 
     test "filters out alerts with no facilities or more than one facility", %{now: now} do
-      expect(@stop, :fetch_parent_station_name_map, fn ->
-        {:ok, %{"place-haecl" => "Haymarket"}}
-      end)
-
-      expect(@route, :fetch, fn _ -> {:ok, [%Route{id: "Red", type: :subway}]} end)
-
       expect(@alert, :fetch_elevator_alerts_with_facilities, fn ->
         alerts = [
           build_alert(informed_entities: [%{stop: "place-haecl", facility: nil}]),
@@ -235,7 +271,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do
 
       logs =
         capture_log([level: :warning], fn ->
-          assert [%ElevatorClosures{stations_with_closures: []} | _] =
+          assert [%ElevatorClosures{stations_with_closures: :no_closures} | _] =
                    Generator.elevator_status_instances(@screen, now)
         end)
 
@@ -412,6 +448,42 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do
                | _
              ] = Generator.elevator_status_instances(@screen, now)
     end
+
+    test "shows only upcoming closure when all other elevators are working",
+         %{now: now} do
+      expect(@alert, :fetch_elevator_alerts_with_facilities, fn ->
+        upcoming_period = {DateTime.add(now, 1, :day), DateTime.add(now, 3, :day)}
+
+        alerts = [
+          build_alert(
+            active_period: [upcoming_period],
+            informed_entities: [%{stop: "place-test", facility: %{name: "Test", id: "111"}}]
+          )
+        ]
+
+        {:ok, alerts}
+      end)
+
+      expected_closures = %ElevatorClosures{
+        app_params: @screen.app_params,
+        now: now,
+        station_id: "place-test",
+        stations_with_closures: :no_closures,
+        upcoming_closure: %ElevatorClosures.Station{
+          id: "place-test",
+          name: "Place Test",
+          route_icons: [%{type: :text, text: "RL", color: :red}],
+          closures: [%Closure{id: "facility-test", name: "Test"}],
+          summary: nil
+        }
+      }
+
+      assert [
+               expected_closures,
+               %NormalHeader{screen: @screen, text: "Elevator 111", time: ^now, variant: nil},
+               %Footer{variant: nil}
+             ] = Generator.elevator_status_instances(@screen, now)
+    end
   end
 
   describe "upcoming closure" do


Reviews:
user: login: digitalcora
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T21:37:19Z
user: login: jacmrob
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T22:35:10Z
user: login: jacmrob
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T22:36:33Z
user: login: jacmrob
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T22:39:35Z
user: login: jacmrob
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T22:42:40Z
user: login: jacmrob
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T22:45:26Z
user: login: jacmrob
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T22:51:34Z
user: login: jacmrob
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T22:52:28Z
user: login: jacmrob
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T16:42:22Z
user: login: digitalcora
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T16:12:38Z
user: login: digitalcora
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T17:45:03Z
user: login: digitalcora
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T17:45:10Z
user: login: digitalcora
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T16:14:00Z
user: login: jacmrob
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T17:50:43Z
user: login: digitalcora
body: Thanks for tracking down that style issue! I had a couple more minor comments but otherwise looks good, pre-approving ✅
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T19:52:02Z

Review comments:
diff_hunk: @@ -86,14 +103,44 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do
   defp header_footer_instances(
          %Screen{app_params: %ElevatorConfig{elevator_id: elevator_id}} = config,
          now,
-         variant \\ nil
+         variant
        ) do
     [
       %NormalHeader{text: "Elevator #{elevator_id}", screen: config, time: now, variant: variant},
       %Footer{screen: config, variant: variant}
     ]
   end
 
+  defp header_footer_instances(
+         %Screen{app_params: %ElevatorConfig{elevator_id: elevator_id}} = config,
+         now,
+         variant,
+         closures
path: lib/screens/v2/candidate_generator/elevator/closures.ex
user: login: digitalcora
body: It's a bit confusing that this is now essentially two separate private functions, each of which is only used once (`header_footer_instances/3` which is only used in the current-elevator-closed case, and `header_footer_instances/4` which is only used in the normal case), but with some substantial duplication between them. We could try to re-consolidate them like this:

```elixir
defp header_footer_instances(
  %Screen{app_params: %ElevatorConfig{elevator_id: elevator_id}} = config,
  now,
  relevant_closures,
  variant \\ :nil
) do
  header = %NormalHeader{text: "Elevator #{elevator_id}", screen: config, time: now, variant: variant}
  if relevant_closures == [], do: [header, %Footer{screen: config, variant: variant}], else: [header]
end
```

...but the conditional logic then feels misplaced. When the header and footer were _always_ generated together, it made sense to have a function `header_footer_instances` which abstracted that away — now that they aren't, maybe the place where we're already making other decisions about _which_ widgets to generate would be a better place to do this?

```elixir
case Enum.find(active_closures, at_this_elevator?) do
      nil ->
        upcoming_closures =
          upcoming |> Enum.flat_map(&elevator_closure/1) |> Enum.filter(at_this_elevator?)

        footer = if relevant_closures == [], do: [], else: [%Footer{screen: config}]
        [elevator_closures(...), header_instance(config, now) | footer]

      _closure ->
        [
          %ElevatorAlternatePath{app_params: app_params},
          header_instance(config, now, :closed),
          %Footer{screen: config, variant: :closed}
        ]
end
```

(proposing introducing a `header_instance/3` which does only the "header" parts of `header_footer_instances`)
created_at: 2025-03-18T16:00:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -65,14 +65,31 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do
     active_closures = Enum.flat_map(active, &elevator_closure/1)
     at_this_elevator? = fn %Closure{id: id} -> id == elevator_id end
 
+    {:ok, %Stop{id: stop_id}} = @facility.fetch_stop_for_facility(elevator_id)
+
+    relevant_closures =
+      active_closures |> Enum.filter(&relevant_closure?(&1, stop_id, active_closures))
path: lib/screens/v2/candidate_generator/elevator/closures.ex
user: login: digitalcora
body: Conventional Elixir tends not to use a pipe operator when only a single function is being piped-into:

```elixir
Enum.filter(active_closures, &relevant_closure?(&1, stop_id, active_closures))
```
created_at: 2025-03-18T21:03:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -65,14 +65,31 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do
     active_closures = Enum.flat_map(active, &elevator_closure/1)
     at_this_elevator? = fn %Closure{id: id} -> id == elevator_id end
 
+    {:ok, %Stop{id: stop_id}} = @facility.fetch_stop_for_facility(elevator_id)
+
+    relevant_closures =
+      active_closures |> Enum.filter(&relevant_closure?(&1, stop_id, active_closures))
+
     case Enum.find(active_closures, at_this_elevator?) do
       nil ->
         upcoming_closures =
           upcoming |> Enum.flat_map(&elevator_closure/1) |> Enum.filter(at_this_elevator?)
 
         [
-          elevator_closures(active_closures, upcoming_closures, app_params, now)
-          | header_footer_instances(config, now)
+          elevator_closures(
+            relevant_closures,
+            active_closures,
path: lib/screens/v2/candidate_generator/elevator/closures.ex
user: login: digitalcora
body: I think we should only be passing in `relevant_closures` here, so all logic is consistently using the same set of closures.
created_at: 2025-03-18T21:04:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -197,7 +261,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do
             station_closures,
             fn %Closure{id: id, name: name} -> %WidgetClosure{id: id, name: name} end
           ),
-        summary: active_summary(station_closures, closures)
+        summary: active_summary(station_closures, active_closures)
path: lib/screens/v2/candidate_generator/elevator/closures.ex
user: login: digitalcora
body: The value here was previously "relevant closures" (filtered earlier in the pipeline), but now is _all_ active closures. It should stay `relevant_closures` so the existing logic of this function remains the same.
created_at: 2025-03-18T21:05:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,7 +20,7 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosures do
             id: Stop.id(),
             name: String.t(),
             route_icons: list(Route.icon()),
-            closures: list(Closure.t()),
+            closures: list(Closure.t()) | :no_closures,
path: lib/screens/v2/widget_instance/elevator_closures.ex
user: login: digitalcora
body: I don't think it makes sense to also add this value to an individual `Station` — we never generate this, and the frontend would not be able to do anything with it. Due to the way we construct `Station`s, the list of closures at a station is always non-empty. (This does point to the fact that we could be more specific in this type and say `nonempty_list(Closure.t())`, though that change would be unrelated to this feature; we could save it for a later time.)
created_at: 2025-03-18T21:08:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -119,7 +119,7 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosures do
   @type t :: %__MODULE__{
           app_params: Elevator.t(),
           now: DateTime.t(),
-          stations_with_closures: list(Station.t()),
+          stations_with_closures: list(Station.t()) | :no_closures,
path: lib/screens/v2/widget_instance/elevator_closures.ex
user: login: digitalcora
body: We could be more specific here now and say `nonempty_list(Station.t())`, since if the list were empty, we would instead use `:no_closures`.
created_at: 2025-03-18T21:09:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -71,27 +71,63 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do
   end
 
   describe "header and footer" do
-    test "have no variant when current elevator is not closed", %{now: now} do
+    test "have closed variant when current elevator is closed", %{now: now} do
+      expect(@alert, :fetch_elevator_alerts_with_facilities, fn ->
+        alerts = [
+          build_alert(
+            informed_entities: [%{stop: "place-test", facility: %{name: "Test", id: "111"}}]
+          )
+        ]
+
+        {:ok, alerts}
+      end)
+
+      assert [_current_closed, %NormalHeader{variant: :closed}, %Footer{variant: :closed}] =
+               Generator.elevator_status_instances(@screen, now)
+    end
+  end
+
+  describe "when all elevators are working" do
path: test/screens/v2/candidate_generator/elevator/closures_test.exs
user: login: digitalcora
body: The method of organization here is getting a little mixed-up: The existing `describe "header and footer"` and `describe "alternate path widget"` and etc. are grouping tests related to specific widgets, but the new `describe "when all elevators are working"` is grouping tests related to a specific data scenario. I think we should try to stick with one or the other, and it feels like the current scheme can accommodate this:

1. The case currently on L91 could go under `header and footer` — this asserts there is no footer at all when there are no relevant closures, and does not assert anything about the closures widget
2. The case currently on L98 could go under `closure list widget` — the point of this is to assert on the `:no_closures` behavior so I think the bits about upcoming closures could be cut (that continues to work exactly as we'd expect from other test cases, there's nothing exceptional about it in this case)
created_at: 2025-03-18T21:16:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -180,9 +199,11 @@ const sortStations = (
     }
   });
 
+type ClosuresStatus = "no_closures";
path: assets/src/components/v2/elevator/closures.tsx
user: login: digitalcora
body: Since this is only used in one place, I don't think we need a separate type definition for it; the prop could be defined as

```ts
stations_with_closures: StationWithClosures[] | "no_closures";
```

This way there is no cross-referencing needed to understand all possible values.
created_at: 2025-03-18T21:21:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -161,6 +162,24 @@ const UpcomingClosure = ({
   );
 };
 
+const NoCurrentClosures = () => {
+  return (
+    <div className="no-closures">
+      <NormalServiceIcon height={150} width={150} fill="#145A06" />
+      <div className="no-closures-header">All MBTA elevators are working.</div>
+      <svg height={4} width={936} className="divider">
+        <line x1="0" y1="4" x2={936} y2={4} />
+      </svg>
path: assets/src/components/v2/elevator/closures.tsx
user: login: digitalcora
body: This feels very specific to the dimensions/margins/etc. of the current screen layout, and I don't think it's necessary to do it that way — what if we replaced this entire `svg` element with:

```html
<hr />
```

and used CSS for the color and thickness?
created_at: 2025-03-18T21:24:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -161,6 +162,24 @@ const UpcomingClosure = ({
   );
 };
 
+const NoCurrentClosures = () => {
+  return (
+    <div className="no-closures">
+      <NormalServiceIcon height={150} width={150} fill="#145A06" />
+      <div className="no-closures-header">All MBTA elevators are working.</div>
path: assets/src/components/v2/elevator/closures.tsx
user: login: digitalcora
body: nit: We've been (somewhat haphazardly) following a BEM-style naming convention for classes, where discrete elements of a single component are named as e.g. `no-closures__header` (`no-closures` is the "block", `header` is the "element"). This makes it a little easier to understand at a glance which "things" are stand-alone components and which are sub-elements of a specific component. If an SCSS selector for an element is nested inside the selector for its block, you can also shorthand to e.g. `&__header`. An example of where this is done already on elevator screens is `.upcoming-closure__title`.
created_at: 2025-03-18T21:31:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -253,41 +276,52 @@ const Closures = ({
 
   return (
     <div className="elevator-closures">
-      <InStationSummary
-        numClosures={numClosuresInStation}
-        upcomingClosure={isUpcomingClosurePage ? undefined : upcomingClosure}
-      />
-
-      <div className="closures-info">
-        {isUpcomingClosurePage ? (
-          <UpcomingClosure closure={upcomingClosure} />
-        ) : (
-          <div className="header-container">
-            <div className="header">
-              <div className="header__title">MBTA Elevator Closures</div>
-              <div>
-                <AccessibilityAlert height={128} width={155} />
+      {stations === "no_closures" ? (
+        <NoCurrentClosures />
path: assets/src/components/v2/elevator/closures.tsx
user: login: digitalcora
body: This doesn't seem to include the design state mentioned in Asana where all elevators are working but there is an upcoming closure of the current elevator — with this current code, if all elevators are working, we unconditionally display the full-page NoCurrentClosures message, and there is no indication of an upcoming closure if there is one.
created_at: 2025-03-18T21:36:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -86,14 +103,44 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do
   defp header_footer_instances(
          %Screen{app_params: %ElevatorConfig{elevator_id: elevator_id}} = config,
          now,
-         variant \\ nil
+         variant
        ) do
     [
       %NormalHeader{text: "Elevator #{elevator_id}", screen: config, time: now, variant: variant},
       %Footer{screen: config, variant: variant}
     ]
   end
 
+  defp header_footer_instances(
+         %Screen{app_params: %ElevatorConfig{elevator_id: elevator_id}} = config,
+         now,
+         variant,
+         closures
path: lib/screens/v2/candidate_generator/elevator/closures.ex
user: login: jacmrob
body: That works for me. I had thought initially that having multiple functions was the "elixir-y" way to do a conditional, even if each was only used once for a particular condition. how do you typically handle situations in elixir that would be an if/else or some other conditional blocking in non-functional code? 
created_at: 2025-03-18T22:35:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -65,14 +65,31 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do
     active_closures = Enum.flat_map(active, &elevator_closure/1)
     at_this_elevator? = fn %Closure{id: id} -> id == elevator_id end
 
+    {:ok, %Stop{id: stop_id}} = @facility.fetch_stop_for_facility(elevator_id)
+
+    relevant_closures =
+      active_closures |> Enum.filter(&relevant_closure?(&1, stop_id, active_closures))
+
     case Enum.find(active_closures, at_this_elevator?) do
       nil ->
         upcoming_closures =
           upcoming |> Enum.flat_map(&elevator_closure/1) |> Enum.filter(at_this_elevator?)
 
         [
-          elevator_closures(active_closures, upcoming_closures, app_params, now)
-          | header_footer_instances(config, now)
+          elevator_closures(
+            relevant_closures,
+            active_closures,
path: lib/screens/v2/candidate_generator/elevator/closures.ex
user: login: jacmrob
body: I had thought so too but this led to some test failures -- see my comment below
created_at: 2025-03-18T22:36:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -197,7 +261,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do
             station_closures,
             fn %Closure{id: id, name: name} -> %WidgetClosure{id: id, name: name} end
           ),
-        summary: active_summary(station_closures, closures)
+        summary: active_summary(station_closures, active_closures)
path: lib/screens/v2/candidate_generator/elevator/closures.ex
user: login: jacmrob
body: Actually the value passed here was the pre-filtered closures (`closures` var is unmodified and was initialized as the `active_closures`, even when we were doing the filtering in this fn). Using `relevant_closures` here caused some test failures because I guess we weren't able to access the full summary given on the closure. (L409 in `closures_test.exs`)
created_at: 2025-03-18T22:39:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,7 +20,7 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosures do
             id: Stop.id(),
             name: String.t(),
             route_icons: list(Route.icon()),
-            closures: list(Closure.t()),
+            closures: list(Closure.t()) | :no_closures,
path: lib/screens/v2/widget_instance/elevator_closures.ex
user: login: jacmrob
body: I think I was confused on what this data model was for -- you're right it doesn't belong here and I'd vote just to revert this to what it was :) 
created_at: 2025-03-18T22:42:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -180,9 +199,11 @@ const sortStations = (
     }
   });
 
+type ClosuresStatus = "no_closures";
path: assets/src/components/v2/elevator/closures.tsx
user: login: jacmrob
body: I made this with the plan in mind of adding the "no relevant closures" case as another keyword (the other half of this ticket). That way the props def could remain `StationWithClosures[] | ClosuresStatus`. But I agree it isn't strictly necessary and maybe just adds boilerplate
created_at: 2025-03-18T22:45:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -253,41 +276,52 @@ const Closures = ({
 
   return (
     <div className="elevator-closures">
-      <InStationSummary
-        numClosures={numClosuresInStation}
-        upcomingClosure={isUpcomingClosurePage ? undefined : upcomingClosure}
-      />
-
-      <div className="closures-info">
-        {isUpcomingClosurePage ? (
-          <UpcomingClosure closure={upcomingClosure} />
-        ) : (
-          <div className="header-container">
-            <div className="header">
-              <div className="header__title">MBTA Elevator Closures</div>
-              <div>
-                <AccessibilityAlert height={128} width={155} />
+      {stations === "no_closures" ? (
+        <NoCurrentClosures />
path: assets/src/components/v2/elevator/closures.tsx
user: login: jacmrob
body: yeah I initially thought that the upcoming closure for this elevator was to be displayed in a smaller header above the "all clear" message, but I was looking at the wrong mocks 🤦 . I'll update with the future closure state following that mock. 
created_at: 2025-03-18T22:51:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -161,6 +162,24 @@ const UpcomingClosure = ({
   );
 };
 
+const NoCurrentClosures = () => {
+  return (
+    <div className="no-closures">
+      <NormalServiceIcon height={150} width={150} fill="#145A06" />
+      <div className="no-closures-header">All MBTA elevators are working.</div>
+      <svg height={4} width={936} className="divider">
+        <line x1="0" y1="4" x2={936} y2={4} />
+      </svg>
path: assets/src/components/v2/elevator/closures.tsx
user: login: jacmrob
body: sounds good. I'll probably just do it as a div. 
created_at: 2025-03-18T22:52:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -161,6 +162,24 @@ const UpcomingClosure = ({
   );
 };
 
+const NoCurrentClosures = () => {
+  return (
+    <div className="no-closures">
+      <NormalServiceIcon height={150} width={150} fill="#145A06" />
+      <div className="no-closures-header">All MBTA elevators are working.</div>
path: assets/src/components/v2/elevator/closures.tsx
user: login: jacmrob
body: cool I'll use that convention. thanks for explaining!
created_at: 2025-03-19T16:42:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -86,14 +103,44 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do
   defp header_footer_instances(
          %Screen{app_params: %ElevatorConfig{elevator_id: elevator_id}} = config,
          now,
-         variant \\ nil
+         variant
        ) do
     [
       %NormalHeader{text: "Elevator #{elevator_id}", screen: config, time: now, variant: variant},
       %Footer{screen: config, variant: variant}
     ]
   end
 
+  defp header_footer_instances(
+         %Screen{app_params: %ElevatorConfig{elevator_id: elevator_id}} = config,
+         now,
+         variant,
+         closures
path: lib/screens/v2/candidate_generator/elevator/closures.ex
user: login: digitalcora
body: > how do you typically handle situations in elixir that would be an if/else or some other conditional blocking in non-functional code?

Well, Elixir has if/else 😄 But here it's more the duplication I wanted to address rather than the specific approach to the conditional logic — it's easy to imagine a version of this that used `if` or `case` or `cond` all within a single function body, but still had the duplication.

In terms of extracting different branches of a conditional into separate functions, I think it's basically a judgment call on how much it helps or harms the readability of the code, with the operations either co-located or abstracted behind a function call; it's never _necessary_ to do one or the other, given the tools Elixir has for conditional logic. Is the output of the process nearly the same in all conditional branches, with just a couple differing fields? Maybe keep everything together and use local variables or put conditionals directly in the field assignments. Is the output _very_ different depending on which branch is chosen? Maybe put each one behind a descriptively-named function call.
created_at: 2025-03-20T16:12:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -141,7 +141,7 @@ const UpcomingClosure = ({
 
   return (
     <div className="upcoming-closure">
-      <CalendarAlertIcon width={224} />
+      <AccessibilityAlert height={249} width={249} />
path: assets/src/components/v2/elevator/closures.tsx
user: login: digitalcora
body: Note: I left a comment on the design because it seemed inconsistent whether we're using the "calendar alert" or "accessibility alert" icon for upcoming closures. If this is indeed an intentional design change, I think we can delete the "calendar alert" from the repo as this was the only place it was used.
created_at: 2025-03-20T16:49:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -161,6 +161,39 @@ const UpcomingClosure = ({
   );
 };
 
+const NoCurrentClosures = ({ closure }: { closure?: UpcomingClosureInfo }) => {
+  return (
+    <>
+      {closure ? (
+        <div className="closures-info">
+          <div className="small-info-strip">
path: assets/src/components/v2/elevator/closures.tsx
user: login: digitalcora
body: Since this "small info strip" is conceptually the same component as the "in-station summary", if we could at least combine their styles (using the same classes with naming adjusted a bit), we could ensure a consistent appearance.
created_at: 2025-03-20T16:52:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -65,14 +65,33 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do
     active_closures = Enum.flat_map(active, &elevator_closure/1)
     at_this_elevator? = fn %Closure{id: id} -> id == elevator_id end
 
+    {:ok, %Stop{id: stop_id}} = @facility.fetch_stop_for_facility(elevator_id)
+
+    relevant_closures =
+      Enum.filter(active_closures, &relevant_closure?(&1, stop_id, active_closures))
+
     case Enum.find(active_closures, at_this_elevator?) do
       nil ->
         upcoming_closures =
           upcoming |> Enum.flat_map(&elevator_closure/1) |> Enum.filter(at_this_elevator?)
 
+        footer =
+          if relevant_closures ++ upcoming_closures == [], do: [], else: [%Footer{screen: config}]
path: lib/screens/v2/candidate_generator/elevator/closures.ex
user: login: digitalcora
body: The `++` operator has to iterate through the entire left-hand side, so using a pattern like this to check for emptiness can be slow when the list is large (relatively speaking; it could probably have thousands of items before you'd really notice, since list iteration itself is extremely fast). The more idiomatic way to handle this would be `relevant_closures == [] and upcoming_closures == []`, or possibly `Enum.empty?(relevant_closures) and Enum.empty?(upcoming_closures)` if that reads better to you.
created_at: 2025-03-20T17:35:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -132,12 +159,30 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do
   defp elevator_closure(_alert), do: []
 
   defp elevator_closures(
+         [],
+         _active_closures,
+         upcoming_closures,
+         %ElevatorConfig{elevator_id: _elevator_id} = app_params,
path: lib/screens/v2/candidate_generator/elevator/closures.ex
user: login: digitalcora
body: The destructuring here is now entirely unused — this line could be replaced with just `app_params,`.
created_at: 2025-03-20T17:37:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -197,7 +261,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do
             station_closures,
             fn %Closure{id: id, name: name} -> %WidgetClosure{id: id, name: name} end
           ),
-        summary: active_summary(station_closures, closures)
+        summary: active_summary(station_closures, active_closures)
path: lib/screens/v2/candidate_generator/elevator/closures.ex
user: login: digitalcora
body: Oh, my bad, this does make sense: When displaying accessibility summaries, we need to know if any of a given elevator's backup elevators are also closed, and that's independent of whether or not the backup elevator was considered "relevant" for the purposes of building the overall closure list. I misread this. Thanks!
created_at: 2025-03-20T17:45:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -141,7 +141,7 @@ const UpcomingClosure = ({
 
   return (
     <div className="upcoming-closure">
-      <CalendarAlertIcon width={224} />
+      <AccessibilityAlert height={249} width={249} />
path: assets/src/components/v2/elevator/closures.tsx
user: login: digitalcora
body: Looks like Snehal decided to change this back to the original "calendar alert" for now to keep things consistent, so we can revert this change.
created_at: 2025-03-21T16:14:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -141,7 +141,7 @@ const UpcomingClosure = ({
 
   return (
     <div className="upcoming-closure">
-      <CalendarAlertIcon width={224} />
+      <AccessibilityAlert height={249} width={249} />
path: assets/src/components/v2/elevator/closures.tsx
user: login: jacmrob
body: yep saw that 👍 
created_at: 2025-03-21T17:50:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -141,7 +142,7 @@ const UpcomingClosure = ({
 
   return (
     <div className="upcoming-closure">
-      <CalendarAlertIcon width={224} />
+      <CalenderAlertIcon height={249} width={249} />
path: assets/src/components/v2/elevator/closures.tsx
user: login: digitalcora
body: Should we also revert the size change here? It's 224 in the design and is non-square (though it's actually 224 in _height_ rather than width, which might be a mistake in the original code).
created_at: 2025-03-21T19:44:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -71,14 +71,6 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do
   end
 
   describe "header and footer" do
-    test "have no variant when current elevator is not closed", %{now: now} do
-      assert [
-               _elevator_closures,
-               %NormalHeader{screen: @screen, text: "Elevator 111", time: ^now, variant: nil},
-               %Footer{screen: @screen, variant: nil}
-             ] = Generator.elevator_status_instances(@screen, now)
-    end
-
path: test/screens/v2/candidate_generator/elevator/closures_test.exs
user: login: digitalcora
body: I think this test case still covers a unique and relevant behavior: that the footer is present (with no variant) when the current elevator is not closed but there are active closures elsewhere. Rather than deleting it, we could add some setup that creates an active closure, so the footer is still generated.
created_at: 2025-03-21T19:47:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -213,12 +214,6 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do
     end
 
     test "filters out alerts with no facilities or more than one facility", %{now: now} do
-      expect(@stop, :fetch_parent_station_name_map, fn ->
-        {:ok, %{"place-haecl" => "Haymarket"}}
-      end)
-
-      expect(@route, :fetch, fn _ -> {:ok, [%Route{id: "Red", type: :subway}]} end)
-
path: test/screens/v2/candidate_generator/elevator/closures_test.exs
user: login: digitalcora
body: Nice that pushing some of the fetch logic further down also simplified the setup of this test.
created_at: 2025-03-21T19:48:22Z
author_association: CONTRIBUTOR

PR comments:
user: login: dmaltzan
created_at: 2025-03-17T16:50:51Z
body: @jacmrob for what it's worth, I would prefer if we could remove the footer
author_association: NONE
user: login: digitalcora
created_at: 2025-03-17T16:59:50Z
body: @dmaltzan I had a huddle with Jackie this morning and we resolved some implementation details about how to omit the footer when this screen is displayed. It should be no problem.
author_association: CONTRIBUTOR
user: login: jacmrob
created_at: 2025-03-17T21:02:10Z
body: New screenshots without the footer. I also modified the wording of the upcoming closures bar to be more readable/in-line with the mocks, represented below: 
![Screenshot 2025-03-17 at 2 49 00 PM](https://github.com/user-attachments/assets/7870ce70-b751-4520-9d3f-b97a486f1116)
![Screenshot 2025-03-17 at 3 07 08 PM](https://github.com/user-attachments/assets/85de44e4-517e-4b3d-bc65-43482e1e0e71)
![Screenshot 2025-03-17 at 3 09 23 PM](https://github.com/user-attachments/assets/d08af4ba-6079-49da-9936-64a2e7296fc1)



author_association: CONTRIBUTOR
user: login: digitalcora
created_at: 2025-03-18T15:27:13Z
body: @jacmrob Upcoming closures should not display as a status bar when all elevators are working — if there is an upcoming closure, it takes up the main body and the "all elevators are working" message goes into the status bar instead, [as outlined here](https://www.figma.com/design/UG6HV7HIsdR6B6QABOFm01/Elevator-Screens?m=auto&node-id=3413-23543&t=ioq6LJhQ8urSzxVY-1) (this is from the Asana task description).
author_association: CONTRIBUTOR
user: login: jacmrob
created_at: 2025-03-18T18:36:39Z
body: @digitalcora oops I think I was looking at the wrong mocks in figma. Just removed it.
author_association: CONTRIBUTOR
user: login: jacmrob
created_at: 2025-03-19T17:17:08Z
body: New screen for no closures + upcoming closure at current station case:

<img width="406" alt="Screenshot 2025-03-19 at 1 16 43 PM" src="https://github.com/user-attachments/assets/c6d38ced-39c2-4979-ac20-28c8efd92b5c" />

author_association: CONTRIBUTOR
user: login: digitalcora
created_at: 2025-03-20T16:24:15Z
body: Looking at the designs for this state, I think some spacing and/or font sizes might have changed since the upcoming-closure component was originally implemented:

Design | This Branch
--- | ---
<img width="248" alt="Screenshot 2025-03-20 at 12 15 40 PM" src="https://github.com/user-attachments/assets/bab5f0a1-1bea-44a5-8248-19c3a87daa2e" /> | <img width="280" alt="Screenshot 2025-03-20 at 12 16 13 PM" src="https://github.com/user-attachments/assets/4f0ff950-338e-4d6e-b2d9-dfecc6117418" />

In particular it's possible for the "accessible route" text to take up 3 lines _and_ for the date description at the bottom to take up 2 lines, and if either of those happened here I think there would be some clipping. Can you take a look at the spacing and sizing in this design and update the upcoming-closure styles (universally) to match?
author_association: CONTRIBUTOR
user: login: jacmrob
created_at: 2025-03-21T17:53:26Z
body: @digitalcora the reason the branch screenshot looked different than the upcoming closures mock was actually because the screen height was smaller because I didn't have the dynamic resizing working properly (so everything looked bigger). fixed that in my latest commit and verified that styles were matching the mock (only thing missing was text color was slightly different). 

<img width="405" alt="Screenshot 2025-03-21 at 1 53 19 PM" src="https://github.com/user-attachments/assets/8c0cbfa9-8a94-4781-aa0a-83a319a9b21c" />

author_association: CONTRIBUTOR