77 canCreateProjectInEnvironment ,
88 findExistingAddProject ,
99 getAddProjectInitialQuery ,
10+ getCloneDestinationBrowsePath ,
11+ getCloneDestinationPath ,
12+ getCloneDirectoryName ,
1013 resolveAddProjectPath ,
1114 sortAddProjectProviderSources ,
1215 type AddProjectRemoteSource ,
@@ -23,8 +26,8 @@ import {
2326} from "@t3tools/client-runtime/state/filesystem" ;
2427import {
2528 appendBrowsePathSegment ,
26- ensureBrowseDirectoryPath ,
2729 inferProjectTitleFromPath ,
30+ isWindowsPlatform ,
2831} from "@t3tools/client-runtime/state/projects" ;
2932import { CommandId , type EnvironmentId , ProjectId } from "@t3tools/contracts" ;
3033import { CommonActions , StackActions , useNavigation } from "@react-navigation/native" ;
@@ -236,11 +239,18 @@ function ProjectPathInput(props: {
236239 ) ;
237240}
238241
239- function useBrowsePathInput ( environment : EnvironmentOption | null ) {
242+ // `pinnedDirectoryName` is the repository folder the clone destination keeps
243+ // appended to whatever folder the user browses to. The plain add-project flow
244+ // passes nothing, so it keeps proposing the browsed folder itself.
245+ function useBrowsePathInput ( environment : EnvironmentOption | null , pinnedDirectoryName = "" ) {
240246 const environmentId = environment ?. environmentId ?? null ;
241247 const environmentBaseDirectory = environment ?. baseDirectory ?? null ;
248+ const clonePathCaseSensitive = ! isWindowsPlatform ( environment ?. platform ?? "" ) ;
242249 const [ pathInput , commitPathInput ] = useState ( ( ) =>
243- getAddProjectInitialQuery ( environmentBaseDirectory ) ,
250+ getCloneDestinationPath (
251+ getAddProjectInitialQuery ( environmentBaseDirectory ) ,
252+ pinnedDirectoryName ,
253+ ) ,
244254 ) ;
245255 const previousEnvironmentIdRef = useRef ( environmentId ) ;
246256 const environmentRuntime = useRemoteEnvironmentRuntime ( environmentId ) ;
@@ -259,33 +269,60 @@ function useBrowsePathInput(environment: EnvironmentOption | null) {
259269 [ browseNavigation ] ,
260270 ) ;
261271 const navigateToBrowsePath = useCallback (
262- async ( path : string ) => {
272+ async ( input : {
273+ readonly browseDirectoryPath : string ;
274+ readonly selectedDirectoryName ?: string ;
275+ } ) => {
276+ const selectedDirectoryPath = input . selectedDirectoryName
277+ ? appendBrowsePathSegment ( input . browseDirectoryPath , input . selectedDirectoryName )
278+ : input . browseDirectoryPath ;
279+ const nextPathInput =
280+ pinnedDirectoryName && input . selectedDirectoryName
281+ ? getCloneDestinationBrowsePath ( {
282+ browseDirectoryPath : input . browseDirectoryPath ,
283+ selectedDirectoryName : input . selectedDirectoryName ,
284+ cloneDirectoryName : pinnedDirectoryName ,
285+ caseSensitive : clonePathCaseSensitive ,
286+ } )
287+ : getCloneDestinationPath ( selectedDirectoryPath , pinnedDirectoryName ) ;
263288 setIsBrowseNavigating ( true ) ;
264289 const committed = await browseNavigation . run (
265290 async ( ) => {
266291 if ( environment && canPreloadBrowsePath ( environmentRuntime ?. connectionState ) ) {
267292 await loadBrowsePath ( {
268293 environmentId : environment . environmentId ,
269- input : { partialPath : path } ,
294+ input : { partialPath : selectedDirectoryPath } ,
270295 } ) ;
271296 }
272297 } ,
273- ( ) => commitPathInput ( path ) ,
298+ ( ) => commitPathInput ( nextPathInput ) ,
274299 ) ;
275300 if ( committed ) {
276301 setIsBrowseNavigating ( false ) ;
277302 }
278303 return committed ;
279304 } ,
280- [ browseNavigation , environment , environmentRuntime ?. connectionState , loadBrowsePath ] ,
305+ [
306+ browseNavigation ,
307+ clonePathCaseSensitive ,
308+ environment ,
309+ environmentRuntime ?. connectionState ,
310+ loadBrowsePath ,
311+ pinnedDirectoryName ,
312+ ] ,
281313 ) ;
282314
283315 useEffect ( ( ) => {
284316 if ( environmentId !== null && environmentId !== previousEnvironmentIdRef . current ) {
285317 previousEnvironmentIdRef . current = environmentId ;
286- setPathInput ( getAddProjectInitialQuery ( environmentBaseDirectory ) ) ;
318+ setPathInput (
319+ getCloneDestinationPath (
320+ getAddProjectInitialQuery ( environmentBaseDirectory ) ,
321+ pinnedDirectoryName ,
322+ ) ,
323+ ) ;
287324 }
288- } , [ environmentBaseDirectory , environmentId , setPathInput ] ) ;
325+ } , [ environmentBaseDirectory , environmentId , pinnedDirectoryName , setPathInput ] ) ;
289326
290327 useEffect (
291328 ( ) => ( ) => {
@@ -632,6 +669,7 @@ export function AddProjectRepositoryScreen(props: {
632669 source,
633670 remoteUrl,
634671 repositoryTitle : remoteUrl ,
672+ repositoryName : getCloneDirectoryName ( remoteUrl ) ,
635673 } ) ,
636674 ) ;
637675 setIsSubmitting ( false ) ;
@@ -655,6 +693,7 @@ export function AddProjectRepositoryScreen(props: {
655693 source,
656694 remoteUrl : repository . sshUrl ,
657695 repositoryTitle : repository . nameWithOwner ,
696+ repositoryName : getCloneDirectoryName ( repository . nameWithOwner ) ,
658697 } ) ,
659698 ) ;
660699 }
@@ -698,7 +737,11 @@ function FolderBrowser(props: {
698737 readonly environment : EnvironmentOption ;
699738 readonly pathInput : string ;
700739 readonly setPathInput : ( path : string ) => void ;
701- readonly navigateToBrowsePath : ( path : string ) => Promise < boolean > ;
740+ readonly navigateToBrowsePath : ( input : {
741+ readonly browseDirectoryPath : string ;
742+ readonly selectedDirectoryName ?: string ;
743+ } ) => Promise < boolean > ;
744+ readonly pinnedDirectoryName ?: string ;
702745} ) {
703746 const accentColor = useThemeColor ( "--color-icon-muted" ) ;
704747 const browsePath = useMemo (
@@ -717,9 +760,16 @@ function FolderBrowser(props: {
717760 input : browseInput ,
718761 } ) ,
719762 ) ;
763+ // A pinned repository folder does not exist yet, so filtering the listing by
764+ // it would empty the folder picker. Anything the user typed still filters.
765+ const pinnedDirectoryName = props . pinnedDirectoryName ?? "" ;
766+ const pinnedDirectoryMatches = isWindowsPlatform ( props . environment . platform )
767+ ? browsePath . filterQuery . toLowerCase ( ) === pinnedDirectoryName . toLowerCase ( )
768+ : browsePath . filterQuery === pinnedDirectoryName ;
769+ const browseFilterQuery = pinnedDirectoryMatches ? "" : browsePath . filterQuery ;
720770 const { visibleEntries : visibleBrowseEntries } = useMemo (
721- ( ) => filterFilesystemBrowseEntries ( browseState . data ?. entries ?? [ ] , browsePath . filterQuery ) ,
722- [ browsePath . filterQuery , browseState . data ?. entries ] ,
771+ ( ) => filterFilesystemBrowseEntries ( browseState . data ?. entries ?? [ ] , browseFilterQuery ) ,
772+ [ browseFilterQuery , browseState . data ?. entries ] ,
723773 ) ;
724774
725775 return (
@@ -747,7 +797,9 @@ function FolderBrowser(props: {
747797 right = { null }
748798 onPress = { ( ) => {
749799 if ( browsePath . parentPath ) {
750- void props . navigateToBrowsePath ( browsePath . parentPath ) ;
800+ void props . navigateToBrowsePath ( {
801+ browseDirectoryPath : browsePath . parentPath ,
802+ } ) ;
751803 }
752804 } }
753805 />
@@ -760,11 +812,10 @@ function FolderBrowser(props: {
760812 isFirst = { index === 0 && ! browsePath . canBrowseUp }
761813 right = { null }
762814 onPress = { ( ) => {
763- const nextPath =
764- browsePath . directoryPath . length > 0
765- ? appendBrowsePathSegment ( browsePath . directoryPath , entry . name )
766- : ensureBrowseDirectoryPath ( entry . fullPath ) ;
767- void props . navigateToBrowsePath ( nextPath ) ;
815+ void props . navigateToBrowsePath ( {
816+ browseDirectoryPath : browsePath . directoryPath ,
817+ selectedDirectoryName : entry . name ,
818+ } ) ;
768819 } }
769820 />
770821 ) ) }
@@ -836,6 +887,7 @@ export function AddProjectDestinationScreen(props: {
836887 readonly environmentId ?: string | string [ ] ;
837888 readonly remoteUrl ?: string | string [ ] ;
838889 readonly repositoryTitle ?: string | string [ ] ;
890+ readonly repositoryName ?: string | string [ ] ;
839891} ) {
840892 const cloneRepository = useAtomCommand ( sourceControlEnvironment . cloneRepository , {
841893 reportFailure : false ,
@@ -844,8 +896,15 @@ export function AddProjectDestinationScreen(props: {
844896 const createProject = useCreateProject ( environment ) ;
845897 const remoteUrl = stringParam ( props . remoteUrl ) ;
846898 const repositoryTitle = stringParam ( props . repositoryTitle ) ;
847- const { isBrowseNavigating, navigateToBrowsePath, pathInput, setPathInput } =
848- useBrowsePathInput ( environment ) ;
899+ // A lookup derives this from "owner/repo", a pasted clone URL from its own
900+ // last segment. Older links without the param keep the browsed folder.
901+ // Trim once here: the path input and the folder-list filter must compare the
902+ // same value, or a deep link with a padded param empties the folder picker.
903+ const repositoryName = stringParam ( props . repositoryName ) ?. trim ( ) ?? "" ;
904+ const { isBrowseNavigating, navigateToBrowsePath, pathInput, setPathInput } = useBrowsePathInput (
905+ environment ,
906+ repositoryName ,
907+ ) ;
849908 const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
850909 const [ error , setError ] = useState < string | null > ( null ) ;
851910
@@ -918,6 +977,7 @@ export function AddProjectDestinationScreen(props: {
918977 navigateToBrowsePath = { navigateToBrowsePath }
919978 pathInput = { pathInput }
920979 setPathInput = { setPathInput }
980+ pinnedDirectoryName = { repositoryName }
921981 />
922982 </ >
923983 ) : (
0 commit comments