Skip to content

Better first-run experience: smart project setup, fuzzy folder picker, optional git#555

Merged
bborn merged 17 commits into
mainfrom
feature/first-run-experience
Jun 5, 2026
Merged

Better first-run experience: smart project setup, fuzzy folder picker, optional git#555
bborn merged 17 commits into
mainfrom
feature/first-run-experience

Conversation

@bborn
Copy link
Copy Markdown
Owner

@bborn bborn commented Jun 5, 2026

Summary

Overhauls the first-time experience so ty no longer drops every user straight into a new-task form. On launch it runs a small decision tree:

  • Project-candidate folder (git repo or markers like package.json/go.mod, minus a deny-list of junk dirs like ~, Desktop, Downloads, /tmp) → an enriched "New Project Detected" card. Name/alias/description inferred with claude -p (reuses the user's Claude CLI auth via CLAUDE_CONFIG_DIRno API key needed), e.g. acme-rocket"Acme Rocket". Inference is async: the card appears instantly with rule-based values (basename + imported README) and enriches in place — no startup freeze.
  • Junk folder with only the auto-created personal project → a Welcome fork: [Set up a project] / [Just start a task].
  • "Set up a project" → a fuzzy folder picker (type-to-search, ~/Projects|src|code|dev|work/* seeded, git-tagged) → the same enriched card.
  • Otherwise → the board.

Design calls (confirmed with product)

  • Git is optional. Git repos default to worktrees on; non-git folders become plain non-worktree projects with no git init and no nag.
  • Nested/sub-git repos are fine — verified git worktree add succeeds with a nested repo present.
  • Never lose form input — all project-form validation errors re-render with every field preserved (including permission mode).

Files

  • internal/ui/project_detect.go — candidacy heuristic + inferred-metadata merge (never erases defaults)
  • internal/ai/project_infer.goclaude -p inference with tolerant JSON parsing + graceful degradation
  • internal/ui/folderpicker.go — fuzzy folder picker (reuses existing fuzzyScore, no new deps)
  • internal/ui/welcome.go — first-run Welcome fork
  • internal/ui/app.go — launch decision tree, async inference, suggestion-card upgrade, dup-path guard, resize propagation
  • internal/ui/settings.go — validation hardening
  • scripts/qa/ty-qa-firstrun.sh — scripted first-run QA scenario

Test Plan

  • go build ./... && go test ./internal/ui/... ./internal/ai/... green; go vet + gofmt clean
  • Unit tests: candidacy heuristic, inference JSON parse + degradation, metadata merge, fuzzy filter
  • Live (QA harness): git repo → enriched card; non-git package.json → card with Worktrees: false; plain folder → Welcome fork; fork → picker → pick → card; esc-back; "Just start a task" → new-task form on personal; non-git create leaves no .git; async card shows instantly then enriches
  • Nested sub-repo → worktree creation succeeds
  • Reviewer spot-check of async-inference UX on a cold claude start

🤖 Generated with Claude Code

bborn and others added 16 commits June 5, 2026 08:42
…eny-list)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n-git coverage

- Replace HasPrefix /tmp check with exact clean == "/tmp" to avoid wrongly blocking /tmpl-style paths
- Remove .git from projectMarkerFiles since dirIsGitRepo() already covers that signal
- Add TestDetectProjectFromDir_NonGitMarker: verifies go.mod-only dir yields a non-nil project with UseWorktrees=false, and empty dir yields nil

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…N parse

- Use CombinedOutput and include trimmed output in error messages for
  better failure diagnostics when claude -p inference fails.
- Add WaitDelay of 2s so I/O goroutines are torn down after context
  cancels the process (handles node child processes).
- Replace single-shot JSON unmarshal with forward scan from each '{' to
  last '}', so stray braces in prose no longer break parsing.
- Add stray-brace test case to TestParseInferenceJSON.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…, cache home

- descend() no longer returns tea.Cmd (always was nil, discarded at call site)
- m.selected clamped to len-1 after filter rather than zeroed, preserving valid cursor positions
- home dir cached as FolderPickerModel.home field; collapseHome converted to method to avoid per-row os.UserHomeDir() syscall

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… suggestion

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ead field

Gate maybeOfferProjectCreation on isProjectCandidate (git repo OR marker
files) instead of dirIsGitRepo, closing the seam where a non-git folder
with e.g. package.json/go.mod received neither the suggestion card nor the
welcome fork. Remove the now-dead onboardingShown struct field (its only
writer was removed earlier in this task).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Convert project-form validation early-returns from bare `m.err = ...; return m, nil`
to `return m.reshowProjectFormWithError(...)` so the form is re-rendered with all
user-typed values intact and an inline error, instead of losing form state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ew views, keep permission mode

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bborn
Copy link
Copy Markdown
Owner Author

bborn commented Jun 5, 2026

✅ Real-TUI QA — scripts/qa harness + VHS (isolated instance)

Driven against a throwaway isolated instance (fresh DB per scenario, WORKTREE_DB_PATH/WORKTREE_SESSION_ID namespacing). Screenshots are the real TUI rendered via VHS.

Walkthrough — Welcome fork → fuzzy folder picker

walkthrough

1. Plain / non-project folder → Welcome fork

No more dumping straight into a new-task form. You choose.
welcome fork

2. Git repo → enriched "New Project Detected" card

Name/alias/description inferred via claude -p (acme-rocket"Acme Rocket"); worktrees on for git repos. Inference is async — the card shows instantly and enriches in place.
git card

3. Non-git folder with a marker (package.json) → card with Worktrees: false

Git is optional: non-git folders become plain projects, no git init, LLM-written description.
non-git card

4. "Set up a project" → fuzzy folder picker

Type-to-search, ~/Projects/* seeded, git repos tagged.
folder picker


Also verified (non-visual): create on a non-git folder leaves no .git (use_worktrees=0); nested sub-repo → git worktree add succeeds; esc paths; "Just start a task" → new-task form on personal. Repro: scripts/qa/ty-qa-firstrun.sh.

…ence

- ty-qa-shoot.sh: render a ty TUI screen to PNG via VHS (correct terminal
  sizing; tmux capture mis-reports width and corrupts modals)
- ty-qa-publish.sh: upload shots to r2-personal:qa-evidence and print the
  PR-ready markdown image block
- README: document the up -> shoot -> publish flow
@bborn bborn merged commit 4be86a4 into main Jun 5, 2026
3 of 4 checks passed
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