Skip to content

feat(projects): mobile shell — responsive layout, swipe board, mobile task modal#271

Merged
jaylfc merged 33 commits intomasterfrom
mobile-shell
Apr 30, 2026
Merged

feat(projects): mobile shell — responsive layout, swipe board, mobile task modal#271
jaylfc merged 33 commits intomasterfrom
mobile-shell

Conversation

@jaylfc
Copy link
Copy Markdown
Owner

@jaylfc jaylfc commented Apr 29, 2026

Summary

Phone-targeted UX for ProjectsApp: surgical useIsMobile() branches plus a small desktop/src/apps/ProjectsApp/mobile/ subtree. Zero backend changes.

  • Outer shell: MobileSplitView wraps ProjectList + ProjectWorkspace; WorkspaceTabPills replaces the tab strip on phone.
  • Tasks: ProjectFab + TaskCreateSheet bottom-sheet. Tasks/Members/Activity rows stack via Tailwind responsive classes.
  • Board: MobileBoardCarousel — sticky column pills + swipe-paged columns with section-header swimlanes. No DnD on mobile (status changes via the modal).
  • Task modal: MobileTaskModal — full-screen, slot-based (Hero/Metadata/SubTasks/Relationships/Activity). Sticky action bar (Claim/Close/Reopen). Swipe-left/right for sibling nav (60px threshold, vertical-dominance + pinch guards). Re-entrancy guard + window.alert on status-change failure.
  • BoardToolbar: collapses to a filter sheet (group-by select + search + filters); viewMode tabs / live indicator / +Task button hidden on mobile.
  • Canvas: read-only on mobile via editor.updateInstanceState({ isReadonly }) (tldraw v4 API; the prop-based readOnly doesn't exist on this version).
  • PWA: verified — viewport-fit=cover, Apple meta tags, manifest, icons, standalone detection all already in place.
  • Tests: 218 vitest (54 files, +new tests for every new component), 1 Playwright e2e (`projects-mobile.spec.ts` on `iphone-14` profile).

Notable deviations from the plan

  1. tldraw v4 has no <Tldraw readOnly> prop — used the editor API.
  2. e2e spec adapted to actual app: Launchpad path (not home screen), aria-label "Back to Projects", modal close aria-label="Close modal" (disambiguated from the "Close" action button).
  3. ARIA: used <section aria-label> (maps to region) rather than <div role="group">; verified <details> and <dl> carry no implicit role.
  4. Added data-testid="task-card" to TaskListItem so the e2e selector targets task cards, not swimlane toggles.

Test plan

  • Vitest green (218/218 verified locally)
  • Vite build succeeds, zero TS errors (verified locally)
  • Playwright projects-mobile.spec.ts passes on iphone-14 profile (verified locally)
  • Manual sweep at iPhone 14 emulation in Chrome devtools:
    • Project list pane → tap project → workspace slides in, back button visible
    • Tab pills scroll horizontally; each tab mounts (tasks / board / files / messages / members / activity / canvas)
    • Tasks tab — FAB → sheet → create → task appears
    • Board tab — column pills sticky-top, swipe between columns, swimlane group-by visible (open Filter sheet → choose Assignee)
    • Tap card → MobileTaskModal full-screen → Claim button changes status → swipe right → previous task → Close
    • Canvas tab — tldraw read-only, pan/zoom work, no shape creation
    • Switch back to desktop viewport — visuals unchanged from pre-plan baseline

Open follow-ups (non-blocking)

  • tablist/tab lacks tabpanel wiring on the workspace tabs (visual UX clear; SR users would benefit).
  • MobileBoardCarousel panes use w-screen rather than w-full — fine for full-width windows, will overflow if the projects window is ever embedded narrow.
  • e2e test creates state ("e2e Mobile Test Project") — future cleanup hook desirable.
  • Pre-existing canvas-e2e.spec.ts skeleton needs @backend-required tag or deletion (it fails without backend).

Summary by CodeRabbit

  • New Features

    • Full mobile support: responsive layouts, mobile-specific navigation, touch-optimized board carousel, and a mobile task modal with swipe navigation
    • Mobile task creation: floating action button and a task creation sheet
    • Mobile tab pills and workspace split-view for improved phone UX
    • Added end-to-end test runner and CI-friendly test configuration
  • Tests

    • Comprehensive unit and E2E tests covering mobile workflows and components
  • Chores

    • Updated test-related ignores and test environment polyfills

jaylfc added 30 commits April 29, 2026 15:55
Address two Important findings from the Task 3 code-quality review:

- Rename JSX variables `list`/`detail` to `listPane`/`detailPane` to avoid
  shadowing the local `const list` inside `refresh()`. Component-level
  rename is the lower-churn fix; `refresh()` body is left untouched.
  `MobileSplitView` prop names (`list`/`detail`) stay as-is since they're
  the component's public API.

- Have the `MobileSplitView` mock surface `listTitle` as `data-list-title`
  and assert on it in the mobile-path test. This means the test now
  verifies the production code actually routed through the
  `MobileSplitView` branch with the expected props, rather than just
  finding any element wearing the testid.
Uses Approach B: editor.updateInstanceState({ isReadonly: isMobile }) inside
onMount, plus a separate useEffect that re-applies the state whenever isMobile
changes (e.g. window resize crossing the 768px breakpoint).

The <Tldraw> component in tldraw v4 (@tldraw/tldraw ^4.5.10) does NOT expose
a readOnly prop — readonly mode is exclusively controlled via the editor API.
Confirmed via tldraw.dev official docs (sdk-features/readonly and
examples/readonly). The existing onMount store.listen() listener for
user-edit forwarding is preserved unchanged.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

📝 Walkthrough

Walkthrough

Adds comprehensive mobile support and E2E/tests to the Projects app: new mobile UI components (carousel board, task modal, FAB, sheet, tab pills), responsive edits to existing components, task creation/refresh event plumbing, Playwright config and scripts, and many mobile-focused unit and E2E tests.

Changes

Cohort / File(s) Summary
Config & Test infra
desktop/.gitignore, desktop/package.json, desktop/playwright.config.ts, desktop/vitest.setup.ts
Playwright added and configured (iPhone 14, local dev server, HTML reporter), test:e2e script, test polyfills, and gitignore updated for test artifacts.
Mobile UI primitives
desktop/src/components/mobile/MobileSplitView.tsx, desktop/src/components/mobile/WorkspaceTabPills.tsx
Adds MobileSplitView test id and a horizontal, scroll-into-view WorkspaceTabPills component with accessible tab semantics.
Mobile board & carousel
desktop/src/apps/ProjectsApp/mobile/MobileBoardCarousel.tsx, desktop/src/apps/ProjectsApp/board/ProjectBoard.tsx
New MobileBoardCarousel component; ProjectBoard now returns mobile carousel branch when useIsMobile() is true and precomputes per-column task maps.
Mobile task surfaces
desktop/src/apps/ProjectsApp/mobile/MobileTaskModal.tsx, desktop/src/apps/ProjectsApp/mobile/TaskCreateSheet.tsx, desktop/src/apps/ProjectsApp/mobile/ProjectFab.tsx, desktop/src/apps/ProjectsApp/board/TaskModal.tsx
Adds MobileTaskModal (swipe navigation, collapsible sections), TaskCreateSheet (form, validation, async submit), ProjectFab (safe-area FAB); TaskModal now delegates to mobile modal and handles status transitions with API calls and refresh events.
Workspace & navigation
desktop/src/apps/ProjectsApp/index.tsx, desktop/src/apps/ProjectsApp/ProjectWorkspace.tsx
Introduces mobile-aware routing (MobileSplitView), mobile tab pills, mobile FAB/TaskCreateSheet flow that calls projectsApi.tasks.create and dispatches projects:tasks-refresh. Desktop ARIA ids preserved.
Task list & event plumbing
desktop/src/apps/ProjectsApp/ProjectTaskList.tsx
Adds listener for projects:tasks-refresh CustomEvent (filtered by projectId) to trigger task refresh; responsive form and task row layout adjustments.
Responsive tweaks
desktop/src/apps/ProjectsApp/ProjectMembers.tsx, desktop/src/apps/ProjectsApp/ProjectActivity.tsx, desktop/src/apps/ProjectsApp/canvas/CanvasBoard.tsx
Responsive layout and truncation changes; CanvasBoard applies isReadonly when mobile via editor instance updates.
Board toolbar mobile mode
desktop/src/apps/ProjectsApp/board/BoardToolbar.tsx
Adds mobile sheet-based toolbar with group/search controls and focus management when opened on mobile.
Tests — unit & e2e
desktop/src/apps/ProjectsApp/__tests__/*, desktop/src/components/mobile/__tests__/*, desktop/src/apps/ProjectsApp/mobile/__tests__/*, desktop/tests/projects-mobile.spec.ts
Many new mobile-focused unit tests and a Playwright E2E test covering sign-in, project creation/selection, FAB flow, board carousel interactions, and mobile task modal behaviors.
Misc
desktop/src/components/mobile/*, desktop/vitest.setup.ts
Small test-id addition and DOM API polyfills for test reliability.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant FAB as ProjectFab
    participant Sheet as TaskCreateSheet
    participant Workspace as ProjectWorkspace
    participant API as projectsApi
    participant Window

    User->>FAB: click "Create task"
    FAB->>Workspace: open sheet
    Workspace->>Sheet: open=true
    User->>Sheet: submit title
    Sheet->>API: tasks.create(projectId, { title })
    API-->>Sheet: created task
    Sheet->>Window: dispatch 'projects:tasks-refresh' { projectId }
    Window->>Workspace: event captured
    Workspace->>API: refresh tasks (via ProjectTaskList)
    API-->>Workspace: updated task list
    Workspace-->>User: UI updated, sheet closed
Loading
sequenceDiagram
    actor User
    participant Carousel as MobileBoardCarousel
    participant ModalWrapper as TaskModal
    participant MobileModal as MobileTaskModal
    participant API as projectsApi
    participant Window

    User->>Carousel: tap task card
    Carousel->>ModalWrapper: onOpenTask(taskId)
    ModalWrapper->>MobileModal: render mobile modal
    User->>MobileModal: tap status action (e.g., "Claim")
    MobileModal->>ModalWrapper: onChangeStatus(next)
    ModalWrapper->>API: tasks.update(taskId, { status: next })
    API-->>ModalWrapper: updated task
    ModalWrapper->>Window: dispatch 'projects:tasks-refresh'
    Window->>Carousel: task list refresh triggers re-render
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped from desktop to tiny screens bright,

Carousels, pills, and swipes in moonlight;
FABs that sparkle with a safe-area nudge,
Sheets that whisk tasks from thought to judge;
Mobile gardens bloom — hop on, take flight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.04% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: mobile shell development with responsive layout, swipe-enabled board, and mobile task modal—all core features of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mobile-shell

Review rate limit: 2/3 reviews remaining, refill in 20 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (2)
desktop/src/apps/ProjectsApp/mobile/__tests__/MobileBoardCarousel.test.tsx (1)

84-86: 💤 Low value

Unnecessary aliased import.

The describe as describe2 alias is redundant since describe is already imported on line 1 and can be reused.

-import { describe as describe2 } from "vitest";
-
-describe2("MobileBoardCarousel — swimlane section headers", () => {
+describe("MobileBoardCarousel — swimlane section headers", () => {

,

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@desktop/src/apps/ProjectsApp/mobile/__tests__/MobileBoardCarousel.test.tsx`
around lines 84 - 86, The test imports describe as an alias describe2 and then
uses describe2 in MobileBoardCarousel.test; remove the aliased import and
replace references to describe2 with the existing describe (i.e., delete the
"import { describe as describe2 } from 'vitest';" line and update the
describe2("MobileBoardCarousel — swimlane section headers", ...) call to
describe(...)) so the file uses the original describe import consistently.
desktop/src/apps/ProjectsApp/mobile/MobileBoardCarousel.tsx (1)

31-56: 💤 Low value

Consider clearing paneRefs when columns change.

If columns were to change dynamically, paneRefs.current could retain stale references at incorrect indices. Since MOBILE_COLUMNS is constant in ProjectBoard.tsx, this is low risk, but for robustness:

 export function MobileBoardCarousel({ columns, tasksByColumn, groupBy, onOpenTask }: Props) {
   const [activeIdx, setActiveIdx] = useState(0);
   const scrollerRef = useRef<HTMLDivElement>(null);
   const paneRefs = useRef<Array<HTMLDivElement | null>>([]);
+
+  // Reset refs array when columns change
+  paneRefs.current.length = columns.length;

,

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@desktop/src/apps/ProjectsApp/mobile/MobileBoardCarousel.tsx` around lines 31
- 56, paneRefs.current can hold stale DOM references if the columns array
changes; add logic to reset or resize paneRefs when columns change by creating a
useEffect that watches columns (or columns.length) and sets paneRefs.current =
new Array(columns.length). Ensure this update occurs before any scroller
interactions (so add the effect alongside the existing scroll effect) and keep
references assigned via the existing ref callbacks so paneRefs entries align
with the current columns.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@desktop/playwright.config.ts`:
- Around line 11-25: The Playwright config currently points at port 5173
(baseURL and webServer.url) which can collide with any local Vite instance;
change to a dedicated E2E port (e.g. 5174) or read from an env var (e.g.
PLAYWRIGHT_E2E_PORT) and update the webServer.command, webServer.url and
use.baseURL to use that port; ensure the webServer.command passes the port env
var to the dev server start command and keep reuseExistingServer behavior as
desired so Playwright no longer silently attaches to unrelated servers.

In `@desktop/src/apps/ProjectsApp/board/ProjectBoard.tsx`:
- Around line 145-154: The mobile branch currently returns only
MobileBoardCarousel and omits the BoardToolbar, so add BoardToolbar above
MobileBoardCarousel in ProjectBoard.tsx; render <BoardToolbar> in the isMobile
branch and pass the same groupBy and its change handler (e.g., onChangeGroupBy /
setGroupBy), current filters and their change handler (e.g., filters /
onChangeFilters or setFilters), and any mobile flag (isMobile or mobileSheet
prop) so the toolbar shows the mobile "Filter / Group" sheet, then keep
MobileBoardCarousel with the existing props (columns/MOBILE_COLUMNS,
tasksByColumn, onOpenTask) underneath.

In `@desktop/src/apps/ProjectsApp/board/TaskModal.tsx`:
- Around line 77-89: The mobile branch currently returns a fullscreen blocker
when task is falsy and can trap users; change the logic in TaskModal.tsx (the
isMobile branch) to distinguish loading vs not-found: treat undefined as
"loading…" and null as "not found" and render a dismissible dialog that includes
the same close affordance as desktop (call the existing onClose handler).
Specifically, update the condition around isMobile and task so that you only
show the spinner when task === undefined, and when task === null render a small
message ("Task not found") plus the header/close button that invokes onClose
(reuse the modal header/close UI used elsewhere in this component).

In `@desktop/src/apps/ProjectsApp/mobile/TaskCreateSheet.tsx`:
- Around line 53-58: In TaskCreateSheet, the backdrop button currently always
calls onClose() even while a create request is in flight; guard the backdrop
dismissal by checking the component's submit state (e.g., isSubmitting or
submitting) and only call onClose when that flag is false: update the backdrop
button's onClick to something like () => { if (!isSubmitting) onClose(); }, add
disabled / aria-disabled when submitting, and ensure the backdrop has
pointer-events disabled (or a CSS class change) while submitting so users cannot
dismiss the sheet mid-request.
- Around line 66-74: The title input in TaskCreateSheet.tsx lacks a stable
accessible name—replace the placeholder-only textbox with an explicitly
associated label: add a <label> element (e.g., "Title" or "Task title") tied to
the input via htmlFor/id or provide an aria-label/aria-labelledby on the input;
update the input (ref inputRef, value title, onChange setTitle) to include
id="task-title" (or similar) and ensure the label uses htmlFor="task-title" so
assistive tech always has a persistent name for the field.

In `@desktop/src/components/mobile/WorkspaceTabPills.tsx`:
- Around line 40-45: The button in WorkspaceTabPills.tsx (the element using
key={t.id}, ref={isActive ? activeRef : undefined}, role="tab",
aria-selected={isActive}, onClick={() => onSelect(t.id)}) needs an explicit type
attribute to avoid accidental form submission; add type="button" to that
<button> element so it behaves as a non-submit button when rendered inside a
form.
- Around line 28-45: The tablist/tab elements lack required ARIA attributes and
keyboard behavior; update the component that renders tabs (the tabs.map callback
using activeRef and onSelect) to either implement the full WAI-ARIA tabs pattern
or convert to a simpler button/segmented-control pattern: if implementing tabs,
add aria-controls on each button pointing to the associated panel id, ensure the
tablist has aria-labelledby, implement roving tabindex (set the active tab
rendered by tabs.map to tabindex="0" and inactive to tabindex="-1"), and add
keyboard handlers on the tab elements to support ArrowLeft/ArrowRight/Home/End
navigation and Space/Enter activation (use onKeyDown alongside onClick and
update focus via activeRef); alternatively, if panels are managed elsewhere,
change roles from role="tablist"/role="tab" to role="group" or leave as plain
buttons and keep existing onSelect.

In `@desktop/tests/projects-mobile.spec.ts`:
- Around line 42-64: The current early-return when project creation times out
(inside the projectCount === 0 branch using the dialogGone boolean) hides a
failed precondition; replace the silent return with an explicit test failure or
skip so CI doesn’t report a false positive. Specifically, in the block that
currently does "if (!dialogGone) { /* ... */ return; }", either throw a clear
Error (e.g. throw new Error('Project creation timed out - cannot continue
test')) or call the test framework’s skip helper, or alternatively seed a
deterministic project before running the rest of the flow; target the dialogGone
handling and the project creation flow (the code that clicks "create project",
fills nameInput and waits for the dialog) to implement the chosen behavior.

---

Nitpick comments:
In `@desktop/src/apps/ProjectsApp/mobile/__tests__/MobileBoardCarousel.test.tsx`:
- Around line 84-86: The test imports describe as an alias describe2 and then
uses describe2 in MobileBoardCarousel.test; remove the aliased import and
replace references to describe2 with the existing describe (i.e., delete the
"import { describe as describe2 } from 'vitest';" line and update the
describe2("MobileBoardCarousel — swimlane section headers", ...) call to
describe(...)) so the file uses the original describe import consistently.

In `@desktop/src/apps/ProjectsApp/mobile/MobileBoardCarousel.tsx`:
- Around line 31-56: paneRefs.current can hold stale DOM references if the
columns array changes; add logic to reset or resize paneRefs when columns change
by creating a useEffect that watches columns (or columns.length) and sets
paneRefs.current = new Array(columns.length). Ensure this update occurs before
any scroller interactions (so add the effect alongside the existing scroll
effect) and keep references assigned via the existing ref callbacks so paneRefs
entries align with the current columns.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: abeea415-7af9-4932-9084-805d4c6315d8

📥 Commits

Reviewing files that changed from the base of the PR and between 629c081 and a3d971c.

⛔ Files ignored due to path filters (1)
  • desktop/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (27)
  • desktop/.gitignore
  • desktop/package.json
  • desktop/playwright.config.ts
  • desktop/src/apps/ProjectsApp/ProjectActivity.tsx
  • desktop/src/apps/ProjectsApp/ProjectMembers.tsx
  • desktop/src/apps/ProjectsApp/ProjectTaskList.tsx
  • desktop/src/apps/ProjectsApp/ProjectWorkspace.tsx
  • desktop/src/apps/ProjectsApp/__tests__/ProjectWorkspace.mobile.test.tsx
  • desktop/src/apps/ProjectsApp/__tests__/ProjectsApp.mobile.test.tsx
  • desktop/src/apps/ProjectsApp/board/BoardToolbar.tsx
  • desktop/src/apps/ProjectsApp/board/ProjectBoard.tsx
  • desktop/src/apps/ProjectsApp/board/TaskModal.tsx
  • desktop/src/apps/ProjectsApp/canvas/CanvasBoard.tsx
  • desktop/src/apps/ProjectsApp/index.tsx
  • desktop/src/apps/ProjectsApp/mobile/MobileBoardCarousel.tsx
  • desktop/src/apps/ProjectsApp/mobile/MobileTaskModal.tsx
  • desktop/src/apps/ProjectsApp/mobile/ProjectFab.tsx
  • desktop/src/apps/ProjectsApp/mobile/TaskCreateSheet.tsx
  • desktop/src/apps/ProjectsApp/mobile/__tests__/MobileBoardCarousel.test.tsx
  • desktop/src/apps/ProjectsApp/mobile/__tests__/MobileTaskModal.test.tsx
  • desktop/src/apps/ProjectsApp/mobile/__tests__/ProjectFab.test.tsx
  • desktop/src/apps/ProjectsApp/mobile/__tests__/TaskCreateSheet.test.tsx
  • desktop/src/components/mobile/MobileSplitView.tsx
  • desktop/src/components/mobile/WorkspaceTabPills.tsx
  • desktop/src/components/mobile/__tests__/WorkspaceTabPills.test.tsx
  • desktop/tests/projects-mobile.spec.ts
  • desktop/vitest.setup.ts

Comment on lines +11 to +25
use: {
baseURL: "http://127.0.0.1:5173",
trace: "on-first-retry",
},
projects: [
{
name: "iphone-14",
use: { ...devices["iPhone 14"] },
},
],
webServer: {
command: "npm run dev",
url: "http://127.0.0.1:5173",
reuseExistingServer: !process.env.CI,
timeout: 60_000,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "playwright.config.ts" -o -name "vite.config.ts" -o -name "vite.config.js" | head -20

Repository: jaylfc/tinyagentos

Length of output: 118


🏁 Script executed:

cat desktop/playwright.config.ts

Repository: jaylfc/tinyagentos

Length of output: 682


🏁 Script executed:

cat package.json | head -50

Repository: jaylfc/tinyagentos

Length of output: 107


🏁 Script executed:

cat desktop/vite.config.ts

Repository: jaylfc/tinyagentos

Length of output: 3647


🏁 Script executed:

ls -la desktop/ | grep -E "(package\.json|vite\.config)"

Repository: jaylfc/tinyagentos

Length of output: 194


🏁 Script executed:

cat desktop/package.json 2>/dev/null || echo "No desktop/package.json"

Repository: jaylfc/tinyagentos

Length of output: 2197


🌐 Web query:

Vite default dev server port

💡 Result:

The default dev server port for Vite is 5173.

Citations:


🌐 Web query:

Playwright reuseExistingServer behavior documentation

💡 Result:

The reuseExistingServer option is part of Playwright's webServer configuration in playwright.config.ts. It controls whether Playwright should reuse an already-running server on the specified port or url instead of starting a new one via the command. When true: - If a server is already listening on the port/url, it reuses it without throwing an error or starting the command. - If no server is running, it starts one using the command. - Commonly set to !process.env.CI to enable locally (reuse dev server for faster iteration) and disable in CI (start fresh for reliability). When false (default in CI setups): - Throws an error if something is already listening on the port/url. - Always starts a new server via command if none exists. Example from official docs: webServer: { command: 'npm run start', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI, }, This option applies to the testConfig.webServer in @playwright/test, not to BrowserServer from BrowserType.launchServer which has no such option.

Citations:


Use a dedicated E2E port instead of reusing whatever is on 5173.

With reuseExistingServer: true (enabled locally), Playwright will silently attach to any process already serving http://127.0.0.1:5173. Since that's Vite's default port, a developer running an unrelated Vite project locally will cause tests to run against the wrong server without any warning, leading to false positives or false negatives.

Suggested change
 import { defineConfig, devices } from "@playwright/test";
 
+const baseURL = "http://127.0.0.1:4173";
+
 export default defineConfig({
   testDir: "./tests",
   testMatch: "**/*.spec.ts",
   fullyParallel: true,
@@
   reporter: "html",
   use: {
-    baseURL: "http://127.0.0.1:5173",
+    baseURL,
     trace: "on-first-retry",
   },
@@
   webServer: {
-    command: "npm run dev",
-    url: "http://127.0.0.1:5173",
+    command: "npm run dev -- --port 4173",
+    url: baseURL,
     reuseExistingServer: !process.env.CI,
     timeout: 60_000,
   },
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@desktop/playwright.config.ts` around lines 11 - 25, The Playwright config
currently points at port 5173 (baseURL and webServer.url) which can collide with
any local Vite instance; change to a dedicated E2E port (e.g. 5174) or read from
an env var (e.g. PLAYWRIGHT_E2E_PORT) and update the webServer.command,
webServer.url and use.baseURL to use that port; ensure the webServer.command
passes the port env var to the dev server start command and keep
reuseExistingServer behavior as desired so Playwright no longer silently
attaches to unrelated servers.

Comment thread desktop/src/apps/ProjectsApp/board/ProjectBoard.tsx
Comment thread desktop/src/apps/ProjectsApp/board/TaskModal.tsx
Comment thread desktop/src/apps/ProjectsApp/mobile/TaskCreateSheet.tsx
Comment thread desktop/src/apps/ProjectsApp/mobile/TaskCreateSheet.tsx
Comment on lines +28 to +45
role="tablist"
data-testid="workspace-tab-pills-scroller"
className="flex gap-2 overflow-x-auto px-3 py-2 border-b border-white/10
[scrollbar-width:none] [-ms-overflow-style:none]
[&::-webkit-scrollbar]:hidden"
style={{
WebkitOverflowScrolling: "touch",
}}
>
{tabs.map((t) => {
const isActive = t.id === active;
return (
<button
key={t.id}
ref={isActive ? activeRef : undefined}
role="tab"
aria-selected={isActive}
onClick={() => onSelect(t.id)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

According to the WAI-ARIA Authoring Practices, what attributes and keyboard interactions are required for a tabs pattern using role="tablist", role="tab", and role="tabpanel"?

💡 Result:

According to the WAI-ARIA Authoring Practices Guide (APG) Tabs pattern, the required attributes and keyboard interactions for a tabs implementation using role="tablist", role="tab", and role="tabpanel" are as follows. Required Roles: - Container for tabs: role="tablist" - Individual tabs: role="tab" (children of tablist) - Content panels: role="tabpanel" Required Attributes: For role="tablist": - aria-labelledby or aria-label to provide an accessible name describing the purpose of the tabs. For role="tab": - aria-controls referencing the id of the associated tabpanel. - aria-selected="true" on the active tab; "false" on others. - Use roving tabindex: tabindex="0" on the active/focused tab, tabindex="-1" on inactive tabs (often implemented with buttons, which are naturally focusable). For role="tabpanel": - aria-labelledby referencing the id of the associated tab. - Optionally, tabindex="0" if the panel lacks focusable content. Optional attributes: - aria-orientation="vertical" if vertically oriented (default: horizontal). - aria-haspopup on tabs if they have popup menus. Keyboard Interactions (required for both automatic and manual activation variants): - Tab: Moves focus into the tablist to the active tab; from active tab, moves to next element (often the tabpanel). - Shift+Tab: Moves focus to previous element. - Right Arrow: Moves focus to next tab (wraps to first if on last). - Left Arrow: Moves focus to previous tab (wraps to last if on first). - Home: Moves focus to first tab. - End: Moves focus to last tab. - Space or Enter: Activates the focused tab (shows its panel) in manual activation; automatic activation happens on focus. - Delete (optional): Removes focused tab if supported. Note: APG provides two variants - automatic (activate on focus) and manual (activate on Space/Enter). Manual is recommended unless panels load instantly. Hide inactive tabpanels (e.g., display:none or hidden attribute). Use buttons for tabs to inherit native keyboard support.

Citations:


🏁 Script executed:

cat -n desktop/src/components/mobile/WorkspaceTabPills.tsx

Repository: jaylfc/tinyagentos

Length of output: 2153


tablist/tab semantics are incomplete for accessibility.

Lines 28 and 43 declare role="tablist" and role="tab", but the component is missing several required ARIA attributes and behaviors per WAI-ARIA Authoring Practices:

  • No aria-controls linking tabs to their panels
  • No roving tabindex (active tab should be tabindex="0", inactive tabindex="-1")
  • No keyboard handlers for Arrow, Home, End, or Space/Enter keys
  • No aria-labelledby on the tablist container

This creates confusing screen-reader and keyboard navigation experience. Either implement full tabs pattern semantics (including keyboard handlers and panel associations) or simplify to button/segmented-control roles if panels are managed elsewhere.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@desktop/src/components/mobile/WorkspaceTabPills.tsx` around lines 28 - 45,
The tablist/tab elements lack required ARIA attributes and keyboard behavior;
update the component that renders tabs (the tabs.map callback using activeRef
and onSelect) to either implement the full WAI-ARIA tabs pattern or convert to a
simpler button/segmented-control pattern: if implementing tabs, add
aria-controls on each button pointing to the associated panel id, ensure the
tablist has aria-labelledby, implement roving tabindex (set the active tab
rendered by tabs.map to tabindex="0" and inactive to tabindex="-1"), and add
keyboard handlers on the tab elements to support ArrowLeft/ArrowRight/Home/End
navigation and Space/Enter activation (use onKeyDown alongside onClick and
update focus via activeRef); alternatively, if panels are managed elsewhere,
change roles from role="tablist"/role="tab" to role="group" or leave as plain
buttons and keep existing onSelect.

Comment thread desktop/src/components/mobile/WorkspaceTabPills.tsx
Comment thread desktop/tests/projects-mobile.spec.ts
- Render BoardToolbar above MobileBoardCarousel so the mobile filter sheet is reachable.
- Add a Close button to the mobile TaskModal loading state so users aren't trapped if the task fetch resolves null.
- Disable backdrop dismissal on TaskCreateSheet while a submit is in flight (matches the Cancel button).
- Wire role=tab to role=tabpanel via id/aria-controls/aria-labelledby on both mobile pills and desktop nav.
- Add type=button to WorkspaceTabPills tab buttons.
- Add aria-label="Task title" to TaskCreateSheet input so assistive tech keeps a stable accessible name once the placeholder is gone.
- Make the e2e backend-precondition path SKIP (not silently PASS) when project creation fails.
@kilo-code-bot
Copy link
Copy Markdown

kilo-code-bot Bot commented Apr 29, 2026

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (10 files)
  • desktop/.gitignore - Added playwright-report/ and test-results/ to .gitignore
  • desktop/package.json - Added @playwright/test dependency and test:e2e script
  • desktop/package-lock.json - Updated with @playwright/test and related packages
  • desktop/playwright.config.ts - New Playwright configuration for e2e testing on iPhone 14
  • desktop/src/apps/ProjectsApp/ProjectActivity.tsx - Updated layout for mobile responsiveness
  • desktop/src/apps/ProjectsApp/ProjectMembers.tsx - Updated layout for mobile responsiveness
  • desktop/src/apps/ProjectsApp/ProjectTaskList.tsx - Added event listener for task refresh and updated layout for mobile
  • desktop/src/apps/ProjectsApp/ProjectWorkspace.tsx - Added mobile-specific components and logic
  • desktop/src/apps/ProjectsApp/board/BoardToolbar.tsx - Added mobile sheet for filtering and grouping
  • desktop/src/apps/ProjectsApp/board/ProjectBoard.tsx - Added mobile board carousel
  • desktop/src/apps/ProjectsApp/board/TaskModal.tsx - Added mobile task modal with swipe gestures
  • desktop/src/apps/ProjectsApp/canvas/CanvasBoard.tsx - Made canvas readonly on mobile
  • desktop/src/apps/ProjectsApp/index.tsx - Added mobile split view
  • desktop/src/apps/ProjectsApp/mobile/MobileBoardCarousel.tsx - New mobile board carousel component
  • desktop/src/apps/ProjectsApp/mobile/MobileTaskModal.tsx - New mobile task modal component
  • desktop/src/apps/ProjectsApp/__tests__/ProjectWorkspace.mobile.test.tsx - New tests for mobile workspace
  • desktop/src/apps/ProjectsApp/__tests__/ProjectsApp.mobile.test.tsx - New tests for mobile app

Reviewed by grok-code-fast-1:optimized:free · 201,519 tokens

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
desktop/tests/projects-mobile.spec.ts (1)

49-50: Consider cleaning up the project/task this spec creates.

This test mutates shared backend state and never removes those records, so repeated runs will gradually make the scenario more data-dependent. If the harness has API helpers, deleting the created entities in a finally block would keep the environment stable.

Also applies to: 86-88

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@desktop/tests/projects-mobile.spec.ts` around lines 49 - 50, This test
creates a persistent project via nameInput.fill(...) and the create button click
(page.getByRole("dialog").getByRole("button", { name: /^create$/i }).click());
wrap the create/assert flows in a try/finally and in the finally call your test
harness/API helper (e.g., testHarness.deleteProject(projectId) or
api.deleteProject(...)) to remove the created project (and any tasks created in
the later block around lines 86-88), capturing the created project ID from the
response or UI so the cleanup targets the exact entity. Ensure the cleanup is
resilient (ignore not-found errors) so repeated runs don’t accumulate state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@desktop/tests/projects-mobile.spec.ts`:
- Around line 49-50: This test creates a persistent project via
nameInput.fill(...) and the create button click
(page.getByRole("dialog").getByRole("button", { name: /^create$/i }).click());
wrap the create/assert flows in a try/finally and in the finally call your test
harness/API helper (e.g., testHarness.deleteProject(projectId) or
api.deleteProject(...)) to remove the created project (and any tasks created in
the later block around lines 86-88), capturing the created project ID from the
response or UI so the cleanup targets the exact entity. Ensure the cleanup is
resilient (ignore not-found errors) so repeated runs don’t accumulate state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f5472d31-b5bf-40ab-ac52-972f554991ad

📥 Commits

Reviewing files that changed from the base of the PR and between a3d971c and 40ad1c7.

📒 Files selected for processing (6)
  • desktop/src/apps/ProjectsApp/ProjectWorkspace.tsx
  • desktop/src/apps/ProjectsApp/board/ProjectBoard.tsx
  • desktop/src/apps/ProjectsApp/board/TaskModal.tsx
  • desktop/src/apps/ProjectsApp/mobile/TaskCreateSheet.tsx
  • desktop/src/components/mobile/WorkspaceTabPills.tsx
  • desktop/tests/projects-mobile.spec.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • desktop/src/apps/ProjectsApp/board/ProjectBoard.tsx
  • desktop/src/apps/ProjectsApp/board/TaskModal.tsx
  • desktop/src/apps/ProjectsApp/ProjectWorkspace.tsx
  • desktop/src/components/mobile/WorkspaceTabPills.tsx

@jaylfc jaylfc merged commit 85a5ff6 into master Apr 30, 2026
8 checks passed
@jaylfc jaylfc deleted the mobile-shell branch April 30, 2026 09:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant