Compare commits

..

3 Commits

Author SHA1 Message Date
3cb1ead5e2 feat(mobile): add hamburger + file explorer button to root empty state 2026-05-16 15:23:33 +00:00
bbf9fac936 docs(roadmap): reconcile post-v1.6.1 + v1.6.2 in-flight
Update version summary: v1.6-mobile-pass and v1.6.1-cleanup are now
merged with SHAs; v1.6.2-mobile-ui-fixes added as in-flight with its
4-commit plan. v1.6.1-cleanup details rewritten to reflect what
actually shipped (B1) vs what was audited-only (secrets, panes,
unused exports, hand-rolled patterns, mount scope, etc.).

Closed two open items: session_renamed has a server publisher since
v1.4; PATCH /api/panes/:id is moot (endpoint never re-introduced).
Dependency graph updated with v1.6.2 node between v1.6.1 and v1.7.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 06:37:27 +00:00
6fa6eb7f32 feat(inference): raise MAX_TOOL_LOOP_DEPTH from 5 to 15
Allows assistant turns up to 15 tool calls in a single chain before
the loop-depth guard trips. Real chats commonly need 6-10 tool calls
(grep -> view_file -> view_file -> grep -> view_file -> answer); the
old cap of 5 was firing on legitimate investigation patterns.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 06:37:27 +00:00
3 changed files with 91 additions and 32 deletions

View File

@@ -10,7 +10,7 @@ const BASE_SYSTEM_PROMPT = (projectPath: string) =>
`You are BooCode Chat, a code investigation assistant. The user is working on a project located at ${projectPath}. Use the file-read tools (view_file, list_dir, grep, find_files) to investigate code when needed. Be concise. Cite file paths and line numbers when discussing code. Do not hallucinate file contents — read the file first. Tool results may be truncated; if so, narrow your query rather than guessing.`; `You are BooCode Chat, a code investigation assistant. The user is working on a project located at ${projectPath}. Use the file-read tools (view_file, list_dir, grep, find_files) to investigate code when needed. Be concise. Cite file paths and line numbers when discussing code. Do not hallucinate file contents — read the file first. Tool results may be truncated; if so, narrow your query rather than guessing.`;
const DB_FLUSH_INTERVAL_MS = 500; const DB_FLUSH_INTERVAL_MS = 500;
const MAX_TOOL_LOOP_DEPTH = 5; const MAX_TOOL_LOOP_DEPTH = 15;
export interface InferenceFrame { export interface InferenceFrame {
type: type:

View File

@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { ChevronDown, ChevronRight, Folder, RotateCcw } from 'lucide-react'; import { ChevronDown, ChevronRight, Folder, FolderTree, Menu, RotateCcw } from 'lucide-react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { AddProjectModal } from '@/components/AddProjectModal'; import { AddProjectModal } from '@/components/AddProjectModal';
@@ -8,6 +8,9 @@ import { api } from '@/api/client';
import type { Project } from '@/api/types'; import type { Project } from '@/api/types';
import { sessionEvents } from '@/hooks/sessionEvents'; import { sessionEvents } from '@/hooks/sessionEvents';
import { useSidebar } from '@/hooks/useSidebar'; import { useSidebar } from '@/hooks/useSidebar';
import { useSidebarDrawer } from '@/hooks/useSidebarDrawer';
import { useRightRailDrawer } from '@/hooks/useRightRailDrawer';
import { useViewport } from '@/hooks/useViewport';
export function Home() { export function Home() {
const { data } = useSidebar(); const { data } = useSidebar();
@@ -15,6 +18,9 @@ export function Home() {
const [createOpen, setCreateOpen] = useState(false); const [createOpen, setCreateOpen] = useState(false);
const [archived, setArchived] = useState<Project[] | null>(null); const [archived, setArchived] = useState<Project[] | null>(null);
const [showArchived, setShowArchived] = useState(false); const [showArchived, setShowArchived] = useState(false);
const { setOpen: setSidebarOpen } = useSidebarDrawer();
const { toggle: toggleRightRail } = useRightRailDrawer();
const { isMobile } = useViewport();
const empty = data ? data.projects.length === 0 : false; const empty = data ? data.projects.length === 0 : false;
@@ -70,6 +76,30 @@ export function Home() {
} }
return ( return (
<div className="flex-1 flex flex-col min-h-0">
{isMobile && (
<header
className="border-b px-3 sm:px-4 py-2 flex items-center gap-1.5 shrink-0 text-sm"
style={{ paddingTop: 'max(0.5rem, env(safe-area-inset-top))' }}
>
<button
type="button"
onClick={() => setSidebarOpen(true)}
className="inline-flex items-center justify-center -ml-1 min-w-[44px] min-h-[44px] rounded text-muted-foreground hover:bg-muted hover:text-foreground shrink-0"
aria-label="Open sidebar"
>
<Menu className="size-5" />
</button>
<button
type="button"
onClick={toggleRightRail}
className="inline-flex items-center justify-center -mr-1 ml-auto min-w-[44px] min-h-[44px] rounded text-muted-foreground hover:bg-muted hover:text-foreground shrink-0"
aria-label="Toggle file browser"
>
<FolderTree className="size-5" />
</button>
</header>
)}
<div className="flex-1 flex flex-col items-center px-6 py-12 overflow-y-auto"> <div className="flex-1 flex flex-col items-center px-6 py-12 overflow-y-auto">
<div className="w-full max-w-md space-y-6"> <div className="w-full max-w-md space-y-6">
<div className="text-center space-y-3"> <div className="text-center space-y-3">
@@ -131,5 +161,6 @@ export function Home() {
<AddProjectModal open={addOpen} onOpenChange={setAddOpen} onAdded={() => {}} /> <AddProjectModal open={addOpen} onOpenChange={setAddOpen} onAdded={() => {}} />
<CreateProjectModal open={createOpen} onOpenChange={setCreateOpen} /> <CreateProjectModal open={createOpen} onOpenChange={setCreateOpen} />
</div> </div>
</div>
); );
} }

View File

@@ -27,8 +27,9 @@ Live at `https://code.indifferentketchup.com` (Caddy → Authelia → Tailscale
|v1.4 |Fork from message + delete message + header polish + housekeeping |✅ Merged |Was original “Batch 5” | |v1.4 |Fork from message + delete message + header polish + housekeeping |✅ Merged |Was original “Batch 5” |
|v1.5 |Refactor splits, vitest harness (23 tests), error-log surfacing, `/opt:ro` + `BOOTSTRAP_ROOT`, persistent context-window tracker |✅ Merged |— | |v1.5 |Refactor splits, vitest harness (23 tests), error-log surfacing, `/opt:ro` + `BOOTSTRAP_ROOT`, persistent context-window tracker |✅ Merged |— |
|v1.5.1 |Bootstrap hotfix: git in container, SSH keypair, known_hosts, SSH URL rewrite, /opt/projects label |✅ Merged |`4a9f207` | |v1.5.1 |Bootstrap hotfix: git in container, SSH keypair, known_hosts, SSH URL rewrite, /opt/projects label |✅ Merged |`4a9f207` |
|v1.6-mobile-pass|Mobile pass: drawer, pane stacking, long-press, swipe-to-close, pull-to-refresh, IME safety, safe-area, tap targets + H1 path-guard fix|🔄 Hand-back received, uncommitted|Was original “Batch 4” | |v1.6-mobile-pass|Mobile pass: drawer, pane stacking, long-press, swipe-to-close, pull-to-refresh, IME safety, safe-area, tap targets + H1 path-guard fix|✅ Merged |`57c883b..943ae7d` (6 commits) |
|v1.6.1-cleanup |Stale code audit, overengineering audit, secrets hygiene, RightRail mobile fix |Planned (next) |— | |v1.6.1-cleanup |Mostly audit-only; one fix shipped: RightRail `max-md:hidden` wrapper. Audit reports for secrets, stale code, panes, mount scope, hand-rolled patterns deferred to follow-ups |✅ Merged |`6a9fe18` |
|v1.6.2-mobile-ui-fixes|Mobile UI polish from device testing: kill single-pane navigator chrome, header rework, “New chat” in long-press menu, RightRail as mobile drawer (reverts v1.6.1 wrapper) |🔄 Hand-back received, uncommitted|— |
|v1.7 |Drag-drop + paste-as-attachment (chip infra extension) |Planned |Was Batch 6 | |v1.7 |Drag-drop + paste-as-attachment (chip infra extension) |Planned |Was Batch 6 |
|v1.8 |Settings drawer (system prompt per project + session, web search toggle) |Planned |Was Batch 7 | |v1.8 |Settings drawer (system prompt per project + session, web search toggle) |Planned |Was Batch 7 |
|v1.9 |Web search backend: SearXNG `web_search` + `web_fetch` tools |Planned |Was Batch 8 | |v1.9 |Web search backend: SearXNG `web_search` + `web_fetch` tools |Planned |Was Batch 8 |
@@ -139,15 +140,16 @@ Dockerfile (git installed in container), docker-compose.yml, project_bootstrap.t
----- -----
### v1.6-mobile-pass 🔄 ### v1.6-mobile-pass
**Hand-back received, uncommitted on `v1.6-mobile-pass`.** 5-commit sequence proposed: **Merged via 6 commits `57c883b..943ae7d`** (5 functional + 1 docs):
1. `chore: fix resolveProjectPath whitelist-root bypass` (H1 — dropped `real !== whitelistReal` short-circuit; 23/23 pass). 1. `57c883b chore: fix resolveProjectPath whitelist-root bypass` (H1 — dropped `real !== whitelistReal` short-circuit; flipped the v1.5 BEHAVIOR GAP test; 23/23 pass).
1. `feat(mobile): viewport hook + sidebar drawer + hamburger headers` (M1 + M2 + M6-header). 1. `a643b5f feat(mobile): viewport hook + sidebar drawer + hamburger headers` (M1 + M2 + M6-header).
1. `feat(mobile): single-pane stack + long-press tab menu + swipe-to-close` (M3 + M4 + A2). 1. `cd897d6 feat(mobile): single-pane stack + long-press tab menu + swipe-to-close` (M3 + M4 + A2).
1. `feat(mobile): chat input keybinds + safe-area + tap targets + overflow safety` (M5 + M6-bottom + M7 + M8). 1. `273eeac feat(mobile): chat input keybinds + safe-area + tap targets + overflow safety` (M5 + M6-bottom + M7 + M8).
1. `feat(mobile): pull-to-refresh sidebar list` (A1). 1. `4b5b9b2 feat(mobile): pull-to-refresh sidebar list` (A1).
1. `943ae7d docs: add v1.x roadmap snapshot` (this file).
**Decisions:** **Decisions:**
@@ -168,21 +170,41 @@ Dockerfile (git installed in container), docker-compose.yml, project_bootstrap.t
----- -----
### v1.6.1-cleanup — Stale + overengineering audit + secrets hygiene (next) ### v1.6.1-cleanup ✅ (`6a9fe18`)
**Depends on:** v1.6 committed. **Shipped:** RightRail wrapped in `<div className="max-md:hidden contents">` so it's hidden entirely below the md breakpoint on mobile. (Note: v1.6.2 reverses this and replaces with a proper mobile drawer — see below.)
**Scope:** **Audited but not shipped (queued for follow-ups):**
1. RightRail mobile fix (`max-md:hidden` on outer container). - **Secrets hygiene:** `secrets/boocode_gitea` is NOT tracked; never committed to any branch; `.gitignore` already covers `secrets/`. Rotation is a Gitea-side action, no repo change needed.
1. Secrets audit: rotate `secrets/boocode_gitea`, confirm `.gitignore` covers `secrets/`, scan git history (`git log --all -- secrets/`), `git filter-repo` or BFG if exposed in history, force-push if rewriting. - **`.bak` files:** 3 leftover from v1.5.1 (`docker-compose.yml.bak-20260516`, `Dockerfile.bak-20260516`, `apps/web/src/components/CreateProjectModal.tsx.bak-20260516`). Git-invisible via global `~/.gitignore_global` (`*.bak*`). Decide per file.
1. Fix agent SSH key path so future Claude Code dispatches dont fall back to in-repo keys. - **Unused exports:** neither `knip` nor `ts-prune` installed. Proposal pending.
1. Stale code audit: pruning unused exports, dead WS frames (e.g. `session_renamed` server publisher TODO from Batch 1), backup `.bak` files, unused imports. - **Dead WS frames:** `session_renamed` HAS a server publisher (`routes/sessions.ts:140`, added in v1.4) — the roadmap's "no server publisher" open item is **STALE**, crossed off. The `InferenceFrame` union still declares `session_renamed` as a type variant but no code publishes it on the per-session channel; trivial 1-line cleanup deferred.
1. Overengineering audit: places where hand-rolled patterns are more complex than necessary, places where singleton hooks should consolidate (`useSessionStream` refcount). - **Unused imports:** web `tsc --noUnusedLocals --noUnusedParameters` returns 0 warnings.
1. PATCH `/api/panes/:id` session-ownership check tightening. - **`useSessionStream` refcount:** opportunity confirmed (~90 lines diff to apply the `useSidebar`-style module-scope singleton pattern). Risk LOW. Queued for v1.6.2 or later.
1. `/opt:/opt:ro` mount whitelist tightening (precursor to BooCoder). - **PATCH `/api/panes/:id` ownership:** **MOOT** — endpoint does not exist (the pane REST API was never re-introduced after pane state moved to client-side localStorage in v1.2). Crossed off open items.
- **Hand-rolled patterns vs library:** 5 hand-rolled hooks/components total 336 lines. None duplicates anything in existing deps; library swap (`@use-gesture`, `react-pull-to-refresh`) not worth the dep cost yet.
- **`/opt:/opt:ro` mount tightening:** Two-option plan documented for v1.6.2 — Option A (per-project bind-mounts) or Option B (deny `.env` pattern in `pathGuard`). Option B is the simpler short-term fix.
**No new features. No schema changes.** -----
### v1.6.2-mobile-ui-fixes 🔄
**Hand-back received, uncommitted on `v1.6.2-mobile-ui-fixes`.** 4-commit sequence proposed:
1. `fix(mobile): hide Split button + single-pane navigator chrome` (G1 — wrap the Workspace Split row in `!isMobile`).
1. `feat(mobile): rework Session and Project headers for narrow viewports` (G2 — breadcrumb `hidden sm:flex`, session name cap `max-w-[140px] sm:max-w-[280px]`, project page heading `text-base sm:text-lg`, “New session” icon-only on mobile).
1. `feat(mobile): add "New chat" to tab long-press context menu` (G3 — top of menu, separator, then existing items).
1. `feat(mobile): right-rail as drawer on mobile, header toggle button` (G4 option b — new `useRightRailDrawer` Context hook, `RightRail` renders as fixed `w-[85vw] max-w-sm` drawer on mobile, FolderTree button in Session header, **reverts v1.6.1's `max-md:hidden` wrapper**).
**Decisions:**
- G4 option b chosen: mobile file browsing IS useful; drawer pattern mirrors `useSidebarDrawer`.
- G2 single-row session-name+model layout (model picker right-aligned), per spec example.
- G3 "New chat" at top, separator, then Rename.
- G2 "New session" button: icon-only on mobile via `<span className="hidden sm:inline">New session</span>`.
**Adjacent uncommitted change (not part of v1.6.2):** `MAX_TOOL_LOOP_DEPTH 5 → 15` in `apps/server/src/services/inference.ts`. Sam-authored, sitting in working tree on `v1.6.2-mobile-ui-fixes`. **NOT on main as of this update.** Commit separately.
----- -----
@@ -407,14 +429,17 @@ settings
## Known open items ## Known open items
- **`useSessionStream` refcount.** Two ChatPanes = two WS. Apply singleton pattern. Tracked in v1.6.1. - **`useSessionStream` refcount.** Two ChatPanes = two WS. Apply singleton pattern. Audited in v1.6.1, queued.
- **PATCH `/api/panes/:id` lacks session-ownership check.** Single-user fine; tighten in v1.6.1. - **`/opt:/opt:ro` mount exposes all `.env` files.** Whitelist scope before BooCoder. Two-option plan documented in v1.6.1 audit; ship in v1.6.2 or v1.7.
- **`/opt:/opt:ro` mount exposes all `.env` files.** Whitelist scope before BooCoder. Tracked in v1.6.1. - **`secrets/boocode_gitea` in repo working tree.** Never committed (git-invisible via global ignore). Rotate the Gitea-side key when convenient; no repo action required.
- **`session_renamed` no server WS publisher.** Carried from Batch 2. Tracked in v1.6.1.
- **`secrets/boocode_gitea` in repo.** v1.5.1 dispatch fallback. Rotation + history scrub in v1.6.1.
- **Dormant in-boolab BooCode mode.** Reference only. - **Dormant in-boolab BooCode mode.** Reference only.
- **BooCoder container.** Post-v1.x. - **BooCoder container.** Post-v1.x.
**Closed since last update:**
- ~~`session_renamed` no server WS publisher~~ — server publishes via `broker.publishUser` from `routes/sessions.ts:140` (added in v1.4). Confirmed in v1.6.1 audit.
- ~~PATCH `/api/panes/:id` lacks session-ownership check~~ — endpoint does not exist; the pane REST API was never re-introduced after v1.2 moved pane state to localStorage.
----- -----
## Dependency graph ## Dependency graph
@@ -456,7 +481,10 @@ v1.5.1 (bootstrap hotfix) │
v1.6-mobile-pass │ v1.6-mobile-pass │
│ │ │ │
▼ │ ▼ │
v1.6.1-cleanup ◄─────────────┘ v1.6.1-cleanup
│ │
▼ │
v1.6.2-mobile-ui-fixes ◄─────┘
v1.7 (drag-drop) ◄── v1.1-batch3.5 v1.7 (drag-drop) ◄── v1.1-batch3.5