Skip to content

feat(app): directory autocomplete for new session path picker#1077

Open
chphch wants to merge 2 commits into
slopus:mainfrom
chphch:feat/44-filepath-autocomplete
Open

feat(app): directory autocomplete for new session path picker#1077
chphch wants to merge 2 commits into
slopus:mainfrom
chphch:feat/44-filepath-autocomplete

Conversation

@chphch
Copy link
Copy Markdown
Contributor

@chphch chphch commented Apr 15, 2026

Closes #44.

What this adds: Live subdirectory autocomplete in the new-session "Project path" picker. As soon as the user types a path (e.g. /home/user/proj) the picker fetches matching subdirs from the selected machine via machineBash (ls -1ap) and shows them above the existing "Recent" list; tapping a suggestion appends / so the user can keep drilling down. Debounced 250ms, per-machine cache (10s TTL), no network or disk hit when no machine is selected or the field is empty.

Proof

Screencast of typing a partial path → suggestions appear → tap → drill down: video pending — will attach before requesting human review per CONTRIBUTING.md.

How it works

User types: /home/user/proj
        ↓
parentDir = /home/user/   prefix = proj
        ↓
machineBash: ls -1ap /home/user/ | grep '/$' | grep -v '^\.\.?/$'
        ↓
Filter by prefix → ["projects/", "project-alpha/"]
        ↓
Show as suggestions; tap → sets value to /home/user/projects/

Scope

  • 3 files, +174 / -0 — pure addition, no existing behaviour changes when the field is empty or no machine is selected.
  • No new dependencies.

@chphch chphch force-pushed the feat/44-filepath-autocomplete branch from d436bd1 to 52fd08f Compare April 26, 2026 07:00
@chphch
Copy link
Copy Markdown
Contributor Author

chphch commented Apr 26, 2026

Proof — directory autocomplete drills correctly

Captured locally via standalone happy-server (PGlite, port 3055) + Expo web (port 8088), authenticated through getDevWebQueryCredentials() dev URL bypass, driven headlessly with Playwright.

useDirSuggestions calls out to machineBash(machineId, 'ls -1ap "<dir>"', dir) — which requires a connected happy-cli daemon. To capture the UI flow without bringing up a CLI, the hook's fetchDirs was tap-injected at the window.__HAPPY_DIR_MOCK__ boundary so a deterministic mock filesystem stands in for the real ls output. No production code path changed; the mock is removed after capture.

directory autocomplete

The GIF cycles three states:

  1. /home/user/proj typed → Suggestions: /home/user/projects, /home/user/project-alpha, /home/user/project-beta (filter by proj prefix).
  2. Tap a suggestion → path appended with trailing /, drilled into the selected directory.
  3. /home/user/projects/happy typed → Suggestions: /home/user/projects/happy, /home/user/projects/happy-cli, /home/user/projects/happy-server (filter by happy prefix at the new depth).

Mock filesystem (substitutes for real ls output):

{
  '/home/user/':            ['projects', 'project-alpha', 'project-beta', 'photos', 'docs'],
  '/home/user/projects/':   ['happy', 'happy-cli', 'happy-server', 'web-app', 'mobile-app'],
}

Production behaviour matches: with a real machine connected, useDirSuggestions calls machineBash with the same ls -1ap | grep '/$' | grep -v '^\.\.\?/$' pipeline, debounced 250 ms with a 10 s per-(machineId, parentDir) cache, and feeds the same prefix-filter into the same <Suggestions> UI rendered above the existing Recent list.

Capture details

@chphch chphch force-pushed the feat/44-filepath-autocomplete branch 3 times, most recently from 0f1bee5 to e194376 Compare May 3, 2026 18:00
@chphch chphch force-pushed the feat/44-filepath-autocomplete branch from e194376 to 764c9c7 Compare May 8, 2026 18:00
When a user types a project path in the new session screen, the path
picker now shows filesystem directory suggestions fetched from the
selected machine via `machineBash`. Suggestions are debounced (250ms),
cached per (machineId, parentDir) pair for 10s, and filtered by the
current typed prefix. Tapping a suggestion appends `/` so the user can
keep drilling down.

New hook: `useDirSuggestions(machineId, pathText)`
Closes: slopus#44

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
@chphch chphch force-pushed the feat/44-filepath-autocomplete branch from 764c9c7 to 6fe4fc0 Compare May 15, 2026 18:00
useDirSuggestions previously shipped the user's typed parent directory
straight into `ls -1ap "${dir}"`. When the path picker is on its default
state (paths formatted relative to home, e.g. `~/proj`), the parent
ends up as `~/` and bash will not expand `~` inside double quotes — so
the ls silently produces no output and the suggestions list is always
empty, even though `Recent` continues to work.

Resolve the parent against the selected machine's `homeDir` before
calling `machineBash`. The user-typed parent is preserved in the
returned `fullPath` so drill-down still reads as `~/projects/happy/`
rather than the absolute form.

The pure split/resolve helper is extracted into
`useDirSuggestions.utils.ts` so the regression case can be covered by
plain vitest without dragging React + sync-ops into the test env.
11 unit tests added (tilde, absolute, relative, missing-homeDir,
Windows separator).

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
@chphch
Copy link
Copy Markdown
Contributor Author

chphch commented May 17, 2026

Follow-up fix — ~-rooted paths now actually return suggestions

Real-world regression I caught while dogfooding: typing into the path field on the default state (which renders home-relative, e.g. ~/proj) produced an empty suggestions list, even though the Recent section worked. Root cause: useDirSuggestions shipped the user's typed parent directly into ls -1ap "${dir}", but bash does not expand ~ inside double quotes, so the ls silently returned nothing for any ~/... prefix.

The earlier proof GIF didn't catch this because the capture mocked fetchDirs against an absolute-path filesystem (/home/user/...), so the ~-expansion path was never exercised.

Fix (commit 4f35abbe)

  • useDirSuggestions(machineId, pathText, homeDir?) — accepts the selected machine's homeDir (already available on machine.metadata).
  • Parent dir is resolved through resolveAbsolutePath(parentDir, homeDir) before being passed to machineBash / used as the cwd.
  • The user-typed parent is preserved in the returned fullPath, so tapping ~/projects fills ~/projects/ (not /home/user/projects/) and drill-down stays in home-relative form.
  • Cache key switches from (machineId, parentDir)(machineId, resolvedParentDir) to avoid ~/ and /home/user/ competing for the same slot.

Tests

The pure split/resolve helper is extracted into useDirSuggestions.utils.ts so it can be unit-tested with plain vitest (the hook itself transitively imports react-native via @/sync/ops, which the test env can't parse). 11 cases added in useDirSuggestions.spec.ts:

  • Tilde paths: ~/proj, ~/projects/hap, ~/, bare ~
  • Absolute paths: /home/h, /usr/local/b, bare /
  • Relative / no-slash input
  • Missing homeDir (machine offline)
  • Windows-style homeDir separator
 ✓ sources/hooks/useDirSuggestions.spec.ts (11 tests) 5ms

Typecheck on the changed files is clean (pnpm typecheck against ~/happy with this branch's files dropped in).

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.

Add autocomplete for filepaths when creating new session

1 participant