Skip to content

feat(web): resizable railbars with max width and reset-on-refresh - #2

Closed
ikarys wants to merge 29 commits into
developfrom
feat/resizable-railbars
Closed

feat(web): resizable railbars with max width and reset-on-refresh#2
ikarys wants to merge 29 commits into
developfrom
feat/resizable-railbars

Conversation

@ikarys

@ikarys ikarys commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

Add mouse-draggable resize handles to the left and right railbars (sidebars). Users can now drag the sidebar edges to resize them, subject to minimum/maximum width constraints. Width resets to default on page refresh (no localStorage persistence).

Why: The sidebars had fixed widths (300px left, 320px right). Users needed the ability to widen them for more content visibility, or narrow them for more center space, with a sensible maximum to prevent collapsing the main content area.

What changed:

  • New useResizable hook (web/src/hooks/useResizable.ts) — handles mousedown/mousemove/mouseup drag logic, clamps width to [min, max], sets body { cursor: col-resize; userSelect: none } during active drag, cleans up on mouseup. Width stored in useState (resets on refresh, no localStorage).
  • New ResizeHandle component (web/src/components/shared/ResizeHandle.tsx) — thin 4px vertical bar, cursor-col-resize, hover:bg-accent-primary/30, role=separator for a11y. Absolute-positioned at the sidebar edge.
  • Sidebar.tsx (left railbar) — uses CSS variable --sidebar-w consumed only by the desktop class md:w-[var(--sidebar-w)]; mobile w-[300px] remains untouched. Handle placed at right edge, hidden md:block.
  • SessionLayout.tsx (right railbar) — inline style={{ width }} on desktop aside (which is hidden md:block; mobile has a separate aside). Handle placed at left edge.

Constraints:

Sidebar Initial Min Max
Left 300px 200px 600px
Right 320px 240px 600px

Testing:

  • 7 new unit tests for useResizable hook (initial width, drag direction left/right, clamp min/max, mouseup cleanup, body style restoration)
  • All 3096 existing unit tests pass
  • npm run typecheck — pass
  • ESLint — clean
  • Manual visual testing on dev server pending (drag requires real DOM, not covered by happy-dom)

AI-Enhanced Development

Tell what models helped shape this PR:

  • AI Models: GLM-5.2

Cache Impact

Does this PR affect anything cached — system prompts, tool definitions, skills, or other context?

  • No

co-l and others added 29 commits August 1, 2026 14:17
…h cmd/PowerShell

RTK only rewrites Unix-style commands and some rewrites exec Unix
binaries absent from default Windows PATH. Show a contextual hint
under the RTK toggle recommending Git Bash, the one shell where the
integration works deterministically. Windows-only render, no server
change.
describeImage already sets an Authorization header as soon as the vision
backend is openai and an api key is present. The only way to get a key in
there was to pick a model from a configured provider, because
resolveVisionFallback forwards apiKey in the providerModelRef branch and
nowhere else.

The manual branch is what the onboarding form falls back to when no
provider exposes a vision capable model. Pointing it at a hosted endpoint
sent the request with no Authorization header and got a 401 back, and the
form had no field for the key anyway.

This adds an optional apiKey to the vision fallback config, uses it in the
manual branch, and shows a password input in the onboarding step when the
backend is openai. Ollama does not use it so the field stays hidden there.

An empty field behaves exactly like today, and providerModelRef still wins
when it resolves, so a provider key can never be shadowed by a manual one.

VisionModelConfigSectionProps now extends ManualVisionConfigProps instead
of repeating its fields, which keeps jscpd happy now that the list is one
line longer.
The precommit typecheck never covered e2e/, so ~280 type errors in the
e2e suite slipped verification — masked at runtime because all
@openfox/shared imports are type-only (stripped by esbuild).

- Wire `tsc --noEmit -p e2e/tsconfig.json` into `npm run typecheck`
- Map @openfox/shared to src via paths in e2e/tsconfig.json
- Widen ws-client.send type param (test harness sends non-protocol msgs)
- Align ChatResponse with ChatDonePayload; annotate res.json() results
- Use bracket notation for index-signature access in mock-mcp-server
- Drop stale disableThinking config (replaced by reasoningEffort)
- Deduplicate Session type in session-pool (use shared type)
Fresh setups (OpenFox workspaces, CI, fresh clones) only ran npm install
at the root, never in the web/ sub-project — so web/node_modules was
absent and web typecheck failed on unresolvable imports like
overlayscrollbars-react. Root postinstall now installs web deps too.

Lockfile gains hasInstallScript to mark the lifecycle script.
…ch, and permission checks on win32

* fix: make open-folder work on Windows

openFolder() ran execFile('mkdir', ['-p', dir]) which fails with ENOENT on
Windows (mkdir is a cmd.exe builtin, not an executable), so the folder icon
in the header and the plugin "Open folder" links always returned 500.

Use fs.mkdir(dir, { recursive: true }) instead. Also tolerate explorer.exe's
exit code 1, which it returns even when the folder opens successfully, so
the route no longer reports a spurious failure on win32.

* fix: detect e2e cwd with backslash separators in test auth path

getAuthConfigPath() checked cwd.endsWith('/e2e'), which never matches on
Windows where cwd uses backslashes, so the test-mode auth config resolved
to cwd/e2e/e2e/... when the server was started from the e2e directory.

Compare basename(cwd) === 'e2e' instead, which is separator-agnostic.

* fix: support backslash directory queries in file search

A query ending with a separator lists the directory contents, but only '/'
was recognized. Windows users naturally type 'src\' and got useless fuzzy
matches on the literal string instead.

Treat a trailing backslash like a trailing slash and normalize '\' to '/'
before building the glob pattern (fast-glob patterns are always POSIX).

* fix: delete workspaces with fs.rm instead of spawning rm -rf

deleteWorkspace() shelled out to execFileSync('rm', ['-rf', path]), which
does not exist on stock Windows (ENOENT) and only worked there through the
fs.rm fallback in the catch block.

Call fs.rm({ recursive, force, maxRetries: 3 }) directly on all platforms:
one code path, no process spawn, and maxRetries absorbs transient Windows
file locks (antivirus/indexer). Tests updated accordingly ÔÇö the shell
injection cases now assert that no child process is spawned at all.

* fix: skip Unix permission management on Windows

checkPermissions()/fixPermissions() are built on id/sudo/getent/chmod and
crash or return nonsense on Windows (id: ENOENT when the server is not
started from Git Bash). Return a clear 501 early instead so the
PermissionDeniedModal gets a deterministic answer.

Also skip the dead 'sudo -u $USER git init' fallback in
createDirectoryWithGit() on win32 ÔÇö sudo does not exist there, the attempt
always failed and only delayed the real error.
… server workdir

Skills defined in project directories (.agents/skills/, .openfox/skills/)
were not visible to agents because runtimeConfig.workdir (the global server
working directory) was used instead of session.workdir (the actual project).

Changes:
- agent-loop, orchestrator, dynamic-context, sub-agents/manager: use
  session.workdir/effectiveWorkdir for getEnabledSkillMetadata
- load-skill tool: use context.workdir from ToolContext
- API /api/skills: accept optional ?workdir= query param with fallback
  to server projectDir; extracted shared resolveProjectDir helper
- Frontend skills store: pass currentSession.workdir via ?workdir=
- Tests: added coverage for all modified paths
* fix(workflows): guard execution git context

Add SessionManager.assertExecutionGitContext and an early gate in
executeWorkflow that runs before any agent or shell step. When the
session branch and the actual git branch on the effective workdir
differ, Dev & Verify is blocked with a clear reason and no write is
performed. Non-git projects and sessions without an explicit branch
expectation are not blocked.

Resolves co-l#183.

* fix(context): refresh agent state after workspace mutation

Add SessionManager.clearCachedPrompt (and the matching
clearSessionCachedPrompt DB helper) and call it from switchWorkspace
after the new workdir/branch has been read authoritatively. Any cached
system prompt that still references the previous workdir is now
invalidated, so the same-turn continuation AND the next user turn
rebuild the prompt against the current workspace/branch.

No fake refreshed context is produced on failure: switchWorkspace only
reaches the invalidation point after the workspace mutation has
completed successfully.

Refs co-l#190.

* fix(workflows): fail closed when expected branch is unavailable

SessionManager.assertExecutionGitContext() previously returned ok when
the session had an expected branch but the actual branch could not be
resolved (detached HEAD, broken workdir, missing .git). This was a
fail-open bug: Dev & Verify could start on a tree whose branch could
not be verified at all.

Tighten the decision matrix:
- expectedBranch null                 → ok (any actualBranch)
- expectedBranch set, actualBranch set, equal    → ok
- expectedBranch set, actualBranch set, differ   → BLOCKED
- expectedBranch set, actualBranch null          → BLOCKED (new, fail-closed)

The BLOCKED reason includes the workdir, the expected branch and an
explicit statement that no agent, no checkout and no file were
performed.

---------

Co-authored-by: theshwal <theshwal@users.noreply.github.com>
…kflow scoping

- User steps now present interactive choices from step_result transitions;
  resuming with userChoice routes via the existing transition evaluator.
- pendingChoices persisted (workflow_executions.pending_choices) for reload
  parity, broadcast on workflow.execution_changed, cleared on resume/terminal.
- Editor hints/warnings for choice transitions (duplicate, always-shadowing,
  reserved 'continue').
- Project workflows scoped per-session: server honors ?workdir=, workflow
  store/modal thread project context so list, save, and launch agree.
- Launch picker includes project-scoped workflows.
- Demo: review.workflow.json branches approve_fixes (apply/skip);
  adds sandbox.workflow.json playground.
- Tests: executor choice routing, messageHandler pendingChoices, migrations,
  crud resolveProjectDir, e2e user-choice resume.
Wire RunCommandView to useAutoScroll (dropping the dead outputRef effect
left by the overlayscrollbars migration), stabilize the hook's exported
handlers so effects don't re-fire on every render (fixes not being able
to escape follow), settle completed output at the tail on transition and
fresh mount, and teach the overlayscrollbars test mock to expose the
OS viewport so the behavior is testable.
… switch

The cached prompt is sacred for local LLMs — never discard it. switchWorkspace
no longer clears it; the new workspace/branch is conveyed via the injected
system reminder instead, and the base prompt tells the model to trust the
latest reminder over the static Working directory line.
The planner now presents criteria and stops, waiting for a fresh
<system-reminder> (user- or workflow-triggered) before writing, instead of
soliciting an approval answer that cannot switch modes.
The postinstall guard cd'ed into web/ whenever web/node_modules was absent,
but the published tarball ships no web/ directory at all, so 'npm i -g openfox'
died with 'cd: can't cd to web'. Gate the auto-install on the web sub-project
actually being present (source checkout) via web/package.json.
@ikarys

ikarys commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

Duplicate — created on upstream instead: co-l#204

@ikarys ikarys closed this Aug 2, 2026
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.

7 participants