Files
qwen3-6-lora/data/raw/sanitized/plans/podemos-realizar-el-plan-expressive-crown.md
T

13 KiB
Raw Blame History

GFIBER-603 — Make the UI fully responsive

Context

Jira GFIBER-603 (High priority, "Tarea", assigned to a.barrientos, In Development):

The team has observed during shadowing that agents normally have multiple windows open on the same screen. Upsell Evaluator would usually occupy between 1/4 and 1/3 of the total width. The app should be adaptable so that it's perfectly usable in these sizes.

Acceptance Criteria:

  • The app is fully responsive
  • All input fields are easily accessible and readable from 1/3 and 1/4 of the screen width

This is not mobile-phone responsiveness — it's a desktop browser window resized narrow (≈320640px: 1/4 of a 1366px laptop ≈340px, 1/3 of a 1920px monitor ≈640px), mouse-driven, used by call-center agents running the Upsell Evaluator alongside CRM/phone software. That range sits entirely below Tailwind's default sm breakpoint (640px), so a mobile-first stacking approach (unstyled/base = narrow, sm:+ = normal desktop) fits naturally with zero custom breakpoints needed — the project has no tailwind.config.js (Tailwind v4, CSS-based config, defaults sm:640 md:768 lg:1024 xl:1280 untouched).

The repo has an established design system (packages/design-system/, 51 components, consumed via transpilePackages from source, DS-first convention: new/changed UI goes in the DS with story+Jest+Cypress). Investigation (Explore agent + manual verification) found the actual defects are app-level compositions (inline styles / missing responsive classes in page/NavBar code), not DS component bugs — FlowStepper, TextField, Select, and Textarea were all checked and are already width-agnostic (no fixed-px widths). So no new DS components are required; fixes are Tailwind-class changes in existing app files, consistent with the DS-first convention (these are app-level compositions with no 1:1 DS component, same category as UpsellEvaluator.tsx's existing upsell.css).

Scope

Per user decision, this covers the full AC-literal reading ("the app is fully responsive") — Upsell Evaluator/My Usage and ranking/admin grids, not just the narrow-window tool.

  1. app/(protected)/upsell-evaluator/_flow/UpsellEvaluator.tsx (lines 183218) — the core two-column flow shell, currently inline style={{}}, zero responsive behavior: outer wrapper maxWidth: 1080, <aside> width: 220, flexShrink: 0, position: sticky, <main> flex: 1. This is the highest-priority fix — it's the actual tool agents run in the narrow window.
  2. components/NavBar.tsx — present on every page including Upsell Evaluator. Single always-visible flex row (brand + 24 links + up to 5 right-side icons: StreakBell, AttendanceBell, ThemeToggle, LogoutButton, TourButton), only one responsive class in the whole file. Will overflow/clip at 320480px. Fixed via a collapsible hamburger menu (see below).
  3. app/(protected)/upsell-evaluator/_history/TicketDetailView.tsx:44 (grid-cols-2) and SessionHistoryView.tsx:119 (grid-cols-3) — confirmed (via grep) these render under /my-metrics (app/(protected)/my-metrics/page.tsx and [id]/page.tsx), reached from the "My Usage" NavBar link right next to Upsell Evaluator.
  4. Two dashboard tables missing overflow-x-auto (have overflow-y-auto only, forcing page-level horizontal overflow instead of a contained scroll): app/(protected)/dashboard/page.tsx:607-608 (min-w-[760px]) and components/dashboard/AllTeamsCard.tsx:321-322 (min-w-[700px]). One-line fix each, matching the already-correct sibling components/dashboard/ValidationsCard.tsx:123-124 (overflow-x-auto + min-w-[600px]).
  5. components/ranking/LeaguesLadder.tsx:96 (grid-cols-4) and components/ranking/RankingConfigPanel.tsx:169,202,241 (grid-cols-2 ×3) — add responsive variants (e.g. grid-cols-2 sm:grid-cols-4 / grid-cols-1 sm:grid-cols-2) so these admin/ranking panels also reflow instead of squeezing at narrow widths.

Verify-only, no code change expected (check visually during QA, fix only if actually broken): components/upsell/CopilotWidget.tsx (w-[380px] max-w-[calc(100vw-2rem)] — already viewport-clamped) and components/NavBar/StreakBell.tsx:107 (w-64 popover anchored right-0 inside the icon cluster).

Implementation approach

1. UpsellEvaluator.tsx — convert inline styles to Tailwind, stack below sm

Replace the inline-styled shell (lines 183193) with:

<div className="upsell-flow" style={{ minHeight: "calc(100vh - 57px)" }}>
  <div className="mx-auto flex max-w-[1080px] flex-col items-stretch gap-4 p-4 sm:flex-row sm:items-start sm:gap-6 sm:p-6">
    <aside aria-label="Flow navigation" className="w-full flex-shrink-0 sm:sticky sm:top-4 sm:w-[220px]">
      <FlowStepper .../>
      <div className="mt-3"><Button variant="ghost" onClick={newCall}>...</Button></div>
      {blocked && <div className="mt-3"><Badge variant="danger">Hard stop active</Badge></div>}
    </aside>
    <main className="min-w-0 flex-1">...</main>
  </div>

Below sm (640px): aside goes full-width and stacks above main content. At sm: and up: reverts to today's sticky 220px sidebar + flex-1 main — no visual change for normal desktop use. FlowStepper needs no changes (already flex-direction: column; width: 100%, no fixed-px widths); the narrowness problem was entirely the parent <aside>'s hardcoded width. Leave the saveToast inline style block (lines 160181) as-is unless touching it is free — not a responsiveness bug (centered fixed toast is width-agnostic).

2. NavBar.tsx — make the existing (orphaned) DS AppHeader component responsive and adopt it

Per user decision, the responsive collapse behavior itself belongs in the design system as a real component, not composed ad hoc in the app. packages/design-system/src/components/AppHeader/index.tsx already exists with almost exactly the right shape (brand?: ReactNode, nav?: ..., actions?: ReactNode) but is presentational, non-responsive (fixed height: 64px row, no collapse), and unused anywhere in the app (grep confirms zero imports outside its own Storybook/tests) — so there's no adoption-migration risk, and enhancing it in place is strictly additive.

DS-side changes (packages/design-system/src/components/AppHeader/):

  • Widen the nav prop from a structured {id,label,href,active}[] array to ReactNode (a slot, same shape as actions already is). This is the key change the user is asking for: the DS component stops knowing about hrefs/active-state/routing and just arranges whatever nav content the app hands it — so NavBar.tsx keeps building its role-gated links with next/link's Link (prefetch, client-side nav) exactly as it does today, and simply passes that finished JSX in as a prop instead of AppHeader re-rendering plain <a> tags.
  • Add "use client" + internal open state (mobile menu). Reuse the sibling Drawer component from the same package (import { Drawer } from '../Drawer' or via the barrel) — no new slide-over logic needed, Drawer already has focus-trap, Tab-trap, Escape, backdrop-close, and focus-restore.
  • style.module.scss: add a ≥768px media query (matching Tailwind's md) that shows the existing full-width .nav/.actions row and hides a new hamburger .trigger button; below 768px, hide .nav/.actions from the row and show .trigger, which opens the Drawer containing the same nav and actions nodes.
  • Add the standard DS file set for this change: update AppHeader.stories.tsx (add a narrow-viewport/mobile story), AppHeader.test.tsx (Jest: trigger opens Drawer, Escape/backdrop closes it, content renders in both desktop and mobile paths), AppHeader.test.cy.tsx (Cypress: real viewport resize + click-through + focus-trap), per the repo's DS-first convention (every component ships with story + Jest + Cypress).

App-side changes (components/NavBar.tsx):

  • Stays a Server Component — all existing responsibilities (role gating via profile, building Link elements, composing StreakBell/AttendanceBell/ThemeToggle/LogoutButton/TourButton) are unchanged. Confirmed division of responsibility: NavBar.tsx decides what to show (which links per role, which icons); AppHeader only decides how to arrange it responsively. AppHeader never sees Profile/Supabase types — this preserves the DS = UI / app = business-logic boundary already established in GFIBER-585 (Paso 3 — "DS = estilos/UI; app = lógica de negocio").
  • Replace the current hand-rolled <nav>/<div> markup with <AppHeader brand={...} nav={<>...existing Link elements...</>} actions={<>...existing icon cluster...</>} /> imported from @gfiber/design-system. Passing server-built JSX as props into a client component (AppHeader) is a standard, supported RSC pattern (the children/props aren't re-executed on the client) — no NavBar behavior changes.
  • Considered and rejected: reusing KebabMenu (wrong semantics/shape for primary nav — actions-menu, not nav, no next/link support) and building a brand-new, separate DS component instead of enhancing AppHeader (would leave AppHeader as dead, non-representative Penpot-sourced code and create two overlapping "app header" components in the DS).

3. TicketDetailView.tsx / SessionHistoryView.tsx grids

grid-cols-2grid-cols-1 sm:grid-cols-2 (TicketDetailView:44); grid-cols-3grid-cols-1 sm:grid-cols-3 (SessionHistoryView:119) — adjust to sm:grid-cols-2 lg:grid-cols-3 instead if 3-up reads cramped exactly at 640px during QA.

4. Dashboard tables

- <div className="max-h-[460px] overflow-y-auto">
+ <div className="max-h-[460px] overflow-x-auto overflow-y-auto">

Apply to both app/(protected)/dashboard/page.tsx:607-608 and components/dashboard/AllTeamsCard.tsx:321-322.

Verification

  • Vitest + Testing Library (existing suites for UpsellEvaluator.tsx, NavBar.tsx, TicketDetailView/SessionHistoryView): keep existing behavioral tests green (stage transitions, role-gated nav links render correctly into the nav/actions props); don't add tests whose only assertion is "this className string exists" — low value, jsdom has no real layout engine.
  • Design-system (Jest + Testing Library + Cypress) for the enhanced AppHeader: Jest test for open/close/focus-trap/Escape/backdrop-close and that nav/actions content renders identically in both the desktop row and the mobile Drawer; Cypress test with real viewport resize (e.g. 340/480/768px) verifying the trigger appears/disappears at the right breakpoint and the drawer is fully interactive. Update AppHeader.stories.tsx with a mobile/narrow story so it's visually reviewable in Storybook.
  • Cypress E2E (app-level, existing Upsell Evaluator flow spec): add cy.viewport(340, 720) / cy.viewport(480, 720) passes, re-running the same GAID lookup → stages → close interactions to catch real click-target/overlap regressions, including opening the header's mobile menu and navigating via it.
  • qa-validator (real browser) — the concrete check for the AC. Resize to 340px, 480px, and 640px (not just one width) and confirm:
    1. Upsell Evaluator: stepper stacks above stage content, no squeeze; every input/select/textarea fully visible, not clipped; all buttons (New Call, Continue/Back, ObjectionDrawer trigger) reachable without horizontal scroll.
    2. NavBar: below md:, only brand + hamburger trigger show; opening the menu reveals all links + icon cluster in the Drawer with working focus-trap/Escape/backdrop-close; nothing clipped or unreachable.
    3. /my-metrics and /my-metrics/[id]: grids stack to 1 column instead of squeezing.
    4. /dashboard: the two fixed tables scroll horizontally within their own box instead of blowing out page width.
    5. /ranking and admin config panels: grids reflow instead of squeezing.
    6. Verify-only items: CopilotWidget panel and StreakBell popover (now inside the Drawer) don't clip at 320340px.
    7. Screenshots at each width for the PR description, as evidence against the AC.

Suggested commit/PR breakdown

Single PR (feat/gfiber-603-responsive-upsell-evaluator):

  1. UpsellEvaluator.tsx shell → Tailwind + stacking.
  2. packages/design-system/src/components/AppHeader/ → responsive rework (nav/actions widened to ReactNode, "use client", internal Drawer-based mobile menu, updated story/Jest/Cypress).
  3. components/NavBar.tsx → adopt AppHeader from @gfiber/design-system, passing existing role-gated Link elements and icon cluster as nav/actions props (server-component responsibilities unchanged).
  4. TicketDetailView.tsx + SessionHistoryView.tsx → responsive grids.
  5. dashboard/page.tsx + AllTeamsCard.tsxoverflow-x-auto.
  6. ranking/LeaguesLadder.tsx + ranking/RankingConfigPanel.tsx → responsive grid-cols-N.
  7. DS Jest/Cypress/Storybook updates for AppHeader; app Cypress narrow-viewport passes (including mobile-menu navigation via the new header).
  8. qa-validator pass at 340/480/640px across Upsell Evaluator, My Usage, Dashboard, and Ranking/Admin pages; screenshots attached to PR description.