# Back Button + Flight Schedule Timeline (B.6) Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Add Angular-parity navigation (back button in header) and a schedule timeline (accordion with scheduled times + duration + days-of-week strip + week date-range note) to the React Online Board details page.

**Architecture:** Two independent feature directories. `DetailsBackButton` is a simple `<Link>`. `FlightSchedule` is a PrimeReact `Accordion` wrapping a Schedule tab, plus a persistent days-of-week strip below. A small `weekDateRange` helper uses `date-fns` ISO week functions. `IFlightLeg` gains an optional `daysOfWeek` field.

**Tech Stack:** React 18, PrimeReact `Accordion` + `AccordionTab`, `date-fns` (`startOfISOWeek`, `endOfISOWeek`, `parseISO`, `format`), React Router `<Link>`, Vitest + React Testing Library.

---

## File Structure

### New files

- `src/features/online-board/components/DetailsBackButton/DetailsBackButton.tsx`
- `src/features/online-board/components/DetailsBackButton/DetailsBackButton.scss`
- `src/features/online-board/components/DetailsBackButton/DetailsBackButton.test.tsx`
- `src/features/online-board/components/DetailsBackButton/index.ts`
- `src/features/online-board/components/FlightSchedule/weekDateRange.ts`
- `src/features/online-board/components/FlightSchedule/weekDateRange.test.ts`
- `src/features/online-board/components/FlightSchedule/DaysOfWeekStrip.tsx`
- `src/features/online-board/components/FlightSchedule/DaysOfWeekStrip.test.tsx`
- `src/features/online-board/components/FlightSchedule/FlightSchedule.tsx`
- `src/features/online-board/components/FlightSchedule/FlightSchedule.scss`
- `src/features/online-board/components/FlightSchedule/FlightSchedule.test.tsx`
- `src/features/online-board/components/FlightSchedule/index.ts`

### Modified files

- `src/features/online-board/types.ts` — add `IDaysOfWeek`, extend `IFlightLeg` with optional `daysOfWeek`
- `src/features/online-board/types.test.ts` — add test case
- `src/features/online-board/components/OnlineBoardDetailsPage.tsx` — swap PageTabs→DetailsBackButton, insert FlightSchedule
- `src/features/online-board/components/OnlineBoardDetailsPage.test.tsx` — update mocks/assertions

---

### Task 1: Extend `IFlightLeg` with `daysOfWeek`

**Files:**
- Modify: `src/features/online-board/types.ts`
- Modify: `src/features/online-board/types.test.ts`

- [ ] **Step 1: Add failing test**

Append to `src/features/online-board/types.test.ts` inside the existing `describe("online-board types extension")` block:

```typescript
  it("IDaysOfWeek has current and flight bit strings", () => {
    const d: IDaysOfWeek = { current: "1000010", flight: "1111111" };
    expect(d.current).toBe("1000010");
    expect(d.flight).toBe("1111111");
  });

  it("IFlightLeg accepts optional daysOfWeek", () => {
    const leg: Partial<IFlightLeg> = {
      daysOfWeek: { current: "1000010", flight: "1111111" },
    };
    expect(leg.daysOfWeek?.flight).toBe("1111111");
  });
```

Also update the imports at the top of `types.test.ts` to include `IDaysOfWeek`:

Find the existing `import type {` block and add `IDaysOfWeek,` to the list.

- [ ] **Step 2: Run test to verify fail**

Run: `pnpm vitest run src/features/online-board/types.test.ts`

Expected: FAIL — `IDaysOfWeek` is not exported.

- [ ] **Step 3: Extend `src/features/online-board/types.ts`**

Add a new exported interface near the `IFlightLegFlags` (just before the `IFlightLeg` definition):

```typescript
// ---------------------------------------------------------------------------
// Days of week
// ---------------------------------------------------------------------------

/**
 * Bit-string indicator of flight operation days.
 * Position 0 = Monday, position 6 = Sunday. `"1"` means active.
 */
export interface IDaysOfWeek {
  current: string;
  flight: string;
}
```

Then add `daysOfWeek?: IDaysOfWeek;` to the `IFlightLeg` interface (add at the end, preserving other fields):

```typescript
export interface IFlightLeg {
  arrival: IFlightLegArrivalStation;
  dayChange: number;
  departure: IFlightLegDepartureStation;
  equipment: { name?: string; code?: string } & IEquipmentFull;
  flags: IFlightLegFlags;
  flyingTime: string;
  index: number;
  operatingBy: { carrier?: string; flightNumber?: string };
  status: FlightStatus;
  updated: string;
  transition?: IFlightTransitions;
  daysOfWeek?: IDaysOfWeek;
}
```

- [ ] **Step 4: Run test to verify pass**

Run: `pnpm vitest run src/features/online-board/types.test.ts`

Expected: all tests pass.

- [ ] **Step 5: Commit**

```bash
git add src/features/online-board/types.ts src/features/online-board/types.test.ts
git commit -m "Extend IFlightLeg with optional daysOfWeek field"
```

---

### Task 2: `weekDateRange` Helper

**Files:**
- Create: `src/features/online-board/components/FlightSchedule/weekDateRange.ts`
- Create: `src/features/online-board/components/FlightSchedule/weekDateRange.test.ts`

- [ ] **Step 1: Write failing test**

```typescript
// weekDateRange.test.ts
import { describe, it, expect } from "vitest";
import { getWeekDateRange } from "./weekDateRange.js";

describe("getWeekDateRange", () => {
  it("returns Monday/Sunday for a mid-week date", () => {
    // 2026-04-15 is a Wednesday
    const result = getWeekDateRange("2026-04-15T10:00:00");
    expect(result.start).toBe("13.04.2026"); // Monday
    expect(result.end).toBe("19.04.2026");   // Sunday
  });

  it("returns Monday/Sunday for a Sunday input (same ISO week)", () => {
    // 2026-04-19 is a Sunday
    const result = getWeekDateRange("2026-04-19T10:00:00");
    expect(result.start).toBe("13.04.2026");
    expect(result.end).toBe("19.04.2026");
  });

  it("returns Monday/Sunday for a Monday input", () => {
    const result = getWeekDateRange("2026-04-13T10:00:00");
    expect(result.start).toBe("13.04.2026");
    expect(result.end).toBe("19.04.2026");
  });

  it("returns empty strings for invalid input", () => {
    const result = getWeekDateRange("not-a-date");
    expect(result).toEqual({ start: "", end: "" });
  });

  it("returns empty strings for undefined input", () => {
    const result = getWeekDateRange(undefined);
    expect(result).toEqual({ start: "", end: "" });
  });
});
```

- [ ] **Step 2: Verify fail**

Run: `pnpm vitest run src/features/online-board/components/FlightSchedule/weekDateRange.test.ts`

Expected: FAIL.

- [ ] **Step 3: Create `weekDateRange.ts`**

```typescript
import { parseISO, startOfISOWeek, endOfISOWeek, format, isValid } from "date-fns";

/**
 * Given a scheduled local-time ISO string, return the ISO-week bounds
 * (Monday/Sunday) as `dd.MM.yyyy` formatted strings.
 * Returns `{ start: "", end: "" }` for invalid or missing input.
 */
export function getWeekDateRange(scheduledLocal: string | undefined): {
  start: string;
  end: string;
} {
  if (!scheduledLocal) return { start: "", end: "" };
  const d = parseISO(scheduledLocal);
  if (!isValid(d)) return { start: "", end: "" };
  return {
    start: format(startOfISOWeek(d), "dd.MM.yyyy"),
    end: format(endOfISOWeek(d), "dd.MM.yyyy"),
  };
}
```

- [ ] **Step 4: Verify pass**

Run: `pnpm vitest run src/features/online-board/components/FlightSchedule/weekDateRange.test.ts`

Expected: 5 tests pass.

- [ ] **Step 5: Commit**

```bash
git add src/features/online-board/components/FlightSchedule/weekDateRange.ts src/features/online-board/components/FlightSchedule/weekDateRange.test.ts
git commit -m "Add weekDateRange helper for flight schedule note"
```

---

### Task 3: `DaysOfWeekStrip` Component

**Files:**
- Create: `src/features/online-board/components/FlightSchedule/DaysOfWeekStrip.tsx`
- Create: `src/features/online-board/components/FlightSchedule/DaysOfWeekStrip.test.tsx`

- [ ] **Step 1: Write failing test**

```tsx
// @vitest-environment jsdom
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { DaysOfWeekStrip } from "./DaysOfWeekStrip.js";

vi.mock("@/i18n/provider.js", () => ({
  useTranslation: () => ({ t: (k: string) => k }),
}));

describe("DaysOfWeekStrip", () => {
  it("renders 7 day boxes", () => {
    render(<DaysOfWeekStrip flightBitString="1111111" />);
    const boxes = screen.getAllByTestId(/^day-of-week-\d$/);
    expect(boxes).toHaveLength(7);
  });

  it("marks first 3 active, last 4 inactive for '1110000'", () => {
    render(<DaysOfWeekStrip flightBitString="1110000" />);
    expect(screen.getByTestId("day-of-week-0").className).not.toMatch(/--inactive/);
    expect(screen.getByTestId("day-of-week-1").className).not.toMatch(/--inactive/);
    expect(screen.getByTestId("day-of-week-2").className).not.toMatch(/--inactive/);
    expect(screen.getByTestId("day-of-week-3").className).toMatch(/--inactive/);
    expect(screen.getByTestId("day-of-week-6").className).toMatch(/--inactive/);
  });

  it("marks all active for '1111111'", () => {
    render(<DaysOfWeekStrip flightBitString="1111111" />);
    for (let i = 0; i < 7; i++) {
      expect(screen.getByTestId(`day-of-week-${i}`).className).not.toMatch(/--inactive/);
    }
  });

  it("marks all inactive for '0000000'", () => {
    render(<DaysOfWeekStrip flightBitString="0000000" />);
    for (let i = 0; i < 7; i++) {
      expect(screen.getByTestId(`day-of-week-${i}`).className).toMatch(/--inactive/);
    }
  });

  it("renders DAYS.1 through DAYS.7 labels in order", () => {
    render(<DaysOfWeekStrip flightBitString="1111111" />);
    expect(screen.getByTestId("day-of-week-0").textContent).toContain("DAYS.1");
    expect(screen.getByTestId("day-of-week-6").textContent).toContain("DAYS.7");
  });
});
```

- [ ] **Step 2: Verify fail**

Run: `pnpm vitest run src/features/online-board/components/FlightSchedule/DaysOfWeekStrip.test.tsx`

Expected: FAIL.

- [ ] **Step 3: Create `DaysOfWeekStrip.tsx`**

```tsx
import type { FC } from "react";
import { useTranslation } from "@/i18n/provider.js";

export interface DaysOfWeekStripProps {
  flightBitString: string;
}

const DAY_INDEXES = [0, 1, 2, 3, 4, 5, 6] as const;

export const DaysOfWeekStrip: FC<DaysOfWeekStripProps> = ({ flightBitString }) => {
  const { t } = useTranslation();

  return (
    <div className="days-of-week-strip">
      {DAY_INDEXES.map((i) => {
        const isActive = flightBitString[i] === "1";
        const className = isActive ? "day" : "day day--inactive";
        return (
          <span
            key={i}
            className={className}
            data-testid={`day-of-week-${i}`}
          >
            {t(`DAYS.${i + 1}`)}
          </span>
        );
      })}
    </div>
  );
};
```

- [ ] **Step 4: Verify pass**

Run: `pnpm vitest run src/features/online-board/components/FlightSchedule/DaysOfWeekStrip.test.tsx`

Expected: 5 tests pass.

- [ ] **Step 5: Commit**

```bash
git add src/features/online-board/components/FlightSchedule/DaysOfWeekStrip.tsx src/features/online-board/components/FlightSchedule/DaysOfWeekStrip.test.tsx
git commit -m "Add DaysOfWeekStrip component for flight schedule"
```

---

### Task 4: `FlightSchedule` Component + SCSS + Barrel

**Files:**
- Create: `src/features/online-board/components/FlightSchedule/FlightSchedule.tsx`
- Create: `src/features/online-board/components/FlightSchedule/FlightSchedule.scss`
- Create: `src/features/online-board/components/FlightSchedule/FlightSchedule.test.tsx`
- Create: `src/features/online-board/components/FlightSchedule/index.ts`

- [ ] **Step 1: Write failing test**

```tsx
// @vitest-environment jsdom
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { FlightSchedule } from "./FlightSchedule.js";
import type { ISimpleFlight } from "../../types.js";

vi.mock("@/i18n/provider.js", () => ({
  useTranslation: () => ({
    t: (k: string) => {
      if (k === "SHARED.NOTE-TIME-SCHEDULE") {
        return "Valid from {START_DATE} to {END_DATE}";
      }
      return k;
    },
  }),
}));

function makeFlight(overrides: { daysOfWeek?: { current: string; flight: string } }): ISimpleFlight {
  return {
    id: "X", routeType: "Direct", flyingTime: "2h 30m", status: "Scheduled",
    flightId: { carrier: "SU", flightNumber: "0022", suffix: "", date: "20260415" },
    operatingBy: { carrier: "SU", flightNumber: "0022" },
    leg: {
      arrival: {
        scheduled: { airport: "LED", airportCode: "LED", city: "SPB", cityCode: "LED", countryCode: "RU" },
        latest: { airport: "LED", airportCode: "LED", city: "SPB", cityCode: "LED", countryCode: "RU" },
        dispatch: "", gate: "", terminal: "",
        times: { scheduledArrival: { dayChange: { value: 0, title: "" }, local: "12:30", localTime: "", tzOffset: 0, utc: "" } },
      },
      dayChange: 0,
      departure: {
        scheduled: { airport: "SVO", airportCode: "SVO", city: "MOW", cityCode: "MOW", countryCode: "RU" },
        latest: { airport: "SVO", airportCode: "SVO", city: "MOW", cityCode: "MOW", countryCode: "RU" },
        dispatch: "", gate: "", terminal: "", checkingStatus: "Scheduled", parkingStand: "",
        times: { scheduledDeparture: { dayChange: { value: 0, title: "" }, local: "2026-04-15T10:00:00", localTime: "10:00", tzOffset: 0, utc: "" } },
      },
      equipment: {},
      flags: { checkinAvailable: false, returnToAirport: false, routeChanged: false },
      flyingTime: "2h 30m", index: 0, operatingBy: {}, status: "Scheduled", updated: "",
      ...overrides,
    },
  } as ISimpleFlight;
}

describe("FlightSchedule", () => {
  it("returns null when firstLeg has no daysOfWeek", () => {
    const { container } = render(<FlightSchedule flight={makeFlight({})} />);
    expect(container.firstChild).toBeNull();
  });

  it("returns null when daysOfWeek.flight is empty string", () => {
    const { container } = render(
      <FlightSchedule flight={makeFlight({ daysOfWeek: { current: "0000000", flight: "" } })} />,
    );
    expect(container.firstChild).toBeNull();
  });

  it("renders with data-testid=flight-schedule when daysOfWeek present", () => {
    render(
      <FlightSchedule flight={makeFlight({ daysOfWeek: { current: "1000010", flight: "1111111" } })} />,
    );
    expect(screen.getByTestId("flight-schedule")).toBeTruthy();
  });

  it("renders 7 day boxes from daysOfWeek.flight", () => {
    render(
      <FlightSchedule flight={makeFlight({ daysOfWeek: { current: "1000010", flight: "1110000" } })} />,
    );
    const boxes = screen.getAllByTestId(/^day-of-week-\d$/);
    expect(boxes).toHaveLength(7);
  });

  it("substitutes {START_DATE} and {END_DATE} into the note", () => {
    render(
      <FlightSchedule flight={makeFlight({ daysOfWeek: { current: "1000010", flight: "1111111" } })} />,
    );
    // 2026-04-15 is a Wednesday; ISO week Mon 13 - Sun 19
    expect(screen.getByTestId("flight-schedule-note").textContent).toContain("13.04.2026");
    expect(screen.getByTestId("flight-schedule-note").textContent).toContain("19.04.2026");
  });

  it("renders scheduled departure/arrival times and duration", () => {
    render(
      <FlightSchedule flight={makeFlight({ daysOfWeek: { current: "1000010", flight: "1111111" } })} />,
    );
    // Note: accordion open by default; times visible in content
    // departure time: the local ISO string's time component
    expect(screen.getByText("10:00")).toBeTruthy();
    expect(screen.getByText("12:30")).toBeTruthy();
    expect(screen.getByText("2h 30m")).toBeTruthy();
  });
});
```

- [ ] **Step 2: Verify fail**

Run: `pnpm vitest run src/features/online-board/components/FlightSchedule/FlightSchedule.test.tsx`

Expected: FAIL.

- [ ] **Step 3: Create `FlightSchedule.scss`**

```scss
.flight-schedule {
  background: #fff;
  border-radius: 8px;
  padding: 16px 24px;
  margin-top: 16px;

  .p-accordion-content {
    padding: 12px 0;
  }

  &__row {
    display: flex;
    justify-content: space-between;
    padding: 4px 0;
  }

  &__label { color: #666; }
  &__value { font-weight: 500; }

  &__days-section {
    margin-top: 16px;
    padding-top: 16px;
    border-top: 1px solid #eee;
  }

  &__section-title {
    font-size: 12px;
    color: #666;
    text-transform: uppercase;
    margin-bottom: 8px;
  }

  &__note {
    margin-top: 12px;
    font-size: 12px;
    color: #666;
    font-style: italic;
  }
}

.days-of-week-strip {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;

  .day {
    padding: 8px 12px;
    border-radius: 6px;
    background: #e6f1fb;
    font-size: 14px;
    font-weight: 500;
    color: #1a3a5c;

    &--inactive {
      background: #f6f6f6;
      color: rgba(102, 102, 102, 0.5);
    }
  }
}
```

- [ ] **Step 4: Create `FlightSchedule.tsx`**

```tsx
import type { FC } from "react";
import { Accordion, AccordionTab } from "primereact/accordion";
import { useTranslation } from "@/i18n/provider.js";
import type { ISimpleFlight } from "../../types.js";
import { DaysOfWeekStrip } from "./DaysOfWeekStrip.js";
import { getWeekDateRange } from "./weekDateRange.js";
import "./FlightSchedule.scss";

export interface FlightScheduleProps {
  flight: ISimpleFlight;
}

function formatLocalTime(iso: string | undefined): string {
  if (!iso) return "";
  // If it's already "HH:mm" (no date), return as-is
  if (/^\d{2}:\d{2}$/.test(iso)) return iso;
  // Otherwise try to extract time part from ISO: "2026-04-15T10:00:00" → "10:00"
  const match = /T(\d{2}:\d{2})/.exec(iso);
  return match ? match[1]! : iso;
}

export const FlightSchedule: FC<FlightScheduleProps> = ({ flight }) => {
  const { t } = useTranslation();

  const firstLeg = flight.routeType === "Direct" ? flight.leg : flight.legs[0];
  const lastLeg = flight.routeType === "Direct" ? flight.leg : flight.legs[flight.legs.length - 1];

  if (!firstLeg?.daysOfWeek?.flight) return null;
  if (!lastLeg) return null;

  const depLocal = firstLeg.departure.times.scheduledDeparture.local;
  const arrLocal = lastLeg.arrival.times.scheduledArrival.local;

  const { start, end } = getWeekDateRange(depLocal);
  const noteTemplate = t("SHARED.NOTE-TIME-SCHEDULE");
  const note = noteTemplate.replace("{START_DATE}", start).replace("{END_DATE}", end);

  return (
    <section className="flight-schedule" data-testid="flight-schedule">
      <Accordion multiple={false} activeIndex={0}>
        <AccordionTab header={t("SHARED.SCHEDULE-FLIGHT")}>
          <div className="flight-schedule__row">
            <span className="flight-schedule__label">{t("SHARED.DEPARTURE-SCHEDULED")}</span>
            <span className="flight-schedule__value">{formatLocalTime(depLocal)}</span>
          </div>
          <div className="flight-schedule__row">
            <span className="flight-schedule__label">{t("SHARED.ARRIVAL-SCHEDULED")}</span>
            <span className="flight-schedule__value">{formatLocalTime(arrLocal)}</span>
          </div>
          <div className="flight-schedule__row">
            <span className="flight-schedule__label">{t("SHARED.PATH-TIME")}</span>
            <span className="flight-schedule__value">{flight.flyingTime}</span>
          </div>
        </AccordionTab>
      </Accordion>

      <div className="flight-schedule__days-section">
        <div className="flight-schedule__section-title">
          {t("SHARED.DAYS-EXECUTE-FLIGHT")}
        </div>
        <DaysOfWeekStrip flightBitString={firstLeg.daysOfWeek.flight} />
        <div className="flight-schedule__note" data-testid="flight-schedule-note">{note}</div>
      </div>
    </section>
  );
};
```

- [ ] **Step 5: Create `index.ts`**

```typescript
export { FlightSchedule } from "./FlightSchedule.js";
export type { FlightScheduleProps } from "./FlightSchedule.js";
```

- [ ] **Step 6: Verify pass**

Run: `pnpm vitest run src/features/online-board/components/FlightSchedule/FlightSchedule.test.tsx`

Expected: 6 tests pass.

- [ ] **Step 7: Commit**

```bash
git add src/features/online-board/components/FlightSchedule/
git commit -m "Add FlightSchedule accordion with days-of-week strip"
```

---

### Task 5: `DetailsBackButton` Component

**Files:**
- Create: `src/features/online-board/components/DetailsBackButton/DetailsBackButton.tsx`
- Create: `src/features/online-board/components/DetailsBackButton/DetailsBackButton.scss`
- Create: `src/features/online-board/components/DetailsBackButton/DetailsBackButton.test.tsx`
- Create: `src/features/online-board/components/DetailsBackButton/index.ts`

- [ ] **Step 1: Write failing test**

```tsx
// @vitest-environment jsdom
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { DetailsBackButton } from "./DetailsBackButton.js";

vi.mock("@/i18n/provider.js", () => ({
  useTranslation: () => ({ t: (k: string) => k }),
}));

vi.mock("@modern-js/runtime/router", () => ({
  Link: ({ children, to, ...props }: { children: React.ReactNode; to: string; [k: string]: unknown }) => (
    <a href={to} {...props}>{children}</a>
  ),
}));

describe("DetailsBackButton", () => {
  it("has data-testid=details-back-button", () => {
    render(<DetailsBackButton locale="ru" />);
    expect(screen.getByTestId("details-back-button")).toBeTruthy();
  });

  it("links to /{locale}/onlineboard", () => {
    render(<DetailsBackButton locale="ru" />);
    const a = screen.getByTestId("details-back-button") as HTMLAnchorElement;
    expect(a.getAttribute("href")).toBe("/ru/onlineboard");
  });

  it("uses the provided locale in the link", () => {
    render(<DetailsBackButton locale="en" />);
    const a = screen.getByTestId("details-back-button") as HTMLAnchorElement;
    expect(a.getAttribute("href")).toBe("/en/onlineboard");
  });

  it("renders the BACK-BOARD label", () => {
    render(<DetailsBackButton locale="ru" />);
    expect(screen.getByText("SHARED.BACK-BOARD")).toBeTruthy();
  });
});
```

- [ ] **Step 2: Verify fail**

Run: `pnpm vitest run src/features/online-board/components/DetailsBackButton/DetailsBackButton.test.tsx`

Expected: FAIL.

- [ ] **Step 3: Create `DetailsBackButton.scss`**

```scss
.details-back-button {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px;
  background: #e3f0ff;
  color: #1a3a5c;
  border-radius: 6px;
  text-decoration: none;
  font-size: 14px;
  font-family: inherit;
  cursor: pointer;

  &:hover {
    background: #c7dff5;
  }

  &__arrow {
    display: inline-block;
    font-size: 16px;
  }
}
```

- [ ] **Step 4: Create `DetailsBackButton.tsx`**

```tsx
import type { FC } from "react";
import { Link } from "@modern-js/runtime/router";
import { useTranslation } from "@/i18n/provider.js";
import "./DetailsBackButton.scss";

export interface DetailsBackButtonProps {
  locale: string;
}

export const DetailsBackButton: FC<DetailsBackButtonProps> = ({ locale }) => {
  const { t } = useTranslation();
  return (
    <Link
      to={`/${locale}/onlineboard`}
      className="details-back-button"
      data-testid="details-back-button"
    >
      <span className="details-back-button__arrow" aria-hidden="true">{"\u2190"}</span>
      <span>{t("SHARED.BACK-BOARD")}</span>
    </Link>
  );
};
```

- [ ] **Step 5: Create `index.ts`**

```typescript
export { DetailsBackButton } from "./DetailsBackButton.js";
export type { DetailsBackButtonProps } from "./DetailsBackButton.js";
```

- [ ] **Step 6: Verify pass**

Run: `pnpm vitest run src/features/online-board/components/DetailsBackButton/DetailsBackButton.test.tsx`

Expected: 4 tests pass.

- [ ] **Step 7: Commit**

```bash
git add src/features/online-board/components/DetailsBackButton/
git commit -m "Add DetailsBackButton component for header navigation"
```

---

### Task 6: Wire Both into `OnlineBoardDetailsPage`

**Files:**
- Modify: `src/features/online-board/components/OnlineBoardDetailsPage.tsx`
- Modify: `src/features/online-board/components/OnlineBoardDetailsPage.test.tsx`

- [ ] **Step 1: Add integration tests**

Append to the outer describe block in `OnlineBoardDetailsPage.test.tsx`:

```tsx
describe("back button integration", () => {
  it("renders DetailsBackButton in headerLeft", () => {
    mockState = { flight: mockFlight, allFlights: [mockFlight], daysOfFlight: ["20260416"], loading: false, error: null };
    render(<OnlineBoardDetailsPage flightId={mockFlightId} locale="ru" canonicalOrigin="https://example.com" />);
    expect(screen.getByTestId("details-back-button")).toBeTruthy();
  });
});

describe("flight schedule integration", () => {
  it("renders FlightSchedule when firstLeg.daysOfWeek is present", () => {
    const flightWithDaysOfWeek = {
      ...mockFlight,
      leg: {
        ...mockFlight.leg,
        daysOfWeek: { current: "1000010", flight: "1111111" },
      },
    };
    mockState = { flight: flightWithDaysOfWeek, allFlights: [flightWithDaysOfWeek], daysOfFlight: ["20260416"], loading: false, error: null };
    render(<OnlineBoardDetailsPage flightId={mockFlightId} locale="ru" canonicalOrigin="https://example.com" />);
    expect(screen.getByTestId("flight-schedule")).toBeTruthy();
  });

  it("does not render FlightSchedule when daysOfWeek is absent", () => {
    mockState = { flight: mockFlight, allFlights: [mockFlight], daysOfFlight: ["20260416"], loading: false, error: null };
    render(<OnlineBoardDetailsPage flightId={mockFlightId} locale="ru" canonicalOrigin="https://example.com" />);
    expect(screen.queryByTestId("flight-schedule")).toBeNull();
  });
});
```

- [ ] **Step 2: Run tests — expect failure**

Run: `pnpm vitest run src/features/online-board/components/OnlineBoardDetailsPage.test.tsx`

Expected: the 3 new tests fail (testids not present).

- [ ] **Step 3: Update imports in `OnlineBoardDetailsPage.tsx`**

Add near other component imports:

```tsx
import { DetailsBackButton } from "./DetailsBackButton/index.js";
import { FlightSchedule } from "./FlightSchedule/index.js";
```

- [ ] **Step 4: Swap PageTabs for DetailsBackButton**

In `OnlineBoardDetailsPage.tsx`, find `const commonLayoutProps` (around line 194):

```tsx
  const commonLayoutProps = {
    headerLeft: <PageTabs viewType="onlineboard" />,
    breadcrumbs: [
      { label: t("BREADCRUMBS.ONLINEBOARD"), url: onlineboardHref },
    ],
  };
```

Change to:

```tsx
  const commonLayoutProps = {
    headerLeft: <DetailsBackButton locale={locale} />,
    breadcrumbs: [
      { label: t("BREADCRUMBS.ONLINEBOARD"), url: onlineboardHref },
    ],
  };
```

Also find the main happy-path return's `<PageLayout>` opening:

```tsx
      <PageLayout
        headerLeft={<PageTabs viewType="onlineboard" />}
        title={<h1 className="flight-details__flight-number">{flightNumber}</h1>}
```

Change to:

```tsx
      <PageLayout
        headerLeft={<DetailsBackButton locale={locale} />}
        title={<h1 className="flight-details__flight-number">{flightNumber}</h1>}
```

The `PageTabs` import becomes unused — remove it from the import list at the top of the file.

- [ ] **Step 5: Insert `<FlightSchedule />` after the flight legs**

In the main return, find the closing tag after the flying-time div (after `<FlightLegs legs={legs} />` + the flying-time div, just before closing `</div>` of `.flight-details`):

```tsx
          {/* Flying time */}
          <div className="flight-details__flying-time" data-testid="flying-time">
            Total flying time: {displayFlight.flyingTime}
          </div>
        </div>
```

Insert `<FlightSchedule flight={displayFlight} />` after the flying-time div (before the `</div>` closing `.flight-details`):

```tsx
          {/* Flying time */}
          <div className="flight-details__flying-time" data-testid="flying-time">
            Total flying time: {displayFlight.flyingTime}
          </div>

          <FlightSchedule flight={displayFlight} />
        </div>
```

- [ ] **Step 6: Run tests — expect pass**

Run: `pnpm vitest run src/features/online-board/components/OnlineBoardDetailsPage.test.tsx`

Expected: all tests pass, including the 3 new integration tests.

- [ ] **Step 7: Full suite + typecheck**

Run: `pnpm test`
Run: `pnpm typecheck`

Expected: no new failures, no new type errors. If any integration tests elsewhere (e.g., `tests/integration/online-board/*.test.tsx`) reference `PageTabs` via the details page, they may need the `DetailsBackButton` testid instead of the `onlineboard-tab` testid — fix those test assertions minimally.

- [ ] **Step 8: Commit**

```bash
git add src/features/online-board/components/OnlineBoardDetailsPage.tsx src/features/online-board/components/OnlineBoardDetailsPage.test.tsx
git commit -m "Wire DetailsBackButton and FlightSchedule into OnlineBoardDetailsPage"
```

---

### Task 7: Manual Browser Verification

**Files:** None — observational.

- [ ] **Step 1: Ensure dev:full is running**

Run `pnpm dev:full`. Verify `/api/appSettings` returns 200.

- [ ] **Step 2: Run the verification script**

```bash
cat << 'SCRIPT' | npx tsx --input-type=module -
import { chromium } from "@playwright/test";
import { mockAngularAPIs } from "./tests/e2e-angular/support/angular-api-mock.js";

const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } });
const page = await ctx.newPage();
await mockAngularAPIs(page);

const depUtc = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
await page.route("**/onlineboard/details*", (route) => {
  route.fulfill({
    status: 200, contentType: "application/json",
    body: JSON.stringify({
      data: {
        partners: [],
        routes: [{
          id: "SU0022-X", routeType: "Direct", flyingTime: "1h 30m", status: "Scheduled",
          flightId: { carrier: "SU", flightNumber: "0022", suffix: "", date: "20260418", dateLT: "20260418" },
          operatingBy: { carrier: "SU", flightNumber: "0022" },
          leg: {
            index: 0, flyingTime: "1h 30m", status: "Scheduled", updated: "2026-04-17T10:00:00Z", dayChange: 0,
            flags: { checkinAvailable: false, returnToAirport: false, routeChanged: false },
            operatingBy: { carrier: "SU", flightNumber: "0022" },
            daysOfWeek: { current: "1000010", flight: "1111100" },
            departure: {
              scheduled: { airport: "SVO", airportCode: "SVO", city: "MOW", cityCode: "MOW", countryCode: "RU" },
              latest: { airport: "SVO", airportCode: "SVO", city: "MOW", cityCode: "MOW", countryCode: "RU" },
              dispatch: "", gate: "", terminal: "", checkingStatus: "Scheduled", parkingStand: "",
              times: { scheduledDeparture: { dayChange: { value: 0, title: "" }, local: "2026-04-18T10:00:00", localTime: "10:00", tzOffset: 3, utc: depUtc } },
            },
            arrival: {
              scheduled: { airport: "LED", airportCode: "LED", city: "SPB", cityCode: "LED", countryCode: "RU" },
              latest: { airport: "LED", airportCode: "LED", city: "SPB", cityCode: "LED", countryCode: "RU" },
              dispatch: "", gate: "", terminal: "",
              times: { scheduledArrival: { dayChange: { value: 0, title: "" }, local: "2026-04-18T12:30:00", localTime: "12:30", tzOffset: 3, utc: "" } },
            },
            equipment: {},
          },
        }],
        daysOfFlight: ["20260418"],
      },
    }),
  });
});

await page.goto("http://localhost:8080/ru/onlineboard/SU0022-20260418", { waitUntil: "networkidle" });
await page.waitForTimeout(3000);

const backBtn = await page.locator('[data-testid="details-back-button"]').count();
const schedule = await page.locator('[data-testid="flight-schedule"]').count();
const dayBoxes = await page.locator('[data-testid^="day-of-week-"]').count();
const noteText = await page.locator('[data-testid="flight-schedule-note"]').textContent().catch(() => "");

console.log("backBtn:", backBtn);
console.log("schedule:", schedule);
console.log("dayBoxes:", dayBoxes);
console.log("note:", noteText);

await page.screenshot({ path: "/tmp/b6-verify.png", fullPage: true });

await browser.close();
SCRIPT
```

Expected: `backBtn: 1, schedule: 1, dayBoxes: 7, note: "Valid from 13.04.2026 to 19.04.2026"` (or Russian equivalent depending on the real i18n key value).

- [ ] **Step 3: No commit — observational**
