Skip to content

feat(web): responsive mobile UI with hamburger menu#1180

Closed
tboome33 wants to merge 24 commits intocoleam00:devfrom
tboome33:feat/responsive-mobile-ui
Closed

feat(web): responsive mobile UI with hamburger menu#1180
tboome33 wants to merge 24 commits intocoleam00:devfrom
tboome33:feat/responsive-mobile-ui

Conversation

@tboome33
Copy link
Copy Markdown

@tboome33 tboome33 commented Apr 13, 2026

Summary

  • Add MobileNavContext to manage the open/close state of the mobile sidebar
  • Refactor Layout with a slide-in drawer sidebar (hamburger ☰ → X) for mobile, including a backdrop overlay
  • Update TopNav with a hamburger button (Menu icon) visible only on mobile (md:hidden), hidden on desktop
  • Update ChatPage to hide the conversation side-panel on mobile (hidden md:flex), giving the chat full-width on small screens
  • Fix SSE URL construction so the app works correctly via Cloudflare tunnel (remote/mobile access)

Desktop behaviour is unchanged.

Changes

File Type Description
packages/web/src/contexts/MobileNavContext.tsx New Context + hook for mobile nav open state
packages/web/src/components/layout/Layout.tsx Modified Drawer sidebar + backdrop overlay for mobile
packages/web/src/components/layout/TopNav.tsx Modified Hamburger button on mobile, nav tabs on desktop
packages/web/src/routes/ChatPage.tsx Modified Conversation panel hidden on mobile
packages/web/src/lib/api.ts Fix Fix SSE base URL for Cloudflare tunnel / remote access

SSE Fix (Cloudflare Tunnel)

The previous SSE URL used window.location.hostname to build a direct connection to the backend port (http://:3090). This broke when accessed via Cloudflare Quick Tunnel because the tunnel only exposes the Vite dev server (port 3000), not the backend (port 3090).

Fix: Only use direct backend URL when on localhost. For any other hostname (tunnel, remote network), fall back to a relative URL so requests go through the Vite proxy, which correctly forwards them to the backend.

Test plan

  • On mobile viewport (< 768px): hamburger opens the sidebar, X closes it, tapping backdrop closes it
  • Navigation links (Chat / Dashboard / Workflows / Settings) close the drawer on tap
  • On desktop (>= 768px): sidebar not visible, top-nav tabs visible, conversation panel visible
  • No regressions on desktop layout
  • Via Cloudflare Quick Tunnel: SSE streams connect correctly (no port 3090 error)
  • On localhost direct: SSE still connects directly (bypassing Vite proxy buffering)

Generated with Claude Sonnet 4.6

Summary by CodeRabbit

  • New Features

    • Mobile navigation drawer with slide-in/out animation, backdrop to close, active link styling and pinned Settings.
    • Hamburger menu on small screens to open the mobile drawer.
    • Chat page: conversations panel now a mobile drawer with mobile-specific controls.
  • Enhancements

    • Improved layout responsiveness and prevention of horizontal scroll on mobile.
    • Adjusted chat input bar layout and padding for better mobile safe-area support.
  • API

    • Added allow-env-keys option for codebases and a new endpoint to update codebases.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Layout adds a local mobile-nav state and provides it via a new MobileNavContext; TopNav can open the mobile drawer. ChatPage gains a mobile conversations drawer and responsive behaviors. API types/functions, global CSS, and MessageInput layout were also updated.

Changes

Cohort / File(s) Summary
Mobile Navigation Context
packages/web/src/contexts/MobileNavContext.tsx
New MobileNavContextValue interface, MobileNavContext, and useMobileNav() hook exposing open and setOpen.
Layout & Mobile Drawer
packages/web/src/components/layout/Layout.tsx
Adds local open state, wraps children with MobileNavContext.Provider, replaces h-screen with h-dvh, and renders a mobile-only slide-in <aside> drawer with backdrop; nav items (including pinned Settings) close the drawer when activated.
Top Navigation
packages/web/src/components/layout/TopNav.tsx
Adds mobile hamburger button that calls useMobileNav().setOpen(true) (visible on small screens); moves tab area into desktop-only markup and updates icon imports.
Chat Page Responsiveness
packages/web/src/routes/ChatPage.tsx
Conversations sidebar becomes a fixed, sliding overlay on mobile with backdrop and multiple close triggers; drawer auto-closes on resize to desktop; chat column gains mobile sticky top bar and min-w-0; resize handle hidden on mobile.
API surface & types
packages/web/src/lib/api.ts
SSE base URL tightened to only use http://localhost:${apiPort} when DEV and hostname === localhost; CodebaseResponse adds allow_env_keys; addCodebase accepts optional allowEnvKeys?; new updateCodebase(id, input) PATCH function added.
Global CSS
packages/web/src/index.css
Added overflow-x: hidden on html, #root (with width:100%), and body to prevent horizontal scrolling.
Message Input
packages/web/src/components/chat/MessageInput.tsx
Reworked root container layout/styling (uses flex-shrink-0, adjusted padding including safe-area inset); export signatures preserved; interactive logic (drag/drop, file handling) retained.

Sequence Diagram

sequenceDiagram
    actor User
    participant TopNav
    participant MobileNavContext
    participant Layout
    participant MobileDrawer

    User->>TopNav: Click hamburger
    TopNav->>MobileNavContext: setOpen(true)
    MobileNavContext->>Layout: context open = true
    Layout->>MobileDrawer: render backdrop + drawer (visible)
    MobileDrawer-->>User: drawer slides in

    User->>MobileDrawer: Click backdrop or nav item or X
    MobileDrawer->>MobileNavContext: setOpen(false)
    MobileNavContext->>Layout: context open = false
    Layout->>MobileDrawer: hide backdrop + drawer
    MobileDrawer-->>User: drawer slides out
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I nibble code and find a door,
A tiny tap and then — encore!
The drawer slides out with a spin,
Backdrop dims, the links hop in,
I twitch my nose — the nav’s a win! 🎉

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description provides a clear summary with bullets, a detailed changes table, and explanation of the SSE fix. However, it lacks several required template sections: UX Journey diagrams, Architecture Diagram, Label Snapshot metadata, Validation Evidence, Security Impact, Compatibility, Human Verification, and Rollback Plan. Complete the missing template sections: add Before/After UX Journey diagrams, Architecture Diagram with module connections, Label Snapshot (Risk/Size/Scope/Module), validation command results, security impact assessment, compatibility notes, human verification details, and rollback plan.
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding responsive mobile UI with a hamburger menu. It directly corresponds to the primary feature across multiple components (Layout, TopNav, ChatPage).

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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: 2

🧹 Nitpick comments (2)
packages/web/src/components/layout/Layout.tsx (2)

31-39: Consider adding focus trap for accessibility.

The drawer implementation looks good visually, but for full keyboard accessibility, consider adding a focus trap so that keyboard focus stays within the drawer while it's open. Libraries like @radix-ui/react-dialog or focus-trap-react can help, or you could use the native <dialog> element.

This is optional but would improve the experience for keyboard and screen reader users.

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

In `@packages/web/src/components/layout/Layout.tsx` around lines 31 - 39, The
mobile drawer in the Layout component (the aside rendered when open is
controlled by the open prop) needs a focus trap to keep keyboard focus inside
while open; integrate a library like focus-trap-react or replace the aside with
a Radix Dialog to wrap the drawer contents, initialize the trap when open is
true and deactivate on close, ensure aria-modal/role="dialog" and initial focus
are set and that focus is returned to the triggering element on close (reference
the Layout component, the aside element using cn(...) and the open prop to
locate where to add the trap).

8-12: Consider extracting shared navigation items to reduce duplication.

The navItems array here duplicates the tabs array in TopNav.tsx (lines 8-13). While not strictly necessary, extracting these to a shared constant would ensure the navigation structure stays in sync between desktop tabs and mobile drawer.

💡 Example shared navigation config

Create a shared config file (e.g., @/lib/navigation.ts):

import { MessageSquare, LayoutDashboard, Workflow, Settings } from 'lucide-react';
import type { LucideIcon } from 'lucide-react';

export interface NavItem {
  to: string;
  end: boolean;
  icon: LucideIcon;
  label: string;
}

export const mainNavItems: readonly NavItem[] = [
  { to: '/chat', end: false, icon: MessageSquare, label: 'Chat' },
  { to: '/dashboard', end: true, icon: LayoutDashboard, label: 'Dashboard' },
  { to: '/workflows', end: false, icon: Workflow, label: 'Workflows' },
] as const;

export const settingsNavItem: NavItem = {
  to: '/settings', end: false, icon: Settings, label: 'Settings'
};

Then import in both Layout.tsx and TopNav.tsx.

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

In `@packages/web/src/components/layout/Layout.tsx` around lines 8 - 12, navItems
in Layout.tsx duplicates the tabs list in TopNav.tsx; extract the shared
navigation structure into a single exported constant (e.g., a readonly array of
NavItem objects) and import it in both Layout.tsx and TopNav.tsx so both use the
same source of truth. Create a small NavItem shape (to, end, icon, label), move
the existing entries (the items currently in navItems and tabs) into that shared
export, update Layout.tsx to use navItems from the shared module and update
TopNav.tsx to import the same array (and keep any component-specific filtering
or additions local). Ensure you keep the original symbol names used in each file
(navItems in Layout.tsx and tabs in TopNav.tsx) by assigning them to the
imported shared constant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/web/src/components/layout/Layout.tsx`:
- Around line 49-55: The aria-label on the close button in Layout.tsx is in
French ("Fermer le menu"); update the button element that calls setOpen(false)
and renders <X .../> to use an English aria-label such as "Close menu" (or the
project's standard English wording) to keep accessibility text consistent with
the UI language while leaving the onClick, className, and icon unchanged.

In `@packages/web/src/components/layout/TopNav.tsx`:
- Around line 36-43: The mobile hamburger button in TopNav uses a French
aria-label ("Ouvrir le menu") which is inconsistent with the app's English UI;
update the button's aria-label (the element with onClick={() => { setOpen(true);
}} and the <Menu /> icon) to an English string such as "Open menu" to restore
accessibility and language consistency.

---

Nitpick comments:
In `@packages/web/src/components/layout/Layout.tsx`:
- Around line 31-39: The mobile drawer in the Layout component (the aside
rendered when open is controlled by the open prop) needs a focus trap to keep
keyboard focus inside while open; integrate a library like focus-trap-react or
replace the aside with a Radix Dialog to wrap the drawer contents, initialize
the trap when open is true and deactivate on close, ensure
aria-modal/role="dialog" and initial focus are set and that focus is returned to
the triggering element on close (reference the Layout component, the aside
element using cn(...) and the open prop to locate where to add the trap).
- Around line 8-12: navItems in Layout.tsx duplicates the tabs list in
TopNav.tsx; extract the shared navigation structure into a single exported
constant (e.g., a readonly array of NavItem objects) and import it in both
Layout.tsx and TopNav.tsx so both use the same source of truth. Create a small
NavItem shape (to, end, icon, label), move the existing entries (the items
currently in navItems and tabs) into that shared export, update Layout.tsx to
use navItems from the shared module and update TopNav.tsx to import the same
array (and keep any component-specific filtering or additions local). Ensure you
keep the original symbol names used in each file (navItems in Layout.tsx and
tabs in TopNav.tsx) by assigning them to the imported shared constant.
🪄 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

Run ID: 735569db-6d84-46ca-97d2-f76f682e2acb

📥 Commits

Reviewing files that changed from the base of the PR and between a8ac3f0 and d7577f6.

📒 Files selected for processing (4)
  • packages/web/src/components/layout/Layout.tsx
  • packages/web/src/components/layout/TopNav.tsx
  • packages/web/src/contexts/MobileNavContext.tsx
  • packages/web/src/routes/ChatPage.tsx

Comment thread packages/web/src/components/layout/Layout.tsx Outdated
Comment thread packages/web/src/components/layout/TopNav.tsx Outdated
Replace window.location.hostname direct-connect with localhost-only check.
When accessed via Cloudflare tunnel, use relative URL so requests go through
Vite proxy which forwards to the backend (port 3090 not directly accessible).
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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/web/src/lib/api.ts`:
- Around line 21-23: Replace the top-level SSE_BASE_URL constant with a lazy
function getSSEBaseUrl() that guards access to window using typeof window !==
'undefined'; inside the function check import.meta.env.DEV and only then read
window.location.hostname === 'localhost' to return `http://localhost:${apiPort}`
or '' as fallback, so module evaluation during SSR/tests won't touch window;
update any callers to invoke getSSEBaseUrl() instead of referencing
SSE_BASE_URL.
- Line 44: Client-server contract mismatch: the client uses allowEnvKeys /
allow_env_keys but the server schemas and handlers (addCodebaseBodySchema,
codebaseSchema, and no PATCH handler for /api/codebases/:id) don’t support it.
Fix by either removing allowEnvKeys from the client API or adding full server
support: update addCodebaseBodySchema to accept allow_env_keys, extend
codebaseSchema to include allow_env_keys in responses, implement a PATCH handler
for /api/codebases/:id that validates and persists allow_env_keys to the DB (and
add a DB column/migration if needed), and ensure the client and server use the
same snake_case/key names in request/response contracts.
🪄 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

Run ID: 8ac64a18-11a6-4f41-be55-9e4811d3d4b0

📥 Commits

Reviewing files that changed from the base of the PR and between d7577f6 and a45ec74.

📒 Files selected for processing (1)
  • packages/web/src/lib/api.ts

Comment thread packages/web/src/lib/api.ts Outdated
Comment on lines 21 to 23
export const SSE_BASE_URL = import.meta.env.DEV && window.location.hostname === 'localhost'
? `http://localhost:${apiPort}`
: '';
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

Guard module-load window access for SSR/tests.

Line 21 can throw when this module is evaluated without window (SSR/tests). Use a lazy helper with a typeof window guard.

Suggested fix
 const apiPort = (import.meta.env.VITE_API_PORT as string | undefined) ?? '3090';
-export const SSE_BASE_URL = import.meta.env.DEV && window.location.hostname === 'localhost'
-  ? `http://localhost:${apiPort}`
-  : '';
+function getSSEBaseUrl(): string {
+  const hostname = typeof window !== 'undefined' ? window.location.hostname : 'localhost';
+  return import.meta.env.DEV && hostname === 'localhost' ? `http://localhost:${apiPort}` : '';
+}
+
+export const SSE_BASE_URL = getSSEBaseUrl();

Based on learnings: implement SSE URL via a lazy getSSEBaseUrl() with typeof window !== 'undefined' and 'localhost' fallback to avoid SSR/test crashes.

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

In `@packages/web/src/lib/api.ts` around lines 21 - 23, Replace the top-level
SSE_BASE_URL constant with a lazy function getSSEBaseUrl() that guards access to
window using typeof window !== 'undefined'; inside the function check
import.meta.env.DEV and only then read window.location.hostname === 'localhost'
to return `http://localhost:${apiPort}` or '' as fallback, so module evaluation
during SSR/tests won't touch window; update any callers to invoke
getSSEBaseUrl() instead of referencing SSE_BASE_URL.

repository_url: string | null;
default_cwd: string;
ai_assistant_type: string;
allow_env_keys: boolean;
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:

#!/bin/bash
# Verify backend contract support for allowEnvKeys / allow_env_keys
set -euo pipefail

echo "== Server schemas =="
rg -n --type=ts -C3 'codebaseSchema|addCodebaseBodySchema|allowEnvKeys|allow_env_keys' packages/server/src/routes/schemas

echo "== Server API routes =="
rg -n --type=ts -C3 '/api/codebases|registerOpenApiRoute|allowEnvKeys|allow_env_keys' packages/server/src/routes/api.ts

echo "== Generated web API types =="
rg -n --type=ts -C2 'allow_env_keys|Codebase' packages/web/src/lib/api.generated.ts

Repository: coleam00/Archon

Length of output: 15737


🏁 Script executed:

# Check the web client file at the mentioned lines
cat -n packages/web/src/lib/api.ts | sed -n '40,50p;160,185p'

Repository: coleam00/Archon

Length of output: 1455


🏁 Script executed:

# Read the complete codebase schema file
cat -n packages/server/src/routes/schemas/codebase.schemas.ts | head -50

Repository: coleam00/Archon

Length of output: 2013


🏁 Script executed:

# Search for generated API type files in the web package
fd -t f "api" packages/web/src/lib/ | head -20

Repository: coleam00/Archon

Length of output: 126


🏁 Script executed:

# Search for allowEnvKeys or allow_env_keys in web package
rg -n 'allowEnvKeys|allow_env_keys' packages/web/src/ -t ts

Repository: coleam00/Archon

Length of output: 307


🏁 Script executed:

# Search for updateCodebase schema or PATCH handling
rg -n 'updateCodebaseBodySchema|updateCodebaseSchema|PATCH.*codebases' packages/server/src/routes/ -t ts

Repository: coleam00/Archon

Length of output: 41


🏁 Script executed:

# Search for any mention of allowEnvKeys on server side
rg -n 'allowEnvKeys|allow_env_keys' packages/server/src/ -t ts

Repository: coleam00/Archon

Length of output: 41


Fix client-server contract misalignment for allowEnvKeys field.

The client sends and expects allowEnvKeys across three operations, but the server provides no support:

  • POST /api/codebases (line 164): Client sends optional allowEnvKeys but server's addCodebaseBodySchema only accepts url and path—field is silently ignored.
  • PATCH /api/codebases/:id (line 175): Client function sends allowEnvKeys, but server has no PATCH handler or schema defined.
  • Response contract (line 44): Client's CodebaseResponse expects mandatory allow_env_keys field, but server's codebaseSchema does not include it.

Either add full server support (schema fields, handlers, database column if needed) or remove allowEnvKeys from the client API until the backend is ready.

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

In `@packages/web/src/lib/api.ts` at line 44, Client-server contract mismatch: the
client uses allowEnvKeys / allow_env_keys but the server schemas and handlers
(addCodebaseBodySchema, codebaseSchema, and no PATCH handler for
/api/codebases/:id) don’t support it. Fix by either removing allowEnvKeys from
the client API or adding full server support: update addCodebaseBodySchema to
accept allow_env_keys, extend codebaseSchema to include allow_env_keys in
responses, implement a PATCH handler for /api/codebases/:id that validates and
persists allow_env_keys to the DB (and add a DB column/migration if needed), and
ensure the client and server use the same snake_case/key names in
request/response contracts.

Adds a slide-in conversations panel accessible from mobile via a
'Conversations' button in the chat topbar. Also fixes horizontal scroll
parasite on mobile by adding overflow-x: hidden to html/body/#root.
@tboome33
Copy link
Copy Markdown
Author

Nouveaux commits ajoutés (patch responsive mobile — phase 2)

eat(mobile): add conversation drawer and overflow-x fix for mobile

  • ChatPage.tsx : le panneau gauche des conversations devient un drawer overlay mobile (slide-in depuis la gauche) au lieu d'être simplement caché avec \hidden md:flex\
  • Un bouton « Conversations » + « New » apparaît dans une mini-topbar mobile (classe \md:hidden) au-dessus du chat
  • Sur desktop (≥ 768 px), le panneau reste en sidebar inline — comportement identique à avant, aucune régression
  • Le drawer se ferme automatiquement via backdrop click, bouton ×, sélection d'une conversation, ou redimensionnement en desktop
  • Approche un seul élément DOM (même div, CSS transforms), sans duplication de code ni nouveau contexte

\ ix(mobile): prevent horizontal scroll with overflow-x hidden on html/body/#root\

  • index.css : \overflow-x: hidden\ sur \html, \�ody\ et #root\ pour éliminer le scroll horizontal parasite lors du scroll vertical sur mobile (affecte toutes les routes : Chat, Dashboard, Workflows, Settings)

Tests visuels effectués

  • ✅ Desktop pleine largeur : sidebar visible, nav tabs corrects, topbar mobile masquée
  • ✅ Mobile (~420 px) : chat pleine largeur, topbar « Conversations | New » visible
  • ✅ Drawer ouvert : liste des conversations, New Chat, search, filtre projet, bouton ×

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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/web/src/routes/ChatPage.tsx`:
- Line 234: The aria-label text "Fermer le panneau conversations" in the
ChatPage component is in French while the UI is English; locate the occurrences
of aria-label="Fermer le panneau conversations" inside the ChatPage React
component (both instances reported) and replace them with an English assistive
label such as "Close conversations panel" so the screen-reader text matches the
page locale; ensure both occurrences (the one around the first reported location
and the second around the later reported location) are updated to the identical
English string for consistency.
- Around line 206-216: The drawer width uses the persisted desktop width
directly (style on the container using width and flexShrink) which can exceed
the viewport on mobile; modify ChatPage.tsx to cap the applied width on small
screens by taking the min of the saved width and the current viewport width (or
apply a CSS maxWidth: '100vw') when mobileConvOpen is used, ensuring the drawer
never exceeds window.innerWidth and still respects PANEL_MAX for desktop; update
the width calculation (the expression that builds style={{ width:
`${String(width)}px`, flexShrink: 0 }}) to use Math.min(width,
window.innerWidth) or a responsive maxWidth fallback so controls aren’t clipped
on mobile.
- Around line 150-175: The handleAddSubmit function currently uses
.then/.catch/.finally promise chains; refactor it to be an async function using
try/catch/finally to follow async/await style: mark handleAddSubmit as async,
await addCodebase(input) inside a try block, call
queryClient.invalidateQueries({ queryKey: ['codebases'] }),
setSelectedProjectId(codebase.id), setShowAddInput(false), setAddValue('') and
clear addError on success, setAddError(err.message) in the catch, and reset
setAddLoading(false) in finally; keep existing logic for determining isLocalPath
and input and preserve dependencies (addValue, addLoading, queryClient,
setSelectedProjectId) in the hook declaration.
🪄 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

Run ID: 2d91c397-82b5-47e1-9634-e8643836de0e

📥 Commits

Reviewing files that changed from the base of the PR and between a45ec74 and a19e630.

📒 Files selected for processing (2)
  • packages/web/src/index.css
  • packages/web/src/routes/ChatPage.tsx
✅ Files skipped from review due to trivial changes (1)
  • packages/web/src/index.css

Comment thread packages/web/src/routes/ChatPage.tsx Outdated
Comment on lines +150 to +175
const handleAddSubmit = useCallback((): void => {
const trimmed = addValue.trim();
if (!trimmed || addLoading) return;

setAddLoading(true);
setAddError(null);

const isLocalPath =
trimmed.startsWith('/') || trimmed.startsWith('~') || /^[A-Za-z]:[/\\]/.test(trimmed);
const input = isLocalPath ? { path: trimmed } : { url: trimmed };

void addCodebase(input)
.then(codebase => {
void queryClient.invalidateQueries({ queryKey: ['codebases'] });
setSelectedProjectId(codebase.id);
setShowAddInput(false);
setAddValue('');
setAddError(null);
})
.catch((err: Error) => {
setAddError(err.message);
})
.finally(() => {
setAddLoading(false);
});
}, [addValue, addLoading, queryClient, setSelectedProjectId]);
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:

#!/bin/bash
# Verify promise-chain usage in this file (should be zero after refactor).
rg -nP --type=tsx '\.then\s*\(|\.catch\s*\(|\.finally\s*\(' packages/web/src/routes/ChatPage.tsx

Repository: coleam00/Archon

Length of output: 86


🏁 Script executed:

#!/bin/bash
# First, let's check if the file exists and see its size
wc -l packages/web/src/routes/ChatPage.tsx

# Search for promise chains using correct syntax (tsx is treated as ts in ripgrep)
rg -n '\.then\s*\(|\.catch\s*\(|\.finally\s*\(' packages/web/src/routes/ChatPage.tsx

# Also read the specific lines mentioned in the review (150-175 and 177-181)
echo "=== Lines 150-175 ==="
sed -n '150,175p' packages/web/src/routes/ChatPage.tsx

echo "=== Lines 177-181 ==="
sed -n '177,181p' packages/web/src/routes/ChatPage.tsx

Repository: coleam00/Archon

Length of output: 1277


Use async/await in handleAddSubmit (avoid promise chains).

Lines 150-175 use .then()/.catch()/.finally() chains which violate the async/await guideline. Convert to async/await with try/catch for clearer control flow.

Proposed refactor
-  const handleAddSubmit = useCallback((): void => {
+  const handleAddSubmit = useCallback(async (): Promise<void> => {
     const trimmed = addValue.trim();
     if (!trimmed || addLoading) return;

     setAddLoading(true);
     setAddError(null);

     const isLocalPath =
       trimmed.startsWith('/') || trimmed.startsWith('~') || /^[A-Za-z]:[/\\]/.test(trimmed);
     const input = isLocalPath ? { path: trimmed } : { url: trimmed };

-    void addCodebase(input)
-      .then(codebase => {
-        void queryClient.invalidateQueries({ queryKey: ['codebases'] });
-        setSelectedProjectId(codebase.id);
-        setShowAddInput(false);
-        setAddValue('');
-        setAddError(null);
-      })
-      .catch((err: Error) => {
-        setAddError(err.message);
-      })
-      .finally(() => {
-        setAddLoading(false);
-      });
+    try {
+      const codebase = await addCodebase(input);
+      await queryClient.invalidateQueries({ queryKey: ['codebases'] });
+      setSelectedProjectId(codebase.id);
+      setShowAddInput(false);
+      setAddValue('');
+      setAddError(null);
+    } catch (err: unknown) {
+      setAddError(err instanceof Error ? err.message : 'Failed to add project');
+    } finally {
+      setAddLoading(false);
+    }
   }, [addValue, addLoading, queryClient, setSelectedProjectId]);

   const handleAddKeyDown = useCallback(
     (e: React.KeyboardEvent): void => {
       if (e.key === 'Enter') {
-        handleAddSubmit();
+        void handleAddSubmit();
       } else if (e.key === 'Escape') {
         setShowAddInput(false);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleAddSubmit = useCallback((): void => {
const trimmed = addValue.trim();
if (!trimmed || addLoading) return;
setAddLoading(true);
setAddError(null);
const isLocalPath =
trimmed.startsWith('/') || trimmed.startsWith('~') || /^[A-Za-z]:[/\\]/.test(trimmed);
const input = isLocalPath ? { path: trimmed } : { url: trimmed };
void addCodebase(input)
.then(codebase => {
void queryClient.invalidateQueries({ queryKey: ['codebases'] });
setSelectedProjectId(codebase.id);
setShowAddInput(false);
setAddValue('');
setAddError(null);
})
.catch((err: Error) => {
setAddError(err.message);
})
.finally(() => {
setAddLoading(false);
});
}, [addValue, addLoading, queryClient, setSelectedProjectId]);
const handleAddSubmit = useCallback(async (): Promise<void> => {
const trimmed = addValue.trim();
if (!trimmed || addLoading) return;
setAddLoading(true);
setAddError(null);
const isLocalPath =
trimmed.startsWith('/') || trimmed.startsWith('~') || /^[A-Za-z]:[/\\]/.test(trimmed);
const input = isLocalPath ? { path: trimmed } : { url: trimmed };
try {
const codebase = await addCodebase(input);
await queryClient.invalidateQueries({ queryKey: ['codebases'] });
setSelectedProjectId(codebase.id);
setShowAddInput(false);
setAddValue('');
setAddError(null);
} catch (err: unknown) {
setAddError(err instanceof Error ? err.message : 'Failed to add project');
} finally {
setAddLoading(false);
}
}, [addValue, addLoading, queryClient, setSelectedProjectId]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/routes/ChatPage.tsx` around lines 150 - 175, The
handleAddSubmit function currently uses .then/.catch/.finally promise chains;
refactor it to be an async function using try/catch/finally to follow
async/await style: mark handleAddSubmit as async, await addCodebase(input)
inside a try block, call queryClient.invalidateQueries({ queryKey: ['codebases']
}), setSelectedProjectId(codebase.id), setShowAddInput(false), setAddValue('')
and clear addError on success, setAddError(err.message) in the catch, and reset
setAddLoading(false) in finally; keep existing logic for determining isLocalPath
and input and preserve dependencies (addValue, addLoading, queryClient,
setSelectedProjectId) in the hook declaration.

Comment thread packages/web/src/routes/ChatPage.tsx Outdated
Comment on lines +206 to +216
className={cn(
'flex flex-col border-r border-border bg-surface overflow-hidden',
// Mobile: fixed full-height overlay
'fixed inset-y-0 left-0 z-50',
// Desktop: back to normal inline flow
'md:relative md:inset-auto md:z-auto md:h-full',
// Slide animation — mobile toggles, desktop always shown
'transition-transform duration-300 ease-in-out',
mobileConvOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'
)}
style={{ width: `${String(width)}px`, flexShrink: 0 }}
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

Cap mobile drawer width to viewport to prevent clipped controls.

Line 216 applies persisted desktop width directly on mobile. If width is saved near PANEL_MAX, the fixed drawer can render wider than the viewport and hide actionable UI on small screens.

📱 Proposed fix
-        style={{ width: `${String(width)}px`, flexShrink: 0 }}
+        style={{ width: `min(100vw, ${String(width)}px)`, flexShrink: 0 }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
className={cn(
'flex flex-col border-r border-border bg-surface overflow-hidden',
// Mobile: fixed full-height overlay
'fixed inset-y-0 left-0 z-50',
// Desktop: back to normal inline flow
'md:relative md:inset-auto md:z-auto md:h-full',
// Slide animation — mobile toggles, desktop always shown
'transition-transform duration-300 ease-in-out',
mobileConvOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'
)}
style={{ width: `${String(width)}px`, flexShrink: 0 }}
className={cn(
'flex flex-col border-r border-border bg-surface overflow-hidden',
// Mobile: fixed full-height overlay
'fixed inset-y-0 left-0 z-50',
// Desktop: back to normal inline flow
'md:relative md:inset-auto md:z-auto md:h-full',
// Slide animation — mobile toggles, desktop always shown
'transition-transform duration-300 ease-in-out',
mobileConvOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'
)}
style={{ width: `min(100vw, ${String(width)}px)`, flexShrink: 0 }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/routes/ChatPage.tsx` around lines 206 - 216, The drawer
width uses the persisted desktop width directly (style on the container using
width and flexShrink) which can exceed the viewport on mobile; modify
ChatPage.tsx to cap the applied width on small screens by taking the min of the
saved width and the current viewport width (or apply a CSS maxWidth: '100vw')
when mobileConvOpen is used, ensuring the drawer never exceeds window.innerWidth
and still respects PANEL_MAX for desktop; update the width calculation (the
expression that builds style={{ width: `${String(width)}px`, flexShrink: 0 }})
to use Math.min(width, window.innerWidth) or a responsive maxWidth fallback so
controls aren’t clipped on mobile.

Comment thread packages/web/src/routes/ChatPage.tsx Outdated
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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/web/src/components/layout/Layout.tsx`:
- Around line 15-18: The mobile nav state (open from useState in Layout and
provided via MobileNavContext.Provider) isn't reset when switching to desktop,
causing the drawer to reopen when returning to mobile; add a useEffect inside
the Layout component that listens for viewport changes (e.g., window.matchMedia
for the desktop breakpoint or window resize) and calls setOpen(false) when the
viewport crosses into desktop size, and ensure the listener is cleaned up on
unmount.

In `@packages/web/src/routes/ChatPage.tsx`:
- Around line 194-199: The mobile overlay (rendered when mobileConvOpen) is
being rendered with z-40/z-50 but the sticky mobile topbar (the sticky topbar
element with z-50) is rendered later and remains above the backdrop; update the
stacking so the overlay sits above the topbar by changing the overlay or topbar
z-index consistently (e.g., make the overlay z higher than the sticky topbar or
reduce the topbar z), ensuring the element rendered for the backdrop
(mobileConvOpen handler and its div) uses a z-class greater than the sticky
topbar’s z-class so the backdrop truly blocks and prevents clicks on the sticky
topbar.
🪄 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

Run ID: 88711c7b-b41e-4c56-b64f-6b2487966b57

📥 Commits

Reviewing files that changed from the base of the PR and between a19e630 and ba0dbc7.

📒 Files selected for processing (3)
  • packages/web/src/components/chat/MessageInput.tsx
  • packages/web/src/components/layout/Layout.tsx
  • packages/web/src/routes/ChatPage.tsx
✅ Files skipped from review due to trivial changes (1)
  • packages/web/src/components/chat/MessageInput.tsx

Comment on lines +15 to +18
const [open, setOpen] = useState(false);

return (
<MobileNavContext.Provider value={{ open, setOpen }}>
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 | 🟡 Minor

Reset the mobile nav state on desktop resize.

Line 15 keeps open alive, and Lines 25-42 only hide the drawer with responsive classes. If the menu is open during a rotate/resize, switching back to mobile reopens it unexpectedly.

🔧 Proposed fix
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
@@
 export function Layout(): React.ReactElement {
   const [open, setOpen] = useState(false);
+
+  useEffect((): (() => void) => {
+    const onResize = (): void => {
+      if (window.innerWidth >= 768) {
+        setOpen(false);
+      }
+    };
+
+    window.addEventListener('resize', onResize);
+    return (): void => {
+      window.removeEventListener('resize', onResize);
+    };
+  }, []);
 
   return (

Also applies to: 25-42

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

In `@packages/web/src/components/layout/Layout.tsx` around lines 15 - 18, The
mobile nav state (open from useState in Layout and provided via
MobileNavContext.Provider) isn't reset when switching to desktop, causing the
drawer to reopen when returning to mobile; add a useEffect inside the Layout
component that listens for viewport changes (e.g., window.matchMedia for the
desktop breakpoint or window resize) and calls setOpen(false) when the viewport
crosses into desktop size, and ensure the listener is cleaned up on unmount.

Comment thread packages/web/src/routes/ChatPage.tsx Outdated
Change drawer from 'fixed inset-y-0 z-50' to 'fixed top-12 bottom-0 z-40'
so it opens below the 48px topbar instead of overlapping it.
Prevents the mobile hamburger drawer and its backdrop from overlapping
the sticky topbar (z-50). Pattern matches ChatPage.tsx fix.
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: 1

♻️ Duplicate comments (3)
packages/web/src/routes/ChatPage.tsx (3)

231-235: ⚠️ Potential issue | 🟡 Minor

Keep the aria-labels in English to match the rest of the screen.

These labels are still French while the visible UI copy is English. Please update both strings so assistive text stays consistent with the page locale.

Also applies to: 372-376

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

In `@packages/web/src/routes/ChatPage.tsx` around lines 231 - 235, The aria-labels
in ChatPage.tsx are in French while the UI is English; update the two button
elements (the one that calls setMobileConvOpen(false) and the other at the
second occurrence that toggles mobile conversation panel) to use English
aria-labels (e.g., "Close conversations panel" and "Open conversations panel" or
similar) so the assistive text matches the page locale; locate the button
elements in the ChatPage component that use setMobileConvOpen and replace the
French strings with the appropriate English aria-labels.

205-216: ⚠️ Potential issue | 🟠 Major

Cap the mobile drawer width to the viewport.

This still applies the persisted desktop width directly on mobile, so a saved value near PANEL_MAX can overflow smaller screens and clip the drawer UI.

Suggested fix
-        style={{ width: `${String(width)}px`, flexShrink: 0 }}
+        style={{ width: `${String(width)}px`, maxWidth: '100vw', flexShrink: 0 }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/routes/ChatPage.tsx` around lines 205 - 216, The mobile
drawer currently applies the persisted desktop "width" directly, which can
overflow small viewports; update the ChatPage drawer element (the div using
mobileConvOpen and the width variable) to cap the applied width on mobile by
either adding a CSS constraint (e.g., max-width: 100vw / maxWidth: '100%') or
computing a cappedWidth = Math.min(width, window.innerWidth || width) and using
that for the style instead of raw width; ensure the style keeps flexShrink: 0
and only constrains the width value used for rendering on narrow screens.

150-175: ⚠️ Potential issue | 🟠 Major

Convert handleAddSubmit to async/await.

This still uses a .then()/.catch()/.finally() chain, which keeps the error path and loading cleanup harder to follow than necessary. Please refactor this callback to async/await, and once you do, call it as void handleAddSubmit() at Line 180. Based on learnings: Applies to **/*.{ts,tsx} : Always use async/await for promises instead of .then() chains or other promise handling patterns.

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

In `@packages/web/src/routes/ChatPage.tsx` around lines 150 - 175, Convert the
handleAddSubmit callback to an async function that uses try/catch/finally: mark
function handleAddSubmit as async, compute trimmed/isLocalPath and input the
same way, then await addCodebase(input) inside try, on success call
queryClient.invalidateQueries({ queryKey: ['codebases'] }),
setSelectedProjectId(codebase.id), setShowAddInput(false), setAddValue(''), and
setAddError(null); in catch setAddError(err.message); in finally
setAddLoading(false). Ensure you still guard early for empty trimmed or
addLoading and setAddLoading(true)/setAddError(null) before the try block, and
update the call site to invoke it as void handleAddSubmit().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/web/src/routes/ChatPage.tsx`:
- Around line 205-216: The panel currently only translates off-screen when
mobileConvOpen is false, leaving interactive elements reachable; update the
container (the div using mobileConvOpen in its className and style) so that when
mobileConvOpen is false on small screens it is non-interactive and hidden from
assistive tech—either conditionally don't render the panel on mobile or add
attributes/classes to disable interaction (e.g., aria-hidden="true", inert,
pointer-events-none, and remove focusability by ensuring child inputs/buttons
get tabIndex={-1} or are unmounted) while preserving the desktop behavior (md:
classes); use the existing mobileConvOpen flag to toggle this behavior.

---

Duplicate comments:
In `@packages/web/src/routes/ChatPage.tsx`:
- Around line 231-235: The aria-labels in ChatPage.tsx are in French while the
UI is English; update the two button elements (the one that calls
setMobileConvOpen(false) and the other at the second occurrence that toggles
mobile conversation panel) to use English aria-labels (e.g., "Close
conversations panel" and "Open conversations panel" or similar) so the assistive
text matches the page locale; locate the button elements in the ChatPage
component that use setMobileConvOpen and replace the French strings with the
appropriate English aria-labels.
- Around line 205-216: The mobile drawer currently applies the persisted desktop
"width" directly, which can overflow small viewports; update the ChatPage drawer
element (the div using mobileConvOpen and the width variable) to cap the applied
width on mobile by either adding a CSS constraint (e.g., max-width: 100vw /
maxWidth: '100%') or computing a cappedWidth = Math.min(width, window.innerWidth
|| width) and using that for the style instead of raw width; ensure the style
keeps flexShrink: 0 and only constrains the width value used for rendering on
narrow screens.
- Around line 150-175: Convert the handleAddSubmit callback to an async function
that uses try/catch/finally: mark function handleAddSubmit as async, compute
trimmed/isLocalPath and input the same way, then await addCodebase(input) inside
try, on success call queryClient.invalidateQueries({ queryKey: ['codebases'] }),
setSelectedProjectId(codebase.id), setShowAddInput(false), setAddValue(''), and
setAddError(null); in catch setAddError(err.message); in finally
setAddLoading(false). Ensure you still guard early for empty trimmed or
addLoading and setAddLoading(true)/setAddError(null) before the try block, and
update the call site to invoke it as void handleAddSubmit().
🪄 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

Run ID: 8b3065bd-6df5-4485-b55e-12d82342ba14

📥 Commits

Reviewing files that changed from the base of the PR and between ba0dbc7 and f74f344.

📒 Files selected for processing (1)
  • packages/web/src/routes/ChatPage.tsx

Comment thread packages/web/src/routes/ChatPage.tsx Outdated
Comment on lines +205 to +216
<div
className={cn(
'flex flex-col border-r border-border bg-surface overflow-hidden',
// Mobile: fixed overlay starting below the sticky topbar (~48 px)
'fixed top-12 bottom-0 left-0 z-40',
// Desktop: back to normal inline flow
'md:relative md:inset-auto md:z-auto md:h-full',
// Slide animation — mobile toggles, desktop always shown
'transition-transform duration-300 ease-in-out',
mobileConvOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'
)}
style={{ width: `${String(width)}px`, flexShrink: 0 }}
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

Hide the closed mobile drawer from focus and assistive tech.

When mobileConvOpen is false, this panel is only moved off-screen. Its buttons, inputs, and select remain mounted and can still be reached by keyboard/screen-reader users on mobile. Please also disable interaction when closed here—e.g. conditionally render it on mobile, or mark the closed state inert/non-focusable instead of relying on translate-x-full alone.

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

In `@packages/web/src/routes/ChatPage.tsx` around lines 205 - 216, The panel
currently only translates off-screen when mobileConvOpen is false, leaving
interactive elements reachable; update the container (the div using
mobileConvOpen in its className and style) so that when mobileConvOpen is false
on small screens it is non-interactive and hidden from assistive tech—either
conditionally don't render the panel on mobile or add attributes/classes to
disable interaction (e.g., aria-hidden="true", inert, pointer-events-none, and
remove focusability by ensuring child inputs/buttons get tabIndex={-1} or are
unmounted) while preserving the desktop behavior (md: classes); use the existing
mobileConvOpen flag to toggle this behavior.

tboome33 and others added 11 commits April 13, 2026 16:51
- Ajout du hook useVisualViewport qui suit window.visualViewport.height
  pour que le conteneur principal se redimensionne quand le clavier mobile apparaît
- Layout.tsx : remplace h-dvh par le hook (style inline height) + overflow-hidden
- index.html : ajout interactive-widget=resizes-content dans la meta viewport
  (Chrome Android redimensionne le viewport au lieu de chevaucher le clavier)
- index.css : html/body/#root overflow:hidden pour supprimer les scrollbars
  parasites ; scrollbar-width:none + ::-webkit-scrollbar{display:none} sur mobile ;
  scrollbars desktop conservées via media query min-width:769px
On iOS Safari the virtual keyboard overlays the page instead of resizing
the viewport. Without position:fixed on the root container, iOS scrolls the
layout viewport down when the keyboard opens, causing the chat input to remain
hidden even though visualViewport.height correctly decreases.

Setting position:fixed + top/left/right:0 + height:vpHeight pins the container
to the visible area above the keyboard, so the MessageInput is always reachable.
@tboome33
Copy link
Copy Markdown
Author

Superseded by a more comprehensive PR that includes all features from the beginning plus slash command autocomplete (#1204) and projects sidebar (#1205).

@tboome33 tboome33 closed this Apr 14, 2026
@tboome33
Copy link
Copy Markdown
Author

Superseded by #1209 which consolidates all responsive mobile UI improvements plus new features (slash command autocomplete, ChatGPT-style projects sidebar, sidebar pin/unpin). Please review #1209 instead.

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