docs(skills): define Kandev mobile UI language (#1820)

* docs(skills): define Kandev mobile UI language

* docs(skills): restore mobile fixture import guidance
This commit is contained in:
José Almeida
2026-07-20 17:34:59 +01:00
committed by GitHub
parent 3479fb601e
commit f9956a90d5
3 changed files with 148 additions and 31 deletions
+42 -31
View File
@@ -1,6 +1,6 @@
---
name: mobile-parity
description: Ensures UI feature work ships with desktop and mobile parity, responsive behavior, and mobile Playwright E2E coverage. Use when implementing, planning, reviewing, or testing any new feature, page, component, workflow, form, dialog, sidebar, navigation, dashboard, or visual UI change; if work touches frontend or user-facing UI, this skill must run even when user mentions only desktop or says "new feature".
description: Ensures Kandev UI work uses native mobile interaction patterns instead of compressed desktop adaptations while preserving desktop/mobile capability parity, responsive behavior, and mobile Playwright E2E coverage. Use when implementing, planning, reviewing, or testing any new feature, page, component, workflow, form, dialog, sidebar, navigation, dashboard, or visual UI change; if work touches frontend or user-facing UI, this skill must run even when user mentions only desktop or says "new feature".
kandev:
system: true
version: "0.51.0"
@@ -9,7 +9,9 @@ kandev:
# Mobile Parity
Use this skill before changing UI code for a feature. Goal: feature works on desktop and mobile, and tests prove the mobile path.
Use this skill before planning or changing UI. Goal: desktop and mobile deliver the same user value, while each viewport gets an intentional composition and tests prove the mobile path.
Before proposing a mobile design, read [Kandev Mobile UI Language](references/kandev-mobile-ui-language.md) and inspect the closest shipped mobile surface. Responsive CSS alone does not establish mobile parity.
## When It Applies
@@ -21,35 +23,56 @@ Apply when task changes user-facing UI:
If task has no UI surface, say why this skill does not apply and continue.
## Mobile Design Contract
When a task changes composition, navigation, overlays, touch behavior, scrolling, or breakpoint behavior, state these choices in the working plan or task notes. Keep them brief; do not create a separate document unless the task already uses a spec or committed plan. For copy, icon, color, or content-only styling inside an unchanged surface, identify the nearest mobile exemplar and the rendered mobile check instead of forcing the full contract.
- desktop user outcome and mobile entry point
- nearest shipped mobile exemplar and which interaction/geometry it contributes
- mobile information hierarchy and primary action
- presentation choice: inline, inset bottom drawer, full-height surface, or direct navigation
- surface rationale: why task frequency and content depth make that choice preferable to the alternatives
- single scroll owner, dynamic viewport behavior, safe-area handling, and touch targets
- shared state, view-model, filtering/selection, and business logic versus mobile-specific presentation
- mobile Playwright scenario proving the same user value
## Workflow
1. Map affected surfaces.
- Identify every page, modal, menu, tab, empty state, loading state, and error state the feature touches.
- Check where desktop layout assumptions can fail: fixed widths, hover-only controls, sidebars, tables, dense toolbars, keyboard shortcuts, overflow, and absolute positioning.
- Use `rg` to find the nearest existing mobile component and the route's current `useResponsiveBreakpoint` branch. Name the closest curated exemplar from the reference and state which parts are reused.
- Treat live code as the source of truth for APIs, state, and current behavior. Treat this skill's curated guide and exemplar list as the desired mobile interaction baseline; nearby legacy surfaces that deviate from it need justification, not automatic reuse.
2. Design desktop and mobile behavior together.
- Prefer existing responsive patterns in the repo.
- Define mobile navigation, stacking order, scrolling region, touch targets, and truncated text behavior before coding.
- Make primary actions reachable without hover and without horizontal page scroll.
- Preserve capability, data, and state semantics; do not require identical markup or navigation.
- Apply the surface decision guide in the mobile UI language reference. Prefer a focused, one-dimensional phone flow over stacked desktop panes or two-axis scrolling.
- Explicitly distinguish temporary choices that belong in a drawer from primary or dense content that deserves direct navigation or a full-height surface.
- When a card or row has an obvious primary destination and no competing selection, drag, or inline-control behavior, make its body tap perform that action. Otherwise expose an explicit touch control. Put secondary actions behind a visible target, never hover, right-click, or undiscoverable long press.
- Define mobile navigation, hierarchy, scroll owner, touch targets, truncation, empty/error states, and responsive fallback behavior before coding.
3. Implement responsive UI.
- Use semantic controls and existing design-system components.
- Reuse domain hooks, state, view-model derivation, filtering/selection, and action handlers across viewports; keep responsive wrappers focused on presentation. Branch composition when the desktop interaction model depends on width or a fine pointer. Do not mount a heavyweight desktop workbench and merely hide or squeeze it on phones.
- Use `useResponsiveBreakpoint` and existing `@kandev/ui` or mobile primitives. Reuse current Dropdown/ContextMenu primitives for contextual actions; use `Drawer` or an existing picker shell for structured phone navigation and choices. Use `useTouchDrawer` when a hover disclosure needs a coarse-pointer alternative.
- Keep touch targets large enough for touch use, generally at least 44px in the active dimension.
- Ensure forms, dialogs, sheets, menus, tables, and empty states fit narrow screens.
- Use dynamic viewport units and an explicit internal scroll region for viewport-bound/full-height or potentially overflowing surfaces. Ensure bottom-fixed controls and tall drawers clear safe-area insets; short drawers can retain the shared primitive's intrinsic sizing. Keep document-level horizontal overflow at zero.
- If mobile substitutes an unsupported desktop view, derive an effective mobile view without overwriting the user's saved desktop preference.
- Use semantic controls, visible labels or accessible names, focus return, and existing design-system components.
- Avoid hiding required functionality on mobile unless there is a clear alternate path.
4. Add E2E coverage.
- Add or update Playwright tests for the feature's happy path on desktop if missing.
- Add mobile Playwright coverage for the same user value, using existing mobile projects/devices when configured.
- In this repo, name mobile test files `mobile-*.spec.ts` so the `mobile-chrome` Playwright project picks them up automatically.
- If no mobile project exists, configure the test or project with a realistic mobile viewport plus touch/mobile settings.
- Cover mobile-specific controls such as drawers, overflow menus, stacked actions, responsive tables, or bottom controls.
- Cover the actual mobile composition: drawer or full-height surface, visible overflow action, focused navigation, direct route, or bottom control.
- For overlay and dense-navigation changes, assert viewport containment, internal scrolling, and the absence of document horizontal overflow where those properties are part of the regression.
- When a touch-only control is replaced or hidden, run `rg` across mobile E2E tests for the removed control. Replace every affected interaction with the intended gesture or alternate control, then run those tests together.
5. Verify visually and behaviorally.
- Run the narrowest relevant viewport locally or with screenshots when possible.
- Even small user-facing UI tweaks need at least focused rendered verification when feasible: dev-server/browser check, Playwright screenshot, or targeted E2E. If not run, report the exact reason.
- Check that text does not overlap, controls remain clickable, focus/keyboard flows still work, and no unintended horizontal scroll appears.
- Check phone ergonomics, not only responsive fit: entry point is discoverable, primary action is thumb-reachable, hierarchy is understandable, sheet shape matches nearby surfaces, and back/dismiss behavior is predictable.
- Check that text does not overlap, controls remain clickable, focus/keyboard flows still work, safe-area content is unobstructed, and no unintended horizontal scroll appears.
- Run the focused Playwright tests. If full E2E cannot run, report the command and blocker.
- E2E runs against the production Vite build served by the Go backend, not a dev server, so rebuild after frontend changes: `make build-web` (and `make build-backend` for Go), or use `make test-e2e` which rebuilds both. Skipping this silently tests stale code. See `/e2e`.
@@ -71,34 +94,22 @@ Good mobile tests assert real user outcomes, not only visibility. Prefer:
- handle empty/error/loading state on narrow viewport
- confirm no required action is desktop-only
## Playwright Pattern
## Playwright Routing
Use repo conventions first. In this repo, create `mobile-*.spec.ts` files and let the `mobile-chrome` project apply the mobile device; do not add per-test device overrides. Follow `/e2e` conventions for fixture imports, page objects, selectors, and local reproduction. Adjust the fixture import path to the spec location: top-level `tests/mobile-*.spec.ts` files use `../fixtures/test-base`, while nested files use paths such as `../../fixtures/test-base`.
```ts
import { test, expect } from "../../fixtures/test-base";
test.describe("feature on mobile", () => {
test("completes primary workflow", async ({ testPage }) => {
await testPage.goto("/feature");
await testPage.getByRole("button", { name: /menu|open/i }).click();
await testPage.getByRole("link", { name: /feature/i }).click();
await expect(
testPage.getByRole("heading", { name: /feature/i }),
).toBeVisible();
await testPage.getByRole("button", { name: /primary action/i }).click();
await expect(testPage.getByText(/success|saved|created/i)).toBeVisible();
});
});
```
Create `mobile-*.spec.ts` files and let the `mobile-chrome` project apply its configured Pixel 5 device; do not add per-test device overrides. Follow `/e2e` for fixtures, page objects, selectors, build requirements, and local reproduction. Top-level `tests/mobile-*.spec.ts` files import `../fixtures/test-base`; nested specs adjust the depth, commonly using `../../fixtures/test-base`. Mobile parity owns which interaction and geometry contracts need proof; `/e2e` owns test mechanics.
## Done Checklist
- Desktop path still works.
- Mobile path has designed layout and interaction behavior.
- Structural/touch changes include a mobile design contract naming entry point, nearest shipped exemplar, hierarchy, surface, scroll owner, and primary action.
- For those changes, surface rationale explains why the chosen composition fits task frequency and content depth.
- Content-only styling changes identify the nearest mobile exemplar and focused rendered mobile check instead.
- Mobile path follows a shipped Kandev pattern or explains why a new pattern is needed.
- Mobile composition is intentional, not desktop UI stacked, squeezed, or hidden with CSS.
- Required controls are reachable by touch.
- No required workflow depends on hover, wide viewport, or hidden desktop-only UI.
- Viewport-bound/full-height or potentially overflowing surfaces use dynamic viewport sizing, safe-area clearance, and internal scrolling.
- Responsive fallbacks do not overwrite saved desktop preferences.
- Mobile E2E tests no longer invoke touch controls that the change replaced or hid.
- Mobile Playwright coverage exists or absence is justified.
- Focused rendered/visual verification was run for UI tweaks, or exact "not run" reason is reported.
@@ -0,0 +1,99 @@
# Kandev Mobile UI Language
Use this reference to turn a desktop capability into a phone-native Kandev experience. Capability parity does not mean layout parity. Preserve user outcomes, permissions, state, and error handling; change composition and interaction when a narrow touch viewport needs it.
This curated guide and its exemplar list define the desired mobile interaction baseline. Other live code remains authoritative for APIs and current behavior, but a nearby surface may predate this language; do not copy a deviation merely because it is close to the new feature.
## Core Principles
1. **One focal task at a time.** Replace multi-pane and multi-column desktop layouts with a focused mobile surface. Make hierarchy visible through a compact navigator, then let the main list or content own vertical scrolling.
2. **Primary tap goes somewhere useful.** When a card or row has an obvious main destination and no competing selection, drag, or inline-control behavior, its body tap should perform that action. Otherwise provide a visible explicit control. Put secondary actions behind an ellipsis or labeled control; do not make long press, hover, or right-click the only path.
3. **Temporary choices rise from the bottom.** Phone pickers, menus, and navigation choices normally use an inset bottom `Drawer`. Keep side sheets and anchored popovers for tablet/desktop unless a nearby phone surface establishes another pattern.
4. **Mobile can simplify presentation, not capability.** It may focus one workflow, hide an unsuitable visualization, or navigate instead of previewing. Required actions remain reachable, and mobile-only fallbacks must not overwrite desktop preferences.
5. **Viewport geometry is product behavior.** Safe areas, browser chrome, on-screen keyboards, scroll ownership, and touch target size are part of correctness.
6. **Share logic, specialize composition.** Reuse store state, domain hooks, view-model derivation, filtering/selection, permissions, and action handlers. Keep viewport wrappers focused on rendering. Use a dedicated mobile layout or responsive surface wrapper when desktop structure itself is wrong for phones.
## Surface Decision Guide
| Desktop interaction | Preferred phone composition | Notes |
|---|---|---|
| Dropdown or context menu | Visible trigger plus compact bottom-sheet menu | Reuse existing Radix menu primitives and their responsive treatment. Test nested menus and long content. |
| Hover disclosure | Fine-pointer `Popover`; coarse-pointer `Drawer` | Use `useTouchDrawer`; hover cannot be the only touch path. |
| Click/search combobox popover | Keep Popover when touch-usable and viewport-contained; otherwise use `Drawer` | Base the choice on option count, search depth, keyboard behavior, and nearby curated precedent. |
| Temporary sidebar or navigation rail | Inset bottom `Drawer` | Give it a fixed header, `min-h-0` scrolling body, and bottom safe-area padding. |
| Task-workbench header picker | `MobilePillButton` opening `MobilePickerSheet` | Show current context and chevron so the control reads as interactive. Outside the task domain, reuse the interaction pattern without importing a domain-owned component blindly. |
| Multi-column board or long horizontal tabs | One focused column/item plus current-context navigator | Offer previous/next or swipe shortcuts and a drawer containing the full hierarchy. Avoid document-level horizontal scroll. |
| Master/detail or floating preview | Direct navigation or a dedicated full-height mobile surface | Do not squeeze both panes onto one screen or add an unnecessary intermediate action sheet. |
| Dense content viewer such as diff, file, or chat | Dedicated route/layout or full-height drawer | Preserve one clear internal scroll owner and use `100dvh`/`h-dvh`. |
| Persistent desktop toolbar | Compact top/bottom controls; optional safe-area-aware FAB for the primary action | Keep the primary action visible and thumb-reachable. Move secondary actions into an explicit overflow surface. |
| Desktop-only visualization | Mobile-native effective fallback | Derive the fallback at render/selection time; do not persist it over the user's desktop choice. |
Choose based on task frequency and content depth. A frequently revisited primary destination may deserve a route or bottom navigation instead of a drawer. A short, temporary choice should not take over the whole screen.
## Composition and Feel
- Phone breakpoint is below 640px. Use `useResponsiveBreakpoint`; it distinguishes phone, tablet, compact desktop, and full desktop using width plus pointer mode.
- Reuse `@kandev/ui` primitives. `Drawer` is the standard bottom surface; `Sheet` remains useful for tablet/desktop side panels.
- Use inset, rounded card surfaces with restrained borders/backgrounds and shadow, matching nearby components. Prefer existing component classes over copying a frozen class list.
- Give new primary touch actions, standalone icon buttons, and menu rows an actual hitbox of at least 44px in the active dimension (`min-h-11`, `h-11 w-11`). Existing compact chrome such as `MobilePillButton` is a deliberate density exception, not a sizing precedent for unrelated controls; do not claim surrounding spacing enlarges its hitbox. Provide pressed feedback such as the existing short `active:scale-*` treatment where nearby controls use it.
- Keep labels and current context visible. Use truncation for long names, tabular number badges for counts, and accessible names that include enough hierarchy to disambiguate the action.
- Use `100dvh`/`h-dvh`, not `100vh`, for viewport-filling phone layouts. Account for `env(safe-area-inset-*)` on fixed controls and drawer content.
- In flex surfaces, the usual scroll body is `min-h-0 flex-1 overflow-y-auto overscroll-contain`. Keep headers/actions fixed and avoid nested vertical scrollers unless the interaction truly needs them.
- Keep page/document horizontal overflow at zero. A deliberately scrollable code/table region must contain its own overflow without making navigation two-dimensional.
- Let the existing coarse-pointer input rule keep editable controls at 16px or larger so iOS does not zoom on focus.
- Preserve focus movement, Escape/back dismissal, focus return, and semantic button/link behavior. Touch-first does not mean keyboard-inaccessible.
## State and Responsive Boundaries
- Share domain state and mutations between mobile and desktop. Split presentation before duplicating business logic.
- Branch early when the desktop workbench is expensive or structurally unsuitable. Avoid mounting it and hiding it with CSS on phones.
- Treat tablet separately when current routing does. A phone bottom drawer is not automatically the right tablet surface.
- Keep responsive fallbacks effective rather than persisted. Example: render the mobile-supported view while retaining the user's saved desktop view.
- When live data removes the focused item, choose a deterministic visible fallback. Do not leave an empty shell or update state in a render/effect loop.
- When switching context inside an open drawer, keep it open if the next choice is part of the same hierarchy; close it when the user's selection completes the task.
## Current Code Precedents
Inspect these live files before implementing; use `rg` to find their replacements if paths move.
- `apps/web/hooks/use-responsive-breakpoint.ts` — canonical breakpoint and pointer model.
- `apps/web/hooks/use-compact-task-chrome.ts` — `useTouchDrawer` for coarse-pointer alternatives to hover disclosures.
- `apps/web/components/task/task-layout.tsx` — dedicated mobile, tablet, and desktop compositions sharing task data.
- `apps/web/components/kanban/mobile-menu-sheet.tsx` — phone `Drawer` versus wider `Sheet`, inset card, fixed header, safe-area-aware internal scroll.
- `apps/web/components/task/mobile/session-task-switcher-sheet.tsx` — rich task navigation in an inset bottom drawer while wider layouts retain a side sheet.
- `apps/web/components/task/mobile/mobile-picker-sheet.tsx` and `mobile-pill-button.tsx` — reusable bottom picker and discoverable header trigger.
- `apps/web/components/kanban/mobile-column-tabs.tsx` — one focused board step, visible workflow/step context, previous/next shortcuts, and combined hierarchy drawer.
- `apps/web/components/kanban-with-preview.tsx` — direct phone navigation instead of a compressed desktop preview pane.
- `apps/web/components/kanban/mobile-fab.tsx` — safe-area-aware, thumb-reachable primary action.
- `apps/web/app/globals.css` — phone menu containment, 44px menu rows, safe-area utilities, and coarse-pointer input sizing.
The global Radix menu override is a compatibility layer, not a template for more broad selectors. Prefer a scoped class or shared responsive primitive for new behavior, and cover long and nested menus because parent and submenu surfaces are both portaled.
## Anti-Patterns
- stacking every desktop panel vertically and calling it responsive
- rendering a desktop side sheet at `width: 100%` instead of choosing a phone surface
- preserving a wide table, tab strip, board, or toolbar through page-level horizontal scrolling
- adding an intermediate sheet before the card's obvious primary destination
- hiding required actions or relying on hover, right-click, or long press
- custom overlay markup when an existing `Drawer`, picker, dialog, or menu primitive fits
- stacking drawers/popovers for consecutive choices that belong in one navigable surface
- fixed bottom controls that ignore safe areas or on-screen keyboard overlap
- multiple ambiguous scroll owners inside a viewport-height surface
- cloning action/state logic into mobile components
- persisting a mobile fallback over a desktop preference
## Verification Focus
Use the configured `mobile-chrome` Pixel 5 project and assert user outcomes. Add geometry assertions when containment is the behavior under test:
- primary action is visible and completes the same outcome as desktop
- drawer/menu remains inside the viewport and long content scrolls internally
- new primary actions, standalone icon controls, and menu rows meet the 44px hitbox expectation
- nested choices remain usable without horizontal overflow
- document `scrollWidth` does not exceed `clientWidth`
- fixed controls and final rows clear the bottom safe area
- direct navigation, back/dismiss, and focus return behave predictably
- mobile fallback leaves the stored desktop preference unchanged
Also inspect a rendered phone viewport or screenshot. Tests can prove reachability while still missing a cramped hierarchy, awkward sheet height, or desktop-looking composition.
+7
View File
@@ -20,6 +20,13 @@ import { Dialog } from "@kandev/ui/dialog";
- For data tables, use `@kandev/ui/table` with TanStack Table; use shadcn Pagination components.
- Only create custom components when shadcn doesn't provide what's needed.
### Responsive and touch surfaces
- Use `hooks/use-responsive-breakpoint.ts` for application layout decisions. Its phone boundary is 640px and it also models tablet, compact desktop, full desktop, and pointer precision; do not substitute the UI package's generic `useIsMobile` hook.
- Use `useTouchDrawer` when a hover/popover disclosure needs a coarse-pointer `Drawer` alternative. Width-based phone composition and pointer-based disclosure behavior are related but not interchangeable.
- Existing Radix DropdownMenu and ContextMenu surfaces receive inset, safe-area-aware bottom-sheet treatment below 640px in `app/globals.css`. Reuse those primitives for contextual actions and add focused coverage for long or nested menus instead of creating a parallel mobile menu.
- Mobile capability parity does not require desktop layout parity. Load `/mobile-parity` for the Kandev surface decision guide, mobile design contract, and verification requirements.
## Data Flow Pattern (Critical)
```text